@@ -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+
7783type 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+
299413func (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