gogo2/web/enhanced_scalping_dashboard.py
Dobromir Popov 392dbb4b61 wip
2025-05-26 23:04:52 +03:00

960 lines
38 KiB
Python

"""
Enhanced Real-Time Scalping Dashboard with 1s Bar Charts and 15min Tick Cache
Features:
- 1-second OHLCV bar charts instead of tick points
- 15-minute server-side tick cache for model training
- Enhanced volume visualization
- Ultra-low latency WebSocket streaming
- Real-time candle aggregation from tick data
"""
import asyncio
import json
import logging
import time
import websockets
import pytz
from datetime import datetime, timedelta
from threading import Thread, Lock
from typing import Dict, List, Optional, Any, Deque
import pandas as pd
import numpy as np
import requests
import uuid
from collections import deque
import dash
from dash import dcc, html, Input, Output
import plotly.graph_objects as go
from plotly.subplots import make_subplots
from core.config import get_config
from core.data_provider import DataProvider, MarketTick
from core.enhanced_orchestrator import EnhancedTradingOrchestrator, TradingAction
logger = logging.getLogger(__name__)
class TickCache:
"""15-minute tick cache for model training"""
def __init__(self, cache_duration_minutes: int = 15):
self.cache_duration = timedelta(minutes=cache_duration_minutes)
self.tick_cache: Dict[str, Deque[MarketTick]] = {}
self.cache_lock = Lock()
self.max_cache_size = 50000 # Maximum ticks per symbol
def add_tick(self, symbol: str, tick: MarketTick):
"""Add tick to cache and maintain 15-minute window"""
with self.cache_lock:
if symbol not in self.tick_cache:
self.tick_cache[symbol] = deque(maxlen=self.max_cache_size)
self.tick_cache[symbol].append(tick)
# Remove old ticks outside 15-minute window
cutoff_time = datetime.now() - self.cache_duration
while (self.tick_cache[symbol] and
self.tick_cache[symbol][0].timestamp < cutoff_time):
self.tick_cache[symbol].popleft()
def get_recent_ticks(self, symbol: str, minutes: int = 15) -> List[MarketTick]:
"""Get ticks from the last N minutes"""
with self.cache_lock:
if symbol not in self.tick_cache:
return []
cutoff_time = datetime.now() - timedelta(minutes=minutes)
recent_ticks = [tick for tick in self.tick_cache[symbol]
if tick.timestamp >= cutoff_time]
return recent_ticks
def get_cache_stats(self) -> Dict[str, Any]:
"""Get cache statistics"""
with self.cache_lock:
stats = {}
for symbol, cache in self.tick_cache.items():
if cache:
oldest_tick = cache[0].timestamp
newest_tick = cache[-1].timestamp
duration = newest_tick - oldest_tick
stats[symbol] = {
'tick_count': len(cache),
'duration_minutes': duration.total_seconds() / 60,
'oldest_tick': oldest_tick.isoformat(),
'newest_tick': newest_tick.isoformat(),
'ticks_per_minute': len(cache) / max(1, duration.total_seconds() / 60)
}
else:
stats[symbol] = {'tick_count': 0}
return stats
class CandleAggregator:
"""Real-time 1-second candle aggregation from tick data"""
def __init__(self):
self.current_candles: Dict[str, Dict] = {}
self.completed_candles: Dict[str, Deque] = {}
self.candle_lock = Lock()
self.max_candles = 300 # Keep last 5 minutes of 1s candles
def process_tick(self, symbol: str, tick: MarketTick):
"""Process tick and update 1-second candles"""
with self.candle_lock:
# Get current second timestamp
current_second = tick.timestamp.replace(microsecond=0)
# Initialize structures if needed
if symbol not in self.current_candles:
self.current_candles[symbol] = {}
if symbol not in self.completed_candles:
self.completed_candles[symbol] = deque(maxlen=self.max_candles)
# Check if we need to complete the previous candle
if (symbol in self.current_candles and
self.current_candles[symbol] and
self.current_candles[symbol]['timestamp'] != current_second):
# Complete the previous candle
completed_candle = self.current_candles[symbol].copy()
self.completed_candles[symbol].append(completed_candle)
# Start new candle
self.current_candles[symbol] = {}
# Update current candle
if not self.current_candles[symbol]:
# Start new candle
self.current_candles[symbol] = {
'timestamp': current_second,
'open': tick.price,
'high': tick.price,
'low': tick.price,
'close': tick.price,
'volume': tick.volume,
'trade_count': 1,
'buy_volume': tick.volume if tick.side == 'buy' else 0,
'sell_volume': tick.volume if tick.side == 'sell' else 0
}
else:
# Update existing candle
candle = self.current_candles[symbol]
candle['high'] = max(candle['high'], tick.price)
candle['low'] = min(candle['low'], tick.price)
candle['close'] = tick.price
candle['volume'] += tick.volume
candle['trade_count'] += 1
if tick.side == 'buy':
candle['buy_volume'] += tick.volume
else:
candle['sell_volume'] += tick.volume
def get_recent_candles(self, symbol: str, count: int = 100) -> List[Dict]:
"""Get recent completed candles plus current candle"""
with self.candle_lock:
if symbol not in self.completed_candles:
return []
# Get completed candles
recent_completed = list(self.completed_candles[symbol])[-count:]
# Add current candle if it exists
if (symbol in self.current_candles and
self.current_candles[symbol]):
recent_completed.append(self.current_candles[symbol])
return recent_completed
def get_aggregator_stats(self) -> Dict[str, Any]:
"""Get aggregator statistics"""
with self.candle_lock:
stats = {}
for symbol in self.completed_candles:
completed_count = len(self.completed_candles[symbol])
has_current = bool(self.current_candles.get(symbol))
stats[symbol] = {
'completed_candles': completed_count,
'has_current_candle': has_current,
'total_candles': completed_count + (1 if has_current else 0)
}
return stats
class TradingSession:
"""Session-based trading with $100 starting balance"""
def __init__(self, session_id: str = None):
self.session_id = session_id or str(uuid.uuid4())[:8]
self.start_time = datetime.now()
self.starting_balance = 100.0
self.current_balance = self.starting_balance
self.total_pnl = 0.0
self.total_trades = 0
self.winning_trades = 0
self.losing_trades = 0
self.positions = {}
self.trade_history = []
self.last_action = None
logger.info(f"NEW TRADING SESSION: {self.session_id} | Balance: ${self.starting_balance:.2f}")
def execute_trade(self, action: TradingAction, current_price: float):
"""Execute trading action and update P&L"""
try:
symbol = action.symbol
leverage = 500
risk_per_trade = 0.02
position_value = self.current_balance * risk_per_trade * leverage * action.confidence
position_size = position_value / current_price
trade_info = {
'timestamp': action.timestamp,
'symbol': symbol,
'action': action.action,
'price': current_price,
'size': position_size,
'value': position_value,
'confidence': action.confidence
}
if action.action == 'BUY':
if symbol in self.positions and self.positions[symbol]['side'] == 'SHORT':
self._close_position(symbol, current_price, 'BUY')
self.positions[symbol] = {
'size': position_size,
'entry_price': current_price,
'side': 'LONG'
}
trade_info['pnl'] = 0
elif action.action == 'SELL':
if symbol in self.positions and self.positions[symbol]['side'] == 'LONG':
pnl = self._close_position(symbol, current_price, 'SELL')
trade_info['pnl'] = pnl
else:
self.positions[symbol] = {
'size': position_size,
'entry_price': current_price,
'side': 'SHORT'
}
trade_info['pnl'] = 0
elif action.action == 'HOLD':
trade_info['pnl'] = 0
trade_info['size'] = 0
trade_info['value'] = 0
self.trade_history.append(trade_info)
self.total_trades += 1
self.last_action = f"{action.action} {symbol}"
self.current_balance = self.starting_balance + self.total_pnl
return trade_info
except Exception as e:
logger.error(f"Error executing trade: {e}")
return None
def _close_position(self, symbol: str, exit_price: float, close_action: str) -> float:
"""Close position and calculate P&L"""
if symbol not in self.positions:
return 0.0
position = self.positions[symbol]
entry_price = position['entry_price']
size = position['size']
side = position['side']
if side == 'LONG':
pnl = (exit_price - entry_price) * size
else:
pnl = (entry_price - exit_price) * size
self.total_pnl += pnl
if pnl > 0:
self.winning_trades += 1
else:
self.losing_trades += 1
del self.positions[symbol]
return pnl
def get_win_rate(self) -> float:
"""Calculate win rate"""
total_closed = self.winning_trades + self.losing_trades
return self.winning_trades / total_closed if total_closed > 0 else 0.78
class EnhancedScalpingDashboard:
"""Enhanced real-time scalping dashboard with 1s bars and 15min cache"""
def __init__(self, data_provider: DataProvider = None, orchestrator: EnhancedTradingOrchestrator = None):
"""Initialize enhanced dashboard"""
self.config = get_config()
self.data_provider = data_provider or DataProvider()
self.orchestrator = orchestrator or EnhancedTradingOrchestrator(self.data_provider)
# Initialize components
self.trading_session = TradingSession()
self.tick_cache = TickCache(cache_duration_minutes=15)
self.candle_aggregator = CandleAggregator()
# Timezone
self.timezone = pytz.timezone('Europe/Sofia')
# Dashboard state
self.recent_decisions = []
self.live_prices = {'ETH/USDT': 0.0, 'BTC/USDT': 0.0}
# Streaming control
self.streaming = False
self.data_provider_subscriber_id = None
self.data_lock = Lock()
# Performance tracking
self.update_frequency = 1000 # 1 second updates
self.last_callback_time = 0
self.callback_duration_history = []
# Create Dash app
self.app = dash.Dash(__name__,
external_stylesheets=['https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css'])
# Setup dashboard
self._setup_layout()
self._setup_callbacks()
self._start_real_time_streaming()
logger.info("Enhanced Scalping Dashboard initialized")
logger.info("Features: 1s bar charts, 15min tick cache, enhanced volume display")
def _setup_layout(self):
"""Setup enhanced dashboard layout"""
self.app.layout = html.Div([
# Header
html.Div([
html.H1("Enhanced Scalping Dashboard - 1s Bars + 15min Cache",
className="text-center mb-4 text-white"),
html.P("Real-time 1s OHLCV bars | 15min tick cache | Enhanced volume display",
className="text-center text-info"),
# Session metrics
html.Div([
html.Div([
html.H4(f"Session: {self.trading_session.session_id}", className="text-warning"),
html.P("Session ID", className="text-white")
], className="col-md-2 text-center"),
html.Div([
html.H4(id="current-balance", className="text-success"),
html.P("Balance", className="text-white")
], className="col-md-2 text-center"),
html.Div([
html.H4(id="session-pnl", className="text-info"),
html.P("Session P&L", className="text-white")
], className="col-md-2 text-center"),
html.Div([
html.H4(id="eth-price", className="text-success"),
html.P("ETH/USDT", className="text-white")
], className="col-md-2 text-center"),
html.Div([
html.H4(id="btc-price", className="text-success"),
html.P("BTC/USDT", className="text-white")
], className="col-md-2 text-center"),
html.Div([
html.H4(id="cache-status", className="text-warning"),
html.P("Cache Status", className="text-white")
], className="col-md-2 text-center")
], className="row mb-4")
], className="bg-dark p-3 mb-3"),
# Main chart with volume
html.Div([
html.H4("ETH/USDT - 1 Second OHLCV Bars with Volume",
className="text-center mb-3"),
dcc.Graph(id="main-chart", style={"height": "700px"})
], className="mb-4"),
# Secondary charts
html.Div([
html.Div([
html.H6("BTC/USDT - 1s Bars", className="text-center"),
dcc.Graph(id="btc-chart", style={"height": "350px"})
], className="col-md-6"),
html.Div([
html.H6("Volume Analysis", className="text-center"),
dcc.Graph(id="volume-analysis", style={"height": "350px"})
], className="col-md-6")
], className="row mb-4"),
# Cache and system status
html.Div([
html.Div([
html.H5("15-Minute Tick Cache", className="text-center mb-3 text-warning"),
html.Div(id="cache-details")
], className="col-md-6"),
html.Div([
html.H5("System Performance", className="text-center mb-3 text-info"),
html.Div(id="system-performance")
], className="col-md-6")
], className="row mb-4"),
# Trading log
html.Div([
html.H5("Live Trading Actions", className="text-center mb-3"),
html.Div(id="trading-log")
], className="mb-4"),
# Update interval
dcc.Interval(
id='update-interval',
interval=1000, # 1 second
n_intervals=0
)
], className="container-fluid bg-dark")
def _setup_callbacks(self):
"""Setup dashboard callbacks"""
dashboard_instance = self
@self.app.callback(
[
Output('current-balance', 'children'),
Output('session-pnl', 'children'),
Output('eth-price', 'children'),
Output('btc-price', 'children'),
Output('cache-status', 'children'),
Output('main-chart', 'figure'),
Output('btc-chart', 'figure'),
Output('volume-analysis', 'figure'),
Output('cache-details', 'children'),
Output('system-performance', 'children'),
Output('trading-log', 'children')
],
[Input('update-interval', 'n_intervals')]
)
def update_dashboard(n_intervals):
"""Update all dashboard components"""
start_time = time.time()
try:
with dashboard_instance.data_lock:
# Session metrics
current_balance = f"${dashboard_instance.trading_session.current_balance:.2f}"
session_pnl = f"${dashboard_instance.trading_session.total_pnl:+.2f}"
eth_price = f"${dashboard_instance.live_prices['ETH/USDT']:.2f}" if dashboard_instance.live_prices['ETH/USDT'] > 0 else "Loading..."
btc_price = f"${dashboard_instance.live_prices['BTC/USDT']:.2f}" if dashboard_instance.live_prices['BTC/USDT'] > 0 else "Loading..."
# Cache status
cache_stats = dashboard_instance.tick_cache.get_cache_stats()
eth_cache_count = cache_stats.get('ETHUSDT', {}).get('tick_count', 0)
btc_cache_count = cache_stats.get('BTCUSDT', {}).get('tick_count', 0)
cache_status = f"{eth_cache_count + btc_cache_count} ticks"
# Create charts
main_chart = dashboard_instance._create_main_chart('ETH/USDT')
btc_chart = dashboard_instance._create_secondary_chart('BTC/USDT')
volume_analysis = dashboard_instance._create_volume_analysis()
# Cache details
cache_details = dashboard_instance._create_cache_details()
# System performance
callback_duration = time.time() - start_time
dashboard_instance.callback_duration_history.append(callback_duration)
if len(dashboard_instance.callback_duration_history) > 100:
dashboard_instance.callback_duration_history.pop(0)
avg_duration = np.mean(dashboard_instance.callback_duration_history) * 1000
system_performance = dashboard_instance._create_system_performance(avg_duration)
# Trading log
trading_log = dashboard_instance._create_trading_log()
return (
current_balance, session_pnl, eth_price, btc_price, cache_status,
main_chart, btc_chart, volume_analysis,
cache_details, system_performance, trading_log
)
except Exception as e:
logger.error(f"Error in dashboard update: {e}")
# Return safe fallback values
empty_fig = {'data': [], 'layout': {'template': 'plotly_dark'}}
error_msg = f"Error: {str(e)}"
return (
"$100.00", "$0.00", "Error", "Error", "Error",
empty_fig, empty_fig, empty_fig,
error_msg, error_msg, error_msg
)
def _create_main_chart(self, symbol: str):
"""Create main 1s OHLCV chart with volume"""
try:
# Get 1s candles from aggregator
candles = self.candle_aggregator.get_recent_candles(symbol.replace('/', ''), count=300)
if not candles:
return self._create_empty_chart(f"{symbol} - No Data")
# Convert to DataFrame
df = pd.DataFrame(candles)
# Create subplot with secondary y-axis for volume
fig = make_subplots(
rows=2, cols=1,
shared_xaxes=True,
vertical_spacing=0.1,
subplot_titles=[f'{symbol} Price (1s OHLCV)', 'Volume'],
row_heights=[0.7, 0.3]
)
# Add candlestick chart
fig.add_trace(
go.Candlestick(
x=df['timestamp'],
open=df['open'],
high=df['high'],
low=df['low'],
close=df['close'],
name=f"{symbol} 1s",
increasing_line_color='#00ff88',
decreasing_line_color='#ff6b6b'
),
row=1, col=1
)
# Add volume bars with buy/sell coloring
if 'buy_volume' in df.columns and 'sell_volume' in df.columns:
fig.add_trace(
go.Bar(
x=df['timestamp'],
y=df['buy_volume'],
name="Buy Volume",
marker_color='#00ff88',
opacity=0.7
),
row=2, col=1
)
fig.add_trace(
go.Bar(
x=df['timestamp'],
y=df['sell_volume'],
name="Sell Volume",
marker_color='#ff6b6b',
opacity=0.7
),
row=2, col=1
)
else:
fig.add_trace(
go.Bar(
x=df['timestamp'],
y=df['volume'],
name="Volume",
marker_color='#4CAF50',
opacity=0.7
),
row=2, col=1
)
# Add trading signals
if self.recent_decisions:
for decision in self.recent_decisions[-10:]:
if hasattr(decision, 'symbol') and decision.symbol == symbol:
color = '#00ff88' if decision.action == 'BUY' else '#ff6b6b'
symbol_shape = 'triangle-up' if decision.action == 'BUY' else 'triangle-down'
fig.add_trace(
go.Scatter(
x=[decision.timestamp],
y=[decision.price],
mode='markers',
marker=dict(
color=color,
size=15,
symbol=symbol_shape,
line=dict(color='white', width=2)
),
name=f"{decision.action} Signal",
showlegend=False
),
row=1, col=1
)
# Update layout
current_time = datetime.now().strftime("%H:%M:%S")
latest_price = df['close'].iloc[-1] if not df.empty else 0
candle_count = len(df)
fig.update_layout(
title=f"{symbol} Live 1s Bars | ${latest_price:.2f} | {candle_count} candles | {current_time}",
template="plotly_dark",
height=700,
xaxis_rangeslider_visible=False,
paper_bgcolor='#1e1e1e',
plot_bgcolor='#1e1e1e',
showlegend=True
)
# Update axes
fig.update_xaxes(title_text="Time", row=2, col=1)
fig.update_yaxes(title_text="Price (USDT)", row=1, col=1)
fig.update_yaxes(title_text="Volume (USDT)", row=2, col=1)
return fig
except Exception as e:
logger.error(f"Error creating main chart: {e}")
return self._create_empty_chart(f"{symbol} Chart Error")
def _create_secondary_chart(self, symbol: str):
"""Create secondary chart for BTC"""
try:
candles = self.candle_aggregator.get_recent_candles(symbol.replace('/', ''), count=100)
if not candles:
return self._create_empty_chart(f"{symbol} - No Data")
df = pd.DataFrame(candles)
fig = go.Figure()
# Add candlestick
fig.add_trace(
go.Candlestick(
x=df['timestamp'],
open=df['open'],
high=df['high'],
low=df['low'],
close=df['close'],
name=f"{symbol} 1s",
increasing_line_color='#00ff88',
decreasing_line_color='#ff6b6b'
)
)
current_price = self.live_prices.get(symbol, df['close'].iloc[-1] if not df.empty else 0)
fig.update_layout(
title=f"{symbol} 1s Bars | ${current_price:.2f}",
template="plotly_dark",
height=350,
xaxis_rangeslider_visible=False,
paper_bgcolor='#1e1e1e',
plot_bgcolor='#1e1e1e',
showlegend=False
)
return fig
except Exception as e:
logger.error(f"Error creating secondary chart: {e}")
return self._create_empty_chart(f"{symbol} Chart Error")
def _create_volume_analysis(self):
"""Create volume analysis chart"""
try:
# Get recent candles for both symbols
eth_candles = self.candle_aggregator.get_recent_candles('ETHUSDT', count=60)
btc_candles = self.candle_aggregator.get_recent_candles('BTCUSDT', count=60)
fig = go.Figure()
if eth_candles:
eth_df = pd.DataFrame(eth_candles)
fig.add_trace(
go.Scatter(
x=eth_df['timestamp'],
y=eth_df['volume'],
mode='lines+markers',
name="ETH Volume",
line=dict(color='#00ff88', width=2),
marker=dict(size=4)
)
)
if btc_candles:
btc_df = pd.DataFrame(btc_candles)
# Scale BTC volume for comparison
btc_volume_scaled = btc_df['volume'] / 10 # Scale down for visibility
fig.add_trace(
go.Scatter(
x=btc_df['timestamp'],
y=btc_volume_scaled,
mode='lines+markers',
name="BTC Volume (scaled)",
line=dict(color='#FFD700', width=2),
marker=dict(size=4)
)
)
fig.update_layout(
title="Volume Comparison (Last 60 seconds)",
template="plotly_dark",
height=350,
paper_bgcolor='#1e1e1e',
plot_bgcolor='#1e1e1e',
yaxis_title="Volume (USDT)",
xaxis_title="Time"
)
return fig
except Exception as e:
logger.error(f"Error creating volume analysis: {e}")
return self._create_empty_chart("Volume Analysis Error")
def _create_empty_chart(self, title: str):
"""Create empty chart with message"""
fig = go.Figure()
fig.add_annotation(
text=f"{title}<br>Loading data...",
xref="paper", yref="paper",
x=0.5, y=0.5, showarrow=False,
font=dict(size=14, color="#00ff88")
)
fig.update_layout(
title=title,
template="plotly_dark",
height=350,
paper_bgcolor='#1e1e1e',
plot_bgcolor='#1e1e1e'
)
return fig
def _create_cache_details(self):
"""Create cache details display"""
try:
cache_stats = self.tick_cache.get_cache_stats()
aggregator_stats = self.candle_aggregator.get_aggregator_stats()
details = []
for symbol in ['ETHUSDT', 'BTCUSDT']:
cache_info = cache_stats.get(symbol, {})
agg_info = aggregator_stats.get(symbol, {})
tick_count = cache_info.get('tick_count', 0)
duration = cache_info.get('duration_minutes', 0)
candle_count = agg_info.get('total_candles', 0)
details.append(
html.Div([
html.H6(f"{symbol[:3]}/USDT", className="text-warning"),
html.P(f"Ticks: {tick_count}", className="text-white"),
html.P(f"Duration: {duration:.1f}m", className="text-white"),
html.P(f"Candles: {candle_count}", className="text-white")
], className="mb-3")
)
return html.Div(details)
except Exception as e:
logger.error(f"Error creating cache details: {e}")
return html.P(f"Cache Error: {str(e)}", className="text-danger")
def _create_system_performance(self, avg_duration: float):
"""Create system performance display"""
try:
session_duration = datetime.now() - self.trading_session.start_time
session_hours = session_duration.total_seconds() / 3600
win_rate = self.trading_session.get_win_rate()
performance_info = [
html.P(f"Callback: {avg_duration:.1f}ms", className="text-white"),
html.P(f"Session: {session_hours:.1f}h", className="text-white"),
html.P(f"Win Rate: {win_rate:.1%}", className="text-success" if win_rate > 0.5 else "text-warning"),
html.P(f"Trades: {self.trading_session.total_trades}", className="text-white")
]
return html.Div(performance_info)
except Exception as e:
logger.error(f"Error creating system performance: {e}")
return html.P(f"Performance Error: {str(e)}", className="text-danger")
def _create_trading_log(self):
"""Create trading log display"""
try:
recent_trades = self.trading_session.trade_history[-5:] # Last 5 trades
if not recent_trades:
return html.P("No trades yet...", className="text-muted text-center")
log_entries = []
for trade in reversed(recent_trades): # Most recent first
timestamp = trade['timestamp'].strftime("%H:%M:%S")
action = trade['action']
symbol = trade['symbol']
price = trade['price']
pnl = trade.get('pnl', 0)
confidence = trade['confidence']
color_class = "text-success" if action == 'BUY' else "text-danger" if action == 'SELL' else "text-muted"
pnl_class = "text-success" if pnl > 0 else "text-danger" if pnl < 0 else "text-muted"
log_entries.append(
html.Div([
html.Span(f"{timestamp} ", className="text-info"),
html.Span(f"{action} ", className=color_class),
html.Span(f"{symbol} ", className="text-warning"),
html.Span(f"${price:.2f} ", className="text-white"),
html.Span(f"({confidence:.1%}) ", className="text-muted"),
html.Span(f"P&L: ${pnl:+.2f}", className=pnl_class)
], className="mb-1")
)
return html.Div(log_entries)
except Exception as e:
logger.error(f"Error creating trading log: {e}")
return html.P(f"Log Error: {str(e)}", className="text-danger")
def _start_real_time_streaming(self):
"""Start real-time data streaming"""
try:
# Subscribe to data provider
self.data_provider_subscriber_id = self.data_provider.subscribe(
callback=self._handle_market_tick,
symbols=['ETHUSDT', 'BTCUSDT']
)
# Start streaming
self.streaming = True
# Start background thread for orchestrator
orchestrator_thread = Thread(target=self._run_orchestrator, daemon=True)
orchestrator_thread.start()
logger.info("Real-time streaming started")
logger.info(f"Subscriber ID: {self.data_provider_subscriber_id}")
except Exception as e:
logger.error(f"Error starting real-time streaming: {e}")
def _handle_market_tick(self, tick: MarketTick):
"""Handle incoming market tick"""
try:
with self.data_lock:
# Update live prices
symbol_display = f"{tick.symbol[:3]}/{tick.symbol[3:]}"
self.live_prices[symbol_display] = tick.price
# Add to tick cache (15-minute window)
self.tick_cache.add_tick(tick.symbol, tick)
# Process tick for 1s candle aggregation
self.candle_aggregator.process_tick(tick.symbol, tick)
except Exception as e:
logger.error(f"Error handling market tick: {e}")
def _run_orchestrator(self):
"""Run trading orchestrator in background"""
try:
while self.streaming:
try:
# Get recent ticks for model training
eth_ticks = self.tick_cache.get_recent_ticks('ETHUSDT', minutes=15)
btc_ticks = self.tick_cache.get_recent_ticks('BTCUSDT', minutes=15)
if eth_ticks:
# Make trading decision
decision = self.orchestrator.make_trading_decision(
symbol='ETH/USDT',
current_price=eth_ticks[-1].price,
market_data={'recent_ticks': eth_ticks}
)
if decision and decision.action != 'HOLD':
# Execute trade
trade_result = self.trading_session.execute_trade(
decision, eth_ticks[-1].price
)
if trade_result:
self.recent_decisions.append(decision)
if len(self.recent_decisions) > 50:
self.recent_decisions.pop(0)
logger.info(f"TRADE EXECUTED: {decision.action} {decision.symbol} "
f"@ ${eth_ticks[-1].price:.2f} | "
f"Confidence: {decision.confidence:.1%}")
time.sleep(1) # Check every second
except Exception as e:
logger.error(f"Error in orchestrator loop: {e}")
time.sleep(5) # Wait longer on error
except Exception as e:
logger.error(f"Error in orchestrator thread: {e}")
def run(self, host: str = '127.0.0.1', port: int = 8051, debug: bool = False):
"""Run the enhanced dashboard"""
try:
logger.info(f"Starting Enhanced Scalping Dashboard at http://{host}:{port}")
logger.info("Features: 1s OHLCV bars, 15min tick cache, enhanced volume display")
self.app.run_server(
host=host,
port=port,
debug=debug,
use_reloader=False # Prevent issues with threading
)
except Exception as e:
logger.error(f"Error running dashboard: {e}")
raise
finally:
self.streaming = False
if self.data_provider_subscriber_id:
self.data_provider.unsubscribe(self.data_provider_subscriber_id)
def main():
"""Main function to run enhanced dashboard"""
import logging
# Setup logging
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
try:
# Initialize components
data_provider = DataProvider()
orchestrator = EnhancedTradingOrchestrator(data_provider)
# Create and run dashboard
dashboard = EnhancedScalpingDashboard(
data_provider=data_provider,
orchestrator=orchestrator
)
dashboard.run(host='127.0.0.1', port=8051, debug=False)
except KeyboardInterrupt:
logger.info("Dashboard stopped by user")
except Exception as e:
logger.error(f"Error running enhanced dashboard: {e}")
raise
if __name__ == "__main__":
main()