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 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 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"<b style='color:{pnl_color}'>{self.session_pnl:.4f}</b>"
# Create trading info text
info_text = (
f"Signal: <b style='color:{signal_color}'>{self.current_signal}</b> | "
f"Position: <b>{position_text}</b> | "
f"Balance: <b>${self.session_balance:.2f}</b> | "
f"PnL: <b>{self.session_pnl:.4f}</b>"
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")
logger.info("Application terminated by user")

View File

@ -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)}")