From a8438f8f720d5c1c931f9b93b8de327dd76dc5dc Mon Sep 17 00:00:00 2001 From: Andy Cheng Date: Thu, 25 Aug 2022 10:17:42 +0800 Subject: [PATCH 01/11] exits/hhllstop: add basic parameters --- pkg/bbgo/hh_ll_stop.go | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 pkg/bbgo/hh_ll_stop.go diff --git a/pkg/bbgo/hh_ll_stop.go b/pkg/bbgo/hh_ll_stop.go new file mode 100644 index 000000000..dade7e000 --- /dev/null +++ b/pkg/bbgo/hh_ll_stop.go @@ -0,0 +1,32 @@ +package bbgo + +import ( + "github.com/c9s/bbgo/pkg/fixedpoint" + "github.com/c9s/bbgo/pkg/types" +) + +type HigherHighLowerLowStopLoss struct { + Symbol string `json:"symbol"` + + Side types.SideType `json:"side"` + + types.IntervalWindow + + HighLowWindow int `json:"highLowWindow"` + + MaxHighLow int `json:"maxHighLow"` + + MinHighLow int `json:"minHighLow"` + + // ActivationRatio is the trigger condition + // When the price goes higher (lower for side buy) than this ratio, the stop will be activated. + ActivationRatio fixedpoint.Value `json:"activationRatio"` + + // DeactivationRatio is the kill condition + // When the price goes higher (lower for short position) than this ratio, the stop will be deactivated. + // You can use this to combine several exits + DeactivationRatio fixedpoint.Value `json:"deactivationRatio"` + + session *ExchangeSession + orderExecutor *GeneralOrderExecutor +} From eb5479ffdff7eb9bc6ae1ad998642c0140345e8f Mon Sep 17 00:00:00 2001 From: Andy Cheng Date: Thu, 16 Mar 2023 18:30:48 +0800 Subject: [PATCH 02/11] exits/hhllstop: hhllstop prototype --- pkg/bbgo/exit_hh_ll_stop.go | 184 ++++++++++++++++++++++++++++++++++++ pkg/bbgo/hh_ll_stop.go | 32 ------- 2 files changed, 184 insertions(+), 32 deletions(-) create mode 100644 pkg/bbgo/exit_hh_ll_stop.go delete mode 100644 pkg/bbgo/hh_ll_stop.go diff --git a/pkg/bbgo/exit_hh_ll_stop.go b/pkg/bbgo/exit_hh_ll_stop.go new file mode 100644 index 000000000..7c3c0270d --- /dev/null +++ b/pkg/bbgo/exit_hh_ll_stop.go @@ -0,0 +1,184 @@ +package bbgo + +import ( + "context" + log "github.com/sirupsen/logrus" + + "github.com/c9s/bbgo/pkg/fixedpoint" + "github.com/c9s/bbgo/pkg/types" +) + +type HigherHighLowerLowStopLoss struct { + Symbol string `json:"symbol"` + + Side types.SideType `json:"side"` + + types.IntervalWindow + + HighLowWindow int `json:"highLowWindow"` + + MaxHighLow int `json:"maxHighLow"` + + MinHighLow int `json:"minHighLow"` + + // ActivationRatio is the trigger condition + // When the price goes higher (lower for short position) than this ratio, the stop will be activated. + // You can use this to combine several exits + ActivationRatio fixedpoint.Value `json:"activationRatio"` + + // DeactivationRatio is the kill condition + // When the price goes higher (lower for short position) than this ratio, the stop will be deactivated. + // You can use this to combine several exits + DeactivationRatio fixedpoint.Value `json:"deactivationRatio"` + + OppositeDirectionAsPosition bool `json:"oppositeDirectionAsPosition"` + + klines types.KLineWindow + + // activated: when the price reaches the min profit price, we set the activated to true to enable trailing stop + activated bool + + highLows []types.Direction + + session *ExchangeSession + orderExecutor *GeneralOrderExecutor +} + +// Subscribe required k-line stream +func (s *HigherHighLowerLowStopLoss) Subscribe(session *ExchangeSession) { + // use 1m kline to handle roi stop + session.Subscribe(types.KLineChannel, s.Symbol, types.SubscribeOptions{Interval: s.Interval}) +} + +// updateActivated checks the position cost against the close price, activation ratio, and deactivation ratio to +// determine whether this stop should be activated +func (s *HigherHighLowerLowStopLoss) updateActivated(position *types.Position, closePrice fixedpoint.Value) { + if position.IsClosed() || position.IsDust(closePrice) { + s.activated = false + } else if s.activated { + if position.IsLong() { + r := fixedpoint.One.Add(s.DeactivationRatio) + if closePrice.Compare(position.AverageCost.Mul(r)) >= 0 { + s.activated = false + Notify("[hhllStop] Stop of %s deactivated for long position, deactivation ratio %f:", s.Symbol, s.DeactivationRatio) + } + } else if position.IsShort() { + r := fixedpoint.One.Sub(s.DeactivationRatio) + // for short position, if the close price is less than the activation price then this is a profit position. + if closePrice.Compare(position.AverageCost.Mul(r)) <= 0 { + s.activated = false + Notify("[hhllStop] Stop of %s deactivated for short position, deactivation ratio %f:", s.Symbol, s.DeactivationRatio) + } + } + } else { + if position.IsLong() { + r := fixedpoint.One.Add(s.ActivationRatio) + if closePrice.Compare(position.AverageCost.Mul(r)) >= 0 { + s.activated = true + Notify("[hhllStop] Stop of %s activated for long position, activation ratio %f:", s.Symbol, s.ActivationRatio) + } + } else if position.IsShort() { + r := fixedpoint.One.Sub(s.ActivationRatio) + // for short position, if the close price is less than the activation price then this is a profit position. + if closePrice.Compare(position.AverageCost.Mul(r)) <= 0 { + s.activated = true + Notify("[hhllStop] Stop of %s activated for short position, activation ratio %f:", s.Symbol, s.ActivationRatio) + } + } + } +} + +func (s *HigherHighLowerLowStopLoss) updateHighLowNumber(kline types.KLine) { + if !s.activated { + s.reset() + return + } + + if s.klines.GetHigh().Compare(kline.GetHigh()) < 0 { + s.highLows = append(s.highLows, types.DirectionUp) + } else if s.klines.GetLow().Compare(kline.GetLow()) > 0 { + s.highLows = append(s.highLows, types.DirectionDown) + } else { + s.highLows = append(s.highLows, types.DirectionNone) + } + // Truncate highLows + if len(s.highLows) > s.HighLowWindow { + end := len(s.highLows) + start := end - s.HighLowWindow + if start < 0 { + start = 0 + } + kn := s.highLows[start:] + s.highLows = kn + } + + s.klines.Add(kline) + s.klines.Truncate(s.Window - 1) +} + +func (s *HigherHighLowerLowStopLoss) shouldStop(position *types.Position) bool { + if s.activated { + highs := 0 + lows := 0 + for _, hl := range s.highLows { + switch hl { + case types.DirectionUp: + highs++ + case types.DirectionDown: + lows++ + } + } + + log.Debugf("[hhllStop] %d higher highs and %d lower lows in window of %d", highs, lows, s.HighLowWindow) + + // Check higher highs + if (position.IsLong() && !s.OppositeDirectionAsPosition) || (position.IsShort() && s.OppositeDirectionAsPosition) { + if (s.MinHighLow > 0 && highs < s.MinHighLow) || (s.MaxHighLow > 0 && highs > s.MaxHighLow) { + return true + } + // Check lower lows + } else if (position.IsShort() && !s.OppositeDirectionAsPosition) || (position.IsLong() && s.OppositeDirectionAsPosition) { + if (s.MinHighLow > 0 && lows < s.MinHighLow) || (s.MaxHighLow > 0 && lows > s.MaxHighLow) { + return true + } + } + } + return false +} + +func (s *HigherHighLowerLowStopLoss) reset() { + s.highLows = []types.Direction{} + s.klines.Truncate(0) +} + +func (s *HigherHighLowerLowStopLoss) Bind(session *ExchangeSession, orderExecutor *GeneralOrderExecutor) { + s.session = session + s.orderExecutor = orderExecutor + + position := orderExecutor.Position() + session.MarketDataStream.OnKLineClosed(types.KLineWith(s.Symbol, s.Interval, func(kline types.KLine) { + s.updateActivated(position, kline.GetClose()) + + s.updateHighLowNumber(kline) + + // Close position & reset + if s.activated && s.shouldStop(position) { + err := s.orderExecutor.ClosePosition(context.Background(), fixedpoint.One, "hhllStop") + if err != nil { + Notify("[hhllStop] Stop of %s triggered but failed to close %s position:", s.Symbol, err) + } else { + s.reset() + s.activated = false + Notify("[hhllStop] Stop of %s triggered and position closed", s.Symbol) + } + } + })) + + // Make sure the stop is reset when position is closed or dust + orderExecutor.TradeCollector().OnPositionUpdate(func(position *types.Position) { + if position.IsClosed() || position.IsDust(position.AverageCost) { + s.reset() + s.activated = false + } + }) +} diff --git a/pkg/bbgo/hh_ll_stop.go b/pkg/bbgo/hh_ll_stop.go deleted file mode 100644 index dade7e000..000000000 --- a/pkg/bbgo/hh_ll_stop.go +++ /dev/null @@ -1,32 +0,0 @@ -package bbgo - -import ( - "github.com/c9s/bbgo/pkg/fixedpoint" - "github.com/c9s/bbgo/pkg/types" -) - -type HigherHighLowerLowStopLoss struct { - Symbol string `json:"symbol"` - - Side types.SideType `json:"side"` - - types.IntervalWindow - - HighLowWindow int `json:"highLowWindow"` - - MaxHighLow int `json:"maxHighLow"` - - MinHighLow int `json:"minHighLow"` - - // ActivationRatio is the trigger condition - // When the price goes higher (lower for side buy) than this ratio, the stop will be activated. - ActivationRatio fixedpoint.Value `json:"activationRatio"` - - // DeactivationRatio is the kill condition - // When the price goes higher (lower for short position) than this ratio, the stop will be deactivated. - // You can use this to combine several exits - DeactivationRatio fixedpoint.Value `json:"deactivationRatio"` - - session *ExchangeSession - orderExecutor *GeneralOrderExecutor -} From 2e00e584421b9235713295fd4e063960afc70cbb Mon Sep 17 00:00:00 2001 From: Andy Cheng Date: Thu, 16 Mar 2023 18:39:27 +0800 Subject: [PATCH 03/11] exits/hhllstop: add hhllstop to exits --- pkg/bbgo/exit.go | 20 +++++++++++++++----- pkg/bbgo/exit_hh_ll_stop.go | 14 +++++++------- 2 files changed, 22 insertions(+), 12 deletions(-) diff --git a/pkg/bbgo/exit.go b/pkg/bbgo/exit.go index e4a59e6e0..aedc936d6 100644 --- a/pkg/bbgo/exit.go +++ b/pkg/bbgo/exit.go @@ -29,16 +29,17 @@ func (s *ExitMethodSet) Bind(session *ExchangeSession, orderExecutor *GeneralOrd } type ExitMethod struct { - RoiStopLoss *RoiStopLoss `json:"roiStopLoss"` - ProtectiveStopLoss *ProtectiveStopLoss `json:"protectiveStopLoss"` - RoiTakeProfit *RoiTakeProfit `json:"roiTakeProfit"` - TrailingStop *TrailingStop2 `json:"trailingStop"` + RoiStopLoss *RoiStopLoss `json:"roiStopLoss"` + ProtectiveStopLoss *ProtectiveStopLoss `json:"protectiveStopLoss"` + RoiTakeProfit *RoiTakeProfit `json:"roiTakeProfit"` + TrailingStop *TrailingStop2 `json:"trailingStop"` + HigherHighLowerLowStop *HigherHighLowerLowStop `json:"higherHighLowerLowStopLoss"` // Exit methods for short positions // ================================================= LowerShadowTakeProfit *LowerShadowTakeProfit `json:"lowerShadowTakeProfit"` CumulatedVolumeTakeProfit *CumulatedVolumeTakeProfit `json:"cumulatedVolumeTakeProfit"` - SupportTakeProfit *SupportTakeProfit `json:"supportTakeProfit"` + SupportTakeProfit *SupportTakeProfit `json:"supportTakeProfit"` } func (e ExitMethod) String() string { @@ -78,6 +79,11 @@ func (e ExitMethod) String() string { buf.WriteString("supportTakeProfit: " + string(b) + ", ") } + if e.HigherHighLowerLowStop != nil { + b, _ := json.Marshal(e.HigherHighLowerLowStop) + buf.WriteString("hhllStop: " + string(b) + ", ") + } + return buf.String() } @@ -135,4 +141,8 @@ func (m *ExitMethod) Bind(session *ExchangeSession, orderExecutor *GeneralOrderE if m.TrailingStop != nil { m.TrailingStop.Bind(session, orderExecutor) } + + if m.HigherHighLowerLowStop != nil { + m.HigherHighLowerLowStop.Bind(session, orderExecutor) + } } diff --git a/pkg/bbgo/exit_hh_ll_stop.go b/pkg/bbgo/exit_hh_ll_stop.go index 7c3c0270d..0b3d57fdd 100644 --- a/pkg/bbgo/exit_hh_ll_stop.go +++ b/pkg/bbgo/exit_hh_ll_stop.go @@ -8,7 +8,7 @@ import ( "github.com/c9s/bbgo/pkg/types" ) -type HigherHighLowerLowStopLoss struct { +type HigherHighLowerLowStop struct { Symbol string `json:"symbol"` Side types.SideType `json:"side"` @@ -45,14 +45,14 @@ type HigherHighLowerLowStopLoss struct { } // Subscribe required k-line stream -func (s *HigherHighLowerLowStopLoss) Subscribe(session *ExchangeSession) { +func (s *HigherHighLowerLowStop) Subscribe(session *ExchangeSession) { // use 1m kline to handle roi stop session.Subscribe(types.KLineChannel, s.Symbol, types.SubscribeOptions{Interval: s.Interval}) } // updateActivated checks the position cost against the close price, activation ratio, and deactivation ratio to // determine whether this stop should be activated -func (s *HigherHighLowerLowStopLoss) updateActivated(position *types.Position, closePrice fixedpoint.Value) { +func (s *HigherHighLowerLowStop) updateActivated(position *types.Position, closePrice fixedpoint.Value) { if position.IsClosed() || position.IsDust(closePrice) { s.activated = false } else if s.activated { @@ -88,7 +88,7 @@ func (s *HigherHighLowerLowStopLoss) updateActivated(position *types.Position, c } } -func (s *HigherHighLowerLowStopLoss) updateHighLowNumber(kline types.KLine) { +func (s *HigherHighLowerLowStop) updateHighLowNumber(kline types.KLine) { if !s.activated { s.reset() return @@ -116,7 +116,7 @@ func (s *HigherHighLowerLowStopLoss) updateHighLowNumber(kline types.KLine) { s.klines.Truncate(s.Window - 1) } -func (s *HigherHighLowerLowStopLoss) shouldStop(position *types.Position) bool { +func (s *HigherHighLowerLowStop) shouldStop(position *types.Position) bool { if s.activated { highs := 0 lows := 0 @@ -146,12 +146,12 @@ func (s *HigherHighLowerLowStopLoss) shouldStop(position *types.Position) bool { return false } -func (s *HigherHighLowerLowStopLoss) reset() { +func (s *HigherHighLowerLowStop) reset() { s.highLows = []types.Direction{} s.klines.Truncate(0) } -func (s *HigherHighLowerLowStopLoss) Bind(session *ExchangeSession, orderExecutor *GeneralOrderExecutor) { +func (s *HigherHighLowerLowStop) Bind(session *ExchangeSession, orderExecutor *GeneralOrderExecutor) { s.session = session s.orderExecutor = orderExecutor From 86bce7403b9a6cea23d2411a097d516511f6abd4 Mon Sep 17 00:00:00 2001 From: Andy Cheng Date: Thu, 16 Mar 2023 19:44:58 +0800 Subject: [PATCH 04/11] exits/hhllstop: fix out of index error of klines --- pkg/bbgo/exit_hh_ll_stop.go | 38 +++++++++++++++++++++---------------- 1 file changed, 22 insertions(+), 16 deletions(-) diff --git a/pkg/bbgo/exit_hh_ll_stop.go b/pkg/bbgo/exit_hh_ll_stop.go index 0b3d57fdd..eb6f0bbbb 100644 --- a/pkg/bbgo/exit_hh_ll_stop.go +++ b/pkg/bbgo/exit_hh_ll_stop.go @@ -8,11 +8,13 @@ import ( "github.com/c9s/bbgo/pkg/types" ) +// TODO: if parameter not set +// TODO: log and notify +// TODO: check all procedures + type HigherHighLowerLowStop struct { Symbol string `json:"symbol"` - Side types.SideType `json:"side"` - types.IntervalWindow HighLowWindow int `json:"highLowWindow"` @@ -94,23 +96,27 @@ func (s *HigherHighLowerLowStop) updateHighLowNumber(kline types.KLine) { return } - if s.klines.GetHigh().Compare(kline.GetHigh()) < 0 { - s.highLows = append(s.highLows, types.DirectionUp) - } else if s.klines.GetLow().Compare(kline.GetLow()) > 0 { - s.highLows = append(s.highLows, types.DirectionDown) + if s.klines.Len() > 0 { + if s.klines.GetHigh().Compare(kline.GetHigh()) < 0 { + s.highLows = append(s.highLows, types.DirectionUp) + } else if s.klines.GetLow().Compare(kline.GetLow()) > 0 { + s.highLows = append(s.highLows, types.DirectionDown) + } else { + s.highLows = append(s.highLows, types.DirectionNone) + } + // Truncate highLows + if len(s.highLows) > s.HighLowWindow { + end := len(s.highLows) + start := end - s.HighLowWindow + if start < 0 { + start = 0 + } + kn := s.highLows[start:] + s.highLows = kn + } } else { s.highLows = append(s.highLows, types.DirectionNone) } - // Truncate highLows - if len(s.highLows) > s.HighLowWindow { - end := len(s.highLows) - start := end - s.HighLowWindow - if start < 0 { - start = 0 - } - kn := s.highLows[start:] - s.highLows = kn - } s.klines.Add(kline) s.klines.Truncate(s.Window - 1) From f8c0e44e24a9b84cfe9a2558caf4f6e211432df0 Mon Sep 17 00:00:00 2001 From: Andy Cheng Date: Thu, 16 Mar 2023 19:45:48 +0800 Subject: [PATCH 05/11] exits/hhllstop: update supertrend config as hhllStop example --- config/supertrend.yaml | 32 +++++++++++++++++++++----------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/config/supertrend.yaml b/config/supertrend.yaml index bee067b5d..8a0fef446 100644 --- a/config/supertrend.yaml +++ b/config/supertrend.yaml @@ -80,19 +80,29 @@ exchangeStrategies: exits: # roiStopLoss is the stop loss percentage of the position ROI (currently the price change) - roiStopLoss: - percentage: 4.6% - - protectiveStopLoss: - activationRatio: 3.5% - stopLossRatio: 2.9% - placeStopOrder: false - - protectiveStopLoss: - activationRatio: 11.1% - stopLossRatio: 9.5% - placeStopOrder: false + percentage: 2% - trailingStop: - callbackRate: 1.1% + callbackRate: 2% #activationRatio: 20% - minProfit: 19.5% + minProfit: 10% interval: 1m side: both closePosition: 100% + - higherHighLowerLowStopLoss: + interval: 1h + window: 20 + highLowWindow: 5 + minHighLow: 2 + maxHighLow: 0 + activationRatio: 0.5% + deactivationRatio: 10% + oppositeDirectionAsPosition: false + - higherHighLowerLowStopLoss: + interval: 1h + window: 20 + highLowWindow: 5 + minHighLow: 0 + maxHighLow: 3 + activationRatio: 0.5% + deactivationRatio: 10% + oppositeDirectionAsPosition: true From 170d41a4927170c44499a9cc54fb929fa1e59420 Mon Sep 17 00:00:00 2001 From: Andy Cheng Date: Mon, 20 Mar 2023 15:47:44 +0800 Subject: [PATCH 06/11] exits/trailingstop: updateHighLowNumber no matter activated or not --- pkg/bbgo/exit_hh_ll_stop.go | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/pkg/bbgo/exit_hh_ll_stop.go b/pkg/bbgo/exit_hh_ll_stop.go index eb6f0bbbb..9f72299aa 100644 --- a/pkg/bbgo/exit_hh_ll_stop.go +++ b/pkg/bbgo/exit_hh_ll_stop.go @@ -91,11 +91,6 @@ func (s *HigherHighLowerLowStop) updateActivated(position *types.Position, close } func (s *HigherHighLowerLowStop) updateHighLowNumber(kline types.KLine) { - if !s.activated { - s.reset() - return - } - if s.klines.Len() > 0 { if s.klines.GetHigh().Compare(kline.GetHigh()) < 0 { s.highLows = append(s.highLows, types.DirectionUp) @@ -152,11 +147,6 @@ func (s *HigherHighLowerLowStop) shouldStop(position *types.Position) bool { return false } -func (s *HigherHighLowerLowStop) reset() { - s.highLows = []types.Direction{} - s.klines.Truncate(0) -} - func (s *HigherHighLowerLowStop) Bind(session *ExchangeSession, orderExecutor *GeneralOrderExecutor) { s.session = session s.orderExecutor = orderExecutor @@ -173,7 +163,6 @@ func (s *HigherHighLowerLowStop) Bind(session *ExchangeSession, orderExecutor *G if err != nil { Notify("[hhllStop] Stop of %s triggered but failed to close %s position:", s.Symbol, err) } else { - s.reset() s.activated = false Notify("[hhllStop] Stop of %s triggered and position closed", s.Symbol) } @@ -183,7 +172,6 @@ func (s *HigherHighLowerLowStop) Bind(session *ExchangeSession, orderExecutor *G // Make sure the stop is reset when position is closed or dust orderExecutor.TradeCollector().OnPositionUpdate(func(position *types.Position) { if position.IsClosed() || position.IsDust(position.AverageCost) { - s.reset() s.activated = false } }) From 1f3579e3ece6f367e1621b430aca597f111315e9 Mon Sep 17 00:00:00 2001 From: Andy Cheng Date: Mon, 20 Mar 2023 15:56:51 +0800 Subject: [PATCH 07/11] exits/trailingstop: shouldStop() only works after enough data collected --- pkg/bbgo/exit_hh_ll_stop.go | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/pkg/bbgo/exit_hh_ll_stop.go b/pkg/bbgo/exit_hh_ll_stop.go index 9f72299aa..224bacc53 100644 --- a/pkg/bbgo/exit_hh_ll_stop.go +++ b/pkg/bbgo/exit_hh_ll_stop.go @@ -62,14 +62,14 @@ func (s *HigherHighLowerLowStop) updateActivated(position *types.Position, close r := fixedpoint.One.Add(s.DeactivationRatio) if closePrice.Compare(position.AverageCost.Mul(r)) >= 0 { s.activated = false - Notify("[hhllStop] Stop of %s deactivated for long position, deactivation ratio %f:", s.Symbol, s.DeactivationRatio) + Notify("[hhllStop] Stop of %s deactivated for long position, deactivation ratio %s:", s.Symbol, s.DeactivationRatio.Percentage()) } } else if position.IsShort() { r := fixedpoint.One.Sub(s.DeactivationRatio) // for short position, if the close price is less than the activation price then this is a profit position. if closePrice.Compare(position.AverageCost.Mul(r)) <= 0 { s.activated = false - Notify("[hhllStop] Stop of %s deactivated for short position, deactivation ratio %f:", s.Symbol, s.DeactivationRatio) + Notify("[hhllStop] Stop of %s deactivated for short position, deactivation ratio %s:", s.Symbol, s.DeactivationRatio.Percentage()) } } } else { @@ -77,20 +77,22 @@ func (s *HigherHighLowerLowStop) updateActivated(position *types.Position, close r := fixedpoint.One.Add(s.ActivationRatio) if closePrice.Compare(position.AverageCost.Mul(r)) >= 0 { s.activated = true - Notify("[hhllStop] Stop of %s activated for long position, activation ratio %f:", s.Symbol, s.ActivationRatio) + Notify("[hhllStop] Stop of %s activated for long position, activation ratio %s:", s.Symbol, s.ActivationRatio.Percentage()) } } else if position.IsShort() { r := fixedpoint.One.Sub(s.ActivationRatio) // for short position, if the close price is less than the activation price then this is a profit position. if closePrice.Compare(position.AverageCost.Mul(r)) <= 0 { s.activated = true - Notify("[hhllStop] Stop of %s activated for short position, activation ratio %f:", s.Symbol, s.ActivationRatio) + Notify("[hhllStop] Stop of %s activated for short position, activation ratio %s:", s.Symbol, s.ActivationRatio.Percentage()) } } } } func (s *HigherHighLowerLowStop) updateHighLowNumber(kline types.KLine) { + s.klines.Truncate(s.Window - 1) + if s.klines.Len() > 0 { if s.klines.GetHigh().Compare(kline.GetHigh()) < 0 { s.highLows = append(s.highLows, types.DirectionUp) @@ -114,10 +116,14 @@ func (s *HigherHighLowerLowStop) updateHighLowNumber(kline types.KLine) { } s.klines.Add(kline) - s.klines.Truncate(s.Window - 1) } func (s *HigherHighLowerLowStop) shouldStop(position *types.Position) bool { + if s.klines.Len() < s.Window || len(s.highLows) < s.HighLowWindow { + log.Debugf("[hhllStop] not enough data for %s yet", s.Symbol) + return false + } + if s.activated { highs := 0 lows := 0 From afc262da8b249d733ef015ae911ab1767edac5c9 Mon Sep 17 00:00:00 2001 From: Andy Cheng Date: Tue, 11 Apr 2023 14:55:32 +0800 Subject: [PATCH 08/11] exits/trailingstop: more logs --- pkg/bbgo/exit_hh_ll_stop.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/pkg/bbgo/exit_hh_ll_stop.go b/pkg/bbgo/exit_hh_ll_stop.go index 224bacc53..d4feb6336 100644 --- a/pkg/bbgo/exit_hh_ll_stop.go +++ b/pkg/bbgo/exit_hh_ll_stop.go @@ -93,11 +93,13 @@ func (s *HigherHighLowerLowStop) updateActivated(position *types.Position, close func (s *HigherHighLowerLowStop) updateHighLowNumber(kline types.KLine) { s.klines.Truncate(s.Window - 1) - if s.klines.Len() > 0 { + if s.klines.Len() >= s.Window-1 { if s.klines.GetHigh().Compare(kline.GetHigh()) < 0 { s.highLows = append(s.highLows, types.DirectionUp) + log.Debugf("[hhllStop] new higher high for %s", s.Symbol) } else if s.klines.GetLow().Compare(kline.GetLow()) > 0 { s.highLows = append(s.highLows, types.DirectionDown) + log.Debugf("[hhllStop] new lower low for %s", s.Symbol) } else { s.highLows = append(s.highLows, types.DirectionNone) } @@ -164,7 +166,7 @@ func (s *HigherHighLowerLowStop) Bind(session *ExchangeSession, orderExecutor *G s.updateHighLowNumber(kline) // Close position & reset - if s.activated && s.shouldStop(position) { + if s.shouldStop(position) { err := s.orderExecutor.ClosePosition(context.Background(), fixedpoint.One, "hhllStop") if err != nil { Notify("[hhllStop] Stop of %s triggered but failed to close %s position:", s.Symbol, err) From 7f33b54312faf895f7401fe61f0f1348c3432654 Mon Sep 17 00:00:00 2001 From: Andy Cheng Date: Tue, 11 Apr 2023 15:11:11 +0800 Subject: [PATCH 09/11] exits/trailingstop: check parameters --- pkg/bbgo/exit_hh_ll_stop.go | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/pkg/bbgo/exit_hh_ll_stop.go b/pkg/bbgo/exit_hh_ll_stop.go index d4feb6336..afffc0ca0 100644 --- a/pkg/bbgo/exit_hh_ll_stop.go +++ b/pkg/bbgo/exit_hh_ll_stop.go @@ -2,16 +2,13 @@ package bbgo import ( "context" + "fmt" log "github.com/sirupsen/logrus" "github.com/c9s/bbgo/pkg/fixedpoint" "github.com/c9s/bbgo/pkg/types" ) -// TODO: if parameter not set -// TODO: log and notify -// TODO: check all procedures - type HigherHighLowerLowStop struct { Symbol string `json:"symbol"` @@ -156,6 +153,17 @@ func (s *HigherHighLowerLowStop) shouldStop(position *types.Position) bool { } func (s *HigherHighLowerLowStop) Bind(session *ExchangeSession, orderExecutor *GeneralOrderExecutor) { + // Check parameters + if s.Window <= 0 { + panic(fmt.Errorf("[hhllStop] window must be larger than zero")) + } + if s.HighLowWindow <= 0 { + panic(fmt.Errorf("[hhllStop] highLowWindow must be larger than zero")) + } + if s.MaxHighLow <= 0 && s.MinHighLow <= 0 { + panic(fmt.Errorf("[hhllStop] either maxHighLow or minHighLow must be larger than zero")) + } + s.session = session s.orderExecutor = orderExecutor From d4e42426abb3640d17db2fcc0317a680f9e20d5d Mon Sep 17 00:00:00 2001 From: Andy Cheng Date: Tue, 11 Apr 2023 16:02:54 +0800 Subject: [PATCH 10/11] exits/trailingstop: add descriptions for parameters --- config/supertrend.yaml | 30 ++++++++++++++++++------------ pkg/bbgo/exit_hh_ll_stop.go | 11 ++++++++++- 2 files changed, 28 insertions(+), 13 deletions(-) diff --git a/config/supertrend.yaml b/config/supertrend.yaml index 8a0fef446..5eb57087c 100644 --- a/config/supertrend.yaml +++ b/config/supertrend.yaml @@ -89,20 +89,26 @@ exchangeStrategies: side: both closePosition: 100% - higherHighLowerLowStopLoss: - interval: 1h - window: 20 - highLowWindow: 5 + # interval is the kline interval used by this exit + interval: 15 + # window is used as the range to determining higher highs and lower lows + window: 5 + # highLowWindow is the range to calculate the number of higher highs and lower lows + highLowWindow: 12 + # If the number of higher highs or lower lows with in HighLowWindow is less than MinHighLow, the exit is + # triggered. 0 disables this parameter. Either one of MaxHighLow and MinHighLow must be larger than 0 minHighLow: 2 + # If the number of higher highs or lower lows with in HighLowWindow is more than MaxHighLow, the exit is + # triggered. 0 disables this parameter. Either one of MaxHighLow and MinHighLow must be larger than 0 maxHighLow: 0 + # ActivationRatio is the trigger condition + # When the price goes higher (lower for short position) than this ratio, the stop will be activated. + # You can use this to combine several exits activationRatio: 0.5% + # DeactivationRatio is the kill condition + # When the price goes higher (lower for short position) than this ratio, the stop will be deactivated. + # You can use this to combine several exits deactivationRatio: 10% + # If true, looking for lower lows in long position and higher highs in short position. If false, looking for + # higher highs in long position and lower lows in short position oppositeDirectionAsPosition: false - - higherHighLowerLowStopLoss: - interval: 1h - window: 20 - highLowWindow: 5 - minHighLow: 0 - maxHighLow: 3 - activationRatio: 0.5% - deactivationRatio: 10% - oppositeDirectionAsPosition: true diff --git a/pkg/bbgo/exit_hh_ll_stop.go b/pkg/bbgo/exit_hh_ll_stop.go index afffc0ca0..1b094fbc7 100644 --- a/pkg/bbgo/exit_hh_ll_stop.go +++ b/pkg/bbgo/exit_hh_ll_stop.go @@ -12,12 +12,19 @@ import ( type HigherHighLowerLowStop struct { Symbol string `json:"symbol"` + // Interval is the kline interval used by this exit. Window is used as the range to determining higher highs and + // lower lows types.IntervalWindow + // HighLowWindow is the range to calculate the number of higher highs and lower lows HighLowWindow int `json:"highLowWindow"` + // If the number of higher highs or lower lows with in HighLowWindow is more than MaxHighLow, the exit is triggered. + // 0 disables this parameter. Either one of MaxHighLow and MinHighLow must be larger than 0 MaxHighLow int `json:"maxHighLow"` + // If the number of higher highs or lower lows with in HighLowWindow is less than MinHighLow, the exit is triggered. + // 0 disables this parameter. Either one of MaxHighLow and MinHighLow must be larger than 0 MinHighLow int `json:"minHighLow"` // ActivationRatio is the trigger condition @@ -30,11 +37,13 @@ type HigherHighLowerLowStop struct { // You can use this to combine several exits DeactivationRatio fixedpoint.Value `json:"deactivationRatio"` + // If true, looking for lower lows in long position and higher highs in short position. If false, looking for higher + // highs in long position and lower lows in short position OppositeDirectionAsPosition bool `json:"oppositeDirectionAsPosition"` klines types.KLineWindow - // activated: when the price reaches the min profit price, we set the activated to true to enable trailing stop + // activated: when the price reaches the min profit price, we set the activated to true to enable hhll stop activated bool highLows []types.Direction From c3318cbb50c2cf0fcddebac9e7014a13c69f835f Mon Sep 17 00:00:00 2001 From: Andy Cheng Date: Tue, 18 Apr 2023 11:31:51 +0800 Subject: [PATCH 11/11] exits/trailingstop: update comment --- pkg/bbgo/exit_hh_ll_stop.go | 1 - 1 file changed, 1 deletion(-) diff --git a/pkg/bbgo/exit_hh_ll_stop.go b/pkg/bbgo/exit_hh_ll_stop.go index 1b094fbc7..af8ec89bc 100644 --- a/pkg/bbgo/exit_hh_ll_stop.go +++ b/pkg/bbgo/exit_hh_ll_stop.go @@ -54,7 +54,6 @@ type HigherHighLowerLowStop struct { // Subscribe required k-line stream func (s *HigherHighLowerLowStop) Subscribe(session *ExchangeSession) { - // use 1m kline to handle roi stop session.Subscribe(types.KLineChannel, s.Symbol, types.SubscribeOptions{Interval: s.Interval}) }