bollmaker: add shadow protection config

This commit is contained in:
c9s 2022-01-16 04:40:50 +08:00
parent a68ad20ddc
commit 5c0e3a1254
3 changed files with 43 additions and 48 deletions

View File

@ -10,8 +10,8 @@ import (
"github.com/c9s/bbgo/pkg/types"
)
const CancelOrderWaitTime = 10 * time.Millisecond
const SentOrderWaitTime = 10 * time.Millisecond
const SentOrderWaitTime = 50 * time.Millisecond
const CancelOrderWaitTime = 20 * time.Millisecond
// LocalActiveOrderBook manages the local active order books.
//go:generate callbackgen -type LocalActiveOrderBook
@ -81,7 +81,7 @@ func (b *LocalActiveOrderBook) GracefulCancel(ctx context.Context, ex types.Exch
// Some orders in the variable are not created on the server side yet,
// If we cancel these orders directly, we will get an unsent order error
// We wait here for a while for server to create these orders.
time.Sleep(SentOrderWaitTime)
// time.Sleep(SentOrderWaitTime)
// since ctx might be canceled, we should use background context here
if err := ex.CancelOrders(context.Background(), orders...); err != nil {

View File

@ -115,7 +115,7 @@ type Strategy struct {
UptrendSkew fixedpoint.Value `json:"uptrendSkew"`
// ShadowProtection is used to avoid placing bid order when price goes down strongly (without shadow)
ShadowProtection *bool `json:"shadowProtection"`
ShadowProtection bool `json:"shadowProtection"`
ShadowProtectionRatio fixedpoint.Value `json:"shadowProtectionRatio"`
session *bbgo.ExchangeSession
@ -293,6 +293,26 @@ func (s *Strategy) placeOrders(ctx context.Context, orderExecutor bbgo.OrderExec
canBuy := hasQuoteBalance && quoteBalance.Available > s.Quantity.Mul(midPrice) && (s.MaxExposurePosition > 0 && base < s.MaxExposurePosition)
canSell := hasBaseBalance && baseBalance.Available > s.Quantity && (s.MaxExposurePosition > 0 && base > -s.MaxExposurePosition)
if s.ShadowProtection && kline != nil {
switch kline.Direction() {
case types.DirectionDown:
shadowHeight := kline.GetLowerShadowHeight()
shadowRatio := kline.GetLowerShadowRatio()
if shadowHeight == 0.0 && shadowRatio < s.ShadowProtectionRatio.Float64() {
log.Infof("%s shadow protection enabled, lower shadow ratio %f < %f", s.Symbol, shadowRatio, s.ShadowProtectionRatio.Float64())
canBuy = false
}
case types.DirectionUp:
shadowHeight := kline.GetUpperShadowHeight()
shadowRatio := kline.GetUpperShadowRatio()
if shadowHeight == 0.0 || shadowRatio < s.ShadowProtectionRatio.Float64() {
log.Infof("%s shadow protection enabled, upper shadow ratio %f < %f", s.Symbol, shadowRatio, s.ShadowProtectionRatio.Float64())
canSell = false
}
}
}
// adjust quantity for closing position if we over sold or over bought
if s.MaxExposurePosition > 0 && base.Abs() > s.MaxExposurePosition {
scale := &bbgo.ExponentialScale{
@ -315,67 +335,39 @@ func (s *Strategy) placeOrders(ctx context.Context, orderExecutor bbgo.OrderExec
if midPrice.Float64() > s.neutralBoll.LastDownBand() && midPrice.Float64() < s.neutralBoll.LastUpBand() {
// we don't have position yet
if base == 0 || base.Abs() < minQuantity {
// place orders on both side if it's in oscillating band
if canBuy {
submitOrders = append(submitOrders, buyOrder)
}
if !s.DisableShort && canSell {
submitOrders = append(submitOrders, sellOrder)
}
}
// place orders on both side if it's in oscillating band
// if base == 0 || base.Abs() < minQuantity { }
} else if midPrice.Float64() > s.defaultBoll.LastDownBand() && midPrice.Float64() < s.neutralBoll.LastDownBand() { // downtrend, might bounce back
skew := s.DowntrendSkew.Float64()
ratio := 1.0 / skew
sellOrder.Quantity = math.Max(s.market.MinQuantity, buyOrder.Quantity*ratio)
} else if midPrice.Float64() < s.defaultBoll.LastUpBand() && midPrice.Float64() > s.neutralBoll.LastUpBand() { // uptrend, might bounce back
skew := s.UptrendSkew.Float64()
buyOrder.Quantity = math.Max(s.market.MinQuantity, sellOrder.Quantity*skew)
} else if midPrice.Float64() < s.defaultBoll.LastDownBand() { // strong downtrend
skew := s.StrongDowntrendSkew.Float64()
ratio := 1.0 / skew
sellOrder.Quantity = math.Max(s.market.MinQuantity, buyOrder.Quantity*ratio)
} else if midPrice.Float64() > s.defaultBoll.LastUpBand() { // strong uptrend
skew := s.StrongUptrendSkew.Float64()
buyOrder.Quantity = math.Max(s.market.MinQuantity, sellOrder.Quantity*skew)
}
if midPrice > s.state.Position.AverageCost.MulFloat64(1.0+s.MinProfitSpread.Float64()) && canSell {
if canSell && midPrice > s.state.Position.AverageCost.MulFloat64(1.0+s.MinProfitSpread.Float64()) {
if !(s.DisableShort && (base.Float64()-sellOrder.Quantity < 0)) {
submitOrders = append(submitOrders, sellOrder)
}
}
if midPrice < s.state.Position.AverageCost.MulFloat64(1.0-s.MinProfitSpread.Float64()) && canBuy {
// submitOrders = append(submitOrders, buyOrder)
if canBuy {
submitOrders = append(submitOrders, buyOrder)
}
if canBuy {
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 {
// condition for lower the average cost
/*
if midPrice < s.state.Position.AverageCost.MulFloat64(1.0-s.MinProfitSpread.Float64()) && canBuy {
submitOrders = append(submitOrders, buyOrder)
}
}
*/
if len(submitOrders) == 0 {
return
@ -426,14 +418,8 @@ 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)
s.ShadowProtectionRatio = fixedpoint.NewFromFloat(0.01)
}
// initial required information

View File

@ -131,6 +131,11 @@ func (k KLine) GetMaxChange() float64 {
return k.GetHigh() - k.GetLow()
}
func (k KLine) GetAmplification() float64 {
return k.GetMaxChange() / k.GetLow()
}
// GetThickness returns the thickness of the kline. 1 => thick, 0.1 => thin
func (k KLine) GetThickness() float64 {
return math.Abs(k.GetChange()) / math.Abs(k.GetMaxChange())
@ -292,6 +297,10 @@ func (k KLineWindow) GetMaxChange() float64 {
return k.GetHigh() - k.GetLow()
}
func (k KLineWindow) GetAmplification() float64 {
return k.GetMaxChange() / k.GetLow()
}
func (k KLineWindow) AllDrop() bool {
for _, n := range k {
if n.Direction() >= 0 {