From 77e185ffa7c49e07a9d03f2f816abc7051235f9d Mon Sep 17 00:00:00 2001 From: c9s Date: Mon, 26 Aug 2024 12:45:18 +0800 Subject: [PATCH 1/3] xmaker: add profit fixer --- pkg/strategy/xmaker/strategy.go | 55 ++++++++++++++++++++++++++++++--- 1 file changed, 50 insertions(+), 5 deletions(-) diff --git a/pkg/strategy/xmaker/strategy.go b/pkg/strategy/xmaker/strategy.go index 3de63260a..4449e519c 100644 --- a/pkg/strategy/xmaker/strategy.go +++ b/pkg/strategy/xmaker/strategy.go @@ -17,12 +17,16 @@ import ( indicatorv2 "github.com/c9s/bbgo/pkg/indicator/v2" "github.com/c9s/bbgo/pkg/pricesolver" "github.com/c9s/bbgo/pkg/risk/circuitbreaker" + "github.com/c9s/bbgo/pkg/strategy/common" "github.com/c9s/bbgo/pkg/types" "github.com/c9s/bbgo/pkg/util" ) var defaultMargin = fixedpoint.NewFromFloat(0.003) -var Two = fixedpoint.NewFromInt(2) +var two = fixedpoint.NewFromInt(2) + +var lastPriceModifier = fixedpoint.NewFromFloat(1.001) +var minGap = fixedpoint.NewFromFloat(1.02) const priceUpdateTimeout = 30 * time.Second @@ -89,6 +93,9 @@ type Strategy struct { // Pips is the pips of the layer prices Pips fixedpoint.Value `json:"pips"` + // ProfitFixerConfig is the profit fixer configuration + ProfitFixerConfig *common.ProfitFixerConfig `json:"profitFixer,omitempty"` + // -------------------------------- // private field @@ -215,7 +222,7 @@ func (s *Strategy) updateQuote(ctx context.Context, orderExecutionRouter bbgo.Or } // use mid-price for the last price - s.lastPrice = bestBid.Price.Add(bestAsk.Price).Div(Two) + s.lastPrice = bestBid.Price.Add(bestAsk.Price).Div(two) s.priceSolver.Update(s.Symbol, s.lastPrice) @@ -543,9 +550,6 @@ func (s *Strategy) updateQuote(ctx context.Context, orderExecutionRouter bbgo.Or _ = createdOrders } -var lastPriceModifier = fixedpoint.NewFromFloat(1.001) -var minGap = fixedpoint.NewFromFloat(1.02) - func (s *Strategy) Hedge(ctx context.Context, pos fixedpoint.Value) { side := types.SideTypeBuy if pos.IsZero() { @@ -852,6 +856,47 @@ func (s *Strategy) CrossRun( }) } + if s.ProfitFixerConfig != nil { + bbgo.Notify("Fixing %s profitStats and position...", s.Symbol) + + log.Infof("profitFixer is enabled, checking checkpoint: %+v", s.ProfitFixerConfig.TradesSince) + + if s.ProfitFixerConfig.TradesSince.Time().IsZero() { + return errors.New("tradesSince time can not be zero") + } + + makerMarket, _ := makerSession.Market(s.Symbol) + position := types.NewPositionFromMarket(makerMarket) + profitStats := types.NewProfitStats(makerMarket) + + fixer := common.NewProfitFixer() + // fixer.ConverterManager = s.ConverterManager + + if ss, ok := makerSession.Exchange.(types.ExchangeTradeHistoryService); ok { + log.Infof("adding makerSession %s to profitFixer", makerSession.Name) + fixer.AddExchange(makerSession.Name, ss) + } + + if ss, ok := sourceSession.Exchange.(types.ExchangeTradeHistoryService); ok { + log.Infof("adding hedgeSession %s to profitFixer", sourceSession.Name) + fixer.AddExchange(sourceSession.Name, ss) + } + + if err2 := fixer.Fix(ctx, makerMarket.Symbol, + s.ProfitFixerConfig.TradesSince.Time(), + time.Now(), + profitStats, + position); err2 != nil { + return err2 + } + + bbgo.Notify("Fixed %s position", s.Symbol, position) + bbgo.Notify("Fixed %s profitStats", s.Symbol, profitStats) + + s.Position = position + s.ProfitStats.ProfitStats = profitStats + } + s.book = types.NewStreamBook(s.Symbol, s.sourceSession.ExchangeName) s.book.BindStream(s.sourceSession.MarketDataStream) From 321eb23514f8295194708e51f90cfe6eef95c7eb Mon Sep 17 00:00:00 2001 From: c9s Date: Mon, 26 Aug 2024 12:46:45 +0800 Subject: [PATCH 2/3] config: add profit fixer to the sample config --- config/xmaker.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/config/xmaker.yaml b/config/xmaker.yaml index dddf31166..340e5ad57 100644 --- a/config/xmaker.yaml +++ b/config/xmaker.yaml @@ -59,6 +59,9 @@ crossExchangeStrategies: # 0.1 pip is 0.01, here we use 10, so we will get 18000.00, 18001.00 and # 18002.00 pips: 10 + ## profitFixer is used for fixing the profit stats and the position + # profitFixer: + # tradesSince: "2024-08-01T15:00:00.000+08:00" circuitBreaker: maximumConsecutiveTotalLoss: 36.0 maximumConsecutiveLossTimes: 10 From 7fdb3f671f2930d1f9680952cdfd6f85d866f309 Mon Sep 17 00:00:00 2001 From: c9s Date: Mon, 26 Aug 2024 12:50:13 +0800 Subject: [PATCH 3/3] risk: add Enabled config to circuitbreaker --- pkg/risk/circuitbreaker/basic.go | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/pkg/risk/circuitbreaker/basic.go b/pkg/risk/circuitbreaker/basic.go index e18bfef52..5b5e8430a 100644 --- a/pkg/risk/circuitbreaker/basic.go +++ b/pkg/risk/circuitbreaker/basic.go @@ -75,6 +75,8 @@ func init() { } type BasicCircuitBreaker struct { + Enabled bool `json:"enabled"` + MaximumConsecutiveTotalLoss fixedpoint.Value `json:"maximumConsecutiveTotalLoss"` MaximumConsecutiveLossTimes int `json:"maximumConsecutiveLossTimes"` @@ -117,14 +119,17 @@ type BasicCircuitBreaker struct { func NewBasicCircuitBreaker(strategyID, strategyInstance string) *BasicCircuitBreaker { b := &BasicCircuitBreaker{ + Enabled: true, MaximumConsecutiveLossTimes: 8, MaximumHaltTimes: 3, MaximumHaltTimesExceededPanic: false, - HaltDuration: types.Duration(1 * time.Hour), - strategyID: strategyID, - strategyInstance: strategyInstance, - metricsLabels: prometheus.Labels{"strategy": strategyID, "strategyInstance": strategyInstance}, + + HaltDuration: types.Duration(1 * time.Hour), + strategyID: strategyID, + strategyInstance: strategyInstance, + metricsLabels: prometheus.Labels{"strategy": strategyID, "strategyInstance": strategyInstance}, } + b.updateMetrics() return b } @@ -182,7 +187,7 @@ func (b *BasicCircuitBreaker) RecordProfit(profit fixedpoint.Value, now time.Tim b.winRatio = float64(b.winTimes) / float64(b.lossTimes) } - b.updateMetrics() + defer b.updateMetrics() if b.MaximumConsecutiveLossTimes > 0 && b.consecutiveLossTimes >= b.MaximumConsecutiveLossTimes { b.halt(now, "exceeded MaximumConsecutiveLossTimes") @@ -224,6 +229,10 @@ func (b *BasicCircuitBreaker) reset() { } func (b *BasicCircuitBreaker) IsHalted(now time.Time) (string, bool) { + if !b.Enabled { + return "disabled", false + } + b.mu.Lock() defer b.mu.Unlock() @@ -251,6 +260,8 @@ func (b *BasicCircuitBreaker) halt(now time.Time, reason string) { haltCounterMetrics.With(labels).Set(float64(b.haltCounter)) haltMetrics.With(labels).Set(1.0) + defer b.updateMetrics() + if b.MaximumHaltTimesExceededPanic && b.haltCounter > b.MaximumHaltTimes { panic(fmt.Errorf("total %d halt times > maximumHaltTimesExceededPanic %d", b.haltCounter, b.MaximumHaltTimes)) }