From 3a37154737a1285d8caad8c931b5799db85cf697 Mon Sep 17 00:00:00 2001 From: c9s Date: Mon, 4 Jul 2022 02:20:15 +0800 Subject: [PATCH] pivotshort: fix supportTakeProfit binding --- pkg/strategy/pivotshort/breaklow.go | 11 +- pkg/strategy/pivotshort/resistance.go | 9 +- pkg/strategy/pivotshort/strategy.go | 141 ++++++++++++++++---------- 3 files changed, 94 insertions(+), 67 deletions(-) diff --git a/pkg/strategy/pivotshort/breaklow.go b/pkg/strategy/pivotshort/breaklow.go index d19011cc7..ae61967c3 100644 --- a/pkg/strategy/pivotshort/breaklow.go +++ b/pkg/strategy/pivotshort/breaklow.go @@ -87,24 +87,19 @@ func (s *BreakLow) Bind(session *bbgo.ExchangeSession, orderExecutor *bbgo.Gener } previousLow := s.pivotLowPrices[len(s.pivotLowPrices)-1] - - // truncate the pivot low prices - if len(s.pivotLowPrices) > 10 { - s.pivotLowPrices = s.pivotLowPrices[len(s.pivotLowPrices)-10:] - } - ratio := fixedpoint.One.Add(s.Ratio) breakPrice := previousLow.Mul(ratio) openPrice := kline.Open closePrice := kline.Close - // if previous low is not break, skip + // if the previous low is not break, or the kline is not strong enough to break it, skip if closePrice.Compare(breakPrice) >= 0 { return } - // we need the price cross the break line or we do nothing + // we need the price cross the break line, or we do nothing: + // open > break price > close price if !(openPrice.Compare(breakPrice) > 0 && closePrice.Compare(breakPrice) < 0) { return } diff --git a/pkg/strategy/pivotshort/resistance.go b/pkg/strategy/pivotshort/resistance.go index 6c8aa21d6..b2726f98d 100644 --- a/pkg/strategy/pivotshort/resistance.go +++ b/pkg/strategy/pivotshort/resistance.go @@ -60,7 +60,6 @@ func (s *ResistanceShort) Bind(session *bbgo.ExchangeSession, orderExecutor *bbg session.MarketDataStream.OnKLineClosed(types.KLineWith(s.Symbol, s.Interval, func(kline types.KLine) { position := s.orderExecutor.Position() if position.IsOpened(kline.Close) { - log.Infof("position is already opened, skip placing resistance orders") return } @@ -182,10 +181,10 @@ func (s *ResistanceShort) placeResistanceOrders(ctx context.Context, resistanceP s.activeOrders.Add(createdOrders...) } -func findPossibleSupportPrices(closePrice float64, minDistance float64, lows []float64) []float64 { - return group(lower(lows, closePrice), minDistance) +func findPossibleSupportPrices(closePrice float64, groupDistance float64, lows []float64) []float64 { + return group(lower(lows, closePrice), groupDistance) } -func findPossibleResistancePrices(closePrice float64, minDistance float64, lows []float64) []float64 { - return group(higher(lows, closePrice), minDistance) +func findPossibleResistancePrices(closePrice float64, groupDistance float64, lows []float64) []float64 { + return group(higher(lows, closePrice), groupDistance) } diff --git a/pkg/strategy/pivotshort/strategy.go b/pkg/strategy/pivotshort/strategy.go index 728aa822e..60801e54c 100644 --- a/pkg/strategy/pivotshort/strategy.go +++ b/pkg/strategy/pivotshort/strategy.go @@ -33,6 +33,7 @@ type IntervalWindowSetting struct { type SupportTakeProfit struct { Symbol string types.IntervalWindow + Ratio fixedpoint.Value `json:"ratio"` pivot *indicator.Pivot @@ -40,20 +41,97 @@ type SupportTakeProfit struct { session *bbgo.ExchangeSession activeOrders *bbgo.ActiveOrderBook currentSupportPrice fixedpoint.Value + + triggeredPrices []fixedpoint.Value } func (s *SupportTakeProfit) Subscribe(session *bbgo.ExchangeSession) { + log.Infof("[supportTakeProfit] Subscribe(%s, %s)", s.Symbol, s.Interval) session.Subscribe(types.KLineChannel, s.Symbol, types.SubscribeOptions{Interval: s.Interval}) } +func (s *SupportTakeProfit) Bind(session *bbgo.ExchangeSession, orderExecutor *bbgo.GeneralOrderExecutor) { + log.Infof("[supportTakeProfit] Bind(%s, %s)", s.Symbol, s.Interval) + + s.session = session + s.orderExecutor = orderExecutor + s.activeOrders = bbgo.NewActiveOrderBook(s.Symbol) + session.UserDataStream.OnOrderUpdate(func(order types.Order) { + if s.activeOrders.Exists(order) { + if !s.currentSupportPrice.IsZero() { + s.triggeredPrices = append(s.triggeredPrices, s.currentSupportPrice) + } + } + }) + s.activeOrders.BindStream(session.UserDataStream) + + position := orderExecutor.Position() + symbol := position.Symbol + store, _ := session.MarketDataStore(symbol) + s.pivot = &indicator.Pivot{IntervalWindow: s.IntervalWindow} + s.pivot.Bind(store) + preloadPivot(s.pivot, store) + + session.MarketDataStream.OnKLineClosed(types.KLineWith(s.Symbol, s.Interval, func(kline types.KLine) { + if !s.updateSupportPrice(kline.Close) { + return + } + + if !position.IsOpened(kline.Close) { + log.Infof("position is not opened, skip updating support take profit order") + return + } + + buyPrice := s.currentSupportPrice.Mul(one.Add(s.Ratio)) + quantity := position.GetQuantity() + ctx := context.Background() + + if err := orderExecutor.GracefulCancelActiveOrderBook(ctx, s.activeOrders); err != nil { + log.WithError(err).Errorf("cancel order failed") + } + + bbgo.Notify("placing %s take profit order at price %f", s.Symbol, buyPrice.Float64()) + createdOrders, err := orderExecutor.SubmitOrders(ctx, types.SubmitOrder{ + Symbol: symbol, + Type: types.OrderTypeLimitMaker, + Side: types.SideTypeBuy, + Price: buyPrice, + Quantity: quantity, + Tag: "supportTakeProfit", + }) + + if err != nil { + log.WithError(err).Errorf("can not submit orders: %+v", createdOrders) + } + + s.activeOrders.Add(createdOrders...) + })) +} + func (s *SupportTakeProfit) updateSupportPrice(closePrice fixedpoint.Value) bool { - supportPrices := findPossibleSupportPrices(closePrice.Float64(), 0.05, s.pivot.Lows) + log.Infof("[supportTakeProfit] lows: %v", s.pivot.Lows) + + groupDistance := 0.01 + minDistance := 0.05 + supportPrices := findPossibleSupportPrices(closePrice.Float64()*(1.0-minDistance), groupDistance, s.pivot.Lows) if len(supportPrices) == 0 { return false } - // nextSupportPrice are sorted in decreasing order - nextSupportPrice := fixedpoint.NewFromFloat(supportPrices[0]) + log.Infof("[supportTakeProfit] found possible support prices: %v", supportPrices) + + // nextSupportPrice are sorted in increasing order + nextSupportPrice := fixedpoint.NewFromFloat(supportPrices[len(supportPrices)-1]) + + // it's price that we have been used to take profit + for _, p := range s.triggeredPrices { + var l = p.Mul(one.Sub(fixedpoint.NewFromFloat(0.01))) + var h = p.Mul(one.Add(fixedpoint.NewFromFloat(0.01))) + if p.Compare(l) > 0 && p.Compare(h) < 0 { + return false + } + } + currentBuyPrice := s.currentSupportPrice.Mul(one.Add(s.Ratio)) if s.currentSupportPrice.IsZero() { @@ -72,52 +150,6 @@ func (s *SupportTakeProfit) updateSupportPrice(closePrice fixedpoint.Value) bool return false } -func (s *SupportTakeProfit) Bind(session *bbgo.ExchangeSession, orderExecutor *bbgo.GeneralOrderExecutor) { - s.session = session - s.orderExecutor = orderExecutor - s.activeOrders = bbgo.NewActiveOrderBook(s.Symbol) - - position := orderExecutor.Position() - symbol := position.Symbol - store, _ := session.MarketDataStore(symbol) - s.pivot = &indicator.Pivot{IntervalWindow: s.IntervalWindow} - s.pivot.Bind(store) - preloadPivot(s.pivot, store) - - session.UserDataStream.OnKLineClosed(types.KLineWith(s.Symbol, s.Interval, func(kline types.KLine) { - if !position.IsOpened(kline.Close) { - return - } - - if !s.updateSupportPrice(kline.Close) { - return - } - - buyPrice := s.currentSupportPrice.Mul(one.Add(s.Ratio)) - quantity := position.GetQuantity() - ctx := context.Background() - - if err := orderExecutor.GracefulCancelActiveOrderBook(ctx, s.activeOrders); err != nil { - log.WithError(err).Errorf("cancel order failed") - } - - bbgo.Notify("placing %s take profit order at price %f", s.Symbol, buyPrice.Float64()) - createdOrders, err := orderExecutor.SubmitOrders(ctx, types.SubmitOrder{ - Symbol: symbol, - Type: types.OrderTypeLimitMaker, - Price: buyPrice, - Quantity: quantity, - Tag: "supportTakeProfit", - }) - - if err != nil { - log.WithError(err).Errorf("can not submit orders: %+v", createdOrders) - } - - s.activeOrders.Add(createdOrders...) - })) -} - type Strategy struct { Environment *bbgo.Environment Symbol string `json:"symbol"` @@ -137,7 +169,7 @@ type Strategy struct { // ResistanceShort is one of the entry method ResistanceShort *ResistanceShort `json:"resistanceShort"` - SupportTakeProfit []SupportTakeProfit `json:"supportTakeProfit"` + SupportTakeProfit []*SupportTakeProfit `json:"supportTakeProfit"` ExitMethods bbgo.ExitMethodSet `json:"exits"` @@ -167,8 +199,9 @@ func (s *Strategy) Subscribe(session *bbgo.ExchangeSession) { } for i := range s.SupportTakeProfit { - dynamic.InheritStructValues(&s.SupportTakeProfit[i], s) - s.SupportTakeProfit[i].Subscribe(session) + m := s.SupportTakeProfit[i] + dynamic.InheritStructValues(m, s) + m.Subscribe(session) } if !bbgo.IsBackTesting { @@ -243,8 +276,8 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se s.BreakLow.Bind(session, s.orderExecutor) } - for _, m := range s.SupportTakeProfit { - m.Bind(session, s.orderExecutor) + for i := range s.SupportTakeProfit { + s.SupportTakeProfit[i].Bind(session, s.orderExecutor) } bbgo.OnShutdown(func(ctx context.Context, wg *sync.WaitGroup) {