From 076f196621d454d3541b4237b6e6479a9abc7def Mon Sep 17 00:00:00 2001 From: c9s Date: Wed, 27 Jul 2022 01:28:18 +0800 Subject: [PATCH 01/14] risk: return quantity directly if it's not zero --- pkg/risk/account_value.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pkg/risk/account_value.go b/pkg/risk/account_value.go index e7c881627..66070f4fb 100644 --- a/pkg/risk/account_value.go +++ b/pkg/risk/account_value.go @@ -164,6 +164,7 @@ func CalculateBaseQuantity(session *bbgo.ExchangeSession, market types.Market, p leverage = fixedpoint.NewFromInt(3) } + baseBalance, _ := session.Account.Balance(market.BaseCurrency) quoteBalance, _ := session.Account.Balance(market.QuoteCurrency) @@ -185,11 +186,11 @@ func CalculateBaseQuantity(session *bbgo.ExchangeSession, market types.Market, p return quantity, fmt.Errorf("quantity is zero, can not submit sell order, please check your quantity settings") } - // using leverage -- starts from here if !quantity.IsZero() { return quantity, nil } + // using leverage -- starts from here logrus.Infof("calculating available leveraged base quantity: base balance = %+v, quote balance = %+v", baseBalance, quoteBalance) // calculate the quantity automatically From 5dd14feb427e07a6e5f24f3a698f392711f76d07 Mon Sep 17 00:00:00 2001 From: c9s Date: Wed, 27 Jul 2022 01:30:43 +0800 Subject: [PATCH 02/14] indicator: fix pivot low indicator --- pkg/indicator/pivot_low.go | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/pkg/indicator/pivot_low.go b/pkg/indicator/pivot_low.go index cdcbb4890..f6a701371 100644 --- a/pkg/indicator/pivot_low.go +++ b/pkg/indicator/pivot_low.go @@ -11,9 +11,10 @@ import ( //go:generate callbackgen -type PivotLow type PivotLow struct { - types.IntervalWindow types.SeriesBase + types.IntervalWindow + Lows types.Float64Slice Values types.Float64Slice EndTime time.Time @@ -71,15 +72,10 @@ func calculatePivotLow(lows types.Float64Slice, window int) (float64, error) { return 0., fmt.Errorf("insufficient elements for calculating with window = %d", window) } - var pv types.Float64Slice - for _, low := range lows { - pv.Push(low) + min := lows[length-(window-1):].Min() + if min == lows.Index(int(window/2.)-1) { + return min, nil } - pl := 0. - if lows.Min() == lows.Index(int(window/2.)-1) { - pl = lows.Min() - } - - return pl, nil + return 0., nil } From 0e18aa68f7145b4f64ba75d2d40445bbddcd0a26 Mon Sep 17 00:00:00 2001 From: c9s Date: Wed, 27 Jul 2022 01:32:37 +0800 Subject: [PATCH 03/14] indicator: fix length slice calculation --- pkg/indicator/pivot_low.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/indicator/pivot_low.go b/pkg/indicator/pivot_low.go index f6a701371..af27e2f44 100644 --- a/pkg/indicator/pivot_low.go +++ b/pkg/indicator/pivot_low.go @@ -72,7 +72,7 @@ func calculatePivotLow(lows types.Float64Slice, window int) (float64, error) { return 0., fmt.Errorf("insufficient elements for calculating with window = %d", window) } - min := lows[length-(window-1):].Min() + min := lows[length-1-(window-1):].Min() if min == lows.Index(int(window/2.)-1) { return min, nil } From 4fd318701d0dd8080d12212c319c6469d7e34fe0 Mon Sep 17 00:00:00 2001 From: c9s Date: Wed, 27 Jul 2022 01:43:36 +0800 Subject: [PATCH 04/14] indicator: fix slice --- pkg/indicator/pivot_low.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pkg/indicator/pivot_low.go b/pkg/indicator/pivot_low.go index af27e2f44..b3bbeee3f 100644 --- a/pkg/indicator/pivot_low.go +++ b/pkg/indicator/pivot_low.go @@ -72,7 +72,8 @@ func calculatePivotLow(lows types.Float64Slice, window int) (float64, error) { return 0., fmt.Errorf("insufficient elements for calculating with window = %d", window) } - min := lows[length-1-(window-1):].Min() + end := length-1 + min := lows[end-(window-1):].Min() if min == lows.Index(int(window/2.)-1) { return min, nil } From 6f64b6d08e5c8b7a49465caec752a962bd04c49f Mon Sep 17 00:00:00 2001 From: c9s Date: Wed, 27 Jul 2022 01:51:47 +0800 Subject: [PATCH 05/14] pivotshort: introduce new config struct --- pkg/strategy/pivotshort/breaklow.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/pkg/strategy/pivotshort/breaklow.go b/pkg/strategy/pivotshort/breaklow.go index 565c8336f..7a2516d66 100644 --- a/pkg/strategy/pivotshort/breaklow.go +++ b/pkg/strategy/pivotshort/breaklow.go @@ -10,6 +10,15 @@ import ( "github.com/c9s/bbgo/pkg/types" ) +type StopEMA struct { + types.IntervalWindow + Range fixedpoint.Value `json:"range"` +} + +type TrendEMA struct { + types.IntervalWindow +} + // BreakLow -- when price breaks the previous pivot low, we set a trade entry type BreakLow struct { Symbol string From 854af6b4bd95498817f5c42a762fe1cd0588b5a9 Mon Sep 17 00:00:00 2001 From: c9s Date: Wed, 27 Jul 2022 01:53:53 +0800 Subject: [PATCH 06/14] pivotshort: use new config struct stopEMA and trendEMA --- pkg/strategy/pivotshort/breaklow.go | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/pkg/strategy/pivotshort/breaklow.go b/pkg/strategy/pivotshort/breaklow.go index 7a2516d66..a12297099 100644 --- a/pkg/strategy/pivotshort/breaklow.go +++ b/pkg/strategy/pivotshort/breaklow.go @@ -35,12 +35,13 @@ type BreakLow struct { // limit sell price = breakLowPrice * (1 + BounceRatio) BounceRatio fixedpoint.Value `json:"bounceRatio"` - Leverage fixedpoint.Value `json:"leverage"` - Quantity fixedpoint.Value `json:"quantity"` - StopEMARange fixedpoint.Value `json:"stopEMARange"` - StopEMA *types.IntervalWindow `json:"stopEMA"` + Leverage fixedpoint.Value `json:"leverage"` + Quantity fixedpoint.Value `json:"quantity"` + + StopEMA *StopEMA `json:"stopEMA"` + + TrendEMA *TrendEMA `json:"trendEMA"` - TrendEMA *types.IntervalWindow `json:"trendEMA"` lastLow fixedpoint.Value pivot *indicator.PivotLow @@ -81,11 +82,11 @@ func (s *BreakLow) Bind(session *bbgo.ExchangeSession, orderExecutor *bbgo.Gener s.pivot = standardIndicator.PivotLow(s.IntervalWindow) if s.StopEMA != nil { - s.stopEWMA = standardIndicator.EWMA(*s.StopEMA) + s.stopEWMA = standardIndicator.EWMA(s.StopEMA.IntervalWindow) } if s.TrendEMA != nil { - s.trendEWMA = standardIndicator.EWMA(*s.TrendEMA) + s.trendEWMA = standardIndicator.EWMA(s.TrendEMA.IntervalWindow) session.MarketDataStream.OnKLineClosed(types.KLineWith(s.Symbol, s.TrendEMA.Interval, func(kline types.KLine) { s.trendEWMALast = s.trendEWMACurrent @@ -202,9 +203,9 @@ func (s *BreakLow) Bind(session *bbgo.ExchangeSession, orderExecutor *bbgo.Gener return } - emaStopShortPrice := ema.Mul(fixedpoint.One.Sub(s.StopEMARange)) + emaStopShortPrice := ema.Mul(fixedpoint.One.Sub(s.StopEMA.Range)) if closePrice.Compare(emaStopShortPrice) < 0 { - log.Infof("stopEMA protection: close price %f < EMA(%v) = %f", closePrice.Float64(), s.StopEMA, ema.Float64()) + log.Infof("stopEMA protection: close price %f < EMA(%v %f) * (1 - RANGE %f) = %f", closePrice.Float64(), s.StopEMA, ema.Float64(), s.StopEMA.Range.Float64(), emaStopShortPrice.Float64()) return } } From b746f801f7759a855c6781d2609b2aae324ef1e4 Mon Sep 17 00:00:00 2001 From: c9s Date: Wed, 27 Jul 2022 01:56:18 +0800 Subject: [PATCH 07/14] pivotshort: get the correct pivot low value --- pkg/strategy/pivotshort/breaklow.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pkg/strategy/pivotshort/breaklow.go b/pkg/strategy/pivotshort/breaklow.go index a12297099..318e22274 100644 --- a/pkg/strategy/pivotshort/breaklow.go +++ b/pkg/strategy/pivotshort/breaklow.go @@ -96,13 +96,13 @@ func (s *BreakLow) Bind(session *bbgo.ExchangeSession, orderExecutor *bbgo.Gener // update pivot low data session.MarketDataStream.OnStart(func() { - lastLow := fixedpoint.NewFromFloat(s.pivot.Lows.Last()) + lastLow := fixedpoint.NewFromFloat(s.pivot.Last()) if lastLow.IsZero() { return } if lastLow.Compare(s.lastLow) != 0 { - bbgo.Notify("%s found new pivot low: %f", s.Symbol, s.pivot.Lows.Last()) + bbgo.Notify("%s found new pivot low: %f", s.Symbol, s.pivot.Last()) } s.lastLow = lastLow @@ -127,7 +127,7 @@ func (s *BreakLow) Bind(session *bbgo.ExchangeSession, orderExecutor *bbgo.Gener }) session.MarketDataStream.OnKLineClosed(types.KLineWith(symbol, s.Interval, func(kline types.KLine) { - lastLow := fixedpoint.NewFromFloat(s.pivot.Lows.Last()) + lastLow := fixedpoint.NewFromFloat(s.pivot.Last()) if lastLow.IsZero() { return } @@ -144,7 +144,7 @@ func (s *BreakLow) Bind(session *bbgo.ExchangeSession, orderExecutor *bbgo.Gener return } - bbgo.Notify("%s new pivot low: %f", s.Symbol, s.pivot.Lows.Last()) + bbgo.Notify("%s new pivot low: %f", s.Symbol, s.pivot.Last()) })) session.MarketDataStream.OnKLineClosed(types.KLineWith(symbol, types.Interval1m, func(kline types.KLine) { From ac496e8488a97ece00f46ea98e995466a0ec2a28 Mon Sep 17 00:00:00 2001 From: c9s Date: Wed, 27 Jul 2022 01:57:28 +0800 Subject: [PATCH 08/14] pivotshort: refactor pivot low collector --- pkg/strategy/pivotshort/breaklow.go | 92 ++++++++++++++--------------- 1 file changed, 44 insertions(+), 48 deletions(-) diff --git a/pkg/strategy/pivotshort/breaklow.go b/pkg/strategy/pivotshort/breaklow.go index 318e22274..651a40edc 100644 --- a/pkg/strategy/pivotshort/breaklow.go +++ b/pkg/strategy/pivotshort/breaklow.go @@ -42,16 +42,15 @@ type BreakLow struct { TrendEMA *TrendEMA `json:"trendEMA"` + lastLow fixedpoint.Value + pivotLow *indicator.PivotLow + pivotLowPrices []fixedpoint.Value - lastLow fixedpoint.Value - pivot *indicator.PivotLow stopEWMA *indicator.EWMA trendEWMA *indicator.EWMA trendEWMALast, trendEWMACurrent float64 - pivotLowPrices []fixedpoint.Value - orderExecutor *bbgo.GeneralOrderExecutor session *bbgo.ExchangeSession } @@ -79,7 +78,7 @@ func (s *BreakLow) Bind(session *bbgo.ExchangeSession, orderExecutor *bbgo.Gener s.lastLow = fixedpoint.Zero - s.pivot = standardIndicator.PivotLow(s.IntervalWindow) + s.pivotLow = standardIndicator.PivotLow(s.IntervalWindow) if s.StopEMA != nil { s.stopEWMA = standardIndicator.EWMA(s.StopEMA.IntervalWindow) @@ -96,55 +95,22 @@ func (s *BreakLow) Bind(session *bbgo.ExchangeSession, orderExecutor *bbgo.Gener // update pivot low data session.MarketDataStream.OnStart(func() { - lastLow := fixedpoint.NewFromFloat(s.pivot.Last()) - if lastLow.IsZero() { - return + if s.updatePivotLow() { + bbgo.Notify("%s new pivot low: %f", s.Symbol, s.pivotLow.Last()) } - if lastLow.Compare(s.lastLow) != 0 { - bbgo.Notify("%s found new pivot low: %f", s.Symbol, s.pivot.Last()) - } - - s.lastLow = lastLow - s.pivotLowPrices = append(s.pivotLowPrices, s.lastLow) - - log.Infof("pilot calculation for max position: last low = %f, quantity = %f, leverage = %f", - s.lastLow.Float64(), - s.Quantity.Float64(), - s.Leverage.Float64()) - - quantity, err := risk.CalculateBaseQuantity(s.session, s.Market, s.lastLow, s.Quantity, s.Leverage) - if err != nil { - log.WithError(err).Errorf("quantity calculation error") - } - - if quantity.IsZero() { - log.WithError(err).Errorf("quantity is zero, can not submit order") - return - } - - bbgo.Notify("%s %f quantity will be used for shorting", s.Symbol, quantity.Float64()) + s.pilotQuantityCalculation() }) session.MarketDataStream.OnKLineClosed(types.KLineWith(symbol, s.Interval, func(kline types.KLine) { - lastLow := fixedpoint.NewFromFloat(s.pivot.Last()) - if lastLow.IsZero() { - return + if s.updatePivotLow() { + // when position is opened, do not send pivot low notify + if position.IsOpened(kline.Close) { + return + } + + bbgo.Notify("%s new pivot low: %f", s.Symbol, s.pivotLow.Last()) } - - if lastLow.Compare(s.lastLow) == 0 { - return - } - - s.lastLow = lastLow - s.pivotLowPrices = append(s.pivotLowPrices, s.lastLow) - - // when position is opened, do not send pivot low notify - if position.IsOpened(kline.Close) { - return - } - - bbgo.Notify("%s new pivot low: %f", s.Symbol, s.pivot.Last()) })) session.MarketDataStream.OnKLineClosed(types.KLineWith(symbol, types.Interval1m, func(kline types.KLine) { @@ -251,3 +217,33 @@ func (s *BreakLow) Bind(session *bbgo.ExchangeSession, orderExecutor *bbgo.Gener } })) } + +func (s *BreakLow) pilotQuantityCalculation() { + log.Infof("pilot calculation for max position: last low = %f, quantity = %f, leverage = %f", + s.lastLow.Float64(), + s.Quantity.Float64(), + s.Leverage.Float64()) + + quantity, err := risk.CalculateBaseQuantity(s.session, s.Market, s.lastLow, s.Quantity, s.Leverage) + if err != nil { + log.WithError(err).Errorf("quantity calculation error") + } + + if quantity.IsZero() { + log.WithError(err).Errorf("quantity is zero, can not submit order") + return + } + + bbgo.Notify("%s %f quantity will be used for shorting", s.Symbol, quantity.Float64()) +} + +func (s *BreakLow) updatePivotLow() bool { + lastLow := fixedpoint.NewFromFloat(s.pivotLow.Last()) + if lastLow.IsZero() || lastLow.Compare(s.lastLow) == 0 { + return false + } + + s.lastLow = lastLow + s.pivotLowPrices = append(s.pivotLowPrices, lastLow) + return true +} From feef912930a60dc63c357fa937d65302d72e055a Mon Sep 17 00:00:00 2001 From: c9s Date: Wed, 27 Jul 2022 01:58:05 +0800 Subject: [PATCH 09/14] indicator: pivot low reformat --- pkg/indicator/pivot_low.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/indicator/pivot_low.go b/pkg/indicator/pivot_low.go index b3bbeee3f..8f605dfe0 100644 --- a/pkg/indicator/pivot_low.go +++ b/pkg/indicator/pivot_low.go @@ -72,7 +72,7 @@ func calculatePivotLow(lows types.Float64Slice, window int) (float64, error) { return 0., fmt.Errorf("insufficient elements for calculating with window = %d", window) } - end := length-1 + end := length - 1 min := lows[end-(window-1):].Min() if min == lows.Index(int(window/2.)-1) { return min, nil From 3adeb46c658061d4239b70a80b08a9f3a00480e1 Mon Sep 17 00:00:00 2001 From: c9s Date: Wed, 27 Jul 2022 01:58:19 +0800 Subject: [PATCH 10/14] config: update pivotshort default config --- config/pivotshort.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/config/pivotshort.yaml b/config/pivotshort.yaml index a6e21c212..67142b902 100644 --- a/config/pivotshort.yaml +++ b/config/pivotshort.yaml @@ -42,13 +42,13 @@ exchangeStrategies: # Notice: When marketOrder is set, bounceRatio will not be used. # bounceRatio: 0.1% - # stopEMARange is the price range we allow short. + # stopEMA is the price range we allow short. # Short-allowed price range = [current price] > [EMA] * (1 - [stopEMARange]) # Higher the stopEMARange than higher the chance to open a short - stopEMARange: 2% stopEMA: interval: 1h window: 99 + range: 2% trendEMA: interval: 1d From 3fbc634d815048909dcaa578bdbd5d2292d629fc Mon Sep 17 00:00:00 2001 From: c9s Date: Wed, 27 Jul 2022 02:21:25 +0800 Subject: [PATCH 11/14] bbgo: narrow down indicator interface type --- pkg/bbgo/standard_indicator_set.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/bbgo/standard_indicator_set.go b/pkg/bbgo/standard_indicator_set.go index a43286c30..3b16061bf 100644 --- a/pkg/bbgo/standard_indicator_set.go +++ b/pkg/bbgo/standard_indicator_set.go @@ -25,7 +25,7 @@ type StandardIndicatorSet struct { // interval -> window boll map[types.IntervalWindowBandWidth]*indicator.BOLL stoch map[types.IntervalWindow]*indicator.STOCH - simples map[types.IntervalWindow]indicator.Simple + simples map[types.IntervalWindow]indicator.KLinePusher stream types.Stream store *MarketDataStore @@ -36,7 +36,7 @@ func NewStandardIndicatorSet(symbol string, stream types.Stream, store *MarketDa Symbol: symbol, store: store, stream: stream, - simples: make(map[types.IntervalWindow]indicator.Simple), + simples: make(map[types.IntervalWindow]indicator.KLinePusher), boll: make(map[types.IntervalWindowBandWidth]*indicator.BOLL), stoch: make(map[types.IntervalWindow]*indicator.STOCH), @@ -53,7 +53,7 @@ func (s *StandardIndicatorSet) initAndBind(inc indicator.KLinePusher, iw types.I s.stream.OnKLineClosed(types.KLineWith(s.Symbol, iw.Interval, inc.PushK)) } -func (s *StandardIndicatorSet) allocateSimpleIndicator(t indicator.Simple, iw types.IntervalWindow) indicator.Simple { +func (s *StandardIndicatorSet) allocateSimpleIndicator(t indicator.KLinePusher, iw types.IntervalWindow) indicator.KLinePusher { inc, ok := s.simples[iw] if ok { return inc From f323e91a5617bc6bced1138f968311c4c85ac3ef Mon Sep 17 00:00:00 2001 From: c9s Date: Wed, 27 Jul 2022 11:30:32 +0800 Subject: [PATCH 12/14] pivotshort: fix resistance short --- pkg/strategy/pivotshort/resistance.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/strategy/pivotshort/resistance.go b/pkg/strategy/pivotshort/resistance.go index 2cf336f79..de054863e 100644 --- a/pkg/strategy/pivotshort/resistance.go +++ b/pkg/strategy/pivotshort/resistance.go @@ -50,7 +50,7 @@ func (s *ResistanceShort) Bind(session *bbgo.ExchangeSession, orderExecutor *bbg s.resistancePivot = session.StandardIndicatorSet(s.Symbol).PivotLow(s.IntervalWindow) // use the last kline from the history before we get the next closed kline - s.updateResistanceOrders(fixedpoint.NewFromFloat(s.resistancePivot.Lows.Last())) + s.updateResistanceOrders(fixedpoint.NewFromFloat(s.resistancePivot.Last())) session.MarketDataStream.OnKLineClosed(types.KLineWith(s.Symbol, s.Interval, func(kline types.KLine) { position := s.orderExecutor.Position() @@ -77,7 +77,7 @@ func tail(arr []float64, length int) []float64 { func (s *ResistanceShort) updateCurrentResistancePrice(closePrice fixedpoint.Value) bool { minDistance := s.MinDistance.Float64() groupDistance := s.GroupDistance.Float64() - resistancePrices := findPossibleResistancePrices(closePrice.Float64()*(1.0+minDistance), groupDistance, tail(s.resistancePivot.Lows, 6)) + resistancePrices := findPossibleResistancePrices(closePrice.Float64()*(1.0+minDistance), groupDistance, s.resistancePivot.Values.Tail(6)) if len(resistancePrices) == 0 { return false } From 74387983907eab2cf3f92a3eecb156a2a54d3ad4 Mon Sep 17 00:00:00 2001 From: c9s Date: Wed, 27 Jul 2022 11:47:12 +0800 Subject: [PATCH 13/14] bbgo: add ClosedKLineStop trigger --- pkg/bbgo/exit_cumulated_volume_take_profit.go | 4 +- pkg/strategy/pivotshort/breaklow.go | 49 ++++++++++++++++++- 2 files changed, 50 insertions(+), 3 deletions(-) diff --git a/pkg/bbgo/exit_cumulated_volume_take_profit.go b/pkg/bbgo/exit_cumulated_volume_take_profit.go index 8ba7ed65f..e75f6f1cc 100644 --- a/pkg/bbgo/exit_cumulated_volume_take_profit.go +++ b/pkg/bbgo/exit_cumulated_volume_take_profit.go @@ -72,7 +72,9 @@ func (s *CumulatedVolumeTakeProfit) Bind(session *ExchangeSession, orderExecutor cqv.Float64(), s.MinQuoteVolume.Float64(), kline.Close.Float64()) - _ = orderExecutor.ClosePosition(context.Background(), fixedpoint.One, "cumulatedVolumeTakeProfit") + if err := orderExecutor.ClosePosition(context.Background(), fixedpoint.One, "cumulatedVolumeTakeProfit") ; err != nil { + log.WithError(err).Errorf("close position error") + } return } })) diff --git a/pkg/strategy/pivotshort/breaklow.go b/pkg/strategy/pivotshort/breaklow.go index 651a40edc..ea21d940a 100644 --- a/pkg/strategy/pivotshort/breaklow.go +++ b/pkg/strategy/pivotshort/breaklow.go @@ -19,6 +19,10 @@ type TrendEMA struct { types.IntervalWindow } +type ClosedKLineStop struct { + types.IntervalWindow +} + // BreakLow -- when price breaks the previous pivot low, we set a trade entry type BreakLow struct { Symbol string @@ -42,7 +46,13 @@ type BreakLow struct { TrendEMA *TrendEMA `json:"trendEMA"` - lastLow fixedpoint.Value + ClosedKLineStop *ClosedKLineStop `json:"closedKLineStop"` + + lastLow fixedpoint.Value + + // lastBreakLow is the low that the price just break + lastBreakLow fixedpoint.Value + pivotLow *indicator.PivotLow pivotLowPrices []fixedpoint.Value @@ -66,6 +76,10 @@ func (s *BreakLow) Subscribe(session *bbgo.ExchangeSession) { if s.TrendEMA != nil { session.Subscribe(types.KLineChannel, s.Symbol, types.SubscribeOptions{Interval: s.TrendEMA.Interval}) } + + if s.ClosedKLineStop != nil { + session.Subscribe(types.KLineChannel, s.Symbol, types.SubscribeOptions{Interval: s.ClosedKLineStop.Interval}) + } } func (s *BreakLow) Bind(session *bbgo.ExchangeSession, orderExecutor *bbgo.GeneralOrderExecutor) { @@ -113,7 +127,34 @@ func (s *BreakLow) Bind(session *bbgo.ExchangeSession, orderExecutor *bbgo.Gener } })) - session.MarketDataStream.OnKLineClosed(types.KLineWith(symbol, types.Interval1m, func(kline types.KLine) { + if s.ClosedKLineStop != nil { + // if the position is already opened, and we just break the low, this checks if the kline closed above the low, + // so that we can close the position earlier + session.MarketDataStream.OnKLineClosed(types.KLineWith(s.Symbol, s.ClosedKLineStop.Interval, func(k types.KLine) { + // make sure the position is opened, and it's a short position + if !position.IsOpened(k.Close) || !position.IsShort() { + return + } + + // make sure we recorded the last break low + if s.lastBreakLow.IsZero() { + return + } + + // the kline opened below the last break low, and closed above the last break low + if k.Open.Compare(s.lastBreakLow) < 0 && k.Close.Compare(s.lastBreakLow) > 0 { + bbgo.Notify("kLine closed above the last break low, triggering stop earlier") + if err := s.orderExecutor.ClosePosition(context.Background(), one, "kLineClosedStop"); err != nil { + log.WithError(err).Error("position close error") + } + + // reset to zero + s.lastBreakLow = fixedpoint.Zero + } + })) + } + + session.MarketDataStream.OnKLineClosed(types.KLineWith(s.Symbol, types.Interval1m, func(kline types.KLine) { if len(s.pivotLowPrices) == 0 { log.Infof("currently there is no pivot low prices, can not check break low...") return @@ -146,6 +187,10 @@ func (s *BreakLow) Bind(session *bbgo.ExchangeSession, orderExecutor *bbgo.Gener log.Infof("%s breakLow signal detected, closed price %f < breakPrice %f", kline.Symbol, closePrice.Float64(), breakPrice.Float64()) + if s.lastBreakLow.IsZero() || previousLow.Compare(s.lastBreakLow) < 0 { + s.lastBreakLow = previousLow + } + if position.IsOpened(kline.Close) { log.Infof("position is already opened, skip short") return From 4c6fe1179693c684e665ed66df5a9f26dd796f1b Mon Sep 17 00:00:00 2001 From: c9s Date: Wed, 27 Jul 2022 12:04:54 +0800 Subject: [PATCH 14/14] pivotshort: rename ClosedKLineStop to fake break stop --- pkg/strategy/pivotshort/breaklow.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/pkg/strategy/pivotshort/breaklow.go b/pkg/strategy/pivotshort/breaklow.go index ea21d940a..d0d744454 100644 --- a/pkg/strategy/pivotshort/breaklow.go +++ b/pkg/strategy/pivotshort/breaklow.go @@ -19,7 +19,7 @@ type TrendEMA struct { types.IntervalWindow } -type ClosedKLineStop struct { +type FakeBreakStop struct { types.IntervalWindow } @@ -46,7 +46,7 @@ type BreakLow struct { TrendEMA *TrendEMA `json:"trendEMA"` - ClosedKLineStop *ClosedKLineStop `json:"closedKLineStop"` + FakeBreakStop *FakeBreakStop `json:"fakeBreakStop"` lastLow fixedpoint.Value @@ -77,8 +77,8 @@ func (s *BreakLow) Subscribe(session *bbgo.ExchangeSession) { session.Subscribe(types.KLineChannel, s.Symbol, types.SubscribeOptions{Interval: s.TrendEMA.Interval}) } - if s.ClosedKLineStop != nil { - session.Subscribe(types.KLineChannel, s.Symbol, types.SubscribeOptions{Interval: s.ClosedKLineStop.Interval}) + if s.FakeBreakStop != nil { + session.Subscribe(types.KLineChannel, s.Symbol, types.SubscribeOptions{Interval: s.FakeBreakStop.Interval}) } } @@ -127,10 +127,10 @@ func (s *BreakLow) Bind(session *bbgo.ExchangeSession, orderExecutor *bbgo.Gener } })) - if s.ClosedKLineStop != nil { + if s.FakeBreakStop != nil { // if the position is already opened, and we just break the low, this checks if the kline closed above the low, // so that we can close the position earlier - session.MarketDataStream.OnKLineClosed(types.KLineWith(s.Symbol, s.ClosedKLineStop.Interval, func(k types.KLine) { + session.MarketDataStream.OnKLineClosed(types.KLineWith(s.Symbol, s.FakeBreakStop.Interval, func(k types.KLine) { // make sure the position is opened, and it's a short position if !position.IsOpened(k.Close) || !position.IsShort() { return