diff --git a/docs/HANDOFF.md b/docs/HANDOFF.md index 85a1454..261fd59 100644 --- a/docs/HANDOFF.md +++ b/docs/HANDOFF.md @@ -193,6 +193,31 @@ Implement dated dividend events and a provenance-validated PIT score provider be --- +## Session 2026-08-27 — PIT factor store + partial PIT score provider (verified + pushed) + +**Branch:** main · **HEAD:** 1f630be (pushed `1b971ac..1f630be`) + +### Completed this session (verified + pushed) +- **PIT factor store** `backend/app/factor_vintages.py`: append-only, provenance-complete store with `observed_at` / `released_at` / `retrieved_at` and a SHA-256 canonical **hash chain** (tamper/reorder detectable). `value_at(as_of)` returns only rows whose `released_at <= as_of` — a real testable **anti-look-ahead** guarantee; no value released by `as_of` **fails closed** (None / `have_pit_value=False`). Provenance invariants enforced: `released >= observed`, `retrieved >= released`, non-finite / naive-timestamp rejected. +- **Partial PIT score provider** `backend/app/pit_scorer.py`: `PitScoreProvider.score_board(as_of)` rebuilds the per-symbol board from PIT factor values (theme 60% dimension) plus a **partial** siamchart view (EPS growth from the 5-year series; current ratios flagged `partial_pit`). `make_pit_score_fn` adapts it to the engine's `ScoreFn`; date-only `as_of` normalized to midnight +07:00. +- **Honest leakage gating** `backend/app/backtest.py`: `_resolve_scores` now sets `leakage_guard=True` **only** when the supplied score_fn's per-symbol meta asserts `pit_meta.pit=True`. An arbitrary callable with no PIT proof is no longer treated as PIT — closes the "supplied fn ⇒ PIT" hole. +- **API** `backend/app/__init__.py`: `POST /api/v1/backtest` accepts `use_pit`, wiring the PIT provider; `_load_siamchart_snapshot` loads the SET50 fundamental snapshot. Empty factor store **fail-closes** (live probe: `leakage_guard=false`). + +### Verified +- Backend full suite **255 passed** (was 239); new tests 14 (factor store 9 + pit scorer 5) + 2 backtest leakage-gating + 1 corrected. +- compileall, `git diff --check`, static secret/dangerous scan all clean. +- Live route probe: `use_pit` with empty store → HTTP 200, `leakage_guard=false`, real 2-rebalance run. + +### Honest scope / known limitations +- Theme dimension is PIT **from this store forward**; **no factor history exists before 2026-08-27**, so any pre-today backtest remains non-PIT by construction. +- Siamchart fundamental is **partial**: EPS 5-year growth is PIT-grade, but ratios (Yield %, PE, P/BV, ROE, DPS) are current-snapshot only and the snapshot has no dated vintage chain yet. Results are flagged `partial_pit`, never fully-PIT. +- `dividend_income` still uses the final-holdings yield proxy (not a dated dividend ledger); that is the next milestone. + +### Exact next action +Implement the dated **dividend cash-flow ledger** (ex-date / record date / per-share / qty held) replacing the final-holdings proxy. After that (or in parallel), add a **siamchart vintage chain** so the fundamental dimension stops being partial. Do not reset or stage unrelated pre-existing working-tree changes. + +--- + ## Session 2026-08-25 — Multi-theme + Simulation (appended) **Branch:** main · **HEAD:** aae1d13 (pushed `8a6991b..aae1d13`) diff --git a/docs/engineering-log.md b/docs/engineering-log.md index ddcd91e..4aeb0d2 100644 --- a/docs/engineering-log.md +++ b/docs/engineering-log.md @@ -98,3 +98,4 @@ - P0-B + P3 + P4 (commit `8db3d48`, reviews `deleg_fe6f45cd` + `deleg_718218f8` both `passed=true`): the declarative FACTORS/THEMES registry is now the single source of truth (`compute_theme_surprises` reads registry; hand-written per-theme blocks removed; FACTORS carries center/span normalization spec; bank NPL wired into banks). P3 rebuilt `run_backtest` as a real multi-rebalance PIT engine (`leakage_guard`, `momentum_at` true 12-1). P4 added factor-weight learning (Spearman IC -> `apply_weight_update`) + `/api/v1/learning/momentum`. Live result: momentum IC=0.012, t=0.132 over 22 periods (no reliable predictive power in this SET50 window). 226 tests pass. - Follow-up (A+B): theme surprises now weight-normalized by total |weight| so cross-theme magnitudes are comparable (retail 0.189->0.145). Added append-only `FactorHistory` store (`data/factor_history/.jsonl`) that records every FACTORS value each scheduler run, wired into `refresh_all` (non-fatal), plus `/api/v1/learning/factors` readiness endpoint. Macro/demographic factors start at n=1 and become learnable (P4) as history accumulates. 233 tests pass. - Backtest accounting remediation (2026-08-27): deterministic flat-price tests exposed that ending holdings value was reported as `price_pnl` and the dividend proxy was excluded from final value/net return. The corrected identity is `final_value = capital + price_pnl + dividend_income`; API now emits `dividend_method=final_holdings_yield_proxy`, and both result/history UI paths label the proxy and warn when `leakage_guard=false`. RED failures reproduced `price_pnl=20,000/49,000`; GREEN verification: 239 backend tests, compileall, Vite build, npm audit 0 high-severity vulnerabilities, served-bundle disclosure check, static scan and diff check passed. Fresh final independent review `deleg_10918fed` passed with empty blocker arrays. Evidence: `docs/engineering-log/2026-08-27-backtest-accounting-remediation.md` and `docs/test-evidence/2026-08-27-backtest-accounting-remediation.md`. The dividend model remains a proxy and the default public backtest remains descriptive non-PIT. +- PIT factor store + partial score provider (2026-08-27, commit `1f630be`): added `factor_vintages.py` (append-only PIT factor store: observed/released/retrieved_at + SHA-256 hash chain; `value_at(as_of)` anti-look-ahead, fail-closed when nothing released by as_of) and `pit_scorer.py` (`PitScoreProvider.score_board(as_of)` + `make_pit_score_fn`). `backtest._resolve_scores` now sets `leakage_guard=True` only when the supplied score_fn asserts `pit_meta.pit=True` (closes "any callable ⇒ PIT"); `/api/v1/backtest` accepts `use_pit`. Empty factor store fail-closes (live probe `leakage_guard=false`). Full backend **255 passed** (was 239). Honest scope: theme dimension PIT from this store forward; no factor history before 2026-08-27 so pre-today backtests stay non-PIT; siamchart fundamental partial (EPS 5-yr series PIT-grade, ratios current → `partial_pit`). Next: dated dividend cash-flow ledger.