Skip to content

Commit 8498ee8

Browse files
committed
xmaker: implement bestPriceHedge mech
1 parent b4fc5e4 commit 8498ee8

File tree

1 file changed

+125
-0
lines changed

1 file changed

+125
-0
lines changed

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)

0 commit comments

Comments
 (0)