diff --git a/realtime.py b/realtime.py index b0d77a2..ecc840f 100644 --- a/realtime.py +++ b/realtime.py @@ -1457,6 +1457,7 @@ class RealTimeChart: # Set up callbacks self._setup_interval_callback(button_style, active_button_style) self._setup_chart_callback() + self._setup_position_list_callback() # We've removed the ticks callback, so don't call it # self._setup_ticks_callback() @@ -1497,6 +1498,29 @@ class RealTimeChart: # Main chart dcc.Graph(id='live-chart', style={'height': '75vh'}), + # Last 5 Positions component + html.Div([ + html.H4("Last 5 Positions", style={'textAlign': 'center', 'color': '#FFFFFF'}), + html.Table([ + html.Thead( + html.Tr([ + html.Th("Action", style={'padding': '8px', 'border': '1px solid #444', 'backgroundColor': '#333'}), + html.Th("Size", style={'padding': '8px', 'border': '1px solid #444', 'backgroundColor': '#333'}), + html.Th("Entry Price", style={'padding': '8px', 'border': '1px solid #444', 'backgroundColor': '#333'}), + html.Th("Exit Price", style={'padding': '8px', 'border': '1px solid #444', 'backgroundColor': '#333'}), + html.Th("PnL", style={'padding': '8px', 'border': '1px solid #444', 'backgroundColor': '#333'}), + html.Th("Time", style={'padding': '8px', 'border': '1px solid #444', 'backgroundColor': '#333'}) + ]) + ), + html.Tbody(id='position-table-body') + ], style={ + 'width': '100%', + 'borderCollapse': 'collapse', + 'color': '#FFFFFF', + 'backgroundColor': '#222' + }) + ], style={'marginTop': '20px', 'marginBottom': '20px', 'overflowX': 'auto'}) + # Chart acknowledgment html.Div("Real-time trading chart with ML signals", style={ 'textAlign': 'center', @@ -2317,6 +2341,46 @@ class RealTimeChart: ) return fig + def _setup_position_list_callback(self): + """Callback to update the position list with the latest 5 trades""" + @self.app.callback( + Output('position-list', 'children'), + [Input('interval-component', 'n_intervals')] + ) + def update_position_list(n): + try: + # Get the last 5 positions + if not hasattr(self, 'trades') or not self.trades: + return [html.Li("No trades yet", style={'color': '#AAAAAA'})] + + last_positions = self.trades[-5:] + position_items = [] + + for trade in last_positions: + action = trade.get('action', 'UNKNOWN') + price = trade.get('price', 'N/A') + timestamp = trade.get('timestamp', 'N/A') + pnl = trade.get('pnl', None) + + # Format the display + pnl_str = f", PnL: {pnl:.4f}" if pnl is not None else "" + time_str = timestamp.strftime('%H:%M:%S') if isinstance(timestamp, datetime) else str(timestamp) + + # Set color based on action + color = '#00FF00' if action == 'BUY' else '#FF0000' + + position_items.append( + html.Li( + f"{action} @ {price} ({time_str}){pnl_str}", + style={'color': color, 'padding': '5px', 'borderBottom': '1px solid #333'} + ) + ) + + return position_items + except Exception as e: + logger.error(f"Error updating position list: {str(e)}") + return [html.Li("Error loading positions", style={'color': '#FF0000'})] + def _interval_to_seconds(self, interval_key: str) -> int: """Convert interval key to seconds""" mapping = { @@ -2761,6 +2825,18 @@ class RealTimeChart: # Add to our trades list if not hasattr(self, 'trades'): self.trades = [] + + # If this is a SELL trade, try to find the corresponding BUY trade and update it with close_price + if trade_type == 'SELL' and len(self.trades) > 0: + for i in range(len(self.trades) - 1, -1, -1): + prev_trade = self.trades[i] + if prev_trade.get('action') == 'BUY' and 'close_price' not in prev_trade: + # Found a BUY trade without a close_price, consider it the matching trade + prev_trade['close_price'] = price + prev_trade['close_timestamp'] = timestamp + logger.info(f"Updated BUY trade at {prev_trade['timestamp']} with close price {price}") + break + self.trades.append(trade) # Log the trade for debugging @@ -2773,11 +2849,17 @@ class RealTimeChart: try: # Only update if we have a dash app running # This is a workaround to make trades appear immediately - interval_component = self.app.get_asset_url('interval-component') - if interval_component: - self.app.callback_map[interval_component]['callback']() - except: + callback_context = dash.callback_context + # Force an update by triggering the callback + for callback_id, callback_info in self.app.callback_map.items(): + if 'live-chart' in callback_id: + # Found the chart callback, try to trigger it + logger.debug(f"Triggering chart update callback after trade") + callback_info['callback']() + break + except Exception as e: # If callback triggering fails, it's not critical + logger.debug(f"Failed to trigger chart update: {str(e)}") pass return trade @@ -2863,3 +2945,4 @@ if __name__ == "__main__": asyncio.run(main()) except KeyboardInterrupt: logger.info("Application terminated by user") +