From 32d13d4b00b01a397a2cb0eaa894174290eac0b4 Mon Sep 17 00:00:00 2001 From: lowkeynicc <85139158+lowkeynicc@users.noreply.github.com> Date: Wed, 17 Sep 2025 15:52:57 -0400 Subject: [PATCH 1/3] add mid major setting for dynamic slippage (#509) --- src/utils/constants.ts | 2 ++ src/utils/utils.ts | 7 ++++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/utils/constants.ts b/src/utils/constants.ts index 99e9d70..1b21d5c 100644 --- a/src/utils/constants.ts +++ b/src/utils/constants.ts @@ -23,3 +23,5 @@ export const DEFAULT_AUCTION_PARAMS: Partial = { auctionStartPriceOffsetFrom: 'marketBased', auctionEndPriceOffsetFrom: DEFAULT_AUCTION_END_PRICE_FROM, }; + +export const MID_MAJOR_MARKETS = [13, 23, 4, 59, 9]; // XRP, WIF, BONK, HYPE, SUI diff --git a/src/utils/utils.ts b/src/utils/utils.ts index 230b65c..83613b8 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -31,7 +31,7 @@ import { NextFunction, Request, Response } from 'express'; import FEATURE_FLAGS from './featureFlags'; import { Connection } from '@solana/web3.js'; import { wsMarketArgs } from 'src/dlob-subscriber/DLOBSubscriberIO'; -import { DEFAULT_AUCTION_PARAMS } from './constants'; +import { DEFAULT_AUCTION_PARAMS, MID_MAJOR_MARKETS } from './constants'; import { AuctionParamArgs } from './types'; import { COMMON_MATH, ENUM_UTILS } from '@drift/common'; @@ -1143,9 +1143,12 @@ export const calculateDynamicSlippage = ( // Determine if this is a major market (PERP with marketIndex 0, 1, or 2) const isPerp = marketType.toLowerCase() === 'perp'; const isMajor = isPerp && marketIndex < 3; + const isMidMajor = isPerp && MID_MAJOR_MARKETS.includes(marketIndex); const baseSlippage = isMajor ? parseFloat(process.env.DYNAMIC_BASE_SLIPPAGE_MAJOR || '0') // 0% default + : isMidMajor + ? parseFloat(process.env.DYNAMIC_BASE_SLIPPAGE_MID_MAJOR || '0.025') // 0.025% default : parseFloat(process.env.DYNAMIC_BASE_SLIPPAGE_NON_MAJOR || '0.5'); // 0.5% default // Calculate spread using L2 data @@ -1193,6 +1196,8 @@ export const calculateDynamicSlippage = ( // Apply multiplier from env var const multiplier = isMajor ? parseFloat(process.env.DYNAMIC_SLIPPAGE_MULTIPLIER_MAJOR || '1.01') + : isMidMajor + ? parseFloat(process.env.DYNAMIC_SLIPPAGE_MULTIPLIER_MID_MAJOR || '1.2') : parseFloat(process.env.DYNAMIC_SLIPPAGE_MULTIPLIER_NON_MAJOR || '1.4'); dynamicSlippage = dynamicSlippage * multiplier; From 58a87f685fae95205a4ab7163a5d49f0e3d76d7c Mon Sep 17 00:00:00 2001 From: Nick Caradonna Date: Wed, 17 Sep 2025 20:25:58 -0400 Subject: [PATCH 2/3] fix typo --- src/utils/utils.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/utils.ts b/src/utils/utils.ts index 83613b8..3338769 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -1148,7 +1148,7 @@ export const calculateDynamicSlippage = ( const baseSlippage = isMajor ? parseFloat(process.env.DYNAMIC_BASE_SLIPPAGE_MAJOR || '0') // 0% default : isMidMajor - ? parseFloat(process.env.DYNAMIC_BASE_SLIPPAGE_MID_MAJOR || '0.025') // 0.025% default + ? parseFloat(process.env.DYNAMIC_BASE_SLIPPAGE_MID_MAJOR || '0.25') // 0.25% default : parseFloat(process.env.DYNAMIC_BASE_SLIPPAGE_NON_MAJOR || '0.5'); // 0.5% default // Calculate spread using L2 data From a314b5f1f873f7092f0c39baf91ab23df247941a Mon Sep 17 00:00:00 2001 From: wphan Date: Wed, 22 Oct 2025 23:50:24 -0700 Subject: [PATCH 3/3] add direction aware adjustments --- src/utils/utils.ts | 144 ++++++++++++++++++++++++++++++++++----------- 1 file changed, 110 insertions(+), 34 deletions(-) diff --git a/src/utils/utils.ts b/src/utils/utils.ts index 00c27fd..8faf676 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -36,6 +36,7 @@ import { AuctionParamArgs } from './types'; import { COMMON_MATH, ENUM_UTILS } from '@drift/common'; import { TakerFillVsOracleBpsRedisResult } from '../athena/repositories/fillQualityAnalytics'; +const MAX_FILL_QUALITY_AGE_MS = 10 * 60 * 1000; // 10 minutes export const GROUPING_OPTIONS = [1, 10, 100, 500, 1000]; export const GROUPING_DEPENDENCIES = { 1: null, @@ -979,6 +980,12 @@ export const mapToMarketOrderParams = async ( adjustedMarkPrice: null as string | null, isCrossed: false, fillQualityBps: null as number | null, + fillQualityDataStale: false, + priceReferenceUsed: null as string | null, + priceReferenceDistance: null as string | null, + reason: null as string | null, + markVsOracle: null as string | null, + isMarkFavorableForDirection: null as boolean | null, appliedV2Adjustment: false, }; @@ -1031,45 +1038,106 @@ export const mapToMarketOrderParams = async ( debugInfo.isCrossed = isCrossed; if (isCrossed) { - // Get fill quality metric based on direction - const fillQualityBpsStr = - direction === PositionDirection.LONG - ? fillQualityInfo.takerBuyBpsFromOracle?.all - : fillQualityInfo.takerSellBpsFromOracle?.all; + // Check data staleness - ignore if older than 10 minutes + const dataAge = Date.now() - (fillQualityInfo.updatedAtTs || 0); - if ( - fillQualityBpsStr && - fillQualityBpsStr !== 'null' && - fillQualityBpsStr !== null - ) { - const fillQualityBps = Math.round( - parseFloat(fillQualityBpsStr) * 100 + if (dataAge > MAX_FILL_QUALITY_AGE_MS) { + logger.warn( + `Version 2: Fill quality data is stale (${Math.round( + dataAge / 1000 + )}s old), skipping adjustment for market ${params.marketIndex}` ); - debugInfo.fillQualityBps = fillQualityBps; + debugInfo.fillQualityDataStale = true; + } else { + // Get fill quality metric based on direction + const fillQualityBpsStr = + direction === PositionDirection.LONG + ? fillQualityInfo.takerBuyBpsFromOracle?.all + : fillQualityInfo.takerSellBpsFromOracle?.all; - if (!isNaN(fillQualityBps)) { - const adjustment = oraclePrice - .muln(fillQualityBps) - .divn(10000 * 100); // adjustmentFactor is in bps - const adjustedOraclePrice = oraclePrice.add(adjustment); + if ( + fillQualityBpsStr && + fillQualityBpsStr !== 'null' && + fillQualityBpsStr !== null + ) { + const fillQualityBps = Math.round( + parseFloat(fillQualityBpsStr) * 100 + ); + debugInfo.fillQualityBps = fillQualityBps; - // Update ALL relevant prices in estimatedPrices to maintain consistency - // This ensures auction parameters calculated from any price reference (oracle, mark, best, worst) - // will reflect the fill quality adjustment - estimatedPrices.oraclePrice = adjustedOraclePrice; - estimatedPrices.bestPrice = - estimatedPrices.bestPrice.add(adjustment); - estimatedPrices.entryPrice = - estimatedPrices.entryPrice.add(adjustment); - estimatedPrices.worstPrice = - estimatedPrices.worstPrice.add(adjustment); - estimatedPrices.markPrice = - estimatedPrices.markPrice.add(adjustment); + if (!isNaN(fillQualityBps)) { + const adjustment = oraclePrice + .muln(fillQualityBps) + .divn(10000 * 100); + const fillQualityAdjustedPrice = oraclePrice.add(adjustment); - debugInfo.adjustedOraclePrice = adjustedOraclePrice.toString(); - debugInfo.adjustedMarkPrice = - estimatedPrices.markPrice.toString(); - debugInfo.appliedV2Adjustment = true; + // Compare fill quality adjusted price vs mark price to determine which is better for takers + // We want to use the price that gets takers closer to where makers have been filling + // AND consider whether the mark price is favorable for the taker's direction + const markPrice = estimatedPrices.markPrice; + const originalOraclePrice = oraclePrice; + + // Determine if mark price favors this direction + const markVsOracle = markPrice.sub(originalOraclePrice); + const isMarkFavorableForDirection = + direction === PositionDirection.LONG + ? markVsOracle.lt(ZERO) // Mark below oracle is good for longs + : markVsOracle.gt(ZERO); // Mark above oracle is good for shorts + + // Calculate distances from each price to the fill quality adjusted price + // The fill quality adjusted price represents where makers have been filling + const distanceFromMark = markPrice + .sub(fillQualityAdjustedPrice) + .abs(); + const distanceFromOracle = originalOraclePrice + .sub(fillQualityAdjustedPrice) + .abs(); + + // Decision logic: prefer fill quality adjusted price when: + // 1. It's closer to the target, OR + // 2. Mark price is unfavorable for this direction + if ( + distanceFromOracle.lt(distanceFromMark) || + !isMarkFavorableForDirection + ) { + // Use fill quality adjusted price + estimatedPrices.markPrice = fillQualityAdjustedPrice; + debugInfo.priceReferenceUsed = 'oracleAdjusted'; + debugInfo.priceReferenceDistance = + distanceFromOracle.toString(); + debugInfo.reason = isMarkFavorableForDirection + ? 'closerToTarget' + : 'markUnfavorable'; + } else { + // Use mark price + debugInfo.priceReferenceUsed = 'mark'; + debugInfo.priceReferenceDistance = + distanceFromMark.toString(); + debugInfo.reason = 'markFavorableAndCloser'; + } + + // Store additional debug info + debugInfo.markVsOracle = markVsOracle.toString(); + debugInfo.isMarkFavorableForDirection = + isMarkFavorableForDirection; + + // Always update oracle price with fill quality adjustment for consistency + estimatedPrices.oraclePrice = fillQualityAdjustedPrice; + + // Update other prices to maintain consistency + estimatedPrices.bestPrice = + estimatedPrices.bestPrice.add(adjustment); + estimatedPrices.entryPrice = + estimatedPrices.entryPrice.add(adjustment); + estimatedPrices.worstPrice = + estimatedPrices.worstPrice.add(adjustment); + + debugInfo.adjustedOraclePrice = + fillQualityAdjustedPrice.toString(); + debugInfo.adjustedMarkPrice = + estimatedPrices.markPrice.toString(); + debugInfo.appliedV2Adjustment = true; + } } } } @@ -1160,11 +1228,19 @@ export const mapToMarketOrderParams = async ( adjustedOraclePrice: debugInfo.adjustedOraclePrice, adjustedMarkPrice: debugInfo.adjustedMarkPrice, appliedAdjustment: debugInfo.appliedV2Adjustment, + fillQualityDataStale: debugInfo.fillQualityDataStale, + priceReferenceUsed: debugInfo.priceReferenceUsed, + priceReferenceDistance: debugInfo.priceReferenceDistance, + reason: debugInfo.reason, + markVsOracle: debugInfo.markVsOracle, + isMarkFavorableForDirection: + debugInfo.isMarkFavorableForDirection, fillQualityData: fillQualityInfo ? { takerBuyBpsAll: fillQualityInfo.takerBuyBpsFromOracle?.all, takerSellBpsAll: fillQualityInfo.takerSellBpsFromOracle?.all, + updatedAtTs: fillQualityInfo.updatedAtTs, } : null, }