**Progress: 1,178 → 57 test errors (95% reduction)** ## Status Summary - ✅ Production code: Compiles cleanly (0 errors) - ⚠️ Test code: 57 errors remain (massive improvement) - ⚙️ All services build successfully - 📊 Warning count: 253 (target: <20) - AGENTS WILL FIX ## Remaining Test Errors (57 total) ### Primary Issues: 1. 23× E0308 mismatched types 2. 17× E0433 undeclared Decimal 3. 15× E0433 compliance module not found 4. 6× E0624 private method access 5. Various import and type issues ## Next Phase: Wave 33-2 Launch 10+ parallel agents to: - Fix remaining 57 test compilation errors - Reduce 253 warnings to <20 - Achieve 95% test coverage - Ensure all tests pass 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
323 lines
11 KiB
Rust
323 lines
11 KiB
Rust
//! Dashboard Framework for TLI Terminal Interface
|
|
//!
|
|
//! This module provides a comprehensive dashboard system for the Foxhunt HFT trading system.
|
|
//! It implements a multi-dashboard architecture with real-time data streaming and interactive
|
|
//! controls using Ratatui for terminal-based visualization.
|
|
//!
|
|
//! ## Architecture
|
|
//! - **`DashboardManager`**: Central coordinator for all dashboards
|
|
//! - **Dashboard Trait**: Common interface for all dashboard implementations
|
|
//! - **Real-time Updates**: Event-driven data streaming from gRPC services
|
|
//! - **Navigation**: Keyboard shortcuts for dashboard switching
|
|
//! - **Layout Management**: Consistent UI layout across all dashboards
|
|
|
|
use anyhow::Result;
|
|
use crossterm::event::KeyEvent;
|
|
use ratatui::prelude::*;
|
|
use std::collections::HashMap;
|
|
use tokio::sync::mpsc;
|
|
|
|
// Import from events module
|
|
use crate::dashboard::events::DashboardEvent;
|
|
use crate::dashboard::layout::LayoutManager;
|
|
|
|
pub mod backtesting;
|
|
pub mod config;
|
|
pub mod events;
|
|
pub mod layout;
|
|
pub mod ml;
|
|
pub mod performance;
|
|
pub mod risk;
|
|
pub mod trading;
|
|
pub mod vault_status;
|
|
|
|
// Import dashboard implementations after module declarations
|
|
use backtesting::BacktestingDashboard;
|
|
use config::create_config_dashboard;
|
|
use ml::MLDashboard;
|
|
use performance::PerformanceDashboard;
|
|
use risk::RiskDashboard;
|
|
use trading::TradingDashboard;
|
|
use vault_status::VaultStatusWidget;
|
|
|
|
// NO RE-EXPORTS: Import directly from submodules
|
|
// Use tli::dashboard::events::DashboardEvent instead
|
|
// Use tli::dashboard::layout::LayoutManager instead
|
|
|
|
/// Main dashboard manager that coordinates all dashboards
|
|
pub struct DashboardManager {
|
|
pub active_dashboard: DashboardType,
|
|
pub dashboards: HashMap<DashboardType, Box<dyn Dashboard>>,
|
|
pub layout_manager: LayoutManager,
|
|
pub event_receiver: mpsc::Receiver<DashboardEvent>,
|
|
pub _event_sender: mpsc::Sender<DashboardEvent>,
|
|
// Vault service removed - TLI is pure client, uses shared config crate
|
|
}
|
|
|
|
/// Available dashboard types
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
|
pub enum DashboardType {
|
|
Trading, // Live positions, orders, executions, market data
|
|
Risk, // VaR, drawdown, position limits, safety controls
|
|
ML, // Model predictions, signal strength, confidence
|
|
Performance, // PnL, Sharpe ratios, strategy performance
|
|
Config, // System configuration management
|
|
Backtesting, // Strategy testing, historical analysis, results
|
|
Vault, // Vault status, credentials, service discovery
|
|
}
|
|
|
|
impl DashboardType {
|
|
pub fn all() -> Vec<DashboardType> {
|
|
vec![
|
|
DashboardType::Trading,
|
|
DashboardType::Risk,
|
|
DashboardType::ML,
|
|
DashboardType::Performance,
|
|
DashboardType::Config,
|
|
DashboardType::Backtesting,
|
|
DashboardType::Vault,
|
|
]
|
|
}
|
|
|
|
pub const fn shortcut_key(&self) -> char {
|
|
match self {
|
|
DashboardType::Trading => 't',
|
|
DashboardType::Risk => 'r',
|
|
DashboardType::ML => 'm',
|
|
DashboardType::Performance => 'p',
|
|
DashboardType::Config => 'c',
|
|
DashboardType::Backtesting => 'b',
|
|
DashboardType::Vault => 'v',
|
|
}
|
|
}
|
|
|
|
pub const fn title(&self) -> &'static str {
|
|
match self {
|
|
DashboardType::Trading => "Trading",
|
|
DashboardType::Risk => "Risk",
|
|
DashboardType::ML => "ML",
|
|
DashboardType::Performance => "Performance",
|
|
DashboardType::Config => "Configuration",
|
|
DashboardType::Backtesting => "Backtesting",
|
|
DashboardType::Vault => "Vault Status",
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Common interface for all dashboard implementations
|
|
pub trait Dashboard: Send + Sync {
|
|
/// Render the dashboard to the given frame area
|
|
fn render(&mut self, frame: &mut Frame, area: Rect) -> Result<()>;
|
|
|
|
/// Handle keyboard input and return optional dashboard events
|
|
fn handle_input(&mut self, key: KeyEvent) -> Result<Option<DashboardEvent>>;
|
|
|
|
/// Update dashboard with new data/events
|
|
fn update(&mut self, event: DashboardEvent) -> Result<()>;
|
|
|
|
/// Get dashboard title for display
|
|
fn title(&self) -> &str;
|
|
|
|
/// Get keyboard shortcut for this dashboard
|
|
fn shortcut_key(&self) -> char;
|
|
|
|
/// Check if dashboard needs redraw
|
|
fn needs_redraw(&self) -> bool;
|
|
|
|
/// Mark dashboard as drawn
|
|
fn mark_drawn(&mut self);
|
|
}
|
|
|
|
impl DashboardManager {
|
|
pub fn new() -> (Self, mpsc::Sender<DashboardEvent>) {
|
|
let (_event_sender, event_receiver) = mpsc::channel(1000);
|
|
|
|
let mut dashboards: HashMap<DashboardType, Box<dyn Dashboard>> = HashMap::new();
|
|
|
|
// Initialize all dashboards
|
|
dashboards.insert(
|
|
DashboardType::Trading,
|
|
Box::new(TradingDashboard::new(_event_sender.clone())),
|
|
);
|
|
dashboards.insert(
|
|
DashboardType::Risk,
|
|
Box::new(RiskDashboard::new(_event_sender.clone())),
|
|
);
|
|
dashboards.insert(
|
|
DashboardType::ML,
|
|
Box::new(MLDashboard::new(_event_sender.clone())),
|
|
);
|
|
dashboards.insert(
|
|
DashboardType::Performance,
|
|
Box::new(PerformanceDashboard::new(_event_sender.clone())),
|
|
);
|
|
dashboards.insert(
|
|
DashboardType::Config,
|
|
create_config_dashboard(_event_sender.clone()),
|
|
);
|
|
dashboards.insert(
|
|
DashboardType::Backtesting,
|
|
Box::new(BacktestingDashboard::new(_event_sender.clone())),
|
|
);
|
|
dashboards.insert(
|
|
DashboardType::Vault,
|
|
Box::new(VaultStatusWidget::new(_event_sender.clone())),
|
|
);
|
|
|
|
let manager = Self {
|
|
active_dashboard: DashboardType::Trading,
|
|
dashboards,
|
|
layout_manager: LayoutManager::new(),
|
|
event_receiver,
|
|
_event_sender: _event_sender.clone(),
|
|
};
|
|
|
|
(manager, _event_sender)
|
|
}
|
|
|
|
pub fn render(&mut self, frame: &mut Frame) -> Result<()> {
|
|
let area = frame.area();
|
|
|
|
// Create main layout
|
|
let (header_area, content_area, sidebar_area, footer_area) =
|
|
self.layout_manager.create_layout(area);
|
|
|
|
// Render header with navigation tabs
|
|
self.render_header(frame, header_area)?;
|
|
|
|
// Render active dashboard
|
|
if let Some(dashboard) = self.dashboards.get_mut(&self.active_dashboard) {
|
|
dashboard.render(frame, content_area)?;
|
|
}
|
|
|
|
// Render sidebar with quick stats
|
|
self.render_sidebar(frame, sidebar_area)?;
|
|
|
|
// Render footer with help and status
|
|
self.render_footer(frame, footer_area)?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
pub fn handle_input(&mut self, key: KeyEvent) -> Result<Option<DashboardEvent>> {
|
|
// Check for dashboard switching shortcuts first
|
|
for dashboard_type in DashboardType::all() {
|
|
if key.code == crossterm::event::KeyCode::Char(dashboard_type.shortcut_key()) {
|
|
self.active_dashboard = dashboard_type;
|
|
return Ok(Some(DashboardEvent::SwitchDashboard(dashboard_type)));
|
|
}
|
|
}
|
|
|
|
// Handle ESC for exit
|
|
if key.code == crossterm::event::KeyCode::Esc {
|
|
return Ok(Some(DashboardEvent::Exit));
|
|
}
|
|
|
|
// Pass input to active dashboard
|
|
if let Some(dashboard) = self.dashboards.get_mut(&self.active_dashboard) {
|
|
dashboard.handle_input(key)
|
|
} else {
|
|
Ok(None)
|
|
}
|
|
}
|
|
|
|
pub async fn handle_event(&mut self, event: DashboardEvent) -> Result<bool> {
|
|
match event {
|
|
DashboardEvent::SwitchDashboard(dashboard_type) => {
|
|
self.active_dashboard = dashboard_type;
|
|
Ok(false)
|
|
},
|
|
DashboardEvent::Exit => {
|
|
Ok(true) // Signal to exit
|
|
},
|
|
_ => {
|
|
// Forward event to all dashboards that might be interested
|
|
for dashboard in self.dashboards.values_mut() {
|
|
let _ = dashboard.update(event.clone());
|
|
}
|
|
Ok(false)
|
|
},
|
|
}
|
|
}
|
|
|
|
// Vault service functionality removed - TLI is pure client, uses shared config crate
|
|
|
|
/// All vault-related methods removed - TLI uses shared config crate instead
|
|
// Vault service functionality completely removed from TLI
|
|
// TLI is a pure client - no vault service management
|
|
|
|
fn render_header(&self, frame: &mut Frame, area: Rect) -> Result<()> {
|
|
let titles: Vec<String> = DashboardType::all()
|
|
.iter()
|
|
.map(|dt| {
|
|
let _prefix = if *dt == self.active_dashboard {
|
|
"\u{25cf}"
|
|
} else {
|
|
"\u{25cb}"
|
|
};
|
|
format!("[{}]{}", dt.shortcut_key().to_uppercase(), dt.title())
|
|
})
|
|
.collect();
|
|
|
|
let tabs = ratatui::widgets::Tabs::new(titles)
|
|
.block(
|
|
ratatui::widgets::Block::default()
|
|
.borders(ratatui::widgets::Borders::ALL)
|
|
.title("Foxhunt HFT Trading System - TLI Terminal"),
|
|
)
|
|
.style(Style::default().fg(Color::White))
|
|
.highlight_style(
|
|
Style::default()
|
|
.fg(Color::Yellow)
|
|
.add_modifier(Modifier::BOLD),
|
|
)
|
|
.select(self.active_dashboard as usize);
|
|
|
|
frame.render_widget(tabs, area);
|
|
Ok(())
|
|
}
|
|
|
|
fn render_sidebar(&self, frame: &mut Frame, area: Rect) -> Result<()> {
|
|
let block = ratatui::widgets::Block::default()
|
|
.borders(ratatui::widgets::Borders::ALL)
|
|
.title("Quick Stats");
|
|
|
|
// Get actual Vault status (placeholder - TLI uses shared config crate)
|
|
let vault_status = "\u{25cb}"; // Empty circle for not available - use config crate integration
|
|
|
|
let content = ratatui::widgets::Paragraph::new(
|
|
format!(
|
|
"Connection: \u{25cf}\u{25cf}\u{25cf}\nVault: {}\nLatency: 12ms\nOrders: 15\nPositions: 5\nPnL: +$2,500",
|
|
vault_status
|
|
),
|
|
)
|
|
.block(block)
|
|
.wrap(ratatui::widgets::Wrap { trim: true });
|
|
|
|
frame.render_widget(content, area);
|
|
Ok(())
|
|
}
|
|
|
|
fn render_footer(&self, frame: &mut Frame, area: Rect) -> Result<()> {
|
|
let help_text = format!(
|
|
"[{}] Dashboards | [ESC] Exit | Status: Connected",
|
|
DashboardType::all()
|
|
.iter()
|
|
.map(|dt| format!(
|
|
"[{}]{}",
|
|
dt.shortcut_key().to_uppercase(),
|
|
dt.title().chars().next().unwrap()
|
|
))
|
|
.collect::<Vec<_>>()
|
|
.join(" ")
|
|
);
|
|
|
|
let footer = ratatui::widgets::Paragraph::new(help_text)
|
|
.block(ratatui::widgets::Block::default().borders(ratatui::widgets::Borders::ALL))
|
|
.style(Style::default().fg(Color::Gray));
|
|
|
|
frame.render_widget(footer, area);
|
|
Ok(())
|
|
}
|
|
}
|