From 932307b23174b59bd0e5a75f9be17189a1ebd40f Mon Sep 17 00:00:00 2001 From: jayy04 <103467857+jayy04@users.noreply.github.com> Date: Thu, 11 Jan 2024 16:28:07 -0500 Subject: [PATCH] [CLOB-1054] add some telemetry for conditional order triggering (#958) * [CLOB-1054] add some telemetry for conditional order triggering * update metric name * Update protocol/x/clob/keeper/untriggered_conditional_orders.go Co-authored-by: Jonathan Fung <121899091+jonfung-dydx@users.noreply.github.com> * comments --------- Co-authored-by: Jonathan Fung <121899091+jonfung-dydx@users.noreply.github.com> --- protocol/lib/metrics/constants.go | 3 + protocol/lib/metrics/metric_keys.go | 6 +- .../x/clob/keeper/stateful_order_state.go | 9 - .../keeper/untriggered_conditional_orders.go | 163 ++++++++++++------ 4 files changed, 117 insertions(+), 64 deletions(-) diff --git a/protocol/lib/metrics/constants.go b/protocol/lib/metrics/constants.go index 86932182ac..f56966c755 100644 --- a/protocol/lib/metrics/constants.go +++ b/protocol/lib/metrics/constants.go @@ -31,6 +31,7 @@ const ( SampleRate = "sample_rate" SequenceNumber = "sequence_number" Success = "success" + Type = "type" Valid = "valid" ValidateBasic = "validate_basic" CheckTx = "check_tx" @@ -119,6 +120,8 @@ const ( Hydrate = "hydrate" IsLong = "is_long" IterateOverPendingMatches = "iterate_over_pending_matches" + MaxTradePrice = "max_trade_price" + MinTradePrice = "min_trade_price" MemClobReplayOperations = "memclob_replay_operations" MemClobPurgeInvalidState = "memclob_purge_invalid_state" NumConditionalOrderRemovals = "num_conditional_order_removals" diff --git a/protocol/lib/metrics/metric_keys.go b/protocol/lib/metrics/metric_keys.go index ab209fdd21..1ffaf22381 100644 --- a/protocol/lib/metrics/metric_keys.go +++ b/protocol/lib/metrics/metric_keys.go @@ -23,8 +23,10 @@ const ( SubaccountsNegativeTncSubaccountSeen = "negative_tnc_subaccount_seen" // Gauges - InsuranceFundBalance = "insurance_fund_balance" - ClobMev = "clob_mev" + InsuranceFundBalance = "insurance_fund_balance" + ClobMev = "clob_mev" + ClobConditionalOrderTriggerPrice = "clob_conditional_order_trigger_price" + ClobConditionalOrderTriggered = "clob_conditional_order_triggered" // Samples ClobDeleverageSubaccountTotalQuoteQuantumsDistribution = "clob_deleverage_subaccount_total_quote_quantums_distribution" diff --git a/protocol/x/clob/keeper/stateful_order_state.go b/protocol/x/clob/keeper/stateful_order_state.go index 996710a169..6afb569715 100644 --- a/protocol/x/clob/keeper/stateful_order_state.go +++ b/protocol/x/clob/keeper/stateful_order_state.go @@ -273,15 +273,6 @@ func (k Keeper) MustTriggerConditionalOrder( // Delete the `StatefulOrderPlacement` from Untriggered state store/memstore. untriggeredConditionalOrderStore.Delete(orderKey) untriggeredConditionalOrderMemStore.Delete(orderKey) - - telemetry.IncrCounterWithLabels( - []string{types.ModuleName, metrics.ConditionalOrderTriggered, metrics.Count}, - 1, - append( - orderId.GetOrderIdLabels(), - metrics.GetLabelForIntValue(metrics.ClobPairId, int(orderId.GetClobPairId())), - ), - ) } // MustAddOrderToStatefulOrdersTimeSlice adds a new `OrderId` to an existing time slice, or creates a new time slice diff --git a/protocol/x/clob/keeper/untriggered_conditional_orders.go b/protocol/x/clob/keeper/untriggered_conditional_orders.go index 80ffdbbf16..cfa31506a9 100644 --- a/protocol/x/clob/keeper/untriggered_conditional_orders.go +++ b/protocol/x/clob/keeper/untriggered_conditional_orders.go @@ -8,7 +8,9 @@ import ( indexerevents "github.com/dydxprotocol/v4-chain/protocol/indexer/events" "github.com/dydxprotocol/v4-chain/protocol/indexer/indexer_manager" "github.com/dydxprotocol/v4-chain/protocol/lib" + "github.com/dydxprotocol/v4-chain/protocol/lib/metrics" "github.com/dydxprotocol/v4-chain/protocol/x/clob/types" + gometrics "github.com/hashicorp/go-metrics" ) // UntriggeredConditionalOrders is an in-memory struct stored on the clob Keeper. @@ -267,16 +269,16 @@ func (untriggeredOrders *UntriggeredConditionalOrders) PollTriggeredConditionalO // Function returns a sorted list of conditional order ids that were triggered, intended to be written // to `ProcessProposerMatchesEvents.ConditionalOrderIdsTriggeredInLastBlock`. // This function is called in EndBlocker. -func (k Keeper) MaybeTriggerConditionalOrders(ctx sdk.Context) (triggeredConditionalOrderIds []types.OrderId) { - triggeredConditionalOrderIds = make([]types.OrderId, 0) +func (k Keeper) MaybeTriggerConditionalOrders(ctx sdk.Context) (allTriggeredOrderIds []types.OrderId) { // Sort the keys for the untriggered conditional orders struct. We need to trigger // the conditional orders in an ordered way to have deterministic state writes. sortedKeys := lib.GetSortedKeys[types.SortedClobPairId](k.UntriggeredConditionalOrders) + allTriggeredOrderIds = make([]types.OrderId, 0) // For all clob pair ids in UntriggeredConditionalOrders, fetch the updated // oracle price and poll out triggered conditional orders. for _, clobPairId := range sortedKeys { - untriggeredConditionalOrders := k.UntriggeredConditionalOrders[clobPairId] + untriggered := k.UntriggeredConditionalOrders[clobPairId] clobPair, found := k.GetClobPair(ctx, clobPairId) if !found { panic( @@ -288,67 +290,61 @@ func (k Keeper) MaybeTriggerConditionalOrders(ctx sdk.Context) (triggeredConditi } // Trigger conditional orders using the oracle price. - currentOraclePriceSubticksRat := k.GetOraclePriceSubticksRat(ctx, clobPair) - triggeredOrderIds := untriggeredConditionalOrders.PollTriggeredConditionalOrders( - currentOraclePriceSubticksRat, - ) - triggeredConditionalOrderIds = append(triggeredConditionalOrderIds, triggeredOrderIds...) + perpetualId := clobPair.MustGetPerpetualId() + oraclePrice := k.GetOraclePriceSubticksRat(ctx, clobPair) + triggered := k.TriggerOrdersWithPrice(ctx, untriggered, oraclePrice, perpetualId, metrics.OraclePrice) + allTriggeredOrderIds = append(allTriggeredOrderIds, triggered...) // Trigger conditional orders using the last traded price. - perpetualId := clobPair.MustGetPerpetualId() - minTradePriceSubticks, maxTradePriceSubticks, found := k.GetTradePricesForPerpetual(ctx, perpetualId) - if found { - // Get the perpetual. - perpetual, err := k.perpetualsKeeper.GetPerpetual(ctx, perpetualId) - if err != nil { - panic( - fmt.Errorf( - "EndBlocker: untriggeredConditionalOrders failed to find perpetualId %+v", - perpetualId, - ), - ) - } + clampedMinTradePrice, + clampedMaxTradePrice, + found := k.getClampedTradePricesForTriggering( + ctx, + perpetualId, + oraclePrice, + ) - // Get the market param. - marketParam, exists := k.pricesKeeper.GetMarketParam(ctx, perpetual.Params.MarketId) - if !exists { - panic( - fmt.Errorf( - "EndBlocker: untriggeredConditionalOrders failed to find marketParam %+v", - perpetual.Params.MarketId, - ), - ) - } + if found { + triggered = k.TriggerOrdersWithPrice(ctx, untriggered, clampedMinTradePrice, perpetualId, metrics.MinTradePrice) + allTriggeredOrderIds = append(allTriggeredOrderIds, triggered...) - // Calculate the max allowed range. - maxAllowedRange := lib.BigRatMulPpm(currentOraclePriceSubticksRat, marketParam.MinPriceChangePpm) - maxAllowedRange.Mul(maxAllowedRange, new(big.Rat).SetUint64(types.ConditionalOrderTriggerMultiplier)) - - upperBound := new(big.Rat).Add(currentOraclePriceSubticksRat, maxAllowedRange) - lowerBound := new(big.Rat).Sub(currentOraclePriceSubticksRat, maxAllowedRange) - - for _, price := range []types.Subticks{minTradePriceSubticks, maxTradePriceSubticks} { - // Clamp the min and max trade prices to the upper and lower bounds. - clampedTradePrice := lib.BigRatClamp( - new(big.Rat).SetUint64(price.ToUint64()), - lowerBound, - upperBound, - ) - triggeredOrderIds := untriggeredConditionalOrders.PollTriggeredConditionalOrders(clampedTradePrice) - triggeredConditionalOrderIds = append(triggeredConditionalOrderIds, triggeredOrderIds...) - } + triggered = k.TriggerOrdersWithPrice(ctx, untriggered, clampedMaxTradePrice, perpetualId, metrics.MaxTradePrice) + allTriggeredOrderIds = append(allTriggeredOrderIds, triggered...) } // Set the modified untriggeredConditionalOrders back on the keeper field. - k.UntriggeredConditionalOrders[clobPairId] = untriggeredConditionalOrders + k.UntriggeredConditionalOrders[clobPairId] = untriggered } + return allTriggeredOrderIds +} + +// TriggerOrdersWithPrice triggers all untriggered conditional orders using the given price. It returns +// a list of order ids that were triggered. This function is called in EndBlocker. +// It removes all triggered conditional orders from the `UntriggeredConditionalOrders ` struct. +func (k Keeper) TriggerOrdersWithPrice( + ctx sdk.Context, + untriggered *UntriggeredConditionalOrders, + price *big.Rat, + perpetualId uint32, + priceType string, +) (triggeredOrderIds []types.OrderId) { + triggeredOrderIds = untriggered.PollTriggeredConditionalOrders(price) + + // Emit metrics. + priceFloat, _ := price.Float32() + labels := []gometrics.Label{ + metrics.GetLabelForStringValue(metrics.Type, priceType), + metrics.GetLabelForIntValue(metrics.PerpetualId, int(perpetualId)), + } + metrics.SetGaugeWithLabels(metrics.ClobConditionalOrderTriggerPrice, priceFloat, labels...) + // State write - move the conditional order placement in state from untriggered to triggered state. // Emit an event for each triggered conditional order. - for _, triggeredConditionalOrderId := range triggeredConditionalOrderIds { + for _, orderId := range triggeredOrderIds { k.MustTriggerConditionalOrder( ctx, - triggeredConditionalOrderId, + orderId, ) k.GetIndexerEventManager().AddTxnEvent( ctx, @@ -356,10 +352,71 @@ func (k Keeper) MaybeTriggerConditionalOrders(ctx sdk.Context) (triggeredConditi indexerevents.StatefulOrderEventVersion, indexer_manager.GetBytes( indexerevents.NewConditionalOrderTriggeredEvent( - triggeredConditionalOrderId, + orderId, ), ), ) + + metrics.IncrCountMetricWithLabels( + types.ModuleName, + metrics.ClobConditionalOrderTriggered, + append(orderId.GetOrderIdLabels(), labels...)..., + ) + } + return triggeredOrderIds +} + +func (k Keeper) getClampedTradePricesForTriggering( + ctx sdk.Context, + perpetualId uint32, + oraclePrice *big.Rat, +) ( + clampedMinTradePrice *big.Rat, + clampedMaxTradePrice *big.Rat, + found bool, +) { + minTradePriceSubticks, maxTradePriceSubticks, found := k.GetTradePricesForPerpetual(ctx, perpetualId) + if found { + // Get the perpetual. + perpetual, err := k.perpetualsKeeper.GetPerpetual(ctx, perpetualId) + if err != nil { + panic( + fmt.Errorf( + "EndBlocker: untriggeredConditionalOrders failed to find perpetualId %+v", + perpetualId, + ), + ) + } + + // Get the market param. + marketParam, exists := k.pricesKeeper.GetMarketParam(ctx, perpetual.Params.MarketId) + if !exists { + panic( + fmt.Errorf( + "EndBlocker: untriggeredConditionalOrders failed to find marketParam %+v", + perpetual.Params.MarketId, + ), + ) + } + + // Calculate the max allowed range. + maxAllowedRange := lib.BigRatMulPpm(oraclePrice, marketParam.MinPriceChangePpm) + maxAllowedRange.Mul(maxAllowedRange, new(big.Rat).SetUint64(types.ConditionalOrderTriggerMultiplier)) + + upperBound := new(big.Rat).Add(oraclePrice, maxAllowedRange) + lowerBound := new(big.Rat).Sub(oraclePrice, maxAllowedRange) + + // Clamp the min and max trade prices to the upper and lower bounds. + clampedMinTradePrice = lib.BigRatClamp( + new(big.Rat).SetUint64(minTradePriceSubticks.ToUint64()), + lowerBound, + upperBound, + ) + clampedMaxTradePrice = lib.BigRatClamp( + new(big.Rat).SetUint64(maxTradePriceSubticks.ToUint64()), + lowerBound, + upperBound, + ) } - return triggeredConditionalOrderIds + return clampedMinTradePrice, clampedMaxTradePrice, found }