Merge pull request #1712 from c9s/c9s/xmaker/add-profit-fixer

FEATURE: [xmaker] add profit fixer
This commit is contained in:
c9s 2024-08-26 13:32:41 +08:00 committed by GitHub
commit 866751cc3d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 69 additions and 10 deletions

View File

@ -59,6 +59,9 @@ crossExchangeStrategies:
# 0.1 pip is 0.01, here we use 10, so we will get 18000.00, 18001.00 and # 0.1 pip is 0.01, here we use 10, so we will get 18000.00, 18001.00 and
# 18002.00 # 18002.00
pips: 10 pips: 10
## profitFixer is used for fixing the profit stats and the position
# profitFixer:
# tradesSince: "2024-08-01T15:00:00.000+08:00"
circuitBreaker: circuitBreaker:
enabled: true enabled: true
maximumConsecutiveTotalLoss: 36.0 maximumConsecutiveTotalLoss: 36.0

View File

@ -75,6 +75,8 @@ func init() {
} }
type BasicCircuitBreaker struct { type BasicCircuitBreaker struct {
Enabled bool `json:"enabled"`
MaximumConsecutiveTotalLoss fixedpoint.Value `json:"maximumConsecutiveTotalLoss"` MaximumConsecutiveTotalLoss fixedpoint.Value `json:"maximumConsecutiveTotalLoss"`
MaximumConsecutiveLossTimes int `json:"maximumConsecutiveLossTimes"` MaximumConsecutiveLossTimes int `json:"maximumConsecutiveLossTimes"`
@ -117,14 +119,17 @@ type BasicCircuitBreaker struct {
func NewBasicCircuitBreaker(strategyID, strategyInstance string) *BasicCircuitBreaker { func NewBasicCircuitBreaker(strategyID, strategyInstance string) *BasicCircuitBreaker {
b := &BasicCircuitBreaker{ b := &BasicCircuitBreaker{
Enabled: true,
MaximumConsecutiveLossTimes: 8, MaximumConsecutiveLossTimes: 8,
MaximumHaltTimes: 3, MaximumHaltTimes: 3,
MaximumHaltTimesExceededPanic: false, MaximumHaltTimesExceededPanic: false,
HaltDuration: types.Duration(1 * time.Hour), HaltDuration: types.Duration(1 * time.Hour),
strategyID: strategyID, strategyID: strategyID,
strategyInstance: strategyInstance, strategyInstance: strategyInstance,
metricsLabels: prometheus.Labels{"strategy": strategyID, "strategyInstance": strategyInstance}, metricsLabels: prometheus.Labels{"strategy": strategyID, "strategyInstance": strategyInstance},
} }
b.updateMetrics() b.updateMetrics()
return b 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.winRatio = float64(b.winTimes) / float64(b.lossTimes)
} }
b.updateMetrics() defer b.updateMetrics()
if b.MaximumConsecutiveLossTimes > 0 && b.consecutiveLossTimes >= b.MaximumConsecutiveLossTimes { if b.MaximumConsecutiveLossTimes > 0 && b.consecutiveLossTimes >= b.MaximumConsecutiveLossTimes {
b.halt(now, "exceeded MaximumConsecutiveLossTimes") b.halt(now, "exceeded MaximumConsecutiveLossTimes")
@ -224,6 +229,10 @@ func (b *BasicCircuitBreaker) reset() {
} }
func (b *BasicCircuitBreaker) IsHalted(now time.Time) (string, bool) { func (b *BasicCircuitBreaker) IsHalted(now time.Time) (string, bool) {
if !b.Enabled {
return "disabled", false
}
b.mu.Lock() b.mu.Lock()
defer b.mu.Unlock() defer b.mu.Unlock()
@ -251,6 +260,8 @@ func (b *BasicCircuitBreaker) halt(now time.Time, reason string) {
haltCounterMetrics.With(labels).Set(float64(b.haltCounter)) haltCounterMetrics.With(labels).Set(float64(b.haltCounter))
haltMetrics.With(labels).Set(1.0) haltMetrics.With(labels).Set(1.0)
defer b.updateMetrics()
if b.MaximumHaltTimesExceededPanic && b.haltCounter > b.MaximumHaltTimes { if b.MaximumHaltTimesExceededPanic && b.haltCounter > b.MaximumHaltTimes {
panic(fmt.Errorf("total %d halt times > maximumHaltTimesExceededPanic %d", b.haltCounter, b.MaximumHaltTimes)) panic(fmt.Errorf("total %d halt times > maximumHaltTimesExceededPanic %d", b.haltCounter, b.MaximumHaltTimes))
} }

View File

@ -17,12 +17,16 @@ import (
indicatorv2 "github.com/c9s/bbgo/pkg/indicator/v2" indicatorv2 "github.com/c9s/bbgo/pkg/indicator/v2"
"github.com/c9s/bbgo/pkg/pricesolver" "github.com/c9s/bbgo/pkg/pricesolver"
"github.com/c9s/bbgo/pkg/risk/circuitbreaker" "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/types"
"github.com/c9s/bbgo/pkg/util" "github.com/c9s/bbgo/pkg/util"
) )
var defaultMargin = fixedpoint.NewFromFloat(0.003) 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 const priceUpdateTimeout = 30 * time.Second
@ -89,6 +93,9 @@ type Strategy struct {
// Pips is the pips of the layer prices // Pips is the pips of the layer prices
Pips fixedpoint.Value `json:"pips"` Pips fixedpoint.Value `json:"pips"`
// ProfitFixerConfig is the profit fixer configuration
ProfitFixerConfig *common.ProfitFixerConfig `json:"profitFixer,omitempty"`
// -------------------------------- // --------------------------------
// private field // private field
@ -215,7 +222,7 @@ func (s *Strategy) updateQuote(ctx context.Context, orderExecutionRouter bbgo.Or
} }
// use mid-price for the last price // 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) s.priceSolver.Update(s.Symbol, s.lastPrice)
@ -543,9 +550,6 @@ func (s *Strategy) updateQuote(ctx context.Context, orderExecutionRouter bbgo.Or
_ = createdOrders _ = createdOrders
} }
var lastPriceModifier = fixedpoint.NewFromFloat(1.001)
var minGap = fixedpoint.NewFromFloat(1.02)
func (s *Strategy) Hedge(ctx context.Context, pos fixedpoint.Value) { func (s *Strategy) Hedge(ctx context.Context, pos fixedpoint.Value) {
side := types.SideTypeBuy side := types.SideTypeBuy
if pos.IsZero() { 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 = types.NewStreamBook(s.Symbol, s.sourceSession.ExchangeName)
s.book.BindStream(s.sourceSession.MarketDataStream) s.book.BindStream(s.sourceSession.MarketDataStream)