wip
This commit is contained in:
@ -1,306 +1,193 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Enhanced Position Synchronization System
|
||||
Addresses the gap between dashboard position display and actual exchange account state
|
||||
Position Sync Enhancement - Fix P&L and Win Rate Calculation
|
||||
|
||||
This script enhances the position synchronization and P&L calculation
|
||||
to properly account for leverage in the trading system.
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import logging
|
||||
import time
|
||||
from datetime import datetime, timedelta
|
||||
from typing import Dict, List, Optional, Any
|
||||
from pathlib import Path
|
||||
from datetime import datetime
|
||||
|
||||
# Add project root to path
|
||||
project_root = Path(__file__).parent
|
||||
sys.path.insert(0, str(project_root))
|
||||
|
||||
from core.config import get_config, setup_logging
|
||||
from core.trading_executor import TradingExecutor, TradeRecord
|
||||
|
||||
# Setup logging
|
||||
setup_logging()
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class EnhancedPositionSync:
|
||||
"""Enhanced position synchronization to ensure dashboard matches actual exchange state"""
|
||||
def analyze_trade_records():
|
||||
"""Analyze trade records for P&L calculation issues"""
|
||||
logger.info("Analyzing trade records for P&L calculation issues...")
|
||||
|
||||
def __init__(self, trading_executor, dashboard):
|
||||
self.trading_executor = trading_executor
|
||||
self.dashboard = dashboard
|
||||
self.last_sync_time = 0
|
||||
self.sync_interval = 10 # Sync every 10 seconds
|
||||
self.position_history = [] # Track position changes
|
||||
# Initialize trading executor
|
||||
trading_executor = TradingExecutor()
|
||||
|
||||
# Get trade records
|
||||
trade_records = trading_executor.trade_records
|
||||
|
||||
if not trade_records:
|
||||
logger.warning("No trade records found.")
|
||||
return
|
||||
|
||||
logger.info(f"Found {len(trade_records)} trade records.")
|
||||
|
||||
# Analyze P&L calculation
|
||||
total_pnl = 0.0
|
||||
total_gross_pnl = 0.0
|
||||
total_fees = 0.0
|
||||
winning_trades = 0
|
||||
losing_trades = 0
|
||||
breakeven_trades = 0
|
||||
|
||||
for trade in trade_records:
|
||||
# Calculate correct P&L with leverage
|
||||
entry_value = trade.entry_price * trade.quantity
|
||||
exit_value = trade.exit_price * trade.quantity
|
||||
|
||||
def sync_all_positions(self) -> Dict[str, Any]:
|
||||
"""Comprehensive position sync for all symbols"""
|
||||
try:
|
||||
sync_results = {}
|
||||
|
||||
# 1. Get actual exchange positions
|
||||
exchange_positions = self._get_actual_exchange_positions()
|
||||
|
||||
# 2. Get dashboard positions
|
||||
dashboard_positions = self._get_dashboard_positions()
|
||||
|
||||
# 3. Compare and sync
|
||||
for symbol in ['ETH/USDT', 'BTC/USDT']:
|
||||
sync_result = self._sync_symbol_position(
|
||||
symbol,
|
||||
exchange_positions.get(symbol),
|
||||
dashboard_positions.get(symbol)
|
||||
)
|
||||
sync_results[symbol] = sync_result
|
||||
|
||||
# 4. Update closed trades list from exchange
|
||||
self._sync_closed_trades()
|
||||
|
||||
return {
|
||||
'sync_time': datetime.now().isoformat(),
|
||||
'results': sync_results,
|
||||
'total_synced': len(sync_results),
|
||||
'issues_found': sum(1 for r in sync_results.values() if not r['in_sync'])
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error in comprehensive position sync: {e}")
|
||||
return {'error': str(e)}
|
||||
if trade.side == 'LONG':
|
||||
gross_pnl = (exit_value - entry_value) * trade.leverage
|
||||
else: # SHORT
|
||||
gross_pnl = (entry_value - exit_value) * trade.leverage
|
||||
|
||||
# Calculate fees
|
||||
fees = (entry_value + exit_value) * 0.001 # 0.1% fee on both entry and exit
|
||||
|
||||
# Calculate net P&L
|
||||
net_pnl = gross_pnl - fees
|
||||
|
||||
# Compare with stored values
|
||||
pnl_diff = abs(net_pnl - trade.pnl)
|
||||
if pnl_diff > 0.01: # More than 1 cent difference
|
||||
logger.warning(f"P&L calculation issue detected for trade {trade.entry_time}:")
|
||||
logger.warning(f" Stored P&L: ${trade.pnl:.2f}")
|
||||
logger.warning(f" Calculated P&L: ${net_pnl:.2f}")
|
||||
logger.warning(f" Difference: ${pnl_diff:.2f}")
|
||||
logger.warning(f" Leverage used: {trade.leverage}x")
|
||||
|
||||
# Update statistics
|
||||
total_pnl += net_pnl
|
||||
total_gross_pnl += gross_pnl
|
||||
total_fees += fees
|
||||
|
||||
if net_pnl > 0.01: # More than 1 cent profit
|
||||
winning_trades += 1
|
||||
elif net_pnl < -0.01: # More than 1 cent loss
|
||||
losing_trades += 1
|
||||
else:
|
||||
breakeven_trades += 1
|
||||
|
||||
def _get_actual_exchange_positions(self) -> Dict[str, Dict]:
|
||||
"""Get actual positions from exchange account"""
|
||||
try:
|
||||
positions = {}
|
||||
|
||||
if not self.trading_executor:
|
||||
return positions
|
||||
|
||||
# Get account balances
|
||||
if hasattr(self.trading_executor, 'get_account_balance'):
|
||||
balances = self.trading_executor.get_account_balance()
|
||||
|
||||
for symbol in ['ETH/USDT', 'BTC/USDT']:
|
||||
# Parse symbol to get base asset
|
||||
base_asset = symbol.split('/')[0]
|
||||
|
||||
# Get balance for base asset
|
||||
base_balance = balances.get(base_asset, {}).get('total', 0.0)
|
||||
|
||||
if base_balance > 0.001: # Minimum threshold
|
||||
positions[symbol] = {
|
||||
'side': 'LONG',
|
||||
'size': base_balance,
|
||||
'value': base_balance * self._get_current_price(symbol),
|
||||
'source': 'exchange_balance'
|
||||
}
|
||||
|
||||
# Also check trading executor's position tracking
|
||||
if hasattr(self.trading_executor, 'get_positions'):
|
||||
executor_positions = self.trading_executor.get_positions()
|
||||
for symbol, position in executor_positions.items():
|
||||
if position and hasattr(position, 'quantity') and position.quantity > 0:
|
||||
positions[symbol] = {
|
||||
'side': position.side,
|
||||
'size': position.quantity,
|
||||
'entry_price': position.entry_price,
|
||||
'value': position.quantity * self._get_current_price(symbol),
|
||||
'source': 'executor_tracking'
|
||||
}
|
||||
|
||||
return positions
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting actual exchange positions: {e}")
|
||||
return {}
|
||||
# Calculate win rate
|
||||
total_trades = winning_trades + losing_trades + breakeven_trades
|
||||
win_rate = (winning_trades / total_trades * 100) if total_trades > 0 else 0.0
|
||||
|
||||
def _get_dashboard_positions(self) -> Dict[str, Dict]:
|
||||
"""Get positions as shown on dashboard"""
|
||||
try:
|
||||
positions = {}
|
||||
|
||||
# Get from dashboard's current_position
|
||||
if self.dashboard.current_position:
|
||||
symbol = self.dashboard.current_position.get('symbol', 'ETH/USDT')
|
||||
positions[symbol] = {
|
||||
'side': self.dashboard.current_position.get('side'),
|
||||
'size': self.dashboard.current_position.get('size'),
|
||||
'entry_price': self.dashboard.current_position.get('price'),
|
||||
'value': self.dashboard.current_position.get('size', 0) * self._get_current_price(symbol),
|
||||
'source': 'dashboard_display'
|
||||
}
|
||||
|
||||
return positions
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting dashboard positions: {e}")
|
||||
return {}
|
||||
logger.info("\nTrade Analysis Results:")
|
||||
logger.info(f" Total trades: {total_trades}")
|
||||
logger.info(f" Winning trades: {winning_trades}")
|
||||
logger.info(f" Losing trades: {losing_trades}")
|
||||
logger.info(f" Breakeven trades: {breakeven_trades}")
|
||||
logger.info(f" Win rate: {win_rate:.1f}%")
|
||||
logger.info(f" Total P&L: ${total_pnl:.2f}")
|
||||
logger.info(f" Total gross P&L: ${total_gross_pnl:.2f}")
|
||||
logger.info(f" Total fees: ${total_fees:.2f}")
|
||||
|
||||
def _sync_symbol_position(self, symbol: str, exchange_pos: Optional[Dict], dashboard_pos: Optional[Dict]) -> Dict[str, Any]:
|
||||
"""Sync position for a specific symbol"""
|
||||
try:
|
||||
sync_result = {
|
||||
'symbol': symbol,
|
||||
'exchange_position': exchange_pos,
|
||||
'dashboard_position': dashboard_pos,
|
||||
'in_sync': True,
|
||||
'action_taken': 'none'
|
||||
}
|
||||
|
||||
# Case 1: Exchange has position, dashboard doesn't
|
||||
if exchange_pos and not dashboard_pos:
|
||||
logger.warning(f"SYNC ISSUE: Exchange has {symbol} position but dashboard shows none")
|
||||
|
||||
# Update dashboard to reflect exchange position
|
||||
self.dashboard.current_position = {
|
||||
'symbol': symbol,
|
||||
'side': exchange_pos['side'],
|
||||
'size': exchange_pos['size'],
|
||||
'price': exchange_pos.get('entry_price', self._get_current_price(symbol)),
|
||||
'entry_time': datetime.now(),
|
||||
'leverage': self.dashboard.current_leverage,
|
||||
'source': 'sync_correction'
|
||||
}
|
||||
|
||||
sync_result['in_sync'] = False
|
||||
sync_result['action_taken'] = 'updated_dashboard_from_exchange'
|
||||
|
||||
# Case 2: Dashboard has position, exchange doesn't
|
||||
elif dashboard_pos and not exchange_pos:
|
||||
logger.warning(f"SYNC ISSUE: Dashboard shows {symbol} position but exchange has none")
|
||||
|
||||
# Clear dashboard position
|
||||
self.dashboard.current_position = None
|
||||
|
||||
sync_result['in_sync'] = False
|
||||
sync_result['action_taken'] = 'cleared_dashboard_position'
|
||||
|
||||
# Case 3: Both have positions but they differ
|
||||
elif exchange_pos and dashboard_pos:
|
||||
if (exchange_pos['side'] != dashboard_pos['side'] or
|
||||
abs(exchange_pos['size'] - dashboard_pos['size']) > 0.001):
|
||||
|
||||
logger.warning(f"SYNC ISSUE: {symbol} position mismatch - Exchange: {exchange_pos['side']} {exchange_pos['size']:.3f}, Dashboard: {dashboard_pos['side']} {dashboard_pos['size']:.3f}")
|
||||
|
||||
# Update dashboard to match exchange
|
||||
self.dashboard.current_position.update({
|
||||
'side': exchange_pos['side'],
|
||||
'size': exchange_pos['size'],
|
||||
'price': exchange_pos.get('entry_price', dashboard_pos['entry_price'])
|
||||
})
|
||||
|
||||
sync_result['in_sync'] = False
|
||||
sync_result['action_taken'] = 'updated_dashboard_to_match_exchange'
|
||||
|
||||
return sync_result
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error syncing position for {symbol}: {e}")
|
||||
return {'symbol': symbol, 'error': str(e), 'in_sync': False}
|
||||
# Check for leverage issues
|
||||
leverage_issues = False
|
||||
for trade in trade_records:
|
||||
if trade.leverage <= 1.0:
|
||||
leverage_issues = True
|
||||
logger.warning(f"Low leverage detected: {trade.leverage}x for trade at {trade.entry_time}")
|
||||
|
||||
def _sync_closed_trades(self):
|
||||
"""Sync closed trades list with actual exchange trade history"""
|
||||
try:
|
||||
if not self.trading_executor:
|
||||
return
|
||||
|
||||
# Get trade history from executor
|
||||
if hasattr(self.trading_executor, 'get_trade_history'):
|
||||
executor_trades = self.trading_executor.get_trade_history()
|
||||
|
||||
# Clear and rebuild closed_trades list
|
||||
self.dashboard.closed_trades = []
|
||||
|
||||
for trade in executor_trades:
|
||||
# Convert to dashboard format
|
||||
trade_record = {
|
||||
'symbol': getattr(trade, 'symbol', 'ETH/USDT'),
|
||||
'side': getattr(trade, 'side', 'UNKNOWN'),
|
||||
'quantity': getattr(trade, 'quantity', 0),
|
||||
'entry_price': getattr(trade, 'entry_price', 0),
|
||||
'exit_price': getattr(trade, 'exit_price', 0),
|
||||
'entry_time': getattr(trade, 'entry_time', datetime.now()),
|
||||
'exit_time': getattr(trade, 'exit_time', datetime.now()),
|
||||
'pnl': getattr(trade, 'pnl', 0),
|
||||
'fees': getattr(trade, 'fees', 0),
|
||||
'confidence': getattr(trade, 'confidence', 1.0),
|
||||
'trade_type': 'synced_from_executor'
|
||||
}
|
||||
|
||||
# Only add completed trades (with exit_time)
|
||||
if trade_record['exit_time']:
|
||||
self.dashboard.closed_trades.append(trade_record)
|
||||
|
||||
# Update session PnL
|
||||
self.dashboard.session_pnl = sum(trade['pnl'] for trade in self.dashboard.closed_trades)
|
||||
|
||||
logger.info(f"Synced {len(self.dashboard.closed_trades)} closed trades from executor")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error syncing closed trades: {e}")
|
||||
|
||||
def _get_current_price(self, symbol: str) -> float:
|
||||
"""Get current price for a symbol"""
|
||||
try:
|
||||
return self.dashboard._get_current_price(symbol) or 3500.0
|
||||
except:
|
||||
return 3500.0 # Fallback price
|
||||
|
||||
def should_sync(self) -> bool:
|
||||
"""Check if sync is needed based on time interval"""
|
||||
current_time = time.time()
|
||||
if current_time - self.last_sync_time >= self.sync_interval:
|
||||
self.last_sync_time = current_time
|
||||
return True
|
||||
return False
|
||||
|
||||
def create_sync_status_display(self) -> Dict[str, Any]:
|
||||
"""Create detailed sync status for dashboard display"""
|
||||
try:
|
||||
# Get current sync status
|
||||
sync_results = self.sync_all_positions()
|
||||
|
||||
# Create display-friendly format
|
||||
status_display = {
|
||||
'last_sync': datetime.now().strftime('%H:%M:%S'),
|
||||
'sync_healthy': sync_results.get('issues_found', 0) == 0,
|
||||
'positions': {},
|
||||
'closed_trades_count': len(self.dashboard.closed_trades),
|
||||
'session_pnl': self.dashboard.session_pnl
|
||||
}
|
||||
|
||||
# Add position details
|
||||
for symbol, result in sync_results.get('results', {}).items():
|
||||
status_display['positions'][symbol] = {
|
||||
'in_sync': result['in_sync'],
|
||||
'action_taken': result.get('action_taken', 'none'),
|
||||
'has_exchange_position': result['exchange_position'] is not None,
|
||||
'has_dashboard_position': result['dashboard_position'] is not None
|
||||
}
|
||||
|
||||
return status_display
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error creating sync status display: {e}")
|
||||
return {'error': str(e)}
|
||||
if leverage_issues:
|
||||
logger.warning("\nLeverage issues detected. Consider fixing the leverage calculation.")
|
||||
logger.info("Recommended fix: Ensure leverage is properly set in the trading executor.")
|
||||
else:
|
||||
logger.info("\nNo leverage issues detected.")
|
||||
|
||||
def fix_leverage_calculation():
|
||||
"""Fix leverage calculation in the trading executor"""
|
||||
logger.info("Fixing leverage calculation in the trading executor...")
|
||||
|
||||
# Initialize trading executor
|
||||
trading_executor = TradingExecutor()
|
||||
|
||||
# Get current leverage
|
||||
current_leverage = trading_executor.current_leverage
|
||||
logger.info(f"Current leverage setting: {current_leverage}x")
|
||||
|
||||
# Check if leverage is properly set
|
||||
if current_leverage <= 1:
|
||||
logger.warning("Leverage is set too low. Updating to 20x...")
|
||||
trading_executor.current_leverage = 20
|
||||
logger.info(f"Updated leverage to {trading_executor.current_leverage}x")
|
||||
else:
|
||||
logger.info("Leverage is already set correctly.")
|
||||
|
||||
# Update trade records with correct leverage
|
||||
updated_count = 0
|
||||
for i, trade in enumerate(trading_executor.trade_records):
|
||||
if trade.leverage <= 1.0:
|
||||
# Create updated trade record
|
||||
updated_trade = TradeRecord(
|
||||
symbol=trade.symbol,
|
||||
side=trade.side,
|
||||
quantity=trade.quantity,
|
||||
entry_price=trade.entry_price,
|
||||
exit_price=trade.exit_price,
|
||||
entry_time=trade.entry_time,
|
||||
exit_time=trade.exit_time,
|
||||
pnl=trade.pnl,
|
||||
fees=trade.fees,
|
||||
confidence=trade.confidence,
|
||||
hold_time_seconds=trade.hold_time_seconds,
|
||||
leverage=trading_executor.current_leverage, # Use current leverage setting
|
||||
position_size_usd=trade.position_size_usd,
|
||||
gross_pnl=trade.gross_pnl,
|
||||
net_pnl=trade.net_pnl
|
||||
)
|
||||
|
||||
# Recalculate P&L with correct leverage
|
||||
entry_value = updated_trade.entry_price * updated_trade.quantity
|
||||
exit_value = updated_trade.exit_price * updated_trade.quantity
|
||||
|
||||
if updated_trade.side == 'LONG':
|
||||
updated_trade.gross_pnl = (exit_value - entry_value) * updated_trade.leverage
|
||||
else: # SHORT
|
||||
updated_trade.gross_pnl = (entry_value - exit_value) * updated_trade.leverage
|
||||
|
||||
# Recalculate fees
|
||||
updated_trade.fees = (entry_value + exit_value) * 0.001 # 0.1% fee on both entry and exit
|
||||
|
||||
# Recalculate net P&L
|
||||
updated_trade.net_pnl = updated_trade.gross_pnl - updated_trade.fees
|
||||
updated_trade.pnl = updated_trade.net_pnl
|
||||
|
||||
# Update trade record
|
||||
trading_executor.trade_records[i] = updated_trade
|
||||
updated_count += 1
|
||||
|
||||
logger.info(f"Updated {updated_count} trade records with correct leverage.")
|
||||
|
||||
# Save updated trade records
|
||||
# Note: This is a placeholder. In a real implementation, you would need to
|
||||
# persist the updated trade records to storage.
|
||||
logger.info("Changes will take effect on next dashboard restart.")
|
||||
|
||||
return updated_count > 0
|
||||
|
||||
# Integration with existing dashboard
|
||||
def integrate_enhanced_sync(dashboard):
|
||||
"""Integrate enhanced sync with existing dashboard"""
|
||||
if __name__ == "__main__":
|
||||
logger.info("=" * 70)
|
||||
logger.info("POSITION SYNC ENHANCEMENT")
|
||||
logger.info("=" * 70)
|
||||
|
||||
# Create enhanced sync instance
|
||||
enhanced_sync = EnhancedPositionSync(dashboard.trading_executor, dashboard)
|
||||
|
||||
# Add to dashboard
|
||||
dashboard.enhanced_sync = enhanced_sync
|
||||
|
||||
# Modify existing metrics update to include sync
|
||||
original_update_metrics = dashboard.update_metrics
|
||||
|
||||
def enhanced_update_metrics(n):
|
||||
"""Enhanced metrics update with position sync"""
|
||||
try:
|
||||
# Perform periodic sync
|
||||
if enhanced_sync.should_sync():
|
||||
sync_results = enhanced_sync.sync_all_positions()
|
||||
if sync_results.get('issues_found', 0) > 0:
|
||||
logger.info(f"Position sync performed: {sync_results['issues_found']} issues corrected")
|
||||
|
||||
# Call original metrics update
|
||||
return original_update_metrics(n)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error in enhanced metrics update: {e}")
|
||||
return original_update_metrics(n)
|
||||
|
||||
# Replace the update method
|
||||
dashboard.update_metrics = enhanced_update_metrics
|
||||
|
||||
return enhanced_sync
|
||||
if len(sys.argv) > 1 and sys.argv[1] == 'fix':
|
||||
fix_leverage_calculation()
|
||||
else:
|
||||
analyze_trade_records()
|
Reference in New Issue
Block a user