feat(metrics): real Sharpe ratio from daily PnL curve with minimum-sample gate
CI/CD / build-and-push (push) Successful in 10s
CI/CD / build-and-push (push) Successful in 10s
sharpe_ratio was hardcoded to 0.0 in MetricsTracker and exposed as 'or 0' in /api/summary. With only 1 resolved trade (~40 flat days plus one +299 jump) any computed Sharpe is statistically meaningless, so: - bot/metrics/sharpe.py: annualized Sharpe (sqrt(365)) from daily total_pnl closes, normalized by bankroll; sharpe_with_gate() returns None + status until >=30 days observed AND >=10 resolved trades. - Database.get_daily_pnl_closes(): last metrics_daily snapshot per UTC day, oldest first — the return series input. - MetricsTracker: stores the real (gated) Sharpe in the snapshot, NULL below the gate; log line now includes sharpe. - /api/summary: live Sharpe + sharpe_status/days_observed/min_* fields explaining why it is null; resolved_count now live from COUNT(*). - promotion_ready: requires resolved>=10, days>=30, and non-null win_rate/calibration/sharpe plus existing thresholds — a single lucky resolved trade can no longer promote. - Dashboard Sharpe card shows the insufficient-sample explanation when null instead of a bare em dash. Tests: 13 new in tests/test_sharpe_gate.py (formula, gate, API contract, tracker snapshot); verified failing pre-fix. Suite: 62 passed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
1797b79f7b
commit
43d9577fb2
@@ -348,6 +348,24 @@ class Database:
|
||||
)
|
||||
return [dict(r) for r in rows]
|
||||
|
||||
async def get_daily_pnl_closes(self) -> list[float]:
|
||||
"""Return the closing total_pnl of every observed UTC day, oldest first.
|
||||
|
||||
One value per calendar day with at least one metrics_daily snapshot
|
||||
(the day's last snapshot, same collapse rule as get_metrics_history).
|
||||
This is the input series for the Sharpe ratio: len() = days observed,
|
||||
consecutive deltas = daily PnL changes.
|
||||
"""
|
||||
async with self._pool.acquire() as conn:
|
||||
rows = await conn.fetch(
|
||||
"""
|
||||
SELECT DISTINCT ON (timestamp::date) total_pnl
|
||||
FROM metrics_daily
|
||||
ORDER BY timestamp::date ASC, timestamp DESC
|
||||
"""
|
||||
)
|
||||
return [float(r["total_pnl"] or 0) for r in rows]
|
||||
|
||||
async def backfill_feature_columns(self) -> int:
|
||||
"""Back-populate feat_*_lo for trades created before Phase 6.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user