cap dynamic slippage when book crosses

This commit is contained in:
wphan
2025-10-16 10:58:40 -07:00
parent 208bbb71cf
commit f99253a4c1
4 changed files with 260 additions and 276 deletions

View File

@@ -12,6 +12,7 @@ import {
createMarketBasedAuctionParams,
mapToMarketOrderParams,
formatAuctionParamsForResponse,
calculateDynamicSlippage,
} from '../utils';
describe('Auction Parameters Functions', () => {
@@ -692,3 +693,97 @@ describe('Auction Parameters Functions', () => {
});
});
});
describe('calculateDynamicSlippage - crossed book handling', () => {
const mockDriftClient = {
getOracleDataForPerpMarket: jest.fn(),
getOracleDataForSpotMarket: jest.fn(),
} as any;
beforeEach(() => {
jest.clearAllMocks();
});
it('caps spread contribution when crossed (default cap mode)', () => {
// Set env to deterministic values
process.env.DYNAMIC_BASE_SLIPPAGE_MAJOR = '0';
process.env.DYNAMIC_SLIPPAGE_MULTIPLIER_MAJOR = '1';
process.env.DYNAMIC_SLIPPAGE_MIN = '0';
process.env.DYNAMIC_SLIPPAGE_MAX = '100';
delete process.env.DYNAMIC_CROSS_SPREAD_MODE; // default 'cap'
process.env.DYNAMIC_CROSS_SPREAD_CAP = '0.1'; // 0.1%
// Oracle 100
mockDriftClient.getOracleDataForPerpMarket.mockReturnValue({
price: new BN(100).mul(PRICE_PRECISION),
});
const l2Crossed = {
bids: [{ price: new BN(101).mul(PRICE_PRECISION), size: new BN(1) }],
asks: [{ price: new BN(99).mul(PRICE_PRECISION), size: new BN(1) }],
} as any;
const startPrice = new BN(100).mul(PRICE_PRECISION);
const worstPrice = new BN(100).mul(PRICE_PRECISION);
const slip = calculateDynamicSlippage(
0, // major perp
'perp',
mockDriftClient,
l2Crossed,
startPrice,
worstPrice
);
// Should be capped at 0.1% given our env
expect(slip).toBeLessThanOrEqual(0.1);
expect(slip).toBeGreaterThanOrEqual(0);
});
it('normal (non-crossed) book produces spread-based slippage > crossed-capped', () => {
process.env.DYNAMIC_BASE_SLIPPAGE_MAJOR = '0';
process.env.DYNAMIC_SLIPPAGE_MULTIPLIER_MAJOR = '1';
process.env.DYNAMIC_SLIPPAGE_MIN = '0';
process.env.DYNAMIC_SLIPPAGE_MAX = '100';
delete process.env.DYNAMIC_CROSS_SPREAD_MODE; // default cap
process.env.DYNAMIC_CROSS_SPREAD_CAP = '0.1';
mockDriftClient.getOracleDataForPerpMarket.mockReturnValue({
price: new BN(100).mul(PRICE_PRECISION),
});
const l2Normal = {
bids: [{ price: new BN(99).mul(PRICE_PRECISION), size: new BN(1) }],
asks: [{ price: new BN(101).mul(PRICE_PRECISION), size: new BN(1) }],
} as any;
const startPrice = new BN(100).mul(PRICE_PRECISION);
const worstPrice = new BN(100).mul(PRICE_PRECISION);
const slipNormal = calculateDynamicSlippage(
0,
'perp',
mockDriftClient,
l2Normal,
startPrice,
worstPrice
);
const l2Crossed = {
bids: [{ price: new BN(101).mul(PRICE_PRECISION), size: new BN(1) }],
asks: [{ price: new BN(99).mul(PRICE_PRECISION), size: new BN(1) }],
} as any;
const slipCrossed = calculateDynamicSlippage(
0,
'perp',
mockDriftClient,
l2Crossed,
startPrice,
worstPrice
);
// Non-crossed spread should be >= crossed (which is capped)
expect(slipNormal).toBeGreaterThanOrEqual(slipCrossed);
});
});

View File

@@ -311,7 +311,7 @@ export function publishGroupings(
bids: aggregatedBids,
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}`)
@@ -1181,6 +1181,21 @@ export const calculateDynamicSlippage = (
if (spreadInfo?.spreadPct) {
spreadBaseSlippage = spreadPctNum / 2;
// If the L2 is crossed (best bid > best ask), cap the spread contribution
const bestBid = spreadInfo.bestBidPrice;
const bestAsk = spreadInfo.bestAskPrice;
const isCrossed = !!(bestBid && bestAsk && bestBid.gt(bestAsk));
if (isCrossed) {
// Always cap the spread component tightly when crossed, default to 0.1%
const defaultCrossCap = 0.1;
const crossCap =
parseFloat(
process.env.DYNAMIC_CROSS_SPREAD_CAP || defaultCrossCap.toString()
) ?? defaultCrossCap;
spreadBaseSlippage = Math.min(spreadBaseSlippage, crossCap);
}
}
} catch (error) {
console.warn('Failed to calculate spread, using fallback:', error);