fix: correct backtest job result serialization and add Playwright test fixtures
- Fix BacktestPageRunResult serialization in jobs.py to correctly access nested fields from scenario and run_result objects - Add test_backtest_job.py with comprehensive tests for job execution - Add conftest_playwright.py with ServerManager that starts FastAPI server for Playwright tests using uvicorn - Add test_playwright_server.py with E2E tests using the server fixture The job serialization bug was causing backtest results to fail silently because it was trying to access non-existent fields on BacktestPageRunResult.
This commit is contained in:
@@ -214,30 +214,64 @@ def run_backtest_job(
|
||||
)
|
||||
|
||||
# Convert result to dict for serialization
|
||||
# BacktestPageRunResult has: scenario, run_result, entry_spot, data_source, data_cost_usd
|
||||
template_results = result.run_result.template_results
|
||||
first_template = template_results[0] if template_results else None
|
||||
summary = first_template.summary_metrics if first_template else None
|
||||
|
||||
result_dict = {
|
||||
"scenario_name": result.scenario_name,
|
||||
"entry_date": result.entry_date.isoformat() if result.entry_date else None,
|
||||
"scenario_id": result.scenario.scenario_id,
|
||||
"scenario_name": result.scenario.display_name,
|
||||
"symbol": result.scenario.symbol,
|
||||
"start_date": result.scenario.start_date.isoformat(),
|
||||
"end_date": result.scenario.end_date.isoformat(),
|
||||
"entry_spot": result.entry_spot,
|
||||
"underlying_units": result.underlying_units,
|
||||
"loan_amount": result.loan_amount,
|
||||
"margin_call_ltv": result.margin_call_ltv,
|
||||
"total_pnl": result.total_pnl,
|
||||
"total_pnl_pct": result.total_pnl_pct,
|
||||
"hedging_cost": result.hedging_cost,
|
||||
"hedging_cost_pct": result.hedging_cost_pct,
|
||||
"unhedged_pnl": result.unhedged_pnl,
|
||||
"unhedged_pnl_pct": result.unhedged_pnl_pct,
|
||||
"margin_calls": result.margin_calls,
|
||||
"margin_call_events": [
|
||||
"underlying_units": result.scenario.initial_portfolio.underlying_units,
|
||||
"loan_amount": result.scenario.initial_portfolio.loan_amount,
|
||||
"margin_call_ltv": result.scenario.initial_portfolio.margin_call_ltv,
|
||||
"data_source": result.data_source,
|
||||
"data_cost_usd": result.data_cost_usd,
|
||||
# Summary metrics from first template result
|
||||
"start_value": summary.start_value if summary else 0.0,
|
||||
"end_value_hedged_net": summary.end_value_hedged_net if summary else 0.0,
|
||||
"total_hedge_cost": summary.total_hedge_cost if summary else 0.0,
|
||||
"max_ltv_hedged": summary.max_ltv_hedged if summary else 0.0,
|
||||
"max_ltv_unhedged": summary.max_ltv_unhedged if summary else 0.0,
|
||||
"margin_call_days_hedged": summary.margin_call_days_hedged if summary else 0,
|
||||
"margin_call_days_unhedged": summary.margin_call_days_unhedged if summary else 0,
|
||||
"margin_threshold_breached_hedged": summary.margin_threshold_breached_hedged if summary else False,
|
||||
"margin_threshold_breached_unhedged": summary.margin_threshold_breached_unhedged if summary else False,
|
||||
# Template results with full daily path
|
||||
"template_results": [
|
||||
{
|
||||
"date": event.date.isoformat(),
|
||||
"price": event.price,
|
||||
"ltv": event.ltv,
|
||||
"action": event.action,
|
||||
"template_slug": tr.template_slug,
|
||||
"template_name": tr.template_name,
|
||||
"summary_metrics": {
|
||||
"start_value": tr.summary_metrics.start_value,
|
||||
"end_value_hedged_net": tr.summary_metrics.end_value_hedged_net,
|
||||
"total_hedge_cost": tr.summary_metrics.total_hedge_cost,
|
||||
"max_ltv_hedged": tr.summary_metrics.max_ltv_hedged,
|
||||
"max_ltv_unhedged": tr.summary_metrics.max_ltv_unhedged,
|
||||
"margin_call_days_hedged": tr.summary_metrics.margin_call_days_hedged,
|
||||
"margin_call_days_unhedged": tr.summary_metrics.margin_call_days_unhedged,
|
||||
},
|
||||
"daily_path": [
|
||||
{
|
||||
"date": dp.date.isoformat(),
|
||||
"spot_close": dp.spot_close,
|
||||
"underlying_value": dp.underlying_value,
|
||||
"option_market_value": dp.option_market_value,
|
||||
"net_portfolio_value": dp.net_portfolio_value,
|
||||
"ltv_hedged": dp.ltv_hedged,
|
||||
"ltv_unhedged": dp.ltv_unhedged,
|
||||
"margin_call_hedged": dp.margin_call_hedged,
|
||||
"margin_call_unhedged": dp.margin_call_unhedged,
|
||||
}
|
||||
for dp in tr.daily_path
|
||||
],
|
||||
}
|
||||
for event in (result.margin_call_events or [])
|
||||
for tr in template_results
|
||||
],
|
||||
"prices": [{"date": p.date.isoformat(), "close": p.close} for p in (result.prices or [])],
|
||||
}
|
||||
|
||||
# Stage 4: Complete
|
||||
|
||||
Reference in New Issue
Block a user