add auction anchoring in v2

This commit is contained in:
wphan
2025-10-21 16:33:30 -07:00
parent a627c89e0a
commit 3d4cf24296
8 changed files with 826 additions and 26 deletions

View File

@@ -34,6 +34,7 @@ import { wsMarketArgs } from 'src/dlob-subscriber/DLOBSubscriberIO';
import { DEFAULT_AUCTION_PARAMS, MID_MAJOR_MARKETS } from './constants';
import { AuctionParamArgs } from './types';
import { COMMON_MATH, ENUM_UTILS } from '@drift/common';
import { TakerFillVsOracleBpsRedisResult } from '../athena/repositories/fillQualityAnalytics';
export const GROUPING_OPTIONS = [1, 10, 100, 500, 1000];
export const GROUPING_DEPENDENCIES = {
@@ -312,10 +313,17 @@ export function publishGroupings(
asks: aggregatedAsks,
});
if(['SOL-PERP', 'BTC-PERP', 'ETH-PERP'].includes(l2Formatted_grouped20.marketName) && aggregatedBids.length !== 20 || aggregatedAsks.length !== 20) {
logger.error(`Error aggregating dlob levels: group=${group}, bids=${fullAggregatedBids.length}, asks=${fullAggregatedAsks.length}`)
logger.error(`Response: ${JSON.stringify(l2Formatted_grouped20)}`)
if (
(['SOL-PERP', 'BTC-PERP', 'ETH-PERP'].includes(
l2Formatted_grouped20.marketName
) &&
aggregatedBids.length !== 20) ||
aggregatedAsks.length !== 20
) {
logger.error(
`Error aggregating dlob levels: group=${group}, bids=${fullAggregatedBids.length}, asks=${fullAggregatedAsks.length}`
);
logger.error(`Response: ${JSON.stringify(l2Formatted_grouped20)}`);
}
redisClient.publish(
@@ -911,6 +919,8 @@ export const getEstimatedPrices = async (
* @param driftClient - DriftClient instance (optional, for price calculation)
* @param fetchFromRedis - Redis fetch function (optional, for price calculation)
* @param selectMostRecentBySlot - Slot selection function (optional, for price calculation)
* @param fillQualityInfo - Fill quality analytics data (version 2 only)
* @param apiVersion - API version (1 or 2)
* @returns Object formatted for deriveMarketOrderParams function or error response
*/
export const mapToMarketOrderParams = async (
@@ -920,7 +930,9 @@ export const mapToMarketOrderParams = async (
key: string,
selectionCriteria: (responses: any) => any
) => Promise<any>,
selectMostRecentBySlot?: (responses: any[]) => any
selectMostRecentBySlot?: (responses: any[]) => any,
fillQualityInfo?: TakerFillVsOracleBpsRedisResult,
apiVersion: number = 1
): Promise<{
success: boolean;
data?: {
@@ -960,6 +972,15 @@ export const mapToMarketOrderParams = async (
let estimatedPrices;
let processedSlippageTolerance = params.slippageTolerance;
// Track debug info for logging
let debugInfo = {
originalOraclePrice: null as string | null,
adjustedOraclePrice: null as string | null,
isCrossed: false,
fillQualityBps: null as number | null,
appliedV2Adjustment: false,
};
if (driftClient && fetchFromRedis && selectMostRecentBySlot) {
// Get L2 orderbook data using the utility function
const redisL2 = await fetchL2FromRedis(
@@ -980,6 +1001,73 @@ export const mapToMarketOrderParams = async (
redisL2
);
// Store original oracle for debugging
debugInfo.originalOraclePrice = estimatedPrices.oraclePrice.toString();
// VERSION 2: Adjust oracle price based on fill quality when orderbook is crossed
if (apiVersion === 2 && fillQualityInfo && redisL2) {
try {
// Convert raw L2 to formatted L2 for cross detection
const l2Formatted = convertRawL2ToBN(redisL2);
const isSpot = isVariant(marketType, 'spot');
const oracleData = isSpot
? driftClient.getOracleDataForSpotMarket(params.marketIndex)
: driftClient.getOracleDataForPerpMarket(params.marketIndex);
const oraclePrice = oracleData.price ?? ZERO;
// Detect if orderbook is crossed
const spreadInfo = COMMON_MATH.calculateSpreadBidAskMark(
l2Formatted,
oraclePrice
);
const isCrossed =
spreadInfo.bestBidPrice &&
spreadInfo.bestAskPrice &&
spreadInfo.bestBidPrice.gte(spreadInfo.bestAskPrice);
debugInfo.isCrossed = isCrossed;
if (isCrossed) {
// Get fill quality metric based on direction
const fillQualityBpsStr =
direction === PositionDirection.LONG
? fillQualityInfo.takerBuyBpsFromOracle?.all
: fillQualityInfo.takerSellBpsFromOracle?.all;
if (
fillQualityBpsStr &&
fillQualityBpsStr !== 'null' &&
fillQualityBpsStr !== null
) {
const fillQualityBps = Math.round(
parseFloat(fillQualityBpsStr) * 100
); // mul 100 to maintain 2 sigfigs (1.23 bps = 0.000123)
debugInfo.fillQualityBps = fillQualityBps;
if (!isNaN(fillQualityBps)) {
const adjustment = oraclePrice
.muln(fillQualityBps)
.divn(10000 * 100); // adjustmentFactor is in bps
const adjustedOraclePrice = oraclePrice.add(adjustment);
// Update the oracle price in estimatedPrices
estimatedPrices.oraclePrice = adjustedOraclePrice;
debugInfo.adjustedOraclePrice = adjustedOraclePrice.toString();
debugInfo.appliedV2Adjustment = true;
}
}
}
} catch (error) {
logger.warn(
'Version 2: Failed to apply fill quality adjustment, using standard oracle:',
error
);
// Fall through to use unadjusted oracle
}
}
// Handle dynamic slippage tolerance calculation if needed
if (params.slippageTolerance === undefined) {
// Convert raw L2 to formatted L2 for slippage calculation
@@ -1028,6 +1116,62 @@ export const mapToMarketOrderParams = async (
baseAmount = amount.mul(BASE_PRECISION).div(estimatedPrices.entryPrice);
}
// Comprehensive debug logging
logger.info(
JSON.stringify({
event: 'auction_params_calculated',
requestParams: {
marketIndex: params.marketIndex,
marketType: params.marketType,
direction: direction === PositionDirection.LONG ? 'long' : 'short',
amount: params.amount,
assetType: params.assetType,
slippageTolerance: params.slippageTolerance,
apiVersion,
},
priceDiscovery: {
originalOraclePrice: debugInfo.originalOraclePrice,
finalOraclePrice: estimatedPrices.oraclePrice.toString(),
bestPrice: estimatedPrices.bestPrice.toString(),
entryPrice: estimatedPrices.entryPrice.toString(),
worstPrice: estimatedPrices.worstPrice.toString(),
markPrice: estimatedPrices.markPrice.toString(),
priceImpactBps: estimatedPrices.priceImpact.toString(),
},
v2CrossDetection:
apiVersion === 2
? {
isCrossed: debugInfo.isCrossed,
fillQualityBps: debugInfo.fillQualityBps,
adjustedOraclePrice: debugInfo.adjustedOraclePrice,
appliedAdjustment: debugInfo.appliedV2Adjustment,
fillQualityData: fillQualityInfo
? {
takerBuyBpsAll: fillQualityInfo.takerBuyBpsFromOracle?.all,
takerSellBpsAll:
fillQualityInfo.takerSellBpsFromOracle?.all,
}
: null,
}
: undefined,
auctionConfig: {
duration: params.auctionDuration,
startPriceOffset: params.auctionStartPriceOffset,
startPriceOffsetFrom: params.auctionStartPriceOffsetFrom,
endPriceOffset: params.auctionEndPriceOffset,
endPriceOffsetFrom: params.auctionEndPriceOffsetFrom,
slippageTolerance: processedSlippageTolerance,
isOracleOrder: params.isOracleOrder,
reduceOnly: params.reduceOnly ?? false,
allowInfSlippage: params.allowInfSlippage ?? false,
},
calculatedValues: {
baseAmount: baseAmount.toString(),
slippageToleranceFinal: processedSlippageTolerance,
},
})
);
return {
success: true,
data: {