import {
createMarginCalculator,
priceUsdToTicksWithMarketParams,
type ExchangeMarketSnapshot,
type MarketParams,
type SubaccountMarginInputs,
} from "@ellipsis-labs/rise";
const bps = (value: number | undefined, fallback: number) =>
String(value ?? fallback);
const paramsFromExchangeCache = (
market: ExchangeMarketSnapshot,
markPriceUsd: number
): MarketParams => ({
symbol: market.symbol,
assetId: market.assetId,
markPriceTicks: priceUsdToTicksWithMarketParams(markPriceUsd, {
tickSize: market.tickSize,
baseLotsDecimals: market.baseLotsDecimals,
}).toString(),
tickSize: market.tickSize.toString(),
baseLotDecimals: market.baseLotsDecimals,
leverageTiers: market.leverageTiers.map((tier) => ({
upperBoundSize: tier.maxSizeBaseLots.toString(),
maxLeverage: String(tier.maxLeverage),
limitOrderRiskFactorBps: String(tier.limitOrderRiskFactor),
})),
riskFactors: {
maintenanceMarginFactorBps: bps(
market.riskFactors.maintenanceBps,
market.riskFactors.maintenance
),
backstopMarginFactorBps: bps(
market.riskFactors.backstopBps,
market.riskFactors.backstop
),
highRiskMarginFactorBps: bps(
market.riskFactors.highRiskBps,
market.riskFactors.highRisk
),
},
cancelOrderRiskFactorBps: bps(
market.riskFactors.cancelOrderBps,
market.riskFactors.cancelOrder
),
upnlRiskFactor: bps(market.riskFactors.upnlBps, market.riskFactors.upnl),
upnlRiskFactorForWithdrawals: bps(
market.riskFactors.upnlForWithdrawalsBps,
market.riskFactors.upnlForWithdrawals
),
isolatedOnly: market.isolatedOnly,
});
const liquidationTiers = new Set([
"liquidatable",
"backstopLiquidatable",
"highRisk",
]);
const estimateLiquidationPriceUsd = (
basePositionLots: bigint,
currentPriceUsd: number,
crossesLiquidation: (priceUsd: number) => boolean
) => {
if (basePositionLots === 0n || currentPriceUsd <= 0) return null;
if (crossesLiquidation(currentPriceUsd)) return currentPriceUsd;
if (basePositionLots > 0n) {
let low = Math.max(currentPriceUsd / 1_000_000, Number.EPSILON);
let high = currentPriceUsd;
if (!crossesLiquidation(low)) return null;
for (let i = 0; i < 48; i++) {
const mid = (low + high) / 2;
if (crossesLiquidation(mid)) low = mid;
else high = mid;
}
return high;
}
let low = currentPriceUsd;
let high = currentPriceUsd * 2;
for (let i = 0; i < 20 && !crossesLiquidation(high); i++) {
low = high;
high *= 2;
}
if (!crossesLiquidation(high)) return null;
for (let i = 0; i < 48; i++) {
const mid = (low + high) / 2;
if (crossesLiquidation(mid)) high = mid;
else low = mid;
}
return high;
};
await client.exchange.ready();
const marketData = client.marketData();
const releaseMarketData = marketData.retain();
await marketData.ready();
const marginInputs = resource.marginInputs();
const subaccount = marginInputs?.subaccounts.find(
(item) => item.subaccountIndex === 0
);
if (!subaccount) throw new Error("subaccount margin inputs are not ready");
const paramsBySymbol: Record<string, MarketParams> = {};
for (const marketInput of subaccount.markets) {
const market = client.exchange.market(marketInput.symbol);
const markPrice = marketData.market(marketInput.symbol)?.markPrice;
if (!market || markPrice === null || markPrice === undefined) {
throw new Error(`missing cached market data for ${marketInput.symbol}`);
}
paramsBySymbol[marketInput.symbol] = paramsFromExchangeCache(market, markPrice);
}
const symbol = "SOL";
const target = subaccount.markets.find((market) => market.symbol === symbol);
const basePositionLots = BigInt(target?.position?.basePositionLots ?? "0");
const currentPrice = marketData.market(symbol)?.markPrice;
if (currentPrice === null || currentPrice === undefined) {
throw new Error(`missing mark price for ${symbol}`);
}
const crossesLiquidation = (priceUsd: number) => {
const market = client.exchange.market(symbol);
if (!market) throw new Error(`unknown market ${symbol}`);
const calculator = createMarginCalculator(
Object.values({
...paramsBySymbol,
[symbol]: paramsFromExchangeCache(market, priceUsd),
})
);
const result = calculator.computeSubaccountMarginFromInputs(
subaccount as SubaccountMarginInputs
);
return liquidationTiers.has(result.margin.riskTier);
};
const estimatedLiquidationPriceUsd = estimateLiquidationPriceUsd(
basePositionLots,
currentPrice,
crossesLiquidation
);
console.log(estimatedLiquidationPriceUsd);
releaseMarketData();