90 lines
3.2 KiB
Markdown
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`).
|