feat(fxt): render epoch financial metrics in watch TUI list + detail views

Add Sharpe/Win% columns to training list table, financial summary lines
(Sharpe, Sortino, Win Rate, Max DD, PF, Return, Avg, Trades) to the
detail overview, action distribution (BUY/SELL/HOLD %) to current metrics,
and four new sparklines (Sharpe, Win Rate, Max DD, Total Return) to the
Metrics sub-tab.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
jgrusewski
2026-03-03 16:06:00 +01:00
parent 5842859d78
commit 778adb7ce0

View File

@@ -149,6 +149,8 @@ fn render_training_list(frame: &mut Frame, area: Rect, tab: &TrainingTabState) {
Cell::from("LR"),
Cell::from("Batch/s"),
Cell::from("Grad"),
Cell::from("Sharpe"),
Cell::from("Win%"),
])
.style(
Style::default()
@@ -171,6 +173,8 @@ fn render_training_list(frame: &mut Frame, area: Rect, tab: &TrainingTabState) {
Cell::from(format!("{:.2e}", s.learning_rate)),
Cell::from(format!("{:.1}", s.batches_per_second)),
Cell::from(format!("{:.3}", s.gradient_norm)),
Cell::from(if s.epoch_sharpe != 0.0 { format!("{:.2}", s.epoch_sharpe) } else { "-".to_owned() }),
Cell::from(if s.epoch_win_rate > 0.0 { format!("{:.0}%", s.epoch_win_rate * 100.0) } else { "-".to_owned() }),
])
})
.collect();
@@ -188,6 +192,8 @@ fn render_training_list(frame: &mut Frame, area: Rect, tab: &TrainingTabState) {
Constraint::Length(10),
Constraint::Length(8),
Constraint::Length(8),
Constraint::Length(8),
Constraint::Length(8),
],
)
.header(header)
@@ -351,8 +357,8 @@ fn render_detail_overview(
let chunks = Layout::default()
.direction(Direction::Vertical)
.constraints([
Constraint::Length(8), // key stats
Constraint::Min(0), // loss sparkline
Constraint::Length(12), // key stats (incl. financial metrics)
Constraint::Min(0), // loss sparkline
])
.split(area);
@@ -377,6 +383,17 @@ fn render_detail_overview(
" NaN: {} Grad Explosions: {} Feature Errors: {}",
session.nan_detected, session.gradient_explosions, session.feature_errors,
)),
Line::from(""),
Line::from(format!(
" Sharpe: {:.2} Sortino: {:.2} Win Rate: {:.1}% Max DD: {:.1}%",
session.epoch_sharpe, session.epoch_sortino,
session.epoch_win_rate * 100.0, session.epoch_max_drawdown * 100.0,
)),
Line::from(format!(
" PF: {:.2} Return: {:+.2}% Avg: {:+.4} Trades: {}",
session.epoch_profit_factor, session.epoch_total_return * 100.0,
session.epoch_avg_return, session.epoch_total_trades,
)),
])
.block(
Block::default()
@@ -454,7 +471,7 @@ fn render_detail_metrics(
let chunks = Layout::default()
.direction(Direction::Vertical)
.constraints([
Constraint::Length(5), // current values
Constraint::Length(7), // current values (incl. action distribution)
Constraint::Min(0), // sparklines
])
.split(area);
@@ -468,6 +485,12 @@ fn render_detail_metrics(
" Precision: {:.4} Recall: {:.4}",
session.eval_precision, session.eval_recall,
)),
Line::from(format!(
" Action: BUY {:.0}% SELL {:.0}% HOLD {:.0}%",
session.action_buy_pct * 100.0,
session.action_sell_pct * 100.0,
session.action_hold_pct * 100.0,
)),
])
.block(
Block::default()
@@ -479,10 +502,14 @@ fn render_detail_metrics(
let spark_area = Layout::default()
.direction(Direction::Vertical)
.constraints([
Constraint::Percentage(25),
Constraint::Percentage(25),
Constraint::Percentage(25),
Constraint::Percentage(25),
Constraint::Ratio(1, 8),
Constraint::Ratio(1, 8),
Constraint::Ratio(1, 8),
Constraint::Ratio(1, 8),
Constraint::Ratio(1, 8),
Constraint::Ratio(1, 8),
Constraint::Ratio(1, 8),
Constraint::Ratio(1, 8),
])
.split(chunks[1]);
@@ -490,6 +517,10 @@ fn render_detail_metrics(
render_sparkline_row(frame, spark_area[1], "Precision", &history.precision, 1.0, Color::Cyan);
render_sparkline_row(frame, spark_area[2], "Recall", &history.recall, 1.0, Color::Magenta);
render_sparkline_row(frame, spark_area[3], "F1", &history.f1, 1.0, Color::Yellow);
render_sparkline_row(frame, spark_area[4], "Sharpe", &history.sharpe, 20.0, Color::Cyan);
render_sparkline_row(frame, spark_area[5], "Win Rate", &history.win_rate, 1.0, Color::Green);
render_sparkline_row(frame, spark_area[6], "Max DD", &history.max_drawdown, 1.0, Color::Red);
render_sparkline_row(frame, spark_area[7], "Total Return", &history.total_return, 1.0, Color::Magenta);
}
fn render_detail_hyperopt(