|
@@ -10,7 +10,6 @@ import os
|
|
|
import logging
|
|
|
from datetime import datetime, timedelta, timezone
|
|
|
from typing import Dict, List, Any, Optional, Tuple, Union
|
|
|
-import numpy as np
|
|
|
import math
|
|
|
from collections import defaultdict
|
|
|
import uuid
|
|
@@ -115,6 +114,13 @@ class TradingStats:
|
|
|
-- P&L tracking
|
|
|
realized_pnl REAL DEFAULT 0,
|
|
|
unrealized_pnl REAL DEFAULT 0,
|
|
|
+ mark_price REAL DEFAULT 0,
|
|
|
+ position_value REAL DEFAULT NULL,
|
|
|
+
|
|
|
+ -- Risk Info from Exchange
|
|
|
+ liquidation_price REAL DEFAULT NULL,
|
|
|
+ margin_used REAL DEFAULT NULL,
|
|
|
+ leverage REAL DEFAULT NULL,
|
|
|
|
|
|
-- Timestamps
|
|
|
position_opened_at TEXT,
|
|
@@ -1501,16 +1507,16 @@ class TradingStats:
|
|
|
if abs(exchange_position_size) < 1e-8 and not has_open_orders:
|
|
|
# Calculate realized P&L based on position side
|
|
|
position_side = current_trade['position_side']
|
|
|
- entry_price = current_trade['entry_price']
|
|
|
- current_amount = current_trade['current_position_size']
|
|
|
+ entry_price_db = current_trade['entry_price'] # entry_price from db
|
|
|
+ # current_amount = current_trade['current_position_size'] # Not directly used for PNL calc here
|
|
|
|
|
|
# For a closed position, we need to calculate final P&L
|
|
|
# This would typically come from the closing trade, but for confirmation we estimate
|
|
|
- estimated_pnl = current_trade.get('realized_pnl', 0)
|
|
|
+ estimated_pnl = current_trade.get('realized_pnl', 0) # Use existing realized_pnl if any
|
|
|
|
|
|
success = self.update_trade_position_closed(
|
|
|
lifecycle_id,
|
|
|
- entry_price, # Using entry price as estimate since position is confirmed closed
|
|
|
+ entry_price_db, # Using entry price from DB as estimate since position is confirmed closed
|
|
|
estimated_pnl,
|
|
|
"exchange_confirmed_closed"
|
|
|
)
|
|
@@ -1525,3 +1531,81 @@ class TradingStats:
|
|
|
except Exception as e:
|
|
|
logger.error(f"❌ Error confirming position with exchange: {e}")
|
|
|
return False
|
|
|
+
|
|
|
+ def update_trade_market_data(self,
|
|
|
+ trade_lifecycle_id: str,
|
|
|
+ unrealized_pnl: Optional[float] = None,
|
|
|
+ mark_price: Optional[float] = None,
|
|
|
+ current_position_size: Optional[float] = None,
|
|
|
+ entry_price: Optional[float] = None,
|
|
|
+ liquidation_price: Optional[float] = None,
|
|
|
+ margin_used: Optional[float] = None,
|
|
|
+ leverage: Optional[float] = None,
|
|
|
+ position_value: Optional[float] = None) -> bool:
|
|
|
+ """Update market-related data for an open trade lifecycle.
|
|
|
+ Only updates fields for which a non-None value is provided.
|
|
|
+ """
|
|
|
+ try:
|
|
|
+ updates = []
|
|
|
+ params = []
|
|
|
+
|
|
|
+ if unrealized_pnl is not None:
|
|
|
+ updates.append("unrealized_pnl = ?")
|
|
|
+ params.append(unrealized_pnl)
|
|
|
+ if mark_price is not None:
|
|
|
+ updates.append("mark_price = ?")
|
|
|
+ params.append(mark_price)
|
|
|
+ if current_position_size is not None:
|
|
|
+ updates.append("current_position_size = ?")
|
|
|
+ params.append(current_position_size)
|
|
|
+ if entry_price is not None: # If exchange provides updated avg entry
|
|
|
+ updates.append("entry_price = ?")
|
|
|
+ params.append(entry_price)
|
|
|
+ if liquidation_price is not None:
|
|
|
+ updates.append("liquidation_price = ?")
|
|
|
+ params.append(liquidation_price)
|
|
|
+ if margin_used is not None:
|
|
|
+ updates.append("margin_used = ?")
|
|
|
+ params.append(margin_used)
|
|
|
+ if leverage is not None:
|
|
|
+ updates.append("leverage = ?")
|
|
|
+ params.append(leverage)
|
|
|
+ if position_value is not None:
|
|
|
+ updates.append("position_value = ?")
|
|
|
+ params.append(position_value)
|
|
|
+
|
|
|
+ if not updates:
|
|
|
+ logger.debug(f"No market data fields provided to update for lifecycle {trade_lifecycle_id}.")
|
|
|
+ return True # No update needed, not an error
|
|
|
+
|
|
|
+ timestamp = datetime.now(timezone.utc).isoformat()
|
|
|
+ updates.append("updated_at = ?")
|
|
|
+ params.append(timestamp)
|
|
|
+
|
|
|
+ set_clause = ", ".join(updates)
|
|
|
+ query = f"""
|
|
|
+ UPDATE trades
|
|
|
+ SET {set_clause}
|
|
|
+ WHERE trade_lifecycle_id = ? AND status = 'position_opened'
|
|
|
+ """
|
|
|
+ params.append(trade_lifecycle_id)
|
|
|
+
|
|
|
+ # Use the class's own connection self.conn
|
|
|
+ cursor = self.conn.cursor()
|
|
|
+ cursor.execute(query, tuple(params))
|
|
|
+ self.conn.commit()
|
|
|
+ updated_rows = cursor.rowcount
|
|
|
+
|
|
|
+ if updated_rows > 0:
|
|
|
+ logger.debug(f"💹 Updated market data for lifecycle {trade_lifecycle_id}. Fields: {updates}")
|
|
|
+ return True
|
|
|
+ else:
|
|
|
+ # This might happen if the lifecycle ID doesn't exist or status is not 'position_opened'
|
|
|
+ # logger.warning(f"⚠️ No trade found or not in 'position_opened' state for lifecycle {trade_lifecycle_id} to update market data.")
|
|
|
+ return False # Not necessarily an error
|
|
|
+
|
|
|
+ except Exception as e:
|
|
|
+ logger.error(f"❌ Error updating market data for trade lifecycle {trade_lifecycle_id}: {e}")
|
|
|
+ return False
|
|
|
+
|
|
|
+ # --- End Trade Lifecycle Management ---
|