Skip to content

Commit

Permalink
[CLOB-1054] add some telemetry for conditional order triggering (#958)
Browse files Browse the repository at this point in the history
* [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 <[email protected]>

* comments

---------

Co-authored-by: Jonathan Fung <[email protected]>
  • Loading branch information
jayy04 and jonfung-dydx authored Jan 11, 2024
1 parent c42ab34 commit 932307b
Show file tree
Hide file tree
Showing 4 changed files with 117 additions and 64 deletions.
3 changes: 3 additions & 0 deletions protocol/lib/metrics/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ const (
SampleRate = "sample_rate"
SequenceNumber = "sequence_number"
Success = "success"
Type = "type"
Valid = "valid"
ValidateBasic = "validate_basic"
CheckTx = "check_tx"
Expand Down Expand Up @@ -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"
Expand Down
6 changes: 4 additions & 2 deletions protocol/lib/metrics/metric_keys.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
9 changes: 0 additions & 9 deletions protocol/x/clob/keeper/stateful_order_state.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
163 changes: 110 additions & 53 deletions protocol/x/clob/keeper/untriggered_conditional_orders.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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(
Expand All @@ -288,78 +290,133 @@ 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,
indexerevents.SubtypeStatefulOrder,
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
}

0 comments on commit 932307b

Please sign in to comment.