wip
This commit is contained in:
112
web/dashboard.py
112
web/dashboard.py
@ -358,16 +358,26 @@ class TradingDashboard:
|
||||
pnl_text = f"${total_session_pnl:.2f}"
|
||||
pnl_class = "text-success mb-0 small" if total_session_pnl >= 0 else "text-danger mb-0 small"
|
||||
|
||||
# Position info with real-time unrealized PnL
|
||||
# Position info with real-time unrealized PnL and color coding
|
||||
if self.current_position:
|
||||
pos_side = self.current_position['side']
|
||||
pos_size = self.current_position['size']
|
||||
pos_price = self.current_position['price']
|
||||
unrealized_pnl = self._calculate_unrealized_pnl(current_price) if current_price else 0.0
|
||||
pnl_color = "text-success" if unrealized_pnl >= 0 else "text-danger"
|
||||
position_text = f"{pos_side} {pos_size} @ ${pos_price:.2f} | P&L: ${unrealized_pnl:.2f}"
|
||||
|
||||
# Color code the position based on side and P&L (no Unicode for Windows compatibility)
|
||||
if pos_side == 'LONG':
|
||||
side_icon = "[LONG]" # ASCII indicator for long
|
||||
side_color = "success" if unrealized_pnl >= 0 else "warning"
|
||||
else: # SHORT
|
||||
side_icon = "[SHORT]" # ASCII indicator for short
|
||||
side_color = "danger" if unrealized_pnl >= 0 else "info"
|
||||
|
||||
position_text = f"{side_icon} {pos_size} @ ${pos_price:.2f} | P&L: ${unrealized_pnl:.2f}"
|
||||
position_class = f"text-{side_color} fw-bold"
|
||||
else:
|
||||
position_text = "None"
|
||||
position_text = "No Position"
|
||||
position_class = "text-muted"
|
||||
|
||||
# Trade count
|
||||
trade_count_text = f"{len(self.session_trades)}"
|
||||
@ -906,6 +916,26 @@ class TradingDashboard:
|
||||
|
||||
confidence_pct = f"{confidence*100:.1f}%" if confidence else "N/A"
|
||||
|
||||
# Check if this is a trade with PnL information
|
||||
pnl_info = ""
|
||||
if isinstance(decision, dict) and 'pnl' in decision:
|
||||
pnl = decision['pnl']
|
||||
pnl_color = "text-success" if pnl >= 0 else "text-danger"
|
||||
pnl_info = html.Span([
|
||||
" • PnL: ",
|
||||
html.Strong(f"${pnl:.2f}", className=pnl_color)
|
||||
])
|
||||
|
||||
# Check for position action to show entry/exit info
|
||||
position_info = ""
|
||||
if isinstance(decision, dict) and 'position_action' in decision:
|
||||
pos_action = decision['position_action']
|
||||
if 'CLOSE' in pos_action and 'entry_price' in decision:
|
||||
entry_price = decision['entry_price']
|
||||
position_info = html.Small([
|
||||
f" (Entry: ${entry_price:.2f})"
|
||||
], className="text-muted")
|
||||
|
||||
decisions_html.append(
|
||||
html.Div([
|
||||
html.Div([
|
||||
@ -913,11 +943,13 @@ class TradingDashboard:
|
||||
html.Strong(action, className=action_class),
|
||||
html.Span(f" {symbol} ", className="text-muted"),
|
||||
html.Small(f"@${price:.2f}", className="text-muted"),
|
||||
position_info,
|
||||
html.Span(className=f"{badge_class} ms-2", children=badge_text, style={"fontSize": "0.7em"})
|
||||
], className="d-flex align-items-center"),
|
||||
html.Small([
|
||||
html.Span(f"Confidence: {confidence_pct} • ", className="text-info"),
|
||||
html.Span(time_str, className="text-muted")
|
||||
html.Span(time_str, className="text-muted"),
|
||||
pnl_info
|
||||
])
|
||||
], className="border-bottom pb-2 mb-2")
|
||||
)
|
||||
@ -1139,7 +1171,7 @@ class TradingDashboard:
|
||||
return None
|
||||
|
||||
def _process_trading_decision(self, decision: Dict) -> None:
|
||||
"""Process a trading decision and update PnL tracking"""
|
||||
"""Process a trading decision and update PnL tracking with position flipping"""
|
||||
try:
|
||||
if not decision:
|
||||
return
|
||||
@ -1168,6 +1200,49 @@ class TradingDashboard:
|
||||
|
||||
logger.info(f"[TRADE] OPENED LONG: {decision['size']} @ ${decision['price']:.2f}")
|
||||
|
||||
elif self.current_position['side'] == 'SHORT':
|
||||
# Close short position and flip to long
|
||||
entry_price = self.current_position['price']
|
||||
exit_price = decision['price']
|
||||
size = self.current_position['size']
|
||||
|
||||
# Calculate PnL for closing short
|
||||
gross_pnl = (entry_price - exit_price) * size # Short PnL calculation
|
||||
fee = exit_price * size * fee_rate
|
||||
net_pnl = gross_pnl - fee - self.current_position['fees']
|
||||
|
||||
self.total_realized_pnl += net_pnl
|
||||
self.total_fees += fee
|
||||
|
||||
# Record the close trade
|
||||
close_record = decision.copy()
|
||||
close_record['position_action'] = 'CLOSE_SHORT'
|
||||
close_record['entry_price'] = entry_price
|
||||
close_record['pnl'] = net_pnl
|
||||
close_record['fees'] = fee
|
||||
self.session_trades.append(close_record)
|
||||
|
||||
logger.info(f"[TRADE] CLOSED SHORT: {size} @ ${exit_price:.2f} | PnL: ${net_pnl:.2f} | FLIPPING TO LONG")
|
||||
|
||||
# Open new long position
|
||||
new_fee = decision['price'] * decision['size'] * fee_rate
|
||||
self.current_position = {
|
||||
'side': 'LONG',
|
||||
'price': decision['price'],
|
||||
'size': decision['size'],
|
||||
'timestamp': current_time,
|
||||
'fees': new_fee
|
||||
}
|
||||
self.total_fees += new_fee
|
||||
|
||||
# Record the new long position
|
||||
open_record = decision.copy()
|
||||
open_record['position_action'] = 'OPEN_LONG'
|
||||
open_record['fees'] = new_fee
|
||||
self.session_trades.append(open_record)
|
||||
|
||||
logger.info(f"[TRADE] OPENED LONG: {decision['size']} @ ${decision['price']:.2f}")
|
||||
|
||||
elif decision['action'] == 'SELL':
|
||||
if self.current_position and self.current_position['side'] == 'LONG':
|
||||
# Close long position
|
||||
@ -1175,28 +1250,29 @@ class TradingDashboard:
|
||||
exit_price = decision['price']
|
||||
size = self.current_position['size']
|
||||
|
||||
# Calculate PnL
|
||||
gross_pnl = (exit_price - entry_price) * size
|
||||
# Calculate PnL for closing long
|
||||
gross_pnl = (exit_price - entry_price) * size # Long PnL calculation
|
||||
fee = exit_price * size * fee_rate
|
||||
net_pnl = gross_pnl - fee - self.current_position['fees']
|
||||
|
||||
self.total_realized_pnl += net_pnl
|
||||
self.total_fees += fee
|
||||
|
||||
trade_record = decision.copy()
|
||||
trade_record['position_action'] = 'CLOSE_LONG'
|
||||
trade_record['entry_price'] = entry_price
|
||||
trade_record['pnl'] = net_pnl
|
||||
trade_record['fees'] = fee
|
||||
self.session_trades.append(trade_record)
|
||||
# Record the close trade
|
||||
close_record = decision.copy()
|
||||
close_record['position_action'] = 'CLOSE_LONG'
|
||||
close_record['entry_price'] = entry_price
|
||||
close_record['pnl'] = net_pnl
|
||||
close_record['fees'] = fee
|
||||
self.session_trades.append(close_record)
|
||||
|
||||
logger.info(f"[TRADE] CLOSED LONG: {size} @ ${exit_price:.2f} | PnL: ${net_pnl:.2f}")
|
||||
|
||||
# Clear position
|
||||
# Clear position (or could flip to short here if desired)
|
||||
self.current_position = None
|
||||
|
||||
elif self.current_position is None:
|
||||
# Open short position (for demo)
|
||||
# Open short position
|
||||
fee = decision['price'] * decision['size'] * fee_rate
|
||||
self.current_position = {
|
||||
'side': 'SHORT',
|
||||
@ -1213,6 +1289,10 @@ class TradingDashboard:
|
||||
self.session_trades.append(trade_record)
|
||||
|
||||
logger.info(f"[TRADE] OPENED SHORT: {decision['size']} @ ${decision['price']:.2f}")
|
||||
|
||||
elif self.current_position['side'] == 'LONG':
|
||||
# This case is already handled above, but adding for completeness
|
||||
pass
|
||||
|
||||
# Add to recent decisions
|
||||
self.recent_decisions.append(decision)
|
||||
|
@ -757,4 +757,203 @@ class EnhancedScalpingDashboard:
|
||||
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
|
||||
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()
|
||||
|
Reference in New Issue
Block a user