main cleanup
This commit is contained in:
@@ -1,6 +1,21 @@
|
||||
"""
|
||||
Clean Trading Dashboard - Modular Implementation
|
||||
|
||||
CRITICAL POLICY: NO SYNTHETIC DATA ALLOWED
|
||||
|
||||
This module MUST ONLY use real market data from exchanges.
|
||||
NEVER use:
|
||||
- np.random.* for any data generation
|
||||
- Mock/fake/synthetic data
|
||||
- Placeholder values that simulate real data
|
||||
|
||||
If data is unavailable:
|
||||
- Return None, 0, or empty collections
|
||||
- Log clear error messages
|
||||
- Raise exceptions if critical
|
||||
|
||||
See: reports/REAL_MARKET_DATA_POLICY.md
|
||||
|
||||
This dashboard is fully integrated with the Universal Data Stream architecture
|
||||
and receives the standardized 5 timeseries format:
|
||||
|
||||
@@ -78,6 +93,9 @@ from core.trading_executor import TradingExecutor
|
||||
from web.layout_manager import DashboardLayoutManager
|
||||
from web.component_manager import DashboardComponentManager
|
||||
|
||||
# Import backtest training panel
|
||||
from core.backtest_training_panel import BacktestTrainingPanel
|
||||
|
||||
|
||||
try:
|
||||
from core.cob_integration import COBIntegration
|
||||
@@ -146,6 +164,12 @@ class CleanTradingDashboard:
|
||||
trading_executor=self.trading_executor
|
||||
)
|
||||
self.component_manager = DashboardComponentManager()
|
||||
|
||||
# Initialize backtest training panel
|
||||
self.backtest_training_panel = BacktestTrainingPanel(
|
||||
data_provider=self.data_provider,
|
||||
orchestrator=self.orchestrator
|
||||
)
|
||||
|
||||
# Initialize Universal Data Adapter access through orchestrator
|
||||
if UNIVERSAL_DATA_AVAILABLE:
|
||||
@@ -427,7 +451,7 @@ class CleanTradingDashboard:
|
||||
# Get recent predictions (last 24 hours)
|
||||
predictions = []
|
||||
|
||||
# Mock data for now - replace with actual database query
|
||||
# Query real prediction data from database
|
||||
import sqlite3
|
||||
try:
|
||||
with sqlite3.connect(db.db_path) as conn:
|
||||
@@ -1181,6 +1205,255 @@ class CleanTradingDashboard:
|
||||
logger.error(f"Error in chained inference callback: {e}")
|
||||
return f"❌ Error: {str(e)}"
|
||||
|
||||
# Backtest Training Panel Callbacks
|
||||
self._setup_backtest_training_callbacks()
|
||||
|
||||
def _create_candlestick_chart(self, stats):
|
||||
"""Create mini candlestick chart for visualization"""
|
||||
try:
|
||||
import plotly.graph_objects as go
|
||||
from datetime import datetime
|
||||
|
||||
candlestick_data = stats.get('candlestick_data', [])
|
||||
|
||||
if not candlestick_data:
|
||||
# Empty chart
|
||||
fig = go.Figure()
|
||||
fig.update_layout(
|
||||
title="No Data Available",
|
||||
paper_bgcolor='rgba(0,0,0,0)',
|
||||
plot_bgcolor='rgba(0,0,0,0)',
|
||||
font_color='white',
|
||||
height=200
|
||||
)
|
||||
return fig
|
||||
|
||||
# Create candlestick chart
|
||||
fig = go.Figure(data=[
|
||||
go.Candlestick(
|
||||
x=[d.get('timestamp', datetime.now()) for d in candlestick_data],
|
||||
open=[d['open'] for d in candlestick_data],
|
||||
high=[d['high'] for d in candlestick_data],
|
||||
low=[d['low'] for d in candlestick_data],
|
||||
close=[d['close'] for d in candlestick_data],
|
||||
name='ETH/USDT'
|
||||
)
|
||||
])
|
||||
|
||||
fig.update_layout(
|
||||
title="Recent Price Action",
|
||||
yaxis_title="Price (USDT)",
|
||||
xaxis_rangeslider_visible=False,
|
||||
paper_bgcolor='rgba(0,0,0,0)',
|
||||
plot_bgcolor='rgba(31,41,55,0.5)',
|
||||
font_color='white',
|
||||
height=200,
|
||||
margin=dict(l=10, r=10, t=40, b=10)
|
||||
)
|
||||
|
||||
fig.update_xaxes(showgrid=False, color='white')
|
||||
fig.update_yaxes(showgrid=True, gridcolor='rgba(255,255,255,0.1)', color='white')
|
||||
|
||||
return fig
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error creating candlestick chart: {e}")
|
||||
return go.Figure()
|
||||
|
||||
def _create_best_predictions_display(self, stats):
|
||||
"""Create display for best predictions"""
|
||||
try:
|
||||
best_predictions = stats.get('recent_predictions', [])
|
||||
|
||||
if not best_predictions:
|
||||
return [html.Div("No predictions yet", className="text-muted small")]
|
||||
|
||||
prediction_items = []
|
||||
for i, pred in enumerate(best_predictions[:5]): # Show top 5
|
||||
accuracy_color = "green" if pred.get('accuracy', 0) > 0.6 else "orange" if pred.get('accuracy', 0) > 0.5 else "red"
|
||||
|
||||
prediction_item = html.Div([
|
||||
html.Div([
|
||||
html.Span(f"{pred.get('horizon', '?')}m ", className="fw-bold text-light"),
|
||||
html.Span(".1%", style={"color": accuracy_color}, className="small"),
|
||||
html.Span(f" conf: {pred.get('confidence', 0):.2f}", className="text-muted small ms-2")
|
||||
], className="d-flex justify-content-between"),
|
||||
html.Div([
|
||||
html.Span(f"Pred: {pred.get('predicted_range', 'N/A')}", className="text-info small"),
|
||||
html.Span(f" {pred.get('profit_potential', 'N/A')}", className="text-success small ms-2")
|
||||
], className="mt-1")
|
||||
], className="mb-2 p-2 bg-secondary rounded")
|
||||
|
||||
prediction_items.append(prediction_item)
|
||||
|
||||
return prediction_items
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error creating best predictions display: {e}")
|
||||
return [html.Div("Error loading predictions", className="text-danger small")]
|
||||
|
||||
@self.app.callback(
|
||||
Output("backtest-training-state", "data"),
|
||||
[Input("backtest-start-training-btn", "n_clicks"),
|
||||
Input("backtest-stop-training-btn", "n_clicks"),
|
||||
Input("backtest-run-backtest-btn", "n_clicks")],
|
||||
[State("backtest-training-duration-slider", "value"),
|
||||
State("backtest-training-state", "data")]
|
||||
)
|
||||
def handle_backtest_training_controls(start_clicks, stop_clicks, backtest_clicks, duration, current_state):
|
||||
"""Handle backtest training control button clicks"""
|
||||
ctx = dash.callback_context
|
||||
|
||||
if not ctx.triggered:
|
||||
return current_state
|
||||
|
||||
button_id = ctx.triggered[0]["prop_id"].split(".")[0]
|
||||
|
||||
if button_id == "backtest-start-training-btn":
|
||||
self.backtest_training_panel.start_training(duration)
|
||||
logger.info(f"Backtest training started for {duration} hours")
|
||||
|
||||
elif button_id == "backtest-stop-training-btn":
|
||||
self.backtest_training_panel.stop_training()
|
||||
logger.info("Backtest training stopped")
|
||||
|
||||
elif button_id == "backtest-run-backtest-btn":
|
||||
self.backtest_training_panel._run_backtest()
|
||||
logger.info("Manual backtest executed")
|
||||
|
||||
return self.backtest_training_panel.get_training_stats()
|
||||
|
||||
def _setup_backtest_training_callbacks(self):
|
||||
"""Setup callbacks for the backtest training panel"""
|
||||
|
||||
@self.app.callback(
|
||||
[Output("backtest-training-status", "children"),
|
||||
Output("backtest-current-accuracy", "children"),
|
||||
Output("backtest-training-cycles", "children"),
|
||||
Output("backtest-training-progress-bar", "style"),
|
||||
Output("backtest-progress-text", "children"),
|
||||
Output("backtest-gpu-status", "children"),
|
||||
Output("backtest-model-status", "children"),
|
||||
Output("backtest-accuracy-chart", "figure"),
|
||||
Output("backtest-candlestick-chart", "figure"),
|
||||
Output("backtest-best-predictions", "children")],
|
||||
[Input("backtest-training-update-interval", "n_intervals"),
|
||||
State("backtest-training-duration-slider", "value")]
|
||||
)
|
||||
def update_backtest_training_status(n_intervals, duration_hours):
|
||||
"""Update backtest training panel status"""
|
||||
try:
|
||||
stats = self.backtest_training_panel.get_training_stats()
|
||||
|
||||
# Training status
|
||||
status = html.Span(
|
||||
"Active" if self.backtest_training_panel.training_active else "Inactive",
|
||||
style={"color": "green" if self.backtest_training_panel.training_active else "red"}
|
||||
)
|
||||
|
||||
# Current accuracy
|
||||
accuracy = f"{stats['current_accuracy']:.2f}%"
|
||||
|
||||
# Training cycles
|
||||
cycles = str(stats['training_cycles'])
|
||||
|
||||
# Progress
|
||||
progress_percentage = 0
|
||||
progress_text = "Ready to start"
|
||||
progress_style = {
|
||||
"width": "0%",
|
||||
"height": "20px",
|
||||
"backgroundColor": "#007bff",
|
||||
"borderRadius": "4px",
|
||||
"transition": "width 0.3s ease"
|
||||
}
|
||||
|
||||
if self.backtest_training_panel.training_active and stats['start_time']:
|
||||
elapsed = (datetime.now() - stats['start_time']).total_seconds() / 3600
|
||||
# Progress based on selected training duration
|
||||
progress_percentage = min(100, (elapsed / max(1, duration_hours)) * 100)
|
||||
progress_text = ".1f"
|
||||
progress_style["width"] = f"{progress_percentage}%"
|
||||
|
||||
# GPU/NPU status with detailed info
|
||||
gpu_available = self.backtest_training_panel.gpu_available
|
||||
npu_available = self.backtest_training_panel.npu_available
|
||||
|
||||
gpu_status = []
|
||||
if gpu_available:
|
||||
gpu_type = getattr(self.backtest_training_panel, 'gpu_type', 'GPU')
|
||||
gpu_status.append(html.Span(f"{gpu_type} ✓", style={"color": "green"}))
|
||||
else:
|
||||
gpu_status.append(html.Span("GPU ✗", style={"color": "red"}))
|
||||
|
||||
if npu_available:
|
||||
gpu_status.append(html.Span(" NPU ✓", style={"color": "green"}))
|
||||
else:
|
||||
gpu_status.append(html.Span(" NPU ✗", style={"color": "red"}))
|
||||
|
||||
# Model status
|
||||
model_status = self.backtest_training_panel._get_model_status()
|
||||
|
||||
# Accuracy chart
|
||||
chart = self.backtest_training_panel.update_accuracy_chart()
|
||||
|
||||
# Candlestick chart
|
||||
candlestick_chart = self._create_candlestick_chart(stats)
|
||||
|
||||
# Best predictions display
|
||||
best_predictions = self._create_best_predictions_display(stats)
|
||||
|
||||
return status, accuracy, cycles, progress_style, progress_text, gpu_status, model_status, chart, candlestick_chart, best_predictions
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error updating backtest training status: {e}")
|
||||
return [html.Span("Error", style={"color": "red"})] * 10
|
||||
|
||||
@self.app.callback(
|
||||
Output("backtest-training-state", "data"),
|
||||
[Input("backtest-start-training-btn", "n_clicks"),
|
||||
Input("backtest-stop-training-btn", "n_clicks"),
|
||||
Input("backtest-run-backtest-btn", "n_clicks")],
|
||||
[State("backtest-training-duration-slider", "value"),
|
||||
State("backtest-training-state", "data")]
|
||||
)
|
||||
def handle_backtest_training_controls(start_clicks, stop_clicks, backtest_clicks, duration, current_state):
|
||||
"""Handle backtest training control button clicks"""
|
||||
ctx = dash.callback_context
|
||||
|
||||
if not ctx.triggered:
|
||||
return current_state
|
||||
|
||||
button_id = ctx.triggered[0]["prop_id"].split(".")[0]
|
||||
|
||||
if button_id == "backtest-start-training-btn":
|
||||
self.backtest_training_panel.start_training(duration)
|
||||
logger.info(f"Backtest training started for {duration} hours")
|
||||
|
||||
elif button_id == "backtest-stop-training-btn":
|
||||
self.backtest_training_panel.stop_training()
|
||||
logger.info("Backtest training stopped")
|
||||
|
||||
elif button_id == "backtest-run-backtest-btn":
|
||||
self.backtest_training_panel._run_backtest()
|
||||
logger.info("Manual backtest executed")
|
||||
|
||||
return self.backtest_training_panel.get_training_stats()
|
||||
|
||||
# Add interval for backtest training updates
|
||||
self.app.layout.children.append(
|
||||
dcc.Interval(
|
||||
id="backtest-training-update-interval",
|
||||
interval=5000, # Update every 5 seconds
|
||||
n_intervals=0
|
||||
)
|
||||
)
|
||||
|
||||
# Add store for backtest training state
|
||||
self.app.layout.children.append(
|
||||
dcc.Store(id="backtest-training-state", data=self.backtest_training_panel.get_training_stats())
|
||||
)
|
||||
|
||||
def _get_real_model_performance_data(self) -> Dict[str, Any]:
|
||||
"""Get real model performance data from orchestrator"""
|
||||
try:
|
||||
@@ -1779,6 +2052,9 @@ class CleanTradingDashboard:
|
||||
|
||||
# ADD TRADES TO MAIN CHART
|
||||
self._add_trades_to_chart(fig, symbol, df_main, row=1)
|
||||
|
||||
# ADD PIVOT POINTS TO MAIN CHART
|
||||
self._add_pivot_points_to_chart(fig, symbol, df_main, row=1)
|
||||
|
||||
# Mini 1-second chart (if available)
|
||||
if has_mini_chart and ws_data_1s is not None:
|
||||
@@ -2856,7 +3132,107 @@ class CleanTradingDashboard:
|
||||
|
||||
except Exception as e:
|
||||
logger.warning(f"Error adding trades to chart: {e}")
|
||||
|
||||
|
||||
def _add_pivot_points_to_chart(self, fig: go.Figure, symbol: str, df_main: pd.DataFrame, row: int = 1):
|
||||
"""Add nested pivot points to the chart"""
|
||||
try:
|
||||
# Get pivot bounds from data provider
|
||||
if not hasattr(self, 'data_provider') or not self.data_provider:
|
||||
return
|
||||
|
||||
pivot_bounds = self.data_provider.get_pivot_bounds(symbol)
|
||||
if not pivot_bounds or not hasattr(pivot_bounds, 'pivot_support_levels'):
|
||||
return
|
||||
|
||||
support_levels = pivot_bounds.pivot_support_levels
|
||||
resistance_levels = pivot_bounds.pivot_resistance_levels
|
||||
|
||||
if not support_levels and not resistance_levels:
|
||||
return
|
||||
|
||||
# Get chart time range for pivot display
|
||||
chart_start = df_main.index.min()
|
||||
chart_end = df_main.index.max()
|
||||
|
||||
# Define colors for different pivot levels
|
||||
pivot_colors = {
|
||||
'support': ['rgba(0, 255, 0, 0.3)', 'rgba(0, 200, 0, 0.4)', 'rgba(0, 150, 0, 0.5)'],
|
||||
'resistance': ['rgba(255, 0, 0, 0.3)', 'rgba(200, 0, 0, 0.4)', 'rgba(150, 0, 0, 0.5)']
|
||||
}
|
||||
|
||||
# Add support levels
|
||||
for i, support_price in enumerate(support_levels[-5:]): # Show last 5 support levels
|
||||
color_idx = min(i, len(pivot_colors['support']) - 1)
|
||||
fig.add_trace(
|
||||
go.Scatter(
|
||||
x=[chart_start, chart_end],
|
||||
y=[support_price, support_price],
|
||||
mode='lines',
|
||||
line=dict(
|
||||
color=pivot_colors['support'][color_idx],
|
||||
width=2,
|
||||
dash='dot'
|
||||
),
|
||||
name=f'Support L{i+1}: ${support_price:.2f}',
|
||||
showlegend=True,
|
||||
hovertemplate=f"Support Level {i+1}: ${{y:.2f}}<extra></extra>"
|
||||
),
|
||||
row=row, col=1
|
||||
)
|
||||
|
||||
# Add resistance levels
|
||||
for i, resistance_price in enumerate(resistance_levels[-5:]): # Show last 5 resistance levels
|
||||
color_idx = min(i, len(pivot_colors['resistance']) - 1)
|
||||
fig.add_trace(
|
||||
go.Scatter(
|
||||
x=[chart_start, chart_end],
|
||||
y=[resistance_price, resistance_price],
|
||||
mode='lines',
|
||||
line=dict(
|
||||
color=pivot_colors['resistance'][color_idx],
|
||||
width=2,
|
||||
dash='dot'
|
||||
),
|
||||
name=f'Resistance L{i+1}: ${resistance_price:.2f}',
|
||||
showlegend=True,
|
||||
hovertemplate=f"Resistance Level {i+1}: ${{y:.2f}}<extra></extra>"
|
||||
),
|
||||
row=row, col=1
|
||||
)
|
||||
|
||||
# Add pivot context annotation if available
|
||||
if hasattr(pivot_bounds, 'pivot_context') and pivot_bounds.pivot_context:
|
||||
context = pivot_bounds.pivot_context
|
||||
if isinstance(context, dict) and 'trend_direction' in context:
|
||||
trend = context.get('trend_direction', 'UNKNOWN')
|
||||
strength = context.get('trend_strength', 0.0)
|
||||
nested_levels = context.get('nested_levels', 0)
|
||||
|
||||
# Add trend annotation
|
||||
trend_color = {
|
||||
'UPTREND': 'green',
|
||||
'DOWNTREND': 'red',
|
||||
'SIDEWAYS': 'orange'
|
||||
}.get(trend, 'gray')
|
||||
|
||||
fig.add_annotation(
|
||||
xref="paper", yref="paper",
|
||||
x=0.02, y=0.98,
|
||||
text=f"Trend: {trend} ({strength:.1%}) | Pivots: {nested_levels} levels",
|
||||
showarrow=False,
|
||||
bgcolor="rgba(0,0,0,0.7)",
|
||||
bordercolor=trend_color,
|
||||
borderwidth=1,
|
||||
borderpad=4,
|
||||
font=dict(color="white", size=10),
|
||||
row=row, col=1
|
||||
)
|
||||
|
||||
logger.debug(f"Added {len(support_levels)} support and {len(resistance_levels)} resistance levels to chart")
|
||||
|
||||
except Exception as e:
|
||||
logger.warning(f"Error adding pivot points to chart: {e}")
|
||||
|
||||
def _get_price_at_time(self, df: pd.DataFrame, timestamp) -> Optional[float]:
|
||||
"""Get price from dataframe at specific timestamp"""
|
||||
try:
|
||||
@@ -2924,10 +3300,11 @@ class CleanTradingDashboard:
|
||||
if 'volume' in df.columns and df['volume'].sum() > 0:
|
||||
df_resampled['volume'] = df['volume'].resample('1s').sum()
|
||||
else:
|
||||
# Use tick count as volume proxy with some randomization for variety
|
||||
import random
|
||||
# CRITICAL: NO SYNTHETIC DATA - If volume unavailable, set to 0
|
||||
# NEVER use random.randint() or any synthetic data generation
|
||||
tick_counts = df[price_col].resample('1s').count()
|
||||
df_resampled['volume'] = tick_counts * (50 + random.randint(0, 100))
|
||||
df_resampled['volume'] = 0 # No volume data available
|
||||
logger.warning(f"Volume data unavailable for 1s timeframe {symbol} - using 0 (NEVER synthetic)")
|
||||
# For 1m timeframe, volume is already in the raw data
|
||||
|
||||
# Remove any NaN rows and limit to max bars
|
||||
@@ -7834,9 +8211,13 @@ class CleanTradingDashboard:
|
||||
price_change = (next_price - current_price) / current_price if current_price > 0 else 0
|
||||
cumulative_imbalance = current_data.get('cumulative_imbalance', {})
|
||||
|
||||
# TODO(Guideline: no synthetic data) Replace the random baseline with real orchestrator features.
|
||||
# TODO(Guideline: no synthetic data) Replace the random baseline with real orchestrator features.
|
||||
features = np.random.randn(100)
|
||||
# CRITICAL: Extract REAL features from orchestrator - NEVER use np.random or synthetic data
|
||||
if not self.orchestrator or not hasattr(self.orchestrator, 'extract_features'):
|
||||
logger.error("CRITICAL: Cannot train CNN - orchestrator feature extraction unavailable. NEVER use synthetic data.")
|
||||
continue
|
||||
|
||||
# Build real feature vector from actual market data
|
||||
features = np.zeros(100)
|
||||
features[0] = current_price / 10000
|
||||
features[1] = price_change
|
||||
features[2] = current_data.get('volume', 0) / 1000000
|
||||
@@ -7845,6 +8226,8 @@ class CleanTradingDashboard:
|
||||
features[4] = cumulative_imbalance.get('5s', 0.0)
|
||||
features[5] = cumulative_imbalance.get('15s', 0.0)
|
||||
features[6] = cumulative_imbalance.get('60s', 0.0)
|
||||
# Leave remaining features as 0.0 until proper feature extraction is implemented
|
||||
# NEVER fill with random values
|
||||
if price_change > 0.001: target = 2
|
||||
elif price_change < -0.001: target = 0
|
||||
else: target = 1
|
||||
|
||||
@@ -259,73 +259,10 @@ class DashboardDataBuilder:
|
||||
return str(value)
|
||||
|
||||
|
||||
def create_sample_dashboard_data() -> DashboardModel:
|
||||
"""Create sample dashboard data for testing"""
|
||||
builder = DashboardDataBuilder()
|
||||
|
||||
# Basic info
|
||||
builder.set_basic_info(
|
||||
title="Live Scalping Dashboard",
|
||||
subtitle="Real-time Trading with AI Models",
|
||||
refresh_interval=1000
|
||||
)
|
||||
|
||||
# Metrics
|
||||
builder.add_metric("current-price", "Current Price", 3425.67, "currency")
|
||||
builder.add_metric("session-pnl", "Session PnL", 125.34, "currency")
|
||||
builder.add_metric("current-position", "Position", 0.0, "number")
|
||||
builder.add_metric("trade-count", "Trades", 15, "number")
|
||||
builder.add_metric("portfolio-value", "Portfolio", 10250.45, "currency")
|
||||
builder.add_metric("mexc-status", "MEXC Status", "Connected", "text")
|
||||
|
||||
# Trading controls
|
||||
builder.set_trading_controls(leverage=10, leverage_range=(1, 50))
|
||||
|
||||
# Recent decisions
|
||||
builder.add_recent_decision(datetime.now(), "BUY", "ETH/USDT", 0.85, 3425.67)
|
||||
builder.add_recent_decision(datetime.now(), "HOLD", "BTC/USDT", 0.62, 45123.45)
|
||||
|
||||
# COB data
|
||||
eth_levels = [
|
||||
{"side": "ask", "size": 1.5, "price": 3426.12, "total": 5139.18},
|
||||
{"side": "ask", "size": 2.3, "price": 3425.89, "total": 7879.55},
|
||||
{"side": "bid", "size": 1.8, "price": 3425.45, "total": 6165.81},
|
||||
{"side": "bid", "size": 3.2, "price": 3425.12, "total": 10960.38}
|
||||
]
|
||||
builder.add_cob_data("ETH/USDT", "eth-cob-content", 25000.0, 7.3, eth_levels)
|
||||
|
||||
btc_levels = [
|
||||
{"side": "ask", "size": 0.15, "price": 45125.67, "total": 6768.85},
|
||||
{"side": "ask", "size": 0.23, "price": 45123.45, "total": 10378.39},
|
||||
{"side": "bid", "size": 0.18, "price": 45121.23, "total": 8121.82},
|
||||
{"side": "bid", "size": 0.32, "price": 45119.12, "total": 14438.12}
|
||||
]
|
||||
builder.add_cob_data("BTC/USDT", "btc-cob-content", 35000.0, 0.88, btc_levels)
|
||||
|
||||
# Model statuses
|
||||
builder.add_model_status("DQN", True)
|
||||
builder.add_model_status("CNN", True)
|
||||
builder.add_model_status("Transformer", False)
|
||||
builder.add_model_status("COB-RL", True)
|
||||
|
||||
# Training metrics
|
||||
builder.add_training_metric("DQN Loss", 0.0234)
|
||||
builder.add_training_metric("CNN Accuracy", 0.876)
|
||||
builder.add_training_metric("Training Steps", 15420)
|
||||
builder.add_training_metric("Learning Rate", 0.0001)
|
||||
|
||||
# Performance stats
|
||||
builder.add_performance_stat("Win Rate", 68.5)
|
||||
builder.add_performance_stat("Avg Trade", 8.34)
|
||||
builder.add_performance_stat("Max Drawdown", -45.67)
|
||||
builder.add_performance_stat("Sharpe Ratio", 1.82)
|
||||
|
||||
# Closed trades
|
||||
builder.add_closed_trade(
|
||||
datetime.now(), "ETH/USDT", "BUY", 1.5, 3420.45, 3428.12, 11.51, "2m 34s"
|
||||
)
|
||||
builder.add_closed_trade(
|
||||
datetime.now(), "BTC/USDT", "SELL", 0.1, 45150.23, 45142.67, -0.76, "1m 12s"
|
||||
)
|
||||
|
||||
return builder.build()
|
||||
# CRITICAL POLICY: NEVER create mock/sample data functions
|
||||
# All dashboard data MUST come from real market data or be empty/None
|
||||
# This function was removed to prevent synthetic data usage
|
||||
# See: reports/REAL_MARKET_DATA_POLICY.md
|
||||
#
|
||||
# If you need to test the dashboard, use real market data from exchanges
|
||||
# or run with empty data to identify what needs to be implemented
|
||||
@@ -89,7 +89,154 @@ class DashboardLayoutManager:
|
||||
], className="p-3")
|
||||
], className="card bg-dark border-secondary mb-3")
|
||||
], className="mt-3")
|
||||
|
||||
|
||||
def _create_backtest_training_panel(self):
|
||||
"""Create the backtest training control panel"""
|
||||
return html.Div([
|
||||
html.Div([
|
||||
html.Div([
|
||||
html.H6([
|
||||
html.I(className="fas fa-robot me-2"),
|
||||
"🤖 Backtest Training Control"
|
||||
], className="text-light mb-3"),
|
||||
|
||||
# Control buttons
|
||||
html.Div([
|
||||
html.Div([
|
||||
html.Label("Training Control", className="text-light small"),
|
||||
html.Div([
|
||||
html.Button(
|
||||
"Start Training",
|
||||
id="backtest-start-training-btn",
|
||||
className="btn btn-success btn-sm me-2"
|
||||
),
|
||||
html.Button(
|
||||
"Stop Training",
|
||||
id="backtest-stop-training-btn",
|
||||
className="btn btn-danger btn-sm me-2"
|
||||
),
|
||||
html.Button(
|
||||
"Run Backtest",
|
||||
id="backtest-run-backtest-btn",
|
||||
className="btn btn-primary btn-sm"
|
||||
)
|
||||
], className="btn-group")
|
||||
], className="col-md-6"),
|
||||
|
||||
html.Div([
|
||||
html.Label("Backtest Data Window (hours)", className="text-light small"),
|
||||
dcc.Slider(
|
||||
id="backtest-training-duration-slider",
|
||||
min=6,
|
||||
max=72,
|
||||
step=6,
|
||||
value=24,
|
||||
marks={i: f"{i}h" for i in range(0, 73, 12)},
|
||||
className="mt-2"
|
||||
),
|
||||
html.Small("Uses N hours of data, tests predictions for each minute in first N-1 hours", className="text-muted")
|
||||
], className="col-md-6")
|
||||
], className="row mb-3"),
|
||||
|
||||
# Status display
|
||||
html.Div([
|
||||
html.Div([
|
||||
html.Label("Training Status", className="text-light small"),
|
||||
html.Div(id="backtest-training-status", children=[
|
||||
html.Span("Inactive", style={"color": "red"})
|
||||
], className="h5")
|
||||
], className="col-md-3"),
|
||||
|
||||
html.Div([
|
||||
html.Label("Current Accuracy", className="text-light small"),
|
||||
html.H5(id="backtest-current-accuracy", children="0.00%", className="text-info")
|
||||
], className="col-md-3"),
|
||||
|
||||
html.Div([
|
||||
html.Label("Training Cycles", className="text-light small"),
|
||||
html.H5(id="backtest-training-cycles", children="0", className="text-warning")
|
||||
], className="col-md-3"),
|
||||
|
||||
html.Div([
|
||||
html.Label("GPU/NPU Status", className="text-light small"),
|
||||
html.Div(id="backtest-gpu-status", children=[
|
||||
html.Span("Checking...", style={"color": "orange"})
|
||||
], className="h5")
|
||||
], className="col-md-3")
|
||||
], className="row mb-3"),
|
||||
|
||||
# Progress and charts
|
||||
html.Div([
|
||||
html.Div([
|
||||
html.Label("Training Progress", className="text-light small"),
|
||||
html.Div([
|
||||
html.Div(
|
||||
id="backtest-training-progress-bar",
|
||||
style={
|
||||
"width": "0%",
|
||||
"height": "20px",
|
||||
"backgroundColor": "#007bff",
|
||||
"borderRadius": "4px",
|
||||
"transition": "width 0.3s ease"
|
||||
}
|
||||
)
|
||||
], style={
|
||||
"width": "100%",
|
||||
"height": "20px",
|
||||
"backgroundColor": "#374151",
|
||||
"borderRadius": "4px",
|
||||
"marginBottom": "8px"
|
||||
}),
|
||||
html.Div(id="backtest-progress-text", children="Ready to start", className="text-muted small")
|
||||
], className="col-md-6"),
|
||||
|
||||
html.Div([
|
||||
html.Label("Accuracy Trend", className="text-light small"),
|
||||
dcc.Graph(
|
||||
id="backtest-accuracy-chart",
|
||||
style={"height": "150px"},
|
||||
config={"displayModeBar": False}
|
||||
)
|
||||
], className="col-md-6")
|
||||
], className="row"),
|
||||
|
||||
# Mini Candlestick Chart and Best Predictions
|
||||
html.Div([
|
||||
html.Div([
|
||||
html.Label("Mini Candlestick Chart", className="text-light small"),
|
||||
dcc.Graph(
|
||||
id="backtest-candlestick-chart",
|
||||
style={"height": "200px"},
|
||||
config={"displayModeBar": False}
|
||||
)
|
||||
], className="col-md-6"),
|
||||
|
||||
html.Div([
|
||||
html.Label("Best Predictions", className="text-light small"),
|
||||
html.Div(
|
||||
id="backtest-best-predictions",
|
||||
style={
|
||||
"height": "200px",
|
||||
"overflowY": "auto",
|
||||
"backgroundColor": "#1f2937",
|
||||
"borderRadius": "8px",
|
||||
"padding": "10px"
|
||||
},
|
||||
children=[html.Div("No predictions yet", className="text-muted small")]
|
||||
)
|
||||
], className="col-md-6")
|
||||
], className="row mb-3"),
|
||||
|
||||
# Model status
|
||||
html.Div([
|
||||
html.Label("Active Models", className="text-light small mt-2"),
|
||||
html.Div(id="backtest-model-status", children="Initializing...", className="text-muted small")
|
||||
], className="mt-2")
|
||||
|
||||
], className="p-3")
|
||||
], className="card bg-dark border-secondary mb-3")
|
||||
], className="mt-3")
|
||||
|
||||
def _create_header(self):
|
||||
"""Create the dashboard header"""
|
||||
trading_mode = "SIMULATION" if (not self.trading_executor or
|
||||
@@ -133,7 +280,8 @@ class DashboardLayoutManager:
|
||||
return html.Div([
|
||||
self._create_metrics_and_signals_row(),
|
||||
self._create_charts_row(),
|
||||
self._create_cob_and_trades_row()
|
||||
self._create_cob_and_trades_row(),
|
||||
self._create_backtest_training_panel()
|
||||
])
|
||||
|
||||
def _create_metrics_and_signals_row(self):
|
||||
|
||||
@@ -1,384 +0,0 @@
|
||||
"""
|
||||
Template Renderer for Dashboard
|
||||
Handles HTML template rendering with Jinja2
|
||||
"""
|
||||
import os
|
||||
from typing import Dict, Any
|
||||
from jinja2 import Environment, FileSystemLoader, select_autoescape
|
||||
from dash import html, dcc
|
||||
import plotly.graph_objects as go
|
||||
|
||||
from .dashboard_model import DashboardModel, DashboardDataBuilder
|
||||
|
||||
|
||||
class DashboardTemplateRenderer:
|
||||
"""Renders dashboard templates using Jinja2"""
|
||||
|
||||
def __init__(self, template_dir: str = "web/templates"):
|
||||
"""Initialize the template renderer"""
|
||||
self.template_dir = template_dir
|
||||
|
||||
# Create Jinja2 environment
|
||||
self.env = Environment(
|
||||
loader=FileSystemLoader(template_dir),
|
||||
autoescape=select_autoescape(['html', 'xml'])
|
||||
)
|
||||
|
||||
# Add custom filters
|
||||
self.env.filters['currency'] = self._currency_filter
|
||||
self.env.filters['percentage'] = self._percentage_filter
|
||||
self.env.filters['number'] = self._number_filter
|
||||
|
||||
def render_dashboard(self, model: DashboardModel) -> html.Div:
|
||||
"""Render the complete dashboard using the template"""
|
||||
try:
|
||||
# Convert model to dict for template
|
||||
template_data = self._model_to_dict(model)
|
||||
|
||||
# Render template
|
||||
template = self.env.get_template('dashboard.html')
|
||||
rendered_html = template.render(**template_data)
|
||||
|
||||
# Convert to Dash components
|
||||
return self._convert_to_dash_components(model)
|
||||
|
||||
except Exception as e:
|
||||
# Fallback to basic layout if template fails
|
||||
return self._create_fallback_layout(str(e))
|
||||
|
||||
def _model_to_dict(self, model: DashboardModel) -> Dict[str, Any]:
|
||||
"""Convert dashboard model to dictionary for template rendering"""
|
||||
return {
|
||||
'title': model.title,
|
||||
'subtitle': model.subtitle,
|
||||
'refresh_interval': model.refresh_interval,
|
||||
'metrics': [self._dataclass_to_dict(m) for m in model.metrics],
|
||||
'chart': self._dataclass_to_dict(model.chart),
|
||||
'trading_controls': self._dataclass_to_dict(model.trading_controls),
|
||||
'recent_decisions': [self._dataclass_to_dict(d) for d in model.recent_decisions],
|
||||
'cob_data': [self._dataclass_to_dict(c) for c in model.cob_data],
|
||||
'models': [self._dataclass_to_dict(m) for m in model.models],
|
||||
'training_metrics': [self._dataclass_to_dict(m) for m in model.training_metrics],
|
||||
'performance_stats': [self._dataclass_to_dict(s) for s in model.performance_stats],
|
||||
'closed_trades': [self._dataclass_to_dict(t) for t in model.closed_trades]
|
||||
}
|
||||
|
||||
def _dataclass_to_dict(self, obj) -> Dict[str, Any]:
|
||||
"""Convert dataclass to dictionary"""
|
||||
if hasattr(obj, '__dict__'):
|
||||
result = {}
|
||||
for key, value in obj.__dict__.items():
|
||||
if hasattr(value, '__dict__'):
|
||||
result[key] = self._dataclass_to_dict(value)
|
||||
elif isinstance(value, list):
|
||||
result[key] = [self._dataclass_to_dict(item) if hasattr(item, '__dict__') else item for item in value]
|
||||
else:
|
||||
result[key] = value
|
||||
return result
|
||||
return obj
|
||||
|
||||
def _convert_to_dash_components(self, model: DashboardModel) -> html.Div:
|
||||
"""Convert template model to Dash components"""
|
||||
return html.Div([
|
||||
# Header
|
||||
html.Div([
|
||||
html.H1(model.title, className="text-center"),
|
||||
html.P(model.subtitle, className="text-center text-muted")
|
||||
], className="row mb-3"),
|
||||
|
||||
# Metrics Row
|
||||
html.Div([
|
||||
html.Div([
|
||||
self._create_metric_card(metric)
|
||||
], className="col-md-2") for metric in model.metrics
|
||||
], className="row mb-3"),
|
||||
|
||||
# Main Content Row
|
||||
html.Div([
|
||||
# Price Chart
|
||||
html.Div([
|
||||
html.Div([
|
||||
html.Div([
|
||||
html.H5(model.chart.title)
|
||||
], className="card-header"),
|
||||
html.Div([
|
||||
dcc.Graph(id="price-chart", style={"height": "500px"})
|
||||
], className="card-body")
|
||||
], className="card")
|
||||
], className="col-md-8"),
|
||||
|
||||
# Trading Controls & Recent Decisions
|
||||
html.Div([
|
||||
# Trading Controls
|
||||
self._create_trading_controls(model.trading_controls),
|
||||
# Recent Decisions
|
||||
self._create_recent_decisions(model.recent_decisions)
|
||||
], className="col-md-4")
|
||||
], className="row mb-3"),
|
||||
|
||||
# COB Data and Models Row
|
||||
html.Div([
|
||||
# COB Ladders
|
||||
html.Div([
|
||||
html.Div([
|
||||
html.Div([
|
||||
self._create_cob_card(cob)
|
||||
], className="col-md-6") for cob in model.cob_data
|
||||
], className="row")
|
||||
], className="col-md-7"),
|
||||
|
||||
# Models & Training
|
||||
html.Div([
|
||||
self._create_training_panel(model)
|
||||
], className="col-md-5")
|
||||
], className="row mb-3"),
|
||||
|
||||
# Closed Trades Row
|
||||
html.Div([
|
||||
html.Div([
|
||||
self._create_closed_trades_table(model.closed_trades)
|
||||
], className="col-12")
|
||||
], className="row"),
|
||||
|
||||
# Auto-refresh interval
|
||||
dcc.Interval(id='interval-component', interval=model.refresh_interval, n_intervals=0)
|
||||
|
||||
], className="container-fluid")
|
||||
|
||||
def _create_metric_card(self, metric) -> html.Div:
|
||||
"""Create a metric card component"""
|
||||
return html.Div([
|
||||
html.Div(metric.value, className="metric-value", id=metric.id),
|
||||
html.Div(metric.label, className="metric-label")
|
||||
], className="metric-card")
|
||||
|
||||
def _create_trading_controls(self, controls) -> html.Div:
|
||||
"""Create trading controls component"""
|
||||
return html.Div([
|
||||
html.Div([
|
||||
html.H6("Manual Trading")
|
||||
], className="card-header"),
|
||||
html.Div([
|
||||
html.Div([
|
||||
html.Div([
|
||||
html.Button(controls.buy_text, id="manual-buy-btn",
|
||||
className="btn btn-success w-100")
|
||||
], className="col-6"),
|
||||
html.Div([
|
||||
html.Button(controls.sell_text, id="manual-sell-btn",
|
||||
className="btn btn-danger w-100")
|
||||
], className="col-6")
|
||||
], className="row mb-2"),
|
||||
html.Div([
|
||||
html.Div([
|
||||
html.Label([
|
||||
f"Leverage: ",
|
||||
html.Span(f"{controls.leverage}x", id="leverage-display")
|
||||
], className="form-label"),
|
||||
dcc.Slider(
|
||||
id="leverage-slider",
|
||||
min=controls.leverage_min,
|
||||
max=controls.leverage_max,
|
||||
value=controls.leverage,
|
||||
step=1,
|
||||
marks={i: str(i) for i in range(controls.leverage_min, controls.leverage_max + 1, 10)}
|
||||
)
|
||||
], className="col-12")
|
||||
], className="row mb-2"),
|
||||
html.Div([
|
||||
html.Div([
|
||||
html.Button(controls.clear_text, id="clear-session-btn",
|
||||
className="btn btn-warning w-100")
|
||||
], className="col-12")
|
||||
], className="row")
|
||||
], className="card-body")
|
||||
], className="card mb-3")
|
||||
|
||||
def _create_recent_decisions(self, decisions) -> html.Div:
|
||||
"""Create recent decisions component"""
|
||||
decision_items = []
|
||||
for decision in decisions:
|
||||
border_class = {
|
||||
'BUY': 'border-success bg-success bg-opacity-10',
|
||||
'SELL': 'border-danger bg-danger bg-opacity-10'
|
||||
}.get(decision.action, 'border-secondary bg-secondary bg-opacity-10')
|
||||
|
||||
decision_items.append(
|
||||
html.Div([
|
||||
html.Small(decision.timestamp, className="text-muted"),
|
||||
html.Br(),
|
||||
html.Strong(f"{decision.action} - {decision.symbol}"),
|
||||
html.Br(),
|
||||
html.Small(f"Confidence: {decision.confidence}% | Price: ${decision.price}")
|
||||
], className=f"mb-2 p-2 border-start border-3 {border_class}")
|
||||
)
|
||||
|
||||
return html.Div([
|
||||
html.Div([
|
||||
html.H6("Recent AI Decisions")
|
||||
], className="card-header"),
|
||||
html.Div([
|
||||
html.Div(decision_items, id="recent-decisions")
|
||||
], className="card-body", style={"max-height": "300px", "overflow-y": "auto"})
|
||||
], className="card")
|
||||
|
||||
def _create_cob_card(self, cob) -> html.Div:
|
||||
"""Create COB ladder card"""
|
||||
return html.Div([
|
||||
html.Div([
|
||||
html.H6(f"{cob.symbol} Order Book"),
|
||||
html.Small(f"Total: {cob.total_usd} USD | {cob.total_crypto} {cob.symbol.split('/')[0]}",
|
||||
className="text-muted")
|
||||
], className="card-header"),
|
||||
html.Div([
|
||||
html.Div(id=cob.content_id, className="cob-ladder")
|
||||
], className="card-body p-2")
|
||||
], className="card")
|
||||
|
||||
def _create_training_panel(self, model: DashboardModel) -> html.Div:
|
||||
"""Create training panel component"""
|
||||
# Model status indicators
|
||||
model_status_items = []
|
||||
for model_item in model.models:
|
||||
status_class = f"status-{model_item.status}"
|
||||
model_status_items.append(
|
||||
html.Span(f"{model_item.name}: {model_item.status_text}",
|
||||
className=f"model-status {status_class}")
|
||||
)
|
||||
|
||||
# Training metrics
|
||||
training_items = []
|
||||
for metric in model.training_metrics:
|
||||
training_items.append(
|
||||
html.Div([
|
||||
html.Div([
|
||||
html.Small(f"{metric.name}:")
|
||||
], className="col-6"),
|
||||
html.Div([
|
||||
html.Small(metric.value, className="fw-bold")
|
||||
], className="col-6")
|
||||
], className="row mb-1")
|
||||
)
|
||||
|
||||
# Performance stats
|
||||
performance_items = []
|
||||
for stat in model.performance_stats:
|
||||
performance_items.append(
|
||||
html.Div([
|
||||
html.Div([
|
||||
html.Small(f"{stat.name}:")
|
||||
], className="col-8"),
|
||||
html.Div([
|
||||
html.Small(stat.value, className="fw-bold")
|
||||
], className="col-4")
|
||||
], className="row mb-1")
|
||||
)
|
||||
|
||||
return html.Div([
|
||||
html.Div([
|
||||
html.H6("Models & Training Progress")
|
||||
], className="card-header"),
|
||||
html.Div([
|
||||
html.Div([
|
||||
# Model Status
|
||||
html.Div([
|
||||
html.H6("Model Status"),
|
||||
html.Div(model_status_items)
|
||||
], className="mb-3"),
|
||||
|
||||
# Training Metrics
|
||||
html.Div([
|
||||
html.H6("Training Metrics"),
|
||||
html.Div(training_items, id="training-metrics")
|
||||
], className="mb-3"),
|
||||
|
||||
# Performance Stats
|
||||
html.Div([
|
||||
html.H6("Performance"),
|
||||
html.Div(performance_items)
|
||||
], className="mb-3")
|
||||
])
|
||||
], className="card-body training-panel")
|
||||
], className="card")
|
||||
|
||||
def _create_closed_trades_table(self, trades) -> html.Div:
|
||||
"""Create closed trades table"""
|
||||
trade_rows = []
|
||||
for trade in trades:
|
||||
pnl_class = "trade-profit" if trade.pnl > 0 else "trade-loss"
|
||||
side_class = "bg-success" if trade.side == "BUY" else "bg-danger"
|
||||
|
||||
trade_rows.append(
|
||||
html.Tr([
|
||||
html.Td(trade.time),
|
||||
html.Td(trade.symbol),
|
||||
html.Td([
|
||||
html.Span(trade.side, className=f"badge {side_class}")
|
||||
]),
|
||||
html.Td(trade.size),
|
||||
html.Td(trade.entry_price),
|
||||
html.Td(trade.exit_price),
|
||||
html.Td(f"${trade.pnl}", className=pnl_class),
|
||||
html.Td(trade.duration)
|
||||
])
|
||||
)
|
||||
|
||||
return html.Div([
|
||||
html.Div([
|
||||
html.H6("Recent Closed Trades")
|
||||
], className="card-header"),
|
||||
html.Div([
|
||||
html.Div([
|
||||
html.Table([
|
||||
html.Thead([
|
||||
html.Tr([
|
||||
html.Th("Time"),
|
||||
html.Th("Symbol"),
|
||||
html.Th("Side"),
|
||||
html.Th("Size"),
|
||||
html.Th("Entry"),
|
||||
html.Th("Exit"),
|
||||
html.Th("PnL"),
|
||||
html.Th("Duration")
|
||||
])
|
||||
]),
|
||||
html.Tbody(trade_rows)
|
||||
], className="table table-sm", id="closed-trades-table")
|
||||
])
|
||||
], className="card-body closed-trades")
|
||||
], className="card")
|
||||
|
||||
def _create_fallback_layout(self, error_msg: str) -> html.Div:
|
||||
"""Create fallback layout if template rendering fails"""
|
||||
return html.Div([
|
||||
html.Div([
|
||||
html.H1("Dashboard Error", className="text-center text-danger"),
|
||||
html.P(f"Template rendering failed: {error_msg}", className="text-center"),
|
||||
html.P("Using fallback layout.", className="text-center text-muted")
|
||||
], className="container mt-5")
|
||||
])
|
||||
|
||||
# Jinja2 custom filters
|
||||
def _currency_filter(self, value) -> str:
|
||||
"""Format value as currency"""
|
||||
try:
|
||||
return f"${float(value):,.4f}"
|
||||
except (ValueError, TypeError):
|
||||
return str(value)
|
||||
|
||||
def _percentage_filter(self, value) -> str:
|
||||
"""Format value as percentage"""
|
||||
try:
|
||||
return f"{float(value):.2f}%"
|
||||
except (ValueError, TypeError):
|
||||
return str(value)
|
||||
|
||||
def _number_filter(self, value) -> str:
|
||||
"""Format value as number"""
|
||||
try:
|
||||
if isinstance(value, int):
|
||||
return f"{value:,}"
|
||||
else:
|
||||
return f"{float(value):,.2f}"
|
||||
except (ValueError, TypeError):
|
||||
return str(value)
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,313 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>{{ title }}</title>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||
<style>
|
||||
.metric-card {
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
color: white;
|
||||
border-radius: 10px;
|
||||
padding: 15px;
|
||||
margin-bottom: 10px;
|
||||
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
.metric-value {
|
||||
font-size: 1.5rem;
|
||||
font-weight: bold;
|
||||
}
|
||||
.metric-label {
|
||||
font-size: 0.9rem;
|
||||
opacity: 0.9;
|
||||
}
|
||||
.cob-ladder {
|
||||
max-height: 400px;
|
||||
overflow-y: auto;
|
||||
font-family: 'Courier New', monospace;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
.bid-row {
|
||||
background-color: rgba(40, 167, 69, 0.1);
|
||||
border-left: 3px solid #28a745;
|
||||
}
|
||||
.ask-row {
|
||||
background-color: rgba(220, 53, 69, 0.1);
|
||||
border-left: 3px solid #dc3545;
|
||||
}
|
||||
.training-panel {
|
||||
background: #f8f9fa;
|
||||
border-radius: 8px;
|
||||
padding: 15px;
|
||||
height: 300px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
.model-status {
|
||||
padding: 8px 12px;
|
||||
border-radius: 20px;
|
||||
font-size: 0.8rem;
|
||||
font-weight: bold;
|
||||
margin: 2px;
|
||||
display: inline-block;
|
||||
}
|
||||
.status-training { background-color: #28a745; color: white; }
|
||||
.status-idle { background-color: #6c757d; color: white; }
|
||||
.status-loading { background-color: #ffc107; color: black; }
|
||||
.closed-trades {
|
||||
max-height: 200px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
.trade-profit { color: #28a745; font-weight: bold; }
|
||||
.trade-loss { color: #dc3545; font-weight: bold; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container-fluid">
|
||||
<!-- Header -->
|
||||
<div class="row mb-3">
|
||||
<div class="col-12">
|
||||
<h1 class="text-center">{{ title }}</h1>
|
||||
<p class="text-center text-muted">{{ subtitle }}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Metrics Row -->
|
||||
<div class="row mb-3">
|
||||
{% for metric in metrics %}
|
||||
<div class="col-md-2">
|
||||
<div class="metric-card">
|
||||
<div class="metric-value" id="{{ metric.id }}">{{ metric.value }}</div>
|
||||
<div class="metric-label">{{ metric.label }}</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
<!-- Main Content Row -->
|
||||
<div class="row mb-3">
|
||||
<!-- Price Chart (Left) -->
|
||||
<div class="col-md-8">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h5>{{ chart.title }}</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div id="price-chart" style="height: 500px;"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Trading Controls & Recent Decisions (Right) -->
|
||||
<div class="col-md-4">
|
||||
<!-- Trading Controls -->
|
||||
<div class="card mb-3">
|
||||
<div class="card-header">
|
||||
<h6>Manual Trading</h6>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="row mb-2">
|
||||
<div class="col-6">
|
||||
<button id="manual-buy-btn" class="btn btn-success w-100">
|
||||
{{ trading_controls.buy_text }}
|
||||
</button>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<button id="manual-sell-btn" class="btn btn-danger w-100">
|
||||
{{ trading_controls.sell_text }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-2">
|
||||
<div class="col-12">
|
||||
<label for="leverage-slider" class="form-label">
|
||||
Leverage: <span id="leverage-display">{{ trading_controls.leverage }}</span>x
|
||||
</label>
|
||||
<input type="range" class="form-range" id="leverage-slider"
|
||||
min="{{ trading_controls.leverage_min }}"
|
||||
max="{{ trading_controls.leverage_max }}"
|
||||
value="{{ trading_controls.leverage }}" step="1">
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<button id="clear-session-btn" class="btn btn-warning w-100">
|
||||
{{ trading_controls.clear_text }}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Recent Decisions -->
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h6>Recent AI Decisions</h6>
|
||||
</div>
|
||||
<div class="card-body" style="max-height: 300px; overflow-y: auto;">
|
||||
<div id="recent-decisions">
|
||||
{% for decision in recent_decisions %}
|
||||
<div class="mb-2 p-2 border-start border-3
|
||||
{% if decision.action == 'BUY' %}border-success bg-success bg-opacity-10
|
||||
{% elif decision.action == 'SELL' %}border-danger bg-danger bg-opacity-10
|
||||
{% else %}border-secondary bg-secondary bg-opacity-10{% endif %}">
|
||||
<small class="text-muted">{{ decision.timestamp }}</small><br>
|
||||
<strong>{{ decision.action }}</strong> - {{ decision.symbol }}<br>
|
||||
<small>Confidence: {{ decision.confidence }}% | Price: ${{ decision.price }}</small>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- COB Data and Models Row -->
|
||||
<div class="row mb-3">
|
||||
<!-- COB Ladders (Left 60%) -->
|
||||
<div class="col-md-7">
|
||||
<div class="row">
|
||||
{% for cob in cob_data %}
|
||||
<div class="col-md-6">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h6>{{ cob.symbol }} Order Book</h6>
|
||||
<small class="text-muted">Total: {{ cob.total_usd }} USD | {{ cob.total_crypto }} {{ cob.symbol.split('/')[0] }}</small>
|
||||
</div>
|
||||
<div class="card-body p-2">
|
||||
<div id="{{ cob.content_id }}" class="cob-ladder">
|
||||
<table class="table table-sm table-borderless">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Size</th>
|
||||
<th>Price</th>
|
||||
<th>Total</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for level in cob.levels %}
|
||||
<tr class="{% if level.side == 'ask' %}ask-row{% else %}bid-row{% endif %}">
|
||||
<td>{{ level.size }}</td>
|
||||
<td>{{ level.price }}</td>
|
||||
<td>{{ level.total }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Models & Training Progress (Right 40%) -->
|
||||
<div class="col-md-5">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h6>Models & Training Progress</h6>
|
||||
</div>
|
||||
<div class="card-body training-panel">
|
||||
<div id="training-metrics">
|
||||
<!-- Model Status Indicators -->
|
||||
<div class="mb-3">
|
||||
<h6>Model Status</h6>
|
||||
{% for model in models %}
|
||||
<span class="model-status status-{{ model.status }}">
|
||||
{{ model.name }}: {{ model.status_text }}
|
||||
</span>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
<!-- Training Metrics -->
|
||||
<div class="mb-3">
|
||||
<h6>Training Metrics</h6>
|
||||
{% for metric in training_metrics %}
|
||||
<div class="row mb-1">
|
||||
<div class="col-6">
|
||||
<small>{{ metric.name }}:</small>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<small class="fw-bold">{{ metric.value }}</small>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
<!-- Performance Stats -->
|
||||
<div class="mb-3">
|
||||
<h6>Performance</h6>
|
||||
{% for stat in performance_stats %}
|
||||
<div class="row mb-1">
|
||||
<div class="col-8">
|
||||
<small>{{ stat.name }}:</small>
|
||||
</div>
|
||||
<div class="col-4">
|
||||
<small class="fw-bold">{{ stat.value }}</small>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Closed Trades Row -->
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h6>Recent Closed Trades</h6>
|
||||
</div>
|
||||
<div class="card-body closed-trades">
|
||||
<div id="closed-trades-table">
|
||||
<table class="table table-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Time</th>
|
||||
<th>Symbol</th>
|
||||
<th>Side</th>
|
||||
<th>Size</th>
|
||||
<th>Entry</th>
|
||||
<th>Exit</th>
|
||||
<th>PnL</th>
|
||||
<th>Duration</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for trade in closed_trades %}
|
||||
<tr>
|
||||
<td>{{ trade.time }}</td>
|
||||
<td>{{ trade.symbol }}</td>
|
||||
<td>
|
||||
<span class="badge {% if trade.side == 'BUY' %}bg-success{% else %}bg-danger{% endif %}">
|
||||
{{ trade.side }}
|
||||
</span>
|
||||
</td>
|
||||
<td>{{ trade.size }}</td>
|
||||
<td>${{ trade.entry_price }}</td>
|
||||
<td>${{ trade.exit_price }}</td>
|
||||
<td class="{% if trade.pnl > 0 %}trade-profit{% else %}trade-loss{% endif %}">
|
||||
${{ trade.pnl }}
|
||||
</td>
|
||||
<td>{{ trade.duration }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Auto-refresh interval -->
|
||||
<div id="interval-component" style="display: none;" data-interval="{{ refresh_interval }}"></div>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user