From 5b23671b446bde50c27683dd0c3dd545208fc7ce Mon Sep 17 00:00:00 2001 From: c9s Date: Wed, 20 Nov 2024 13:40:09 +0800 Subject: [PATCH 1/9] types: reset order book when stream disconnected --- pkg/types/orderbook.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pkg/types/orderbook.go b/pkg/types/orderbook.go index 0134cfbd0..328ebe204 100644 --- a/pkg/types/orderbook.go +++ b/pkg/types/orderbook.go @@ -212,6 +212,10 @@ func (sb *StreamOrderBook) updateMetrics(t time.Time) { } func (sb *StreamOrderBook) BindStream(stream Stream) { + stream.OnDisconnect(func() { + sb.Reset() + }) + stream.OnBookSnapshot(func(book SliceOrderBook) { if sb.MutexOrderBook.Symbol != book.Symbol { return From 2f261dd0a5e2206115293a267aebc404b68d74db Mon Sep 17 00:00:00 2001 From: c9s Date: Wed, 20 Nov 2024 13:44:06 +0800 Subject: [PATCH 2/9] xmaker: add MaxQuoteUsageRatio option --- pkg/strategy/xmaker/strategy.go | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/pkg/strategy/xmaker/strategy.go b/pkg/strategy/xmaker/strategy.go index b57ab3d70..14c4723dc 100644 --- a/pkg/strategy/xmaker/strategy.go +++ b/pkg/strategy/xmaker/strategy.go @@ -179,6 +179,8 @@ type Strategy struct { RecoverTradeScanPeriod types.Duration `json:"recoverTradeScanPeriod"` + MaxQuoteUsageRatio fixedpoint.Value `json:"maxQuoteUsageRatio"` + NumLayers int `json:"numLayers"` // Pips is the pips of the layer prices @@ -696,7 +698,14 @@ func (s *Strategy) updateQuote(ctx context.Context) error { if b, ok := makerBalances[s.makerMarket.QuoteCurrency]; ok { if b.Available.Compare(s.makerMarket.MinNotional) > 0 { - makerQuota.QuoteAsset.Add(b.Available) + if s.MaxQuoteUsageRatio.Sign() > 0 { + quoteAvailable := b.Available.Mul(s.MaxQuoteUsageRatio) + makerQuota.QuoteAsset.Add(quoteAvailable) + } else { + // use all quote balances as much as possible + makerQuota.QuoteAsset.Add(b.Available) + } + } else { disableMakerBid = true s.logger.Infof("%s maker bid disabled: insufficient quote balance %s", s.Symbol, b.String()) From c0ee183426c3151b9e687b286f1349764d3bc231 Mon Sep 17 00:00:00 2001 From: c9s Date: Wed, 20 Nov 2024 14:21:17 +0800 Subject: [PATCH 3/9] xmaker: set default signal margin scales --- pkg/strategy/xmaker/strategy.go | 42 ++++++++++++++++++++++++++++----- 1 file changed, 36 insertions(+), 6 deletions(-) diff --git a/pkg/strategy/xmaker/strategy.go b/pkg/strategy/xmaker/strategy.go index 14c4723dc..16fc1e181 100644 --- a/pkg/strategy/xmaker/strategy.go +++ b/pkg/strategy/xmaker/strategy.go @@ -126,9 +126,10 @@ type Strategy struct { SubscribeFeeTokenMarkets bool `json:"subscribeFeeTokenMarkets"` - EnableSignalMargin bool `json:"enableSignalMargin"` - SignalConfigList []SignalConfig `json:"signals"` - SignalMarginScale *bbgo.SlideRule `json:"signalMarginScale,omitempty"` + EnableSignalMargin bool `json:"enableSignalMargin"` + SignalConfigList []SignalConfig `json:"signals"` + SignalReverseSideMarginScale *bbgo.SlideRule `json:"signalReverseSideMarginScale,omitempty"` + SignalTrendSideMarginScale *bbgo.SlideRule `json:"signalTrendSideMarginScale,omitempty"` Margin fixedpoint.Value `json:"margin"` BidMargin fixedpoint.Value `json:"bidMargin"` @@ -379,7 +380,7 @@ func (s *Strategy) applySignalMargin(ctx context.Context, quote *Quote) error { return nil } - scale, err := s.SignalMarginScale.Scale() + scale, err := s.SignalReverseSideMarginScale.Scale() if err != nil { return err } @@ -844,7 +845,7 @@ func (s *Strategy) updateQuote(ctx context.Context) error { } if disableMakerAsk && disableMakerBid { - log.Warnf("%s bid/ask maker is disabled due to insufficient balances", s.Symbol) + log.Warnf("%s bid/ask maker is disabled", s.Symbol) return nil } @@ -1419,6 +1420,27 @@ func (s *Strategy) Defaults() error { s.CircuitBreaker.SetMetricsInfo(ID, s.InstanceID(), s.Symbol) } + if s.EnableSignalMargin { + if s.SignalReverseSideMarginScale == nil { + s.SignalReverseSideMarginScale = &bbgo.SlideRule{ + ExpScale: &bbgo.ExponentialScale{ + Domain: [2]float64{0, 2.0}, + Range: [2]float64{0.00010, 0.00500}, + }, + QuadraticScale: nil, + } + } + + if s.SignalTrendSideMarginScale == nil { + s.SignalTrendSideMarginScale = &bbgo.SlideRule{ + ExpScale: &bbgo.ExponentialScale{ + Domain: [2]float64{0, 2.0}, + Range: [2]float64{0.00010, 0.00500}, + }, + } + } + } + // circuitBreakerAlertLimiter is for CircuitBreaker alerts s.circuitBreakerAlertLimiter = rate.NewLimiter(rate.Every(3*time.Minute), 2) s.reportProfitStatsRateLimiter = rate.NewLimiter(rate.Every(3*time.Minute), 1) @@ -1770,7 +1792,15 @@ func (s *Strategy) CrossRun( if s.EnableSignalMargin { s.logger.Infof("signal margin is enabled") - scale, err := s.SignalMarginScale.Scale() + if s.SignalReverseSideMarginScale == nil { + return errors.New("signalReverseSideMarginScale can not be nil when signal margin is enabled") + } + + if s.SignalTrendSideMarginScale == nil { + return errors.New("signalTrendSideMarginScale can not be nil when signal margin is enabled") + } + + scale, err := s.SignalReverseSideMarginScale.Scale() if err != nil { return err } From f1f5f1ff3ac8ade7fc33fd66341d06a2ab1a50d7 Mon Sep 17 00:00:00 2001 From: c9s Date: Wed, 20 Nov 2024 15:09:18 +0800 Subject: [PATCH 4/9] xmaker: solve scale in the Initialize() call --- pkg/strategy/xmaker/strategy.go | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/pkg/strategy/xmaker/strategy.go b/pkg/strategy/xmaker/strategy.go index 16fc1e181..755988cb5 100644 --- a/pkg/strategy/xmaker/strategy.go +++ b/pkg/strategy/xmaker/strategy.go @@ -320,6 +320,29 @@ func (s *Strategy) Initialize() error { "exchange": s.MakerExchange, "symbol": s.Symbol, } + + if s.SignalReverseSideMarginScale != nil { + scale, err := s.SignalReverseSideMarginScale.Scale() + if err != nil { + return err + } + + if solveErr := scale.Solve(); solveErr != nil { + return solveErr + } + } + + if s.SignalTrendSideMarginScale != nil { + scale, err := s.SignalTrendSideMarginScale.Scale() + if err != nil { + return err + } + + if solveErr := scale.Solve(); solveErr != nil { + return solveErr + } + } + return nil } @@ -1805,10 +1828,6 @@ func (s *Strategy) CrossRun( return err } - if solveErr := scale.Solve(); solveErr != nil { - return solveErr - } - minAdditionalMargin := scale.Call(0.0) middleAdditionalMargin := scale.Call(1.0) maxAdditionalMargin := scale.Call(2.0) From 6e38210af802c99205ef5acf4ba724a4ce2dabe5 Mon Sep 17 00:00:00 2001 From: c9s Date: Wed, 20 Nov 2024 16:42:19 +0800 Subject: [PATCH 5/9] xmaker: add signalTrendSideMargin support --- pkg/strategy/xmaker/strategy.go | 130 ++++++++++++++++++++++---------- 1 file changed, 90 insertions(+), 40 deletions(-) diff --git a/pkg/strategy/xmaker/strategy.go b/pkg/strategy/xmaker/strategy.go index 755988cb5..763de64c7 100644 --- a/pkg/strategy/xmaker/strategy.go +++ b/pkg/strategy/xmaker/strategy.go @@ -109,6 +109,12 @@ func init() { bbgo.RegisterStrategy(ID, &Strategy{}) } +type SignalMargin struct { + Enabled bool `json:"enabled"` + Scale *bbgo.SlideRule `json:"scale,omitempty"` + Threshold float64 `json:"threshold,omitempty"` +} + type Strategy struct { Environment *bbgo.Environment @@ -126,14 +132,20 @@ type Strategy struct { SubscribeFeeTokenMarkets bool `json:"subscribeFeeTokenMarkets"` - EnableSignalMargin bool `json:"enableSignalMargin"` - SignalConfigList []SignalConfig `json:"signals"` - SignalReverseSideMarginScale *bbgo.SlideRule `json:"signalReverseSideMarginScale,omitempty"` - SignalTrendSideMarginScale *bbgo.SlideRule `json:"signalTrendSideMarginScale,omitempty"` + EnableSignalMargin bool `json:"enableSignalMargin"` + SignalConfigList []SignalConfig `json:"signals"` + + SignalReverseSideMargin *SignalMargin `json:"signalReverseSideMargin,omitempty"` + SignalTrendSideMargin *SignalMargin `json:"signalTrendSideMargin,omitempty"` + + // Margin is the default margin for the quote + Margin fixedpoint.Value `json:"margin"` + BidMargin fixedpoint.Value `json:"bidMargin"` + AskMargin fixedpoint.Value `json:"askMargin"` + + // MinMargin is the minimum margin protection for signal margin + MinMargin *fixedpoint.Value `json:"minMargin"` - Margin fixedpoint.Value `json:"margin"` - BidMargin fixedpoint.Value `json:"bidMargin"` - AskMargin fixedpoint.Value `json:"askMargin"` UseDepthPrice bool `json:"useDepthPrice"` DepthQuantity fixedpoint.Value `json:"depthQuantity"` SourceDepthLevel types.Depth `json:"sourceDepthLevel"` @@ -180,7 +192,7 @@ type Strategy struct { RecoverTradeScanPeriod types.Duration `json:"recoverTradeScanPeriod"` - MaxQuoteUsageRatio fixedpoint.Value `json:"maxQuoteUsageRatio"` + MaxQuoteQuotaRatio fixedpoint.Value `json:"maxQuoteQuotaRatio,omitempty"` NumLayers int `json:"numLayers"` @@ -321,8 +333,8 @@ func (s *Strategy) Initialize() error { "symbol": s.Symbol, } - if s.SignalReverseSideMarginScale != nil { - scale, err := s.SignalReverseSideMarginScale.Scale() + if s.SignalReverseSideMargin != nil && s.SignalReverseSideMargin.Scale != nil { + scale, err := s.SignalReverseSideMargin.Scale.Scale() if err != nil { return err } @@ -332,8 +344,8 @@ func (s *Strategy) Initialize() error { } } - if s.SignalTrendSideMarginScale != nil { - scale, err := s.SignalTrendSideMarginScale.Scale() + if s.SignalTrendSideMargin != nil && s.SignalTrendSideMargin.Scale != nil { + scale, err := s.SignalTrendSideMargin.Scale.Scale() if err != nil { return err } @@ -403,32 +415,65 @@ func (s *Strategy) applySignalMargin(ctx context.Context, quote *Quote) error { return nil } - scale, err := s.SignalReverseSideMarginScale.Scale() - if err != nil { - return err - } + signalAbs := math.Abs(signal) - margin := scale.Call(math.Abs(signal)) - - s.logger.Infof("signal margin: %f", margin) - - marginFp := fixedpoint.NewFromFloat(margin) - if signal < 0.0 { - quote.BidMargin = quote.BidMargin.Add(marginFp) - if signal <= -2.0 { - // quote.BidMargin = fixedpoint.Zero + var trendSideMarginDiscount, reverseSideMargin float64 + var trendSideMarginDiscountFp, reverseSideMarginFp fixedpoint.Value + if s.SignalTrendSideMargin != nil && s.SignalTrendSideMargin.Enabled { + trendSideMarginScale, err := s.SignalTrendSideMargin.Scale.Scale() + if err != nil { + return err } - 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 + if signalAbs > s.SignalTrendSideMargin.Threshold { + // trendSideMarginDiscount is the discount for the trend side margin + trendSideMarginDiscount = trendSideMarginScale.Call(math.Abs(signal)) + trendSideMarginDiscountFp = fixedpoint.NewFromFloat(trendSideMarginDiscount) + + if signal > 0.0 { + quote.BidMargin = quote.BidMargin.Sub(trendSideMarginDiscountFp) + } else if signal < 0.0 { + quote.AskMargin = quote.AskMargin.Sub(trendSideMarginDiscountFp) + } + } + } + + if s.SignalReverseSideMargin != nil && s.SignalReverseSideMargin.Enabled { + reverseSideMarginScale, err := s.SignalReverseSideMargin.Scale.Scale() + if err != nil { + return err } - s.logger.Infof("adjusted ask margin: %f", quote.AskMargin.Float64()) + if signalAbs > s.SignalReverseSideMargin.Threshold { + reverseSideMargin = reverseSideMarginScale.Call(math.Abs(signal)) + reverseSideMarginFp = fixedpoint.NewFromFloat(reverseSideMargin) + if signal < 0.0 { + quote.BidMargin = quote.BidMargin.Add(reverseSideMarginFp) + } else if signal > 0.0 { + quote.AskMargin = quote.AskMargin.Add(reverseSideMarginFp) + } + } } + s.logger.Infof("signal margin params: signal = %f, reverseSideMargin = %f, trendSideMarginDiscount = %f", signal, reverseSideMargin, trendSideMarginDiscount) + + s.logger.Infof("calculated signal margin: signal = %f, askMargin = %s, bidMargin = %s", + signal, + quote.AskMargin, + quote.BidMargin, + ) + + if s.MinMargin != nil { + quote.AskMargin = fixedpoint.Max(*s.MinMargin, quote.AskMargin) + quote.BidMargin = fixedpoint.Max(*s.MinMargin, quote.BidMargin) + } + + s.logger.Infof("final signal margin: signal = %f, askMargin = %s, bidMargin = %s", + signal, + quote.AskMargin, + quote.BidMargin, + ) + return nil } @@ -722,8 +767,8 @@ func (s *Strategy) updateQuote(ctx context.Context) error { if b, ok := makerBalances[s.makerMarket.QuoteCurrency]; ok { if b.Available.Compare(s.makerMarket.MinNotional) > 0 { - if s.MaxQuoteUsageRatio.Sign() > 0 { - quoteAvailable := b.Available.Mul(s.MaxQuoteUsageRatio) + if s.MaxQuoteQuotaRatio.Sign() > 0 { + quoteAvailable := b.Available.Mul(s.MaxQuoteQuotaRatio) makerQuota.QuoteAsset.Add(quoteAvailable) } else { // use all quote balances as much as possible @@ -1396,6 +1441,7 @@ func (s *Strategy) Defaults() error { if s.BollBandMarginFactor.IsZero() { s.BollBandMarginFactor = fixedpoint.One } + if s.BollBandMargin.IsZero() { s.BollBandMargin = fixedpoint.NewFromFloat(0.001) } @@ -1444,8 +1490,8 @@ func (s *Strategy) Defaults() error { } if s.EnableSignalMargin { - if s.SignalReverseSideMarginScale == nil { - s.SignalReverseSideMarginScale = &bbgo.SlideRule{ + if s.SignalReverseSideMargin.Scale == nil { + s.SignalReverseSideMargin.Scale = &bbgo.SlideRule{ ExpScale: &bbgo.ExponentialScale{ Domain: [2]float64{0, 2.0}, Range: [2]float64{0.00010, 0.00500}, @@ -1454,14 +1500,18 @@ func (s *Strategy) Defaults() error { } } - if s.SignalTrendSideMarginScale == nil { - s.SignalTrendSideMarginScale = &bbgo.SlideRule{ + if s.SignalTrendSideMargin.Scale == nil { + s.SignalTrendSideMargin.Scale = &bbgo.SlideRule{ ExpScale: &bbgo.ExponentialScale{ Domain: [2]float64{0, 2.0}, Range: [2]float64{0.00010, 0.00500}, }, } } + + if s.SignalTrendSideMargin.Threshold == 0.0 { + s.SignalTrendSideMargin.Threshold = 1.0 + } } // circuitBreakerAlertLimiter is for CircuitBreaker alerts @@ -1815,15 +1865,15 @@ func (s *Strategy) CrossRun( if s.EnableSignalMargin { s.logger.Infof("signal margin is enabled") - if s.SignalReverseSideMarginScale == nil { + if s.SignalReverseSideMargin == nil || s.SignalReverseSideMargin.Scale == nil { return errors.New("signalReverseSideMarginScale can not be nil when signal margin is enabled") } - if s.SignalTrendSideMarginScale == nil { + if s.SignalTrendSideMargin == nil || s.SignalTrendSideMargin.Scale == nil { return errors.New("signalTrendSideMarginScale can not be nil when signal margin is enabled") } - scale, err := s.SignalReverseSideMarginScale.Scale() + scale, err := s.SignalReverseSideMargin.Scale.Scale() if err != nil { return err } From f2366908c3ac3324b9b12b92412c9440251d45c0 Mon Sep 17 00:00:00 2001 From: c9s Date: Wed, 20 Nov 2024 17:56:45 +0800 Subject: [PATCH 6/9] xmaker: enhance DelayedHedge --- pkg/strategy/xmaker/strategy.go | 102 ++++++++++++++++++++++++++------ 1 file changed, 85 insertions(+), 17 deletions(-) diff --git a/pkg/strategy/xmaker/strategy.go b/pkg/strategy/xmaker/strategy.go index 763de64c7..e03650bf4 100644 --- a/pkg/strategy/xmaker/strategy.go +++ b/pkg/strategy/xmaker/strategy.go @@ -3,10 +3,13 @@ package xmaker import ( "context" "fmt" + "io" "math" + "os" "sync" "time" + "github.com/jedib0t/go-pretty/v6/table" "github.com/pkg/errors" "github.com/prometheus/client_golang/prometheus" "github.com/sirupsen/logrus" @@ -14,12 +17,14 @@ import ( "github.com/c9s/bbgo/pkg/bbgo" "github.com/c9s/bbgo/pkg/core" + "github.com/c9s/bbgo/pkg/dynamic" "github.com/c9s/bbgo/pkg/fixedpoint" indicatorv2 "github.com/c9s/bbgo/pkg/indicator/v2" "github.com/c9s/bbgo/pkg/pricesolver" "github.com/c9s/bbgo/pkg/profile/timeprofile" "github.com/c9s/bbgo/pkg/risk/circuitbreaker" "github.com/c9s/bbgo/pkg/strategy/common" + "github.com/c9s/bbgo/pkg/style" "github.com/c9s/bbgo/pkg/types" "github.com/c9s/bbgo/pkg/util" "github.com/c9s/bbgo/pkg/util/timejitter" @@ -115,6 +120,23 @@ type SignalMargin struct { Threshold float64 `json:"threshold,omitempty"` } +type DelayedHedge struct { + // EnableDelayHedge enables the delay hedge feature + Enabled bool `json:"enabled"` + + // MaxDelayDuration is the maximum delay duration to hedge the position + MaxDelayDuration types.Duration `json:"maxDelay"` + + // FixedDelayDuration is the fixed delay duration + FixedDelayDuration types.Duration `json:"fixedDelay"` + + // SignalThreshold is the signal threshold to trigger the delay hedge + SignalThreshold float64 `json:"signalThreshold"` + + // DynamicDelayScale is the dynamic delay scale + DynamicDelayScale *bbgo.SlideRule `json:"dynamicDelayScale,omitempty"` +} + type Strategy struct { Environment *bbgo.Environment @@ -135,8 +157,8 @@ type Strategy struct { EnableSignalMargin bool `json:"enableSignalMargin"` SignalConfigList []SignalConfig `json:"signals"` - SignalReverseSideMargin *SignalMargin `json:"signalReverseSideMargin,omitempty"` - SignalTrendSideMargin *SignalMargin `json:"signalTrendSideMargin,omitempty"` + SignalReverseSideMargin *SignalMargin `json:"signalReverseSideMargin,omitempty"` + SignalTrendSideMarginDiscount *SignalMargin `json:"signalTrendSideMarginDiscount,omitempty"` // Margin is the default margin for the quote Margin fixedpoint.Value `json:"margin"` @@ -156,6 +178,8 @@ type Strategy struct { MaxDelayHedgeDuration types.Duration `json:"maxHedgeDelayDuration"` DelayHedgeSignalThreshold float64 `json:"delayHedgeSignalThreshold"` + DelayedHedge *DelayedHedge `json:"delayedHedge,omitempty"` + EnableBollBandMargin bool `json:"enableBollBandMargin"` BollBandInterval types.Interval `json:"bollBandInterval"` BollBandMargin fixedpoint.Value `json:"bollBandMargin"` @@ -344,8 +368,8 @@ func (s *Strategy) Initialize() error { } } - if s.SignalTrendSideMargin != nil && s.SignalTrendSideMargin.Scale != nil { - scale, err := s.SignalTrendSideMargin.Scale.Scale() + if s.SignalTrendSideMarginDiscount != nil && s.SignalTrendSideMarginDiscount.Scale != nil { + scale, err := s.SignalTrendSideMarginDiscount.Scale.Scale() if err != nil { return err } @@ -355,9 +379,27 @@ func (s *Strategy) Initialize() error { } } + if s.DelayedHedge != nil && s.DelayedHedge.DynamicDelayScale != nil { + if scale, _ := s.DelayedHedge.DynamicDelayScale.Scale(); scale != nil { + if err := scale.Solve(); err != nil { + return err + } + } + } + + s.PrintConfig(os.Stdout, true, false) return nil } +func (s *Strategy) PrintConfig(f io.Writer, pretty bool, withColor ...bool) { + var tableStyle *table.Style + if pretty { + tableStyle = style.NewDefaultTableStyle() + } + + dynamic.PrintConfig(s, f, tableStyle, len(withColor) > 0 && withColor[0], dynamic.DefaultWhiteList()...) +} + // 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 @@ -419,13 +461,13 @@ func (s *Strategy) applySignalMargin(ctx context.Context, quote *Quote) error { var trendSideMarginDiscount, reverseSideMargin float64 var trendSideMarginDiscountFp, reverseSideMarginFp fixedpoint.Value - if s.SignalTrendSideMargin != nil && s.SignalTrendSideMargin.Enabled { - trendSideMarginScale, err := s.SignalTrendSideMargin.Scale.Scale() + if s.SignalTrendSideMarginDiscount != nil && s.SignalTrendSideMarginDiscount.Enabled { + trendSideMarginScale, err := s.SignalTrendSideMarginDiscount.Scale.Scale() if err != nil { return err } - if signalAbs > s.SignalTrendSideMargin.Threshold { + if signalAbs > s.SignalTrendSideMarginDiscount.Threshold { // trendSideMarginDiscount is the discount for the trend side margin trendSideMarginDiscount = trendSideMarginScale.Call(math.Abs(signal)) trendSideMarginDiscountFp = fixedpoint.NewFromFloat(trendSideMarginDiscount) @@ -1256,14 +1298,16 @@ func AdjustHedgeQuantityWithAvailableBalance( return market.TruncateQuantity(quantity) } -func (s *Strategy) canDelayHedge(side types.SideType, pos fixedpoint.Value) bool { - if !s.EnableDelayHedge { +// canDelayHedge returns true if the hedge can be delayed +func (s *Strategy) canDelayHedge(hedgeSide types.SideType, pos fixedpoint.Value) bool { + if s.DelayedHedge == nil || !s.DelayedHedge.Enabled { return false } signal := s.lastAggregatedSignal.Get() - if math.Abs(signal) < s.DelayHedgeSignalThreshold { + signalAbs := math.Abs(signal) + if signalAbs < s.DelayedHedge.SignalThreshold { return false } @@ -1273,8 +1317,21 @@ func (s *Strategy) canDelayHedge(side types.SideType, pos fixedpoint.Value) bool return false } - if (signal > 0 && side == types.SideTypeSell) || (signal < 0 && side == types.SideTypeBuy) { - if period < s.MaxDelayHedgeDuration.Duration() { + var maxDelay = s.DelayedHedge.MaxDelayDuration.Duration() + var delay = s.DelayedHedge.FixedDelayDuration.Duration() + + if s.DelayedHedge.DynamicDelayScale != nil { + if scale, _ := s.DelayedHedge.DynamicDelayScale.Scale(); scale != nil { + delay = time.Duration(scale.Call(signalAbs)) * time.Millisecond + } + } + + if delay > maxDelay { + delay = maxDelay + } + + if (signal > 0 && hedgeSide == types.SideTypeSell) || (signal < 0 && hedgeSide == types.SideTypeBuy) { + if period < delay { s.logger.Infof("delay hedge enabled, signal %f is strong enough, waiting for the next tick to hedge %s quantity (max period %s)", signal, pos, s.MaxDelayHedgeDuration.Duration().String()) delayHedgeCounterMetrics.With(s.metricsLabels).Inc() @@ -1500,8 +1557,8 @@ func (s *Strategy) Defaults() error { } } - if s.SignalTrendSideMargin.Scale == nil { - s.SignalTrendSideMargin.Scale = &bbgo.SlideRule{ + if s.SignalTrendSideMarginDiscount.Scale == nil { + s.SignalTrendSideMarginDiscount.Scale = &bbgo.SlideRule{ ExpScale: &bbgo.ExponentialScale{ Domain: [2]float64{0, 2.0}, Range: [2]float64{0.00010, 0.00500}, @@ -1509,8 +1566,19 @@ func (s *Strategy) Defaults() error { } } - if s.SignalTrendSideMargin.Threshold == 0.0 { - s.SignalTrendSideMargin.Threshold = 1.0 + if s.SignalTrendSideMarginDiscount.Threshold == 0.0 { + s.SignalTrendSideMarginDiscount.Threshold = 1.0 + } + } + + if s.DelayedHedge != nil { + // default value protection for delayed hedge + if s.DelayedHedge.MaxDelayDuration == 0 { + s.DelayedHedge.MaxDelayDuration = types.Duration(3 * time.Second) + } + + if s.DelayedHedge.SignalThreshold == 0.0 { + s.DelayedHedge.SignalThreshold = 0.5 } } @@ -1869,7 +1937,7 @@ func (s *Strategy) CrossRun( return errors.New("signalReverseSideMarginScale can not be nil when signal margin is enabled") } - if s.SignalTrendSideMargin == nil || s.SignalTrendSideMargin.Scale == nil { + if s.SignalTrendSideMarginDiscount == nil || s.SignalTrendSideMarginDiscount.Scale == nil { return errors.New("signalTrendSideMarginScale can not be nil when signal margin is enabled") } From 7da83e9e6c3edfd18f569420dbb7e948af8f1abe Mon Sep 17 00:00:00 2001 From: c9s Date: Wed, 20 Nov 2024 18:12:15 +0800 Subject: [PATCH 7/9] fix twap tests --- pkg/twap/v2/stream_executor_test.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pkg/twap/v2/stream_executor_test.go b/pkg/twap/v2/stream_executor_test.go index fe3f05cdb..e53658efc 100644 --- a/pkg/twap/v2/stream_executor_test.go +++ b/pkg/twap/v2/stream_executor_test.go @@ -81,6 +81,9 @@ func bindMockMarketDataStream(mockStream *mocks.MockStream, stream *types.Standa mockStream.EXPECT().OnConnect(Catch(func(x any) { stream.OnConnect(x.(func())) })).AnyTimes() + mockStream.EXPECT().OnDisconnect(Catch(func(x any) { + stream.OnDisconnect(x.(func())) + })).AnyTimes() } func bindMockUserDataStream(mockStream *mocks.MockStream, stream *types.StandardStream) { From f74ea33e539ad9191bd561e766439928b4e80ee6 Mon Sep 17 00:00:00 2001 From: c9s Date: Wed, 20 Nov 2024 18:12:22 +0800 Subject: [PATCH 8/9] fix buffer test --- pkg/depth/buffer_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/depth/buffer_test.go b/pkg/depth/buffer_test.go index fe8023641..8bb05cd56 100644 --- a/pkg/depth/buffer_test.go +++ b/pkg/depth/buffer_test.go @@ -64,7 +64,7 @@ func TestDepthBuffer_CorruptedUpdateAtTheBeginning(t *testing.T) { {Price: itov(99), Volume: itov(1)}, }, }, snapshotFinalID, nil - }) + }, time.Millisecond*5) resetC := make(chan struct{}, 1) @@ -104,7 +104,7 @@ func TestDepthBuffer_ConcurrentRun(t *testing.T) { {Price: itov(99), Volume: itov(1)}, }, }, snapshotFinalID, nil - }) + }, time.Millisecond*5) readyCnt := 0 resetCnt := 0 From af71b5e4a5aaf4bec98a851450950c8f7e8f94d6 Mon Sep 17 00:00:00 2001 From: c9s Date: Thu, 21 Nov 2024 12:17:51 +0800 Subject: [PATCH 9/9] xmaker: print config into bytes --- pkg/strategy/xmaker/strategy.go | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/pkg/strategy/xmaker/strategy.go b/pkg/strategy/xmaker/strategy.go index e03650bf4..86d9e0e6c 100644 --- a/pkg/strategy/xmaker/strategy.go +++ b/pkg/strategy/xmaker/strategy.go @@ -1,11 +1,11 @@ package xmaker import ( + "bytes" "context" "fmt" "io" "math" - "os" "sync" "time" @@ -387,7 +387,6 @@ func (s *Strategy) Initialize() error { } } - s.PrintConfig(os.Stdout, true, false) return nil } @@ -1751,6 +1750,10 @@ func (s *Strategy) CrossRun( ) error { instanceID := s.InstanceID() + configWriter := bytes.NewBuffer(nil) + s.PrintConfig(configWriter, true, false) + s.logger.Infof("config: %s", configWriter.String()) + // configure sessions sourceSession, ok := sessions[s.SourceExchange] if !ok {