From 249fdace73f4c0f815f23ad0d7d81a23ddd15eb0 Mon Sep 17 00:00:00 2001 From: Dobromir Popov Date: Wed, 25 Jun 2025 17:02:21 +0300 Subject: [PATCH] try to have sell actions --- core/trading_executor.py | 5 +- web/clean_dashboard.py | 273 ++++++++++++++++++++++++++++++++++----- web/layout_manager.py | 152 ++++++++++++---------- 3 files changed, 324 insertions(+), 106 deletions(-) diff --git a/core/trading_executor.py b/core/trading_executor.py index 1fe6f50..c03f6db 100644 --- a/core/trading_executor.py +++ b/core/trading_executor.py @@ -318,8 +318,9 @@ class TradingExecutor: """Execute a sell order""" # Check if we have a position to sell if symbol not in self.positions: - logger.info(f"No position to sell in {symbol}") - return False + logger.info(f"No position to sell in {symbol}. Opening short position") + # TODO: Open short position + position = self.positions[symbol] diff --git a/web/clean_dashboard.py b/web/clean_dashboard.py index 7d787f4..a10271a 100644 --- a/web/clean_dashboard.py +++ b/web/clean_dashboard.py @@ -243,13 +243,39 @@ class CleanTradingDashboard: session_pnl_str = f"${self.session_pnl:.2f}" session_pnl_class = "text-success" if self.session_pnl >= 0 else "text-danger" - # Current position + # Current position with unrealized P&L (x50 leverage) position_str = "No Position" if self.current_position: side = self.current_position.get('side', 'UNKNOWN') size = self.current_position.get('size', 0) entry_price = self.current_position.get('price', 0) - position_str = f"{side} {size:.3f} @ ${entry_price:.2f}" + + # Calculate unrealized P&L with x50 leverage + unrealized_pnl = 0.0 + pnl_str = "" + pnl_class = "" + + if current_price and entry_price and size > 0: + # Calculate raw P&L per unit + if side.upper() == 'LONG' or side.upper() == 'BUY': + raw_pnl_per_unit = current_price - entry_price + else: # SHORT or SELL + raw_pnl_per_unit = entry_price - current_price + + # Apply x50 leverage to P&L calculation + # With leverage, P&L is amplified by the leverage factor + leveraged_pnl_per_unit = raw_pnl_per_unit * 50 + unrealized_pnl = leveraged_pnl_per_unit * size + + # Format P&L string with color + if unrealized_pnl >= 0: + pnl_str = f" (+${unrealized_pnl:.2f})" + pnl_class = "text-success" + else: + pnl_str = f" (${unrealized_pnl:.2f})" + pnl_class = "text-danger" + + position_str = f"{side.upper()} {size:.3f} @ ${entry_price:.2f}{pnl_str}" # Portfolio value initial_balance = self._get_initial_balance() @@ -315,18 +341,13 @@ class CleanTradingDashboard: return html.P(f"Error: {str(e)}", className="text-danger") @self.app.callback( - [Output('cob-status-content', 'children'), - Output('eth-cob-content', 'children'), + [Output('eth-cob-content', 'children'), Output('btc-cob-content', 'children')], [Input('interval-component', 'n_intervals')] ) def update_cob_data(n): """Update COB data displays""" try: - # COB Status - cob_status = self._get_cob_status() - status_components = self.component_manager.format_system_status(cob_status) - # ETH/USDT COB eth_cob = self._get_cob_snapshot('ETH/USDT') eth_components = self.component_manager.format_cob_data(eth_cob, 'ETH/USDT') @@ -335,12 +356,12 @@ class CleanTradingDashboard: btc_cob = self._get_cob_snapshot('BTC/USDT') btc_components = self.component_manager.format_cob_data(btc_cob, 'BTC/USDT') - return status_components, eth_components, btc_components + return eth_components, btc_components except Exception as e: logger.error(f"Error updating COB data: {e}") error_msg = html.P(f"Error: {str(e)}", className="text-danger") - return error_msg, error_msg, error_msg + return error_msg, error_msg @self.app.callback( Output('training-metrics', 'children'), @@ -1151,8 +1172,9 @@ class CleanTradingDashboard: 'orchestrator_type': 'Basic' } + # EXAMPLE OF WHAT WE SHOULD NEVER DO!!! use only real data or report we have no data # COB $1 Buckets (sample data for now) - metrics['cob_buckets'] = self._get_cob_dollar_buckets() + # metrics['cob_buckets'] = self._get_cob_dollar_buckets() return metrics @@ -1300,9 +1322,42 @@ class CleanTradingDashboard: signal['blocked'] = False signal['manual'] = False - # Execute signal if confidence is above threshold - confidence_threshold = 0.25 # Lower threshold for testing (was too high) - if signal.get('confidence', 0) >= confidence_threshold: + # Smart confidence-based execution with different thresholds for opening vs closing + confidence = signal.get('confidence', 0) + action = signal.get('action', 'HOLD') + should_execute = False + execution_reason = "" + + # Define confidence thresholds + CLOSE_POSITION_THRESHOLD = 0.25 # Lower threshold to close positions + OPEN_POSITION_THRESHOLD = 0.60 # Higher threshold to open new positions + + # Determine if we should execute based on current position and action + if action == 'BUY': + if self.current_position and self.current_position.get('side') == 'SHORT': + # Closing SHORT position - use lower threshold + if confidence >= CLOSE_POSITION_THRESHOLD: + should_execute = True + execution_reason = f"Closing SHORT position (threshold: {CLOSE_POSITION_THRESHOLD})" + else: + # Opening new LONG position - use higher threshold + if confidence >= OPEN_POSITION_THRESHOLD: + should_execute = True + execution_reason = f"Opening LONG position (threshold: {OPEN_POSITION_THRESHOLD})" + + elif action == 'SELL': + if self.current_position and self.current_position.get('side') == 'LONG': + # Closing LONG position - use lower threshold + if confidence >= CLOSE_POSITION_THRESHOLD: + should_execute = True + execution_reason = f"Closing LONG position (threshold: {CLOSE_POSITION_THRESHOLD})" + else: + # Opening new SHORT position - use higher threshold + if confidence >= OPEN_POSITION_THRESHOLD: + should_execute = True + execution_reason = f"Opening SHORT position (threshold: {OPEN_POSITION_THRESHOLD})" + + if should_execute: try: # Attempt to execute the signal symbol = signal.get('symbol', 'ETH/USDT') @@ -1314,7 +1369,7 @@ class CleanTradingDashboard: if result: signal['executed'] = True logger.info(f"✅ EXECUTED {action} signal: {symbol} @ ${signal.get('price', 0):.2f} " - f"(conf: {signal['confidence']:.2f}, size: {size})") + f"(conf: {signal['confidence']:.2f}, size: {size}) - {execution_reason}") # Create trade record for tracking trade_record = { @@ -1329,6 +1384,45 @@ class CleanTradingDashboard: 'trade_type': 'auto_signal' } + # Create/update current position for unrealized P&L tracking + current_price = signal.get('price', 0) + if action == 'BUY': + # Create or add to LONG position + self.current_position = { + 'side': 'LONG', + 'size': size, + 'price': current_price, + 'symbol': symbol, + 'entry_time': datetime.now(), + 'leverage': 50 + } + logger.info(f"Auto-signal created LONG position: {size:.3f} @ ${current_price:.2f}") + else: # SELL + # Create SHORT position or close LONG + if self.current_position and self.current_position.get('side') == 'LONG': + # Close LONG position and calculate realized P&L + entry_price = self.current_position.get('price', 0) + position_size = self.current_position.get('size', 0) + if entry_price and position_size: + # Calculate leveraged P&L for position close + raw_pnl = (current_price - entry_price) * position_size + leveraged_pnl = raw_pnl * 50 # x50 leverage + self.session_pnl += leveraged_pnl + trade_record['pnl'] = leveraged_pnl + logger.info(f"Closed LONG position: P&L ${leveraged_pnl:.2f} (x50 leverage)") + self.current_position = None + else: + # Create SHORT position + self.current_position = { + 'side': 'SHORT', + 'size': size, + 'price': current_price, + 'symbol': symbol, + 'entry_time': datetime.now(), + 'leverage': 50 + } + logger.info(f"Auto-signal created SHORT position: {size:.3f} @ ${current_price:.2f}") + self.closed_trades.append(trade_record) # Update session metrics immediately @@ -1352,9 +1446,28 @@ class CleanTradingDashboard: signal['block_reason'] = str(e) logger.error(f"❌ EXECUTION ERROR for {signal.get('action', 'UNKNOWN')}: {e}") else: + # Determine which threshold was not met + if action == 'BUY': + if self.current_position and self.current_position.get('side') == 'SHORT': + required_threshold = CLOSE_POSITION_THRESHOLD + operation = "close SHORT position" + else: + required_threshold = OPEN_POSITION_THRESHOLD + operation = "open LONG position" + elif action == 'SELL': + if self.current_position and self.current_position.get('side') == 'LONG': + required_threshold = CLOSE_POSITION_THRESHOLD + operation = "close LONG position" + else: + required_threshold = OPEN_POSITION_THRESHOLD + operation = "open SHORT position" + else: + required_threshold = 0.25 + operation = "execute signal" + signal['blocked'] = True - signal['block_reason'] = f"Confidence {signal.get('confidence', 0):.3f} below threshold {confidence_threshold}" - logger.debug(f"Signal confidence {signal.get('confidence', 0):.3f} below execution threshold {confidence_threshold}") + signal['block_reason'] = f"Confidence {confidence:.3f} below threshold {required_threshold:.2f} to {operation}" + logger.debug(f"Signal confidence {confidence:.3f} below {required_threshold:.2f} threshold to {operation}") # Add to recent decisions for display self.recent_decisions.append(signal) @@ -1376,22 +1489,23 @@ class CleanTradingDashboard: # Basic orchestrator doesn't have DQN features return - def _get_cob_dollar_buckets(self) -> List[Dict]: - """Get COB $1 price buckets with volume data""" - try: - # This would normally come from the COB integration - # For now, return sample data structure - sample_buckets = [ - {'price': 2000, 'total_volume': 150000, 'bid_pct': 45, 'ask_pct': 55}, - {'price': 2001, 'total_volume': 120000, 'bid_pct': 52, 'ask_pct': 48}, - {'price': 1999, 'total_volume': 98000, 'bid_pct': 38, 'ask_pct': 62}, - {'price': 2002, 'total_volume': 87000, 'bid_pct': 60, 'ask_pct': 40}, - {'price': 1998, 'total_volume': 76000, 'bid_pct': 35, 'ask_pct': 65} - ] - return sample_buckets - except Exception as e: - logger.debug(f"Error getting COB buckets: {e}") - return [] + # EXAMPLE OF WHAT WE SHOULD NEVER DO!!! use only real data or report we have no data + # def _get_cob_dollar_buckets(self) -> List[Dict]: + # """Get COB $1 price buckets with volume data""" + # try: + # # This would normally come from the COB integration + # # For now, return sample data structure + # sample_buckets = [ + # {'price': 2000, 'total_volume': 150000, 'bid_pct': 45, 'ask_pct': 55}, + # {'price': 2001, 'total_volume': 120000, 'bid_pct': 52, 'ask_pct': 48}, + # {'price': 1999, 'total_volume': 98000, 'bid_pct': 38, 'ask_pct': 62}, + # {'price': 2002, 'total_volume': 87000, 'bid_pct': 60, 'ask_pct': 40}, + # {'price': 1998, 'total_volume': 76000, 'bid_pct': 35, 'ask_pct': 65} + # ] + # return sample_buckets + # except Exception as e: + # logger.debug(f"Error getting COB buckets: {e}") + # return [] def _execute_manual_trade(self, action: str): """Execute manual trading action - FIXED to properly execute and track trades""" @@ -1467,6 +1581,36 @@ class CleanTradingDashboard: 'training_ready': True # Mark as ready for cold start training } + # Create/update current position for unrealized P&L tracking + if action == 'BUY': + # Create or add to LONG position + self.current_position = { + 'side': 'LONG', + 'size': 0.004, # Example position size as mentioned by user + 'price': current_price, + 'symbol': symbol, + 'entry_time': datetime.now(), + 'leverage': 50 + } + logger.info(f"Created LONG position: {self.current_position['size']:.3f} @ ${current_price:.2f}") + else: # SELL + # Create SHORT position or close LONG + if self.current_position and self.current_position.get('side') == 'LONG': + # Close LONG position + self.current_position = None + logger.info("Closed LONG position") + else: + # Create SHORT position + self.current_position = { + 'side': 'SHORT', + 'size': 0.004, + 'price': current_price, + 'symbol': symbol, + 'entry_time': datetime.now(), + 'leverage': 50 + } + logger.info(f"Created SHORT position: {self.current_position['size']:.3f} @ ${current_price:.2f}") + # Add to closed trades for display self.closed_trades.append(trade_record) @@ -1712,11 +1856,70 @@ class CleanTradingDashboard: self.closed_trades = [] self.recent_decisions = [] + # Clear tick cache and associated signals + self.tick_cache = [] + self.ws_price_cache = {} + self.current_prices = {} + + # Clear current position + self.current_position = None + logger.info("Session data cleared") except Exception as e: logger.error(f"Error clearing session: {e}") + def _clear_old_signals_for_tick_range(self): + """Clear old signals that are outside the current tick cache time range""" + try: + if not self.tick_cache or len(self.tick_cache) == 0: + return + + # Get the time range of the current tick cache + oldest_tick_time = self.tick_cache[0].get('datetime') + if not oldest_tick_time: + return + + # Filter recent_decisions to only keep signals within the tick cache time range + filtered_decisions = [] + for signal in self.recent_decisions: + signal_time = signal.get('timestamp') + if signal_time: + # Convert signal timestamp to datetime for comparison + try: + if isinstance(signal_time, str): + # Handle time-only format (HH:MM:SS) + if ':' in signal_time and len(signal_time.split(':')) == 3: + signal_datetime = datetime.now().replace( + hour=int(signal_time.split(':')[0]), + minute=int(signal_time.split(':')[1]), + second=int(signal_time.split(':')[2]), + microsecond=0 + ) + else: + signal_datetime = pd.to_datetime(signal_time) + else: + signal_datetime = signal_time + + # Keep signal if it's within the tick cache time range + if signal_datetime >= oldest_tick_time: + filtered_decisions.append(signal) + + except Exception: + # Keep signal if we can't parse the timestamp + filtered_decisions.append(signal) + else: + # Keep signal if no timestamp + filtered_decisions.append(signal) + + # Update the decisions list + self.recent_decisions = filtered_decisions + + logger.debug(f"Cleared old signals: kept {len(filtered_decisions)} signals within tick range") + + except Exception as e: + logger.warning(f"Error clearing old signals: {e}") + def _initialize_cob_integration_proper(self): """Initialize COB integration - Basic orchestrator has no COB features""" try: @@ -1890,6 +2093,8 @@ class CleanTradingDashboard: self.tick_cache.append(tick_record) if len(self.tick_cache) > 1000: self.tick_cache = self.tick_cache[-1000:] + # Clear old signals when tick cache is trimmed + self._clear_old_signals_for_tick_range() # NO COB SIMULATION - Real COB data comes from enhanced orchestrator @@ -2026,6 +2231,8 @@ class CleanTradingDashboard: self.tick_cache.extend(data_packet['ticks'][-50:]) # Last 50 ticks if len(self.tick_cache) > 1000: self.tick_cache = self.tick_cache[-1000:] + # Clear old signals when tick cache is trimmed + self._clear_old_signals_for_tick_range() if 'ohlcv' in data_packet: # Update multi-timeframe data for both ETH and BTC (BTC for reference) diff --git a/web/layout_manager.py b/web/layout_manager.py index 74edd7f..1f675e9 100644 --- a/web/layout_manager.py +++ b/web/layout_manager.py @@ -51,17 +51,18 @@ class DashboardLayoutManager: return html.Div([ self._create_metrics_and_signals_row(), self._create_charts_row(), - self._create_analytics_row(), - self._create_performance_row() + self._create_analytics_and_performance_row() ]) def _create_metrics_and_signals_row(self): - """Create the top row with key metrics and recent signals""" + """Create the top row with key metrics, recent signals, and session controls""" return html.Div([ # Left side - Key metrics (compact cards) self._create_metrics_grid(), - # Right side - Recent Signals & Model Training - self._create_signals_and_training_panels() + # Middle - Recent Signals + self._create_signals_panel(), + # Right side - Session Controls + self._create_session_controls_panel() ], className="d-flex mb-3") def _create_metrics_grid(self): @@ -96,10 +97,9 @@ class DashboardLayoutManager: } ) - def _create_signals_and_training_panels(self): - """Create the signals and training panels""" + def _create_signals_panel(self): + """Create the signals panel""" return html.Div([ - # Recent Trading Signals Column (50%) html.Div([ html.Div([ html.H6([ @@ -108,19 +108,39 @@ class DashboardLayoutManager: ], className="card-title mb-2"), html.Div(id="recent-decisions", style={"height": "160px", "overflowY": "auto"}) ], className="card-body p-2") - ], className="card", style={"width": "48%"}), - - # Model Training + COB Buckets Column (50%) + ], className="card") + ], style={"width": "35%", "marginLeft": "2%"}) + + def _create_session_controls_panel(self): + """Create the session controls panel""" + return html.Div([ html.Div([ html.Div([ html.H6([ - html.I(className="fas fa-brain me-2"), - "Training Progress & COB $1 Buckets" + html.I(className="fas fa-cog me-2"), + "Session Controls" ], className="card-title mb-2"), - html.Div(id="training-metrics", style={"height": "160px", "overflowY": "auto"}) + html.Button([ + html.I(className="fas fa-trash me-1"), + "Clear Session" + ], id="clear-session-btn", className="btn btn-warning btn-sm w-100"), + html.Hr(className="my-2"), + html.Small("System Status", className="text-muted d-block mb-1"), + html.Div([ + html.Span("Trading: ", className="small"), + html.Span("SIMULATION", className="badge bg-info small") + ], className="mb-1"), + html.Div([ + html.Span("Data: ", className="small"), + html.Span("Active", className="badge bg-success small") + ], className="mb-1"), + html.Div([ + html.Span("WebSocket: ", className="small"), + html.Span("Connected", className="badge bg-success small") + ]) ], className="card-body p-2") - ], className="card", style={"width": "48%", "marginLeft": "4%"}), - ], style={"width": "48%", "marginLeft": "2%", "display": "flex"}) + ], className="card") + ], style={"width": "23%", "marginLeft": "2%"}) def _create_charts_row(self): """Create the charts row with price chart and manual trading buttons""" @@ -154,68 +174,58 @@ class DashboardLayoutManager: ], className="card") ]) - def _create_analytics_row(self): - """Create the analytics row with COB data and system status""" + def _create_analytics_and_performance_row(self): + """Create the combined analytics and performance row with COB data, trades, and training progress""" return html.Div([ - # COB Status + # Left side - COB panels and trades (68% width) html.Div([ + # Top section - COB panels html.Div([ - html.H6([ - html.I(className="fas fa-server me-2"), - "System Status" - ], className="card-title mb-2"), - html.Div(id="cob-status-content") - ], className="card-body p-2") - ], className="card", style={"width": "32%"}), + # ETH/USDT COB + html.Div([ + html.Div([ + html.H6([ + html.I(className="fab fa-ethereum me-2"), + "ETH/USDT COB" + ], className="card-title mb-2"), + html.Div(id="eth-cob-content") + ], className="card-body p-2") + ], className="card", style={"width": "48%"}), + + # BTC/USDT COB + html.Div([ + html.Div([ + html.H6([ + html.I(className="fab fa-bitcoin me-2"), + "BTC/USDT COB" + ], className="card-title mb-2"), + html.Div(id="btc-cob-content") + ], className="card-body p-2") + ], className="card", style={"width": "48%", "marginLeft": "4%"}) + ], className="d-flex mb-3"), + + # Bottom section - Closed Trades + html.Div([ + html.Div([ + html.H6([ + html.I(className="fas fa-history me-2"), + "Closed Trades" + ], className="card-title mb-2"), + html.Div(id="closed-trades-table", style={"height": "250px", "overflowY": "auto"}) + ], className="card-body p-2") + ], className="card") + ], style={"width": "68%"}), - # ETH/USDT COB + # Right side - Training Progress & COB $1 Buckets (30% width, spans full height) html.Div([ html.Div([ html.H6([ - html.I(className="fab fa-ethereum me-2"), - "ETH/USDT COB" + html.I(className="fas fa-brain me-2"), + "Training Progress & COB $1 Buckets" ], className="card-title mb-2"), - html.Div(id="eth-cob-content") + html.Div(id="training-metrics", style={"height": "550px", "overflowY": "auto"}) ], className="card-body p-2") - ], className="card", style={"width": "32%", "marginLeft": "2%"}), - - # BTC/USDT COB - html.Div([ - html.Div([ - html.H6([ - html.I(className="fab fa-bitcoin me-2"), - "BTC/USDT COB" - ], className="card-title mb-2"), - html.Div(id="btc-cob-content") - ], className="card-body p-2") - ], className="card", style={"width": "32%", "marginLeft": "2%"}) - ], className="d-flex mb-3") + ], className="card", style={"width": "30%", "marginLeft": "2%"}) + ], className="d-flex") - def _create_performance_row(self): - """Create the performance row with closed trades and session controls""" - return html.Div([ - # Closed Trades Table - html.Div([ - html.Div([ - html.H6([ - html.I(className="fas fa-history me-2"), - "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": "70%"}), - - # Session Controls - html.Div([ - html.Div([ - html.H6([ - html.I(className="fas fa-cog me-2"), - "Session Controls" - ], className="card-title mb-2"), - html.Button([ - html.I(className="fas fa-trash me-1"), - "Clear Session" - ], id="clear-session-btn", className="btn btn-warning btn-sm w-100") - ], className="card-body p-2") - ], className="card", style={"width": "28%", "marginLeft": "2%"}) - ], className="d-flex") \ No newline at end of file + \ No newline at end of file