add: 30d/90d/180d/365d forward returns in all backtest views
- Bracket table now shows Avg 30d, 90d, 180d, and 1yr columns - Signal events show all 4 timeframes - Current context shows all 4 average returns - Comparable examples show all available timeframes - Updated backtest screenshot
This commit is contained in:
parent
0ddb4ab01b
commit
e385765fda
@ -361,11 +361,13 @@ def run_backtest():
|
||||
if abs(d["score"] - current_score) <= margin and d["forward_returns"]:
|
||||
comparable.append(d)
|
||||
|
||||
avg_1yr = None
|
||||
avg_returns = {}
|
||||
if comparable:
|
||||
yr_returns = [d["forward_returns"]["365d"] for d in comparable if "365d" in d["forward_returns"]]
|
||||
if yr_returns:
|
||||
avg_1yr = round(sum(yr_returns) / len(yr_returns), 2)
|
||||
for period in ["30d", "90d", "180d", "365d"]:
|
||||
vals = [d["forward_returns"][period] for d in comparable if period in d["forward_returns"]]
|
||||
if vals:
|
||||
avg_returns[period] = round(sum(vals) / len(vals), 2)
|
||||
avg_1yr = avg_returns.get("365d")
|
||||
|
||||
# Best comparable examples (most recent 5)
|
||||
examples = []
|
||||
@ -383,6 +385,9 @@ def run_backtest():
|
||||
"percentile": percentile,
|
||||
"comparable_days": len(comparable),
|
||||
"avg_1yr_return": avg_1yr,
|
||||
"avg_30d_return": avg_returns.get("30d"),
|
||||
"avg_90d_return": avg_returns.get("90d"),
|
||||
"avg_180d_return": avg_returns.get("180d"),
|
||||
"examples": examples,
|
||||
}
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"provider": "ollama",
|
||||
"model": "qwen3.5:27b",
|
||||
"provider": "openrouter",
|
||||
"model": "minimax/minimax-m2.5",
|
||||
"providers": {
|
||||
"ollama": {
|
||||
"base_url": "http://100.100.242.21:11434"
|
||||
@ -15,7 +15,7 @@
|
||||
"api_key": ""
|
||||
},
|
||||
"openrouter": {
|
||||
"api_key": ""
|
||||
"api_key": "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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1121,7 +1121,7 @@ tr:hover td{background:var(--card-hover)}
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Score Range</th><th>Label</th><th>Days</th>
|
||||
<th>Avg 30d</th><th>Avg 90d</th><th>Avg 1yr</th>
|
||||
<th>Avg 30d</th><th>Avg 90d</th><th>Avg 180d</th><th>Avg 1yr</th>
|
||||
<th>Win Rate (1yr)</th><th>Max Gain (1yr)</th><th>Max Loss (1yr)</th>
|
||||
<th>Avg Max DD</th>
|
||||
</tr>
|
||||
@ -1228,7 +1228,7 @@ function renderAll() {
|
||||
document.getElementById('mainContent').innerHTML = `
|
||||
<div class="section"><div class="context-box" id="contextBox"><h2>Current Signal Context</h2><div class="context-score" id="ctxScore">--</div><div class="context-percentile" id="ctxPercentile"></div><div class="context-return" id="ctxReturn"></div><div class="comparable-list" id="ctxComparables"></div></div></div>
|
||||
<div class="section"><div class="card"><h2>Historical Score vs BTC Price</h2><div class="chart-dual"><canvas id="dualChart"></canvas></div></div></div>
|
||||
<div class="section"><div class="card"><h2>Score Bracket Performance</h2><div style="overflow-x:auto"><table><thead><tr><th>Score Range</th><th>Label</th><th>Days</th><th>Avg 30d</th><th>Avg 90d</th><th>Avg 1yr</th><th>Win Rate (1yr)</th><th>Max Gain</th><th>Max Loss</th><th>Avg Max DD</th></tr></thead><tbody id="bracketBody"></tbody></table></div></div></div>
|
||||
<div class="section"><div class="card"><h2>Score Bracket Performance</h2><div style="overflow-x:auto"><table><thead><tr><th>Score Range</th><th>Label</th><th>Days</th><th>Avg 30d</th><th>Avg 90d</th><th>Avg 180d</th><th>Avg 1yr</th><th>Win Rate (1yr)</th><th>Max Gain</th><th>Max Loss</th><th>Avg Max DD</th></tr></thead><tbody id="bracketBody"></tbody></table></div></div></div>
|
||||
<div class="section"><div class="card"><h2>Major Signal Events (Score Crossed 70/80/90+)</h2><div id="signalEvents"></div></div></div>
|
||||
`;
|
||||
renderContext();
|
||||
@ -1246,9 +1246,12 @@ function renderContext() {
|
||||
document.getElementById('ctxPercentile').textContent =
|
||||
'Historical percentile: top ' + (100 - ctx.percentile).toFixed(1) + '% of all days (' + ctx.comparable_days + ' comparable days found)';
|
||||
if (ctx.avg_1yr_return != null) {
|
||||
document.getElementById('ctxReturn').textContent =
|
||||
'Average 1-year return from this score level: ' + fmtPct(ctx.avg_1yr_return);
|
||||
document.getElementById('ctxReturn').className = 'context-return ' + retClass(ctx.avg_1yr_return);
|
||||
let retHtml = 'Average returns from this score level: ';
|
||||
if (ctx.avg_30d_return != null) retHtml += '<span class="' + retClass(ctx.avg_30d_return) + '">30d: ' + fmtPct(ctx.avg_30d_return) + '</span> · ';
|
||||
if (ctx.avg_90d_return != null) retHtml += '<span class="' + retClass(ctx.avg_90d_return) + '">90d: ' + fmtPct(ctx.avg_90d_return) + '</span> · ';
|
||||
if (ctx.avg_180d_return != null) retHtml += '<span class="' + retClass(ctx.avg_180d_return) + '">180d: ' + fmtPct(ctx.avg_180d_return) + '</span> · ';
|
||||
retHtml += '<span class="' + retClass(ctx.avg_1yr_return) + '"><strong>1yr: ' + fmtPct(ctx.avg_1yr_return) + '</strong></span>';
|
||||
document.getElementById('ctxReturn').innerHTML = retHtml;
|
||||
}
|
||||
// Examples
|
||||
const list = document.getElementById('ctxComparables');
|
||||
@ -1259,6 +1262,8 @@ function renderContext() {
|
||||
html += '<div class="comparable-item"><span>' + ex.date + ' — Score ' + ex.score + ' — ' + fmtPrice(ex.price) + '</span>';
|
||||
html += '<span>';
|
||||
if (fr['30d'] != null) html += '<span class="' + retClass(fr['30d']) + '">30d: ' + fmtPct(fr['30d']) + '</span> ';
|
||||
if (fr['90d'] != null) html += '<span class="' + retClass(fr['90d']) + '">90d: ' + fmtPct(fr['90d']) + '</span> ';
|
||||
if (fr['180d'] != null) html += '<span class="' + retClass(fr['180d']) + '">180d: ' + fmtPct(fr['180d']) + '</span> ';
|
||||
if (fr['365d'] != null) html += '<span class="' + retClass(fr['365d']) + '">1yr: ' + fmtPct(fr['365d']) + '</span>';
|
||||
html += '</span></div>';
|
||||
}
|
||||
@ -1362,6 +1367,7 @@ function renderBracketTable() {
|
||||
html += '<td>' + b.days + '</td>';
|
||||
html += '<td class="' + retClass(b.avg_30d) + '">' + fmtPct(b.avg_30d) + '</td>';
|
||||
html += '<td class="' + retClass(b.avg_90d) + '">' + fmtPct(b.avg_90d) + '</td>';
|
||||
html += '<td class="' + retClass(b.avg_180d) + '">' + fmtPct(b.avg_180d) + '</td>';
|
||||
html += '<td class="' + retClass(b.avg_365d) + '">' + fmtPct(b.avg_365d) + '</td>';
|
||||
html += '<td>' + (b.win_rate_365d != null ? b.win_rate_365d + '%' : '--') + '</td>';
|
||||
html += '<td class="t-green">' + fmtPct(b.max_gain_365d) + '</td>';
|
||||
@ -1390,6 +1396,7 @@ function renderSignalEvents() {
|
||||
const fr = ev.forward_returns || {};
|
||||
if (fr['30d'] != null) html += '<span class="' + retClass(fr['30d']) + '">30d: ' + fmtPct(fr['30d']) + '</span>';
|
||||
if (fr['90d'] != null) html += '<span class="' + retClass(fr['90d']) + '">90d: ' + fmtPct(fr['90d']) + '</span>';
|
||||
if (fr['180d'] != null) html += '<span class="' + retClass(fr['180d']) + '">180d: ' + fmtPct(fr['180d']) + '</span>';
|
||||
if (fr['365d'] != null) html += '<span class="' + retClass(fr['365d']) + '">1yr: ' + fmtPct(fr['365d']) + '</span>';
|
||||
if (ev.price_365d) html += '<span style="color:var(--text-dim)">Price 1yr: ' + fmtPrice(ev.price_365d) + '</span>';
|
||||
html += '</div></div>';
|
||||
|
||||
@ -2,3 +2,4 @@
|
||||
{"timestamp": "2026-03-20T22:30:13.547149+00:00", "composite_score": 51.0, "scored_count": 10, "metrics": {"fear_greed": {"score": 7, "value": 11}, "puell_multiple": {"score": 5, "value": 0.6602699608966011}, "mvrv_zscore": {"score": 5, "value": 0.5211180167687892}, "drawdown": {"score": 6, "value": 43.910215736040605}, "price_vs_200w_sma": {"score": 3, "value": 58895.78086828114}, "reserve_risk": {"score": 10, "value": 0.0012985709697654493}, "rhodl_ratio": {"score": 4, "value": 1230.6243545314708}, "nupl": {"score": 7, "value": 0.22243290955405431}, "lth_realized_price": {"score": 1, "value": 43346.58756410873}, "hash_ribbons": {"score": 3, "value": null}}}
|
||||
{"timestamp": "2026-03-20T22:46:34.952569+00:00", "composite_score": 51.0, "scored_count": 10, "metrics": {"fear_greed": {"score": 7, "value": 11}, "puell_multiple": {"score": 5, "value": 0.6602699608966011}, "mvrv_zscore": {"score": 5, "value": 0.5211180167687892}, "drawdown": {"score": 6, "value": 43.931630710659896}, "price_vs_200w_sma": {"score": 3, "value": 58895.78086828114}, "reserve_risk": {"score": 10, "value": 0.0012985709697654493}, "rhodl_ratio": {"score": 4, "value": 1230.6243545314708}, "nupl": {"score": 7, "value": 0.22243290955405431}, "lth_realized_price": {"score": 1, "value": 43346.58756410873}, "hash_ribbons": {"score": 3, "value": null}}}
|
||||
{"timestamp": "2026-03-20T22:51:27.724327+00:00", "composite_score": 54.0, "scored_count": 10, "metrics": {"fear_greed": {"score": 7, "value": 11}, "puell_multiple": {"score": 5, "value": 0.6602699608966011}, "mvrv_zscore": {"score": 5, "value": 0.5211180167687892}, "drawdown": {"score": 6, "value": 43.94907994923858}, "price_vs_200w_sma": {"score": 6, "value": 58895.78086828114}, "reserve_risk": {"score": 10, "value": 0.0012985709697654493}, "rhodl_ratio": {"score": 4, "value": 1230.6243545314708}, "nupl": {"score": 7, "value": 0.22243290955405431}, "lth_realized_price": {"score": 1, "value": 43346.58756410873}, "hash_ribbons": {"score": 3, "value": null}}}
|
||||
{"timestamp": "2026-03-20T23:07:48.303808+00:00", "composite_score": 51.0, "scored_count": 10, "metrics": {"fear_greed": {"score": 7, "value": 11}, "puell_multiple": {"score": 5, "value": 0.6602699608966011}, "mvrv_zscore": {"score": 5, "value": 0.5211180167687892}, "drawdown": {"score": 6, "value": 43.942734771573605}, "price_vs_200w_sma": {"score": 3, "value": 58895.78086828114}, "reserve_risk": {"score": 10, "value": 0.0012985709697654493}, "rhodl_ratio": {"score": 4, "value": 1230.6243545314708}, "nupl": {"score": 7, "value": 0.22243290955405431}, "lth_realized_price": {"score": 1, "value": 43346.58756410873}, "hash_ribbons": {"score": 3, "value": null}}}
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 603 KiB After Width: | Height: | Size: 633 KiB |
Loading…
x
Reference in New Issue
Block a user