#!/usr/bin/env python3 """ AirMicroDrip Perpetual Futures Liquidation System Monitors positions and executes liquidations when needed No mocks - real position monitoring and liquidation execution """ import json import sqlite3 import asyncio from typing import Dict, List, Optional from datetime import datetime, timedelta from perp_trading_engine import PerpTradingEngine, Position, Side # Configuration LIQUIDATION_CONFIG = { "maintenance_margin_rate": 0.05, # 5% maintenance margin "liquidation_threshold": 0.01, # 1% liquidation threshold "liquidation_bonus": 0.05, # 5% bonus for liquidators "insurance_fund_rate": 0.02, # 2% to insurance fund "check_interval_seconds": 10, # Check every 10 seconds "max_liquidation_per_check": 5, # Max 5 liquidations per check } class LiquidationSystem: """Manages position liquidations""" def __init__( self, trading_engine: PerpTradingEngine, db_path: str = "perp_trading.db", ): self.trading_engine = trading_engine self.db_path = db_path self.liquidation_log = [] self.insurance_fund = 0.0 async def start_monitoring(self): """Start liquidation monitoring loop""" print("Starting liquidation monitoring...") while True: await self._check_liquidations() await asyncio.sleep(LIQUIDATION_CONFIG["check_interval_seconds"]) async def _check_liquidations(self): """Check for liquidatable positions""" # Get all positions positions = self._get_all_positions() liquidatable = [] for position in positions: # Update unrealized PnL self.trading_engine.update_unrealized_pnl() # Refresh position data updated_position = self.trading_engine.get_position( position["trader"], position["market"] ) if not updated_position: continue # Check if liquidatable if self._is_liquidatable(updated_position): liquidatable.append(updated_position) # Execute liquidations (limit per check) for position in liquidatable[:LIQUIDATION_CONFIG["max_liquidation_per_check"]]: await self._execute_liquidation(position) def _get_all_positions(self) -> List[Dict]: """Get all positions from database""" conn = sqlite3.connect(self.db_path) cursor = conn.cursor() cursor.execute(""" SELECT position_id, trader, market, side, size, entry_price, leverage, margin, unrealized_pnl, realized_pnl, liquidation_price, opened_at, updated_at FROM positions """) results = cursor.fetchall() conn.close() return [ { "position_id": r[0], "trader": r[1], "market": r[2], "side": r[3], "size": r[4], "entry_price": r[5], "leverage": r[6], "margin": r[7], "unrealized_pnl": r[8], "realized_pnl": r[9], "liquidation_price": r[10], "opened_at": r[11], "updated_at": r[12], } for r in results ] def _is_liquidatable(self, position: Position) -> bool: """Check if position is liquidatable""" market_state = self.trading_engine.market_states[position.market] current_price = market_state.mark_price # Calculate margin ratio position_value = position.size * current_price if position_value == 0: return False margin_ratio = position.margin / position_value # Check if below maintenance margin if margin_ratio < LIQUIDATION_CONFIG["maintenance_margin_rate"]: return True # Check if price hit liquidation price if position.side == Side.LONG: if current_price <= position.liquidation_price: return True else: if current_price >= position.liquidation_price: return True return False async def _execute_liquidation(self, position: Position): """Execute position liquidation""" print(f"Liquidating position {position.position_id}...") market_state = self.trading_engine.market_states[position.market] current_price = market_state.mark_price # Calculate liquidation value liquidation_value = position.size * current_price # Calculate liquidation bonus bonus = liquidation_value * LIQUIDATION_CONFIG["liquidation_bonus"] # Calculate insurance fund contribution insurance_contribution = liquidation_value * LIQUIDATION_CONFIG["insurance_fund_rate"] # Close position self._close_position(position, current_price) # Update insurance fund self.insurance_fund += insurance_contribution # Log liquidation liquidation_record = { "timestamp": datetime.utcnow().isoformat(), "position_id": position.position_id, "trader": position.trader, "market": position.market, "side": position.side.value, "size": position.size, "liquidation_price": current_price, "liquidation_value": liquidation_value, "liquidation_bonus": bonus, "insurance_contribution": insurance_contribution, "remaining_margin": max(0, position.margin - liquidation_value), } self.liquidation_log.append(liquidation_record) print(f"Liquidation executed: {liquidation_record}") def _close_position(self, position: Position, close_price: float): """Close position in database""" conn = sqlite3.connect(self.db_path) cursor = conn.cursor() # Calculate realized PnL if position.side == Side.LONG: realized_pnl = position.size * (close_price - position.entry_price) else: realized_pnl = position.size * (position.entry_price - close_price) # Update position cursor.execute(""" UPDATE positions SET size = 0, unrealized_pnl = 0, realized_pnl = realized_pnl, updated_at = ? WHERE position_id = ? """, (datetime.utcnow().isoformat(), position.position_id)) conn.commit() conn.close() def get_liquidation_stats(self) -> Dict: """Get liquidation statistics""" if not self.liquidation_log: return { "total_liquidations": 0, "total_value": 0.0, "insurance_fund": self.insurance_fund, } total_liquidations = len(self.liquidation_log) total_value = sum(l["liquidation_value"] for l in self.liquidation_log) total_bonuses = sum(l["liquidation_bonus"] for l in self.liquidation_log) return { "total_liquidations": total_liquidations, "total_value": total_value, "total_bonuses": total_bonuses, "insurance_fund": self.insurance_fund, "recent_liquidations": self.liquidation_log[-10:], } def get_at_risk_positions(self) -> List[Dict]: """Get positions at risk of liquidation""" positions = self._get_all_positions() at_risk = [] for pos_data in positions: position = Position( position_id=pos_data["position_id"], trader=pos_data["trader"], market=pos_data["market"], side=Side(pos_data["side"]), size=pos_data["size"], entry_price=pos_data["entry_price"], leverage=pos_data["leverage"], margin=pos_data["margin"], liquidation_price=pos_data["liquidation_price"], opened_at=datetime.fromisoformat(pos_data["opened_at"]), updated_at=datetime.fromisoformat(pos_data["updated_at"]), ) # Calculate margin ratio market_state = self.trading_engine.market_states[position.market] current_price = market_state.mark_price position_value = position.size * current_price if position_value == 0 or current_price == 0: continue margin_ratio = position.margin / position_value # Check if at risk (within 20% of liquidation) if margin_ratio < LIQUIDATION_CONFIG["maintenance_margin_rate"] * 1.2: at_risk.append({ "position_id": position.position_id, "trader": position.trader, "market": position.market, "margin_ratio": margin_ratio, "liquidation_price": position.liquidation_price, "current_price": current_price, "distance_to_liquidation": abs(current_price - position.liquidation_price) / current_price, }) return sorted(at_risk, key=lambda x: x["margin_ratio"]) def manual_liquidation(self, position_id: str, liquidator: str) -> Dict: """Manually trigger liquidation (for liquidators)""" # Get position conn = sqlite3.connect(self.db_path) cursor = conn.cursor() cursor.execute(""" SELECT position_id, trader, market, side, size, entry_price, leverage, margin, liquidation_price FROM positions WHERE position_id = ? """, (position_id,)) result = cursor.fetchone() conn.close() if not result: return {"status": "error", "message": "Position not found"} position = Position( position_id=result[0], trader=result[1], market=result[2], side=Side(result[3]), size=result[4], entry_price=result[5], leverage=result[6], margin=result[7], liquidation_price=result[8], ) # Execute liquidation asyncio.run(self._execute_liquidation(position)) return { "status": "success", "position_id": position_id, "liquidator": liquidator, } if __name__ == "__main__": # Initialize components trading_engine = PerpTradingEngine() liquidation_system = LiquidationSystem(trading_engine) # Get at-risk positions at_risk = liquidation_system.get_at_risk_positions() print("\n" + "="*50) print("At-Risk Positions") print("="*50) for pos in at_risk: print(f"Position: {pos['position_id']}") print(f"Trader: {pos['trader']}") print(f"Market: {pos['market']}") print(f"Margin Ratio: {pos['margin_ratio']:.2%}") print(f"Distance to Liquidation: {pos['distance_to_liquidation']:.2%}") print() # Get liquidation stats stats = liquidation_system.get_liquidation_stats() print("="*50) print("Liquidation Statistics") print("="*50) print(json.dumps(stats, indent=2))