Skip to content

Commit 20221cf

Browse files
authored
Merge pull request #2602 from c9s/dboy/xfundingv2/exit-annualized-rate
FEATURE: [xfundingv2] add minExitRate
2 parents 19556a4 + 947280f commit 20221cf

1 file changed

Lines changed: 43 additions & 17 deletions

File tree

pkg/strategy/xfundingv2/strategy.go

Lines changed: 43 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -71,11 +71,15 @@ type Strategy struct {
7171

7272
// CandidateSymbols is the list of symbols to consider for selection
7373
// IMPORTANT: xfundingv2 is now assuming trading on U-major pairs
74-
CandidateSymbols []string `json:"candidateSymbols"`
75-
OpenPositionInterval types.Duration `json:"openPositionInterval"`
76-
TransitRoundInterval types.Duration `json:"transitRoundInterval"`
77-
RoundRebalanceInterval types.Duration `json:"roundRebalanceInterval"`
78-
MaxClosingLossRatio fixedpoint.Value `json:"maxClosingLossRatio"`
74+
CandidateSymbols []string `json:"candidateSymbols"`
75+
OpenPositionInterval types.Duration `json:"openPositionInterval"`
76+
TransitRoundInterval types.Duration `json:"transitRoundInterval"`
77+
RoundRebalanceInterval types.Duration `json:"roundRebalanceInterval"`
78+
79+
// round closing conditions
80+
// TODO: move all the closing conditions into a separate struct
81+
MaxClosingLossRatio fixedpoint.Value `json:"maxClosingLossRatio"`
82+
MinExitRate fixedpoint.Value `json:"minExitRate"`
7983

8084
// TickSymbol is the symbol used for ticking the strategy, default to the first candidate symbol
8185
TickSymbol string `json:"tickSymbol"`
@@ -206,6 +210,15 @@ func (s *Strategy) Defaults() error {
206210
}
207211
s.MarketSelectionConfig.Defaults()
208212

213+
if s.MinExitRate.IsZero() {
214+
// default to 4% (ref: US short-term treasury rate)
215+
s.MinExitRate = fixedpoint.NewFromFloat(0.04) // 4%
216+
}
217+
s.MinExitRate = fixedpoint.Min(
218+
s.MinExitRate,
219+
s.MarketSelectionConfig.MinAnnualizedRate,
220+
)
221+
209222
if s.TradeBalanceRatio.IsZero() {
210223
s.TradeBalanceRatio = fixedpoint.NewFromFloat(0.8)
211224
}
@@ -792,7 +805,10 @@ func (s *Strategy) CrossRun(
792805
round.SpotSymbol(),
793806
round.FuturesSymbol(),
794807
)
795-
bbgo.Notify("⚠️ Round is set to closing state on startup", round.NewNotification(spotPrice, futuresPrice))
808+
bbgo.Notify("⚠️ Round is set to closing state on startup: %s",
809+
round.String(),
810+
round.NewNotification(spotPrice, futuresPrice),
811+
)
796812
}
797813
s.PendingRounds = make(map[string]*PendingRound)
798814
s.mu.Unlock()
@@ -1121,18 +1137,20 @@ func (s *Strategy) transitRound(ctx context.Context, round *ArbitrageRound, curr
11211137

11221138
func (s *Strategy) transitOpeningOrReadyRoundToClosing(round *ArbitrageRound, index *types.PremiumIndex, currentTime time.Time) {
11231139
// if the current funding rate is still favorable, stay in current state, otherwise transit to closing
1140+
lastAnnualizedFundingRate := AnnualizedRate(index.LastFundingRate, round.syncState.FundingIntervalHours)
1141+
spotPrice, futuresPrice, _ := s.getLastPrices(
1142+
round.SpotSymbol(),
1143+
round.FuturesSymbol(),
1144+
)
11241145
if round.TriggeredFundingRate().Sign()*index.LastFundingRate.Sign() <= 0 {
11251146
// the funding rate has flipped
1126-
spotPrice, futuresPrice, _ := s.getLastPrices(
1127-
round.SpotSymbol(),
1128-
round.FuturesSymbol(),
1129-
)
11301147
rateDiffAbs := index.LastFundingRate.Sub(round.TriggeredFundingRate()).Abs()
11311148
if rateDiffAbs.Compare(s.CriticalErrorConfig.MaxFundingRateFlip) > 0 {
1132-
bbgo.Notify("🚨 Round funding rate flip is too large: %s -> %s (threshold %s), closing round",
1149+
bbgo.Notify("🚨 Round funding rate flip is too large: %s -> %s (threshold %s), closing: %s",
11331150
round.TriggeredFundingRate(),
11341151
index.LastFundingRate,
11351152
s.CriticalErrorConfig.MaxFundingRateFlip,
1153+
round.String(),
11361154
round.NewCriticalNotification(spotPrice, futuresPrice),
11371155
)
11381156
round.SetClosing(currentTime, s.TWAPWorkerConfig.ClosingDuration)
@@ -1155,9 +1173,10 @@ func (s *Strategy) transitOpeningOrReadyRoundToClosing(round *ArbitrageRound, in
11551173

11561174
// the round is beyond the max holding time, transit to closing
11571175
if currentTime.Sub(round.StartedAt()) >= s.MarketSelectionConfig.MaxHoldingDuration.Duration() {
1158-
s.logger.Infof(
1159-
"[transitOpeningOrReadyRound %s] max holding hours reached, transit state %s -> closing, current funding rate %s: %s",
1160-
currentTime.Format(time.RFC3339), round.State(), index.LastFundingRate, round,
1176+
bbgo.Notify(
1177+
"⚠️ Max holding hours reached, transit state %s -> closing, current funding rate %s: %s",
1178+
round.State(), index.LastFundingRate, round.String(),
1179+
round.NewNotification(spotPrice, futuresPrice),
11611180
)
11621181
round.SetClosing(currentTime, s.TWAPWorkerConfig.ClosingDuration)
11631182
return
@@ -1187,12 +1206,19 @@ func (s *Strategy) transitOpeningOrReadyRoundToClosing(round *ArbitrageRound, in
11871206
"[transitOpeningOrReadyRound %s] unrealized total PnL: %s, next funding income: %s, futures position notional: %s, max closing loss ratio: %s",
11881207
unrealizedTotalPnL, nextFundingIncome, futuresPositionNotional, s.MaxClosingLossRatio,
11891208
)
1190-
s.logger.Infof(
1191-
"[transitOpeningOrReadyRound %s] transit state %s -> closing, current funding rate %s: %s",
1192-
currentTime.Format(time.RFC3339), round.State(), index.LastFundingRate, round)
1209+
bbgo.Notify(
1210+
"⚠️ Unrealized total PnL too large (%s), transit state %s -> closing, current funding rate %s: %s",
1211+
unrealizedTotalPnL, round.State(), index.LastFundingRate, round.String(),
1212+
round.NewNotification(spotPrice, futuresPrice),
1213+
)
11931214
round.SetClosing(currentTime, s.TWAPWorkerConfig.ClosingDuration)
11941215
return
11951216
}
1217+
} else if lastAnnualizedFundingRate.Abs().Compare(s.MinExitRate) <= 0 {
1218+
bbgo.Notify("⚠️ Last funding rate %s(annualized %s) is below the min exit rate %s, transit state %s -> closing: %s",
1219+
index.LastFundingRate, lastAnnualizedFundingRate, s.MinExitRate, round.State(), round.String(),
1220+
round.NewNotification(spotPrice, futuresPrice),
1221+
)
11961222
}
11971223

11981224
if s.allowLog(currentTime) {

0 commit comments

Comments
 (0)