/**
* Anti-Liquidation Position Sizing System
* Calculate safe leverage, position sizes, liquidation prices with margin buffers
*/
import {
LiquidationPriceCalculation,
SafePositionSize,
FuturesMarketData
} from '../types/futures-types'
export interface PositionSizeConfig {
maxRiskPerTrade: number // Max risk per trade (% of capital)
maxLeverage: number // Max leverage (20x, 50x, 100x)
minLiquidationBuffer: number // Min liquidation buffer (%)
marginBuffer: number // Margin buffer (%)
capital: number // Available capital
}
const DEFAULT_CONFIG: PositionSizeConfig = {
maxRiskPerTrade: 3, // 3% max risk per trade
maxLeverage: 20, // 20x default max
minLiquidationBuffer: 30, // 30% min buffer
marginBuffer: 20, // 20% margin buffer
capital: 1000 // $1000 default
}
/**
* Calculate liquidation price
*/
export function calculateLiquidationPrice(
entryPrice: number,
leverage: number,
side: 'LONG' | 'SHORT',
marginBuffer: number = 0
): LiquidationPriceCalculation {
// Liquidation price formula:
// LONG: liquidation_price = entry_price * (1 - (1 / leverage) * (1 - margin_buffer))
// SHORT: liquidation_price = entry_price * (1 + (1 / leverage) * (1 - margin_buffer))
if (entryPrice <= 0 || leverage <= 0 || !isFinite(entryPrice) || !isFinite(leverage)) {
throw new Error('Invalid entry price or leverage')
}
const marginRate = 1 / leverage // Margin rate (e.g., 1/20 = 5% for 20x)
const adjustedMarginRate = marginRate * (1 - marginBuffer / 100) // Apply margin buffer
let liquidationPrice: number
if (side === 'LONG') {
liquidationPrice = entryPrice * (1 - adjustedMarginRate)
} else {
liquidationPrice = entryPrice * (1 + adjustedMarginRate)
}
// Calculate distance to liquidation (%)
const distance = Math.abs(liquidationPrice - entryPrice) / entryPrice * 100
// Safe distance (should be > minLiquidationBuffer)
const safeDistance = distance
const marginBufferPct = marginBuffer
return {
entryPrice: isNaN(entryPrice) ? 0 : entryPrice,
leverage: isNaN(leverage) ? 1 : leverage,
side,
liquidationPrice: isNaN(liquidationPrice) || !isFinite(liquidationPrice) ? 0 : liquidationPrice,
liquidationDistance: isNaN(distance) || !isFinite(distance) ? 0 : distance,
safeDistance: isNaN(safeDistance) || !isFinite(safeDistance) ? 0 : safeDistance,
marginBuffer: isNaN(marginBufferPct) ? 0 : marginBufferPct
}
}
/**
* Calculate safe leverage based on volatility and liquidation distance
*/
export function calculateSafeLeverage(
volatility: number, // ATR% or volatility %
minLiquidationBuffer: number = 30,
maxLeverage: number = 100,
price: number,
nearestLiquidationCluster?: number
): number {
// Safe leverage: inversely proportional to volatility
// Higher volatility = lower leverage
if (volatility <= 0 || !isFinite(volatility)) {
return 1 // Default to 1x if invalid volatility
}
// Calculate safe leverage based on volatility
// Formula: max_leverage = minLiquidationBuffer / volatility
// Example: 30% buffer, 2% volatility = 15x max leverage
const volatilitySafeLeverage = minLiquidationBuffer / volatility
// Also check liquidation cluster distance
let clusterSafeLeverage = maxLeverage
if (nearestLiquidationCluster && nearestLiquidationCluster > 0) {
const clusterDistance = Math.abs(nearestLiquidationCluster - price) / price * 100
if (clusterDistance < minLiquidationBuffer) {
// Cluster too close, reduce leverage
clusterSafeLeverage = (clusterDistance / minLiquidationBuffer) * maxLeverage
}
}
// Use the more conservative (lower) leverage
const safeLeverage = Math.min(
maxLeverage,
Math.max(1, Math.min(volatilitySafeLeverage, clusterSafeLeverage))
)
return isNaN(safeLeverage) || !isFinite(safeLeverage) ? 1 : Math.max(1, safeLeverage)
}
/**
* Calculate safe position size
*/
export function calculateSafePositionSize(
entryPrice: number,
side: 'LONG' | 'SHORT',
stopLoss: number,
leverage: number,
config: Partial<PositionSizeConfig> = {},
futuresData?: FuturesMarketData
): SafePositionSize {
const cfg = { ...DEFAULT_CONFIG, ...config }
if (entryPrice <= 0 || stopLoss <= 0 || leverage <= 0) {
throw new Error('Invalid entry price, stop loss, or leverage')
}
// Calculate risk amount
const riskAmount = cfg.capital * (cfg.maxRiskPerTrade / 100)
// Calculate price distance to stop loss (%)
const priceDistance = Math.abs(entryPrice - stopLoss) / entryPrice * 100
if (priceDistance === 0 || !isFinite(priceDistance)) {
throw new Error('Stop loss too close to entry price')
}
// Calculate position size based on risk
// Position size (USD) = risk_amount / (price_distance * leverage)
// Example: $30 risk, 2% stop loss, 20x leverage = $30 / (0.02 * 20) = $75
const positionSizeUsd = riskAmount / (priceDistance / 100) / leverage
// Calculate liquidation price
const liquidationCalc = calculateLiquidationPrice(entryPrice, leverage, side, cfg.marginBuffer)
// Check if liquidation distance is safe
const isLiquidationSafe = liquidationCalc.liquidationDistance >= cfg.minLiquidationBuffer
// Adjust leverage if needed
let recommendedLeverage = leverage
let recommendedSizeUsd = positionSizeUsd
// If liquidation distance too close, reduce leverage
if (!isLiquidationSafe && futuresData) {
// Find nearest liquidation cluster
const clusters = futuresData.liquidation.clusters || []
if (clusters.length > 0) {
const nearest = clusters.reduce((prev, curr) => {
const distPrev = Math.abs(prev.price - entryPrice)
const distCurr = Math.abs(curr.price - entryPrice)
return distPrev < distCurr ? prev : curr
})
// Calculate safe leverage based on cluster distance
// const clusterDistance = Math.abs(nearest.price - entryPrice) / entryPrice * 100
const volatility = 2 // Placeholder - would use ATR% from market data
recommendedLeverage = calculateSafeLeverage(
volatility,
cfg.minLiquidationBuffer,
cfg.maxLeverage,
entryPrice,
nearest.price
)
// Recalculate position size with new leverage
recommendedSizeUsd = riskAmount / (priceDistance / 100) / recommendedLeverage
// Recalculate liquidation with new leverage
const newLiquidationCalc = calculateLiquidationPrice(
entryPrice,
recommendedLeverage,
side,
cfg.marginBuffer
)
// Check if new leverage is safe
if (newLiquidationCalc.liquidationDistance >= cfg.minLiquidationBuffer) {
recommendedSizeUsd = positionSizeUsd * (recommendedLeverage / leverage) // Adjust size proportionally
}
}
}
// Cap position size at max available capital
const maxSizeUsd = cfg.capital * 0.2 // Max 20% of capital per position
recommendedSizeUsd = Math.min(recommendedSizeUsd, maxSizeUsd)
// Determine risk level
let riskLevel: 'low' | 'medium' | 'high' | 'extreme' = 'low'
if (recommendedLeverage >= 50) {
riskLevel = 'extreme'
} else if (recommendedLeverage >= 30) {
riskLevel = 'high'
} else if (recommendedLeverage >= 15) {
riskLevel = 'medium'
}
// Generate reasoning
const reasoning = `Position size: $${recommendedSizeUsd.toFixed(2)}, Leverage: ${recommendedLeverage}x, ` +
`Liquidation distance: ${liquidationCalc.liquidationDistance.toFixed(1)}%, ` +
`Risk: ${(priceDistance * recommendedLeverage).toFixed(1)}%, ` +
`Risk level: ${riskLevel}`
return {
maxSizeUsd: isNaN(maxSizeUsd) ? 0 : maxSizeUsd,
recommendedSizeUsd: isNaN(recommendedSizeUsd) || !isFinite(recommendedSizeUsd) ? 0 : recommendedSizeUsd,
recommendedLeverage: isNaN(recommendedLeverage) || !isFinite(recommendedLeverage) ? 1 : recommendedLeverage,
liquidationDistance: liquidationCalc.liquidationDistance,
marginBuffer: cfg.marginBuffer,
riskLevel,
reasoning
}
}
/**
* Validate position safety
*/
export function validatePositionSafety(
entryPrice: number,
leverage: number,
side: 'LONG' | 'SHORT',
minLiquidationBuffer: number = 30,
marginBuffer: number = 20,
nearestLiquidationCluster?: number
): { safe: boolean; reason?: string } {
try {
const liquidationCalc = calculateLiquidationPrice(entryPrice, leverage, side, marginBuffer)
// Check liquidation distance
if (liquidationCalc.liquidationDistance < minLiquidationBuffer) {
return {
safe: false,
reason: `Liquidation distance (${liquidationCalc.liquidationDistance.toFixed(1)}%) below minimum (${minLiquidationBuffer}%)`
}
}
// Check liquidation cluster proximity
if (nearestLiquidationCluster && nearestLiquidationCluster > 0) {
const clusterDistance = Math.abs(nearestLiquidationCluster - entryPrice) / entryPrice * 100
if (clusterDistance < minLiquidationBuffer) {
return {
safe: false,
reason: `Nearest liquidation cluster (${clusterDistance.toFixed(1)}% away) too close (minimum: ${minLiquidationBuffer}%)`
}
}
}
return { safe: true }
} catch (error: any) {
return {
safe: false,
reason: error.message || 'Unknown error validating position safety'
}
}
}