Download monitoring/dashboard.py from raghava4u/Trading-Bot-M20: direct link, hf CLI and curl.
- Browser
- Download file 11.6 kB
-
https://huggingface.co/raghava4u/Trading-Bot-M20/resolve/main/monitoring/dashboard.py
- Command line
-
hf download hf://raghava4u/Trading-Bot-M20/monitoring/dashboard.py
-
curl -L -o dashboard.py https://huggingface.co/raghava4u/Trading-Bot-M20/resolve/main/monitoring/dashboard.py
11.6 kB
| """ | |
| monitoring/dashboard.py β Rich live dashboard. | |
| Shows mode, safe mode, PDT, positions, P&L, predictions, sentiment status. | |
| """ | |
| from __future__ import annotations | |
| import datetime | |
| import logging | |
| from typing import Callable | |
| from zoneinfo import ZoneInfo | |
| _ET = ZoneInfo("America/New_York") | |
| from rich.console import Console | |
| from rich.layout import Layout | |
| from rich.live import Live | |
| from rich.panel import Panel | |
| from rich.progress import BarColumn, Progress, TextColumn | |
| from rich.table import Table | |
| from rich.text import Text | |
| import config | |
| from contracts import PriceTarget, FinalScore | |
| from execution import broker | |
| from execution.portfolio import Portfolio | |
| from execution import risk as risk_module | |
| from execution.pdt_tracker import count_day_trades | |
| from signals.sentiment import is_warming_up, get_last_refresh | |
| logger = logging.getLogger("trading_system.dashboard") | |
| console = Console() | |
| class TradingDashboard: | |
| """Rich live dashboard for the trading system.""" | |
| def __init__(self, portfolio: Portfolio): | |
| self.portfolio = portfolio | |
| self.predictions: dict[str, PriceTarget] = {} | |
| self.signals: dict[str, FinalScore] = {} | |
| self._live: Live | None = None | |
| self._daily_target = config.TARGET_DAILY_PROFIT_USD | |
| def update_prediction(self, symbol: str, target: PriceTarget): | |
| self.predictions[symbol] = target | |
| def update_signal(self, symbol: str, signal: FinalScore): | |
| self.signals[symbol] = signal | |
| def _build_mode_panel(self) -> Panel: | |
| """Build the trading mode indicator.""" | |
| if config.DRY_RUN: | |
| mode_text = Text(" DRY RUN ", style="bold white on red") | |
| elif config.TRADING_MODE == "paper": | |
| mode_text = Text(" PAPER ", style="bold black on yellow") | |
| elif config.TRADING_MODE == "live": | |
| mode_text = Text(" LIVE ", style="bold white on green") | |
| else: | |
| mode_text = Text(f" {config.TRADING_MODE.upper()} ", style="bold") | |
| # Safe mode | |
| if broker.safe_mode_active: | |
| mode_text.append(" ") | |
| mode_text.append(Text(" SAFE MODE ", style="bold white on red blink")) | |
| return Panel(mode_text, title="Mode", border_style="bright_blue") | |
| def _build_pnl_panel(self) -> Panel: | |
| """Build daily P&L display with progress bar toward target.""" | |
| realized = self.portfolio.daily_realized_pnl | |
| unrealized = self.portfolio.total_unrealized_pnl | |
| total = realized + unrealized | |
| target = self._daily_target | |
| # Color based on P&L | |
| if total >= target: | |
| pnl_style = "bold green" | |
| status = "β TARGET HIT" | |
| elif total > 0: | |
| pnl_style = "green" | |
| status = "π" | |
| elif total > -config.MAX_DAILY_LOSS_USD / 2: | |
| pnl_style = "yellow" | |
| status = "β οΈ" | |
| else: | |
| pnl_style = "bold red" | |
| status = "π" | |
| text = Text() | |
| text.append(f"Realized: ${realized:>10.2f}\n", style="dim") | |
| text.append(f"Unrealized: ${unrealized:>10.2f}\n", style="dim") | |
| text.append(f"Total: ", style="dim") | |
| text.append(f"${total:>10.2f}", style=pnl_style) | |
| text.append(f" {status}\n") | |
| text.append(f"Target: ${target:>10.2f}\n", style="dim") | |
| pct = min(max(total / target * 100, 0), 100) if target > 0 else 0 | |
| text.append(f"Progress: {pct:>9.1f}%\n", style="dim cyan") | |
| # Progress bar | |
| bar_width = 30 | |
| filled = int(bar_width * pct / 100) | |
| bar = "β" * filled + "β" * (bar_width - filled) | |
| text.append(f"[{bar}]", style="cyan") | |
| return Panel(text, title="Daily P&L", border_style="green" if total >= 0 else "red") | |
| def _build_positions_table(self) -> Panel: | |
| """Build positions table with P&L and order info.""" | |
| table = Table(show_header=True, header_style="bold cyan") | |
| table.add_column("Symbol", width=8) | |
| table.add_column("Side", width=6) | |
| table.add_column("Qty", width=8, justify="right") | |
| table.add_column("Entry", width=10, justify="right") | |
| table.add_column("Current", width=10, justify="right") | |
| table.add_column("P&L", width=10, justify="right") | |
| table.add_column("Stop", width=10, justify="right") | |
| table.add_column("TP", width=10, justify="right") | |
| for sym, pos in self.portfolio.positions.items(): | |
| pnl = pos.get("unrealized_pl", 0) | |
| pnl_style = "green" if pnl >= 0 else "red" | |
| pred = self.predictions.get(sym) | |
| table.add_row( | |
| sym, | |
| pos.get("side", "β")[:4], | |
| f"{pos.get('qty', 0):.2f}", | |
| f"${pos.get('entry_price', 0):.2f}", | |
| f"${pos.get('current_price', 0):.2f}", | |
| Text(f"${pnl:.2f}", style=pnl_style), | |
| f"${pred.stop_loss_price:.2f}" if pred and pred.stop_loss_price else "β", | |
| f"${pred.take_profit_price:.2f}" if pred and pred.take_profit_price else "β", | |
| ) | |
| if not self.portfolio.positions: | |
| table.add_row("β", "β", "β", "β", "β", "β", "β", "β") | |
| return Panel(table, title=f"Positions ({self.portfolio.open_count})", border_style="cyan") | |
| def _build_predictions_table(self) -> Panel: | |
| """Build price prediction table showing expected profits.""" | |
| table = Table(show_header=True, header_style="bold magenta") | |
| table.add_column("Symbol", width=8) | |
| table.add_column("Price", width=10, justify="right") | |
| table.add_column("Direction", width=10) | |
| table.add_column("Signal", width=8) | |
| table.add_column("Entry", width=10, justify="right") | |
| table.add_column("Stop", width=10, justify="right") | |
| table.add_column("Target", width=10, justify="right") | |
| table.add_column("Est P&L", width=10, justify="right") | |
| table.add_column("Conf", width=6, justify="right") | |
| for sym in config.UNIVERSE: | |
| pred = self.predictions.get(sym) | |
| sig = self.signals.get(sym) | |
| if pred: | |
| dir_style = "green" if pred.predicted_direction == "UP" else ( | |
| "red" if pred.predicted_direction == "DOWN" else "dim" | |
| ) | |
| decision = sig.decision if sig else "β" | |
| dec_style = "bold green" if decision == "BUY" else ( | |
| "bold red" if decision == "SELL" else "dim" | |
| ) | |
| table.add_row( | |
| sym, | |
| f"${pred.current_price:.2f}", | |
| Text(pred.predicted_direction, style=dir_style), | |
| Text(decision, style=dec_style), | |
| f"${pred.entry_price:.2f}" if pred.entry_price else "β", | |
| f"${pred.stop_loss_price:.2f}" if pred.stop_loss_price else "β", | |
| f"${pred.take_profit_price:.2f}" if pred.take_profit_price else "β", | |
| Text( | |
| f"${pred.expected_profit_usd:.2f}", | |
| style="green" if pred.expected_profit_usd > 0 else "dim", | |
| ), | |
| f"{pred.confidence:.0%}", | |
| ) | |
| else: | |
| table.add_row(sym, "β", "β", "β", "β", "β", "β", "β", "β") | |
| return Panel(table, title="π Predictions & Signals", border_style="magenta") | |
| def _build_status_panel(self) -> Panel: | |
| """Build system status panel.""" | |
| text = Text() | |
| # PDT status | |
| if not config.ACCOUNT_BALANCE_ABOVE_25K: | |
| pdt_count = count_day_trades() | |
| pdt_max = config.PDT_MAX_DAY_TRADES | |
| if pdt_count >= pdt_max: | |
| pdt_style = "bold red" | |
| elif pdt_count >= pdt_max - 1: | |
| pdt_style = "yellow" | |
| else: | |
| pdt_style = "green" | |
| text.append(f"PDT: {pdt_count}/{pdt_max}", style=pdt_style) | |
| else: | |
| text.append("PDT: N/A (>$25k)", style="dim") | |
| text.append("\n") | |
| # Sentiment status | |
| if is_warming_up(): | |
| text.append("Sentiment: warming up...", style="yellow") | |
| else: | |
| last = get_last_refresh() | |
| if last: | |
| age = (datetime.datetime.now(datetime.timezone.utc) - last).total_seconds() / 60 | |
| if age > 35: | |
| text.append(f"Sentiment: stale ({age:.0f}m ago)", style="yellow") | |
| else: | |
| text.append(f"Sentiment: fresh ({age:.0f}m ago)", style="green") | |
| else: | |
| text.append("Sentiment: no data", style="red") | |
| text.append("\n") | |
| # Circuit breakers | |
| circuits = [] | |
| if risk_module.profit_locked: | |
| circuits.append("profit_locked") | |
| if risk_module.half_size_mode: | |
| circuits.append("half_size") | |
| if risk_module.consecutive_losses >= 3: | |
| circuits.append(f"losing_streak({risk_module.consecutive_losses})") | |
| if broker.safe_mode_active: | |
| circuits.append("SAFE_MODE") | |
| if circuits: | |
| text.append("Circuits: ", style="dim") | |
| text.append(", ".join(circuits), style="bold red") | |
| else: | |
| text.append("Circuits: none active", style="green") | |
| text.append("\n") | |
| # Overnight warning | |
| if self.portfolio.has_overnight_risk(): | |
| text.append("β οΈ OVERNIGHT RISK: positions open past 15:30 ET", style="bold yellow") | |
| text.append("\n") | |
| # Time | |
| now_et = datetime.datetime.now(_ET) | |
| text.append(f"Time: {now_et.strftime('%H:%M:%S %Z')}", style="dim") | |
| return Panel(text, title="System Status", border_style="yellow") | |
| def build_layout(self) -> Layout: | |
| """Build the complete dashboard layout.""" | |
| layout = Layout() | |
| layout.split_column( | |
| Layout(name="top", size=3), | |
| Layout(name="middle"), | |
| Layout(name="bottom"), | |
| ) | |
| layout["top"].update(self._build_mode_panel()) | |
| layout["middle"].split_row( | |
| Layout(name="left", ratio=1), | |
| Layout(name="right", ratio=2), | |
| ) | |
| layout["middle"]["left"].split_column( | |
| Layout(self._build_pnl_panel(), name="pnl"), | |
| Layout(self._build_status_panel(), name="status"), | |
| ) | |
| layout["middle"]["right"].update(self._build_positions_table()) | |
| layout["bottom"].update(self._build_predictions_table()) | |
| return layout | |
| def render_once(self): | |
| """Render dashboard to console once.""" | |
| console.clear() | |
| console.print(self.build_layout()) | |
| def start_live(self) -> Live: | |
| """Start live auto-refreshing dashboard.""" | |
| self._live = Live( | |
| self.build_layout(), | |
| console=console, | |
| refresh_per_second=1, | |
| screen=True, | |
| ) | |
| self._live.start() | |
| return self._live | |
| def update_live(self): | |
| """Update the live dashboard.""" | |
| if self._live: | |
| self._live.update(self.build_layout()) | |
| def stop_live(self): | |
| """Stop the live dashboard.""" | |
| if self._live: | |
| self._live.stop() | |
| self._live = None | |