From d9fb9ff3e01c4e4c99b653e4470201a7c447212b Mon Sep 17 00:00:00 2001 From: c9s Date: Thu, 29 Aug 2024 13:18:50 +0800 Subject: [PATCH 1/6] xmaker: remove unused var --- pkg/strategy/xmaker/strategy.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/pkg/strategy/xmaker/strategy.go b/pkg/strategy/xmaker/strategy.go index 67811e630..52dec31f5 100644 --- a/pkg/strategy/xmaker/strategy.go +++ b/pkg/strategy/xmaker/strategy.go @@ -25,8 +25,6 @@ import ( var defaultMargin = fixedpoint.NewFromFloat(0.003) var two = fixedpoint.NewFromInt(2) -var lastPriceModifier = fixedpoint.NewFromFloat(1.001) - const priceUpdateTimeout = 30 * time.Second const ID = "xmaker" From 9ebab4f4f7f5a6940cdbde945fe1a14fdd8d988d Mon Sep 17 00:00:00 2001 From: c9s Date: Fri, 30 Aug 2024 15:44:55 +0800 Subject: [PATCH 2/6] xmaker: add signal providers --- pkg/strategy/xmaker/metrics.go | 7 ++ pkg/strategy/xmaker/signal_boll.go | 87 ++++++++++++++++ pkg/strategy/xmaker/signal_book.go | 70 +++++++++++++ pkg/strategy/xmaker/strategy.go | 154 +++++++++++++++++++++++------ 4 files changed, 289 insertions(+), 29 deletions(-) create mode 100644 pkg/strategy/xmaker/signal_boll.go create mode 100644 pkg/strategy/xmaker/signal_book.go diff --git a/pkg/strategy/xmaker/metrics.go b/pkg/strategy/xmaker/metrics.go index a05743d19..9fb070d1e 100644 --- a/pkg/strategy/xmaker/metrics.go +++ b/pkg/strategy/xmaker/metrics.go @@ -38,6 +38,12 @@ var askMarginMetrics = prometheus.NewGaugeVec( Help: "the current ask margin (dynamic)", }, []string{"strategy_type", "strategy_id", "exchange", "symbol"}) +var finalSignalMetrics = prometheus.NewGaugeVec( + prometheus.GaugeOpts{ + Name: "xmaker_final_signal", + Help: "", + }, []string{"strategy_type", "strategy_id", "exchange", "symbol"}) + var configNumOfLayersMetrics = prometheus.NewGaugeVec( prometheus.GaugeOpts{ Name: "xmaker_config_num_of_layers", @@ -70,6 +76,7 @@ func init() { makerBestAskPriceMetrics, bidMarginMetrics, askMarginMetrics, + finalSignalMetrics, configNumOfLayersMetrics, configMaxExposureMetrics, configBidMarginMetrics, diff --git a/pkg/strategy/xmaker/signal_boll.go b/pkg/strategy/xmaker/signal_boll.go new file mode 100644 index 000000000..dedb03c56 --- /dev/null +++ b/pkg/strategy/xmaker/signal_boll.go @@ -0,0 +1,87 @@ +package xmaker + +import ( + "context" + + "github.com/prometheus/client_golang/prometheus" + + "github.com/c9s/bbgo/pkg/bbgo" + "github.com/c9s/bbgo/pkg/fixedpoint" + "github.com/c9s/bbgo/pkg/indicator/v2" + "github.com/c9s/bbgo/pkg/types" +) + +var bollingerBandSignalMetrics = prometheus.NewGaugeVec( + prometheus.GaugeOpts{ + Name: "xmaker_bollinger_band_signal", + Help: "", + }, []string{"symbol"}) + +func init() { + prometheus.MustRegister(bollingerBandSignalMetrics) +} + +type BollingerBandTrendSignal struct { + types.IntervalWindow + MinBandWidth float64 `json:"minBandWidth"` + MaxBandWidth float64 `json:"maxBandWidth"` + + indicator *indicatorv2.BOLLStream + symbol string + lastK *types.KLine +} + +func (s *BollingerBandTrendSignal) Bind(ctx context.Context, session *bbgo.ExchangeSession, symbol string) error { + if s.MaxBandWidth == 0.0 { + s.MaxBandWidth = 2.0 + } + + if s.MinBandWidth == 0.0 { + s.MinBandWidth = 1.0 + } + + s.symbol = symbol + s.indicator = session.Indicators(symbol).BOLL(s.IntervalWindow, s.MinBandWidth) + + session.MarketDataStream.OnKLineClosed(types.KLineWith(s.symbol, s.IntervalWindow.Interval, func(kline types.KLine) { + s.lastK = &kline + })) + + bollingerBandSignalMetrics.WithLabelValues(s.symbol).Set(0.0) + return nil +} + +func (s *BollingerBandTrendSignal) CalculateSignal(ctx context.Context) (float64, error) { + if s.lastK == nil { + return 0, nil + } + + closePrice := s.lastK.Close + + // 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.indicator.DownBand.Last(0)) + lastUpBand := fixedpoint.NewFromFloat(s.indicator.UpBand.Last(0)) + + log.Infof("bollinger band: up/down = %f/%f, close price = %f", + lastUpBand.Float64(), + lastDownBand.Float64(), + closePrice.Float64()) + + // if the price is inside the band, do not vote + if closePrice.Compare(lastDownBand) > 0 && closePrice.Compare(lastUpBand) < 0 { + return 0.0, nil + } + + maxBandWidth := s.indicator.StdDev.Last(0) * s.MaxBandWidth + + signal := 0.0 + if closePrice.Compare(lastDownBand) < 0 { + signal = lastDownBand.Sub(closePrice).Float64() / maxBandWidth * -2.0 + } else if closePrice.Compare(lastUpBand) > 0 { + signal = closePrice.Sub(lastUpBand).Float64() / maxBandWidth * 2.0 + } + + log.Infof("bollinger signal: %f", signal) + return signal, nil +} diff --git a/pkg/strategy/xmaker/signal_book.go b/pkg/strategy/xmaker/signal_book.go new file mode 100644 index 000000000..ee1cb9f76 --- /dev/null +++ b/pkg/strategy/xmaker/signal_book.go @@ -0,0 +1,70 @@ +package xmaker + +import ( + "context" + + "github.com/pkg/errors" + "github.com/prometheus/client_golang/prometheus" + + "github.com/c9s/bbgo/pkg/bbgo" + "github.com/c9s/bbgo/pkg/fixedpoint" + "github.com/c9s/bbgo/pkg/types" +) + +var orderBookSignalMetrics = prometheus.NewGaugeVec( + prometheus.GaugeOpts{ + Name: "xmaker_order_book_signal", + Help: "", + }, []string{"symbol"}) + +func init() { + prometheus.MustRegister(orderBookSignalMetrics) +} + +type OrderBookBestPriceVolumeSignal struct { + RatioThreshold fixedpoint.Value `json:"ratioThreshold"` + MinVolume fixedpoint.Value `json:"minVolume"` + + symbol string + book *types.StreamOrderBook +} + +func (s *OrderBookBestPriceVolumeSignal) Bind(ctx context.Context, session *bbgo.ExchangeSession, symbol string) error { + if s.book == nil { + return errors.New("s.book can not be nil") + } + + s.symbol = symbol + orderBookSignalMetrics.WithLabelValues(s.symbol).Set(0.0) + return nil +} + +func (s *OrderBookBestPriceVolumeSignal) CalculateSignal(ctx context.Context) (float64, error) { + bid, ask, ok := s.book.BestBidAndAsk() + if !ok { + return 0.0, nil + } + + if bid.Volume.Compare(s.MinVolume) < 0 && ask.Volume.Compare(s.MinVolume) < 0 { + return 0.0, nil + } + + log.Infof("OrderBookBestPriceVolumeSignal: bid/ask = %f/%f", bid.Volume.Float64(), ask.Volume.Float64()) + + // TODO: may use scale to define this + sumVol := bid.Volume.Add(ask.Volume) + bidRatio := bid.Volume.Div(sumVol) + askRatio := ask.Volume.Div(sumVol) + denominator := fixedpoint.One.Sub(s.RatioThreshold) + signal := 0.0 + if bidRatio.Compare(s.RatioThreshold) >= 0 { + numerator := bidRatio.Sub(s.RatioThreshold) + signal = numerator.Div(denominator).Float64() + } else if askRatio.Compare(s.RatioThreshold) >= 0 { + numerator := askRatio.Sub(s.RatioThreshold) + signal = -numerator.Div(denominator).Float64() + } + + orderBookSignalMetrics.WithLabelValues(s.symbol).Set(signal) + return signal, nil +} diff --git a/pkg/strategy/xmaker/strategy.go b/pkg/strategy/xmaker/strategy.go index 52dec31f5..d51628902 100644 --- a/pkg/strategy/xmaker/strategy.go +++ b/pkg/strategy/xmaker/strategy.go @@ -31,6 +31,41 @@ const ID = "xmaker" var log = logrus.WithField("strategy", ID) +type Quote struct { + BestBidPrice, BestAskPrice fixedpoint.Value + + BidMargin, AskMargin fixedpoint.Value + + // BidLayerPips is the price pips between each layer + BidLayerPips, AskLayerPips fixedpoint.Value +} + +type SessionBinder interface { + Bind(ctx context.Context, session *bbgo.ExchangeSession, symbol string) error +} + +type SignalNumber float64 + +const ( + SignalNumberMaxLong = 2.0 + SignalNumberMaxShort = -2.0 +) + +type SignalProvider interface { + CalculateSignal(ctx context.Context) (float64, error) +} + +type KLineShapeSignal struct { + FullBodyThreshold float64 `json:"fullBodyThreshold"` +} + +type SignalConfig struct { + Weight float64 `json:"weight"` + BollingerBandTrendSignal *BollingerBandTrendSignal `json:"bollingerBandTrend,omitempty"` + OrderBookBestPriceSignal *OrderBookBestPriceVolumeSignal `json:"orderBookBestPrice,omitempty"` + KLineShapeSignal *KLineShapeSignal `json:"klineShape,omitempty"` +} + func init() { bbgo.RegisterStrategy(ID, &Strategy{}) } @@ -50,6 +85,8 @@ type Strategy struct { HedgeInterval types.Duration `json:"hedgeInterval"` OrderCancelWaitTime types.Duration `json:"orderCancelWaitTime"` + SignalConfigList []SignalConfig `json:"signals"` + Margin fixedpoint.Value `json:"margin"` BidMargin fixedpoint.Value `json:"bidMargin"` AskMargin fixedpoint.Value `json:"askMargin"` @@ -138,6 +175,8 @@ type Strategy struct { circuitBreakerAlertLimiter *rate.Limiter logger logrus.FieldLogger + + metricsLabels prometheus.Labels } func (s *Strategy) ID() string { @@ -192,18 +231,16 @@ func (s *Strategy) Initialize() error { "strategy": ID, "strategy_id": s.InstanceID(), }) + + s.metricsLabels = prometheus.Labels{ + "strategy_type": ID, + "strategy_id": s.InstanceID(), + "exchange": s.MakerExchange, + "symbol": s.Symbol, + } 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 @@ -211,12 +248,6 @@ func (s *Strategy) getBollingerTrend(quote *Quote) int { lastDownBand := fixedpoint.NewFromFloat(s.boll.DownBand.Last(0)) lastUpBand := fixedpoint.NewFromFloat(s.boll.UpBand.Last(0)) - s.logger.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 { @@ -282,6 +313,43 @@ func (s *Strategy) applyBollingerMargin( return nil } +func (s *Strategy) calculateSignal(ctx context.Context) (float64, error) { + sum := 0.0 + voters := 0.0 + for _, signal := range s.SignalConfigList { + if signal.OrderBookBestPriceSignal != nil { + sig, err := signal.OrderBookBestPriceSignal.CalculateSignal(ctx) + if err != nil { + return 0, err + } + + if signal.Weight > 0.0 { + sum += sig * signal.Weight + voters += signal.Weight + } else { + sum += sig + voters++ + } + + } else if signal.BollingerBandTrendSignal != nil { + sig, err := signal.BollingerBandTrendSignal.CalculateSignal(ctx) + if err != nil { + return 0, err + } + + if signal.Weight > 0.0 { + sum += sig * signal.Weight + voters += signal.Weight + } else { + sum += sig + voters++ + } + } + } + + return sum / voters, nil +} + func (s *Strategy) updateQuote(ctx context.Context) { if err := s.activeMakerOrders.GracefulCancel(ctx, s.makerSession.Exchange); err != nil { s.logger.Warnf("there are some %s orders not canceled, skipping placing maker orders", s.Symbol) @@ -293,6 +361,15 @@ func (s *Strategy) updateQuote(ctx context.Context) { return } + signal, err := s.calculateSignal(ctx) + if err != nil { + return + } + + s.logger.Infof("Final signal: %f", signal) + + finalSignalMetrics.With(s.metricsLabels).Set(signal) + if s.CircuitBreaker != nil { now := time.Now() if reason, halted := s.CircuitBreaker.IsHalted(now); halted { @@ -500,13 +577,6 @@ func (s *Strategy) updateQuote(ctx context.Context) { } } - labels := prometheus.Labels{ - "strategy_type": ID, - "strategy_id": s.InstanceID(), - "exchange": s.MakerExchange, - "symbol": s.Symbol, - } - bidExposureInUsd := fixedpoint.Zero askExposureInUsd := fixedpoint.Zero bidPrice := quote.BestBidPrice @@ -520,8 +590,8 @@ func (s *Strategy) updateQuote(ctx context.Context) { return } - bidMarginMetrics.With(labels).Set(quote.BidMargin.Float64()) - askMarginMetrics.With(labels).Set(quote.AskMargin.Float64()) + bidMarginMetrics.With(s.metricsLabels).Set(quote.BidMargin.Float64()) + askMarginMetrics.With(s.metricsLabels).Set(quote.AskMargin.Float64()) for i := 0; i < s.NumLayers; i++ { // for maker bid orders @@ -566,7 +636,7 @@ func (s *Strategy) updateQuote(ctx context.Context) { if i == 0 { s.logger.Infof("maker best bid price %f", bidPrice.Float64()) - makerBestBidPriceMetrics.With(labels).Set(bidPrice.Float64()) + makerBestBidPriceMetrics.With(s.metricsLabels).Set(bidPrice.Float64()) } if makerQuota.QuoteAsset.Lock(bidQuantity.Mul(bidPrice)) && hedgeQuota.BaseAsset.Lock(bidQuantity) { @@ -634,7 +704,7 @@ func (s *Strategy) updateQuote(ctx context.Context) { if i == 0 { s.logger.Infof("maker best ask price %f", askPrice.Float64()) - makerBestAskPriceMetrics.With(labels).Set(askPrice.Float64()) + makerBestAskPriceMetrics.With(s.metricsLabels).Set(askPrice.Float64()) } if makerQuota.BaseAsset.Lock(askQuantity) && hedgeQuota.QuoteAsset.Lock(askQuantity.Mul(askPrice)) { @@ -687,8 +757,8 @@ func (s *Strategy) updateQuote(ctx context.Context) { log.WithError(err).Errorf("unable to place maker orders: %+v", formattedOrders) } - openOrderBidExposureInUsdMetrics.With(labels).Set(bidExposureInUsd.Float64()) - openOrderAskExposureInUsdMetrics.With(labels).Set(askExposureInUsd.Float64()) + openOrderBidExposureInUsdMetrics.With(s.metricsLabels).Set(bidExposureInUsd.Float64()) + openOrderAskExposureInUsdMetrics.With(s.metricsLabels).Set(askExposureInUsd.Float64()) _ = errIdx _ = createdOrders @@ -1039,7 +1109,6 @@ func (s *Strategy) hedgeWorker(ctx context.Context) { func (s *Strategy) CrossRun( ctx context.Context, orderExecutionRouter bbgo.OrderExecutionRouter, sessions map[string]*bbgo.ExchangeSession, ) error { - instanceID := s.InstanceID() // configure sessions @@ -1125,6 +1194,14 @@ func (s *Strategy) CrossRun( }) } + s.sourceSession.MarketDataStream.OnKLineClosed(types.KLineWith(s.Symbol, types.Interval1m, func(k types.KLine) { + s.priceSolver.Update(k.Symbol, k.Close) + feeToken := s.sourceSession.Exchange.PlatformFeeCurrency() + if feePrice, ok := s.priceSolver.ResolvePrice(feeToken, "USDT"); ok { + s.Position.SetFeeAverageCost(feeToken, feePrice) + } + })) + if s.ProfitFixerConfig != nil { bbgo.Notify("Fixing %s profitStats and position...", s.Symbol) @@ -1169,6 +1246,25 @@ func (s *Strategy) CrossRun( s.book = types.NewStreamBook(s.Symbol, s.sourceSession.ExchangeName) s.book.BindStream(s.sourceSession.MarketDataStream) + for _, signalConfig := range s.SignalConfigList { + var sigAny any + switch { + case signalConfig.OrderBookBestPriceSignal != nil: + sig := signalConfig.OrderBookBestPriceSignal + sig.book = s.book + sigAny = sig + + case signalConfig.BollingerBandTrendSignal != nil: + + } + + if sigAny != nil { + if binder, ok := sigAny.(SessionBinder); ok { + binder.Bind(ctx, s.sourceSession, s.Symbol) + } + } + } + s.activeMakerOrders = bbgo.NewActiveOrderBook(s.Symbol) s.activeMakerOrders.BindStream(s.makerSession.UserDataStream) From b8abc065de8a218f0473f50b3001ab85f5b19a7e Mon Sep 17 00:00:00 2001 From: c9s Date: Fri, 30 Aug 2024 17:15:12 +0800 Subject: [PATCH 3/6] xmaker: initialize bollinger band signal --- pkg/strategy/xmaker/signal_boll.go | 11 +++++------ pkg/strategy/xmaker/signal_book.go | 4 ++-- pkg/strategy/xmaker/strategy.go | 22 ++++++++-------------- 3 files changed, 15 insertions(+), 22 deletions(-) diff --git a/pkg/strategy/xmaker/signal_boll.go b/pkg/strategy/xmaker/signal_boll.go index dedb03c56..c906ad709 100644 --- a/pkg/strategy/xmaker/signal_boll.go +++ b/pkg/strategy/xmaker/signal_boll.go @@ -63,11 +63,6 @@ func (s *BollingerBandTrendSignal) CalculateSignal(ctx context.Context) (float64 lastDownBand := fixedpoint.NewFromFloat(s.indicator.DownBand.Last(0)) lastUpBand := fixedpoint.NewFromFloat(s.indicator.UpBand.Last(0)) - log.Infof("bollinger band: up/down = %f/%f, close price = %f", - lastUpBand.Float64(), - lastDownBand.Float64(), - closePrice.Float64()) - // if the price is inside the band, do not vote if closePrice.Compare(lastDownBand) > 0 && closePrice.Compare(lastUpBand) < 0 { return 0.0, nil @@ -82,6 +77,10 @@ func (s *BollingerBandTrendSignal) CalculateSignal(ctx context.Context) (float64 signal = closePrice.Sub(lastUpBand).Float64() / maxBandWidth * 2.0 } - log.Infof("bollinger signal: %f", signal) + log.Infof("[BollingerBandTrendSignal] %f up/down = %f/%f, close price = %f", + signal, + lastUpBand.Float64(), + lastDownBand.Float64(), + closePrice.Float64()) return signal, nil } diff --git a/pkg/strategy/xmaker/signal_book.go b/pkg/strategy/xmaker/signal_book.go index ee1cb9f76..0423026a7 100644 --- a/pkg/strategy/xmaker/signal_book.go +++ b/pkg/strategy/xmaker/signal_book.go @@ -49,8 +49,6 @@ func (s *OrderBookBestPriceVolumeSignal) CalculateSignal(ctx context.Context) (f return 0.0, nil } - log.Infof("OrderBookBestPriceVolumeSignal: bid/ask = %f/%f", bid.Volume.Float64(), ask.Volume.Float64()) - // TODO: may use scale to define this sumVol := bid.Volume.Add(ask.Volume) bidRatio := bid.Volume.Div(sumVol) @@ -65,6 +63,8 @@ func (s *OrderBookBestPriceVolumeSignal) CalculateSignal(ctx context.Context) (f signal = -numerator.Div(denominator).Float64() } + log.Infof("[OrderBookBestPriceVolumeSignal] %f bid/ask = %f/%f", signal, bid.Volume.Float64(), ask.Volume.Float64()) + orderBookSignalMetrics.WithLabelValues(s.symbol).Set(signal) return signal, nil } diff --git a/pkg/strategy/xmaker/strategy.go b/pkg/strategy/xmaker/strategy.go index d51628902..0fd5743f6 100644 --- a/pkg/strategy/xmaker/strategy.go +++ b/pkg/strategy/xmaker/strategy.go @@ -1247,20 +1247,14 @@ func (s *Strategy) CrossRun( s.book.BindStream(s.sourceSession.MarketDataStream) for _, signalConfig := range s.SignalConfigList { - var sigAny any - switch { - case signalConfig.OrderBookBestPriceSignal != nil: - sig := signalConfig.OrderBookBestPriceSignal - sig.book = s.book - sigAny = sig - - case signalConfig.BollingerBandTrendSignal != nil: - - } - - if sigAny != nil { - if binder, ok := sigAny.(SessionBinder); ok { - binder.Bind(ctx, s.sourceSession, s.Symbol) + if signalConfig.OrderBookBestPriceSignal != nil { + signalConfig.OrderBookBestPriceSignal.book = s.book + if err := signalConfig.OrderBookBestPriceSignal.Bind(ctx, s.sourceSession, s.Symbol); err != nil { + return err + } + } else if signalConfig.BollingerBandTrendSignal != nil { + if err := signalConfig.BollingerBandTrendSignal.Bind(ctx, s.sourceSession, s.Symbol); err != nil { + return err } } } From 371db8e7d1cb272f1a30160dcfb21410ba0f4598 Mon Sep 17 00:00:00 2001 From: c9s Date: Fri, 30 Aug 2024 17:18:29 +0800 Subject: [PATCH 4/6] xmaker: update signal conditions to metrics --- pkg/strategy/xmaker/signal_boll.go | 13 +++++++------ pkg/strategy/xmaker/signal_book.go | 8 +++----- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/pkg/strategy/xmaker/signal_boll.go b/pkg/strategy/xmaker/signal_boll.go index c906ad709..f40cd1edc 100644 --- a/pkg/strategy/xmaker/signal_boll.go +++ b/pkg/strategy/xmaker/signal_boll.go @@ -63,15 +63,14 @@ func (s *BollingerBandTrendSignal) CalculateSignal(ctx context.Context) (float64 lastDownBand := fixedpoint.NewFromFloat(s.indicator.DownBand.Last(0)) lastUpBand := fixedpoint.NewFromFloat(s.indicator.UpBand.Last(0)) - // if the price is inside the band, do not vote - if closePrice.Compare(lastDownBand) > 0 && closePrice.Compare(lastUpBand) < 0 { - return 0.0, nil - } - maxBandWidth := s.indicator.StdDev.Last(0) * s.MaxBandWidth signal := 0.0 - if closePrice.Compare(lastDownBand) < 0 { + + // if the price is inside the band, do not vote + if closePrice.Compare(lastDownBand) > 0 && closePrice.Compare(lastUpBand) < 0 { + signal = 0.0 + } else if closePrice.Compare(lastDownBand) < 0 { signal = lastDownBand.Sub(closePrice).Float64() / maxBandWidth * -2.0 } else if closePrice.Compare(lastUpBand) > 0 { signal = closePrice.Sub(lastUpBand).Float64() / maxBandWidth * 2.0 @@ -82,5 +81,7 @@ func (s *BollingerBandTrendSignal) CalculateSignal(ctx context.Context) (float64 lastUpBand.Float64(), lastDownBand.Float64(), closePrice.Float64()) + + bollingerBandSignalMetrics.WithLabelValues(s.symbol).Set(signal) return signal, nil } diff --git a/pkg/strategy/xmaker/signal_book.go b/pkg/strategy/xmaker/signal_book.go index 0423026a7..ac7288e51 100644 --- a/pkg/strategy/xmaker/signal_book.go +++ b/pkg/strategy/xmaker/signal_book.go @@ -45,17 +45,15 @@ func (s *OrderBookBestPriceVolumeSignal) CalculateSignal(ctx context.Context) (f return 0.0, nil } - if bid.Volume.Compare(s.MinVolume) < 0 && ask.Volume.Compare(s.MinVolume) < 0 { - return 0.0, nil - } - // TODO: may use scale to define this sumVol := bid.Volume.Add(ask.Volume) bidRatio := bid.Volume.Div(sumVol) askRatio := ask.Volume.Div(sumVol) denominator := fixedpoint.One.Sub(s.RatioThreshold) signal := 0.0 - if bidRatio.Compare(s.RatioThreshold) >= 0 { + if bid.Volume.Compare(s.MinVolume) < 0 && ask.Volume.Compare(s.MinVolume) < 0 { + signal = 0.0 + } else if bidRatio.Compare(s.RatioThreshold) >= 0 { numerator := bidRatio.Sub(s.RatioThreshold) signal = numerator.Div(denominator).Float64() } else if askRatio.Compare(s.RatioThreshold) >= 0 { From cc820d3df0f1bacadc4f98fd01d2521e7eeb00c1 Mon Sep 17 00:00:00 2001 From: c9s Date: Fri, 30 Aug 2024 17:39:25 +0800 Subject: [PATCH 5/6] xmaker: apply margin from signal --- pkg/strategy/xmaker/metrics.go | 6 +-- pkg/strategy/xmaker/strategy.go | 67 ++++++++++++++++++++++++++++++--- 2 files changed, 65 insertions(+), 8 deletions(-) diff --git a/pkg/strategy/xmaker/metrics.go b/pkg/strategy/xmaker/metrics.go index 9fb070d1e..adedf59f6 100644 --- a/pkg/strategy/xmaker/metrics.go +++ b/pkg/strategy/xmaker/metrics.go @@ -38,9 +38,9 @@ var askMarginMetrics = prometheus.NewGaugeVec( Help: "the current ask margin (dynamic)", }, []string{"strategy_type", "strategy_id", "exchange", "symbol"}) -var finalSignalMetrics = prometheus.NewGaugeVec( +var aggregatedSignalMetrics = prometheus.NewGaugeVec( prometheus.GaugeOpts{ - Name: "xmaker_final_signal", + Name: "xmaker_aggregated_signal", Help: "", }, []string{"strategy_type", "strategy_id", "exchange", "symbol"}) @@ -76,7 +76,7 @@ func init() { makerBestAskPriceMetrics, bidMarginMetrics, askMarginMetrics, - finalSignalMetrics, + aggregatedSignalMetrics, configNumOfLayersMetrics, configMaxExposureMetrics, configBidMarginMetrics, diff --git a/pkg/strategy/xmaker/strategy.go b/pkg/strategy/xmaker/strategy.go index 0fd5743f6..df9e842a9 100644 --- a/pkg/strategy/xmaker/strategy.go +++ b/pkg/strategy/xmaker/strategy.go @@ -85,7 +85,9 @@ type Strategy struct { HedgeInterval types.Duration `json:"hedgeInterval"` OrderCancelWaitTime types.Duration `json:"orderCancelWaitTime"` - SignalConfigList []SignalConfig `json:"signals"` + EnableSignalMargin bool `json:"enableSignalMargin"` + SignalConfigList []SignalConfig `json:"signals"` + SignalMarginScale *bbgo.SlideRule `json:"signalMarginScale,omitempty"` Margin fixedpoint.Value `json:"margin"` BidMargin fixedpoint.Value `json:"bidMargin"` @@ -257,6 +259,39 @@ func (s *Strategy) getBollingerTrend(quote *Quote) int { } } +func (s *Strategy) applySignalMargin(ctx context.Context, quote *Quote) error { + signal, err := s.calculateSignal(ctx) + if err != nil { + return err + } + + s.logger.Infof("final signal: %f", signal) + + scale, err := s.SignalMarginScale.Scale() + if err != nil { + return err + } + + margin := scale.Call(signal) + + s.logger.Infof("signalMargin: %f", margin) + + marginFp := fixedpoint.NewFromFloat(margin) + if signal < 0.0 { + quote.BidMargin = quote.BidMargin.Add(marginFp) + if signal <= -2.0 { + // quote.BidMargin = fixedpoint.Zero + } + } else if signal > 0.0 { + quote.AskMargin = quote.AskMargin.Add(marginFp) + if signal >= 2.0 { + // quote.AskMargin = fixedpoint.Zero + } + } + + return nil +} + // applyBollingerMargin applies the bollinger band margin to the quote func (s *Strategy) applyBollingerMargin( quote *Quote, @@ -323,6 +358,10 @@ func (s *Strategy) calculateSignal(ctx context.Context) (float64, error) { return 0, err } + if sig == 0.0 { + continue + } + if signal.Weight > 0.0 { sum += sig * signal.Weight voters += signal.Weight @@ -337,6 +376,10 @@ func (s *Strategy) calculateSignal(ctx context.Context) (float64, error) { return 0, err } + if sig == 0.0 { + continue + } + if signal.Weight > 0.0 { sum += sig * signal.Weight voters += signal.Weight @@ -366,9 +409,8 @@ func (s *Strategy) updateQuote(ctx context.Context) { return } - s.logger.Infof("Final signal: %f", signal) - - finalSignalMetrics.With(s.metricsLabels).Set(signal) + s.logger.Infof("aggregated signal: %f", signal) + aggregatedSignalMetrics.With(s.metricsLabels).Set(signal) if s.CircuitBreaker != nil { now := time.Now() @@ -571,7 +613,12 @@ func (s *Strategy) updateQuote(ctx context.Context) { AskLayerPips: s.Pips, } - if s.EnableBollBandMargin { + if s.EnableSignalMargin { + if err := s.applySignalMargin(ctx, quote); err != nil { + s.logger.WithError(err).Errorf("unable to apply signal margin") + } + + } else if s.EnableBollBandMargin { if err := s.applyBollingerMargin(quote); err != nil { log.WithError(err).Errorf("unable to apply bollinger margin") } @@ -1246,6 +1293,16 @@ func (s *Strategy) CrossRun( s.book = types.NewStreamBook(s.Symbol, s.sourceSession.ExchangeName) s.book.BindStream(s.sourceSession.MarketDataStream) + if s.EnableSignalMargin { + scale, err := s.SignalMarginScale.Scale() + if err != nil { + return err + } + if solveErr := scale.Solve(); solveErr != nil { + return solveErr + } + } + for _, signalConfig := range s.SignalConfigList { if signalConfig.OrderBookBestPriceSignal != nil { signalConfig.OrderBookBestPriceSignal.book = s.book From 7c4b3e81df8fb2d9bcf3c6e6520dc8eb29c3502e Mon Sep 17 00:00:00 2001 From: c9s Date: Fri, 30 Aug 2024 17:42:20 +0800 Subject: [PATCH 6/6] xmaker: add more logs --- pkg/strategy/xmaker/strategy.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/pkg/strategy/xmaker/strategy.go b/pkg/strategy/xmaker/strategy.go index df9e842a9..db4d29a5c 100644 --- a/pkg/strategy/xmaker/strategy.go +++ b/pkg/strategy/xmaker/strategy.go @@ -265,7 +265,7 @@ func (s *Strategy) applySignalMargin(ctx context.Context, quote *Quote) error { return err } - s.logger.Infof("final signal: %f", signal) + s.logger.Infof("aggregated signal: %f", signal) scale, err := s.SignalMarginScale.Scale() if err != nil { @@ -274,7 +274,7 @@ func (s *Strategy) applySignalMargin(ctx context.Context, quote *Quote) error { margin := scale.Call(signal) - s.logger.Infof("signalMargin: %f", margin) + s.logger.Infof("signal margin: %f", margin) marginFp := fixedpoint.NewFromFloat(margin) if signal < 0.0 { @@ -282,11 +282,15 @@ func (s *Strategy) applySignalMargin(ctx context.Context, quote *Quote) error { if signal <= -2.0 { // quote.BidMargin = fixedpoint.Zero } + + s.logger.Infof("adjusted bid margin: %f", quote.BidMargin.Float64()) } else if signal > 0.0 { quote.AskMargin = quote.AskMargin.Add(marginFp) if signal >= 2.0 { // quote.AskMargin = fixedpoint.Zero } + + s.logger.Infof("adjusted ask margin: %f", quote.AskMargin.Float64()) } return nil