Skip to content

Commit 6fd452b

Browse files
authored
Merge pull request #2392 from c9s/c9s/xmaker/split-hedge-bestprice
2 parents 81daf8d + 8498ee8 commit 6fd452b

File tree

3 files changed

+150
-0
lines changed

3 files changed

+150
-0
lines changed

pkg/strategy/xmaker/metrics.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,22 @@ var splitHedgeBidHigherThanAskCounter = prometheus.NewCounterVec(
151151
[]string{"strategy_type", "strategy_id", "exchange", "symbol"},
152152
)
153153

154+
var splitHedgeWeightedBidPriceMetrics = prometheus.NewGaugeVec(
155+
prometheus.GaugeOpts{
156+
Name: "xmaker_split_hedge_weighted_bid_price",
157+
Help: "The weighted bid price calculated by split hedge",
158+
},
159+
[]string{"strategy_type", "strategy_id", "exchange", "symbol"},
160+
)
161+
162+
var splitHedgeWeightedAskPriceMetrics = prometheus.NewGaugeVec(
163+
prometheus.GaugeOpts{
164+
Name: "xmaker_split_hedge_weighted_ask_price",
165+
Help: "The weighted ask price calculated by split hedge",
166+
},
167+
[]string{"strategy_type", "strategy_id", "exchange", "symbol"},
168+
)
169+
154170
func init() {
155171
prometheus.MustRegister(
156172
openOrderBidExposureInUsdMetrics,
@@ -175,5 +191,7 @@ func init() {
175191
directionMean,
176192
directionAlignedWeight,
177193
splitHedgeBidHigherThanAskCounter,
194+
splitHedgeWeightedBidPriceMetrics,
195+
splitHedgeWeightedAskPriceMetrics,
178196
)
179197
}

pkg/strategy/xmaker/splithedge.go

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,11 +74,19 @@ type SplitHedgeProportionAlgo struct {
7474
ProportionMarkets []*SplitHedgeProportionMarket `json:"markets"`
7575
}
7676

77+
type BestPriceHedge struct {
78+
Enabled bool `json:"enabled"`
79+
80+
BelowAmount fixedpoint.Value `json:"belowAmount"`
81+
}
82+
7783
type SplitHedge struct {
7884
Enabled bool `json:"enabled"`
7985

8086
Algo SplitHedgeAlgo `json:"algo"`
8187

88+
BestPriceHedge *BestPriceHedge `json:"bestPriceHedge"`
89+
8290
ProportionAlgo *SplitHedgeProportionAlgo `json:"proportionAlgo"`
8391

8492
// HedgeMarkets stores session name to hedge market config mapping.
@@ -114,6 +122,12 @@ func (h *SplitHedge) UnmarshalJSON(data []byte) error {
114122

115123
// config validation
116124
if h.Enabled {
125+
if h.BestPriceHedge != nil && h.BestPriceHedge.Enabled {
126+
if h.BestPriceHedge.BelowAmount.Sign() <= 0 {
127+
return fmt.Errorf("splithedge: bestPriceHedge.belowAmount must be positive")
128+
}
129+
}
130+
117131
switch h.Algo {
118132
case SplitHedgeAlgoProportion:
119133
if h.ProportionAlgo == nil || len(h.ProportionAlgo.ProportionMarkets) == 0 {
@@ -296,12 +310,123 @@ func (h *SplitHedge) hedgeWithProportionAlgo(
296310
return nil
297311
}
298312

313+
func (h *SplitHedge) hedgeWithBestPriceAlgo(
314+
ctx context.Context,
315+
uncoveredPosition, hedgeDelta fixedpoint.Value,
316+
) (bool, error) {
317+
if h.BestPriceHedge == nil || !h.BestPriceHedge.Enabled {
318+
return false, nil
319+
}
320+
321+
orderSide := deltaToSide(hedgeDelta)
322+
quantity := hedgeDelta.Abs()
323+
324+
// get the balance weighted price to calculate the current position value
325+
bid, ask, ok := h.GetBalanceWeightedQuotePrice()
326+
if !ok {
327+
return false, fmt.Errorf("splitHedge: failed to get weighted quote price")
328+
}
329+
330+
price := sideTakerPrice(bid, ask, orderSide)
331+
if price.IsZero() {
332+
return false, fmt.Errorf("splitHedge: zero price from weighted quote price")
333+
}
334+
335+
positionValue := quantity.Mul(price)
336+
if positionValue.Compare(h.BestPriceHedge.BelowAmount) > 0 {
337+
h.logger.Infof("splitHedge: position value %s exceeds belowAmount %s, fallback to split hedge", positionValue.String(), h.BestPriceHedge.BelowAmount.String())
338+
return false, nil
339+
}
340+
341+
var bestPrice fixedpoint.Value
342+
var bestMarket *HedgeMarket
343+
344+
for _, mkt := range h.hedgeMarketInstances {
345+
mktBid, mktAsk := mkt.GetQuotePrice()
346+
mktPrice := sideTakerPrice(mktBid, mktAsk, orderSide)
347+
348+
if mktPrice.IsZero() {
349+
continue
350+
}
351+
352+
if bestMarket == nil {
353+
bestPrice = mktPrice
354+
bestMarket = mkt
355+
continue
356+
}
357+
358+
if orderSide == types.SideTypeBuy {
359+
if mktPrice.Compare(bestPrice) < 0 {
360+
bestPrice = mktPrice
361+
bestMarket = mkt
362+
}
363+
} else {
364+
if mktPrice.Compare(bestPrice) > 0 {
365+
bestPrice = mktPrice
366+
bestMarket = mkt
367+
}
368+
}
369+
}
370+
371+
if bestMarket == nil {
372+
return false, fmt.Errorf("splitHedge: no best market found")
373+
}
374+
375+
canHedge, maxQuantity, err := bestMarket.canHedge(ctx, uncoveredPosition)
376+
if err != nil {
377+
return false, fmt.Errorf("splitHedge: check canHedge failed: %w", err)
378+
}
379+
380+
if !canHedge {
381+
h.logger.Infof("splitHedge: best market %s cannot hedge now", bestMarket.InstanceID())
382+
return false, nil
383+
}
384+
385+
hedgeQuantity := fixedpoint.Min(quantity, maxQuantity)
386+
hedgeQuantity = bestMarket.market.TruncateQuantity(hedgeQuantity)
387+
if hedgeQuantity.IsZero() {
388+
h.logger.Infof("splitHedge: best market %s truncated quantity is zero", bestMarket.InstanceID())
389+
return false, nil
390+
}
391+
392+
if bestMarket.market.IsDustQuantity(hedgeQuantity, bestPrice) {
393+
h.logger.Infof("splitHedge: best market %s dust quantity", bestMarket.InstanceID())
394+
return false, nil
395+
}
396+
397+
h.logger.Infof("splitHedge: best price hedging %s %s on market %s at price %s",
398+
orderSide, hedgeQuantity.String(), bestMarket.InstanceID(), bestPrice.String())
399+
400+
coverDelta := quantityToDelta(hedgeQuantity, orderSide).Neg()
401+
h.strategy.positionExposure.Cover(coverDelta)
402+
403+
select {
404+
case <-ctx.Done():
405+
h.strategy.positionExposure.Uncover(coverDelta)
406+
return true, ctx.Err()
407+
case bestMarket.positionDeltaC <- coverDelta:
408+
}
409+
410+
return true, nil
411+
}
412+
299413
func (h *SplitHedge) Hedge(
300414
ctx context.Context,
301415
uncoveredPosition, hedgeDelta fixedpoint.Value,
302416
) error {
303417
h.logger.Infof("splitHedge: split hedging with delta: %f", hedgeDelta.Float64())
304418

419+
if h.BestPriceHedge != nil && h.BestPriceHedge.Enabled {
420+
handled, err := h.hedgeWithBestPriceAlgo(ctx, uncoveredPosition, hedgeDelta)
421+
if err != nil {
422+
return err
423+
}
424+
425+
if handled {
426+
return nil
427+
}
428+
}
429+
305430
switch h.Algo {
306431
case SplitHedgeAlgoProportion:
307432
return h.hedgeWithProportionAlgo(ctx, uncoveredPosition, hedgeDelta)

pkg/strategy/xmaker/strategy.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -310,6 +310,8 @@ type Strategy struct {
310310
alignedWeightMetrics prometheus.Gauge
311311

312312
splitHedgeBidHigherThanAskCounter prometheus.Counter
313+
splitHedgeWeightedBidPrice prometheus.Gauge
314+
splitHedgeWeightedAskPrice prometheus.Gauge
313315

314316
simpleHedgeMode bool
315317

@@ -461,6 +463,8 @@ func (s *Strategy) Initialize() error {
461463
s.directionMeanMetrics = directionMean.With(s.metricsLabels)
462464
s.alignedWeightMetrics = directionAlignedWeight.With(s.metricsLabels)
463465
s.splitHedgeBidHigherThanAskCounter = splitHedgeBidHigherThanAskCounter.With(s.metricsLabels)
466+
s.splitHedgeWeightedBidPrice = splitHedgeWeightedBidPriceMetrics.With(s.metricsLabels)
467+
s.splitHedgeWeightedAskPrice = splitHedgeWeightedAskPriceMetrics.With(s.metricsLabels)
464468

465469
if s.SignalReverseSideMargin != nil && s.SignalReverseSideMargin.Scale != nil {
466470
scale, err := s.SignalReverseSideMargin.Scale.Scale()
@@ -1017,6 +1021,9 @@ func (s *Strategy) updateQuote(ctx context.Context) error {
10171021
hasPrice := false
10181022
bestBidPrice, bestAskPrice, hasPrice = s.SplitHedge.GetBalanceWeightedQuotePrice()
10191023
if hasPrice && !bestBidPrice.IsZero() && !bestAskPrice.IsZero() {
1024+
s.splitHedgeWeightedBidPrice.Set(bestBidPrice.Float64())
1025+
s.splitHedgeWeightedAskPrice.Set(bestAskPrice.Float64())
1026+
10201027
if bestBidPrice.Compare(bestAskPrice) > 0 {
10211028
s.splitHedgeBidHigherThanAskCounter.Inc()
10221029
s.logger.Warnf("split hedge bid price %s is higher than ask price %s, adjust bid price by tick size %s",

0 commit comments

Comments
 (0)