Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 25 additions & 10 deletions src/__tests__/unit/position-calculations.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -361,11 +361,10 @@ describe("Position Calculation Functions", () => {

const liqPrice = calculateLiquidationPrice(rawData, true);

// For long: liqPrice = entryPrice - (margin - liqRatio * entryNotional) / size
// entryPrice = 50, margin = 100, liqRatio = 0.05, entryNotional = 1 * 50 = 50
// liqPrice = 50 - (100 - 0.05 * 50) / 1 = 50 - 97.5 = -47.5, but max(0, -47.5) = 0
// long: (entry - margin/size) / (1 - liqRatio)
// = (50 - 100/1) / (1 - 0.05) = -50/0.95 < 0 -> clamped to 0
expect(liqPrice).not.toBeNull();
expect(liqPrice).toBeGreaterThanOrEqual(0);
expect(liqPrice).toBe(0);
});

it("should calculate liquidation price for short position", () => {
Expand All @@ -381,11 +380,10 @@ describe("Position Calculation Functions", () => {

const liqPrice = calculateLiquidationPrice(rawData, false);

// For short: liqPrice = entryPrice + (margin - liqRatio * entryNotional) / size
// entryPrice = 50, margin = 100, liqRatio = 0.05, entryNotional = 1 * 50 = 50
// liqPrice = 50 + (100 - 0.05 * 50) / 1 = 50 + 97.5 = 147.5
// short: (entry + margin/size) / (1 + liqRatio)
// = (50 + 100/1) / (1 + 0.05) = 150/1.05 = 142.857
expect(liqPrice).not.toBeNull();
expect(liqPrice).toBeGreaterThan(50);
expect(liqPrice).toBeCloseTo(142.857, 2);
});

it("should return null for zero position size", () => {
Expand Down Expand Up @@ -433,9 +431,9 @@ describe("Position Calculation Functions", () => {

const liqPrice = calculateLiquidationPrice(rawData, true);

// For high leverage longs, liquidation price should be close to entry
// long: (50 - 10/1) / (1 - 0.05) = 40/0.95 = 42.105
expect(liqPrice).not.toBeNull();
expect(liqPrice).toBeGreaterThanOrEqual(0);
expect(liqPrice).toBeCloseTo(42.105, 2);
expect(liqPrice).toBeLessThan(50);
});

Expand Down Expand Up @@ -492,6 +490,23 @@ describe("Position Calculation Functions", () => {
expect(liqPrice).not.toBeNull();
expect(liqPrice).toBeGreaterThanOrEqual(0);
});

it("should return null for long when liq margin ratio is >= 100%", () => {
const rawData: PositionRawData = {
perpId: "0x123" as any,
positionId: 1n,
margin: 100,
entryPerpDelta: 1000000n, // 1 perp token (1e6)
entryUsdDelta: 50000000n, // $50 entry (1e6)
marginRatios: { liq: 1000000, backstop: 500000 }, // liq ratio = 1.0 (100%)
makerDetails: null,
};

const liqPrice = calculateLiquidationPrice(rawData, true);

// long denom = 1 - liqRatio = 0 -> no positive liquidation price
expect(liqPrice).toBeNull();
});
});

describe("Integration: Position metrics", () => {
Expand Down
12 changes: 9 additions & 3 deletions src/functions/position.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,12 +101,18 @@ export function calculateLiquidationPrice(
const margin = effectiveMargin ?? rawData.margin;
if (positionSize === 0 || margin <= 0) return null;

// Per IMarginRatios, the margin ratio is equity / value against the *current*
// notional (size * price). Solving `equity == liqRatio * size * liqPrice` for
// the liquidation price gives:
// long: (entry - margin/size) / (1 - liqRatio)
// short: (entry + margin/size) / (1 + liqRatio)
const liqMarginRatio = rawData.marginRatios.liq / 1e6;
const entryNotional = positionSize * entryPrice;
if (isLong) {
return Math.max(0, entryPrice - (margin - liqMarginRatio * entryNotional) / positionSize);
const denom = 1 - liqMarginRatio;
if (denom <= 0) return null; // liq ratio >= 100%: no positive liquidation price
return Math.max(0, (entryPrice - margin / positionSize) / denom);
}
return entryPrice + (margin - liqMarginRatio * entryNotional) / positionSize;
return (entryPrice + margin / positionSize) / (1 + liqMarginRatio);
}

export function calculatePnlPercentage(pnl: number, funding: number, margin: number): number {
Expand Down
Loading