airmicrodrip / liquidation_system.py
josephrw's picture
Upload folder using huggingface_hub
e9d4f6a verified
Raw
History Blame Contribute Delete
11.6 kB
#!/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))