diff --git a/pkg/strategy/xmaker/strategy.go b/pkg/strategy/xmaker/strategy.go index 9f434c7c1..38ce63a7f 100644 --- a/pkg/strategy/xmaker/strategy.go +++ b/pkg/strategy/xmaker/strategy.go @@ -192,6 +192,93 @@ func (s *Strategy) Initialize() error { return nil } +type Quote struct { + BestBidPrice, BestAskPrice fixedpoint.Value + + BidMargin, AskMargin fixedpoint.Value + + // BidLayerPips is the price pips between each layer + BidLayerPips, AskLayerPips fixedpoint.Value +} + +// getBollingerTrend returns -1 when the price is in the downtrend, 1 when the price is in the uptrend, 0 when the price is in the band +func (s *Strategy) getBollingerTrend(quote *Quote) int { + // when bid price is lower than the down band, then it's in the downtrend + // when ask price is higher than the up band, then it's in the uptrend + lastDownBand := fixedpoint.NewFromFloat(s.boll.DownBand.Last(0)) + lastUpBand := fixedpoint.NewFromFloat(s.boll.UpBand.Last(0)) + + log.Infof("bollinger band: up/down = %f/%f, bid/ask = %f/%f", + lastUpBand.Float64(), + lastDownBand.Float64(), + quote.BestBidPrice.Float64(), + quote.BestAskPrice.Float64()) + + if quote.BestAskPrice.Compare(lastDownBand) < 0 { + return -1 + } else if quote.BestBidPrice.Compare(lastUpBand) > 0 { + return 1 + } else { + return 0 + } +} + +// applyBollingerMargin applies the bollinger band margin to the quote +func (s *Strategy) applyBollingerMargin( + quote *Quote, +) error { + lastDownBand := fixedpoint.NewFromFloat(s.boll.DownBand.Last(0)) + lastUpBand := fixedpoint.NewFromFloat(s.boll.UpBand.Last(0)) + + if lastUpBand.IsZero() || lastDownBand.IsZero() { + log.Warnf("bollinger band value is zero, skipping") + return nil + } + + factor := fixedpoint.Min(s.BollBandMarginFactor, fixedpoint.One) + switch s.getBollingerTrend(quote) { + case -1: + // for the downtrend, increase the bid margin + // ratio here should be greater than 1.00 + ratio := fixedpoint.Min(lastDownBand.Div(quote.BestAskPrice), fixedpoint.One) + + // so that 1.x can multiply the original bid margin + bollMargin := s.BollBandMargin.Mul(ratio).Mul(factor) + + log.Infof("%s bollband downtrend: increasing bid margin %f (bidMargin) + %f (bollMargin) = %f (finalBidMargin)", + s.Symbol, + quote.BidMargin.Float64(), + bollMargin.Float64(), + quote.BidMargin.Add(bollMargin).Float64()) + + quote.BidMargin = quote.BidMargin.Add(bollMargin) + quote.BidLayerPips = quote.BidLayerPips.Mul(ratio) + + case 1: + // for the uptrend, increase the ask margin + // ratio here should be greater than 1.00 + ratio := fixedpoint.Min(quote.BestAskPrice.Div(lastUpBand), fixedpoint.One) + + // so that the original bid margin can be multiplied by 1.x + bollMargin := s.BollBandMargin.Mul(ratio).Mul(factor) + + log.Infof("%s bollband uptrend adjusting bid margin %f (askMargin) + %f (bollMargin) = %f (finalAskMargin)", + s.Symbol, + quote.AskMargin.Float64(), + bollMargin.Float64(), + quote.AskMargin.Add(bollMargin).Float64()) + + quote.AskMargin = quote.AskMargin.Add(bollMargin) + quote.AskLayerPips = quote.AskLayerPips.Mul(ratio) + + default: + // default, in the band + + } + + return nil +} + func (s *Strategy) updateQuote(ctx context.Context) { if err := s.activeMakerOrders.GracefulCancel(ctx, s.makerSession.Exchange); err != nil { log.Warnf("there are some %s orders not canceled, skipping placing maker orders", s.Symbol) @@ -340,55 +427,19 @@ func (s *Strategy) updateQuote(ctx context.Context) { var accumulativeBidQuantity, accumulativeAskQuantity fixedpoint.Value var bidQuantity = s.Quantity var askQuantity = s.Quantity - var bidMargin = s.BidMargin - var askMargin = s.AskMargin - var pips = s.Pips + + var quote = &Quote{ + BestBidPrice: bestBidPrice, + BestAskPrice: bestAskPrice, + BidMargin: s.BidMargin, + AskMargin: s.AskMargin, + BidLayerPips: s.Pips, + AskLayerPips: s.Pips, + } if s.EnableBollBandMargin { - lastDownBand := fixedpoint.NewFromFloat(s.boll.DownBand.Last(0)) - lastUpBand := fixedpoint.NewFromFloat(s.boll.UpBand.Last(0)) - - if lastUpBand.IsZero() || lastDownBand.IsZero() { - log.Warnf("bollinger band value is zero, skipping") - return - } - - log.Infof("bollinger band: up/down = %f/%f", lastUpBand.Float64(), lastDownBand.Float64()) - - // when bid price is lower than the down band, then it's in the downtrend - // when ask price is higher than the up band, then it's in the uptrend - if bestBidPrice.Compare(lastDownBand) < 0 { - // ratio here should be greater than 1.00 - ratio := lastDownBand.Div(bestBidPrice) - - // so that the original bid margin can be multiplied by 1.x - bollMargin := s.BollBandMargin.Mul(ratio).Mul(s.BollBandMarginFactor) - - log.Infof("%s bollband downtrend: adjusting ask margin %v + %v = %v", - s.Symbol, - askMargin, - bollMargin, - askMargin.Add(bollMargin)) - - askMargin = askMargin.Add(bollMargin) - pips = pips.Mul(ratio) - } - - if bestAskPrice.Compare(lastUpBand) > 0 { - // ratio here should be greater than 1.00 - ratio := bestAskPrice.Div(lastUpBand) - - // so that the original bid margin can be multiplied by 1.x - bollMargin := s.BollBandMargin.Mul(ratio).Mul(s.BollBandMarginFactor) - - log.Infof("%s bollband uptrend adjusting bid margin %v + %v = %v", - s.Symbol, - bidMargin, - bollMargin, - bidMargin.Add(bollMargin)) - - bidMargin = bidMargin.Add(bollMargin) - pips = pips.Mul(ratio) + if err := s.applyBollingerMargin(quote); err != nil { + log.WithError(err).Errorf("unable to apply bollinger margin") } } @@ -401,11 +452,11 @@ func (s *Strategy) updateQuote(ctx context.Context) { bidExposureInUsd := fixedpoint.Zero askExposureInUsd := fixedpoint.Zero - bidPrice := bestBidPrice - askPrice := bestAskPrice + bidPrice := quote.BestBidPrice + askPrice := quote.BestAskPrice - bidMarginMetrics.With(labels).Set(bidMargin.Float64()) - askMarginMetrics.With(labels).Set(askMargin.Float64()) + bidMarginMetrics.With(labels).Set(quote.BidMargin.Float64()) + askMarginMetrics.With(labels).Set(quote.AskMargin.Float64()) for i := 0; i < s.NumLayers; i++ { // for maker bid orders @@ -425,19 +476,20 @@ func (s *Strategy) updateQuote(ctx context.Context) { accumulativeBidQuantity = accumulativeBidQuantity.Add(bidQuantity) if s.UseDepthPrice { + sideBook := sourceBook.SideBook(types.SideTypeBuy) if s.DepthQuantity.Sign() > 0 { - bidPrice = aggregatePrice(sourceBook.SideBook(types.SideTypeBuy), s.DepthQuantity) + bidPrice = aggregatePrice(sideBook, s.DepthQuantity) } else { - bidPrice = aggregatePrice(sourceBook.SideBook(types.SideTypeBuy), accumulativeBidQuantity) + bidPrice = aggregatePrice(sideBook, accumulativeBidQuantity) } } - bidPrice = bidPrice.Mul(fixedpoint.One.Sub(bidMargin)) if i == 0 { + bidPrice = bidPrice.Mul(fixedpoint.One.Sub(quote.BidMargin)) makerBestBidPriceMetrics.With(labels).Set(bidPrice.Float64()) - } else if i > 0 && pips.Sign() > 0 { - bidPrice = bidPrice.Sub(pips.Mul(fixedpoint.NewFromInt(int64(i)). - Mul(s.makerMarket.TickSize))) + } else if i > 0 && quote.BidLayerPips.Sign() > 0 { + pips := quote.BidLayerPips.Mul(s.makerMarket.TickSize) + bidPrice = bidPrice.Sub(pips) } if makerQuota.QuoteAsset.Lock(bidQuantity.Mul(bidPrice)) && hedgeQuota.BaseAsset.Lock(bidQuantity) { @@ -489,11 +541,12 @@ func (s *Strategy) updateQuote(ctx context.Context) { } } - askPrice = askPrice.Mul(fixedpoint.One.Add(askMargin)) if i == 0 { + askPrice = askPrice.Mul(fixedpoint.One.Add(quote.AskMargin)) makerBestAskPriceMetrics.With(labels).Set(askPrice.Float64()) - } else if i > 0 && pips.Sign() > 0 { - askPrice = askPrice.Add(pips.Mul(fixedpoint.NewFromInt(int64(i)).Mul(s.makerMarket.TickSize))) + } else if i > 0 && quote.AskLayerPips.Sign() > 0 { + pips := quote.AskLayerPips.Mul(s.makerMarket.TickSize) + askPrice = askPrice.Add(pips) } if makerQuota.BaseAsset.Lock(askQuantity) && hedgeQuota.QuoteAsset.Lock(askQuantity.Mul(askPrice)) {