From ac675d0099cc40414144101db295dcee24863f0f Mon Sep 17 00:00:00 2001 From: c9s Date: Thu, 10 Mar 2022 19:00:02 +0800 Subject: [PATCH] add position table and service --- .../mysql/20220307132917_add_positions.sql | 3 +- .../sqlite3/20220307132917_add_positions.sql | 21 +++-- pkg/service/position.go | 92 +++++++++++++++++++ pkg/service/position_test.go | 34 +++++++ pkg/types/position.go | 30 +++--- 5 files changed, 155 insertions(+), 25 deletions(-) create mode 100644 pkg/service/position.go create mode 100644 pkg/service/position_test.go diff --git a/migrations/mysql/20220307132917_add_positions.sql b/migrations/mysql/20220307132917_add_positions.sql index 2431cef31..38b5b45b2 100644 --- a/migrations/mysql/20220307132917_add_positions.sql +++ b/migrations/mysql/20220307132917_add_positions.sql @@ -6,7 +6,7 @@ CREATE TABLE `positions` `strategy` VARCHAR(32) NOT NULL, `strategy_instance_id` VARCHAR(64) NOT NULL, - `symbol` VARCHAR(20) NOT NULL, + `symbol` VARCHAR(20) NOT NULL, `quote_currency` VARCHAR(10) NOT NULL, `base_currency` VARCHAR(10) NOT NULL, @@ -14,6 +14,7 @@ CREATE TABLE `positions` `average_cost` DECIMAL(16, 8) UNSIGNED NOT NULL, `base` DECIMAL(16, 8) NOT NULL, `quote` DECIMAL(16, 8) NOT NULL, + `profit` DECIMAL(16, 8) NULL, `trade_id` BIGINT UNSIGNED NOT NULL, `traded_at` DATETIME(3) NOT NULL, diff --git a/migrations/sqlite3/20220307132917_add_positions.sql b/migrations/sqlite3/20220307132917_add_positions.sql index 21a6521a9..8ffb43a49 100644 --- a/migrations/sqlite3/20220307132917_add_positions.sql +++ b/migrations/sqlite3/20220307132917_add_positions.sql @@ -3,20 +3,21 @@ CREATE TABLE `positions` ( `gid` INTEGER PRIMARY KEY AUTOINCREMENT, - `strategy` VARCHAR(32) NOT NULL, - `strategy_instance_id` VARCHAR(64) NOT NULL, + `strategy` VARCHAR(32) NOT NULL, + `strategy_instance_id` VARCHAR(64) NOT NULL, - `symbol` VARCHAR(20) NOT NULL, - `quote_currency` VARCHAR(10) NOT NULL, - `base_currency` VARCHAR(10) NOT NULL, + `symbol` VARCHAR(20) NOT NULL, + `quote_currency` VARCHAR(10) NOT NULL, + `base_currency` VARCHAR(10) NOT NULL, -- average_cost is the position average cost - `average_cost` DECIMAL(16, 8) NOT NULL, - `base` DECIMAL(16, 8) NOT NULL, - `quote` DECIMAL(16, 8) NOT NULL, + `average_cost` DECIMAL(16, 8) NOT NULL, + `base` DECIMAL(16, 8) NOT NULL, + `quote` DECIMAL(16, 8) NOT NULL, + `profit` DECIMAL(16, 8) NULL, - `trade_id` BIGINT NOT NULL, - `traded_at` DATETIME(3) NOT NULL + `trade_id` BIGINT NOT NULL, + `traded_at` DATETIME(3) NOT NULL ); -- +down diff --git a/pkg/service/position.go b/pkg/service/position.go new file mode 100644 index 000000000..30e02ffb7 --- /dev/null +++ b/pkg/service/position.go @@ -0,0 +1,92 @@ +package service + +import ( + "context" + + "github.com/jmoiron/sqlx" + "github.com/pkg/errors" + + "github.com/c9s/bbgo/pkg/fixedpoint" + "github.com/c9s/bbgo/pkg/types" +) + +type PositionService struct { + DB *sqlx.DB +} + +func NewPositionService(db *sqlx.DB) *PositionService { + return &PositionService{db} +} + +func (s *PositionService) Load(ctx context.Context, id int64) (*types.Position, error) { + var pos types.Position + + rows, err := s.DB.NamedQuery("SELECT * FROM positions WHERE id = :id", map[string]interface{}{ + "id": id, + }) + if err != nil { + return nil, err + } + + defer rows.Close() + + if rows.Next() { + err = rows.StructScan(&pos) + return &pos, err + } + + return nil, errors.Wrapf(ErrTradeNotFound, "position id:%d not found", id) +} + +func (s *PositionService) scanRows(rows *sqlx.Rows) (positions []types.Position, err error) { + for rows.Next() { + var p types.Position + if err := rows.StructScan(&p); err != nil { + return positions, err + } + + positions = append(positions, p) + } + + return positions, rows.Err() +} + +func (s *PositionService) Insert(position *types.Position, trade types.Trade, profit fixedpoint.Value) error { + _, err := s.DB.NamedExec(` + INSERT INTO positions ( + strategy, + strategy_instance_id, + symbol, + quote_currency, + base_currency, + average_cost, + base, + quote, + profit, + trade_id, + traded_at + ) VALUES ( + :strategy, + :strategy_instance_id, + :symbol, + :quote_currency, + :base_currency, + :average_cost, + :base, + :quote, + :profit, + :trade_id, + :traded_at + )`, + map[string]interface{} { + "strategy": "", + "strategy_instance_id": "", + "symbol": position.Symbol, + "quote_currency": position.QuoteCurrency, + "base_currency": position.BaseCurrency, + "average_cost": position.AverageCost, + "base": position.Base, + "quote": position.Quote, + }) + return err +} diff --git a/pkg/service/position_test.go b/pkg/service/position_test.go new file mode 100644 index 000000000..072a2ddd8 --- /dev/null +++ b/pkg/service/position_test.go @@ -0,0 +1,34 @@ +package service + +import ( + "testing" + "time" + + "github.com/jmoiron/sqlx" + "github.com/stretchr/testify/assert" + + "github.com/c9s/bbgo/pkg/fixedpoint" + "github.com/c9s/bbgo/pkg/types" +) + +func TestPositionService(t *testing.T) { + db, err := prepareDB(t) + if err != nil { + t.Fatal(err) + } + + defer db.Close() + + xdb := sqlx.NewDb(db.DB, "sqlite3") + service := &PositionService{DB: xdb} + + err = service.Insert(&types.Position{ + Symbol: "BTCUSDT", + BaseCurrency: "BTC", + QuoteCurrency: "USDT", + AverageCost: fixedpoint.NewFromFloat(44000), + ChangedAt: time.Now(), + }, types.Trade{}, fixedpoint.NewFromFloat(10.9)) + assert.NoError(t, err) + +} diff --git a/pkg/types/position.go b/pkg/types/position.go index 68c8429ee..dd0d96625 100644 --- a/pkg/types/position.go +++ b/pkg/types/position.go @@ -50,6 +50,8 @@ type Position struct { // TotalFee stores the fee currency -> total fee quantity TotalFee map[string]fixedpoint.Value `json:"totalFee"` + ChangedAt time.Time `json:"changedAt,omitempty"` + sync.Mutex } @@ -66,22 +68,22 @@ func (p *Position) NewProfit(trade Trade, profit, netProfit fixedpoint.Value) Pr ProfitMargin: profit.Div(trade.QuoteQuantity), NetProfitMargin: netProfit.Div(trade.QuoteQuantity), // trade related fields - TradeID: trade.ID, - Side: trade.Side, - IsBuyer: trade.IsBuyer, - IsMaker: trade.IsMaker, - Price: trade.Price, - Quantity: trade.Quantity, - QuoteQuantity: trade.QuoteQuantity, + TradeID: trade.ID, + Side: trade.Side, + IsBuyer: trade.IsBuyer, + IsMaker: trade.IsMaker, + Price: trade.Price, + Quantity: trade.Quantity, + QuoteQuantity: trade.QuoteQuantity, // FeeInUSD: 0, - Fee: trade.Fee, - FeeCurrency: trade.FeeCurrency, + Fee: trade.Fee, + FeeCurrency: trade.FeeCurrency, - Exchange: trade.Exchange, - IsMargin: trade.IsMargin, - IsFutures: trade.IsFutures, - IsIsolated: trade.IsIsolated, - TradedAt: trade.Time.Time(), + Exchange: trade.Exchange, + IsMargin: trade.IsMargin, + IsFutures: trade.IsFutures, + IsIsolated: trade.IsIsolated, + TradedAt: trade.Time.Time(), } }