add direction aware adjustments
This commit is contained in:
@@ -36,6 +36,7 @@ import { AuctionParamArgs } from './types';
|
|||||||
import { COMMON_MATH, ENUM_UTILS } from '@drift/common';
|
import { COMMON_MATH, ENUM_UTILS } from '@drift/common';
|
||||||
import { TakerFillVsOracleBpsRedisResult } from '../athena/repositories/fillQualityAnalytics';
|
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_OPTIONS = [1, 10, 100, 500, 1000];
|
||||||
export const GROUPING_DEPENDENCIES = {
|
export const GROUPING_DEPENDENCIES = {
|
||||||
1: null,
|
1: null,
|
||||||
@@ -979,6 +980,12 @@ export const mapToMarketOrderParams = async (
|
|||||||
adjustedMarkPrice: null as string | null,
|
adjustedMarkPrice: null as string | null,
|
||||||
isCrossed: false,
|
isCrossed: false,
|
||||||
fillQualityBps: null as number | null,
|
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,
|
appliedV2Adjustment: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -1031,45 +1038,106 @@ export const mapToMarketOrderParams = async (
|
|||||||
debugInfo.isCrossed = isCrossed;
|
debugInfo.isCrossed = isCrossed;
|
||||||
|
|
||||||
if (isCrossed) {
|
if (isCrossed) {
|
||||||
// Get fill quality metric based on direction
|
// Check data staleness - ignore if older than 10 minutes
|
||||||
const fillQualityBpsStr =
|
const dataAge = Date.now() - (fillQualityInfo.updatedAtTs || 0);
|
||||||
direction === PositionDirection.LONG
|
|
||||||
? fillQualityInfo.takerBuyBpsFromOracle?.all
|
|
||||||
: fillQualityInfo.takerSellBpsFromOracle?.all;
|
|
||||||
|
|
||||||
if (
|
if (dataAge > MAX_FILL_QUALITY_AGE_MS) {
|
||||||
fillQualityBpsStr &&
|
logger.warn(
|
||||||
fillQualityBpsStr !== 'null' &&
|
`Version 2: Fill quality data is stale (${Math.round(
|
||||||
fillQualityBpsStr !== null
|
dataAge / 1000
|
||||||
) {
|
)}s old), skipping adjustment for market ${params.marketIndex}`
|
||||||
const fillQualityBps = Math.round(
|
|
||||||
parseFloat(fillQualityBpsStr) * 100
|
|
||||||
);
|
);
|
||||||
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)) {
|
if (
|
||||||
const adjustment = oraclePrice
|
fillQualityBpsStr &&
|
||||||
.muln(fillQualityBps)
|
fillQualityBpsStr !== 'null' &&
|
||||||
.divn(10000 * 100); // adjustmentFactor is in bps
|
fillQualityBpsStr !== null
|
||||||
const adjustedOraclePrice = oraclePrice.add(adjustment);
|
) {
|
||||||
|
const fillQualityBps = Math.round(
|
||||||
|
parseFloat(fillQualityBpsStr) * 100
|
||||||
|
);
|
||||||
|
debugInfo.fillQualityBps = fillQualityBps;
|
||||||
|
|
||||||
// Update ALL relevant prices in estimatedPrices to maintain consistency
|
if (!isNaN(fillQualityBps)) {
|
||||||
// This ensures auction parameters calculated from any price reference (oracle, mark, best, worst)
|
const adjustment = oraclePrice
|
||||||
// will reflect the fill quality adjustment
|
.muln(fillQualityBps)
|
||||||
estimatedPrices.oraclePrice = adjustedOraclePrice;
|
.divn(10000 * 100);
|
||||||
estimatedPrices.bestPrice =
|
const fillQualityAdjustedPrice = oraclePrice.add(adjustment);
|
||||||
estimatedPrices.bestPrice.add(adjustment);
|
|
||||||
estimatedPrices.entryPrice =
|
|
||||||
estimatedPrices.entryPrice.add(adjustment);
|
|
||||||
estimatedPrices.worstPrice =
|
|
||||||
estimatedPrices.worstPrice.add(adjustment);
|
|
||||||
estimatedPrices.markPrice =
|
|
||||||
estimatedPrices.markPrice.add(adjustment);
|
|
||||||
|
|
||||||
debugInfo.adjustedOraclePrice = adjustedOraclePrice.toString();
|
// Compare fill quality adjusted price vs mark price to determine which is better for takers
|
||||||
debugInfo.adjustedMarkPrice =
|
// We want to use the price that gets takers closer to where makers have been filling
|
||||||
estimatedPrices.markPrice.toString();
|
// AND consider whether the mark price is favorable for the taker's direction
|
||||||
debugInfo.appliedV2Adjustment = true;
|
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,
|
adjustedOraclePrice: debugInfo.adjustedOraclePrice,
|
||||||
adjustedMarkPrice: debugInfo.adjustedMarkPrice,
|
adjustedMarkPrice: debugInfo.adjustedMarkPrice,
|
||||||
appliedAdjustment: debugInfo.appliedV2Adjustment,
|
appliedAdjustment: debugInfo.appliedV2Adjustment,
|
||||||
|
fillQualityDataStale: debugInfo.fillQualityDataStale,
|
||||||
|
priceReferenceUsed: debugInfo.priceReferenceUsed,
|
||||||
|
priceReferenceDistance: debugInfo.priceReferenceDistance,
|
||||||
|
reason: debugInfo.reason,
|
||||||
|
markVsOracle: debugInfo.markVsOracle,
|
||||||
|
isMarkFavorableForDirection:
|
||||||
|
debugInfo.isMarkFavorableForDirection,
|
||||||
fillQualityData: fillQualityInfo
|
fillQualityData: fillQualityInfo
|
||||||
? {
|
? {
|
||||||
takerBuyBpsAll: fillQualityInfo.takerBuyBpsFromOracle?.all,
|
takerBuyBpsAll: fillQualityInfo.takerBuyBpsFromOracle?.all,
|
||||||
takerSellBpsAll:
|
takerSellBpsAll:
|
||||||
fillQualityInfo.takerSellBpsFromOracle?.all,
|
fillQualityInfo.takerSellBpsFromOracle?.all,
|
||||||
|
updatedAtTs: fillQualityInfo.updatedAtTs,
|
||||||
}
|
}
|
||||||
: null,
|
: null,
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user