cap dynamic slippage when book crosses
This commit is contained in:
@@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user