Spaces:
Paused
Paused
| #!/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)) | |