better charts

This commit is contained in:
Dobromir Popov 2025-04-01 16:48:01 +03:00
parent cb3ec0602c
commit fff5e8cfb7
3 changed files with 458 additions and 12 deletions

View File

@ -55,4 +55,6 @@ python test_model.py
python train_with_realtime_ticks.py python train_with_realtime_ticks.py
python NN/train_rl.py python NN/train_rl.py
python train_rl_with_realtime.py python train_rl_with_realtime.py
python train_rl_with_realtime.py --episodes 2 --no-train --visualize-only

View File

@ -1,7 +1,7 @@
import asyncio import asyncio
import json import json
import logging import logging
import datetime
from typing import Dict, List, Optional from typing import Dict, List, Optional
import websockets import websockets
import plotly.graph_objects as go import plotly.graph_objects as go
@ -1694,13 +1694,13 @@ class RealTimeChart:
else: else:
df_1d = self.tick_storage.get_candles(interval_seconds=86400) 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: 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: 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: 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: except Exception as e:
logger.error(f"Error getting additional timeframes: {str(e)}") logger.error(f"Error getting additional timeframes: {str(e)}")
@ -1757,6 +1757,155 @@ class RealTimeChart:
), ),
row=2, col=1 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 # Add 1m chart
if df_1m is not None and not df_1m.empty and 'open' in df_1m.columns: 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 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 # Add 1h chart
if df_1h is not None and not df_1h.empty and 'open' in df_1h.columns: 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 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 # Add 1d chart
if df_1d is not None and not df_1d.empty and 'open' in df_1d.columns: if df_1d is not None and not df_1d.empty and 'open' in df_1d.columns:
fig.add_trace( fig.add_trace(
@ -1804,7 +2138,100 @@ class RealTimeChart:
), ),
row=5, col=1 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 # Add trading info annotation if available
if hasattr(self, 'current_signal') and self.current_signal: if hasattr(self, 'current_signal') and self.current_signal:
@ -1813,12 +2240,16 @@ class RealTimeChart:
# Format position value # Format position value
position_text = f"{self.current_position:.4f}" if self.current_position < 0.01 else f"{self.current_position:.2f}" 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"<b style='color:{pnl_color}'>{self.session_pnl:.4f}</b>"
# Create trading info text # Create trading info text
info_text = ( info_text = (
f"Signal: <b style='color:{signal_color}'>{self.current_signal}</b> | " f"Signal: <b style='color:{signal_color}'>{self.current_signal}</b> | "
f"Position: <b>{position_text}</b> | " f"Position: <b>{position_text}</b> | "
f"Balance: <b>${self.session_balance:.2f}</b> | " f"Balance: <b>${self.session_balance:.2f}</b> | "
f"PnL: <b>{self.session_pnl:.4f}</b>" f"PnL: {pnl_text}"
) )
# Add annotation # Add annotation
@ -1858,10 +2289,22 @@ class RealTimeChart:
xaxis5_rangeslider_visible=False 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 return fig
except Exception as e: except Exception as e:
logger.error(f"Error updating chart: {str(e)}") logger.error(f"Error updating chart: {str(e)}")
import traceback
logger.error(traceback.format_exc())
fig = go.Figure() fig = go.Figure()
fig.add_annotation( fig.add_annotation(
x=0.5, y=0.5, x=0.5, y=0.5,
@ -1958,6 +2401,7 @@ class RealTimeChart:
tick_count += 1 tick_count += 1
# Also update the old candlestick data for backward compatibility # 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'): if hasattr(self, 'candlestick_data'):
self.candlestick_data.update_from_trade(trade_data) self.candlestick_data.update_from_trade(trade_data)
@ -2403,4 +2847,4 @@ if __name__ == "__main__":
try: try:
asyncio.run(main()) asyncio.run(main())
except KeyboardInterrupt: except KeyboardInterrupt:
logger.info("Application terminated by user") logger.info("Application terminated by user")

View File

@ -534,7 +534,7 @@ def run_training_thread(chart):
def training_thread_func(): def training_thread_func():
try: try:
# Use a small number of episodes to test termination handling # 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: except Exception as e:
logger.error(f"Error in training thread: {str(e)}") logger.error(f"Error in training thread: {str(e)}")