feat: add Portfolio Value, Option Value, and Contracts columns to daily results

- Add option_contracts field to BacktestDailyPoint (number of contracts held)
- Update engine to calculate total option contracts from positions
- Update job serialization to include underlying_value, option_market_value, net_portfolio_value, option_contracts
- Update both render_result and render_job_result tables to show:
  - Low, High, Close (from previous commit)
  - Portfolio value (net_portfolio_value)
  - Option value (option_market_value)
  - Contracts (option_contracts)
  - LTV hedged
  - Margin call status
This commit is contained in:
Bu5hm4nn
2026-04-05 08:54:38 +02:00
parent 7a7b191a6d
commit 6b8336ab7e
4 changed files with 69 additions and 1 deletions

View File

@@ -87,6 +87,9 @@ class SyntheticBacktestEngine:
ltv_unhedged_worst = scenario.initial_portfolio.loan_amount / underlying_value_worst ltv_unhedged_worst = scenario.initial_portfolio.loan_amount / underlying_value_worst
ltv_hedged_worst = scenario.initial_portfolio.loan_amount / net_portfolio_value_worst ltv_hedged_worst = scenario.initial_portfolio.loan_amount / net_portfolio_value_worst
# Total option contracts held
option_contracts = sum(p.quantity for p in open_positions)
daily_points.append( daily_points.append(
BacktestDailyPoint( BacktestDailyPoint(
date=day.date, date=day.date,
@@ -103,6 +106,7 @@ class SyntheticBacktestEngine:
ltv_hedged=ltv_hedged, ltv_hedged=ltv_hedged,
margin_call_unhedged=ltv_unhedged_worst >= scenario.initial_portfolio.margin_call_ltv, margin_call_unhedged=ltv_unhedged_worst >= scenario.initial_portfolio.margin_call_ltv,
margin_call_hedged=ltv_hedged_worst >= scenario.initial_portfolio.margin_call_ltv, margin_call_hedged=ltv_hedged_worst >= scenario.initial_portfolio.margin_call_ltv,
option_contracts=option_contracts,
active_position_ids=tuple(active_position_ids), active_position_ids=tuple(active_position_ids),
) )
) )

View File

@@ -97,10 +97,12 @@ class BacktestDailyPoint:
ltv_hedged: float ltv_hedged: float
margin_call_unhedged: bool margin_call_unhedged: bool
margin_call_hedged: bool margin_call_hedged: bool
active_position_ids: tuple[str, ...] = field(default_factory=tuple)
# Optional OHLC fields for worst-case margin call evaluation # Optional OHLC fields for worst-case margin call evaluation
spot_low: float | None = None # Day's low for margin call evaluation spot_low: float | None = None # Day's low for margin call evaluation
spot_high: float | None = None # Day's high spot_high: float | None = None # Day's high
active_position_ids: tuple[str, ...] = field(default_factory=tuple) # Option position info
option_contracts: float = 0.0 # Number of option contracts held
@dataclass(frozen=True) @dataclass(frozen=True)
@@ -154,3 +156,22 @@ class EventComparisonReport:
scenario: BacktestScenario scenario: BacktestScenario
rankings: tuple[EventComparisonRanking, ...] rankings: tuple[EventComparisonRanking, ...]
run_result: BacktestRunResult run_result: BacktestRunResult
@dataclass(frozen=True)
class BacktestPortfolioPreset:
"""User-facing preset for quick scenario configuration."""
preset_id: str
name: str
description: str
underlying_symbol: str
start_date: date
end_date: date
entry_spot: float | None = None # If None, derive from historical data
underlying_units: float = 1000.0
loan_amount: float = 50000.0
margin_call_ltv: float = 0.80
template_slug: str = "protective-put-atm-12m"
# Event-specific overrides
scenario_overrides: dict[str, object] | None = None

View File

@@ -659,6 +659,24 @@ def _render_backtests_page(workspace_id: str | None = None) -> None:
{"name": "low", "label": "Low", "field": "low", "align": "right"}, {"name": "low", "label": "Low", "field": "low", "align": "right"},
{"name": "high", "label": "High", "field": "high", "align": "right"}, {"name": "high", "label": "High", "field": "high", "align": "right"},
{"name": "close", "label": "Close", "field": "close", "align": "right"}, {"name": "close", "label": "Close", "field": "close", "align": "right"},
{
"name": "portfolio_value",
"label": "Portfolio",
"field": "portfolio_value",
"align": "right",
},
{
"name": "option_value",
"label": "Option value",
"field": "option_value",
"align": "right",
},
{
"name": "contracts",
"label": "Contracts",
"field": "contracts",
"align": "right",
},
{ {
"name": "ltv_hedged", "name": "ltv_hedged",
"label": "LTV hedged", "label": "LTV hedged",
@@ -678,6 +696,9 @@ def _render_backtests_page(workspace_id: str | None = None) -> None:
"low": f"${point.spot_low:,.2f}", "low": f"${point.spot_low:,.2f}",
"high": f"${point.spot_high:,.2f}", "high": f"${point.spot_high:,.2f}",
"close": f"${point.spot_close:,.2f}", "close": f"${point.spot_close:,.2f}",
"portfolio_value": f"${point.net_portfolio_value:,.0f}",
"option_value": f"${point.option_market_value:,.0f}",
"contracts": f"{point.option_contracts:,.0f}",
"ltv_hedged": f"{point.ltv_hedged:.1%}", "ltv_hedged": f"{point.ltv_hedged:.1%}",
"margin_call": "Yes" if point.margin_call_hedged else "No", "margin_call": "Yes" if point.margin_call_hedged else "No",
} }
@@ -991,6 +1012,24 @@ def _render_backtests_page(workspace_id: str | None = None) -> None:
{"name": "low", "label": "Low", "field": "low", "align": "right"}, {"name": "low", "label": "Low", "field": "low", "align": "right"},
{"name": "high", "label": "High", "field": "high", "align": "right"}, {"name": "high", "label": "High", "field": "high", "align": "right"},
{"name": "close", "label": "Close", "field": "close", "align": "right"}, {"name": "close", "label": "Close", "field": "close", "align": "right"},
{
"name": "portfolio_value",
"label": "Portfolio",
"field": "portfolio_value",
"align": "right",
},
{
"name": "option_value",
"label": "Option value",
"field": "option_value",
"align": "right",
},
{
"name": "contracts",
"label": "Contracts",
"field": "contracts",
"align": "right",
},
{ {
"name": "ltv_hedged", "name": "ltv_hedged",
"label": "LTV hedged", "label": "LTV hedged",
@@ -1010,6 +1049,9 @@ def _render_backtests_page(workspace_id: str | None = None) -> None:
"low": f"${dp.get('spot_low', 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}", "high": f"${dp.get('spot_high', dp.get('spot_close', 0)):,.2f}",
"close": f"${dp.get('spot_close', 0):,.2f}", "close": f"${dp.get('spot_close', 0):,.2f}",
"portfolio_value": f"${dp.get('net_portfolio_value', 0):,.0f}",
"option_value": f"${dp.get('option_market_value', 0):,.0f}",
"contracts": f"{dp.get('option_contracts', 0):,.0f}",
"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

@@ -264,6 +264,7 @@ def run_backtest_job(
"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,
"option_contracts": dp.option_contracts,
"ltv_hedged": dp.ltv_hedged, "ltv_hedged": dp.ltv_hedged,
"ltv_unhedged": dp.ltv_unhedged, "ltv_unhedged": dp.ltv_unhedged,
"margin_call_hedged": dp.margin_call_hedged, "margin_call_hedged": dp.margin_call_hedged,