Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions pkg/strategy/xmaker/metrics.go
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,22 @@ var splitHedgeBidHigherThanAskCounter = prometheus.NewCounterVec(
[]string{"strategy_type", "strategy_id", "exchange", "symbol"},
)

var splitHedgeWeightedBidPriceMetrics = prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Name: "xmaker_split_hedge_weighted_bid_price",
Help: "The weighted bid price calculated by split hedge",
},
[]string{"strategy_type", "strategy_id", "exchange", "symbol"},
)

var splitHedgeWeightedAskPriceMetrics = prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Name: "xmaker_split_hedge_weighted_ask_price",
Help: "The weighted ask price calculated by split hedge",
},
[]string{"strategy_type", "strategy_id", "exchange", "symbol"},
)

func init() {
prometheus.MustRegister(
openOrderBidExposureInUsdMetrics,
Expand All @@ -175,5 +191,7 @@ func init() {
directionMean,
directionAlignedWeight,
splitHedgeBidHigherThanAskCounter,
splitHedgeWeightedBidPriceMetrics,
splitHedgeWeightedAskPriceMetrics,
)
}
125 changes: 125 additions & 0 deletions pkg/strategy/xmaker/splithedge.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,11 +74,19 @@ type SplitHedgeProportionAlgo struct {
ProportionMarkets []*SplitHedgeProportionMarket `json:"markets"`
}

type BestPriceHedge struct {
Enabled bool `json:"enabled"`

BelowAmount fixedpoint.Value `json:"belowAmount"`
}

type SplitHedge struct {
Enabled bool `json:"enabled"`

Algo SplitHedgeAlgo `json:"algo"`

BestPriceHedge *BestPriceHedge `json:"bestPriceHedge"`

ProportionAlgo *SplitHedgeProportionAlgo `json:"proportionAlgo"`

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

// config validation
if h.Enabled {
if h.BestPriceHedge != nil && h.BestPriceHedge.Enabled {
if h.BestPriceHedge.BelowAmount.Sign() <= 0 {
return fmt.Errorf("splithedge: bestPriceHedge.belowAmount must be positive")
}
}

switch h.Algo {
case SplitHedgeAlgoProportion:
if h.ProportionAlgo == nil || len(h.ProportionAlgo.ProportionMarkets) == 0 {
Expand Down Expand Up @@ -296,12 +310,123 @@ func (h *SplitHedge) hedgeWithProportionAlgo(
return nil
}

func (h *SplitHedge) hedgeWithBestPriceAlgo(
ctx context.Context,
uncoveredPosition, hedgeDelta fixedpoint.Value,
) (bool, error) {
if h.BestPriceHedge == nil || !h.BestPriceHedge.Enabled {
return false, nil
}

orderSide := deltaToSide(hedgeDelta)
quantity := hedgeDelta.Abs()

// get the balance weighted price to calculate the current position value
bid, ask, ok := h.GetBalanceWeightedQuotePrice()
if !ok {
return false, fmt.Errorf("splitHedge: failed to get weighted quote price")
}

price := sideTakerPrice(bid, ask, orderSide)
if price.IsZero() {
return false, fmt.Errorf("splitHedge: zero price from weighted quote price")
}

positionValue := quantity.Mul(price)
if positionValue.Compare(h.BestPriceHedge.BelowAmount) > 0 {
h.logger.Infof("splitHedge: position value %s exceeds belowAmount %s, fallback to split hedge", positionValue.String(), h.BestPriceHedge.BelowAmount.String())
return false, nil
}

var bestPrice fixedpoint.Value
var bestMarket *HedgeMarket

for _, mkt := range h.hedgeMarketInstances {
mktBid, mktAsk := mkt.GetQuotePrice()
mktPrice := sideTakerPrice(mktBid, mktAsk, orderSide)

if mktPrice.IsZero() {
continue
}

if bestMarket == nil {
bestPrice = mktPrice
bestMarket = mkt
continue
}

if orderSide == types.SideTypeBuy {
if mktPrice.Compare(bestPrice) < 0 {
bestPrice = mktPrice
bestMarket = mkt
}
} else {
if mktPrice.Compare(bestPrice) > 0 {
bestPrice = mktPrice
bestMarket = mkt
}
}
}

if bestMarket == nil {
return false, fmt.Errorf("splitHedge: no best market found")
}

canHedge, maxQuantity, err := bestMarket.canHedge(ctx, uncoveredPosition)
if err != nil {
return false, fmt.Errorf("splitHedge: check canHedge failed: %w", err)
}

if !canHedge {
h.logger.Infof("splitHedge: best market %s cannot hedge now", bestMarket.InstanceID())
return false, nil
}

hedgeQuantity := fixedpoint.Min(quantity, maxQuantity)
hedgeQuantity = bestMarket.market.TruncateQuantity(hedgeQuantity)
if hedgeQuantity.IsZero() {
h.logger.Infof("splitHedge: best market %s truncated quantity is zero", bestMarket.InstanceID())
return false, nil
}

if bestMarket.market.IsDustQuantity(hedgeQuantity, bestPrice) {
h.logger.Infof("splitHedge: best market %s dust quantity", bestMarket.InstanceID())
return false, nil
}

h.logger.Infof("splitHedge: best price hedging %s %s on market %s at price %s",
orderSide, hedgeQuantity.String(), bestMarket.InstanceID(), bestPrice.String())

coverDelta := quantityToDelta(hedgeQuantity, orderSide).Neg()
h.strategy.positionExposure.Cover(coverDelta)

select {
case <-ctx.Done():
h.strategy.positionExposure.Uncover(coverDelta)
return true, ctx.Err()
case bestMarket.positionDeltaC <- coverDelta:
}

return true, nil
}

func (h *SplitHedge) Hedge(
ctx context.Context,
uncoveredPosition, hedgeDelta fixedpoint.Value,
) error {
h.logger.Infof("splitHedge: split hedging with delta: %f", hedgeDelta.Float64())

if h.BestPriceHedge != nil && h.BestPriceHedge.Enabled {
handled, err := h.hedgeWithBestPriceAlgo(ctx, uncoveredPosition, hedgeDelta)
if err != nil {
return err
}

if handled {
return nil
}
}

switch h.Algo {
case SplitHedgeAlgoProportion:
return h.hedgeWithProportionAlgo(ctx, uncoveredPosition, hedgeDelta)
Expand Down
7 changes: 7 additions & 0 deletions pkg/strategy/xmaker/strategy.go
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,8 @@ type Strategy struct {
alignedWeightMetrics prometheus.Gauge

splitHedgeBidHigherThanAskCounter prometheus.Counter
splitHedgeWeightedBidPrice prometheus.Gauge
splitHedgeWeightedAskPrice prometheus.Gauge

simpleHedgeMode bool

Expand Down Expand Up @@ -461,6 +463,8 @@ func (s *Strategy) Initialize() error {
s.directionMeanMetrics = directionMean.With(s.metricsLabels)
s.alignedWeightMetrics = directionAlignedWeight.With(s.metricsLabels)
s.splitHedgeBidHigherThanAskCounter = splitHedgeBidHigherThanAskCounter.With(s.metricsLabels)
s.splitHedgeWeightedBidPrice = splitHedgeWeightedBidPriceMetrics.With(s.metricsLabels)
s.splitHedgeWeightedAskPrice = splitHedgeWeightedAskPriceMetrics.With(s.metricsLabels)

if s.SignalReverseSideMargin != nil && s.SignalReverseSideMargin.Scale != nil {
scale, err := s.SignalReverseSideMargin.Scale.Scale()
Expand Down Expand Up @@ -1017,6 +1021,9 @@ func (s *Strategy) updateQuote(ctx context.Context) error {
hasPrice := false
bestBidPrice, bestAskPrice, hasPrice = s.SplitHedge.GetBalanceWeightedQuotePrice()
if hasPrice && !bestBidPrice.IsZero() && !bestAskPrice.IsZero() {
s.splitHedgeWeightedBidPrice.Set(bestBidPrice.Float64())
s.splitHedgeWeightedAskPrice.Set(bestAskPrice.Float64())

if bestBidPrice.Compare(bestAskPrice) > 0 {
s.splitHedgeBidHigherThanAskCounter.Inc()
s.logger.Warnf("split hedge bid price %s is higher than ask price %s, adjust bid price by tick size %s",
Expand Down
Loading