diff --git a/pkg/strategy/bollmaker/strategy.go b/pkg/strategy/bollmaker/strategy.go index d28428fe6..cbf31da6d 100644 --- a/pkg/strategy/bollmaker/strategy.go +++ b/pkg/strategy/bollmaker/strategy.go @@ -114,6 +114,10 @@ type Strategy struct { // less than 1.0 means when placing sell order, place buy order with less quantity UptrendSkew fixedpoint.Value `json:"uptrendSkew"` + // ShadowProtection is used to avoid placing bid order when price goes down strongly (without shadow) + ShadowProtection *bool `json:"shadowProtection"` + ShadowProtectionRatio fixedpoint.Value `json:"shadowProtectionRatio"` + session *bbgo.ExchangeSession book *types.StreamOrderBook market types.Market @@ -247,7 +251,7 @@ func (s *Strategy) LoadState() error { return nil } -func (s *Strategy) placeOrders(ctx context.Context, orderExecutor bbgo.OrderExecutor, midPrice fixedpoint.Value) { +func (s *Strategy) placeOrders(ctx context.Context, orderExecutor bbgo.OrderExecutor, midPrice fixedpoint.Value, kline *types.KLine) { askPrice := midPrice.Mul(one + s.Spread) bidPrice := midPrice.Mul(one - s.Spread) base := s.state.Position.GetBase() @@ -354,17 +358,33 @@ func (s *Strategy) placeOrders(ctx context.Context, orderExecutor bbgo.OrderExec // submitOrders = append(submitOrders, buyOrder) } - buyOrder = s.adjustOrderQuantity(buyOrder) - sellOrder = s.adjustOrderQuantity(sellOrder) - if canBuy { - submitOrders = append(submitOrders, buyOrder) + if s.ShadowProtection != nil && *s.ShadowProtection && kline != nil { + switch kline.Direction() { + case types.DirectionUp: + + case types.DirectionDown: + lowerShadowRatio := kline.GetLowerShadowRatio() + if lowerShadowRatio < s.ShadowProtectionRatio.Float64() { + log.Infof("%s shadow protection enabled, lower shadow ratio %f < %f", s.Symbol, lowerShadowRatio, s.ShadowProtectionRatio.Float64()) + } else { + submitOrders = append(submitOrders, buyOrder) + } + } + } else { + submitOrders = append(submitOrders, buyOrder) + } + } if len(submitOrders) == 0 { return } + for i := range submitOrders { + submitOrders[i] = s.adjustOrderQuantity(submitOrders[i]) + } + createdOrders, err := orderExecutor.SubmitOrders(ctx, submitOrders...) if err != nil { log.WithError(err).Errorf("can not place ping pong orders") @@ -406,6 +426,16 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se s.DowntrendSkew = fixedpoint.NewFromFloat(1.2) } + // enable shadow protection by default + if s.ShadowProtection == nil { + s.ShadowProtection = &[]bool{true}[0] + } + + if s.ShadowProtectionRatio == 0 { + // 1% + s.ShadowProtectionRatio = fixedpoint.NewFromFloat(0.02) + } + // initial required information s.session = session @@ -475,10 +505,10 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se } midPrice := fixedpoint.NewFromFloat((ticker.Buy + ticker.Sell) / 2) - s.placeOrders(ctx, orderExecutor, midPrice) + s.placeOrders(ctx, orderExecutor, midPrice, nil) } else { if price, ok := session.LastPrice(s.Symbol); ok { - s.placeOrders(ctx, orderExecutor, fixedpoint.NewFromFloat(price)) + s.placeOrders(ctx, orderExecutor, fixedpoint.NewFromFloat(price), nil) } } }) @@ -504,9 +534,9 @@ func (s *Strategy) Run(ctx context.Context, orderExecutor bbgo.OrderExecutor, se } midPrice := fixedpoint.NewFromFloat((ticker.Buy + ticker.Sell) / 2) - s.placeOrders(ctx, orderExecutor, midPrice) + s.placeOrders(ctx, orderExecutor, midPrice, &kline) } else { - s.placeOrders(ctx, orderExecutor, fixedpoint.NewFromFloat(kline.Close)) + s.placeOrders(ctx, orderExecutor, fixedpoint.NewFromFloat(kline.Close), &kline) } }) diff --git a/pkg/types/kline.go b/pkg/types/kline.go index 90a755450..b74122a5e 100644 --- a/pkg/types/kline.go +++ b/pkg/types/kline.go @@ -154,9 +154,11 @@ func (k KLine) GetLowerShadowRatio() float64 { func (k KLine) GetLowerShadowHeight() float64 { low := k.Low - if k.Open < k.Close { + if k.Open < k.Close { // uptrend return k.Open - low } + + // downtrend return k.Close - low }