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

3.2 KiB

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).