From bf260761128b98da8de84cb654683da546193096 Mon Sep 17 00:00:00 2001 From: Andy Cheng Date: Fri, 27 May 2022 14:36:48 +0800 Subject: [PATCH 01/18] strategy: prototype of supertrend strategy --- pkg/cmd/builtin.go | 1 + pkg/strategy/supertrend/strategy.go | 310 ++++++++++++++++++++++++++++ 2 files changed, 311 insertions(+) create mode 100644 pkg/strategy/supertrend/strategy.go diff --git a/pkg/cmd/builtin.go b/pkg/cmd/builtin.go index c8f9fbbc7..15616a22c 100644 --- a/pkg/cmd/builtin.go +++ b/pkg/cmd/builtin.go @@ -19,6 +19,7 @@ import ( _ "github.com/c9s/bbgo/pkg/strategy/rebalance" _ "github.com/c9s/bbgo/pkg/strategy/schedule" _ "github.com/c9s/bbgo/pkg/strategy/skeleton" + _ "github.com/c9s/bbgo/pkg/strategy/supertrend" _ "github.com/c9s/bbgo/pkg/strategy/support" _ "github.com/c9s/bbgo/pkg/strategy/swing" _ "github.com/c9s/bbgo/pkg/strategy/techsignal" diff --git a/pkg/strategy/supertrend/strategy.go b/pkg/strategy/supertrend/strategy.go new file mode 100644 index 000000000..e721d2632 --- /dev/null +++ b/pkg/strategy/supertrend/strategy.go @@ -0,0 +1,310 @@ +package supertrend + +import ( + "context" + "fmt" + "github.com/c9s/bbgo/pkg/bbgo" + "github.com/c9s/bbgo/pkg/fixedpoint" + "github.com/c9s/bbgo/pkg/indicator" + "github.com/c9s/bbgo/pkg/types" + "github.com/pkg/errors" + "github.com/sirupsen/logrus" + "math" +) + +// TODO: +// 1. Position control +// 2. Strategy control + +const ID = "supertrend" + +const stateKey = "state-v1" + +var NotionalModifier = fixedpoint.NewFromFloat(1.0001) + +var zeroiw = types.IntervalWindow{} + +var log = logrus.WithField("strategy", ID) + +func init() { + // Register the pointer of the strategy struct, + // so that bbgo knows what struct to be used to unmarshal the configs (YAML or JSON) + // Note: built-in strategies need to imported manually in the bbgo cmd package. + bbgo.RegisterStrategy(ID, &Strategy{}) +} + +type SuperTrend struct { + // AverageTrueRangeWindow ATR window for calculation of supertrend + AverageTrueRangeWindow types.IntervalWindow `json:"averageTrueRangeWindow"` + // AverageTrueRangeMultiplier ATR multiplier for calculation of supertrend + AverageTrueRangeMultiplier float64 `json:"averageTrueRangeMultiplier"` + + AverageTrueRange *indicator.ATR + + closePrice float64 + lastClosePrice float64 + uptrendPrice float64 + lastUptrendPrice float64 + downtrendPrice float64 + lastDowntrendPrice float64 + + trend types.Direction + lastTrend types.Direction + tradeSignal types.Direction +} + +// Update SuperTrend indicator +func (st *SuperTrend) Update(kline types.KLine) { + highPrice := kline.GetHigh().Float64() + lowPrice := kline.GetLow().Float64() + closePrice := kline.GetClose().Float64() + + // Update ATR + st.AverageTrueRange.Update(highPrice, lowPrice, closePrice) + + // Update last prices + st.lastUptrendPrice = st.uptrendPrice + st.lastDowntrendPrice = st.downtrendPrice + st.lastClosePrice = st.closePrice + st.lastTrend = st.trend + + st.closePrice = closePrice + + src := (highPrice + lowPrice) / 2 + + // Update uptrend + st.uptrendPrice = src - st.AverageTrueRange.Last()*st.AverageTrueRangeMultiplier + if st.lastClosePrice > st.lastUptrendPrice { + st.uptrendPrice = math.Max(st.uptrendPrice, st.lastUptrendPrice) + } + + // Update downtrend + st.downtrendPrice = src + st.AverageTrueRange.Last()*st.AverageTrueRangeMultiplier + if st.lastClosePrice < st.lastDowntrendPrice { + st.downtrendPrice = math.Min(st.downtrendPrice, st.lastDowntrendPrice) + } + + // Update trend + if st.lastTrend == types.DirectionUp && st.closePrice < st.lastUptrendPrice { + st.trend = types.DirectionDown + } else if st.lastTrend == types.DirectionDown && st.closePrice > st.lastDowntrendPrice { + st.trend = types.DirectionUp + } else { + st.trend = st.lastTrend + } + + // Update signal + if st.trend == types.DirectionUp && st.lastTrend == types.DirectionDown { + st.tradeSignal = types.DirectionUp + } else if st.trend == types.DirectionDown && st.lastTrend == types.DirectionUp { + st.tradeSignal = types.DirectionDown + } else { + st.tradeSignal = types.DirectionNone + } +} + +// GetSignal returns SuperTrend signal +func (st *SuperTrend) GetSignal() types.Direction { + return st.tradeSignal +} + +type Strategy struct { + *bbgo.Graceful + *bbgo.Notifiability + *bbgo.Persistence + + Environment *bbgo.Environment + StandardIndicatorSet *bbgo.StandardIndicatorSet + session *bbgo.ExchangeSession + Market types.Market + + // persistence fields + Position *types.Position `json:"position,omitempty" persistence:"position"` + ProfitStats *types.ProfitStats `json:"profitStats,omitempty" persistence:"profit_stats"` + + // Order and trade + orderStore *bbgo.OrderStore + tradeCollector *bbgo.TradeCollector + // groupID is the group ID used for the strategy instance for canceling orders + groupID uint32 + + // Symbol is the market symbol you want to trade + Symbol string `json:"symbol"` + + // Interval is how long do you want to update your order price and quantity + Interval types.Interval `json:"interval"` + + // FastDEMA DEMA window for checking breakout + FastDEMAWindow types.IntervalWindow `json:"fastDEMAWindow"` + // SlowDEMA DEMA window for checking breakout + SlowDEMAWindow types.IntervalWindow `json:"slowDEMAWindow"` + FastDEMA *indicator.DEMA + SlowDEMA *indicator.DEMA + + // SuperTrend indicator + SuperTrend SuperTrend `json:"superTrend"` + + bbgo.QuantityOrAmount + + // StrategyController + bbgo.StrategyController +} + +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 len(s.Symbol) == 0 { + return errors.New("symbol is required") + } + + // TODO: Validate DEMA window and ATR window + return nil +} + +func (s *Strategy) Subscribe(session *bbgo.ExchangeSession) { + session.Subscribe(types.KLineChannel, s.Symbol, types.SubscribeOptions{Interval: s.Interval}) + + if s.FastDEMAWindow != zeroiw { + session.Subscribe(types.KLineChannel, s.Symbol, types.SubscribeOptions{Interval: s.FastDEMAWindow.Interval}) + } + + if s.SlowDEMAWindow != zeroiw { + session.Subscribe(types.KLineChannel, s.Symbol, types.SubscribeOptions{Interval: s.SlowDEMAWindow.Interval}) + } + + session.Subscribe(types.KLineChannel, s.Symbol, types.SubscribeOptions{Interval: s.SuperTrend.AverageTrueRangeWindow.Interval}) +} + +func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, session *bbgo.ExchangeSession) error { + s.session = session + s.Market, _ = session.Market(s.Symbol) + + // If position is nil, we need to allocate a new position for calculation + if s.Position == nil { + s.Position = types.NewPositionFromMarket(s.Market) + } + // Always update the position fields + s.Position.Strategy = ID + s.Position.StrategyInstanceID = s.InstanceID() + + if s.ProfitStats == nil { + s.ProfitStats = types.NewProfitStats(s.Market) + } + + s.orderStore = bbgo.NewOrderStore(s.Symbol) + s.orderStore.BindStream(session.UserDataStream) + + // StrategyController + s.Status = types.StrategyStatusRunning + + // Setup indicators + if s.FastDEMAWindow != zeroiw { + s.FastDEMA = &indicator.DEMA{IntervalWindow: s.FastDEMAWindow} + } + if s.SlowDEMAWindow != zeroiw { + s.SlowDEMA = &indicator.DEMA{IntervalWindow: s.SlowDEMAWindow} + } + s.SuperTrend.AverageTrueRange = &indicator.ATR{IntervalWindow: s.SuperTrend.AverageTrueRangeWindow} + s.SuperTrend.trend = types.DirectionUp + // TODO: Use initializer + + session.MarketDataStream.OnKLineClosed(func(kline types.KLine) { + // skip k-lines from other symbols + if kline.Symbol != s.Symbol { + return + } + + closePrice := kline.GetClose().Float64() + openPrice := kline.GetOpen().Float64() + + // Update indicators + if kline.Interval == s.FastDEMA.Interval { + s.FastDEMA.Update(closePrice) + } + if kline.Interval == s.SlowDEMA.Interval { + s.SlowDEMA.Update(closePrice) + } + if kline.Interval == s.SuperTrend.AverageTrueRange.Interval { + s.SuperTrend.Update(kline) + } + + if kline.Symbol != s.Symbol || kline.Interval != s.Interval { + return + } + + // Get signals + stSignal := s.SuperTrend.GetSignal() + var demaSignal types.Direction + if closePrice > s.FastDEMA.Last() && closePrice > s.SlowDEMA.Last() && !(openPrice > s.FastDEMA.Last() && openPrice > s.SlowDEMA.Last()) { + demaSignal = types.DirectionUp + } else if closePrice < s.FastDEMA.Last() && closePrice < s.SlowDEMA.Last() && !(openPrice < s.FastDEMA.Last() && openPrice < s.SlowDEMA.Last()) { + demaSignal = types.DirectionDown + } else { + demaSignal = types.DirectionNone + } + + // TODO: TP/SL + + // Place order + var side types.SideType + if stSignal == types.DirectionUp && demaSignal == types.DirectionUp { + side = types.SideTypeBuy + } else if stSignal == types.DirectionDown && demaSignal == types.DirectionDown { + side = types.SideTypeSell + } + quantity := s.CalculateQuantity(fixedpoint.NewFromFloat(closePrice)) + + if side == types.SideTypeSell || side == types.SideTypeBuy { + orderForm := types.SubmitOrder{ + Symbol: s.Symbol, + Market: s.Market, + Side: side, + Type: types.OrderTypeMarket, + Quantity: quantity, + } + log.Infof("%v", orderForm) + + createdOrder, err := s.session.Exchange.SubmitOrders(ctx, orderForm) + if err != nil { + log.WithError(err).Errorf("can not place order") + } + s.orderStore.Add(createdOrder...) + } + + // check if there is a canceled order had partially filled. + s.tradeCollector.Process() + }) + + s.tradeCollector = bbgo.NewTradeCollector(s.Symbol, s.Position, s.orderStore) + + s.tradeCollector.OnTrade(func(trade types.Trade, profit, netProfit fixedpoint.Value) { + s.Notifiability.Notify(trade) + s.ProfitStats.AddTrade(trade) + + if profit.Compare(fixedpoint.Zero) == 0 { + s.Environment.RecordPosition(s.Position, trade, nil) + } else { + log.Infof("%s generated profit: %v", s.Symbol, profit) + p := s.Position.NewProfit(trade, profit, netProfit) + p.Strategy = ID + p.StrategyInstanceID = s.InstanceID() + s.Notify(&p) + + s.ProfitStats.AddProfit(p) + s.Notify(&s.ProfitStats) + + s.Environment.RecordPosition(s.Position, trade, &p) + } + log.Infof("%v", s.Position) + }) + + s.tradeCollector.BindStream(session.UserDataStream) + + return nil +} From 39b0013513e94d6b73161bf333c044161c1fad44 Mon Sep 17 00:00:00 2001 From: Andy Cheng Date: Fri, 27 May 2022 18:24:08 +0800 Subject: [PATCH 02/18] strategy: supertrend strategy tp/sl --- pkg/strategy/supertrend/strategy.go | 49 +++++++++++++++++++++++------ 1 file changed, 40 insertions(+), 9 deletions(-) diff --git a/pkg/strategy/supertrend/strategy.go b/pkg/strategy/supertrend/strategy.go index e721d2632..7cee0ce3f 100644 --- a/pkg/strategy/supertrend/strategy.go +++ b/pkg/strategy/supertrend/strategy.go @@ -113,10 +113,9 @@ type Strategy struct { *bbgo.Notifiability *bbgo.Persistence - Environment *bbgo.Environment - StandardIndicatorSet *bbgo.StandardIndicatorSet - session *bbgo.ExchangeSession - Market types.Market + Environment *bbgo.Environment + session *bbgo.ExchangeSession + Market types.Market // persistence fields Position *types.Position `json:"position,omitempty" persistence:"position"` @@ -249,7 +248,40 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se demaSignal = types.DirectionNone } - // TODO: TP/SL + // TP/SL + base := s.Position.GetBase() + quantity := base.Abs() + if quantity.Compare(s.Market.MinQuantity) > 0 && quantity.Mul(kline.GetClose()).Compare(s.Market.MinNotional) > 0 { + if base.Sign() < 0 && (stSignal == types.DirectionUp || demaSignal == types.DirectionUp) { + orderForm := types.SubmitOrder{ + Symbol: s.Symbol, + Market: s.Market, + Side: types.SideTypeBuy, + Type: types.OrderTypeMarket, + Quantity: quantity, + } + log.Infof("submit TP/SL order %v", orderForm) + createdOrder, err := s.session.Exchange.SubmitOrders(ctx, orderForm) + if err != nil { + log.WithError(err).Errorf("can not place TP/SL order") + } + s.orderStore.Add(createdOrder...) + } else if base.Sign() > 0 && (stSignal == types.DirectionDown || demaSignal == types.DirectionDown) { + orderForm := types.SubmitOrder{ + Symbol: s.Symbol, + Market: s.Market, + Side: types.SideTypeSell, + Type: types.OrderTypeMarket, + Quantity: quantity, + } + log.Infof("submit TP/SL order %v", orderForm) + createdOrder, err := s.session.Exchange.SubmitOrders(ctx, orderForm) + if err != nil { + log.WithError(err).Errorf("can not place TP/SL order") + } + s.orderStore.Add(createdOrder...) + } + } // Place order var side types.SideType @@ -258,17 +290,17 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se } else if stSignal == types.DirectionDown && demaSignal == types.DirectionDown { side = types.SideTypeSell } - quantity := s.CalculateQuantity(fixedpoint.NewFromFloat(closePrice)) + balance, _ := s.session.GetAccount().Balance(s.Market.QuoteCurrency) + s.Amount = balance.Available if side == types.SideTypeSell || side == types.SideTypeBuy { orderForm := types.SubmitOrder{ Symbol: s.Symbol, Market: s.Market, Side: side, Type: types.OrderTypeMarket, - Quantity: quantity, + Quantity: s.CalculateQuantity(fixedpoint.NewFromFloat(closePrice)), } - log.Infof("%v", orderForm) createdOrder, err := s.session.Exchange.SubmitOrders(ctx, orderForm) if err != nil { @@ -301,7 +333,6 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se s.Environment.RecordPosition(s.Position, trade, &p) } - log.Infof("%v", s.Position) }) s.tradeCollector.BindStream(session.UserDataStream) From 1d24379c17cd2853c09cafd5177b90eff08cfbaf Mon Sep 17 00:00:00 2001 From: Andy Cheng Date: Mon, 30 May 2022 14:52:51 +0800 Subject: [PATCH 03/18] strategy: refactor supertrend sconfig --- pkg/strategy/supertrend/strategy.go | 38 ++++++++++++++++------------- 1 file changed, 21 insertions(+), 17 deletions(-) diff --git a/pkg/strategy/supertrend/strategy.go b/pkg/strategy/supertrend/strategy.go index 7cee0ce3f..e4e12d89d 100644 --- a/pkg/strategy/supertrend/strategy.go +++ b/pkg/strategy/supertrend/strategy.go @@ -35,7 +35,7 @@ func init() { type SuperTrend struct { // AverageTrueRangeWindow ATR window for calculation of supertrend - AverageTrueRangeWindow types.IntervalWindow `json:"averageTrueRangeWindow"` + AverageTrueRangeWindow int `json:"averageTrueRangeWindow"` // AverageTrueRangeMultiplier ATR multiplier for calculation of supertrend AverageTrueRangeMultiplier float64 `json:"averageTrueRangeMultiplier"` @@ -134,9 +134,9 @@ type Strategy struct { Interval types.Interval `json:"interval"` // FastDEMA DEMA window for checking breakout - FastDEMAWindow types.IntervalWindow `json:"fastDEMAWindow"` + FastDEMAWindow int `json:"fastDEMAWindow"` // SlowDEMA DEMA window for checking breakout - SlowDEMAWindow types.IntervalWindow `json:"slowDEMAWindow"` + SlowDEMAWindow int `json:"slowDEMAWindow"` FastDEMA *indicator.DEMA SlowDEMA *indicator.DEMA @@ -168,16 +168,28 @@ func (s *Strategy) Validate() error { func (s *Strategy) Subscribe(session *bbgo.ExchangeSession) { session.Subscribe(types.KLineChannel, s.Symbol, types.SubscribeOptions{Interval: s.Interval}) +} - if s.FastDEMAWindow != zeroiw { - session.Subscribe(types.KLineChannel, s.Symbol, types.SubscribeOptions{Interval: s.FastDEMAWindow.Interval}) +// SetupIndicators initializes indicators +func (s *Strategy) SetupIndicators() { + if s.FastDEMAWindow == 0 { + s.FastDEMAWindow = 144 } + s.FastDEMA = &indicator.DEMA{IntervalWindow: types.IntervalWindow{Interval: s.Interval, Window: s.FastDEMAWindow}} - if s.SlowDEMAWindow != zeroiw { - session.Subscribe(types.KLineChannel, s.Symbol, types.SubscribeOptions{Interval: s.SlowDEMAWindow.Interval}) + if s.SlowDEMAWindow == 0 { + s.SlowDEMAWindow = 169 } + s.SlowDEMA = &indicator.DEMA{IntervalWindow: types.IntervalWindow{Interval: s.Interval, Window: s.SlowDEMAWindow}} - session.Subscribe(types.KLineChannel, s.Symbol, types.SubscribeOptions{Interval: s.SuperTrend.AverageTrueRangeWindow.Interval}) + if s.SuperTrend.AverageTrueRangeWindow == 0 { + s.SuperTrend.AverageTrueRangeWindow = 43 + } + s.SuperTrend.AverageTrueRange = &indicator.ATR{IntervalWindow: types.IntervalWindow{Window: s.SuperTrend.AverageTrueRangeWindow, Interval: s.Interval}} + s.SuperTrend.trend = types.DirectionUp + if s.SuperTrend.AverageTrueRangeMultiplier == 0 { + s.SuperTrend.AverageTrueRangeMultiplier = 4 + } } func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, session *bbgo.ExchangeSession) error { @@ -203,15 +215,7 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se s.Status = types.StrategyStatusRunning // Setup indicators - if s.FastDEMAWindow != zeroiw { - s.FastDEMA = &indicator.DEMA{IntervalWindow: s.FastDEMAWindow} - } - if s.SlowDEMAWindow != zeroiw { - s.SlowDEMA = &indicator.DEMA{IntervalWindow: s.SlowDEMAWindow} - } - s.SuperTrend.AverageTrueRange = &indicator.ATR{IntervalWindow: s.SuperTrend.AverageTrueRangeWindow} - s.SuperTrend.trend = types.DirectionUp - // TODO: Use initializer + s.SetupIndicators() session.MarketDataStream.OnKLineClosed(func(kline types.KLine) { // skip k-lines from other symbols From 0e1e5369f248e8de61c7652773ffcdc3c5636d4e Mon Sep 17 00:00:00 2001 From: Andy Cheng Date: Mon, 30 May 2022 16:07:36 +0800 Subject: [PATCH 04/18] strategy: leverage parameter --- pkg/strategy/supertrend/strategy.go | 118 ++++++++++++++-------------- 1 file changed, 61 insertions(+), 57 deletions(-) diff --git a/pkg/strategy/supertrend/strategy.go b/pkg/strategy/supertrend/strategy.go index e4e12d89d..54b2341e5 100644 --- a/pkg/strategy/supertrend/strategy.go +++ b/pkg/strategy/supertrend/strategy.go @@ -22,8 +22,6 @@ const stateKey = "state-v1" var NotionalModifier = fixedpoint.NewFromFloat(1.0001) -var zeroiw = types.IntervalWindow{} - var log = logrus.WithField("strategy", ID) func init() { @@ -143,6 +141,9 @@ type Strategy struct { // SuperTrend indicator SuperTrend SuperTrend `json:"superTrend"` + // Leverage + Leverage float64 `json:"leverage"` + bbgo.QuantityOrAmount // StrategyController @@ -192,6 +193,43 @@ func (s *Strategy) SetupIndicators() { } } +// UpdateIndicators updates indicators +func (s *Strategy) UpdateIndicators(kline types.KLine) { + closePrice := kline.GetClose().Float64() + + // Update indicators + if kline.Interval == s.FastDEMA.Interval { + s.FastDEMA.Update(closePrice) + } + if kline.Interval == s.SlowDEMA.Interval { + s.SlowDEMA.Update(closePrice) + } + if kline.Interval == s.SuperTrend.AverageTrueRange.Interval { + s.SuperTrend.Update(kline) + } +} + +func (s *Strategy) GenerateOrderForm(side types.SideType, quantity fixedpoint.Value) types.SubmitOrder { + orderForm := types.SubmitOrder{ + Symbol: s.Symbol, + Market: s.Market, + Side: side, + Type: types.OrderTypeMarket, + Quantity: quantity, + } + + return orderForm +} + +// CalculateQuantity returns leveraged quantity +func (s *Strategy) CalculateQuantity(currentPrice fixedpoint.Value) fixedpoint.Value { + balance, _ := s.session.GetAccount().Balance(s.Market.QuoteCurrency) + amountAvailable := balance.Available.Mul(fixedpoint.NewFromFloat(s.Leverage)) + quantity := amountAvailable.Div(currentPrice) + + return quantity +} + func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, session *bbgo.ExchangeSession) error { s.session = session s.Market, _ = session.Market(s.Symbol) @@ -218,30 +256,17 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se s.SetupIndicators() session.MarketDataStream.OnKLineClosed(func(kline types.KLine) { - // skip k-lines from other symbols - if kline.Symbol != s.Symbol { - return - } - - closePrice := kline.GetClose().Float64() - openPrice := kline.GetOpen().Float64() - - // Update indicators - if kline.Interval == s.FastDEMA.Interval { - s.FastDEMA.Update(closePrice) - } - if kline.Interval == s.SlowDEMA.Interval { - s.SlowDEMA.Update(closePrice) - } - if kline.Interval == s.SuperTrend.AverageTrueRange.Interval { - s.SuperTrend.Update(kline) - } - + // skip k-lines from other symbols or other intervals if kline.Symbol != s.Symbol || kline.Interval != s.Interval { return } + // Update indicators + s.UpdateIndicators(kline) + // Get signals + closePrice := kline.GetClose().Float64() + openPrice := kline.GetOpen().Float64() stSignal := s.SuperTrend.GetSignal() var demaSignal types.Direction if closePrice > s.FastDEMA.Last() && closePrice > s.SlowDEMA.Last() && !(openPrice > s.FastDEMA.Last() && openPrice > s.SlowDEMA.Last()) { @@ -256,38 +281,24 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se base := s.Position.GetBase() quantity := base.Abs() if quantity.Compare(s.Market.MinQuantity) > 0 && quantity.Mul(kline.GetClose()).Compare(s.Market.MinNotional) > 0 { + var side types.SideType if base.Sign() < 0 && (stSignal == types.DirectionUp || demaSignal == types.DirectionUp) { - orderForm := types.SubmitOrder{ - Symbol: s.Symbol, - Market: s.Market, - Side: types.SideTypeBuy, - Type: types.OrderTypeMarket, - Quantity: quantity, - } - log.Infof("submit TP/SL order %v", orderForm) - createdOrder, err := s.session.Exchange.SubmitOrders(ctx, orderForm) - if err != nil { - log.WithError(err).Errorf("can not place TP/SL order") - } - s.orderStore.Add(createdOrder...) + side = types.SideTypeBuy } else if base.Sign() > 0 && (stSignal == types.DirectionDown || demaSignal == types.DirectionDown) { - orderForm := types.SubmitOrder{ - Symbol: s.Symbol, - Market: s.Market, - Side: types.SideTypeSell, - Type: types.OrderTypeMarket, - Quantity: quantity, - } + side = types.SideTypeSell + } + if side == types.SideTypeBuy || side == types.SideTypeSell { + orderForm := s.GenerateOrderForm(side, quantity) log.Infof("submit TP/SL order %v", orderForm) - createdOrder, err := s.session.Exchange.SubmitOrders(ctx, orderForm) + order, err := s.session.Exchange.SubmitOrders(ctx, orderForm) if err != nil { log.WithError(err).Errorf("can not place TP/SL order") } - s.orderStore.Add(createdOrder...) + s.orderStore.Add(order...) } } - // Place order + // Open position var side types.SideType if stSignal == types.DirectionUp && demaSignal == types.DirectionUp { side = types.SideTypeBuy @@ -295,22 +306,14 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se side = types.SideTypeSell } - balance, _ := s.session.GetAccount().Balance(s.Market.QuoteCurrency) - s.Amount = balance.Available if side == types.SideTypeSell || side == types.SideTypeBuy { - orderForm := types.SubmitOrder{ - Symbol: s.Symbol, - Market: s.Market, - Side: side, - Type: types.OrderTypeMarket, - Quantity: s.CalculateQuantity(fixedpoint.NewFromFloat(closePrice)), - } - - createdOrder, err := s.session.Exchange.SubmitOrders(ctx, orderForm) + orderForm := s.GenerateOrderForm(side, s.CalculateQuantity(kline.GetClose())) + log.Infof("submit open position order %v", orderForm) + order, err := s.session.Exchange.SubmitOrders(ctx, orderForm) if err != nil { - log.WithError(err).Errorf("can not place order") + log.WithError(err).Errorf("can not place open position order") } - s.orderStore.Add(createdOrder...) + s.orderStore.Add(order...) } // check if there is a canceled order had partially filled. @@ -319,6 +322,7 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se s.tradeCollector = bbgo.NewTradeCollector(s.Symbol, s.Position, s.orderStore) + // Record profits s.tradeCollector.OnTrade(func(trade types.Trade, profit, netProfit fixedpoint.Value) { s.Notifiability.Notify(trade) s.ProfitStats.AddTrade(trade) From 07fe68d7408f4a1632a3921f9ddfb67cc8dfcf32 Mon Sep 17 00:00:00 2001 From: Andy Cheng Date: Mon, 30 May 2022 16:22:13 +0800 Subject: [PATCH 05/18] strategy: Validate() --- pkg/strategy/supertrend/strategy.go | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/pkg/strategy/supertrend/strategy.go b/pkg/strategy/supertrend/strategy.go index 54b2341e5..9fa2a81bf 100644 --- a/pkg/strategy/supertrend/strategy.go +++ b/pkg/strategy/supertrend/strategy.go @@ -20,8 +20,6 @@ const ID = "supertrend" const stateKey = "state-v1" -var NotionalModifier = fixedpoint.NewFromFloat(1.0001) - var log = logrus.WithField("strategy", ID) func init() { @@ -163,7 +161,14 @@ func (s *Strategy) Validate() error { return errors.New("symbol is required") } - // TODO: Validate DEMA window and ATR window + if len(s.Interval) == 0 { + return errors.New("interval is required") + } + + if s.Leverage == 0.0 { + return errors.New("leverage is required") + } + return nil } @@ -184,12 +189,12 @@ func (s *Strategy) SetupIndicators() { s.SlowDEMA = &indicator.DEMA{IntervalWindow: types.IntervalWindow{Interval: s.Interval, Window: s.SlowDEMAWindow}} if s.SuperTrend.AverageTrueRangeWindow == 0 { - s.SuperTrend.AverageTrueRangeWindow = 43 + s.SuperTrend.AverageTrueRangeWindow = 39 } s.SuperTrend.AverageTrueRange = &indicator.ATR{IntervalWindow: types.IntervalWindow{Window: s.SuperTrend.AverageTrueRangeWindow, Interval: s.Interval}} s.SuperTrend.trend = types.DirectionUp if s.SuperTrend.AverageTrueRangeMultiplier == 0 { - s.SuperTrend.AverageTrueRangeMultiplier = 4 + s.SuperTrend.AverageTrueRangeMultiplier = 3 } } @@ -290,7 +295,7 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se if side == types.SideTypeBuy || side == types.SideTypeSell { orderForm := s.GenerateOrderForm(side, quantity) log.Infof("submit TP/SL order %v", orderForm) - order, err := s.session.Exchange.SubmitOrders(ctx, orderForm) + order, err := orderExecutor.SubmitOrders(ctx, orderForm) if err != nil { log.WithError(err).Errorf("can not place TP/SL order") } @@ -309,7 +314,7 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se if side == types.SideTypeSell || side == types.SideTypeBuy { orderForm := s.GenerateOrderForm(side, s.CalculateQuantity(kline.GetClose())) log.Infof("submit open position order %v", orderForm) - order, err := s.session.Exchange.SubmitOrders(ctx, orderForm) + order, err := orderExecutor.SubmitOrders(ctx, orderForm) if err != nil { log.WithError(err).Errorf("can not place open position order") } From 44469ed3aa41a1d9ee86c74465f477be7d893716 Mon Sep 17 00:00:00 2001 From: Andy Cheng Date: Mon, 30 May 2022 16:26:17 +0800 Subject: [PATCH 06/18] strategy: supertrend position control --- pkg/strategy/supertrend/strategy.go | 40 ++++++++++++++++++++++++++--- 1 file changed, 37 insertions(+), 3 deletions(-) diff --git a/pkg/strategy/supertrend/strategy.go b/pkg/strategy/supertrend/strategy.go index 9fa2a81bf..2e70e1b7e 100644 --- a/pkg/strategy/supertrend/strategy.go +++ b/pkg/strategy/supertrend/strategy.go @@ -12,9 +12,7 @@ import ( "math" ) -// TODO: -// 1. Position control -// 2. Strategy control +// TODO: Strategy control const ID = "supertrend" @@ -176,6 +174,42 @@ func (s *Strategy) Subscribe(session *bbgo.ExchangeSession) { session.Subscribe(types.KLineChannel, s.Symbol, types.SubscribeOptions{Interval: s.Interval}) } +// Position control + +func (s *Strategy) CurrentPosition() *types.Position { + return s.Position +} + +func (s *Strategy) ClosePosition(ctx context.Context, percentage fixedpoint.Value) error { + base := s.Position.GetBase() + if base.IsZero() { + return fmt.Errorf("no opened %s position", s.Position.Symbol) + } + + // make it negative + quantity := base.Mul(percentage).Abs() + side := types.SideTypeBuy + if base.Sign() > 0 { + side = types.SideTypeSell + } + + if quantity.Compare(s.Market.MinQuantity) < 0 { + return fmt.Errorf("order quantity %v is too small, less than %v", quantity, s.Market.MinQuantity) + } + + orderForm := s.GenerateOrderForm(side, quantity) + + s.Notify("Submitting %s %s order to close position by %v", s.Symbol, side.String(), percentage, orderForm) + + createdOrders, err := s.session.Exchange.SubmitOrders(ctx, orderForm) + if err != nil { + log.WithError(err).Errorf("can not place position close order") + } + + s.orderStore.Add(createdOrders...) + return err +} + // SetupIndicators initializes indicators func (s *Strategy) SetupIndicators() { if s.FastDEMAWindow == 0 { From 756284378b49d73154505fd1fadbe7cf13972a44 Mon Sep 17 00:00:00 2001 From: Andy Cheng Date: Mon, 30 May 2022 16:35:10 +0800 Subject: [PATCH 07/18] strategy: supertrend strategy control --- pkg/strategy/supertrend/strategy.go | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/pkg/strategy/supertrend/strategy.go b/pkg/strategy/supertrend/strategy.go index 2e70e1b7e..8252d8554 100644 --- a/pkg/strategy/supertrend/strategy.go +++ b/pkg/strategy/supertrend/strategy.go @@ -207,6 +207,9 @@ func (s *Strategy) ClosePosition(ctx context.Context, percentage fixedpoint.Valu } s.orderStore.Add(createdOrders...) + + s.tradeCollector.Process() + return err } @@ -291,10 +294,26 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se // StrategyController s.Status = types.StrategyStatusRunning + s.OnSuspend(func() { + _ = s.Persistence.Sync(s) + }) + + s.OnEmergencyStop(func() { + // Close 100% position + _ = s.ClosePosition(ctx, fixedpoint.NewFromFloat(1.0)) + + _ = s.Persistence.Sync(s) + }) + // Setup indicators s.SetupIndicators() session.MarketDataStream.OnKLineClosed(func(kline types.KLine) { + // StrategyController + if s.Status != types.StrategyStatusRunning { + return + } + // skip k-lines from other symbols or other intervals if kline.Symbol != s.Symbol || kline.Interval != s.Interval { return @@ -355,7 +374,6 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se s.orderStore.Add(order...) } - // check if there is a canceled order had partially filled. s.tradeCollector.Process() }) From d72a4e8e9495ff9f6e5b56e0b9cb8a5b0702eb0e Mon Sep 17 00:00:00 2001 From: Andy Cheng Date: Mon, 30 May 2022 16:48:07 +0800 Subject: [PATCH 08/18] strategy: supertrend strategy config example --- config/supertrend.yaml | 48 +++++++++++++++++++++++++++++ pkg/strategy/supertrend/strategy.go | 14 +++++++++ 2 files changed, 62 insertions(+) create mode 100644 config/supertrend.yaml diff --git a/config/supertrend.yaml b/config/supertrend.yaml new file mode 100644 index 000000000..c3636b1fb --- /dev/null +++ b/config/supertrend.yaml @@ -0,0 +1,48 @@ +--- +persistence: + redis: + host: 127.0.0.1 + port: 6379 + db: 0 + +sessions: + binance: + exchange: binance + envVarPrefix: binance + +backtest: + sessions: [binance] + # for testing max draw down (MDD) at 03-12 + # see here for more details + # https://www.investopedia.com/terms/m/maximum-drawdown-mdd.asp + startTime: "2022-04-01" + endTime: "2022-04-30" + symbols: + - BTCUSDT + accounts: + binance: + makerCommission: 10 # 0.15% + takerCommission: 15 # 0.15% + balances: + BTC: 1.0 + USDT: 10000.0 + +exchangeStrategies: +- on: binance + supertrend: + symbol: BTCUSDT + + # interval is how long do you want to update your order price and quantity + interval: 1h + + # leverage is the leverage of the orders + leverage: 1 + + # fastDEMAWindow and slowDEMAWindow are for filtering super trend noise + fastDEMAWindow: 144 + slowDEMAWindow: 169 + + superTrend: + averageTrueRangeWindow: 39 + # ATR Multiplier for calculating super trend prices, the higher, the stronger the trends are + averageTrueRangeMultiplier: 3 diff --git a/pkg/strategy/supertrend/strategy.go b/pkg/strategy/supertrend/strategy.go index 8252d8554..7a6080206 100644 --- a/pkg/strategy/supertrend/strategy.go +++ b/pkg/strategy/supertrend/strategy.go @@ -10,6 +10,7 @@ import ( "github.com/pkg/errors" "github.com/sirupsen/logrus" "math" + "sync" ) // TODO: Strategy control @@ -121,6 +122,8 @@ type Strategy struct { // groupID is the group ID used for the strategy instance for canceling orders groupID uint32 + stopC chan struct{} + // Symbol is the market symbol you want to trade Symbol string `json:"symbol"` @@ -284,6 +287,9 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se s.Position.Strategy = ID s.Position.StrategyInstanceID = s.InstanceID() + s.stopC = make(chan struct{}) + + // Profit if s.ProfitStats == nil { s.ProfitStats = types.NewProfitStats(s.Market) } @@ -402,5 +408,13 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se s.tradeCollector.BindStream(session.UserDataStream) + // Graceful shutdown + s.Graceful.OnShutdown(func(ctx context.Context, wg *sync.WaitGroup) { + defer wg.Done() + close(s.stopC) + + s.tradeCollector.Process() + }) + return nil } From 7c98fca0c285338cd32629d1b0733b9612a3f1fe Mon Sep 17 00:00:00 2001 From: Andy Cheng Date: Mon, 30 May 2022 17:02:20 +0800 Subject: [PATCH 09/18] strategy: supertrend strategy doc --- README.md | 4 +++- config/supertrend.yaml | 2 +- doc/strategy/supertrend.md | 29 +++++++++++++++++++++++++++++ 3 files changed, 33 insertions(+), 2 deletions(-) create mode 100644 doc/strategy/supertrend.md diff --git a/README.md b/README.md index bb54ec542..f7cb4ba9e 100644 --- a/README.md +++ b/README.md @@ -312,7 +312,9 @@ Check out the strategy directory [strategy](pkg/strategy) for all built-in strat indicator [bollgrid](pkg/strategy/bollgrid) - `grid` strategy implements the fixed price band grid strategy [grid](pkg/strategy/grid). See [document](./doc/strategy/grid.md). -- `support` strategy implements the fixed price band grid strategy [support](pkg/strategy/support). See +- `supertrend` strategy uses Supertrend indicator as trend, and DEMA indicator as noise filter [supertrend](pkg/strategy/supertrend). See + [document](./doc/strategy/supertrend.md). +- `support` strategy uses K-lines with high volume as support [support](pkg/strategy/support). See [document](./doc/strategy/support.md). - `flashcrash` strategy implements a strategy that catches the flashcrash [flashcrash](pkg/strategy/flashcrash) diff --git a/config/supertrend.yaml b/config/supertrend.yaml index c3636b1fb..e763a3ef7 100644 --- a/config/supertrend.yaml +++ b/config/supertrend.yaml @@ -36,7 +36,7 @@ exchangeStrategies: interval: 1h # leverage is the leverage of the orders - leverage: 1 + leverage: 1.0 # fastDEMAWindow and slowDEMAWindow are for filtering super trend noise fastDEMAWindow: 144 diff --git a/doc/strategy/supertrend.md b/doc/strategy/supertrend.md new file mode 100644 index 000000000..71c1a39f2 --- /dev/null +++ b/doc/strategy/supertrend.md @@ -0,0 +1,29 @@ +### Supertrend Strategy + +This strategy uses Supertrend indicator as trend, and DEMA indicator as noise filter. +This strategy needs margin enabled in order to submit short orders, but you can use `leverage` parameter to limit your risk. + + +#### Parameters + +- `symbol` + - The trading pair symbol, e.g., `BTCUSDT`, `ETHUSDT` +- `interval` + - The K-line interval, e.g., `5m`, `1h` +- `leverage` + - The leverage of the orders. +- `fastDEMAWindow` + - The MA window of the fast DEMA. +- `slowDEMAWindow` + - The MA window of the slow DEMA. +- `superTrend` + - Supertrend indicator for deciding current trend. + - `averageTrueRangeWindow` + - The MA window of the ATR indicator used by Supertrend. + - `averageTrueRangeMultiplier` + - Multiplier for calculating upper and lower bond prices, the higher, the stronger the trends are, but also makes it less sensitive. + + +#### Examples + +See [supertrend.yaml](../../config/supertrend.yaml) \ No newline at end of file From a5124c743f74ab6aac6cc03f952850391148c696 Mon Sep 17 00:00:00 2001 From: Andy Cheng Date: Tue, 31 May 2022 12:53:14 +0800 Subject: [PATCH 10/18] strategy: supertrend strategy TP/SL --- config/supertrend.yaml | 9 +++ doc/strategy/supertrend.md | 6 ++ pkg/strategy/supertrend/strategy.go | 104 ++++++++++++++++++++++------ 3 files changed, 96 insertions(+), 23 deletions(-) diff --git a/config/supertrend.yaml b/config/supertrend.yaml index e763a3ef7..6bacf46b8 100644 --- a/config/supertrend.yaml +++ b/config/supertrend.yaml @@ -46,3 +46,12 @@ exchangeStrategies: averageTrueRangeWindow: 39 # ATR Multiplier for calculating super trend prices, the higher, the stronger the trends are averageTrueRangeMultiplier: 3 + + # TP according to ATR multiple, 0 to disable this + takeProfitMultiplier: 3 + + # Set SL price to the low of the triggering Kline + stopLossByTriggeringK: true + + # TP/SL by reversed signals + tpslBySignal: true diff --git a/doc/strategy/supertrend.md b/doc/strategy/supertrend.md index 71c1a39f2..507a87b1f 100644 --- a/doc/strategy/supertrend.md +++ b/doc/strategy/supertrend.md @@ -22,6 +22,12 @@ This strategy needs margin enabled in order to submit short orders, but you can - The MA window of the ATR indicator used by Supertrend. - `averageTrueRangeMultiplier` - Multiplier for calculating upper and lower bond prices, the higher, the stronger the trends are, but also makes it less sensitive. +- `takeProfitMultiplier` + - TP according to ATR multiple, 0 to disable this. +- `stopLossByTriggeringK` + - Set SL price to the low of the triggering Kline. +- `tpslBySignal` + - TP/SL by reversed signals. #### Examples diff --git a/pkg/strategy/supertrend/strategy.go b/pkg/strategy/supertrend/strategy.go index 7a6080206..453bd91fc 100644 --- a/pkg/strategy/supertrend/strategy.go +++ b/pkg/strategy/supertrend/strategy.go @@ -13,7 +13,8 @@ import ( "sync" ) -// TODO: Strategy control +// TODO: Margin side effect +// TODO: Update balance const ID = "supertrend" @@ -143,7 +144,17 @@ type Strategy struct { // Leverage Leverage float64 `json:"leverage"` - bbgo.QuantityOrAmount + // TakeProfitMultiplier TP according to ATR multiple, 0 to disable this + TakeProfitMultiplier float64 `json:"takeProfitMultiplier"` + + // StopLossByTriggeringK Set SL price to the low of the triggering Kline + StopLossByTriggeringK bool `json:"stopLossByTriggeringK"` + + // TPSLBySignal TP/SL by reversed signals + TPSLBySignal bool `json:"tpslBySignal"` + + currentTakeProfitPrice fixedpoint.Value + currentStopLossPrice fixedpoint.Value // StrategyController bbgo.StrategyController @@ -275,6 +286,15 @@ func (s *Strategy) CalculateQuantity(currentPrice fixedpoint.Value) fixedpoint.V return quantity } +func (s *Strategy) HasTradableBase(currentPrice fixedpoint.Value) bool { + base := s.Position.GetBase() + quantity := base.Abs() + if quantity.Compare(s.Market.MinQuantity) > 0 && quantity.Mul(currentPrice).Compare(s.Market.MinNotional) > 0 { + return true + } + return false +} + func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, session *bbgo.ExchangeSession) error { s.session = session s.Market, _ = session.Market(s.Symbol) @@ -306,7 +326,9 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se s.OnEmergencyStop(func() { // Close 100% position - _ = s.ClosePosition(ctx, fixedpoint.NewFromFloat(1.0)) + if err := s.ClosePosition(ctx, fixedpoint.One); err != nil { + s.Notify("can not close position") + } _ = s.Persistence.Sync(s) }) @@ -314,6 +336,9 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se // Setup indicators s.SetupIndicators() + s.currentStopLossPrice = fixedpoint.Zero + s.currentTakeProfitPrice = fixedpoint.Zero + session.MarketDataStream.OnKLineClosed(func(kline types.KLine) { // StrategyController if s.Status != types.StrategyStatusRunning { @@ -341,24 +366,35 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se demaSignal = types.DirectionNone } - // TP/SL - base := s.Position.GetBase() - quantity := base.Abs() - if quantity.Compare(s.Market.MinQuantity) > 0 && quantity.Mul(kline.GetClose()).Compare(s.Market.MinNotional) > 0 { - var side types.SideType - if base.Sign() < 0 && (stSignal == types.DirectionUp || demaSignal == types.DirectionUp) { - side = types.SideTypeBuy - } else if base.Sign() > 0 && (stSignal == types.DirectionDown || demaSignal == types.DirectionDown) { - side = types.SideTypeSell - } - if side == types.SideTypeBuy || side == types.SideTypeSell { - orderForm := s.GenerateOrderForm(side, quantity) - log.Infof("submit TP/SL order %v", orderForm) - order, err := orderExecutor.SubmitOrders(ctx, orderForm) - if err != nil { - log.WithError(err).Errorf("can not place TP/SL order") + // TP/SL if there's non-dust position + if s.HasTradableBase(kline.GetClose()) { + baseSign := s.Position.GetBase().Sign() + if s.StopLossByTriggeringK && !s.currentStopLossPrice.IsZero() && ((baseSign < 0 && kline.GetClose().Compare(s.currentStopLossPrice) > 0) || (baseSign > 0 && kline.GetClose().Compare(s.currentStopLossPrice) < 0)) { + // SL by triggered Kline low + if err := s.ClosePosition(ctx, fixedpoint.One); err != nil { + s.Notify("can not place SL order") + } else { + s.currentStopLossPrice = fixedpoint.Zero + s.currentTakeProfitPrice = fixedpoint.Zero + } + } else if s.TakeProfitMultiplier > 0 && !s.currentTakeProfitPrice.IsZero() && ((baseSign < 0 && kline.GetClose().Compare(s.currentTakeProfitPrice) < 0) || (baseSign > 0 && kline.GetClose().Compare(s.currentTakeProfitPrice) > 0)) { + // TP by multiple of ATR + if err := s.ClosePosition(ctx, fixedpoint.One); err != nil { + s.Notify("can not place TP order") + } else { + s.currentStopLossPrice = fixedpoint.Zero + s.currentTakeProfitPrice = fixedpoint.Zero + } + } else if s.TPSLBySignal { + // Use signals to TP/SL + if (baseSign < 0 && (stSignal == types.DirectionUp || demaSignal == types.DirectionUp)) || (baseSign > 0 && (stSignal == types.DirectionDown || demaSignal == types.DirectionDown)) { + if err := s.ClosePosition(ctx, fixedpoint.One); err != nil { + s.Notify("can not place TP/SL order") + } else { + s.currentStopLossPrice = fixedpoint.Zero + s.currentTakeProfitPrice = fixedpoint.Zero + } } - s.orderStore.Add(order...) } } @@ -366,21 +402,43 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se var side types.SideType if stSignal == types.DirectionUp && demaSignal == types.DirectionUp { side = types.SideTypeBuy + if s.StopLossByTriggeringK { + s.currentStopLossPrice = kline.GetLow() + } + if s.TakeProfitMultiplier > 0 { + s.currentTakeProfitPrice = kline.GetClose().Add(fixedpoint.NewFromFloat(s.SuperTrend.AverageTrueRange.Last() * s.TakeProfitMultiplier)) + } } else if stSignal == types.DirectionDown && demaSignal == types.DirectionDown { side = types.SideTypeSell + if s.StopLossByTriggeringK { + s.currentStopLossPrice = kline.GetHigh() + } + if s.TakeProfitMultiplier > 0 { + s.currentTakeProfitPrice = kline.GetClose().Sub(fixedpoint.NewFromFloat(s.SuperTrend.AverageTrueRange.Last() * s.TakeProfitMultiplier)) + } } if side == types.SideTypeSell || side == types.SideTypeBuy { + baseSign := s.Position.GetBase().Sign() + // Close opposite position if any + if s.HasTradableBase(kline.GetClose()) && ((side == types.SideTypeSell && baseSign > 0) || (side == types.SideTypeBuy && baseSign < 0)) { + if err := s.ClosePosition(ctx, fixedpoint.One); err != nil { + s.Notify("can not place position close order") + } + } + orderForm := s.GenerateOrderForm(side, s.CalculateQuantity(kline.GetClose())) log.Infof("submit open position order %v", orderForm) order, err := orderExecutor.SubmitOrders(ctx, orderForm) if err != nil { log.WithError(err).Errorf("can not place open position order") + s.Notify("can not place open position order") + } else { + s.orderStore.Add(order...) } - s.orderStore.Add(order...) - } - s.tradeCollector.Process() + s.tradeCollector.Process() + } }) s.tradeCollector = bbgo.NewTradeCollector(s.Symbol, s.Position, s.orderStore) From 3421423cd66184e0bb0c468888e07604cae08c05 Mon Sep 17 00:00:00 2001 From: Andy Cheng Date: Tue, 31 May 2022 14:30:37 +0800 Subject: [PATCH 11/18] strategy: update balance for exchanges like FTX --- pkg/strategy/supertrend/strategy.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/pkg/strategy/supertrend/strategy.go b/pkg/strategy/supertrend/strategy.go index 453bd91fc..60da26c59 100644 --- a/pkg/strategy/supertrend/strategy.go +++ b/pkg/strategy/supertrend/strategy.go @@ -14,7 +14,6 @@ import ( ) // TODO: Margin side effect -// TODO: Update balance const ID = "supertrend" @@ -427,6 +426,12 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se } } + // Update balance for exchanges like FTX, which doesn't update balances automatically + balances, err := s.session.Exchange.QueryAccountBalances(ctx) + if err != nil { + s.session.GetAccount().UpdateBalances(balances) + } + orderForm := s.GenerateOrderForm(side, s.CalculateQuantity(kline.GetClose())) log.Infof("submit open position order %v", orderForm) order, err := orderExecutor.SubmitOrders(ctx, orderForm) From 6285e145a7bbb91bf976e315224d630ac071ac76 Mon Sep 17 00:00:00 2001 From: Andy Cheng Date: Tue, 31 May 2022 15:46:55 +0800 Subject: [PATCH 12/18] strategy: margin side effect --- config/supertrend.yaml | 3 +++ doc/README.md | 1 + doc/strategy/supertrend.md | 3 ++- pkg/strategy/supertrend/strategy.go | 19 +++++++++---------- 4 files changed, 15 insertions(+), 11 deletions(-) diff --git a/config/supertrend.yaml b/config/supertrend.yaml index 6bacf46b8..f9222df50 100644 --- a/config/supertrend.yaml +++ b/config/supertrend.yaml @@ -9,6 +9,9 @@ sessions: binance: exchange: binance envVarPrefix: binance + margin: true + isolatedMargin: true + isolatedMarginSymbol: BTCUSDT backtest: sessions: [binance] diff --git a/doc/README.md b/doc/README.md index bac675a10..ade7d4089 100644 --- a/doc/README.md +++ b/doc/README.md @@ -22,6 +22,7 @@ * [Grid](strategy/grid.md) - Grid Strategy Explanation * [Interaction](strategy/interaction.md) - Interaction registration for strategies * [Price Alert](strategy/pricealert.md) - Send price alert notification on price changes +* [Supertrend](strategy/supertrend.md) - Supertrend strategy uses Supertrend indicator as trend, and DEMA indicator as noise filter * [Support](strategy/support.md) - Support strategy that buys on high volume support ### Development diff --git a/doc/strategy/supertrend.md b/doc/strategy/supertrend.md index 507a87b1f..01421c94f 100644 --- a/doc/strategy/supertrend.md +++ b/doc/strategy/supertrend.md @@ -1,7 +1,8 @@ ### Supertrend Strategy This strategy uses Supertrend indicator as trend, and DEMA indicator as noise filter. -This strategy needs margin enabled in order to submit short orders, but you can use `leverage` parameter to limit your risk. +Supertrend strategy needs margin enabled in order to submit short orders, and you can use `leverage` parameter to limit your risk. +**Please note, using leverage higher than 1 is highly risky.** #### Parameters diff --git a/pkg/strategy/supertrend/strategy.go b/pkg/strategy/supertrend/strategy.go index 60da26c59..2c7dc07cf 100644 --- a/pkg/strategy/supertrend/strategy.go +++ b/pkg/strategy/supertrend/strategy.go @@ -13,8 +13,6 @@ import ( "sync" ) -// TODO: Margin side effect - const ID = "supertrend" const stateKey = "state-v1" @@ -210,7 +208,7 @@ func (s *Strategy) ClosePosition(ctx context.Context, percentage fixedpoint.Valu return fmt.Errorf("order quantity %v is too small, less than %v", quantity, s.Market.MinQuantity) } - orderForm := s.GenerateOrderForm(side, quantity) + orderForm := s.GenerateOrderForm(side, quantity, types.SideEffectTypeAutoRepay) s.Notify("Submitting %s %s order to close position by %v", s.Symbol, side.String(), percentage, orderForm) @@ -264,13 +262,14 @@ func (s *Strategy) UpdateIndicators(kline types.KLine) { } } -func (s *Strategy) GenerateOrderForm(side types.SideType, quantity fixedpoint.Value) types.SubmitOrder { +func (s *Strategy) GenerateOrderForm(side types.SideType, quantity fixedpoint.Value, marginOrderSideEffect types.MarginOrderSideEffectType) types.SubmitOrder { orderForm := types.SubmitOrder{ - Symbol: s.Symbol, - Market: s.Market, - Side: side, - Type: types.OrderTypeMarket, - Quantity: quantity, + Symbol: s.Symbol, + Market: s.Market, + Side: side, + Type: types.OrderTypeMarket, + Quantity: quantity, + MarginSideEffect: marginOrderSideEffect, } return orderForm @@ -432,7 +431,7 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se s.session.GetAccount().UpdateBalances(balances) } - orderForm := s.GenerateOrderForm(side, s.CalculateQuantity(kline.GetClose())) + orderForm := s.GenerateOrderForm(side, s.CalculateQuantity(kline.GetClose()), types.SideEffectTypeMarginBuy) log.Infof("submit open position order %v", orderForm) order, err := orderExecutor.SubmitOrders(ctx, orderForm) if err != nil { From adbea7d4d0969f8ebb08752f584734bfd20db47b Mon Sep 17 00:00:00 2001 From: Andy Cheng Date: Tue, 31 May 2022 17:48:25 +0800 Subject: [PATCH 13/18] strategy: add comments to supertrend config --- config/supertrend.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/config/supertrend.yaml b/config/supertrend.yaml index f9222df50..db880bce2 100644 --- a/config/supertrend.yaml +++ b/config/supertrend.yaml @@ -45,7 +45,9 @@ exchangeStrategies: fastDEMAWindow: 144 slowDEMAWindow: 169 + # Supertrend indicator parameters superTrend: + # ATR window used by Supertrend averageTrueRangeWindow: 39 # ATR Multiplier for calculating super trend prices, the higher, the stronger the trends are averageTrueRangeMultiplier: 3 From 237d1205e82c41bcb70b33b940aa040ecbb8cadf Mon Sep 17 00:00:00 2001 From: Andy Cheng Date: Wed, 1 Jun 2022 10:26:04 +0800 Subject: [PATCH 14/18] strategy: check update balance response in calculateQuantity --- pkg/strategy/supertrend/strategy.go | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/pkg/strategy/supertrend/strategy.go b/pkg/strategy/supertrend/strategy.go index 2c7dc07cf..e67a6bb05 100644 --- a/pkg/strategy/supertrend/strategy.go +++ b/pkg/strategy/supertrend/strategy.go @@ -208,7 +208,7 @@ func (s *Strategy) ClosePosition(ctx context.Context, percentage fixedpoint.Valu return fmt.Errorf("order quantity %v is too small, less than %v", quantity, s.Market.MinQuantity) } - orderForm := s.GenerateOrderForm(side, quantity, types.SideEffectTypeAutoRepay) + orderForm := s.generateOrderForm(side, quantity, types.SideEffectTypeAutoRepay) s.Notify("Submitting %s %s order to close position by %v", s.Symbol, side.String(), percentage, orderForm) @@ -224,8 +224,8 @@ func (s *Strategy) ClosePosition(ctx context.Context, percentage fixedpoint.Valu return err } -// SetupIndicators initializes indicators -func (s *Strategy) SetupIndicators() { +// setupIndicators initializes indicators +func (s *Strategy) setupIndicators() { if s.FastDEMAWindow == 0 { s.FastDEMAWindow = 144 } @@ -246,8 +246,8 @@ func (s *Strategy) SetupIndicators() { } } -// UpdateIndicators updates indicators -func (s *Strategy) UpdateIndicators(kline types.KLine) { +// updateIndicators updates indicators +func (s *Strategy) updateIndicators(kline types.KLine) { closePrice := kline.GetClose().Float64() // Update indicators @@ -262,7 +262,7 @@ func (s *Strategy) UpdateIndicators(kline types.KLine) { } } -func (s *Strategy) GenerateOrderForm(side types.SideType, quantity fixedpoint.Value, marginOrderSideEffect types.MarginOrderSideEffectType) types.SubmitOrder { +func (s *Strategy) generateOrderForm(side types.SideType, quantity fixedpoint.Value, marginOrderSideEffect types.MarginOrderSideEffectType) types.SubmitOrder { orderForm := types.SubmitOrder{ Symbol: s.Symbol, Market: s.Market, @@ -275,9 +275,14 @@ func (s *Strategy) GenerateOrderForm(side types.SideType, quantity fixedpoint.Va return orderForm } -// CalculateQuantity returns leveraged quantity -func (s *Strategy) CalculateQuantity(currentPrice fixedpoint.Value) fixedpoint.Value { - balance, _ := s.session.GetAccount().Balance(s.Market.QuoteCurrency) +// calculateQuantity returns leveraged quantity +func (s *Strategy) calculateQuantity(currentPrice fixedpoint.Value) fixedpoint.Value { + balance, ok := s.session.GetAccount().Balance(s.Market.QuoteCurrency) + if !ok { + log.Error("can not update balance from exchange") + return fixedpoint.Zero + } + amountAvailable := balance.Available.Mul(fixedpoint.NewFromFloat(s.Leverage)) quantity := amountAvailable.Div(currentPrice) @@ -332,7 +337,7 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se }) // Setup indicators - s.SetupIndicators() + s.setupIndicators() s.currentStopLossPrice = fixedpoint.Zero s.currentTakeProfitPrice = fixedpoint.Zero @@ -349,7 +354,7 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se } // Update indicators - s.UpdateIndicators(kline) + s.updateIndicators(kline) // Get signals closePrice := kline.GetClose().Float64() @@ -431,7 +436,7 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se s.session.GetAccount().UpdateBalances(balances) } - orderForm := s.GenerateOrderForm(side, s.CalculateQuantity(kline.GetClose()), types.SideEffectTypeMarginBuy) + orderForm := s.generateOrderForm(side, s.calculateQuantity(kline.GetClose()), types.SideEffectTypeMarginBuy) log.Infof("submit open position order %v", orderForm) order, err := orderExecutor.SubmitOrders(ctx, orderForm) if err != nil { From cd96c0113163464a3bd5a8f61c52ea998f0544b1 Mon Sep 17 00:00:00 2001 From: Andy Cheng Date: Wed, 1 Jun 2022 10:51:57 +0800 Subject: [PATCH 15/18] strategy: use Market.IsDustQuantity instead --- pkg/strategy/supertrend/strategy.go | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/pkg/strategy/supertrend/strategy.go b/pkg/strategy/supertrend/strategy.go index e67a6bb05..d8dc4ce5f 100644 --- a/pkg/strategy/supertrend/strategy.go +++ b/pkg/strategy/supertrend/strategy.go @@ -290,12 +290,10 @@ func (s *Strategy) calculateQuantity(currentPrice fixedpoint.Value) fixedpoint.V } func (s *Strategy) HasTradableBase(currentPrice fixedpoint.Value) bool { - base := s.Position.GetBase() - quantity := base.Abs() - if quantity.Compare(s.Market.MinQuantity) > 0 && quantity.Mul(currentPrice).Compare(s.Market.MinNotional) > 0 { - return true + if s.Market.IsDustQuantity(s.Position.GetBase().Abs(), currentPrice) { + return false } - return false + return true } func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, session *bbgo.ExchangeSession) error { @@ -369,9 +367,11 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se demaSignal = types.DirectionNone } + base := s.Position.GetBase() + baseSign := base.Sign() + // TP/SL if there's non-dust position - if s.HasTradableBase(kline.GetClose()) { - baseSign := s.Position.GetBase().Sign() + if !s.Market.IsDustQuantity(base.Abs(), kline.GetClose()) { if s.StopLossByTriggeringK && !s.currentStopLossPrice.IsZero() && ((baseSign < 0 && kline.GetClose().Compare(s.currentStopLossPrice) > 0) || (baseSign > 0 && kline.GetClose().Compare(s.currentStopLossPrice) < 0)) { // SL by triggered Kline low if err := s.ClosePosition(ctx, fixedpoint.One); err != nil { @@ -422,11 +422,10 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se } if side == types.SideTypeSell || side == types.SideTypeBuy { - baseSign := s.Position.GetBase().Sign() // Close opposite position if any - if s.HasTradableBase(kline.GetClose()) && ((side == types.SideTypeSell && baseSign > 0) || (side == types.SideTypeBuy && baseSign < 0)) { + if !s.Market.IsDustQuantity(base.Abs(), kline.GetClose()) && ((side == types.SideTypeSell && baseSign > 0) || (side == types.SideTypeBuy && baseSign < 0)) { if err := s.ClosePosition(ctx, fixedpoint.One); err != nil { - s.Notify("can not place position close order") + s.Notify("can not place close position order") } } From 205921ea4279d04cc1b48e83ac9b77694a75c014 Mon Sep 17 00:00:00 2001 From: Andy Cheng Date: Wed, 1 Jun 2022 10:54:13 +0800 Subject: [PATCH 16/18] strategy: remove HasTradableBase() --- pkg/strategy/supertrend/strategy.go | 7 ------- 1 file changed, 7 deletions(-) diff --git a/pkg/strategy/supertrend/strategy.go b/pkg/strategy/supertrend/strategy.go index d8dc4ce5f..0e289ef70 100644 --- a/pkg/strategy/supertrend/strategy.go +++ b/pkg/strategy/supertrend/strategy.go @@ -289,13 +289,6 @@ func (s *Strategy) calculateQuantity(currentPrice fixedpoint.Value) fixedpoint.V return quantity } -func (s *Strategy) HasTradableBase(currentPrice fixedpoint.Value) bool { - if s.Market.IsDustQuantity(s.Position.GetBase().Abs(), currentPrice) { - return false - } - return true -} - func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, session *bbgo.ExchangeSession) error { s.session = session s.Market, _ = session.Market(s.Symbol) From bf385899b9360522c5469514d77c9fb045f3e9af Mon Sep 17 00:00:00 2001 From: Andy Cheng Date: Thu, 2 Jun 2022 13:47:16 +0800 Subject: [PATCH 17/18] strategy: use private for non-exported fields and functions --- pkg/strategy/supertrend/strategy.go | 52 ++++++++++++++--------------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/pkg/strategy/supertrend/strategy.go b/pkg/strategy/supertrend/strategy.go index 0e289ef70..04ea5afdb 100644 --- a/pkg/strategy/supertrend/strategy.go +++ b/pkg/strategy/supertrend/strategy.go @@ -32,7 +32,7 @@ type SuperTrend struct { // AverageTrueRangeMultiplier ATR multiplier for calculation of supertrend AverageTrueRangeMultiplier float64 `json:"averageTrueRangeMultiplier"` - AverageTrueRange *indicator.ATR + averageTrueRange *indicator.ATR closePrice float64 lastClosePrice float64 @@ -46,14 +46,14 @@ type SuperTrend struct { tradeSignal types.Direction } -// Update SuperTrend indicator -func (st *SuperTrend) Update(kline types.KLine) { +// update SuperTrend indicator +func (st *SuperTrend) update(kline types.KLine) { highPrice := kline.GetHigh().Float64() lowPrice := kline.GetLow().Float64() closePrice := kline.GetClose().Float64() // Update ATR - st.AverageTrueRange.Update(highPrice, lowPrice, closePrice) + st.averageTrueRange.Update(highPrice, lowPrice, closePrice) // Update last prices st.lastUptrendPrice = st.uptrendPrice @@ -66,13 +66,13 @@ func (st *SuperTrend) Update(kline types.KLine) { src := (highPrice + lowPrice) / 2 // Update uptrend - st.uptrendPrice = src - st.AverageTrueRange.Last()*st.AverageTrueRangeMultiplier + st.uptrendPrice = src - st.averageTrueRange.Last()*st.AverageTrueRangeMultiplier if st.lastClosePrice > st.lastUptrendPrice { st.uptrendPrice = math.Max(st.uptrendPrice, st.lastUptrendPrice) } // Update downtrend - st.downtrendPrice = src + st.AverageTrueRange.Last()*st.AverageTrueRangeMultiplier + st.downtrendPrice = src + st.averageTrueRange.Last()*st.AverageTrueRangeMultiplier if st.lastClosePrice < st.lastDowntrendPrice { st.downtrendPrice = math.Min(st.downtrendPrice, st.lastDowntrendPrice) } @@ -96,8 +96,8 @@ func (st *SuperTrend) Update(kline types.KLine) { } } -// GetSignal returns SuperTrend signal -func (st *SuperTrend) GetSignal() types.Direction { +// getSignal returns SuperTrend signal +func (st *SuperTrend) getSignal() types.Direction { return st.tradeSignal } @@ -128,12 +128,12 @@ type Strategy struct { // Interval is how long do you want to update your order price and quantity Interval types.Interval `json:"interval"` - // FastDEMA DEMA window for checking breakout + // FastDEMAWindow DEMA window for checking breakout FastDEMAWindow int `json:"fastDEMAWindow"` - // SlowDEMA DEMA window for checking breakout + // SlowDEMAWindow DEMA window for checking breakout SlowDEMAWindow int `json:"slowDEMAWindow"` - FastDEMA *indicator.DEMA - SlowDEMA *indicator.DEMA + fastDEMA *indicator.DEMA + slowDEMA *indicator.DEMA // SuperTrend indicator SuperTrend SuperTrend `json:"superTrend"` @@ -229,17 +229,17 @@ func (s *Strategy) setupIndicators() { if s.FastDEMAWindow == 0 { s.FastDEMAWindow = 144 } - s.FastDEMA = &indicator.DEMA{IntervalWindow: types.IntervalWindow{Interval: s.Interval, Window: s.FastDEMAWindow}} + s.fastDEMA = &indicator.DEMA{IntervalWindow: types.IntervalWindow{Interval: s.Interval, Window: s.FastDEMAWindow}} if s.SlowDEMAWindow == 0 { s.SlowDEMAWindow = 169 } - s.SlowDEMA = &indicator.DEMA{IntervalWindow: types.IntervalWindow{Interval: s.Interval, Window: s.SlowDEMAWindow}} + s.slowDEMA = &indicator.DEMA{IntervalWindow: types.IntervalWindow{Interval: s.Interval, Window: s.SlowDEMAWindow}} if s.SuperTrend.AverageTrueRangeWindow == 0 { s.SuperTrend.AverageTrueRangeWindow = 39 } - s.SuperTrend.AverageTrueRange = &indicator.ATR{IntervalWindow: types.IntervalWindow{Window: s.SuperTrend.AverageTrueRangeWindow, Interval: s.Interval}} + s.SuperTrend.averageTrueRange = &indicator.ATR{IntervalWindow: types.IntervalWindow{Window: s.SuperTrend.AverageTrueRangeWindow, Interval: s.Interval}} s.SuperTrend.trend = types.DirectionUp if s.SuperTrend.AverageTrueRangeMultiplier == 0 { s.SuperTrend.AverageTrueRangeMultiplier = 3 @@ -251,14 +251,14 @@ func (s *Strategy) updateIndicators(kline types.KLine) { closePrice := kline.GetClose().Float64() // Update indicators - if kline.Interval == s.FastDEMA.Interval { - s.FastDEMA.Update(closePrice) + if kline.Interval == s.fastDEMA.Interval { + s.fastDEMA.Update(closePrice) } - if kline.Interval == s.SlowDEMA.Interval { - s.SlowDEMA.Update(closePrice) + if kline.Interval == s.slowDEMA.Interval { + s.slowDEMA.Update(closePrice) } - if kline.Interval == s.SuperTrend.AverageTrueRange.Interval { - s.SuperTrend.Update(kline) + if kline.Interval == s.SuperTrend.averageTrueRange.Interval { + s.SuperTrend.update(kline) } } @@ -350,11 +350,11 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se // Get signals closePrice := kline.GetClose().Float64() openPrice := kline.GetOpen().Float64() - stSignal := s.SuperTrend.GetSignal() + stSignal := s.SuperTrend.getSignal() var demaSignal types.Direction - if closePrice > s.FastDEMA.Last() && closePrice > s.SlowDEMA.Last() && !(openPrice > s.FastDEMA.Last() && openPrice > s.SlowDEMA.Last()) { + if closePrice > s.fastDEMA.Last() && closePrice > s.slowDEMA.Last() && !(openPrice > s.fastDEMA.Last() && openPrice > s.slowDEMA.Last()) { demaSignal = types.DirectionUp - } else if closePrice < s.FastDEMA.Last() && closePrice < s.SlowDEMA.Last() && !(openPrice < s.FastDEMA.Last() && openPrice < s.SlowDEMA.Last()) { + } else if closePrice < s.fastDEMA.Last() && closePrice < s.slowDEMA.Last() && !(openPrice < s.fastDEMA.Last() && openPrice < s.slowDEMA.Last()) { demaSignal = types.DirectionDown } else { demaSignal = types.DirectionNone @@ -402,7 +402,7 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se s.currentStopLossPrice = kline.GetLow() } if s.TakeProfitMultiplier > 0 { - s.currentTakeProfitPrice = kline.GetClose().Add(fixedpoint.NewFromFloat(s.SuperTrend.AverageTrueRange.Last() * s.TakeProfitMultiplier)) + s.currentTakeProfitPrice = kline.GetClose().Add(fixedpoint.NewFromFloat(s.SuperTrend.averageTrueRange.Last() * s.TakeProfitMultiplier)) } } else if stSignal == types.DirectionDown && demaSignal == types.DirectionDown { side = types.SideTypeSell @@ -410,7 +410,7 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se s.currentStopLossPrice = kline.GetHigh() } if s.TakeProfitMultiplier > 0 { - s.currentTakeProfitPrice = kline.GetClose().Sub(fixedpoint.NewFromFloat(s.SuperTrend.AverageTrueRange.Last() * s.TakeProfitMultiplier)) + s.currentTakeProfitPrice = kline.GetClose().Sub(fixedpoint.NewFromFloat(s.SuperTrend.averageTrueRange.Last() * s.TakeProfitMultiplier)) } } From ee26d6ce34b88768b29b7d976704b16509d22daf Mon Sep 17 00:00:00 2001 From: Andy Cheng Date: Tue, 7 Jun 2022 16:04:40 +0800 Subject: [PATCH 18/18] strategy: Persistence.Sync() after position change --- pkg/strategy/supertrend/strategy.go | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/pkg/strategy/supertrend/strategy.go b/pkg/strategy/supertrend/strategy.go index 04ea5afdb..afe813210 100644 --- a/pkg/strategy/supertrend/strategy.go +++ b/pkg/strategy/supertrend/strategy.go @@ -221,6 +221,8 @@ func (s *Strategy) ClosePosition(ctx context.Context, percentage fixedpoint.Valu s.tradeCollector.Process() + _ = s.Persistence.Sync(s) + return err } @@ -291,7 +293,6 @@ func (s *Strategy) calculateQuantity(currentPrice fixedpoint.Value) fixedpoint.V func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, session *bbgo.ExchangeSession) error { s.session = session - s.Market, _ = session.Market(s.Symbol) // If position is nil, we need to allocate a new position for calculation if s.Position == nil { @@ -323,8 +324,6 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se if err := s.ClosePosition(ctx, fixedpoint.One); err != nil { s.Notify("can not close position") } - - _ = s.Persistence.Sync(s) }) // Setup indicators @@ -422,12 +421,6 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se } } - // Update balance for exchanges like FTX, which doesn't update balances automatically - balances, err := s.session.Exchange.QueryAccountBalances(ctx) - if err != nil { - s.session.GetAccount().UpdateBalances(balances) - } - orderForm := s.generateOrderForm(side, s.calculateQuantity(kline.GetClose()), types.SideEffectTypeMarginBuy) log.Infof("submit open position order %v", orderForm) order, err := orderExecutor.SubmitOrders(ctx, orderForm) @@ -439,6 +432,8 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se } s.tradeCollector.Process() + + _ = s.Persistence.Sync(s) } })