working actions dsplay, rewrite chart in realtime.py

This commit is contained in:
Dobromir Popov 2025-04-01 21:11:21 +03:00
parent 902593b5f3
commit 44b02b4e7d
3 changed files with 1682 additions and 1429 deletions

File diff suppressed because it is too large Load Diff

View File

@ -22,6 +22,7 @@ import tzlocal
import threading import threading
import random import random
import dash_bootstrap_components as dbc import dash_bootstrap_components as dbc
import uuid
# Configure logging with more detailed format # Configure logging with more detailed format
logging.basicConfig( logging.basicConfig(
@ -2907,97 +2908,45 @@ class RealTimeChart:
logger.info(f"Added NN signal: {signal_type} at {timestamp}") logger.info(f"Added NN signal: {signal_type} at {timestamp}")
def add_trade(self, price, timestamp, pnl=None, amount=0.1, action=None, type=None): def add_trade(self, price, timestamp, amount, pnl=0.0, action="BUY"):
"""Add a trade to be displayed on the chart """Add a trade to the chart and update the positions list"""
# Ensure the positions list exists
if not hasattr(self, 'positions'):
self.positions = []
Args: # Create position ID
price: The price at which the trade was executed position_id = str(uuid.uuid4())[:8]
timestamp: The timestamp for the trade
pnl: Optional profit and loss value for the trade
amount: Amount traded
action: The type of trade (BUY or SELL) - alternative to type parameter
type: The type of trade (BUY or SELL) - alternative to action parameter
"""
# Handle both action and type parameters for backward compatibility
trade_type = type or action
# Default to BUY if trade_type is None or not specified # Log the trade
if trade_type is None: logger.info(f"Added {action} trade: price={price}, amount={amount}, time={timestamp}, PnL={pnl}")
logger.warning(f"Trade type not specified in add_trade call, defaulting to BUY. Price: {price}, Timestamp: {timestamp}")
trade_type = "BUY"
if isinstance(trade_type, int): # Add trade marker to the chart
trade_type = "BUY" if trade_type == 0 else "SELL" if action == "BUY":
color = 'green'
marker = 'triangle-up'
else: # SELL
color = 'red'
marker = 'triangle-down'
# Ensure trade_type is uppercase if it's a string # Add to positions list
if isinstance(trade_type, str): new_position = Position(
trade_type = trade_type.upper() action=action,
entry_price=price,
amount=amount,
timestamp=timestamp,
trade_id=position_id
)
self.positions.append(new_position)
if trade_type not in ['BUY', 'SELL']: # Limit the positions list to the last 10 entries
logger.warning(f"Invalid trade type: {trade_type} (value type: {type(trade_type).__name__}), defaulting to BUY. Price: {price}, Timestamp: {timestamp}") if len(self.positions) > 10:
trade_type = "BUY" self.positions = self.positions[-10:]
# Convert timestamp to datetime if it's not already # Add to the figure
if not isinstance(timestamp, datetime): self._add_trade_marker(price, timestamp, color, marker)
try:
if isinstance(timestamp, str):
timestamp = datetime.fromisoformat(timestamp.replace('Z', '+00:00'))
elif isinstance(timestamp, (int, float)):
timestamp = datetime.fromtimestamp(timestamp / 1000.0)
except Exception as e:
logger.error(f"Error converting timestamp for trade: {str(e)}")
timestamp = datetime.now()
# Create the trade object # Trigger update callback
trade = { self._update_chart_and_positions()
'price': price,
'timestamp': timestamp,
'pnl': pnl,
'amount': amount,
'action': trade_type
}
# 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
pnl_str = f" with PnL: {pnl}" if pnl is not None else ""
logger.info(f"Added trade: {trade_type} {amount} at price {price} at time {timestamp}{pnl_str}")
# Trigger a more frequent update of the chart by scheduling a callback
# This helps ensure the trade appears immediately on the chart
if hasattr(self, 'app') and self.app is not None:
try:
# Only update if we have a dash app running
# This is a workaround to make trades appear immediately
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
def update_trading_info(self, signal=None, position=None, balance=None, pnl=None): def update_trading_info(self, signal=None, position=None, balance=None, pnl=None):
"""Update the current trading information to be displayed on the chart """Update the current trading information to be displayed on the chart
@ -3026,6 +2975,62 @@ class RealTimeChart:
logger.debug(f"Updated trading info: Signal={self.current_signal}, Position={self.current_position}, Balance=${self.session_balance:.2f}, PnL={self.session_pnl:.4f}") logger.debug(f"Updated trading info: Signal={self.current_signal}, Position={self.current_position}, Balance=${self.session_balance:.2f}, PnL={self.session_pnl:.4f}")
def _add_trade_marker(self, price, timestamp, color, marker):
"""Add a trade marker to the chart
Args:
price: The price at which the trade was executed
timestamp: The timestamp for the trade
color: The color of the marker (green for buy, red for sell)
marker: The marker symbol to use (triangle-up for buy, triangle-down for sell)
"""
# Convert timestamp to datetime if it's not already
if not isinstance(timestamp, datetime):
try:
if isinstance(timestamp, str):
timestamp = datetime.fromisoformat(timestamp.replace('Z', '+00:00'))
elif isinstance(timestamp, (int, float)):
timestamp = datetime.fromtimestamp(timestamp / 1000.0)
except Exception as e:
logger.error(f"Error converting timestamp for trade marker: {str(e)}")
timestamp = datetime.now()
# Add marker to the figure
self.fig.add_trace(
go.Scatter(
x=[timestamp],
y=[price],
mode='markers',
name='BUY' if marker == 'triangle-up' else 'SELL',
marker=dict(
symbol=marker,
size=12,
color=f'rgba({0 if color == "green" else 255},{255 if color == "green" else 0},0,0.8)',
line=dict(width=1, color='darkgreen' if color == 'green' else 'darkred')
),
showlegend=True
),
row=1, col=1
)
# Update the chart
self._update_chart_and_positions()
def _update_chart_and_positions(self):
"""Update the chart and positions list"""
try:
# Update the chart
self._update_chart()
# Update the positions list in the UI
if hasattr(self, 'app') and self.app is not None:
self.app.callback_context.triggered = [{'prop_id': 'interval-component.n_intervals'}]
self.app.callback_map['position-list.children']['callback']()
except Exception as e:
logger.error(f"Error updating chart and positions: {str(e)}")
import traceback
logger.error(traceback.format_exc())
async def main(): async def main():
global charts # Make charts globally accessible for NN integration global charts # Make charts globally accessible for NN integration
symbols = ["ETH/USDT", "BTC/USDT"] symbols = ["ETH/USDT", "BTC/USDT"]

File diff suppressed because it is too large Load Diff