Files
fxhnt/tests/unit/test_nav_stats.py
jgrusewski f0ded1247f feat(cockpit): redesign Overview — connect the backfill record, provisional pre-gate Sharpes, cut clutter
Connect the bybit deploy book's multi-year BACKFILL (persisted paper_nav, venue="bybit") to the fund
headline so the page leads with the REAL record ($393,955 · +294% · real Sharpe · maxDD · since 2021)
plus a small live-readiness chip (gate days/min · status) — never the not-started forward track's
0.00 / 0 forward days.

- nav_stats.nav_backfill_stats: pure, unit-tested helper computing total return / annualised Sharpe
  (mean daily ret / pstdev × sqrt(365)) / maxDD / final equity from a persisted NAV equity series.
- Short pre-gate forward Sharpes are kept ("fun to watch") but rendered DIMMED with a "· pre-gate"
  marker; cleared tracks render bright. One caption explains it.
- Compact edges table (edge · class · record · forward · nav); not-started 0-day trackers collapsed
  into a "warming" subsection so they don't dominate.
- Cut clutter: removed the two prose paragraphs (folded into title= tooltips), removed the duplicate
  "Go to" footer, and replaced the grouped Live/Backtest nav with one tidy row
  (Overview · Paper · Replay · Backtest).
- Reusable Jinja macros (_macros.html: headline_card, provisional_num, track_row) for the
  cockpit-wide consistency roll-out. FleetRow gains gate_min_days for the forward "days/min" denominator.

Presentation + data-connection only — no edge return or gate logic changed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 08:07:42 +02:00

52 lines
2.0 KiB
Python

"""nav_backfill_stats: comparable, deterministic Sharpe/return/maxDD/final-equity from a persisted NAV
EQUITY series (the bybit book's multi-year backfill). Pure — no DB, no net. The helper is what connects the
backfilled record to the Overview headline (so the page never leads with 0.00)."""
from __future__ import annotations
import math
from fxhnt.application.nav_stats import nav_backfill_stats
def test_empty_and_singleton_are_zero() -> None:
for nav in ([], [100.0]):
s = nav_backfill_stats(nav)
assert s.total_return_pct == 0.0
assert s.sharpe == 0.0
assert s.max_dd_pct == 0.0
# final equity is the last point if present, else 0
assert s.final_equity == (nav[-1] if nav else 0.0)
def test_total_return_and_final_equity() -> None:
# 100k -> 393,955 is +293.955%
s = nav_backfill_stats([100_000.0, 393_955.0])
assert s.final_equity == 393_955.0
assert math.isclose(s.total_return_pct, 293.955, rel_tol=0, abs_tol=1e-6)
def test_sharpe_matches_mean_over_std_times_sqrt_365() -> None:
# Known equity series -> daily simple returns [+0.10, -0.05, +0.20].
nav = [100.0, 110.0, 104.5, 125.4]
rets = [110.0 / 100.0 - 1.0, 104.5 / 110.0 - 1.0, 125.4 / 104.5 - 1.0]
mean = sum(rets) / len(rets)
var = sum((r - mean) ** 2 for r in rets) / len(rets) # population std (pstdev), the repo convention
std = math.sqrt(var)
expected = mean / std * math.sqrt(365)
s = nav_backfill_stats(nav)
assert math.isclose(s.sharpe, expected, rel_tol=1e-9, abs_tol=1e-9)
def test_max_dd_is_worst_peak_to_trough_negative_pct() -> None:
# peak 120 then trough 90 -> -25%; recovery does not erase the recorded trough.
nav = [100.0, 120.0, 90.0, 130.0]
s = nav_backfill_stats(nav)
assert math.isclose(s.max_dd_pct, -25.0, rel_tol=0, abs_tol=1e-9)
def test_flat_series_has_zero_sharpe_no_divide_by_zero() -> None:
s = nav_backfill_stats([100.0, 100.0, 100.0])
assert s.sharpe == 0.0
assert s.total_return_pct == 0.0
assert s.max_dd_pct == 0.0