limit max positions
This commit is contained in:
@ -927,11 +927,9 @@ class TradingOrchestrator:
|
||||
try:
|
||||
current_time = datetime.now()
|
||||
|
||||
# Check if enough time has passed since last decision
|
||||
if symbol in self.last_decision_time:
|
||||
time_since_last = (current_time - self.last_decision_time[symbol]).total_seconds()
|
||||
if time_since_last < self.decision_frequency:
|
||||
return None
|
||||
# EXECUTE EVERY SIGNAL: Remove decision frequency limit
|
||||
# Allow immediate execution of every signal from the decision model
|
||||
logger.debug(f"Processing signal for {symbol} - no frequency limit applied")
|
||||
|
||||
# Get current market data
|
||||
current_price = self.data_provider.get_current_price(symbol)
|
||||
@ -1333,43 +1331,15 @@ class TradingOrchestrator:
|
||||
current_position_pnl, symbol
|
||||
)
|
||||
|
||||
# Apply aggressiveness-based confidence thresholds
|
||||
if best_action in ['BUY', 'SELL']:
|
||||
# For entry signals, use entry aggressiveness
|
||||
if not self._has_open_position(symbol):
|
||||
if best_confidence < entry_threshold:
|
||||
best_action = 'HOLD'
|
||||
reasoning['entry_threshold_applied'] = True
|
||||
reasoning['entry_threshold'] = entry_threshold
|
||||
# For exit signals, use exit aggressiveness
|
||||
else:
|
||||
if best_confidence < exit_threshold:
|
||||
best_action = 'HOLD'
|
||||
reasoning['exit_threshold_applied'] = True
|
||||
reasoning['exit_threshold'] = exit_threshold
|
||||
else:
|
||||
# Standard threshold for HOLD
|
||||
if best_confidence < self.confidence_threshold:
|
||||
best_action = 'HOLD'
|
||||
reasoning['threshold_applied'] = True
|
||||
# EXECUTE EVERY SIGNAL: Remove confidence thresholds and signal accumulation
|
||||
# The decision model has already aggregated all model outputs (CNN, DQN, transformer, etc.)
|
||||
# So we trust its decision and execute every signal
|
||||
reasoning['execute_every_signal'] = True
|
||||
reasoning['models_aggregated'] = [pred.model_name for pred in predictions]
|
||||
reasoning['aggregated_confidence'] = best_confidence
|
||||
|
||||
# Signal accumulation check - require multiple confident signals
|
||||
if best_action in ['BUY', 'SELL']:
|
||||
required_signals = 3 # Require 3 confident signals
|
||||
recent_decisions = self.get_recent_decisions(symbol, limit=5)
|
||||
|
||||
# Count recent signals in the same direction
|
||||
same_direction_count = sum(1 for d in recent_decisions
|
||||
if d.action == best_action and d.confidence > entry_threshold)
|
||||
|
||||
if same_direction_count < required_signals:
|
||||
best_action = 'HOLD'
|
||||
reasoning['signal_accumulation'] = True
|
||||
reasoning['required_signals'] = required_signals
|
||||
reasoning['current_signals'] = same_direction_count
|
||||
logger.info(f"Signal accumulation: {same_direction_count}/{required_signals} signals for {best_action}")
|
||||
else:
|
||||
logger.info(f"Signal accumulation satisfied: {same_direction_count}/{required_signals} signals for {best_action}")
|
||||
logger.info(f"EXECUTING EVERY SIGNAL: {best_action} (confidence: {best_confidence:.3f}) "
|
||||
f"from aggregated models: {reasoning['models_aggregated']}")
|
||||
|
||||
# Add P&L-based decision adjustment
|
||||
best_action, best_confidence = self._apply_pnl_feedback(
|
||||
|
@ -143,6 +143,10 @@ class TradingExecutor:
|
||||
self.max_open_orders = 2 # Maximum number of open orders allowed
|
||||
self.open_orders_count = 0 # Current count of open orders
|
||||
|
||||
# Rate limiting for open orders sync (30 seconds)
|
||||
self.last_open_orders_sync = datetime.min
|
||||
self.open_orders_sync_interval = 30 # seconds
|
||||
|
||||
# Trading symbols
|
||||
self.symbols = self.config.get('symbols', ['ETH/USDT', 'BTC/USDT'])
|
||||
|
||||
@ -332,21 +336,42 @@ class TradingExecutor:
|
||||
logger.debug(f"LOCK RELEASED: {action} for {symbol}")
|
||||
|
||||
def _get_open_orders_count(self) -> int:
|
||||
"""Get current count of open orders across all symbols"""
|
||||
"""Get current count of open orders across all symbols with rate limiting"""
|
||||
try:
|
||||
if self.simulation_mode:
|
||||
return 0
|
||||
|
||||
# Check if enough time has passed since last sync
|
||||
current_time = datetime.now()
|
||||
time_since_last_sync = (current_time - self.last_open_orders_sync).total_seconds()
|
||||
|
||||
if time_since_last_sync < self.open_orders_sync_interval:
|
||||
# Return cached count if within rate limit
|
||||
logger.debug(f"Using cached open orders count ({self.open_orders_count}) - rate limited")
|
||||
return self.open_orders_count
|
||||
|
||||
# Update last sync time
|
||||
self.last_open_orders_sync = current_time
|
||||
|
||||
total_open_orders = 0
|
||||
for symbol in self.symbols:
|
||||
open_orders = self.exchange.get_open_orders(symbol)
|
||||
total_open_orders += len(open_orders)
|
||||
try:
|
||||
open_orders = self.exchange.get_open_orders(symbol)
|
||||
total_open_orders += len(open_orders)
|
||||
except Exception as e:
|
||||
logger.warning(f"Error getting open orders for {symbol}: {e}")
|
||||
# Continue with other symbols
|
||||
continue
|
||||
|
||||
# Update cached count
|
||||
self.open_orders_count = total_open_orders
|
||||
logger.debug(f"Updated open orders count: {total_open_orders}")
|
||||
|
||||
return total_open_orders
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting open orders count: {e}")
|
||||
return 0
|
||||
return self.open_orders_count # Return cached value on error
|
||||
|
||||
def _can_place_new_order(self) -> bool:
|
||||
"""Check if we can place a new order based on open order limit"""
|
||||
@ -359,7 +384,7 @@ class TradingExecutor:
|
||||
return can_place
|
||||
|
||||
def sync_open_orders(self) -> Dict[str, Any]:
|
||||
"""Synchronize open orders with exchange and update internal state
|
||||
"""Synchronize open orders with exchange and update internal state with rate limiting
|
||||
|
||||
Returns:
|
||||
dict: Sync result with status and order details
|
||||
@ -373,6 +398,24 @@ class TradingExecutor:
|
||||
'count': 0
|
||||
}
|
||||
|
||||
# Check rate limiting
|
||||
current_time = datetime.now()
|
||||
time_since_last_sync = (current_time - self.last_open_orders_sync).total_seconds()
|
||||
|
||||
if time_since_last_sync < self.open_orders_sync_interval:
|
||||
# Return cached result if within rate limit
|
||||
logger.debug(f"Open order sync rate limited - using cached data")
|
||||
return {
|
||||
'status': 'rate_limited',
|
||||
'message': f'Rate limited - last sync was {time_since_last_sync:.1f}s ago',
|
||||
'orders': [],
|
||||
'count': self.open_orders_count,
|
||||
'cached': True
|
||||
}
|
||||
|
||||
# Update last sync time
|
||||
self.last_open_orders_sync = current_time
|
||||
|
||||
sync_result = {
|
||||
'status': 'started',
|
||||
'orders': [],
|
||||
@ -408,7 +451,7 @@ class TradingExecutor:
|
||||
|
||||
except Exception as e:
|
||||
error_msg = f"Error syncing orders for {symbol}: {e}"
|
||||
logger.error(error_msg)
|
||||
logger.warning(error_msg) # Changed to warning since this is expected with rate limits
|
||||
sync_result['errors'].append(error_msg)
|
||||
|
||||
# Update internal state
|
||||
@ -505,11 +548,30 @@ class TradingExecutor:
|
||||
logger.warning(f"Maximum concurrent positions reached: {len(self.positions)}")
|
||||
return False
|
||||
|
||||
# SYNC OPEN ORDERS BEFORE CHECKING LIMITS
|
||||
# This ensures we have accurate position data before making decisions
|
||||
if not self.simulation_mode:
|
||||
try:
|
||||
logger.debug(f"Syncing open orders before trade execution for {symbol}")
|
||||
sync_result = self.sync_open_orders()
|
||||
if sync_result.get('status') == 'success':
|
||||
logger.debug(f"Open orders synced successfully: {sync_result.get('count', 0)} orders")
|
||||
else:
|
||||
logger.warning(f"Open orders sync failed: {sync_result.get('message', 'Unknown error')}")
|
||||
except Exception as e:
|
||||
logger.warning(f"Error syncing open orders: {e}")
|
||||
|
||||
# Check open order limit
|
||||
if not self._can_place_new_order():
|
||||
logger.warning(f"Maximum open orders reached: {self._get_open_orders_count()}/{self.max_open_orders}")
|
||||
return False
|
||||
|
||||
# Check position size limit before opening new positions
|
||||
if action in ['BUY', 'SHORT'] and symbol not in self.positions:
|
||||
if not self._check_position_size_limit():
|
||||
logger.warning(f"Position size limit reached - cannot open new position for {symbol}")
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def _execute_buy(self, symbol: str, confidence: float, current_price: float) -> bool:
|
||||
@ -1153,25 +1215,30 @@ class TradingExecutor:
|
||||
return False
|
||||
|
||||
def _calculate_position_size(self, confidence: float, current_price: float) -> float:
|
||||
"""Calculate position size - use 100% of account balance for short-term scalping"""
|
||||
"""Calculate position size - limited to 20% of account balance by default"""
|
||||
# Get account balance (simulation or real)
|
||||
account_balance = self._get_account_balance_for_sizing()
|
||||
|
||||
# Use 100% of account balance since we're holding for seconds/minutes only
|
||||
# Scale by confidence: 70-100% of balance based on confidence (0.7-1.0 range)
|
||||
# Get maximum position size limit (default 20% of balance)
|
||||
max_position_percentage = self.mexc_config.get('max_position_percentage', 0.20)
|
||||
max_position_value = account_balance * max_position_percentage
|
||||
|
||||
# Calculate desired position size based on confidence
|
||||
# Scale by confidence: 70-100% of max position size based on confidence (0.7-1.0 range)
|
||||
confidence_multiplier = max(0.7, min(1.0, confidence))
|
||||
position_value = account_balance * confidence_multiplier
|
||||
desired_position_value = max_position_value * confidence_multiplier
|
||||
|
||||
# Apply reduction based on consecutive losses (risk management)
|
||||
reduction_factor = self.mexc_config.get('consecutive_loss_reduction_factor', 0.8)
|
||||
adjusted_reduction_factor = reduction_factor ** self.consecutive_losses
|
||||
position_value *= adjusted_reduction_factor
|
||||
final_position_value = desired_position_value * adjusted_reduction_factor
|
||||
|
||||
logger.debug(f"Position calculation: account=${account_balance:.2f}, "
|
||||
f"max_position=${max_position_value:.2f} ({max_position_percentage*100:.0f}%), "
|
||||
f"confidence_mult={confidence_multiplier:.2f}, "
|
||||
f"position=${position_value:.2f}, confidence={confidence:.2f}")
|
||||
f"final_position=${final_position_value:.2f}, confidence={confidence:.2f}")
|
||||
|
||||
return position_value
|
||||
return final_position_value
|
||||
|
||||
def _get_account_balance_for_sizing(self) -> float:
|
||||
"""Get account balance for position sizing calculations"""
|
||||
@ -1188,6 +1255,64 @@ class TradingExecutor:
|
||||
logger.warning(f"Failed to get live account balance: {e}, using simulation default")
|
||||
return self.mexc_config.get('simulation_account_usd', 100.0)
|
||||
|
||||
def _check_position_size_limit(self) -> bool:
|
||||
"""Check if total open position value exceeds the maximum allowed percentage of balance"""
|
||||
try:
|
||||
# Get account balance
|
||||
account_balance = self._get_account_balance_for_sizing()
|
||||
|
||||
# Get maximum position percentage (default 20%)
|
||||
max_position_percentage = self.mexc_config.get('max_position_percentage', 0.20)
|
||||
max_position_value = account_balance * max_position_percentage
|
||||
|
||||
# Calculate total current position value
|
||||
total_position_value = 0.0
|
||||
|
||||
# Add existing positions
|
||||
for symbol, position in self.positions.items():
|
||||
# Get current price for the symbol
|
||||
try:
|
||||
ticker = self.exchange.get_ticker(symbol) if self.exchange else None
|
||||
current_price = ticker['last'] if ticker and 'last' in ticker else position.entry_price
|
||||
except Exception:
|
||||
# Fallback to entry price if we can't get current price
|
||||
current_price = position.entry_price
|
||||
|
||||
# Calculate position value
|
||||
position_value = position.quantity * current_price
|
||||
total_position_value += position_value
|
||||
|
||||
logger.debug(f"Existing position {symbol}: {position.quantity:.6f} @ ${current_price:.2f} = ${position_value:.2f}")
|
||||
|
||||
# Add potential value from open orders that could become positions
|
||||
if not self.simulation_mode and self.exchange:
|
||||
try:
|
||||
open_orders = self.exchange.get_open_orders()
|
||||
for order in open_orders:
|
||||
if order.get('side', '').lower() in ['buy', 'sell']:
|
||||
# Estimate the value if this order gets filled
|
||||
order_quantity = float(order.get('quantity', 0))
|
||||
order_price = float(order.get('price', 0))
|
||||
if order_price > 0:
|
||||
order_value = order_quantity * order_price
|
||||
total_position_value += order_value
|
||||
logger.debug(f"Open order {order.get('symbol')}: {order_quantity:.6f} @ ${order_price:.2f} = ${order_value:.2f}")
|
||||
except Exception as e:
|
||||
logger.debug(f"Error calculating open order values: {e}")
|
||||
|
||||
# Check if we would exceed the limit
|
||||
if total_position_value >= max_position_value:
|
||||
logger.warning(f"Position size limit reached: ${total_position_value:.2f} >= ${max_position_value:.2f} ({max_position_percentage*100:.0f}% of balance)")
|
||||
return False
|
||||
|
||||
logger.debug(f"Position size check passed: ${total_position_value:.2f} < ${max_position_value:.2f} ({max_position_percentage*100:.0f}% of balance)")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error checking position size limit: {e}")
|
||||
# Allow trade if we can't check the limit
|
||||
return True
|
||||
|
||||
def update_positions(self, symbol: str, current_price: float):
|
||||
"""Update position P&L with current market price"""
|
||||
if symbol in self.positions:
|
||||
|
306
position_sync_enhancement.py
Normal file
306
position_sync_enhancement.py
Normal file
@ -0,0 +1,306 @@
|
||||
"""
|
||||
Enhanced Position Synchronization System
|
||||
Addresses the gap between dashboard position display and actual exchange account state
|
||||
"""
|
||||
|
||||
import logging
|
||||
import time
|
||||
from datetime import datetime, timedelta
|
||||
from typing import Dict, List, Optional, Any
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class EnhancedPositionSync:
|
||||
"""Enhanced position synchronization to ensure dashboard matches actual exchange state"""
|
||||
|
||||
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
|
||||
|
||||
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)}
|
||||
|
||||
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 {}
|
||||
|
||||
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 {}
|
||||
|
||||
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}
|
||||
|
||||
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)}
|
||||
|
||||
|
||||
# Integration with existing dashboard
|
||||
def integrate_enhanced_sync(dashboard):
|
||||
"""Integrate enhanced sync with existing dashboard"""
|
||||
|
||||
# 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
|
@ -118,6 +118,9 @@ class CleanTradingDashboard:
|
||||
)
|
||||
self.component_manager = DashboardComponentManager()
|
||||
|
||||
# Initialize enhanced position sync system
|
||||
self._initialize_enhanced_position_sync()
|
||||
|
||||
# Initialize Universal Data Adapter access through orchestrator
|
||||
if UNIVERSAL_DATA_AVAILABLE:
|
||||
self.universal_adapter = UniversalDataAdapter(self.data_provider)
|
||||
@ -2711,26 +2714,58 @@ class CleanTradingDashboard:
|
||||
}
|
||||
|
||||
def _sync_position_from_executor(self, symbol: str):
|
||||
"""Sync current position from trading executor"""
|
||||
"""Sync current position from trading executor and real Bybit positions"""
|
||||
try:
|
||||
if self.trading_executor and hasattr(self.trading_executor, 'get_current_position'):
|
||||
executor_position = self.trading_executor.get_current_position(symbol)
|
||||
if executor_position:
|
||||
# Update dashboard position to match executor
|
||||
self.current_position = {
|
||||
'side': executor_position.get('side', 'UNKNOWN'),
|
||||
'size': executor_position.get('size', 0),
|
||||
'price': executor_position.get('price', 0),
|
||||
'symbol': executor_position.get('symbol', symbol),
|
||||
'entry_time': executor_position.get('entry_time', datetime.now()),
|
||||
'leverage': self.current_leverage, # Store current leverage with position
|
||||
'unrealized_pnl': executor_position.get('unrealized_pnl', 0)
|
||||
}
|
||||
logger.debug(f"Synced position from executor: {self.current_position['side']} {self.current_position['size']:.3f}")
|
||||
# First try to get real position from Bybit
|
||||
real_position = None
|
||||
if self.trading_executor and hasattr(self.trading_executor, 'exchange'):
|
||||
try:
|
||||
# Get real positions from Bybit
|
||||
bybit_positions = self.trading_executor.exchange.get_positions(symbol)
|
||||
if bybit_positions:
|
||||
# Use the first real position found
|
||||
real_position = bybit_positions[0]
|
||||
logger.info(f"Found real Bybit position: {real_position}")
|
||||
else:
|
||||
logger.debug("No real positions found on Bybit")
|
||||
except Exception as e:
|
||||
logger.debug(f"Error getting real Bybit positions: {e}")
|
||||
|
||||
# If we have a real position, use it
|
||||
if real_position:
|
||||
self.current_position = {
|
||||
'side': 'LONG' if real_position.get('side', '').lower() == 'buy' else 'SHORT',
|
||||
'size': real_position.get('size', 0),
|
||||
'price': real_position.get('entry_price', 0),
|
||||
'symbol': real_position.get('symbol', symbol),
|
||||
'entry_time': datetime.now(), # We don't have entry time from API
|
||||
'leverage': real_position.get('leverage', self.current_leverage),
|
||||
'unrealized_pnl': real_position.get('unrealized_pnl', 0)
|
||||
}
|
||||
logger.info(f"Synced real Bybit position: {self.current_position['side']} {self.current_position['size']:.3f} @ ${self.current_position['price']:.2f}")
|
||||
else:
|
||||
# Fallback to executor's internal tracking
|
||||
if self.trading_executor and hasattr(self.trading_executor, 'get_current_position'):
|
||||
executor_position = self.trading_executor.get_current_position(symbol)
|
||||
if executor_position:
|
||||
# Update dashboard position to match executor
|
||||
self.current_position = {
|
||||
'side': executor_position.get('side', 'UNKNOWN'),
|
||||
'size': executor_position.get('size', 0),
|
||||
'price': executor_position.get('price', 0),
|
||||
'symbol': executor_position.get('symbol', symbol),
|
||||
'entry_time': executor_position.get('entry_time', datetime.now()),
|
||||
'leverage': self.current_leverage, # Store current leverage with position
|
||||
'unrealized_pnl': executor_position.get('unrealized_pnl', 0)
|
||||
}
|
||||
logger.debug(f"Synced position from executor: {self.current_position['side']} {self.current_position['size']:.3f}")
|
||||
else:
|
||||
# No position in executor
|
||||
self.current_position = None
|
||||
logger.debug("No position in trading executor")
|
||||
else:
|
||||
# No position in executor
|
||||
self.current_position = None
|
||||
logger.debug("No position in trading executor")
|
||||
logger.debug("No trading executor available")
|
||||
except Exception as e:
|
||||
logger.debug(f"Error syncing position from executor: {e}")
|
||||
|
||||
@ -3999,7 +4034,7 @@ class CleanTradingDashboard:
|
||||
logger.error(f"Error verifying position sync after trade: {e}")
|
||||
|
||||
def _periodic_position_sync_check(self):
|
||||
"""Periodically check and sync position with MEXC account"""
|
||||
"""Periodically check and sync position with Bybit account"""
|
||||
try:
|
||||
symbol = 'ETH/USDT'
|
||||
|
||||
@ -4007,26 +4042,18 @@ class CleanTradingDashboard:
|
||||
if not self.trading_executor or getattr(self.trading_executor, 'simulation_mode', True):
|
||||
return
|
||||
|
||||
# Determine current desired state based on dashboard position
|
||||
# Sync real positions from Bybit
|
||||
logger.debug(f"PERIODIC SYNC: Syncing real Bybit positions for {symbol}")
|
||||
self._sync_position_from_executor(symbol)
|
||||
|
||||
# Log current position state
|
||||
if self.current_position:
|
||||
side = self.current_position.get('side', 'UNKNOWN')
|
||||
if side.upper() in ['LONG', 'BUY']:
|
||||
desired_state = 'LONG'
|
||||
elif side.upper() in ['SHORT', 'SELL']:
|
||||
desired_state = 'SHORT'
|
||||
else:
|
||||
desired_state = 'NO_POSITION'
|
||||
size = self.current_position.get('size', 0)
|
||||
price = self.current_position.get('price', 0)
|
||||
logger.info(f"PERIODIC SYNC: Current position: {side} {size:.3f} @ ${price:.2f}")
|
||||
else:
|
||||
desired_state = 'NO_POSITION'
|
||||
|
||||
# Perform periodic sync check
|
||||
logger.debug(f"PERIODIC SYNC: Checking position sync for {symbol} (desired: {desired_state})")
|
||||
sync_success = self._sync_position_with_mexc(symbol, desired_state)
|
||||
|
||||
if sync_success:
|
||||
logger.debug(f"PERIODIC SYNC: Position sync verified for {symbol}")
|
||||
else:
|
||||
logger.warning(f"PERIODIC SYNC: Position sync issue detected for {symbol}")
|
||||
logger.info(f"PERIODIC SYNC: No current position for {symbol}")
|
||||
|
||||
except Exception as e:
|
||||
logger.debug(f"Error in periodic position sync check: {e}")
|
||||
@ -4914,6 +4941,25 @@ class CleanTradingDashboard:
|
||||
logger.error(f"Error initializing enhanced training system: {e}")
|
||||
self.training_system = None
|
||||
|
||||
def _initialize_enhanced_position_sync(self):
|
||||
"""Initialize enhanced position synchronization system"""
|
||||
try:
|
||||
logger.info("Initializing enhanced position sync system...")
|
||||
|
||||
# Initialize position sync if trading executor is available
|
||||
if self.trading_executor:
|
||||
# Set up periodic position sync
|
||||
self.position_sync_enabled = True
|
||||
self.position_sync_interval = 30 # seconds
|
||||
logger.info("Enhanced position sync system initialized")
|
||||
else:
|
||||
logger.warning("Trading executor not available - position sync disabled")
|
||||
self.position_sync_enabled = False
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error initializing enhanced position sync: {e}")
|
||||
self.position_sync_enabled = False
|
||||
|
||||
def _initialize_cob_integration(self):
|
||||
"""Initialize COB integration using orchestrator's COB system"""
|
||||
try:
|
||||
@ -6846,4 +6892,4 @@ def create_clean_dashboard(data_provider: Optional[DataProvider] = None, orchest
|
||||
)
|
||||
|
||||
|
||||
# test edit
|
||||
# test edit
|
||||
|
Reference in New Issue
Block a user