feat: default to March 2026 dates and show Low/High/Close in results

- Change default backtest date range to 2026-03-02 through 2026-03-25
- Add spot_low and spot_high to BacktestDailyPoint for intraday range
- Update engine to populate low/high from DailyClosePoint
- Update daily results table to show Low, High, Close columns instead of just Spot
- Update job serialization to include spot_low and spot_high
This commit is contained in:
Bu5hm4nn
2026-04-04 23:18:01 +02:00
parent a8e710f790
commit 063ccb6781
4 changed files with 33 additions and 35 deletions

View File

@@ -91,6 +91,8 @@ class SyntheticBacktestEngine:
BacktestDailyPoint( BacktestDailyPoint(
date=day.date, date=day.date,
spot_close=day.close, spot_close=day.close,
spot_low=day.low if day.low is not None else day.close,
spot_high=day.high if day.high is not None else day.close,
underlying_value=underlying_value_close, underlying_value=underlying_value_close,
option_market_value=option_market_value, option_market_value=option_market_value,
premium_cashflow=premium_cashflow, premium_cashflow=premium_cashflow,

View File

@@ -97,6 +97,9 @@ class BacktestDailyPoint:
ltv_hedged: float ltv_hedged: float
margin_call_unhedged: bool margin_call_unhedged: bool
margin_call_hedged: bool margin_call_hedged: bool
# Optional OHLC fields for worst-case margin call evaluation
spot_low: float | None = None # Day's low for margin call evaluation
spot_high: float | None = None # Day's high
active_position_ids: tuple[str, ...] = field(default_factory=tuple) active_position_ids: tuple[str, ...] = field(default_factory=tuple)

View File

@@ -1,7 +1,7 @@
from __future__ import annotations from __future__ import annotations
import logging import logging
from datetime import date, datetime, timedelta from datetime import date, datetime
from typing import TYPE_CHECKING, Any from typing import TYPE_CHECKING, Any
from fastapi.responses import RedirectResponse from fastapi.responses import RedirectResponse
@@ -64,20 +64,12 @@ DATABENTO_DATASET_MIN_DATES = {
def get_default_backtest_dates() -> tuple[date, date]: def get_default_backtest_dates() -> tuple[date, date]:
"""Get default backtest date range (~2 years ending on most recent Friday or earlier). """Get default backtest date range (March 2026 for testing).
Returns dates (start, end) where: Returns dates (start, end) for March 2026.
- end is the most recent Friday (including today if today is Friday)
- start is ~730 days before end
""" """
today = date.today() start = date(2026, 3, 2)
# Find days since most recent Friday end = date(2026, 3, 25)
days_since_friday = (today.weekday() - 4) % 7
# If today is Friday (weekday 4), days_since_friday is 0, so end = today
# If today is Saturday (weekday 5), days_since_friday is 1, so end = yesterday (Friday)
# etc.
end = today - timedelta(days=days_since_friday)
start = end - timedelta(days=730) # ~2 years
return start, end return start, end
@@ -658,41 +650,36 @@ def _render_backtests_page(workspace_id: str | None = None) -> None:
) )
with ui.card().classes( with ui.card().classes(
"w-full rounded-2xl border border-slate-200 bg-white shadow-sm dark:border-slate-800 dark:bg-slate-900" "w-full mt-4 rounded-xl border border-slate-200 bg-slate-50 p-4 shadow-none dark:border-slate-800 dark:bg-slate-950"
): ):
ui.label("Daily Results").classes("text-lg font-semibold text-slate-900 dark:text-slate-100") ui.label("Daily Results").classes("text-md font-semibold text-slate-900 dark:text-slate-100 mb-2")
ui.table( ui.table(
columns=[ columns=[
{"name": "date", "label": "Date", "field": "date", "align": "left"}, {"name": "date", "label": "Date", "field": "date", "align": "left"},
{"name": "spot_close", "label": "Spot", "field": "spot_close", "align": "right"}, {"name": "low", "label": "Low", "field": "low", "align": "right"},
{"name": "high", "label": "High", "field": "high", "align": "right"},
{"name": "close", "label": "Close", "field": "close", "align": "right"},
{ {
"name": "net_portfolio_value", "name": "ltv_hedged",
"label": "Net hedged", "label": "LTV hedged",
"field": "net_portfolio_value", "field": "ltv_hedged",
"align": "right", "align": "right",
}, },
{ {
"name": "ltv_unhedged", "name": "margin_call",
"label": "LTV unhedged", "label": "Margin call",
"field": "ltv_unhedged", "field": "margin_call",
"align": "right",
},
{"name": "ltv_hedged", "label": "LTV hedged", "field": "ltv_hedged", "align": "right"},
{
"name": "margin_call_hedged",
"label": "Hedged breach",
"field": "margin_call_hedged",
"align": "center", "align": "center",
}, },
], ],
rows=[ rows=[
{ {
"date": point.date.isoformat(), "date": point.date.isoformat(),
"spot_close": f"${point.spot_close:,.2f}", "low": f"${point.spot_low:,.2f}",
"net_portfolio_value": f"${point.net_portfolio_value:,.0f}", "high": f"${point.spot_high:,.2f}",
"ltv_unhedged": f"{point.ltv_unhedged:.1%}", "close": f"${point.spot_close:,.2f}",
"ltv_hedged": f"{point.ltv_hedged:.1%}", "ltv_hedged": f"{point.ltv_hedged:.1%}",
"margin_call_hedged": "Yes" if point.margin_call_hedged else "No", "margin_call": "Yes" if point.margin_call_hedged else "No",
} }
for point in template_result.daily_path for point in template_result.daily_path
], ],
@@ -1001,7 +988,9 @@ def _render_backtests_page(workspace_id: str | None = None) -> None:
ui.table( ui.table(
columns=[ columns=[
{"name": "date", "label": "Date", "field": "date", "align": "left"}, {"name": "date", "label": "Date", "field": "date", "align": "left"},
{"name": "spot", "label": "Spot", "field": "spot", "align": "right"}, {"name": "low", "label": "Low", "field": "low", "align": "right"},
{"name": "high", "label": "High", "field": "high", "align": "right"},
{"name": "close", "label": "Close", "field": "close", "align": "right"},
{ {
"name": "ltv_hedged", "name": "ltv_hedged",
"label": "LTV hedged", "label": "LTV hedged",
@@ -1018,7 +1007,9 @@ def _render_backtests_page(workspace_id: str | None = None) -> None:
rows=[ rows=[
{ {
"date": dp.get("date", ""), "date": dp.get("date", ""),
"spot": f"${dp.get('spot_close', 0):,.2f}", "low": f"${dp.get('spot_low', dp.get('spot_close', 0)):,.2f}",
"high": f"${dp.get('spot_high', dp.get('spot_close', 0)):,.2f}",
"close": f"${dp.get('spot_close', 0):,.2f}",
"ltv_hedged": f"{dp.get('ltv_hedged', 0):.1%}", "ltv_hedged": f"{dp.get('ltv_hedged', 0):.1%}",
"margin_call": "Yes" if dp.get("margin_call_hedged") else "No", "margin_call": "Yes" if dp.get("margin_call_hedged") else "No",
} }

View File

@@ -259,6 +259,8 @@ def run_backtest_job(
{ {
"date": dp.date.isoformat(), "date": dp.date.isoformat(),
"spot_close": dp.spot_close, "spot_close": dp.spot_close,
"spot_low": dp.spot_low if dp.spot_low is not None else dp.spot_close,
"spot_high": dp.spot_high if dp.spot_high is not None else dp.spot_close,
"underlying_value": dp.underlying_value, "underlying_value": dp.underlying_value,
"option_market_value": dp.option_market_value, "option_market_value": dp.option_market_value,
"net_portfolio_value": dp.net_portfolio_value, "net_portfolio_value": dp.net_portfolio_value,