Skip to content

Commit a5ad541

Browse files
committed
feat: enhance dashboard with metrics, color coding, and timeline fixes
- Add Consistency/Stability KPI cards and ranking table columns - Add absolute-threshold color coding on ranking metrics with star for best values - Add Reason column to orders table, Stop Loss/Take Profit/Reasons to trades table - Fix empty timeline chart (custom canvas 2D rendering instead of Chart.js) - Add symbol dropdown for timeline filtering - Fix report.show(browser=True) to skip inline Jupyter display
1 parent 1fecaa5 commit a5ad541

3 files changed

Lines changed: 347 additions & 122 deletions

File tree

investing_algorithm_framework/app/reporting/backtest_report.py

Lines changed: 45 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -125,14 +125,17 @@ def show(self, backtest_date_range=None, browser=False):
125125
with open(path, "w") as f:
126126
f.write(self.html_report)
127127

128+
if browser:
129+
webbrowser.open(f"file://{path}")
130+
return
131+
128132
try:
129133
from IPython import get_ipython
130134
shell = get_ipython().__class__.__name__
131135
if shell == 'ZMQInteractiveShell':
132136
from IPython.display import display, HTML
133137
display(HTML(self.html_report))
134-
if not browser:
135-
return
138+
return
136139
except (NameError, ImportError):
137140
pass
138141

@@ -463,6 +466,29 @@ def _build_run_data(self):
463466
op_dt = t.opened_at
464467
cl_dt = t.closed_at
465468
total_fees = getattr(t, 'total_fees', 0) or 0
469+
# Stop-loss / take-profit status
470+
t_sls = getattr(t, 'stop_losses', None)
471+
t_tps = getattr(t, 'take_profits', None)
472+
sl_triggered = any(
473+
getattr(sl, 'triggered', False)
474+
for sl in (t_sls or [])
475+
)
476+
tp_triggered = any(
477+
getattr(tp, 'triggered', False)
478+
for tp in (t_tps or [])
479+
)
480+
has_sl = bool(t_sls and len(t_sls) > 0)
481+
has_tp = bool(t_tps and len(t_tps) > 0)
482+
# Collect unique order reasons
483+
t_reasons = set()
484+
for t_o in (getattr(t, 'orders', None)
485+
or []):
486+
t_o_meta = getattr(
487+
t_o, 'metadata', None
488+
) or {}
489+
r = t_o_meta.get('order_reason')
490+
if r:
491+
t_reasons.add(r)
466492
trades_list.append({
467493
'id': idx_t,
468494
'sym': sym,
@@ -476,6 +502,17 @@ def _build_run_data(self):
476502
'total_fees': round(total_fees, 4),
477503
'net_gain': round(ng, 2),
478504
'pct': round(pct, 2),
505+
'stop_loss': (
506+
'Triggered' if sl_triggered
507+
else ('Set' if has_sl else '')
508+
),
509+
'take_profit': (
510+
'Triggered' if tp_triggered
511+
else ('Set' if has_tp else '')
512+
),
513+
'reasons': ', '.join(
514+
sorted(t_reasons)
515+
),
479516
})
480517
sym_stats.setdefault(sym, {'count': 0, 'gain': 0})
481518
sym_stats[sym]['count'] += 1
@@ -588,6 +625,11 @@ def _build_run_data(self):
588625
o_slippage = getattr(o, 'slippage', None)
589626
if o_slippage is None:
590627
o_slippage = 0
628+
o_meta = getattr(o, 'metadata', None)
629+
o_meta = o_meta if o_meta else {}
630+
o_reason = o_meta.get(
631+
'order_reason', ''
632+
)
591633
orders_list.append({
592634
'sym': getattr(o, 'target_symbol', '')
593635
or '',
@@ -617,6 +659,7 @@ def _build_run_data(self):
617659
'slippage': round(
618660
float(o_slippage), 4
619661
),
662+
'reason': o_reason,
620663
'created': _fmt_date(o_dt)
621664
if o_dt else '',
622665
'updated': _fmt_date(u_dt)

0 commit comments

Comments
 (0)