live position sync for LIMIT orders
This commit is contained in:
@ -357,7 +357,7 @@ class CleanTradingDashboard:
|
||||
except Exception as e:
|
||||
logger.warning(f"Error getting live account balance: {e}, using initial balance")
|
||||
return self._get_initial_balance()
|
||||
|
||||
|
||||
def _setup_layout(self):
|
||||
"""Setup the dashboard layout using layout manager"""
|
||||
self.app.layout = self.layout_manager.create_main_layout()
|
||||
@ -377,8 +377,12 @@ class CleanTradingDashboard:
|
||||
[Input('interval-component', 'n_intervals')]
|
||||
)
|
||||
def update_metrics(n):
|
||||
"""Update key metrics - FIXED callback mismatch"""
|
||||
"""Update key metrics - ENHANCED with position sync monitoring"""
|
||||
try:
|
||||
# PERIODIC POSITION SYNC: Every 30 seconds, verify position sync
|
||||
if n % 30 == 0 and n > 0: # Skip initial load (n=0)
|
||||
self._periodic_position_sync_check()
|
||||
|
||||
# Sync position from trading executor first
|
||||
symbol = 'ETH/USDT'
|
||||
self._sync_position_from_executor(symbol)
|
||||
@ -461,12 +465,12 @@ class CleanTradingDashboard:
|
||||
portfolio_value = current_balance + total_session_pnl # Live balance + unrealized P&L
|
||||
portfolio_str = f"${portfolio_value:.2f}"
|
||||
|
||||
# MEXC status
|
||||
# MEXC status - enhanced with sync status
|
||||
mexc_status = "SIM"
|
||||
if self.trading_executor:
|
||||
if hasattr(self.trading_executor, 'trading_enabled') and self.trading_executor.trading_enabled:
|
||||
if hasattr(self.trading_executor, 'simulation_mode') and not self.trading_executor.simulation_mode:
|
||||
mexc_status = "LIVE"
|
||||
mexc_status = "LIVE+SYNC" # Indicate live trading with position sync
|
||||
|
||||
return price_str, session_pnl_str, position_str, trade_str, portfolio_str, mexc_status
|
||||
|
||||
@ -541,6 +545,18 @@ class CleanTradingDashboard:
|
||||
logger.error(f"Error updating trades table: {e}")
|
||||
return html.P(f"Error: {str(e)}", className="text-danger")
|
||||
|
||||
@self.app.callback(
|
||||
Output('pending-orders-content', 'children'),
|
||||
[Input('interval-component', 'n_intervals')]
|
||||
)
|
||||
def update_pending_orders(n):
|
||||
"""Update pending orders and position sync status"""
|
||||
try:
|
||||
return self._create_pending_orders_panel()
|
||||
except Exception as e:
|
||||
logger.error(f"Error updating pending orders: {e}")
|
||||
return html.Div("Error loading pending orders", className="text-danger")
|
||||
|
||||
@self.app.callback(
|
||||
[Output('eth-cob-content', 'children'),
|
||||
Output('btc-cob-content', 'children')],
|
||||
@ -3480,7 +3496,7 @@ class CleanTradingDashboard:
|
||||
if hasattr(self.orchestrator.cnn_model, 'train_on_batch'):
|
||||
for _ in range(int(training_weight)):
|
||||
loss = self.orchestrator.cnn_model.train_on_batch(feature_tensor, target_tensor)
|
||||
logger.info(f"CNN enhanced training on executed signal - loss: {loss:.4f}, pnl: {pnl:.2f}")
|
||||
logger.info(f"CNN enhanced training on executed signal - loss: {loss:.4f}, pnl: {pnl:.2f}")
|
||||
|
||||
except Exception as e:
|
||||
logger.debug(f"Error training CNN on executed signal: {e}")
|
||||
@ -3646,7 +3662,7 @@ class CleanTradingDashboard:
|
||||
return 0.5
|
||||
|
||||
def _execute_manual_trade(self, action: str):
|
||||
"""Execute manual trading action - ENHANCED with PERSISTENT SIGNAL STORAGE"""
|
||||
"""Execute manual trading action - ENHANCED with POSITION SYNCHRONIZATION"""
|
||||
try:
|
||||
if not self.trading_executor:
|
||||
logger.warning("No trading executor available")
|
||||
@ -3659,7 +3675,16 @@ class CleanTradingDashboard:
|
||||
logger.warning("No current price available for manual trade")
|
||||
return
|
||||
|
||||
# Sync current position from trading executor first
|
||||
# STEP 1: Synchronize position with MEXC account before executing trade
|
||||
desired_state = self._determine_desired_position_state(action)
|
||||
logger.info(f"MANUAL TRADE: Syncing position to {desired_state} before executing {action}")
|
||||
|
||||
sync_success = self._sync_position_with_mexc(symbol, desired_state)
|
||||
if not sync_success:
|
||||
logger.error(f"MANUAL TRADE: Position sync failed - aborting {action}")
|
||||
return
|
||||
|
||||
# STEP 2: Sync current position from trading executor
|
||||
self._sync_position_from_executor(symbol)
|
||||
|
||||
# DEBUG: Log current position state before trade
|
||||
@ -3916,6 +3941,297 @@ class CleanTradingDashboard:
|
||||
|
||||
# Model input capture moved to core.trade_data_manager.TradeDataManager
|
||||
|
||||
def _determine_desired_position_state(self, action: str) -> str:
|
||||
"""Determine the desired position state based on the manual action"""
|
||||
if action == 'BUY':
|
||||
return 'LONG'
|
||||
elif action == 'SELL':
|
||||
# If we have a position, selling should result in NO_POSITION
|
||||
# If we don't have a position, selling should result in SHORT
|
||||
if self.current_position:
|
||||
return 'NO_POSITION'
|
||||
else:
|
||||
return 'SHORT'
|
||||
else: # HOLD or unknown
|
||||
# Maintain current state
|
||||
if self.current_position:
|
||||
side = self.current_position.get('side', 'UNKNOWN')
|
||||
if side.upper() in ['LONG', 'BUY']:
|
||||
return 'LONG'
|
||||
elif side.upper() in ['SHORT', 'SELL']:
|
||||
return 'SHORT'
|
||||
return 'NO_POSITION'
|
||||
|
||||
def _sync_position_with_mexc(self, symbol: str, desired_state: str) -> bool:
|
||||
"""Synchronize position with MEXC account using trading executor"""
|
||||
try:
|
||||
if not self.trading_executor:
|
||||
logger.warning("No trading executor available for position sync")
|
||||
return False
|
||||
|
||||
if hasattr(self.trading_executor, 'sync_position_with_mexc'):
|
||||
return self.trading_executor.sync_position_with_mexc(symbol, desired_state)
|
||||
else:
|
||||
logger.warning("Trading executor does not support position synchronization")
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error syncing position with MEXC: {e}")
|
||||
return False
|
||||
|
||||
def _verify_position_sync_after_trade(self, symbol: str, action: str):
|
||||
"""Verify that position sync is correct after trade execution"""
|
||||
try:
|
||||
# Wait a moment for position updates
|
||||
time.sleep(1)
|
||||
|
||||
# Sync position from executor
|
||||
self._sync_position_from_executor(symbol)
|
||||
|
||||
# Log the final position state
|
||||
if self.current_position:
|
||||
logger.info(f"POSITION VERIFICATION: After {action} - "
|
||||
f"{self.current_position['side']} {self.current_position['size']:.3f} @ ${self.current_position['price']:.2f}")
|
||||
else:
|
||||
logger.info(f"POSITION VERIFICATION: After {action} - No position")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error verifying position sync after trade: {e}")
|
||||
|
||||
def _periodic_position_sync_check(self):
|
||||
"""Periodically check and sync position with MEXC account"""
|
||||
try:
|
||||
symbol = 'ETH/USDT'
|
||||
|
||||
# Only perform sync check for live trading
|
||||
if not self.trading_executor or getattr(self.trading_executor, 'simulation_mode', True):
|
||||
return
|
||||
|
||||
# Determine current desired state based on dashboard position
|
||||
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'
|
||||
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}")
|
||||
|
||||
except Exception as e:
|
||||
logger.debug(f"Error in periodic position sync check: {e}")
|
||||
|
||||
def _create_pending_orders_panel(self):
|
||||
"""Create pending orders and position sync status panel"""
|
||||
try:
|
||||
symbol = 'ETH/USDT'
|
||||
|
||||
# Get pending orders from MEXC
|
||||
pending_orders = self._get_pending_orders(symbol)
|
||||
|
||||
# Get current account balances and position state
|
||||
position_sync_status = self._get_position_sync_status(symbol)
|
||||
|
||||
# Create the panel content
|
||||
content = []
|
||||
|
||||
# Position Sync Status Section
|
||||
content.append(html.Div([
|
||||
html.H6([
|
||||
html.I(className="fas fa-sync me-1"),
|
||||
"Position Sync Status"
|
||||
], className="mb-2 text-primary"),
|
||||
|
||||
html.Div([
|
||||
html.Small("Dashboard Position:", className="text-muted"),
|
||||
html.Span(f" {position_sync_status['dashboard_state']}", className="badge bg-info ms-1")
|
||||
], className="mb-1"),
|
||||
|
||||
html.Div([
|
||||
html.Small("MEXC Account State:", className="text-muted"),
|
||||
html.Span(f" {position_sync_status['mexc_state']}", className="badge bg-secondary ms-1")
|
||||
], className="mb-1"),
|
||||
|
||||
html.Div([
|
||||
html.Small("Sync Status:", className="text-muted"),
|
||||
html.Span(f" {position_sync_status['sync_status']}",
|
||||
className=f"badge {'bg-success' if position_sync_status['in_sync'] else 'bg-warning'} ms-1")
|
||||
], className="mb-2"),
|
||||
|
||||
html.Div([
|
||||
html.Small("ETH Balance:", className="text-muted"),
|
||||
html.Span(f" {position_sync_status['eth_balance']:.6f}", className="text-info ms-1"),
|
||||
], className="mb-1"),
|
||||
|
||||
html.Div([
|
||||
html.Small("USDC Balance:", className="text-muted"),
|
||||
html.Span(f" ${position_sync_status['usdc_balance']:.2f}", className="text-info ms-1"),
|
||||
], className="mb-2"),
|
||||
|
||||
], className="border-bottom pb-2 mb-2"))
|
||||
|
||||
# Pending Orders Section
|
||||
content.append(html.Div([
|
||||
html.H6([
|
||||
html.I(className="fas fa-clock me-1"),
|
||||
f"Pending Orders ({len(pending_orders)})"
|
||||
], className="mb-2 text-warning"),
|
||||
]))
|
||||
|
||||
if pending_orders:
|
||||
# Create table of pending orders
|
||||
order_rows = []
|
||||
for order in pending_orders:
|
||||
side_class = "text-success" if order.get('side', '').upper() == 'BUY' else "text-danger"
|
||||
status_class = "bg-warning" if order.get('status') == 'NEW' else "bg-secondary"
|
||||
|
||||
order_rows.append(html.Tr([
|
||||
html.Td(order.get('side', 'N/A'), className=side_class),
|
||||
html.Td(f"{float(order.get('origQty', 0)):.6f}"),
|
||||
html.Td(f"${float(order.get('price', 0)):.2f}"),
|
||||
html.Td(html.Span(order.get('status', 'UNKNOWN'), className=f"badge {status_class}")),
|
||||
html.Td(order.get('orderId', 'N/A')[-8:] if order.get('orderId') else 'N/A'), # Last 8 chars
|
||||
]))
|
||||
|
||||
orders_table = html.Div([
|
||||
html.Table([
|
||||
html.Thead([
|
||||
html.Tr([
|
||||
html.Th("Side", style={"fontSize": "10px"}),
|
||||
html.Th("Qty", style={"fontSize": "10px"}),
|
||||
html.Th("Price", style={"fontSize": "10px"}),
|
||||
html.Th("Status", style={"fontSize": "10px"}),
|
||||
html.Th("Order ID", style={"fontSize": "10px"}),
|
||||
])
|
||||
]),
|
||||
html.Tbody(order_rows)
|
||||
], className="table table-sm", style={"fontSize": "11px"})
|
||||
])
|
||||
content.append(orders_table)
|
||||
else:
|
||||
content.append(html.Div([
|
||||
html.P("No pending orders", className="text-muted small text-center mt-2")
|
||||
]))
|
||||
|
||||
# Last sync check time
|
||||
content.append(html.Div([
|
||||
html.Hr(),
|
||||
html.Small([
|
||||
html.I(className="fas fa-clock me-1"),
|
||||
f"Last updated: {datetime.now().strftime('%H:%M:%S')}"
|
||||
], className="text-muted")
|
||||
]))
|
||||
|
||||
return content
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error creating pending orders panel: {e}")
|
||||
return html.Div([
|
||||
html.P("Error loading pending orders", className="text-danger"),
|
||||
html.Small(str(e), className="text-muted")
|
||||
])
|
||||
|
||||
def _get_pending_orders(self, symbol: str) -> List[Dict]:
|
||||
"""Get pending orders from MEXC for the symbol"""
|
||||
try:
|
||||
if not self.trading_executor or getattr(self.trading_executor, 'simulation_mode', True):
|
||||
return [] # No pending orders in simulation mode
|
||||
|
||||
if hasattr(self.trading_executor, 'exchange') and self.trading_executor.exchange:
|
||||
orders = self.trading_executor.exchange.get_open_orders(symbol)
|
||||
return orders if orders else []
|
||||
|
||||
return []
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting pending orders: {e}")
|
||||
return []
|
||||
|
||||
def _get_position_sync_status(self, symbol: str) -> Dict[str, Any]:
|
||||
"""Get comprehensive position synchronization status"""
|
||||
try:
|
||||
# Determine dashboard position state
|
||||
if self.current_position:
|
||||
side = self.current_position.get('side', 'UNKNOWN')
|
||||
if side.upper() in ['LONG', 'BUY']:
|
||||
dashboard_state = 'LONG'
|
||||
elif side.upper() in ['SHORT', 'SELL']:
|
||||
dashboard_state = 'SHORT'
|
||||
else:
|
||||
dashboard_state = 'UNKNOWN'
|
||||
else:
|
||||
dashboard_state = 'NO_POSITION'
|
||||
|
||||
# Get MEXC account balances and determine state
|
||||
mexc_state = 'UNKNOWN'
|
||||
eth_balance = 0.0
|
||||
usdc_balance = 0.0
|
||||
|
||||
if self.trading_executor and not getattr(self.trading_executor, 'simulation_mode', True):
|
||||
try:
|
||||
if hasattr(self.trading_executor, '_get_mexc_account_balances'):
|
||||
balances = self.trading_executor._get_mexc_account_balances()
|
||||
eth_balance = balances.get('ETH', {}).get('total', 0.0)
|
||||
usdc_balance = max(
|
||||
balances.get('USDC', {}).get('total', 0.0),
|
||||
balances.get('USDT', {}).get('total', 0.0)
|
||||
)
|
||||
|
||||
# Determine MEXC state using same logic as trading executor
|
||||
if hasattr(self.trading_executor, '_determine_position_state'):
|
||||
holdings = {
|
||||
'base': eth_balance,
|
||||
'quote': usdc_balance,
|
||||
'base_asset': 'ETH',
|
||||
'quote_asset': 'USDC'
|
||||
}
|
||||
mexc_state = self.trading_executor._determine_position_state(symbol, holdings)
|
||||
except Exception as e:
|
||||
logger.debug(f"Error getting MEXC account state: {e}")
|
||||
else:
|
||||
mexc_state = 'SIMULATION'
|
||||
# In simulation, use some placeholder values
|
||||
if self.current_position:
|
||||
eth_balance = self.current_position.get('size', 0.0) if dashboard_state == 'LONG' else 0.0
|
||||
usdc_balance = 100.0 if dashboard_state != 'LONG' else 10.0
|
||||
|
||||
# Determine sync status
|
||||
in_sync = (dashboard_state == mexc_state) or mexc_state == 'SIMULATION'
|
||||
if in_sync:
|
||||
sync_status = 'IN_SYNC'
|
||||
else:
|
||||
sync_status = f'{dashboard_state}≠{mexc_state}'
|
||||
|
||||
return {
|
||||
'dashboard_state': dashboard_state,
|
||||
'mexc_state': mexc_state,
|
||||
'sync_status': sync_status,
|
||||
'in_sync': in_sync,
|
||||
'eth_balance': eth_balance,
|
||||
'usdc_balance': usdc_balance
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting position sync status: {e}")
|
||||
return {
|
||||
'dashboard_state': 'ERROR',
|
||||
'mexc_state': 'ERROR',
|
||||
'sync_status': 'ERROR',
|
||||
'in_sync': False,
|
||||
'eth_balance': 0.0,
|
||||
'usdc_balance': 0.0
|
||||
}
|
||||
|
||||
def _get_comprehensive_market_state(self, symbol: str, current_price: float) -> Dict[str, float]:
|
||||
"""Get comprehensive market state features"""
|
||||
try:
|
||||
|
@ -197,6 +197,10 @@ class DashboardLayoutManager:
|
||||
html.I(className="fas fa-save me-1"),
|
||||
"Store All Models"
|
||||
], id="store-models-btn", className="btn btn-info btn-sm w-100 mt-2"),
|
||||
html.Button([
|
||||
html.I(className="fas fa-arrows-rotate me-1"),
|
||||
"Sync Positions/Orders"
|
||||
], id="manual-sync-btn", className="btn btn-primary btn-sm w-100 mt-2"),
|
||||
html.Hr(className="my-2"),
|
||||
html.Small("System Status", className="text-muted d-block mb-1"),
|
||||
html.Div([
|
||||
@ -248,7 +252,7 @@ class DashboardLayoutManager:
|
||||
])
|
||||
|
||||
def _create_cob_and_trades_row(self):
|
||||
"""Creates the row for COB ladders, closed trades, and model status - REORGANIZED LAYOUT"""
|
||||
"""Creates the row for COB ladders, closed trades, pending orders, and model status"""
|
||||
return html.Div([
|
||||
# Top row: COB Ladders (left) and Models/Training (right)
|
||||
html.Div([
|
||||
@ -273,7 +277,7 @@ class DashboardLayoutManager:
|
||||
], className="d-flex")
|
||||
], style={"width": "60%"}),
|
||||
|
||||
# Right side: Models & Training Progress (40% width) - MOVED UP
|
||||
# Right side: Models & Training Progress (40% width)
|
||||
html.Div([
|
||||
html.Div([
|
||||
html.Div([
|
||||
@ -283,28 +287,47 @@ class DashboardLayoutManager:
|
||||
], className="card-title mb-2"),
|
||||
html.Div(
|
||||
id="training-metrics",
|
||||
style={"height": "300px", "overflowY": "auto"}, # Increased height
|
||||
style={"height": "300px", "overflowY": "auto"},
|
||||
),
|
||||
], className="card-body p-2")
|
||||
], className="card")
|
||||
], style={"width": "38%", "marginLeft": "2%"}),
|
||||
], className="d-flex mb-3"),
|
||||
|
||||
# Bottom row: Closed Trades (full width) - MOVED BELOW COB
|
||||
# Second row: Pending Orders (left) and Closed Trades (right)
|
||||
html.Div([
|
||||
# Left side: Pending Orders (40% width)
|
||||
html.Div([
|
||||
html.Div([
|
||||
html.H6([
|
||||
html.I(className="fas fa-history me-2"),
|
||||
"Recent Closed Trades",
|
||||
], className="card-title mb-2"),
|
||||
html.Div(
|
||||
id="closed-trades-table",
|
||||
style={"height": "200px", "overflowY": "auto"}, # Reduced height
|
||||
),
|
||||
], className="card-body p-2")
|
||||
], className="card")
|
||||
])
|
||||
html.Div([
|
||||
html.H6([
|
||||
html.I(className="fas fa-clock me-2"),
|
||||
"Pending Orders & Position Sync",
|
||||
], className="card-title mb-2"),
|
||||
html.Div(
|
||||
id="pending-orders-content",
|
||||
style={"height": "200px", "overflowY": "auto"},
|
||||
),
|
||||
], className="card-body p-2")
|
||||
], className="card")
|
||||
], style={"width": "40%"}),
|
||||
|
||||
# Right side: Closed Trades (58% width)
|
||||
html.Div([
|
||||
html.Div([
|
||||
html.Div([
|
||||
html.H6([
|
||||
html.I(className="fas fa-history me-2"),
|
||||
"Recent Closed Trades",
|
||||
], className="card-title mb-2"),
|
||||
html.Div(
|
||||
id="closed-trades-table",
|
||||
style={"height": "200px", "overflowY": "auto"},
|
||||
),
|
||||
], className="card-body p-2")
|
||||
], className="card")
|
||||
], style={"width": "58%", "marginLeft": "2%"}),
|
||||
], className="d-flex")
|
||||
])
|
||||
|
||||
def _create_analytics_and_performance_row(self):
|
||||
|
Reference in New Issue
Block a user