diff --git a/_notes.md b/_notes.md index 6d71aa8..8c51da0 100644 --- a/_notes.md +++ b/_notes.md @@ -55,4 +55,6 @@ python test_model.py python train_with_realtime_ticks.py python NN/train_rl.py -python train_rl_with_realtime.py \ No newline at end of file +python train_rl_with_realtime.py + +python train_rl_with_realtime.py --episodes 2 --no-train --visualize-only \ No newline at end of file diff --git a/realtime.py b/realtime.py index 7323f3d..8d8aa34 100644 --- a/realtime.py +++ b/realtime.py @@ -1,7 +1,7 @@ import asyncio import json import logging -import datetime + from typing import Dict, List, Optional import websockets import plotly.graph_objects as go @@ -1694,13 +1694,13 @@ class RealTimeChart: else: df_1d = self.tick_storage.get_candles(interval_seconds=86400) - # Limit the number of candles to display + # Limit the number of candles to display but show more for context if df_1m is not None and not df_1m.empty: - df_1m = df_1m.tail(100) + df_1m = df_1m.tail(200) # Show more 1m candles (3+ hours) if df_1h is not None and not df_1h.empty: - df_1h = df_1h.tail(48) # Last 2 days + df_1h = df_1h.tail(72) # Show 3 days of hourly data if df_1d is not None and not df_1d.empty: - df_1d = df_1d.tail(30) # Last month + df_1d = df_1d.tail(60) # Show 2 months of daily data except Exception as e: logger.error(f"Error getting additional timeframes: {str(e)}") @@ -1757,6 +1757,155 @@ class RealTimeChart: ), row=2, col=1 ) + + # Add buy/sell markers and PnL annotations to the main chart + if hasattr(self, 'trades') and self.trades: + buy_times = [] + buy_prices = [] + buy_markers = [] + sell_times = [] + sell_prices = [] + sell_markers = [] + + # Filter trades to only include recent ones (last 100) + recent_trades = self.trades[-100:] + + for trade in recent_trades: + # Convert timestamp to datetime if it's not already + trade_time = trade.get('timestamp') + if isinstance(trade_time, (int, float)): + trade_time = pd.to_datetime(trade_time, unit='ms') + + price = trade.get('price', 0) + pnl = trade.get('pnl', None) + action = trade.get('action', 'SELL') # Default to SELL + + if action == 'BUY': + buy_times.append(trade_time) + buy_prices.append(price) + buy_markers.append("") + elif action == 'SELL': + sell_times.append(trade_time) + sell_prices.append(price) + # Add PnL as marker text if available + if pnl is not None: + pnl_text = f"{pnl:.4f}" if abs(pnl) < 0.01 else f"{pnl:.2f}" + sell_markers.append(pnl_text) + else: + sell_markers.append("") + + # Add buy markers + if buy_times: + fig.add_trace( + go.Scatter( + x=buy_times, + y=buy_prices, + mode='markers', + name='Buy', + marker=dict( + symbol='triangle-up', + size=12, + color='rgba(0,255,0,0.8)', + line=dict(width=1, color='darkgreen') + ), + text=buy_markers, + hoverinfo='x+y+text' + ), + row=1, col=1 + ) + + # Add vertical and horizontal connecting lines for buys + for i, (btime, bprice) in enumerate(zip(buy_times, buy_prices)): + # Add vertical dashed line to time axis + fig.add_shape( + type="line", + x0=btime, x1=btime, + y0=y_min, y1=bprice, + line=dict(color="rgba(0,255,0,0.5)", width=1, dash="dash"), + row=1, col=1 + ) + # Add horizontal dashed line showing the price level + fig.add_shape( + type="line", + x0=df.index.min(), x1=btime, + y0=bprice, y1=bprice, + line=dict(color="rgba(0,255,0,0.5)", width=1, dash="dash"), + row=1, col=1 + ) + + # Add sell markers with PnL annotations + if sell_times: + fig.add_trace( + go.Scatter( + x=sell_times, + y=sell_prices, + mode='markers+text', + name='Sell', + marker=dict( + symbol='triangle-down', + size=12, + color='rgba(255,0,0,0.8)', + line=dict(width=1, color='darkred') + ), + text=sell_markers, + textposition='top center', + textfont=dict(size=10), + hoverinfo='x+y+text' + ), + row=1, col=1 + ) + + # Add vertical and horizontal connecting lines for sells + for i, (stime, sprice) in enumerate(zip(sell_times, sell_prices)): + # Add vertical dashed line to time axis + fig.add_shape( + type="line", + x0=stime, x1=stime, + y0=y_min, y1=sprice, + line=dict(color="rgba(255,0,0,0.5)", width=1, dash="dash"), + row=1, col=1 + ) + # Add horizontal dashed line showing the price level + fig.add_shape( + type="line", + x0=df.index.min(), x1=stime, + y0=sprice, y1=sprice, + line=dict(color="rgba(255,0,0,0.5)", width=1, dash="dash"), + row=1, col=1 + ) + + # Add connecting lines between consecutive buy-sell pairs + if len(buy_times) > 0 and len(sell_times) > 0: + # Create pairs of buy-sell trades based on timestamps + pairs = [] + buys_copy = list(zip(buy_times, buy_prices)) + + for i, (stime, sprice) in enumerate(zip(sell_times, sell_prices)): + # Find the most recent buy before this sell + matching_buy = None + for j, (btime, bprice) in enumerate(buys_copy): + if btime < stime: + matching_buy = (btime, bprice) + buys_copy.pop(j) # Remove this buy to prevent reuse + break + + if matching_buy: + pairs.append((matching_buy, (stime, sprice))) + + # Add connecting lines for each pair + for (btime, bprice), (stime, sprice) in pairs: + # Draw line connecting the buy and sell points + fig.add_shape( + type="line", + x0=btime, x1=stime, + y0=bprice, y1=sprice, + line=dict( + color="rgba(255,255,255,0.5)", + width=1, + dash="dot" + ), + row=1, col=1 + ) # Add 1m chart if df_1m is not None and not df_1m.empty and 'open' in df_1m.columns: @@ -1772,7 +1921,100 @@ class RealTimeChart: ), row=3, col=1 ) - fig.update_xaxes(title_text="", row=3, col=1) + + # Set appropriate date format for 1m chart + fig.update_xaxes( + title_text="", + row=3, + col=1, + tickformat="%H:%M", + tickmode="auto", + nticks=12 + ) + + # Add buy/sell markers to 1m chart if they fall within the visible timeframe + if hasattr(self, 'trades') and self.trades: + # Filter trades visible in 1m timeframe + min_time = df_1m.index.min() + max_time = df_1m.index.max() + + # Ensure min_time and max_time are pandas.Timestamp objects + if isinstance(min_time, (int, float)): + min_time = pd.to_datetime(min_time, unit='ms') + if isinstance(max_time, (int, float)): + max_time = pd.to_datetime(max_time, unit='ms') + + # Collect only trades within this timeframe + minute_buy_times = [] + minute_buy_prices = [] + minute_sell_times = [] + minute_sell_prices = [] + + for trade in self.trades[-100:]: + trade_time = trade.get('timestamp') + if isinstance(trade_time, (int, float)): + # Convert numeric timestamp to datetime + trade_time = pd.to_datetime(trade_time, unit='ms') + elif not isinstance(trade_time, pd.Timestamp) and not isinstance(trade_time, datetime): + # Skip trades with invalid timestamp format + continue + + # Check if trade falls within 1m chart timeframe + try: + if min_time <= trade_time <= max_time: + price = trade.get('price', 0) + action = trade.get('action', 'SELL') + + if action == 'BUY': + minute_buy_times.append(trade_time) + minute_buy_prices.append(price) + elif action == 'SELL': + minute_sell_times.append(trade_time) + minute_sell_prices.append(price) + except TypeError: + # If comparison fails due to type mismatch, log the error and skip this trade + logger.warning(f"Type mismatch in timestamp comparison: min_time={type(min_time)}, trade_time={type(trade_time)}") + continue + + # Add buy markers to 1m chart + if minute_buy_times: + fig.add_trace( + go.Scatter( + x=minute_buy_times, + y=minute_buy_prices, + mode='markers', + name='Buy (1m)', + marker=dict( + symbol='triangle-up', + size=8, + color='rgba(0,255,0,0.8)', + line=dict(width=1, color='darkgreen') + ), + showlegend=False, + hoverinfo='x+y' + ), + row=3, col=1 + ) + + # Add sell markers to 1m chart + if minute_sell_times: + fig.add_trace( + go.Scatter( + x=minute_sell_times, + y=minute_sell_prices, + mode='markers', + name='Sell (1m)', + marker=dict( + symbol='triangle-down', + size=8, + color='rgba(255,0,0,0.8)', + line=dict(width=1, color='darkred') + ), + showlegend=False, + hoverinfo='x+y' + ), + row=3, col=1 + ) # Add 1h chart if df_1h is not None and not df_1h.empty and 'open' in df_1h.columns: @@ -1788,8 +2030,100 @@ class RealTimeChart: ), row=4, col=1 ) - fig.update_xaxes(title_text="", row=4, col=1) + # Set appropriate date format for 1h chart + fig.update_xaxes( + title_text="", + row=4, + col=1, + tickformat="%m-%d %H:%M", + tickmode="auto", + nticks=8 + ) + + # Add buy/sell markers to 1h chart if they fall within the visible timeframe + if hasattr(self, 'trades') and self.trades: + # Filter trades visible in 1h timeframe + min_time = df_1h.index.min() + max_time = df_1h.index.max() + + # Ensure min_time and max_time are pandas.Timestamp objects + if isinstance(min_time, (int, float)): + min_time = pd.to_datetime(min_time, unit='ms') + if isinstance(max_time, (int, float)): + max_time = pd.to_datetime(max_time, unit='ms') + + # Collect only trades within this timeframe + hour_buy_times = [] + hour_buy_prices = [] + hour_sell_times = [] + hour_sell_prices = [] + + for trade in self.trades[-200:]: # Check more trades for longer timeframe + trade_time = trade.get('timestamp') + if isinstance(trade_time, (int, float)): + # Convert numeric timestamp to datetime + trade_time = pd.to_datetime(trade_time, unit='ms') + elif not isinstance(trade_time, pd.Timestamp) and not isinstance(trade_time, datetime): + # Skip trades with invalid timestamp format + continue + + # Check if trade falls within 1h chart timeframe + try: + if min_time <= trade_time <= max_time: + price = trade.get('price', 0) + action = trade.get('action', 'SELL') + + if action == 'BUY': + hour_buy_times.append(trade_time) + hour_buy_prices.append(price) + elif action == 'SELL': + hour_sell_times.append(trade_time) + hour_sell_prices.append(price) + except TypeError: + # If comparison fails due to type mismatch, log the error and skip this trade + logger.warning(f"Type mismatch in timestamp comparison: min_time={type(min_time)}, trade_time={type(trade_time)}") + continue + + # Add buy markers to 1h chart + if hour_buy_times: + fig.add_trace( + go.Scatter( + x=hour_buy_times, + y=hour_buy_prices, + mode='markers', + name='Buy (1h)', + marker=dict( + symbol='triangle-up', + size=6, + color='rgba(0,255,0,0.8)', + line=dict(width=1, color='darkgreen') + ), + showlegend=False, + hoverinfo='x+y' + ), + row=4, col=1 + ) + + # Add sell markers to 1h chart + if hour_sell_times: + fig.add_trace( + go.Scatter( + x=hour_sell_times, + y=hour_sell_prices, + mode='markers', + name='Sell (1h)', + marker=dict( + symbol='triangle-down', + size=6, + color='rgba(255,0,0,0.8)', + line=dict(width=1, color='darkred') + ), + showlegend=False, + hoverinfo='x+y' + ), + row=4, col=1 + ) # Add 1d chart if df_1d is not None and not df_1d.empty and 'open' in df_1d.columns: fig.add_trace( @@ -1804,7 +2138,100 @@ class RealTimeChart: ), row=5, col=1 ) - fig.update_xaxes(title_text="", row=5, col=1) + + # Set appropriate date format for 1d chart + fig.update_xaxes( + title_text="", + row=5, + col=1, + tickformat="%Y-%m-%d", + tickmode="auto", + nticks=10 + ) + + # Add buy/sell markers to 1d chart if they fall within the visible timeframe + if hasattr(self, 'trades') and self.trades: + # Filter trades visible in 1d timeframe + min_time = df_1d.index.min() + max_time = df_1d.index.max() + + # Ensure min_time and max_time are pandas.Timestamp objects + if isinstance(min_time, (int, float)): + min_time = pd.to_datetime(min_time, unit='ms') + if isinstance(max_time, (int, float)): + max_time = pd.to_datetime(max_time, unit='ms') + + # Collect only trades within this timeframe + day_buy_times = [] + day_buy_prices = [] + day_sell_times = [] + day_sell_prices = [] + + for trade in self.trades[-300:]: # Check more trades for daily timeframe + trade_time = trade.get('timestamp') + if isinstance(trade_time, (int, float)): + # Convert numeric timestamp to datetime + trade_time = pd.to_datetime(trade_time, unit='ms') + elif not isinstance(trade_time, pd.Timestamp) and not isinstance(trade_time, datetime): + # Skip trades with invalid timestamp format + continue + + # Check if trade falls within 1d chart timeframe + try: + if min_time <= trade_time <= max_time: + price = trade.get('price', 0) + action = trade.get('action', 'SELL') + + if action == 'BUY': + day_buy_times.append(trade_time) + day_buy_prices.append(price) + elif action == 'SELL': + day_sell_times.append(trade_time) + day_sell_prices.append(price) + except TypeError: + # If comparison fails due to type mismatch, log the error and skip this trade + logger.warning(f"Type mismatch in timestamp comparison: min_time={type(min_time)}, trade_time={type(trade_time)}") + continue + + # Add buy markers to 1d chart + if day_buy_times: + fig.add_trace( + go.Scatter( + x=day_buy_times, + y=day_buy_prices, + mode='markers', + name='Buy (1d)', + marker=dict( + symbol='triangle-up', + size=5, + color='rgba(0,255,0,0.8)', + line=dict(width=1, color='darkgreen') + ), + showlegend=False, + hoverinfo='x+y' + ), + row=5, col=1 + ) + + # Add sell markers to 1d chart + if day_sell_times: + fig.add_trace( + go.Scatter( + x=day_sell_times, + y=day_sell_prices, + mode='markers', + name='Sell (1d)', + marker=dict( + symbol='triangle-down', + size=5, + color='rgba(255,0,0,0.8)', + line=dict(width=1, color='darkred') + ), + showlegend=False, + hoverinfo='x+y' + ), + row=5, col=1 + ) # Add trading info annotation if available if hasattr(self, 'current_signal') and self.current_signal: @@ -1813,12 +2240,16 @@ class RealTimeChart: # Format position value position_text = f"{self.current_position:.4f}" if self.current_position < 0.01 else f"{self.current_position:.2f}" + # Format PnL with color based on value + pnl_color = "#33DD33" if self.session_pnl >= 0 else "#FF4444" + pnl_text = f"{self.session_pnl:.4f}" + # Create trading info text info_text = ( f"Signal: {self.current_signal} | " f"Position: {position_text} | " f"Balance: ${self.session_balance:.2f} | " - f"PnL: {self.session_pnl:.4f}" + f"PnL: {pnl_text}" ) # Add annotation @@ -1858,10 +2289,22 @@ class RealTimeChart: xaxis5_rangeslider_visible=False ) + # Improve date formatting for the main chart + fig.update_xaxes( + title_text="", + row=1, + col=1, + tickformat="%H:%M:%S", + tickmode="auto", + nticks=15 + ) + return fig except Exception as e: logger.error(f"Error updating chart: {str(e)}") + import traceback + logger.error(traceback.format_exc()) fig = go.Figure() fig.add_annotation( x=0.5, y=0.5, @@ -1958,6 +2401,7 @@ class RealTimeChart: tick_count += 1 # Also update the old candlestick data for backward compatibility + # Add check to ensure the candlestick_data attribute exists before using it if hasattr(self, 'candlestick_data'): self.candlestick_data.update_from_trade(trade_data) @@ -2403,4 +2847,4 @@ if __name__ == "__main__": try: asyncio.run(main()) except KeyboardInterrupt: - logger.info("Application terminated by user") \ No newline at end of file + logger.info("Application terminated by user") diff --git a/train_rl_with_realtime.py b/train_rl_with_realtime.py index ecb8945..5d67ddb 100644 --- a/train_rl_with_realtime.py +++ b/train_rl_with_realtime.py @@ -534,7 +534,7 @@ def run_training_thread(chart): def training_thread_func(): try: # Use a small number of episodes to test termination handling - integrator.start_training(num_episodes=2, max_steps=500) + integrator.start_training(num_episodes=100, max_steps=2000) except Exception as e: logger.error(f"Error in training thread: {str(e)}")