From dad5738f0b3bdbe106e12eb41bd00ce5768597fd Mon Sep 17 00:00:00 2001 From: c9s Date: Mon, 15 Feb 2021 20:30:53 +0800 Subject: [PATCH 01/22] update readme for adding new migration --- README.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/README.md b/README.md index 6d1b6e252..55972d2e0 100644 --- a/README.md +++ b/README.md @@ -283,6 +283,15 @@ Delete chart: helm delete bbgo ``` +## Development + +### Adding new migration + +```sh +rockhopper --config rockhopper_sqlite.yaml create --type sql add_pnl_column +rockhopper --config rockhopper_mysql.yaml create --type sql add_pnl_column +``` + ## Support ### By contributing pull requests From 7d243e60263bdaa32661dc964a7162bf4bc7705b Mon Sep 17 00:00:00 2001 From: c9s Date: Mon, 15 Feb 2021 20:35:27 +0800 Subject: [PATCH 02/22] add strategy column --- .../mysql/20210215203116_add_pnl_column.sql | 19 +++++++++++++++++++ .../sqlite3/20210215203111_add_pnl_column.sql | 18 ++++++++++++++++++ 2 files changed, 37 insertions(+) create mode 100644 migrations/mysql/20210215203116_add_pnl_column.sql create mode 100644 migrations/sqlite3/20210215203111_add_pnl_column.sql diff --git a/migrations/mysql/20210215203116_add_pnl_column.sql b/migrations/mysql/20210215203116_add_pnl_column.sql new file mode 100644 index 000000000..869db285c --- /dev/null +++ b/migrations/mysql/20210215203116_add_pnl_column.sql @@ -0,0 +1,19 @@ +-- +up +-- +begin +ALTER TABLE `trades` ADD COLUMN `pnl` DECIMAL NULL; +-- +end + +-- +begin +ALTER TABLE `trades` ADD COLUMN `strategy` VARCHAR(32) NULL; +-- +end + + +-- +down + +-- +begin +ALTER TABLE `trades` DROP COLUMN `pnl`; +-- +end + +-- +begin +ALTER TABLE `trades` DROP COLUMN `strategy`; +-- +end diff --git a/migrations/sqlite3/20210215203111_add_pnl_column.sql b/migrations/sqlite3/20210215203111_add_pnl_column.sql new file mode 100644 index 000000000..9e6e2e2fa --- /dev/null +++ b/migrations/sqlite3/20210215203111_add_pnl_column.sql @@ -0,0 +1,18 @@ +-- +up +-- +begin +ALTER TABLE `trades` ADD COLUMN `pnl` DECIMAL NULL; +-- +end + +-- +begin +ALTER TABLE `trades` ADD COLUMN `strategy` TEXT; +-- +end + +-- +down + +-- +begin +ALTER TABLE `trades` RENAME COLUMN `pnl` TO `pnl_deleted`; +-- +end + +-- +begin +ALTER TABLE `trades` RENAME COLUMN `strategy` TO `strategy_deleted`; +-- +end From 777d673b14dfbf562bc2932262a1b26266bc24e7 Mon Sep 17 00:00:00 2001 From: c9s Date: Mon, 15 Feb 2021 20:45:32 +0800 Subject: [PATCH 03/22] add script for testing mysql migrations --- .travis.yml | 1 + scripts/test-mysql-migrations.sh | 4 ++++ scripts/test-sqlite3-migrations.sh | 5 ++++- 3 files changed, 9 insertions(+), 1 deletion(-) create mode 100644 scripts/test-mysql-migrations.sh diff --git a/.travis.yml b/.travis.yml index 427b4b326..25604dbb5 100644 --- a/.travis.yml +++ b/.travis.yml @@ -20,4 +20,5 @@ before_script: script: - bash scripts/test-sqlite3-migrations.sh +- bash scripts/test-mysql-migrations.sh - go test -v ./pkg/... diff --git a/scripts/test-mysql-migrations.sh b/scripts/test-mysql-migrations.sh new file mode 100644 index 000000000..9f543bf68 --- /dev/null +++ b/scripts/test-mysql-migrations.sh @@ -0,0 +1,4 @@ +#!/bin/bash +set -e +rockhopper --config rockhopper_mysql.yaml up +rockhopper --config rockhopper_mysql.yaml down --to 1 diff --git a/scripts/test-sqlite3-migrations.sh b/scripts/test-sqlite3-migrations.sh index c552aed86..b101d0f69 100755 --- a/scripts/test-sqlite3-migrations.sh +++ b/scripts/test-sqlite3-migrations.sh @@ -1,2 +1,5 @@ #!/bin/bash -rm -fv bbgo.sqlite3 && rockhopper --config rockhopper_sqlite.yaml up && rockhopper --config rockhopper_sqlite.yaml down --to 1 +set -e +rm -fv bbgo.sqlite3 +rockhopper --config rockhopper_sqlite.yaml up +rockhopper --config rockhopper_sqlite.yaml down --to 1 From f3d65b1281a036b0b2850b5c0210b4d4d1ff64a1 Mon Sep 17 00:00:00 2001 From: c9s Date: Mon, 15 Feb 2021 20:51:25 +0800 Subject: [PATCH 04/22] add UpdatePnL method for updating trade pnl field --- pkg/service/trade.go | 47 ++++++++++++++++++++++++++++++++------------ 1 file changed, 34 insertions(+), 13 deletions(-) diff --git a/pkg/service/trade.go b/pkg/service/trade.go index 05ce2bbf9..5a33b2d74 100644 --- a/pkg/service/trade.go +++ b/pkg/service/trade.go @@ -1,6 +1,8 @@ package service import ( + "context" + "fmt" "strconv" "strings" "time" @@ -12,6 +14,16 @@ import ( "github.com/c9s/bbgo/pkg/types" ) +type QueryTradesOptions struct { + Exchange types.ExchangeName + Symbol string + LastGID int64 + + // ASC or DESC + Ordering string + Limit int +} + type TradingVolume struct { Year int `db:"year" json:"year"` Month int `db:"month" json:"month,omitempty"` @@ -80,7 +92,6 @@ func (s *TradeService) QueryTradingVolume(startTime time.Time, options TradingVo return records, rows.Err() } - func generateSqliteTradingVolumeSQL(options TradingVolumeQueryOptions) string { var sel []string var groupBys []string @@ -127,7 +138,6 @@ func generateSqliteTradingVolumeSQL(options TradingVolumeQueryOptions) string { return sql } - func generateMysqlTradingVolumeQuerySQL(options TradingVolumeQueryOptions) string { var sel []string var groupBys []string @@ -137,8 +147,6 @@ func generateMysqlTradingVolumeQuerySQL(options TradingVolumeQueryOptions) strin switch options.GroupByPeriod { case "month": - - sel = append(sel, "YEAR(traded_at) AS year", "MONTH(traded_at) AS month") groupBys = append([]string{"MONTH(traded_at)", "YEAR(traded_at)"}, groupBys...) orderBys = append(orderBys, "year ASC", "month ASC") @@ -221,15 +229,6 @@ func (s *TradeService) QueryForTradingFeeCurrency(ex types.ExchangeName, symbol return s.scanRows(rows) } -type QueryTradesOptions struct { - Exchange types.ExchangeName - Symbol string - LastGID int64 - // ASC or DESC - Ordering string - Limit int -} - func (s *TradeService) Query(options QueryTradesOptions) ([]types.Trade, error) { sql := queryTradesSQL(options) @@ -249,6 +248,28 @@ func (s *TradeService) Query(options QueryTradesOptions) ([]types.Trade, error) return s.scanRows(rows) } +func (s *TradeService) UpdatePnL(ctx context.Context, id int64, pnl float64) error { + result, err := s.DB.NamedExecContext(ctx, "UPDATE `trades` SET `pnl` = :pnl WHERE `id` = :id", map[string]interface{}{ + "id": id, + "pnl": pnl, + }) + if err != nil { + return err + } + + cnt, err := result.RowsAffected() + if err != nil { + return err + } + + if cnt == 0 { + return fmt.Errorf("trade id:%d not found", id) + } + + return nil + +} + func queryTradesSQL(options QueryTradesOptions) string { ordering := "ASC" switch v := strings.ToUpper(options.Ordering); v { From 786f37e675ec7f7f093e3c75e05665f3c4e4b904 Mon Sep 17 00:00:00 2001 From: c9s Date: Mon, 15 Feb 2021 20:53:19 +0800 Subject: [PATCH 05/22] add MarkStrategyID for marking trade with the source strategy --- pkg/service/trade.go | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/pkg/service/trade.go b/pkg/service/trade.go index 5a33b2d74..565fc53e9 100644 --- a/pkg/service/trade.go +++ b/pkg/service/trade.go @@ -248,8 +248,29 @@ func (s *TradeService) Query(options QueryTradesOptions) ([]types.Trade, error) return s.scanRows(rows) } +func (s *TradeService) MarkStrategyID(ctx context.Context, id int64, strategyID string) error { + result, err := s.DB.NamedExecContext(ctx, "UPDATE `trades` SET `strategy` = :strategy WHERE `id` = :id LIMIT 1", map[string]interface{}{ + "id": id, + "strategy": strategyID, + }) + if err != nil { + return err + } + + cnt, err := result.RowsAffected() + if err != nil { + return err + } + + if cnt == 0 { + return fmt.Errorf("trade id:%d not found", id) + } + + return nil +} + func (s *TradeService) UpdatePnL(ctx context.Context, id int64, pnl float64) error { - result, err := s.DB.NamedExecContext(ctx, "UPDATE `trades` SET `pnl` = :pnl WHERE `id` = :id", map[string]interface{}{ + result, err := s.DB.NamedExecContext(ctx, "UPDATE `trades` SET `pnl` = :pnl WHERE `id` = :id LIMIT 1", map[string]interface{}{ "id": id, "pnl": pnl, }) From 3d47b3f34d8de9fe3b258d4625a30f1ecf3df591 Mon Sep 17 00:00:00 2001 From: c9s Date: Mon, 15 Feb 2021 20:55:14 +0800 Subject: [PATCH 06/22] update trade fields for pnl and strategy id --- pkg/types/trade.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pkg/types/trade.go b/pkg/types/trade.go index 1f2696975..eaaea9288 100644 --- a/pkg/types/trade.go +++ b/pkg/types/trade.go @@ -58,6 +58,9 @@ type Trade struct { IsMargin bool `json:"isMargin" db:"is_margin"` IsIsolated bool `json:"isIsolated" db:"is_isolated"` + + StrategyID string `json:"strategyID" db:"strategy"` + PnL float64 `json:"pnl" db:"pnl"` } func (trade Trade) PlainText() string { From c219dc7be0099fc59b06e27d87f37a38be720e8d Mon Sep 17 00:00:00 2001 From: c9s Date: Mon, 15 Feb 2021 21:04:44 +0800 Subject: [PATCH 07/22] add test code for testing migration scripts --- pkg/bbgo/session.go | 2 +- pkg/service/trade_test.go | 26 +++++++++++++++++++++++++- 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/pkg/bbgo/session.go b/pkg/bbgo/session.go index 6f2c9ae83..7d45a9c24 100644 --- a/pkg/bbgo/session.go +++ b/pkg/bbgo/session.go @@ -506,7 +506,7 @@ func (session *ExchangeSession) UpdatePrices(ctx context.Context) (err error) { symbols := make([]string, len(balances)) for _, b := range balances { - symbols = append(symbols, b.Currency + "USDT") + symbols = append(symbols, b.Currency+"USDT") } tickers, err := session.Exchange.QueryTickers(ctx, symbols...) diff --git a/pkg/service/trade_test.go b/pkg/service/trade_test.go index f7feb74e1..aa2349905 100644 --- a/pkg/service/trade_test.go +++ b/pkg/service/trade_test.go @@ -1,11 +1,35 @@ package service import ( + "context" "testing" + "github.com/c9s/rockhopper" "github.com/stretchr/testify/assert" ) +func Test_tradeService(t *testing.T) { + dialect, err := rockhopper.LoadDialect("sqlite3") + assert.NoError(t, err) + assert.NotNil(t, dialect) + + db, err := rockhopper.Open("sqlite3", dialect, ":memory:") + assert.NoError(t, err) + assert.NotNil(t, db) + + _, err = db.CurrentVersion() + assert.NoError(t, err) + + var loader rockhopper.SqlMigrationLoader + migrations, err := loader.Load("../../migrations/sqlite3") + assert.NoError(t, err) + assert.NotEmpty(t, migrations) + + ctx := context.Background() + err = rockhopper.Up(ctx, db, migrations, 0, 0) + assert.NoError(t, err) +} + func Test_queryTradingVolumeSQL(t *testing.T) { t.Run("group by different period", func(t *testing.T) { o := TradingVolumeQueryOptions{ @@ -52,7 +76,7 @@ func Test_queryTradesSQL(t *testing.T) { Symbol: "btc", LastGID: 123, Ordering: "DESC", - Limit: 500, + Limit: 500, })) }) } From ebe065332c28451a7bd5956475bfafcfaf0aaf34 Mon Sep 17 00:00:00 2001 From: c9s Date: Mon, 15 Feb 2021 21:07:55 +0800 Subject: [PATCH 08/22] allocate sqlx db from rockhopper db --- pkg/service/trade_test.go | 37 ++++++++++++++++++++++++++++++++----- 1 file changed, 32 insertions(+), 5 deletions(-) diff --git a/pkg/service/trade_test.go b/pkg/service/trade_test.go index aa2349905..c91302721 100644 --- a/pkg/service/trade_test.go +++ b/pkg/service/trade_test.go @@ -5,29 +5,56 @@ import ( "testing" "github.com/c9s/rockhopper" + "github.com/jmoiron/sqlx" "github.com/stretchr/testify/assert" ) -func Test_tradeService(t *testing.T) { +func prepareDB(t *testing.T) (*rockhopper.DB, error) { dialect, err := rockhopper.LoadDialect("sqlite3") - assert.NoError(t, err) + if !assert.NoError(t, err) { + return nil, err + } + assert.NotNil(t, dialect) db, err := rockhopper.Open("sqlite3", dialect, ":memory:") - assert.NoError(t, err) + if !assert.NoError(t, err) { + return nil, err + } + assert.NotNil(t, db) _, err = db.CurrentVersion() - assert.NoError(t, err) + if !assert.NoError(t, err) { + return nil, err + } var loader rockhopper.SqlMigrationLoader migrations, err := loader.Load("../../migrations/sqlite3") - assert.NoError(t, err) + if !assert.NoError(t, err) { + return nil, err + } + assert.NotEmpty(t, migrations) ctx := context.Background() err = rockhopper.Up(ctx, db, migrations, 0, 0) assert.NoError(t, err) + + return db, err +} + +func Test_tradeService(t *testing.T) { + db, err := prepareDB(t) + if err != nil { + t.Fatal(err) + } + + xdb := sqlx.NewDb(db.DB, "sqlite3") + service := &TradeService{DB: xdb} + _ = service + + defer db.Close() } func Test_queryTradingVolumeSQL(t *testing.T) { From 67a3c4908189f8a90e4768d059ce3d3296d1f118 Mon Sep 17 00:00:00 2001 From: c9s Date: Tue, 16 Feb 2021 15:34:01 +0800 Subject: [PATCH 09/22] add more trade service tests --- pkg/service/trade.go | 29 ++++++++++++++++++++++++++--- pkg/service/trade_test.go | 37 +++++++++++++++++++++++++++++++++++-- pkg/types/trade.go | 5 +++-- 3 files changed, 64 insertions(+), 7 deletions(-) diff --git a/pkg/service/trade.go b/pkg/service/trade.go index 565fc53e9..81c9b7c4f 100644 --- a/pkg/service/trade.go +++ b/pkg/service/trade.go @@ -14,6 +14,8 @@ import ( "github.com/c9s/bbgo/pkg/types" ) +var ErrTradeNotFound = errors.New("trade not found") + type QueryTradesOptions struct { Exchange types.ExchangeName Symbol string @@ -248,9 +250,30 @@ func (s *TradeService) Query(options QueryTradesOptions) ([]types.Trade, error) return s.scanRows(rows) } +func (s *TradeService) Load(ctx context.Context, id int64) (*types.Trade, error) { + var trade types.Trade + + rows, err := s.DB.NamedQuery("SELECT * FROM trades WHERE id = :id", map[string]interface{}{ + "id": id, + }) + if err != nil { + return nil, err + } + + defer rows.Close() + + if rows.Next() { + err = rows.StructScan(&trade) + return &trade, err + } + + return nil, errors.Wrapf(ErrTradeNotFound,"trade id:%d not found", id) +} + + func (s *TradeService) MarkStrategyID(ctx context.Context, id int64, strategyID string) error { - result, err := s.DB.NamedExecContext(ctx, "UPDATE `trades` SET `strategy` = :strategy WHERE `id` = :id LIMIT 1", map[string]interface{}{ - "id": id, + result, err := s.DB.NamedExecContext(ctx, "UPDATE `trades` SET `strategy` = :strategy WHERE `id` = :id", map[string]interface{}{ + "id": id, "strategy": strategyID, }) if err != nil { @@ -270,7 +293,7 @@ func (s *TradeService) MarkStrategyID(ctx context.Context, id int64, strategyID } func (s *TradeService) UpdatePnL(ctx context.Context, id int64, pnl float64) error { - result, err := s.DB.NamedExecContext(ctx, "UPDATE `trades` SET `pnl` = :pnl WHERE `id` = :id LIMIT 1", map[string]interface{}{ + result, err := s.DB.NamedExecContext(ctx, "UPDATE `trades` SET `pnl` = :pnl WHERE `id` = :id", map[string]interface{}{ "id": id, "pnl": pnl, }) diff --git a/pkg/service/trade_test.go b/pkg/service/trade_test.go index c91302721..9039e0085 100644 --- a/pkg/service/trade_test.go +++ b/pkg/service/trade_test.go @@ -7,6 +7,8 @@ import ( "github.com/c9s/rockhopper" "github.com/jmoiron/sqlx" "github.com/stretchr/testify/assert" + + "github.com/c9s/bbgo/pkg/types" ) func prepareDB(t *testing.T) (*rockhopper.DB, error) { @@ -50,11 +52,42 @@ func Test_tradeService(t *testing.T) { t.Fatal(err) } + defer db.Close() + + ctx := context.Background() + xdb := sqlx.NewDb(db.DB, "sqlite3") service := &TradeService{DB: xdb} - _ = service - defer db.Close() + err = service.Insert(types.Trade{ + ID: 1, + OrderID: 1, + Exchange: "binance", + Price: 1000.0, + Quantity: 0.1, + QuoteQuantity: 1000.0 * 0.1, + Symbol: "BTCUSDT", + Side: "BUY", + IsBuyer: true, + }) + assert.NoError(t, err) + + err = service.MarkStrategyID(ctx, 1, "grid") + assert.NoError(t, err) + + tradeRecord, err := service.Load(ctx, 1) + assert.NoError(t, err) + assert.NotNil(t, tradeRecord) + assert.Equal(t, "grid", tradeRecord.StrategyID) + + err = service.UpdatePnL(ctx, 1, 10.0) + assert.NoError(t, err) + + tradeRecord, err = service.Load(ctx, 1) + assert.NoError(t, err) + assert.NotNil(t, tradeRecord) + assert.True(t, tradeRecord.PnL.Valid) + assert.Equal(t, 10.0, tradeRecord.PnL.Float64) } func Test_queryTradingVolumeSQL(t *testing.T) { diff --git a/pkg/types/trade.go b/pkg/types/trade.go index eaaea9288..936f61caf 100644 --- a/pkg/types/trade.go +++ b/pkg/types/trade.go @@ -1,6 +1,7 @@ package types import ( + "database/sql" "fmt" "sync" @@ -59,8 +60,8 @@ type Trade struct { IsMargin bool `json:"isMargin" db:"is_margin"` IsIsolated bool `json:"isIsolated" db:"is_isolated"` - StrategyID string `json:"strategyID" db:"strategy"` - PnL float64 `json:"pnl" db:"pnl"` + StrategyID string `json:"strategyID" db:"strategy"` + PnL sql.NullFloat64 `json:"pnl" db:"pnl"` } func (trade Trade) PlainText() string { From 1c2646b0af99cd4be80e432b45976b3e5c5fbb60 Mon Sep 17 00:00:00 2001 From: c9s Date: Tue, 16 Feb 2021 15:49:57 +0800 Subject: [PATCH 10/22] add Test_injectField --- pkg/bbgo/injection.go | 4 -- pkg/bbgo/injection_test.go | 31 ++++++++++++++++ pkg/bbgo/trader.go | 76 +++++++++++++++++++------------------- 3 files changed, 70 insertions(+), 41 deletions(-) create mode 100644 pkg/bbgo/injection_test.go diff --git a/pkg/bbgo/injection.go b/pkg/bbgo/injection.go index 25dd14100..5d2338412 100644 --- a/pkg/bbgo/injection.go +++ b/pkg/bbgo/injection.go @@ -3,8 +3,6 @@ package bbgo import ( "fmt" "reflect" - - "github.com/sirupsen/logrus" ) func isSymbolBasedStrategy(rs reflect.Value) (string, bool) { @@ -31,8 +29,6 @@ func injectField(rs reflect.Value, fieldName string, obj interface{}, pointerOnl return nil } - logrus.Infof("found %s in %s, injecting %T...", fieldName, rs.Type(), obj) - if !field.CanSet() { return fmt.Errorf("field %s of %s can not be set", fieldName, rs.Type()) } diff --git a/pkg/bbgo/injection_test.go b/pkg/bbgo/injection_test.go new file mode 100644 index 000000000..08795c33b --- /dev/null +++ b/pkg/bbgo/injection_test.go @@ -0,0 +1,31 @@ +package bbgo + +import ( + "reflect" + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/c9s/bbgo/pkg/service" +) + + +func Test_injectField(t *testing.T) { + type TT struct { + TradeService *service.TradeService + } + + // only pointer object can be set. + var tt = &TT{} + + // get the value of the pointer, or it can not be set. + var rv = reflect.ValueOf(tt).Elem() + + _, ret := hasField(rv, "TradeService") + assert.True(t, ret) + + ts := &service.TradeService{} + + err := injectField(rv, "TradeService", ts, true) + assert.NoError(t, err) +} diff --git a/pkg/bbgo/trader.go b/pkg/bbgo/trader.go index c2c8690dd..68f93eebf 100644 --- a/pkg/bbgo/trader.go +++ b/pkg/bbgo/trader.go @@ -249,52 +249,54 @@ func (trader *Trader) Run(ctx context.Context) error { for _, strategy := range trader.crossExchangeStrategies { rs := reflect.ValueOf(strategy) - if rs.Elem().Kind() == reflect.Struct { - // get the struct element - rs = rs.Elem() - if field, ok := hasField(rs, "Persistence"); ok { - if trader.environment.PersistenceServiceFacade == nil { - log.Warnf("strategy has Persistence field but persistence service is not defined") + if rs.Elem().Kind() != reflect.Struct { + continue + } + + // get the struct element from the struct pointer + rs = rs.Elem() + + if field, ok := hasField(rs, "Persistence"); ok { + if trader.environment.PersistenceServiceFacade == nil { + log.Warnf("strategy has Persistence field but persistence service is not defined") + } else { + log.Infof("found Persistence field, injecting...") + if field.IsNil() { + field.Set(reflect.ValueOf(&Persistence{ + PersistenceSelector: &PersistenceSelector{ + StoreID: "default", + Type: "memory", + }, + Facade: trader.environment.PersistenceServiceFacade, + })) } else { - log.Infof("found Persistence field, injecting...") - if field.IsNil() { - field.Set(reflect.ValueOf(&Persistence{ - PersistenceSelector: &PersistenceSelector{ - StoreID: "default", - Type: "memory", - }, - Facade: trader.environment.PersistenceServiceFacade, - })) - } else { - elem := field.Elem() - if elem.Kind() != reflect.Struct { - return fmt.Errorf("the field Persistence is not a struct element") - } + elem := field.Elem() + if elem.Kind() != reflect.Struct { + return fmt.Errorf("the field Persistence is not a struct element") + } - if err := injectField(elem, "Facade", trader.environment.PersistenceServiceFacade, true); err != nil { - log.WithError(err).Errorf("strategy Persistence injection failed") - return err - } + if err := injectField(elem, "Facade", trader.environment.PersistenceServiceFacade, true); err != nil { + log.WithError(err).Errorf("strategy Persistence injection failed") + return err } } } + } - if err := injectField(rs, "Graceful", &trader.Graceful, true); err != nil { - log.WithError(err).Errorf("strategy Graceful injection failed") - return err - } + if err := injectField(rs, "Graceful", &trader.Graceful, true); err != nil { + log.WithError(err).Errorf("strategy Graceful injection failed") + return err + } - if err := injectField(rs, "Logger", &trader.logger, false); err != nil { - log.WithError(err).Errorf("strategy Logger injection failed") - return err - } - - if err := injectField(rs, "Notifiability", &trader.environment.Notifiability, false); err != nil { - log.WithError(err).Errorf("strategy Notifiability injection failed") - return err - } + if err := injectField(rs, "Logger", &trader.logger, false); err != nil { + log.WithError(err).Errorf("strategy Logger injection failed") + return err + } + if err := injectField(rs, "Notifiability", &trader.environment.Notifiability, false); err != nil { + log.WithError(err).Errorf("strategy Notifiability injection failed") + return err } if err := strategy.CrossRun(ctx, router, trader.environment.sessions); err != nil { From fc4419b49b25e9abdef31adfb2e7f8549e57927e Mon Sep 17 00:00:00 2001 From: c9s Date: Tue, 16 Feb 2021 15:58:21 +0800 Subject: [PATCH 11/22] refactor injection --- pkg/bbgo/injection_test.go | 1 - pkg/bbgo/trader.go | 108 ++++++++++++++++++------------------- 2 files changed, 51 insertions(+), 58 deletions(-) diff --git a/pkg/bbgo/injection_test.go b/pkg/bbgo/injection_test.go index 08795c33b..1bb2fe6f8 100644 --- a/pkg/bbgo/injection_test.go +++ b/pkg/bbgo/injection_test.go @@ -9,7 +9,6 @@ import ( "github.com/c9s/bbgo/pkg/service" ) - func Test_injectField(t *testing.T) { type TT struct { TradeService *service.TradeService diff --git a/pkg/bbgo/trader.go b/pkg/bbgo/trader.go index 68f93eebf..241ce4ae3 100644 --- a/pkg/bbgo/trader.go +++ b/pkg/bbgo/trader.go @@ -6,6 +6,7 @@ import ( "reflect" "sync" + "github.com/pkg/errors" log "github.com/sirupsen/logrus" "github.com/c9s/bbgo/pkg/types" @@ -178,58 +179,46 @@ func (trader *Trader) Run(ctx context.Context) error { for _, strategy := range strategies { rs := reflect.ValueOf(strategy) - if rs.Elem().Kind() == reflect.Struct { - // get the struct element - rs = rs.Elem() - if err := injectField(rs, "Graceful", &trader.Graceful, true); err != nil { - log.WithError(err).Errorf("strategy Graceful injection failed") - return err - } + // get the struct element + rs = rs.Elem() - if err := injectField(rs, "Logger", &trader.logger, false); err != nil { - log.WithError(err).Errorf("strategy Logger injection failed") - return err - } + if rs.Kind() != reflect.Struct { + continue + } - if err := injectField(rs, "Notifiability", &trader.environment.Notifiability, false); err != nil { - log.WithError(err).Errorf("strategy Notifiability injection failed") - return err - } + if err := trader.injectCommonServices(rs); err != nil { + return err + } - if err := injectField(rs, "OrderExecutor", orderExecutor, false); err != nil { - log.WithError(err).Errorf("strategy OrderExecutor injection failed") - return err - } + if err := injectField(rs, "OrderExecutor", orderExecutor, false); err != nil { + return errors.Wrapf(err, "failed to inject OrderExecutor on %T", strategy) + } - if symbol, ok := isSymbolBasedStrategy(rs); ok { - log.Infof("found symbol based strategy from %s", rs.Type()) - if _, ok := hasField(rs, "Market"); ok { - if market, ok := session.Market(symbol); ok { - // let's make the market object passed by pointer - if err := injectField(rs, "Market", &market, false); err != nil { - log.WithError(err).Errorf("strategy %T Market injection failed", strategy) - return err - } + if symbol, ok := isSymbolBasedStrategy(rs); ok { + log.Debugf("found symbol based strategy from %s", rs.Type()) + if _, ok := hasField(rs, "Market"); ok { + if market, ok := session.Market(symbol); ok { + // let's make the market object passed by pointer + if err := injectField(rs, "Market", &market, false); err != nil { + return errors.Wrapf(err, "failed to inject Market on %T", strategy) } } + } - // StandardIndicatorSet - if _, ok := hasField(rs, "StandardIndicatorSet"); ok { - if indicatorSet, ok := session.StandardIndicatorSet(symbol); ok { - if err := injectField(rs, "StandardIndicatorSet", indicatorSet, true); err != nil { - log.WithError(err).Errorf("strategy %T StandardIndicatorSet injection failed", strategy) - return err - } + // StandardIndicatorSet + if _, ok := hasField(rs, "StandardIndicatorSet"); ok { + if indicatorSet, ok := session.StandardIndicatorSet(symbol); ok { + if err := injectField(rs, "StandardIndicatorSet", indicatorSet, true); err != nil { + return errors.Wrapf(err, "failed to inject StandardIndicatorSet on %T", strategy) } } + } - if _, ok := hasField(rs, "MarketDataStore"); ok { - if store, ok := session.MarketDataStore(symbol); ok { - if err := injectField(rs, "MarketDataStore", store, true); err != nil { - log.WithError(err).Errorf("strategy %T MarketDataStore injection failed", strategy) - return err - } + if _, ok := hasField(rs, "MarketDataStore"); ok { + if store, ok := session.MarketDataStore(symbol); ok { + if err := injectField(rs, "MarketDataStore", store, true); err != nil { + return errors.Wrapf(err, "failed to inject MarketDataStore on %T", strategy) } } } @@ -250,13 +239,13 @@ func (trader *Trader) Run(ctx context.Context) error { for _, strategy := range trader.crossExchangeStrategies { rs := reflect.ValueOf(strategy) - if rs.Elem().Kind() != reflect.Struct { - continue - } - // get the struct element from the struct pointer rs = rs.Elem() + if rs.Kind() != reflect.Struct { + continue + } + if field, ok := hasField(rs, "Persistence"); ok { if trader.environment.PersistenceServiceFacade == nil { log.Warnf("strategy has Persistence field but persistence service is not defined") @@ -284,18 +273,7 @@ func (trader *Trader) Run(ctx context.Context) error { } } - if err := injectField(rs, "Graceful", &trader.Graceful, true); err != nil { - log.WithError(err).Errorf("strategy Graceful injection failed") - return err - } - - if err := injectField(rs, "Logger", &trader.logger, false); err != nil { - log.WithError(err).Errorf("strategy Logger injection failed") - return err - } - - if err := injectField(rs, "Notifiability", &trader.environment.Notifiability, false); err != nil { - log.WithError(err).Errorf("strategy Notifiability injection failed") + if err := trader.injectCommonServices(rs); err != nil { return err } @@ -307,6 +285,22 @@ func (trader *Trader) Run(ctx context.Context) error { return trader.environment.Connect(ctx) } +func (trader *Trader) injectCommonServices(rs reflect.Value) error { + if err := injectField(rs, "Graceful", &trader.Graceful, true); err != nil { + return errors.Wrap(err, "failed to inject Graceful") + } + + if err := injectField(rs, "Logger", &trader.logger, false); err != nil { + return errors.Wrap(err, "failed to inject Logger") + } + + if err := injectField(rs, "Notifiability", &trader.environment.Notifiability, false); err != nil { + return errors.Wrap(err, "failed to inject Notifiability") + } + + return nil +} + // ReportPnL configure and set the PnLReporter with the given notifier func (trader *Trader) ReportPnL() *PnLReporterManager { return NewPnLReporter(&trader.environment.Notifiability) From bf0ba89aee745bc73409406b7d2ae52cfb043d98 Mon Sep 17 00:00:00 2001 From: c9s Date: Tue, 16 Feb 2021 16:00:14 +0800 Subject: [PATCH 12/22] convert StrategyID field to NullString --- pkg/service/trade_test.go | 3 ++- pkg/types/trade.go | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/pkg/service/trade_test.go b/pkg/service/trade_test.go index 9039e0085..5bb66f2e0 100644 --- a/pkg/service/trade_test.go +++ b/pkg/service/trade_test.go @@ -78,7 +78,8 @@ func Test_tradeService(t *testing.T) { tradeRecord, err := service.Load(ctx, 1) assert.NoError(t, err) assert.NotNil(t, tradeRecord) - assert.Equal(t, "grid", tradeRecord.StrategyID) + assert.True(t, tradeRecord.StrategyID.Valid) + assert.Equal(t, "grid", tradeRecord.StrategyID.String) err = service.UpdatePnL(ctx, 1, 10.0) assert.NoError(t, err) diff --git a/pkg/types/trade.go b/pkg/types/trade.go index 936f61caf..5505cc633 100644 --- a/pkg/types/trade.go +++ b/pkg/types/trade.go @@ -60,7 +60,7 @@ type Trade struct { IsMargin bool `json:"isMargin" db:"is_margin"` IsIsolated bool `json:"isIsolated" db:"is_isolated"` - StrategyID string `json:"strategyID" db:"strategy"` + StrategyID sql.NullString `json:"strategyID" db:"strategy"` PnL sql.NullFloat64 `json:"pnl" db:"pnl"` } From 5c1630f0008159edb3f53927acc4b30cc97ab226 Mon Sep 17 00:00:00 2001 From: c9s Date: Tue, 16 Feb 2021 16:12:00 +0800 Subject: [PATCH 13/22] refactor strategy executor --- pkg/bbgo/trader.go | 162 ++++++++++++++++++++++++--------------------- 1 file changed, 88 insertions(+), 74 deletions(-) diff --git a/pkg/bbgo/trader.go b/pkg/bbgo/trader.go index 241ce4ae3..85f3f5208 100644 --- a/pkg/bbgo/trader.go +++ b/pkg/bbgo/trader.go @@ -148,6 +148,92 @@ func (trader *Trader) Subscribe() { } } +func (trader *Trader) RunSingleExchangeStrategy(ctx context.Context, strategy SingleExchangeStrategy, session *ExchangeSession, orderExecutor OrderExecutor) error { + rs := reflect.ValueOf(strategy) + + // get the struct element + rs = rs.Elem() + + if rs.Kind() != reflect.Struct { + return errors.New("strategy object is not a struct") + } + + if err := trader.injectCommonServices(rs); err != nil { + return err + } + + if err := injectField(rs, "OrderExecutor", orderExecutor, false); err != nil { + return errors.Wrapf(err, "failed to inject OrderExecutor on %T", strategy) + } + + if symbol, ok := isSymbolBasedStrategy(rs); ok { + log.Debugf("found symbol based strategy from %s", rs.Type()) + if _, ok := hasField(rs, "Market"); ok { + if market, ok := session.Market(symbol); ok { + // let's make the market object passed by pointer + if err := injectField(rs, "Market", &market, false); err != nil { + return errors.Wrapf(err, "failed to inject Market on %T", strategy) + } + } + } + + // StandardIndicatorSet + if _, ok := hasField(rs, "StandardIndicatorSet"); ok { + if indicatorSet, ok := session.StandardIndicatorSet(symbol); ok { + if err := injectField(rs, "StandardIndicatorSet", indicatorSet, true); err != nil { + return errors.Wrapf(err, "failed to inject StandardIndicatorSet on %T", strategy) + } + } + } + + if _, ok := hasField(rs, "MarketDataStore"); ok { + if store, ok := session.MarketDataStore(symbol); ok { + if err := injectField(rs, "MarketDataStore", store, true); err != nil { + return errors.Wrapf(err, "failed to inject MarketDataStore on %T", strategy) + } + } + } + } + + return strategy.Run(ctx, orderExecutor, session) +} + +func (trader *Trader) getSessionOrderExecutor(sessionName string) OrderExecutor { + var session = trader.environment.sessions[sessionName] + + // default to base order executor + var orderExecutor OrderExecutor = session.orderExecutor + + // Since the risk controls are loaded from the config file + if trader.riskControls != nil && trader.riskControls.SessionBasedRiskControl != nil { + if control, ok := trader.riskControls.SessionBasedRiskControl[sessionName] ; ok { + control.SetBaseOrderExecutor(session.orderExecutor) + + // pick the wrapped order executor + if control.OrderExecutor != nil { + return control.OrderExecutor + } + } + } + + return orderExecutor +} + +func (trader *Trader) RunAllSingleExchangeStrategy(ctx context.Context) error { + // load and run Session strategies + for sessionName, strategies := range trader.exchangeStrategies { + var session = trader.environment.sessions[sessionName] + var orderExecutor = trader.getSessionOrderExecutor(sessionName) + for _, strategy := range strategies { + if err := trader.RunSingleExchangeStrategy(ctx, strategy, session, orderExecutor); err != nil { + return err + } + } + } + + return nil +} + func (trader *Trader) Run(ctx context.Context) error { trader.Subscribe() @@ -155,80 +241,8 @@ func (trader *Trader) Run(ctx context.Context) error { return err } - // load and run Session strategies - for sessionName, strategies := range trader.exchangeStrategies { - var session = trader.environment.sessions[sessionName] - - // default to base order executor - var orderExecutor OrderExecutor = session.orderExecutor - - // Since the risk controls are loaded from the config file - if riskControls := trader.riskControls; riskControls != nil { - if trader.riskControls.SessionBasedRiskControl != nil { - control, ok := trader.riskControls.SessionBasedRiskControl[sessionName] - if ok { - control.SetBaseOrderExecutor(session.orderExecutor) - - // pick the order executor - if control.OrderExecutor != nil { - orderExecutor = control.OrderExecutor - } - } - } - } - - for _, strategy := range strategies { - rs := reflect.ValueOf(strategy) - - // get the struct element - rs = rs.Elem() - - if rs.Kind() != reflect.Struct { - continue - } - - if err := trader.injectCommonServices(rs); err != nil { - return err - } - - if err := injectField(rs, "OrderExecutor", orderExecutor, false); err != nil { - return errors.Wrapf(err, "failed to inject OrderExecutor on %T", strategy) - } - - if symbol, ok := isSymbolBasedStrategy(rs); ok { - log.Debugf("found symbol based strategy from %s", rs.Type()) - if _, ok := hasField(rs, "Market"); ok { - if market, ok := session.Market(symbol); ok { - // let's make the market object passed by pointer - if err := injectField(rs, "Market", &market, false); err != nil { - return errors.Wrapf(err, "failed to inject Market on %T", strategy) - } - } - } - - // StandardIndicatorSet - if _, ok := hasField(rs, "StandardIndicatorSet"); ok { - if indicatorSet, ok := session.StandardIndicatorSet(symbol); ok { - if err := injectField(rs, "StandardIndicatorSet", indicatorSet, true); err != nil { - return errors.Wrapf(err, "failed to inject StandardIndicatorSet on %T", strategy) - } - } - } - - if _, ok := hasField(rs, "MarketDataStore"); ok { - if store, ok := session.MarketDataStore(symbol); ok { - if err := injectField(rs, "MarketDataStore", store, true); err != nil { - return errors.Wrapf(err, "failed to inject MarketDataStore on %T", strategy) - } - } - } - } - - err := strategy.Run(ctx, orderExecutor, session) - if err != nil { - return err - } - } + if err := trader.RunAllSingleExchangeStrategy(ctx); err != nil { + return err } router := &ExchangeOrderExecutionRouter{ From c75eb6b5ba4fcec95f4dd2d485d21e8be3c4bd78 Mon Sep 17 00:00:00 2001 From: c9s Date: Tue, 16 Feb 2021 16:13:52 +0800 Subject: [PATCH 14/22] pull out Persistence injection to the common injection --- pkg/bbgo/trader.go | 54 ++++++++++++++++++++++------------------------ 1 file changed, 26 insertions(+), 28 deletions(-) diff --git a/pkg/bbgo/trader.go b/pkg/bbgo/trader.go index 85f3f5208..2b6c4103c 100644 --- a/pkg/bbgo/trader.go +++ b/pkg/bbgo/trader.go @@ -206,7 +206,7 @@ func (trader *Trader) getSessionOrderExecutor(sessionName string) OrderExecutor // Since the risk controls are loaded from the config file if trader.riskControls != nil && trader.riskControls.SessionBasedRiskControl != nil { - if control, ok := trader.riskControls.SessionBasedRiskControl[sessionName] ; ok { + if control, ok := trader.riskControls.SessionBasedRiskControl[sessionName]; ok { control.SetBaseOrderExecutor(session.orderExecutor) // pick the wrapped order executor @@ -260,33 +260,6 @@ func (trader *Trader) Run(ctx context.Context) error { continue } - if field, ok := hasField(rs, "Persistence"); ok { - if trader.environment.PersistenceServiceFacade == nil { - log.Warnf("strategy has Persistence field but persistence service is not defined") - } else { - log.Infof("found Persistence field, injecting...") - if field.IsNil() { - field.Set(reflect.ValueOf(&Persistence{ - PersistenceSelector: &PersistenceSelector{ - StoreID: "default", - Type: "memory", - }, - Facade: trader.environment.PersistenceServiceFacade, - })) - } else { - elem := field.Elem() - if elem.Kind() != reflect.Struct { - return fmt.Errorf("the field Persistence is not a struct element") - } - - if err := injectField(elem, "Facade", trader.environment.PersistenceServiceFacade, true); err != nil { - log.WithError(err).Errorf("strategy Persistence injection failed") - return err - } - } - } - } - if err := trader.injectCommonServices(rs); err != nil { return err } @@ -312,6 +285,31 @@ func (trader *Trader) injectCommonServices(rs reflect.Value) error { return errors.Wrap(err, "failed to inject Notifiability") } + if field, ok := hasField(rs, "Persistence"); ok { + if trader.environment.PersistenceServiceFacade == nil { + log.Warnf("strategy has Persistence field but persistence service is not defined") + } else { + if field.IsNil() { + field.Set(reflect.ValueOf(&Persistence{ + PersistenceSelector: &PersistenceSelector{ + StoreID: "default", + Type: "memory", + }, + Facade: trader.environment.PersistenceServiceFacade, + })) + } else { + elem := field.Elem() + if elem.Kind() != reflect.Struct { + return fmt.Errorf("field Persistence is not a struct element") + } + + if err := injectField(elem, "Facade", trader.environment.PersistenceServiceFacade, true); err != nil { + return errors.Wrap(err, "failed to inject Persistence") + } + } + } + } + return nil } From 8ae4cab550fcadc84b4cc21579dc08a5c70ae46d Mon Sep 17 00:00:00 2001 From: c9s Date: Tue, 16 Feb 2021 16:14:49 +0800 Subject: [PATCH 15/22] inject TradeService field if we found it --- pkg/bbgo/trader.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pkg/bbgo/trader.go b/pkg/bbgo/trader.go index 2b6c4103c..8458a69c1 100644 --- a/pkg/bbgo/trader.go +++ b/pkg/bbgo/trader.go @@ -285,6 +285,12 @@ func (trader *Trader) injectCommonServices(rs reflect.Value) error { return errors.Wrap(err, "failed to inject Notifiability") } + if trader.environment.TradeService != nil { + if err := injectField(rs, "TradeService", &trader.environment.TradeService, true); err != nil { + return errors.Wrap(err, "failed to inject TradeService") + } + } + if field, ok := hasField(rs, "Persistence"); ok { if trader.environment.PersistenceServiceFacade == nil { log.Warnf("strategy has Persistence field but persistence service is not defined") From e3d3eacb78b0dfee38a49ac170e08c5e2fed6139 Mon Sep 17 00:00:00 2001 From: c9s Date: Tue, 16 Feb 2021 16:30:01 +0800 Subject: [PATCH 16/22] fix trade service injection --- pkg/bbgo/environment.go | 10 +++++----- pkg/bbgo/trader.go | 2 +- pkg/strategy/grid/strategy.go | 12 ++++++++++++ 3 files changed, 18 insertions(+), 6 deletions(-) diff --git a/pkg/bbgo/environment.go b/pkg/bbgo/environment.go index c03c8a711..63afe30eb 100644 --- a/pkg/bbgo/environment.go +++ b/pkg/bbgo/environment.go @@ -48,9 +48,9 @@ type Environment struct { PersistenceServiceFacade *PersistenceServiceFacade DatabaseService *service.DatabaseService - OrderService *service.OrderService - TradeService *service.TradeService - TradeSync *service.SyncService + OrderService *service.OrderService + TradeService *service.TradeService + TradeSync *service.SyncService // startTime is the time of start point (which is used in the backtest) startTime time.Time @@ -61,7 +61,7 @@ type Environment struct { func NewEnvironment() *Environment { return &Environment{ // default trade scan time - tradeScanTime: time.Now().AddDate(0, 0, -7), // sync from 7 days ago + tradeScanTime: time.Now().AddDate(0, -1, 0), // sync from 1 month ago sessions: make(map[string]*ExchangeSession), startTime: time.Now(), } @@ -83,7 +83,7 @@ func (environ *Environment) ConfigureDatabase(ctx context.Context, driver string return err } - if err := environ.DatabaseService.Upgrade(ctx) ; err != nil { + if err := environ.DatabaseService.Upgrade(ctx); err != nil { return err } diff --git a/pkg/bbgo/trader.go b/pkg/bbgo/trader.go index 8458a69c1..1118c53fe 100644 --- a/pkg/bbgo/trader.go +++ b/pkg/bbgo/trader.go @@ -286,7 +286,7 @@ func (trader *Trader) injectCommonServices(rs reflect.Value) error { } if trader.environment.TradeService != nil { - if err := injectField(rs, "TradeService", &trader.environment.TradeService, true); err != nil { + if err := injectField(rs, "TradeService", trader.environment.TradeService, true); err != nil { return errors.Wrap(err, "failed to inject TradeService") } } diff --git a/pkg/strategy/grid/strategy.go b/pkg/strategy/grid/strategy.go index 5932da4d0..7c8a878ef 100644 --- a/pkg/strategy/grid/strategy.go +++ b/pkg/strategy/grid/strategy.go @@ -10,6 +10,7 @@ import ( "github.com/c9s/bbgo/pkg/bbgo" "github.com/c9s/bbgo/pkg/fixedpoint" + "github.com/c9s/bbgo/pkg/service" "github.com/c9s/bbgo/pkg/types" ) @@ -39,6 +40,8 @@ type Strategy struct { // This field will be injected automatically since we defined the Symbol field. types.Market `json:"-" yaml:"-"` + TradeService *service.TradeService `json:"-" yaml:"-"` + // These fields will be filled from the config file (it translates YAML to JSON) Symbol string `json:"symbol" yaml:"symbol"` @@ -305,6 +308,7 @@ func (s *Strategy) Subscribe(session *bbgo.ExchangeSession) { } func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, session *bbgo.ExchangeSession) error { + // do some basic validation if s.GridNum == 0 { s.GridNum = 10 } @@ -313,6 +317,14 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se return fmt.Errorf("upper price (%f) should not be less than lower price (%f)", s.UpperPrice.Float64(), s.LowerPrice.Float64()) } + position, ok := session.Position(s.Symbol) + if !ok { + return fmt.Errorf("position not found") + } + + log.Infof("position: %+v", position) + + instanceID := fmt.Sprintf("grid-%s-%d", s.Symbol, s.GridNum) s.groupID = generateGroupID(instanceID) log.Infof("using group id %d from fnv(%s)", s.groupID, instanceID) From 02512805f80944010fa7f651cc5a952eb2d12eda Mon Sep 17 00:00:00 2001 From: c9s Date: Tue, 16 Feb 2021 16:32:48 +0800 Subject: [PATCH 17/22] set default query trade limit to 1000 for max --- pkg/exchange/max/exchange.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pkg/exchange/max/exchange.go b/pkg/exchange/max/exchange.go index 91843cde7..398b49a1d 100644 --- a/pkg/exchange/max/exchange.go +++ b/pkg/exchange/max/exchange.go @@ -498,6 +498,8 @@ func (e *Exchange) QueryTrades(ctx context.Context, symbol string, options *type if options.Limit > 0 { req.Limit(options.Limit) + } else { + req.Limit(1000) } if options.LastTradeID > 0 { @@ -519,7 +521,7 @@ func (e *Exchange) QueryTrades(ctx context.Context, symbol string, options *type continue } - logger.Infof("T: id=%d % 4s %s P=%f Q=%f %s", localTrade.ID, localTrade.Symbol, localTrade.Side, localTrade.Price, localTrade.Quantity, localTrade.Time) + logger.Infof("T: %d %7s %4s P=%f Q=%f %s", localTrade.ID, localTrade.Symbol, localTrade.Side, localTrade.Price, localTrade.Quantity, localTrade.Time) trades = append(trades, *localTrade) } From bc3754d9899ef80b539b34ed1509dc87db276c82 Mon Sep 17 00:00:00 2001 From: c9s Date: Tue, 16 Feb 2021 16:39:56 +0800 Subject: [PATCH 18/22] check if limit is set --- pkg/service/trade.go | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/pkg/service/trade.go b/pkg/service/trade.go index 81c9b7c4f..4e43417f5 100644 --- a/pkg/service/trade.go +++ b/pkg/service/trade.go @@ -267,10 +267,9 @@ func (s *TradeService) Load(ctx context.Context, id int64) (*types.Trade, error) return &trade, err } - return nil, errors.Wrapf(ErrTradeNotFound,"trade id:%d not found", id) + return nil, errors.Wrapf(ErrTradeNotFound, "trade id:%d not found", id) } - func (s *TradeService) MarkStrategyID(ctx context.Context, id int64, strategyID string) error { result, err := s.DB.NamedExecContext(ctx, "UPDATE `trades` SET `strategy` = :strategy WHERE `id` = :id", map[string]interface{}{ "id": id, @@ -348,7 +347,10 @@ func queryTradesSQL(options QueryTradesOptions) string { sql += ` ORDER BY gid ` + ordering - sql += ` LIMIT ` + strconv.Itoa(options.Limit) + if options.Limit > 0 { + sql += ` LIMIT ` + strconv.Itoa(options.Limit) + } + return sql } From 3867fdde9126807b172e74dec7d581b457db6018 Mon Sep 17 00:00:00 2001 From: c9s Date: Tue, 16 Feb 2021 16:40:11 +0800 Subject: [PATCH 19/22] add stringer interface to Position --- pkg/bbgo/position.go | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/pkg/bbgo/position.go b/pkg/bbgo/position.go index 7dfe9a10d..e375134f4 100644 --- a/pkg/bbgo/position.go +++ b/pkg/bbgo/position.go @@ -1,6 +1,8 @@ package bbgo import ( + "fmt" + "github.com/c9s/bbgo/pkg/fixedpoint" "github.com/c9s/bbgo/pkg/types" ) @@ -15,6 +17,15 @@ type Position struct { AverageCost fixedpoint.Value `json:"averageCost"` } +func (p Position) String() string { + return fmt.Sprintf("%s: average cost = %f, base = %f, quote = %f", + p.Symbol, + p.AverageCost.Float64(), + p.Base.Float64(), + p.Quote.Float64(), + ) +} + func (p *Position) BindStream(stream types.Stream) { stream.OnTradeUpdate(func(trade types.Trade) { if p.Symbol == trade.Symbol { From 5f759780c382240475931ab8d60534cd5e83be9f Mon Sep 17 00:00:00 2001 From: c9s Date: Tue, 16 Feb 2021 17:10:48 +0800 Subject: [PATCH 20/22] remove unused since flag --- pkg/cmd/run.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pkg/cmd/run.go b/pkg/cmd/run.go index a23f5cd75..0842d9c55 100644 --- a/pkg/cmd/run.go +++ b/pkg/cmd/run.go @@ -37,7 +37,6 @@ func init() { RunCmd.Flags().String("totp-account-name", "", "") RunCmd.Flags().Bool("enable-web-server", false, "enable web server") RunCmd.Flags().Bool("setup", false, "use setup mode") - RunCmd.Flags().String("since", "", "pnl since time") RootCmd.AddCommand(RunCmd) } @@ -227,7 +226,9 @@ func ConfigureTrader(trader *bbgo.Trader, userConfig *bbgo.Config) error { for _, entry := range userConfig.ExchangeStrategies { for _, mount := range entry.Mounts { log.Infof("attaching strategy %T on %s...", entry.Strategy, mount) - trader.AttachStrategyOn(mount, entry.Strategy) + if err := trader.AttachStrategyOn(mount, entry.Strategy) ; err != nil { + return err + } } } From 9a7437de534c085fe5e3c6ad7700368d2c2efa9b Mon Sep 17 00:00:00 2001 From: c9s Date: Tue, 16 Feb 2021 17:10:58 +0800 Subject: [PATCH 21/22] set default limit to 1000 --- pkg/exchange/max/exchange.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/exchange/max/exchange.go b/pkg/exchange/max/exchange.go index 398b49a1d..9d2ede655 100644 --- a/pkg/exchange/max/exchange.go +++ b/pkg/exchange/max/exchange.go @@ -499,7 +499,7 @@ func (e *Exchange) QueryTrades(ctx context.Context, symbol string, options *type if options.Limit > 0 { req.Limit(options.Limit) } else { - req.Limit(1000) + req.Limit(500) } if options.LastTradeID > 0 { From 49f4039a23c1fb523471af6c3913fdfbbed2cd73 Mon Sep 17 00:00:00 2001 From: c9s Date: Tue, 16 Feb 2021 17:11:15 +0800 Subject: [PATCH 22/22] add timestamp parameter --- pkg/exchange/max/maxapi/trade.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/pkg/exchange/max/maxapi/trade.go b/pkg/exchange/max/maxapi/trade.go index 9aba7dc32..a9c0a07ab 100644 --- a/pkg/exchange/max/maxapi/trade.go +++ b/pkg/exchange/max/maxapi/trade.go @@ -143,7 +143,7 @@ type PrivateTradeRequestParams struct { Market string `json:"market"` // Timestamp is the seconds elapsed since Unix epoch, set to return trades executed before the time only - Timestamp int `json:"timestamp,omitempty"` + Timestamp int64 `json:"timestamp,omitempty"` // From field is a trade id, set ot return trades created after the trade From int64 `json:"from,omitempty"` @@ -176,6 +176,11 @@ func (r *PrivateTradeRequest) From(from int64) *PrivateTradeRequest { return r } +func (r *PrivateTradeRequest) Timestamp(t int64) *PrivateTradeRequest { + r.params.Timestamp = t + return r +} + func (r *PrivateTradeRequest) To(to int64) *PrivateTradeRequest { r.params.To = to return r