diff --git a/config/randomtrader.yaml b/config/randomtrader.yaml new file mode 100644 index 000000000..80503bf6e --- /dev/null +++ b/config/randomtrader.yaml @@ -0,0 +1,9 @@ +--- +exchangeStrategies: + - on: max + randomtrader: + symbol: USDCUSDT + cronExpression: "@every 8h" # https://pkg.go.dev/github.com/robfig/cron#hdr-Predefined_schedules + quantity: 8 + onStart: true + dryRun: true diff --git a/pkg/cmd/strategy/builtin.go b/pkg/cmd/strategy/builtin.go index 05b652a25..2586b48be 100644 --- a/pkg/cmd/strategy/builtin.go +++ b/pkg/cmd/strategy/builtin.go @@ -29,6 +29,7 @@ import ( _ "github.com/c9s/bbgo/pkg/strategy/pivotshort" _ "github.com/c9s/bbgo/pkg/strategy/pricealert" _ "github.com/c9s/bbgo/pkg/strategy/pricedrop" + _ "github.com/c9s/bbgo/pkg/strategy/randomtrader" _ "github.com/c9s/bbgo/pkg/strategy/rebalance" _ "github.com/c9s/bbgo/pkg/strategy/rsicross" _ "github.com/c9s/bbgo/pkg/strategy/rsmaker" diff --git a/pkg/strategy/randomtrader/strategy.go b/pkg/strategy/randomtrader/strategy.go new file mode 100644 index 000000000..1bca02727 --- /dev/null +++ b/pkg/strategy/randomtrader/strategy.go @@ -0,0 +1,117 @@ +package randomtrader + +import ( + "context" + "fmt" + "math/rand" + "sync" + + "github.com/robfig/cron/v3" + "github.com/sirupsen/logrus" + + "github.com/c9s/bbgo/pkg/bbgo" + "github.com/c9s/bbgo/pkg/fixedpoint" + "github.com/c9s/bbgo/pkg/strategy/common" + "github.com/c9s/bbgo/pkg/types" +) + +const ID = "randomtrader" + +var log = logrus.WithField("strategy", ID) + +func init() { + bbgo.RegisterStrategy(ID, &Strategy{}) +} + +type Strategy struct { + *common.Strategy + + Environment *bbgo.Environment + Market types.Market + + Symbol string `json:"symbol"` + CronExpression string `json:"cronExpression"` + Quantity fixedpoint.Value `json:"quantity"` + OnStart bool `json:"onStart"` + DryRun bool `json:"dryRun"` +} + +func (s *Strategy) Defaults() error { + return nil +} + +func (s *Strategy) Initialize() error { + return nil +} + +func (s *Strategy) ID() string { + return ID +} + +func (s *Strategy) InstanceID() string { + return fmt.Sprintf("%s:%s", ID, s.Symbol) +} + +func (s *Strategy) Validate() error { + if s.CronExpression == "" { + return fmt.Errorf("cronExpression is required") + } + return nil +} + +func (s *Strategy) Subscribe(session *bbgo.ExchangeSession) {} + +func (s *Strategy) Run(ctx context.Context, _ bbgo.OrderExecutor, session *bbgo.ExchangeSession) error { + s.Strategy = &common.Strategy{} + s.Strategy.Initialize(ctx, s.Environment, session, s.Market, s.ID(), s.InstanceID()) + + session.UserDataStream.OnStart(func() { + if s.OnStart { + s.trade(ctx) + } + }) + + // the shutdown handler, you can cancel all orders + bbgo.OnShutdown(ctx, func(ctx context.Context, wg *sync.WaitGroup) { + defer wg.Done() + _ = s.OrderExecutor.GracefulCancel(ctx) + }) + + cron := cron.New() + cron.AddFunc(s.CronExpression, func() { + s.trade(ctx) + }) + cron.Start() + + return nil +} + +func (s *Strategy) trade(ctx context.Context) { + orderForm := []types.SubmitOrder{ + { + Symbol: s.Symbol, + Side: types.SideTypeBuy, + Type: types.OrderTypeMarket, + Quantity: s.Quantity, + }, { + Symbol: s.Symbol, + Side: types.SideTypeSell, + Type: types.OrderTypeMarket, + Quantity: s.Quantity, + }, + } + + submitOrder := orderForm[rand.Intn(2)] + log.Infof("submit order: %s", submitOrder.String()) + + if s.DryRun { + log.Infof("dry run, skip submit order") + return + } + + _, err := s.OrderExecutor.SubmitOrders(ctx, submitOrder) + if err != nil { + log.WithError(err).Error("submit order error") + return + } +}