1406 lines
64 KiB
Python
1406 lines
64 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
|
|
|
|
# # Check for losing trades and add to negative case trainer (if available)
|
|
# if trade_info.get('pnl', 0) < 0:
|
|
# self._handle_losing_trade(trade_info, action, current_price)
|
|
|
|
# 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
|
|
|
|
# def _handle_losing_trade(self, trade_info: Dict[str, Any], action: TradingAction, current_price: float):
|
|
# """Handle losing trade by adding it to negative case trainer for intensive training"""
|
|
# try:
|
|
# # Create market data context for the negative case
|
|
# market_data = {
|
|
# 'exit_price': current_price,
|
|
# 'state_before': {
|
|
# 'price': trade_info['price'],
|
|
# 'confidence': trade_info['confidence'],
|
|
# 'timestamp': trade_info['timestamp']
|
|
# },
|
|
# 'state_after': {
|
|
# 'price': current_price,
|
|
# 'timestamp': datetime.now(),
|
|
# 'pnl': trade_info['pnl']
|
|
# },
|
|
# 'tick_data': [], # Could be populated with recent tick data
|
|
# 'technical_indicators': {} # Could be populated with indicators
|
|
# }
|
|
|
|
# # Add to negative case trainer if orchestrator has one
|
|
# if hasattr(self, 'orchestrator') and hasattr(self.orchestrator, 'negative_case_trainer'):
|
|
# case_id = self.orchestrator.negative_case_trainer.add_losing_trade(trade_info, market_data)
|
|
# if case_id:
|
|
# logger.warning(f"LOSING TRADE ADDED TO INTENSIVE TRAINING: {case_id}")
|
|
# logger.warning(f"Loss: ${abs(trade_info['pnl']):.2f} on {trade_info['action']} {trade_info['symbol']}")
|
|
|
|
# except Exception as e:
|
|
# logger.error(f"Error handling losing trade for negative case training: {e}")
|
|
|
|
# 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.trading_session.orchestrator = self.orchestrator # Pass orchestrator reference for negative case training
|
|
# 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"),
|
|
|
|
# # Model Training & Orchestrator Status
|
|
# html.Div([
|
|
# html.Div([
|
|
# html.H5("Model Training Progress", className="text-center mb-3 text-warning"),
|
|
# html.Div(id="model-training-status")
|
|
# ], className="col-md-6"),
|
|
|
|
# html.Div([
|
|
# html.H5("Orchestrator Data Flow", className="text-center mb-3 text-info"),
|
|
# html.Div(id="orchestrator-status")
|
|
# ], className="col-md-6")
|
|
# ], className="row mb-4"),
|
|
|
|
# # RL & CNN Events Log
|
|
# html.Div([
|
|
# html.H5("RL & CNN Training Events (Real-Time)", className="text-center mb-3 text-success"),
|
|
# html.Div(id="training-events-log")
|
|
# ], className="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('model-training-status', 'children'),
|
|
# Output('orchestrator-status', 'children'),
|
|
# Output('training-events-log', 'children'),
|
|
# 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()
|
|
|
|
# # Model training status
|
|
# model_training_status = dashboard_instance._create_model_training_status()
|
|
|
|
# # Orchestrator status
|
|
# orchestrator_status = dashboard_instance._create_orchestrator_status()
|
|
|
|
# # Training events log
|
|
# training_events_log = dashboard_instance._create_training_events_log()
|
|
|
|
# # 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,
|
|
# model_training_status, orchestrator_status, training_events_log,
|
|
# 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,
|
|
# 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 _create_model_training_status(self):
|
|
# """Create model training status display with enhanced extrema information"""
|
|
# try:
|
|
# # Get training status in the expected format
|
|
# training_status = self._get_model_training_status()
|
|
|
|
# # Training data structures
|
|
# tick_cache_size = sum(len(cache) for cache in self.tick_cache.tick_cache.values())
|
|
|
|
# training_items = []
|
|
|
|
# # Training Data Stream
|
|
# training_items.append(
|
|
# html.Div([
|
|
# html.H6([
|
|
# html.I(className="fas fa-database me-2 text-info"),
|
|
# "Training Data Stream"
|
|
# ], className="mb-2"),
|
|
# html.Div([
|
|
# html.Small([
|
|
# html.Strong("Tick Cache: "),
|
|
# html.Span(f"{tick_cache_size:,} ticks", className="text-success" if tick_cache_size > 100 else "text-warning")
|
|
# ], className="d-block"),
|
|
# html.Small([
|
|
# html.Strong("1s Bars: "),
|
|
# html.Span(f"{sum(len(candles) for candles in self.candle_aggregator.completed_candles.values())} bars",
|
|
# className="text-success")
|
|
# ], className="d-block"),
|
|
# html.Small([
|
|
# html.Strong("Stream: "),
|
|
# html.Span("LIVE" if self.streaming else "OFFLINE",
|
|
# className="text-success" if self.streaming else "text-danger")
|
|
# ], className="d-block")
|
|
# ])
|
|
# ], className="mb-3 p-2 border border-info rounded")
|
|
# )
|
|
|
|
# # CNN Model Status
|
|
# training_items.append(
|
|
# html.Div([
|
|
# html.H6([
|
|
# html.I(className="fas fa-brain me-2 text-warning"),
|
|
# "CNN Model"
|
|
# ], className="mb-2"),
|
|
# html.Div([
|
|
# html.Small([
|
|
# html.Strong("Status: "),
|
|
# html.Span(training_status['cnn']['status'],
|
|
# className=f"text-{training_status['cnn']['status_color']}")
|
|
# ], className="d-block"),
|
|
# html.Small([
|
|
# html.Strong("Accuracy: "),
|
|
# html.Span(f"{training_status['cnn']['accuracy']:.1%}", className="text-info")
|
|
# ], className="d-block"),
|
|
# html.Small([
|
|
# html.Strong("Loss: "),
|
|
# html.Span(f"{training_status['cnn']['loss']:.4f}", className="text-muted")
|
|
# ], className="d-block"),
|
|
# html.Small([
|
|
# html.Strong("Epochs: "),
|
|
# html.Span(f"{training_status['cnn']['epochs']}", className="text-muted")
|
|
# ], className="d-block"),
|
|
# html.Small([
|
|
# html.Strong("Learning Rate: "),
|
|
# html.Span(f"{training_status['cnn']['learning_rate']:.6f}", className="text-muted")
|
|
# ], className="d-block")
|
|
# ])
|
|
# ], className="mb-3 p-2 border border-warning rounded")
|
|
# )
|
|
|
|
# # RL Agent Status
|
|
# training_items.append(
|
|
# html.Div([
|
|
# html.H6([
|
|
# html.I(className="fas fa-robot me-2 text-success"),
|
|
# "RL Agent (DQN)"
|
|
# ], className="mb-2"),
|
|
# html.Div([
|
|
# html.Small([
|
|
# html.Strong("Status: "),
|
|
# html.Span(training_status['rl']['status'],
|
|
# className=f"text-{training_status['rl']['status_color']}")
|
|
# ], className="d-block"),
|
|
# html.Small([
|
|
# html.Strong("Win Rate: "),
|
|
# html.Span(f"{training_status['rl']['win_rate']:.1%}", className="text-info")
|
|
# ], className="d-block"),
|
|
# html.Small([
|
|
# html.Strong("Avg Reward: "),
|
|
# html.Span(f"{training_status['rl']['avg_reward']:.2f}", className="text-muted")
|
|
# ], className="d-block"),
|
|
# html.Small([
|
|
# html.Strong("Episodes: "),
|
|
# html.Span(f"{training_status['rl']['episodes']}", className="text-muted")
|
|
# ], className="d-block"),
|
|
# html.Small([
|
|
# html.Strong("Epsilon: "),
|
|
# html.Span(f"{training_status['rl']['epsilon']:.3f}", className="text-muted")
|
|
# ], className="d-block"),
|
|
# html.Small([
|
|
# html.Strong("Memory: "),
|
|
# html.Span(f"{training_status['rl']['memory_size']:,}", className="text-muted")
|
|
# ], className="d-block")
|
|
# ])
|
|
# ], className="mb-3 p-2 border border-success rounded")
|
|
# )
|
|
|
|
# return html.Div(training_items)
|
|
|
|
# except Exception as e:
|
|
# logger.error(f"Error creating model training status: {e}")
|
|
# return html.Div([
|
|
# html.P("⚠️ Error loading training status", className="text-warning text-center"),
|
|
# html.P(f"Error: {str(e)}", className="text-muted text-center small")
|
|
# ], className="p-3")
|
|
|
|
# def _get_model_training_status(self) -> Dict:
|
|
# """Get current model training status and metrics"""
|
|
# try:
|
|
# # Initialize default status
|
|
# status = {
|
|
# 'cnn': {
|
|
# 'status': 'TRAINING',
|
|
# 'status_color': 'warning',
|
|
# 'accuracy': 0.0,
|
|
# 'loss': 0.0,
|
|
# 'epochs': 0,
|
|
# 'learning_rate': 0.001
|
|
# },
|
|
# 'rl': {
|
|
# 'status': 'TRAINING',
|
|
# 'status_color': 'success',
|
|
# 'win_rate': 0.0,
|
|
# 'avg_reward': 0.0,
|
|
# 'episodes': 0,
|
|
# 'epsilon': 1.0,
|
|
# 'memory_size': 0
|
|
# }
|
|
# }
|
|
|
|
# # Try to get real metrics from orchestrator
|
|
# if hasattr(self.orchestrator, 'get_performance_metrics'):
|
|
# try:
|
|
# perf_metrics = self.orchestrator.get_performance_metrics()
|
|
# if perf_metrics:
|
|
# # Update RL metrics from orchestrator performance
|
|
# status['rl']['win_rate'] = perf_metrics.get('win_rate', 0.0)
|
|
# status['rl']['episodes'] = perf_metrics.get('total_actions', 0)
|
|
|
|
# # Check if we have sensitivity learning data
|
|
# if hasattr(self.orchestrator, 'sensitivity_learning_queue'):
|
|
# status['rl']['memory_size'] = len(self.orchestrator.sensitivity_learning_queue)
|
|
# if status['rl']['memory_size'] > 0:
|
|
# status['rl']['status'] = 'LEARNING'
|
|
|
|
# # Check if we have extrema training data
|
|
# if hasattr(self.orchestrator, 'extrema_training_queue'):
|
|
# cnn_queue_size = len(self.orchestrator.extrema_training_queue)
|
|
# if cnn_queue_size > 0:
|
|
# status['cnn']['status'] = 'LEARNING'
|
|
# status['cnn']['epochs'] = min(cnn_queue_size // 10, 100) # Simulate epochs
|
|
|
|
# logger.debug("Updated training status from orchestrator metrics")
|
|
# except Exception as e:
|
|
# logger.warning(f"Error getting orchestrator metrics: {e}")
|
|
|
|
# # Try to get extrema stats for CNN training
|
|
# if hasattr(self.orchestrator, 'get_extrema_stats'):
|
|
# try:
|
|
# extrema_stats = self.orchestrator.get_extrema_stats()
|
|
# if extrema_stats:
|
|
# total_extrema = extrema_stats.get('total_extrema_detected', 0)
|
|
# if total_extrema > 0:
|
|
# status['cnn']['status'] = 'LEARNING'
|
|
# status['cnn']['epochs'] = min(total_extrema // 5, 200)
|
|
# # Simulate improving accuracy based on extrema detected
|
|
# status['cnn']['accuracy'] = min(0.85, total_extrema * 0.01)
|
|
# status['cnn']['loss'] = max(0.001, 1.0 - status['cnn']['accuracy'])
|
|
# except Exception as e:
|
|
# logger.warning(f"Error getting extrema stats: {e}")
|
|
|
|
# return status
|
|
|
|
# except Exception as e:
|
|
# logger.error(f"Error getting model training status: {e}")
|
|
# return {
|
|
# 'cnn': {
|
|
# 'status': 'ERROR',
|
|
# 'status_color': 'danger',
|
|
# 'accuracy': 0.0,
|
|
# 'loss': 0.0,
|
|
# 'epochs': 0,
|
|
# 'learning_rate': 0.001
|
|
# },
|
|
# 'rl': {
|
|
# 'status': 'ERROR',
|
|
# 'status_color': 'danger',
|
|
# 'win_rate': 0.0,
|
|
# 'avg_reward': 0.0,
|
|
# 'episodes': 0,
|
|
# 'epsilon': 1.0,
|
|
# 'memory_size': 0
|
|
# }
|
|
# }
|
|
|
|
# def _create_orchestrator_status(self):
|
|
# """Create orchestrator data flow status"""
|
|
# try:
|
|
# # Get orchestrator status
|
|
# if hasattr(self.orchestrator, 'tick_processor') and self.orchestrator.tick_processor:
|
|
# tick_stats = self.orchestrator.tick_processor.get_processing_stats()
|
|
|
|
# return html.Div([
|
|
# html.Div([
|
|
# html.H6("Data Input", className="text-info"),
|
|
# html.P(f"Symbols: {tick_stats.get('symbols', [])}", className="text-white"),
|
|
# html.P(f"Streaming: {'ACTIVE' if tick_stats.get('streaming', False) else 'INACTIVE'}", className="text-white"),
|
|
# html.P(f"Subscribers: {tick_stats.get('subscribers', 0)}", className="text-white")
|
|
# ], className="col-md-6"),
|
|
|
|
# html.Div([
|
|
# html.H6("Processing", className="text-success"),
|
|
# html.P(f"Tick Counts: {tick_stats.get('tick_counts', {})}", className="text-white"),
|
|
# html.P(f"Buffer Sizes: {tick_stats.get('buffer_sizes', {})}", className="text-white"),
|
|
# html.P(f"Neural DPS: {'ACTIVE' if tick_stats.get('streaming', False) else 'INACTIVE'}", className="text-white")
|
|
# ], className="col-md-6")
|
|
# ], className="row")
|
|
# else:
|
|
# return html.Div([
|
|
# html.Div([
|
|
# html.H6("Universal Data Format", className="text-info"),
|
|
# html.P("OK ETH ticks, 1m, 1h, 1d", className="text-white"),
|
|
# html.P("OK BTC reference ticks", className="text-white"),
|
|
# html.P("OK 5-stream format active", className="text-white")
|
|
# ], className="col-md-6"),
|
|
|
|
# html.Div([
|
|
# html.H6("Model Integration", className="text-success"),
|
|
# html.P("OK CNN pipeline ready", className="text-white"),
|
|
# html.P("OK RL pipeline ready", className="text-white"),
|
|
# html.P("OK Neural DPS active", className="text-white")
|
|
# ], className="col-md-6")
|
|
# ], className="row")
|
|
|
|
# except Exception as e:
|
|
# logger.error(f"Error creating orchestrator status: {e}")
|
|
# return html.Div([
|
|
# html.P("Error loading orchestrator status", className="text-danger")
|
|
# ])
|
|
|
|
# def _create_training_events_log(self):
|
|
# """Create enhanced training events log with 500x leverage training cases and negative case focus"""
|
|
# try:
|
|
# events = []
|
|
|
|
# # Get recent losing trades for intensive training
|
|
# losing_trades = [trade for trade in self.trading_session.trade_history if trade.get('pnl', 0) < 0]
|
|
# if losing_trades:
|
|
# recent_losses = losing_trades[-5:] # Last 5 losing trades
|
|
|
|
# for trade in recent_losses:
|
|
# timestamp = trade['timestamp'].strftime('%H:%M:%S')
|
|
# loss_amount = abs(trade['pnl'])
|
|
# loss_pct = (loss_amount / self.trading_session.starting_balance) * 100
|
|
|
|
# # High priority for losing trades - these need intensive training
|
|
# events.append({
|
|
# 'time': timestamp,
|
|
# 'type': 'LOSS',
|
|
# 'event': f"CRITICAL: Loss ${loss_amount:.2f} ({loss_pct:.1f}%) - Intensive RL training active",
|
|
# 'confidence': min(1.0, loss_pct / 5), # Higher confidence for bigger losses
|
|
# 'color': 'text-danger',
|
|
# 'priority': 5 # Highest priority for losses
|
|
# })
|
|
|
|
# # Get recent price movements for 500x leverage training cases
|
|
# if hasattr(self.orchestrator, 'perfect_moves') and self.orchestrator.perfect_moves:
|
|
# perfect_moves = list(self.orchestrator.perfect_moves)[-8:] # Last 8 perfect moves
|
|
|
|
# for move in perfect_moves:
|
|
# timestamp = move.timestamp.strftime('%H:%M:%S')
|
|
# outcome_pct = move.actual_outcome * 100
|
|
|
|
# # 500x leverage amplifies the move
|
|
# leverage_outcome = outcome_pct * 500
|
|
|
|
# events.append({
|
|
# 'time': timestamp,
|
|
# 'type': 'CNN',
|
|
# 'event': f"Perfect {move.optimal_action} {move.symbol} ({outcome_pct:+.2f}% = {leverage_outcome:+.1f}% @ 500x)",
|
|
# 'confidence': move.confidence_should_have_been,
|
|
# 'color': 'text-warning',
|
|
# 'priority': 3 if abs(outcome_pct) > 0.1 else 2 # High priority for >0.1% moves
|
|
# })
|
|
|
|
# # Add training cases for moves >0.1% (optimized for 500x leverage and 0% fees)
|
|
# recent_candles = self.candle_aggregator.get_recent_candles('ETHUSDT', count=60)
|
|
# if len(recent_candles) >= 2:
|
|
# for i in range(1, min(len(recent_candles), 10)): # Check last 10 candles
|
|
# current_candle = recent_candles[i]
|
|
# prev_candle = recent_candles[i-1]
|
|
|
|
# price_change_pct = ((current_candle['close'] - prev_candle['close']) / prev_candle['close']) * 100
|
|
|
|
# if abs(price_change_pct) > 0.1: # >0.1% move
|
|
# leverage_profit = price_change_pct * 500 # 500x leverage
|
|
|
|
# # With 0% fees, any >0.1% move is profitable with 500x leverage
|
|
# action_type = 'BUY' if price_change_pct > 0 else 'SELL'
|
|
|
|
# events.append({
|
|
# 'time': current_candle['timestamp'].strftime('%H:%M:%S'),
|
|
# 'type': 'FAST',
|
|
# 'event': f"Fast {action_type} opportunity: {price_change_pct:+.2f}% = {leverage_profit:+.1f}% profit @ 500x (0% fees)",
|
|
# 'confidence': min(1.0, abs(price_change_pct) / 0.5), # Higher confidence for bigger moves
|
|
# 'color': 'text-success' if leverage_profit > 50 else 'text-info',
|
|
# 'priority': 3 if abs(leverage_profit) > 100 else 2
|
|
# })
|
|
|
|
# # Add negative case training status
|
|
# if hasattr(self.orchestrator, 'negative_case_trainer'):
|
|
# negative_cases = len(getattr(self.orchestrator.negative_case_trainer, 'stored_cases', []))
|
|
# if negative_cases > 0:
|
|
# events.append({
|
|
# 'time': datetime.now().strftime('%H:%M:%S'),
|
|
# 'type': 'NEG',
|
|
# 'event': f'Negative case training: {negative_cases} losing trades stored for intensive retraining',
|
|
# 'confidence': min(1.0, negative_cases / 20),
|
|
# 'color': 'text-warning',
|
|
# 'priority': 4 # High priority for negative case training
|
|
# })
|
|
|
|
# # Add RL training events based on queue activity
|
|
# if hasattr(self.orchestrator, 'rl_evaluation_queue') and self.orchestrator.rl_evaluation_queue:
|
|
# queue_size = len(self.orchestrator.rl_evaluation_queue)
|
|
# current_time = datetime.now()
|
|
|
|
# if queue_size > 0:
|
|
# events.append({
|
|
# 'time': current_time.strftime('%H:%M:%S'),
|
|
# 'type': 'RL',
|
|
# 'event': f'500x leverage RL training active (queue: {queue_size} fast trades)',
|
|
# 'confidence': min(1.0, queue_size / 10),
|
|
# 'color': 'text-success',
|
|
# 'priority': 3 if queue_size > 5 else 1
|
|
# })
|
|
|
|
# # Sort events by priority and time (losses first)
|
|
# events.sort(key=lambda x: (x.get('priority', 1), x['time']), reverse=True)
|
|
|
|
# if not events:
|
|
# return html.Div([
|
|
# html.P("🚀 500x Leverage Training: Waiting for >0.1% moves to optimize fast trading.",
|
|
# className="text-muted text-center"),
|
|
# html.P("💡 With 0% fees, any >0.1% move = >50% profit at 500x leverage.",
|
|
# className="text-muted text-center"),
|
|
# html.P("🔴 PRIORITY: Losing trades trigger intensive RL retraining.",
|
|
# className="text-danger text-center")
|
|
# ])
|
|
|
|
# log_items = []
|
|
# for event in events[:10]: # Show top 10 events
|
|
# icon = "🧠" if event['type'] == 'CNN' else "🤖" if event['type'] == 'RL' else "⚡" if event['type'] == 'FAST' else "🔴" if event['type'] == 'LOSS' else "⚠️"
|
|
# confidence_display = f"{event['confidence']:.2f}" if event['confidence'] <= 1.0 else f"{event['confidence']:.3f}"
|
|
|
|
# log_items.append(
|
|
# html.P(f"{event['time']} {icon} [{event['type']}] {event['event']} (conf: {confidence_display})",
|
|
# className=f"{event['color']} mb-1")
|
|
# )
|
|
|
|
# return html.Div(log_items)
|
|
|
|
# except Exception as e:
|
|
# logger.error(f"Error creating training events log: {e}")
|
|
# return html.Div([
|
|
# html.P("Error loading training events", className="text-danger")
|
|
# ])
|
|
|
|
# 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()
|