list last 5 positions

This commit is contained in:
Dobromir Popov 2025-04-01 17:40:00 +03:00
parent e434b96860
commit 989e755264

View File

@ -1457,6 +1457,7 @@ class RealTimeChart:
# Set up callbacks # Set up callbacks
self._setup_interval_callback(button_style, active_button_style) self._setup_interval_callback(button_style, active_button_style)
self._setup_chart_callback() self._setup_chart_callback()
self._setup_position_list_callback()
# We've removed the ticks callback, so don't call it # We've removed the ticks callback, so don't call it
# self._setup_ticks_callback() # self._setup_ticks_callback()
@ -1497,6 +1498,29 @@ class RealTimeChart:
# Main chart # Main chart
dcc.Graph(id='live-chart', style={'height': '75vh'}), 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 # Chart acknowledgment
html.Div("Real-time trading chart with ML signals", style={ html.Div("Real-time trading chart with ML signals", style={
'textAlign': 'center', 'textAlign': 'center',
@ -2317,6 +2341,46 @@ class RealTimeChart:
) )
return fig 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: def _interval_to_seconds(self, interval_key: str) -> int:
"""Convert interval key to seconds""" """Convert interval key to seconds"""
mapping = { mapping = {
@ -2761,6 +2825,18 @@ class RealTimeChart:
# Add to our trades list # Add to our trades list
if not hasattr(self, 'trades'): if not hasattr(self, 'trades'):
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) self.trades.append(trade)
# Log the trade for debugging # Log the trade for debugging
@ -2773,11 +2849,17 @@ class RealTimeChart:
try: try:
# Only update if we have a dash app running # Only update if we have a dash app running
# This is a workaround to make trades appear immediately # This is a workaround to make trades appear immediately
interval_component = self.app.get_asset_url('interval-component') callback_context = dash.callback_context
if interval_component: # Force an update by triggering the callback
self.app.callback_map[interval_component]['callback']() for callback_id, callback_info in self.app.callback_map.items():
except: 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 # If callback triggering fails, it's not critical
logger.debug(f"Failed to trigger chart update: {str(e)}")
pass pass
return trade return trade
@ -2863,3 +2945,4 @@ if __name__ == "__main__":
asyncio.run(main()) asyncio.run(main())
except KeyboardInterrupt: except KeyboardInterrupt:
logger.info("Application terminated by user") logger.info("Application terminated by user")