Files
set50-system/docs/test-evidence/2026-08-28-event-driven-backtest.md

90 lines
3.2 KiB
Markdown

# Test Evidence — Event-Driven PIT Backtest (2026-08-28)
## Summary
Replaced the calendar-based backtest loop with a strict point-in-time,
event-driven engine implementing the user-confirmed lifecycle.
## Confirmed user decisions implemented
1. **Strict PIT** — backtest blocks (400 + missing list) when factor vintages,
Siamchart manifest, or executable prices do not fully cover the window.
2. **Execution timing** — signal frozen on release date D; executed at next
trading-day close after D (no same-day look-ahead).
3. **Dividend timing** — entitled on ex-date (shares held before it); cash
available exactly `ex_date + 30` calendar days, exposed as
`ex_date_plus_30d`.
4. **Average cost** — realized P&L uses weighted average cost.
5. **Fees** — all-in 0.3% of notional on every buy and every sell; no added VAT.
## Tasks shipped (commits on main)
| Task | Commit | Tests |
|---|---|---|
| 1. PIT readiness + default dates | `68f2cc1` | 9 |
| 2. Unified event calendar | `6439e9c` | 14 |
| 3. Portfolio accounting ledger | `dc057e9` | 15 |
| 4. Lot/cash-constrained rebalancer | `71b893e` | 5 |
| 5. Event-driven backtest engine | `6459015` | 4 |
| 6. Durable run store + strict route | `c87767c` | 4 |
| 7. Frontend readiness/report | `d73a58b` | build |
| 8. Verification/review | (this doc) | — |
## Standalone suite (unittest, offline, deterministic)
```
51 tests OK
test_backtest_readiness (9) test_backtest_events (14)
test_portfolio_ledger (15) test_portfolio_rebalancer (5)
test_backtest_engine (4) test_backtest_store (4)
```
Compileall (all new modules): pass. `git diff --check`: clean.
## Static security scan (new backend files)
- secrets regex: 4 hits in `backtest_readiness.py` — all are the prose string
"missing token" (the umbrella missing-coverage token), **not** credentials.
- eval/exec: 0. Debug/print: 0.
- No hardcoded credentials, no path traversal, no unsafe eval.
## Live probes
### Fail-closed readiness (real local data — no factor vintages yet)
`POST /api/v1/backtest/run` on the on-disk data (0 factor vintages, no Siamchart
manifest) returns **400** with `missing` listing factor keys + siamchart — it
does not manufacture a fake PIT window.
`GET /api/v1/backtest/readiness` on real data: `ready=false`,
`missing` includes factor + siamchart, `recommended_end=2026-08-27` (yesterday,
Bangkok).
### Happy-path engine (synthetic PIT-complete inputs)
`backend/scripts/probe_event_backtest.py` (deterministic):
- `accounting_reconciled: True`
- accounting identity satisfied: `final_equity - capital == realized + unrealized + dividend_cash - fees`
(`lhs=84760.0 == rhs=84760.0`)
- `leakage_guard: True` (pit_meta-attested scorer)
- dividend cash credited (ex_date+30), receivable 0 by end
- holdings all 100-lot multiples (A=5000, B=3000)
- fee 0.3% applied (240.0)
## Accounting invariant
```
ending_equity - initial_capital
= realized_trading_pnl (gross, avg cost)
+ unrealized_trading_pnl
+ dividend_cash_received
- transaction_costs
```
`price_pnl = realized + unrealized` (compat).
## Independent review
See the reviewer subagent verdict recorded in the commit / engineering-log for
this session (`deleg_5225e9de`).