diff --git a/doc/topics/profile.md b/doc/topics/profile.md new file mode 100644 index 000000000..98bb1c1b0 --- /dev/null +++ b/doc/topics/profile.md @@ -0,0 +1,13 @@ +Profiling +=================== + +```shell +dotenv -f .env.local -- go run -tags pprof ./cmd/bbgo run \ + --config ./makemoney-btcusdt.yaml \ + --cpu-profile makemoney.pprof \ + --enable-profile-server +``` + +```shell +go tool pprof http://localhost:6060/debug/pprof/heap +``` diff --git a/migrations/mysql/20241115165059_add_net_profit_column.sql b/migrations/mysql/20241115165059_add_net_profit_column.sql new file mode 100644 index 000000000..702686276 --- /dev/null +++ b/migrations/mysql/20241115165059_add_net_profit_column.sql @@ -0,0 +1,17 @@ +-- +up +-- +begin +ALTER TABLE `positions` + ADD COLUMN `net_profit` DECIMAL(16, 8) DEFAULT 0.00000000 NOT NULL +; +-- +end +-- +begin +UPDATE positions SET net_profit = profit WHERE net_profit = 0.0; +-- +end + +-- +down + +-- +begin +ALTER TABLE `positions` +DROP COLUMN `net_profit` +; +-- +end diff --git a/migrations/sqlite3/20241115165059_add_net_profit_column.sql b/migrations/sqlite3/20241115165059_add_net_profit_column.sql new file mode 100644 index 000000000..70595bb99 --- /dev/null +++ b/migrations/sqlite3/20241115165059_add_net_profit_column.sql @@ -0,0 +1,15 @@ +-- +up +-- +begin +ALTER TABLE `positions` + ADD COLUMN `net_profit` DECIMAL DEFAULT 0.00000000 NOT NULL +; +-- +end + + +-- +down + +-- +begin +ALTER TABLE `positions` +DROP COLUMN `net_profit` +; +-- +end diff --git a/pkg/bbgo/environment.go b/pkg/bbgo/environment.go index e94e1251f..44f02b9ae 100644 --- a/pkg/bbgo/environment.go +++ b/pkg/bbgo/environment.go @@ -587,7 +587,7 @@ func (environ *Environment) RecordPosition(position *types.Position, trade types log.Infof("recordPosition: position = %s, trade = %+v, profit = %+v", position.Base.String(), trade, profit) if profit != nil { - if err := environ.PositionService.Insert(position, trade, profit.Profit); err != nil { + if err := environ.PositionService.Insert(position, trade, profit.Profit, profit.NetProfit); err != nil { log.WithError(err).Errorf("can not insert position record") } @@ -595,7 +595,7 @@ func (environ *Environment) RecordPosition(position *types.Position, trade types log.WithError(err).Errorf("can not insert profit record: %+v", profit) } } else { - if err := environ.PositionService.Insert(position, trade, fixedpoint.Zero); err != nil { + if err := environ.PositionService.Insert(position, trade, fixedpoint.Zero, fixedpoint.Zero); err != nil { log.WithError(err).Errorf("can not insert position record") } } diff --git a/pkg/cmd/pprof.go b/pkg/cmd/pprof.go new file mode 100644 index 000000000..e9223b2b4 --- /dev/null +++ b/pkg/cmd/pprof.go @@ -0,0 +1,6 @@ +//go:build pprof +// +build pprof + +package cmd + +import _ "net/http/pprof" diff --git a/pkg/cmd/root.go b/pkg/cmd/root.go index 2b0ded1df..a696aef56 100644 --- a/pkg/cmd/root.go +++ b/pkg/cmd/root.go @@ -7,6 +7,7 @@ import ( "runtime/pprof" "strings" "time" + _ "time/tzdata" "github.com/heroku/rollrus" "github.com/joho/godotenv" @@ -20,8 +21,6 @@ import ( "github.com/c9s/bbgo/pkg/bbgo" "github.com/c9s/bbgo/pkg/util" - - _ "time/tzdata" ) var cpuProfileFile *os.File @@ -81,6 +80,24 @@ var RootCmd = &cobra.Command{ }() } + enableProfileServer, err := cmd.Flags().GetBool("enable-profile-server") + if err != nil { + return err + } + + if enableProfileServer { + profileServerBind, err := cmd.Flags().GetString("profile-server-bind") + if err != nil { + return err + } + + go func() { + if err := http.ListenAndServe(profileServerBind, nil); err != nil { + log.WithError(err).Errorf("profile server error") + } + }() + } + cpuProfile, err := cmd.Flags().GetString("cpu-profile") if err != nil { return err @@ -195,6 +212,8 @@ func init() { RootCmd.PersistentFlags().String("max-api-secret", "", "max api secret") RootCmd.PersistentFlags().String("cpu-profile", "", "cpu profile") + RootCmd.PersistentFlags().Bool("enable-profile-server", false, "enable profile server binding") + RootCmd.PersistentFlags().String("profile-server-bind", "localhost:6060", "profile server binding") viper.SetEnvKeyReplacer(strings.NewReplacer("-", "_")) diff --git a/pkg/migrations/mysql/main_20241115165059_add_net_profit_column.go b/pkg/migrations/mysql/main_20241115165059_add_net_profit_column.go new file mode 100644 index 000000000..d17c81c34 --- /dev/null +++ b/pkg/migrations/mysql/main_20241115165059_add_net_profit_column.go @@ -0,0 +1,33 @@ +package mysql + +import ( + "context" + + "github.com/c9s/rockhopper/v2" +) + +func init() { + AddMigration("main", up_main_addNetProfitColumn, down_main_addNetProfitColumn) +} + +func up_main_addNetProfitColumn(ctx context.Context, tx rockhopper.SQLExecutor) (err error) { + // This code is executed when the migration is applied. + _, err = tx.ExecContext(ctx, "ALTER TABLE `positions`\n ADD COLUMN `net_profit` DECIMAL(16, 8) DEFAULT 0.00000000 NOT NULL\n;") + if err != nil { + return err + } + _, err = tx.ExecContext(ctx, "UPDATE positions SET net_profit = profit WHERE net_profit = 0.0;") + if err != nil { + return err + } + return err +} + +func down_main_addNetProfitColumn(ctx context.Context, tx rockhopper.SQLExecutor) (err error) { + // This code is executed when the migration is rolled back. + _, err = tx.ExecContext(ctx, "ALTER TABLE `positions`\nDROP COLUMN `net_profit`\n;") + if err != nil { + return err + } + return err +} diff --git a/pkg/migrations/sqlite3/main_20241115165059_add_net_profit_column.go b/pkg/migrations/sqlite3/main_20241115165059_add_net_profit_column.go new file mode 100644 index 000000000..e397eb71a --- /dev/null +++ b/pkg/migrations/sqlite3/main_20241115165059_add_net_profit_column.go @@ -0,0 +1,29 @@ +package sqlite3 + +import ( + "context" + + "github.com/c9s/rockhopper/v2" +) + +func init() { + AddMigration("main", up_main_addNetProfitColumn, down_main_addNetProfitColumn) +} + +func up_main_addNetProfitColumn(ctx context.Context, tx rockhopper.SQLExecutor) (err error) { + // This code is executed when the migration is applied. + _, err = tx.ExecContext(ctx, "ALTER TABLE `positions`\n ADD COLUMN `net_profit` DECIMAL DEFAULT 0.00000000 NOT NULL\n;") + if err != nil { + return err + } + return err +} + +func down_main_addNetProfitColumn(ctx context.Context, tx rockhopper.SQLExecutor) (err error) { + // This code is executed when the migration is rolled back. + _, err = tx.ExecContext(ctx, "ALTER TABLE `positions`\nDROP COLUMN `net_profit`\n;") + if err != nil { + return err + } + return err +} diff --git a/pkg/service/position.go b/pkg/service/position.go index 5fb38eaa7..7b379a73a 100644 --- a/pkg/service/position.go +++ b/pkg/service/position.go @@ -51,7 +51,11 @@ func (s *PositionService) scanRows(rows *sqlx.Rows) (positions []types.Position, return positions, rows.Err() } -func (s *PositionService) Insert(position *types.Position, trade types.Trade, profit fixedpoint.Value) error { +func (s *PositionService) Insert( + position *types.Position, + trade types.Trade, + profit, netProfit fixedpoint.Value, +) error { _, err := s.DB.NamedExec(` INSERT INTO positions ( strategy, @@ -63,6 +67,7 @@ func (s *PositionService) Insert(position *types.Position, trade types.Trade, pr base, quote, profit, + net_profit, trade_id, exchange, side, @@ -77,6 +82,7 @@ func (s *PositionService) Insert(position *types.Position, trade types.Trade, pr :base, :quote, :profit, + :net_profit, :trade_id, :exchange, :side, @@ -92,6 +98,7 @@ func (s *PositionService) Insert(position *types.Position, trade types.Trade, pr "base": position.Base, "quote": position.Quote, "profit": profit, + "net_profit": netProfit, "trade_id": trade.ID, "exchange": trade.Exchange, "side": trade.Side, diff --git a/pkg/service/position_test.go b/pkg/service/position_test.go index b45a95fcf..d3cb76ef3 100644 --- a/pkg/service/position_test.go +++ b/pkg/service/position_test.go @@ -34,7 +34,7 @@ func TestPositionService(t *testing.T) { ChangedAt: time.Now(), }, types.Trade{ Time: types.Time(time.Now()), - }, fixedpoint.Zero) + }, fixedpoint.Zero, fixedpoint.Zero) assert.NoError(t, err) }) @@ -54,7 +54,7 @@ func TestPositionService(t *testing.T) { Exchange: types.ExchangeBinance, Side: types.SideTypeSell, Time: types.Time(time.Now()), - }, fixedpoint.NewFromFloat(10.9)) + }, fixedpoint.NewFromFloat(10.9), fixedpoint.NewFromFloat(8.1)) assert.NoError(t, err) })