feat(BT-002): add historical snapshot provider

This commit is contained in:
Bu5hm4nn
2026-03-27 18:31:28 +01:00
parent 1a6760bee3
commit 477514f838
15 changed files with 822 additions and 82 deletions

View File

@@ -0,0 +1,66 @@
# BT-002 Historical Options Snapshot Provider
## What shipped
BT-002 adds a point-in-time historical options snapshot provider for backtests.
The new provider lives in `app/services/backtesting/historical_provider.py` and plugs into the same `BacktestService` / engine flow as the existing synthetic provider.
## Provider contract
The snapshot provider exposes the same backtest-facing behaviors as the synthetic provider:
- load underlying daily closes for the scenario window
- validate `ProviderRef`
- open positions at scenario start using only the entry-day snapshot
- mark open positions later using the exact same contract identity
This lets backtests swap:
- synthetic pricing: `synthetic_v1 / synthetic_bs_mid`
- observed snapshot pricing: `daily_snapshots_v1 / snapshot_mid`
## Contract-selection rules
The provider uses explicit, deterministic, point-in-time rules:
1. filter to the entry-day option chain only
2. keep contracts with expiry at or beyond the target expiry date
3. choose the nearest eligible expiry
4. within that expiry, choose the nearest strike to the target strike
5. on equal-distance strike ties:
- puts prefer the higher strike
- calls prefer the lower strike
These rules avoid lookahead bias because later snapshots are not consulted for entry selection.
## Daily mark-to-market rules
After entry, the provider marks positions using the exact same `contract_key`.
It does **not** silently substitute a different strike or expiry when the original contract is missing.
Current fallback policy:
1. use the exact same contract from the same-day snapshot
2. if missing before expiry, carry forward the previous mark for that same contract and emit a warning
3. if the valuation date is at or after expiry, settle to intrinsic value and close the position
## Data-quality tradeoffs
The current BT-002 slice intentionally keeps the data model simple:
- snapshots are assumed to provide a precomputed daily `mid`
- the provider does not currently derive mids from bid/ask pairs
- missing exact-contract marks are explicit warnings, not silent substitutions
- the engine currently still supports `continuous_units` sizing for snapshot-backed runs
## Known limitations / follow-up
This slice does **not** yet include:
- file-backed or external ingestion of real historical snapshot datasets
- listed-contract rounding / contract-size-aware position sizing
- persistent run-status objects beyond template-level warnings
Those follow-ups should remain explicit roadmap work rather than being implied by BT-002.

View File

@@ -700,7 +700,8 @@ If this changes later, it must be a scenario-level parameter.
## 5. Continuous-vs-listed quantity must be explicit
MVP synthetic runs may use `continuous_units`.
BT-002 listed snapshot runs should support `listed_contracts` with contract-size rounding.
The shipped BT-002 provider slice also remains `continuous_units`-only.
`listed_contracts` with contract-size rounding is deferred to follow-up slice `BT-002A`.
Do not hide rounding rules inside providers.
They belong in the position sizing logic and must be recorded in the result.
@@ -714,9 +715,9 @@ Do not collapse the entire hedge economics into end-of-period payoff only.
Any missing snapshot/mark fallback must add:
- a run warning
- a run or template warning
- a template validation note
- a deterministic result status if the template becomes incomplete
- and, in a fuller follow-up slice, a deterministic result status if the template becomes incomplete
---

View File

@@ -13,7 +13,6 @@ notes:
- Pre-alpha policy: we may cut or replace old features without backward compatibility until alpha is declared.
- Alpha migration policy: once alpha is declared, compatibility only needs to move forward; backward migrations are not required.
priority_queue:
- BT-002
- BT-001C
- EXEC-001
- EXEC-002
@@ -21,7 +20,9 @@ priority_queue:
- DATA-001A
- OPS-001
- BT-003
- BT-002A
recently_completed:
- BT-002
- PORT-003
- BT-003B
- CORE-001D
@@ -43,9 +44,9 @@ states:
- OPS-001
- EXEC-001
- EXEC-002
- BT-002
- BT-003
- BT-001C
- BT-002A
in_progress: []
done:
- DATA-001
@@ -61,6 +62,7 @@ states:
- EXEC-001A
- BT-001
- BT-001A
- BT-002
- BT-003A
- BT-003B
- CORE-001A

View File

@@ -1,14 +0,0 @@
id: BT-002
title: Historical Daily Options Snapshot Provider
status: backlog
priority: P2
effort: L
depends_on:
- BT-001
tags: [backtesting, data]
summary: Support real daily historical options premiums in backtests.
acceptance_criteria:
- Historical provider abstraction supports point-in-time daily option snapshots.
- Backtests can swap synthetic pricing for observed historical premiums.
- Contract selection avoids lookahead bias.
- Provider/data-quality tradeoffs are documented.

View File

@@ -0,0 +1,16 @@
id: BT-002A
title: Snapshot Ingestion and Listed Contract Sizing
status: backlog
priority: P3
effort: M
depends_on:
- BT-002
tags:
- backtesting
- data
summary: Extend BT-002 from provider support to file-backed/external snapshot ingestion and listed-contract sizing semantics.
acceptance_criteria:
- Historical snapshot data can be loaded from a documented file-backed or external source, not only injected in-memory fixtures.
- Snapshot-backed runs can size positions in listed contract units with explicit contract-size rounding rules.
- Snapshot data-quality warnings and incomplete-run behavior are persisted/reportable, not only template-local warnings.
- Provider configuration and snapshot-source assumptions are documented for reproducible runs.

View File

@@ -0,0 +1,20 @@
id: BT-002
title: Historical Daily Options Snapshot Provider
status: done
priority: P2
effort: L
depends_on:
- BT-001
tags:
- backtesting
- data
summary: Backtests can now use a point-in-time historical options snapshot provider with exact-contract mark-to-market instead of synthetic-only option pricing.
completed_notes:
- Added shared historical position/mark provider hooks in `app/services/backtesting/historical_provider.py` so `BacktestService` can swap provider implementations while preserving the backtest engine flow.
- Snapshot-backed runs still fail closed on `listed_contracts`; BT-002 ships observed snapshot pricing for `continuous_units` only, with listed-contract sizing explicitly deferred to `BT-002A`.
- Added `DailyOptionsSnapshotProvider` with deterministic entry-day contract selection, exact-contract mark-to-market, and explicit carry-forward warnings when later marks are missing.
- Updated `app/backtesting/engine.py` and `app/services/backtesting/service.py` so snapshot-backed runs and synthetic runs share the same scenario execution path.
- Added focused regression coverage in `tests/test_backtesting_snapshots.py` for entry-day-only selection, observed snapshot marks, and no-substitution missing-mark fallback behavior.
- Added provider/data-quality documentation in `docs/BT-002_HISTORICAL_OPTIONS_SNAPSHOT_PROVIDER.md`, including current limitations around precomputed mids, continuous-units sizing, and follow-up ingestion work.
- Docker-served browser validation still passed on the affected historical routes after the engine/provider seam changes: `/health` returned OK and `tests/test_e2e_playwright.py` passed against the local Docker app.
- While closing that browser loop, `/{workspace_id}/event-comparison` preset changes were corrected to preserve user-edited underlying units and only reset preset-driven template selection, matching the UI copy and stale-state behavior.