Files
set50-system/docs/engineering-log/2026-08-27-backtest-accounting-remediation.md
Kunthawat Greethong 1b971ac72d [verified] Fix backtest accounting identity + honest UI disclosure
Correct the multi-rebalance backtest accounting so ending wealth is
capital + price_pnl + dividend_income with no double counting:
- price_pnl now measures equity change excluding dividends (was reusing
  ending holdings value as 'price profit')
- dividend proxy is included in final_value and net_return, exposed as
  dividend_method=final_holdings_yield_proxy
- regression tests: flat price => zero price_pnl; flat + dividend =>
  dividend-only return; rising no-dividend => correct bucket P&L;
  multi-rebalance accounting identity
- UI (result card + saved-run history) labels dividends as ประมาณการปันผล
  (Proxy) and shows descriptive non-PIT badge when leakage_guard=false

Backend 239 tests passed; targeted backtest 11 passed; frontend build,
npm audit (0), static scan and diff check passed; fresh independent
review deleg_10918fed passed with empty blocker arrays.
Backtest remains descriptive non-PIT (leakage_guard=false) with the
default current-score scorer.
2026-08-27 09:00:39 +07:00

3.9 KiB

Backtest Accounting Remediation — 2026-08-27

Status

complete — core accounting implementation, disclosure remediation, local verification, and fresh current-diff independent review all passed.

Scope

Correct the general multi-theme backtest result accounting without changing scoring, allocation, rebalance, price retrieval, or unrelated dirty working-tree files.

Verified root cause

backend/app/backtest.py previously calculated:

  • final_value = ending cash + ending market value
  • price_pnl = final_value - ending cash - dividend
  • net_return = (final_value - initial capital) / initial capital

This treated ending holdings value as price profit and excluded the dividend proxy from both final value and net return.

A deterministic flat-price reproduction with THB 100,000 produced price_pnl=20,000 without dividends and price_pnl=49,000 with a 2% dividend-yield proxy, when both price P&L values must be zero.

Remediation

  • Added flat-price regressions before production code changes and observed both fail for the expected accounting error.
  • Defined ending equity before dividend as ending cash plus ending holdings market value.
  • Defined price P&L as ending equity before dividend minus initial capital.
  • Included the estimated dividend in final value and net return.
  • Added API disclosure dividend_method=final_holdings_yield_proxy.
  • Updated the Vue result panel to label dividends as estimates and show a non-PIT warning when leakage_guard=false.

Changed files

  • backend/app/backtest.py
  • backend/tests/test_backtest.py
  • frontend/src/App.vue

Verification evidence

  • RED: two focused regressions failed with price_pnl=20,000 and price_pnl=49,000.
  • GREEN: flat/no-dividend, flat/proxy-dividend, rising/no-dividend, and multi-rebalance accounting checks passed.
  • Backtest module: 11 tests passed.
  • Full backend suite: 239 tests passed.
  • Python compileall: passed.
  • Frontend Vite production build: passed.
  • Fresh static server referenced index-DlxBkioz.js; all three new disclosure strings were present in the served bundle.
  • Live API probe returned the corrected accounting fields including dividend_method=final_holdings_yield_proxy and leakage_guard=false.
  • npm audit, high severity threshold: 0 vulnerabilities.
  • Added-line static scan: no hardcoded secrets, shell injection, eval/exec, unsafe pickle, SQL formatting, or debug leftovers.
  • git diff --check: passed.
  • Independent review cycle 1 failed closed on one saved-history disclosure blocker. A fresh fix agent added per-history proxy and non-PIT disclosure. After test hardening, fresh final review deleg_10918fed approved the current scoped diff with passed=true and empty security_concerns, logic_errors, and suggestions.

Full command evidence: docs/test-evidence/2026-08-27-backtest-accounting-remediation.md.

Remaining risks

  • Dividend income remains an annualized proxy based on final holdings and final dividend yield. It is not a dated dividend cash-flow ledger and is disclosed as such.
  • The public default backtest still uses current scores historically and remains descriptive non-PIT when leakage_guard=false.
  • leakage_guard=true means a scorer callable was supplied; it does not independently certify release-time provenance.
  • Existing backtest run history remains process-local memory.
  • The in-app preview loaded the current application, but result-state visual capture could not be completed; fresh served-artifact and live API checks are recorded instead.

Data safety and rollback

No database migration, external write, order dispatch, commit, or push occurred. Rollback is limited to the three changed code/test files listed above, but the repository contains unrelated pre-existing modifications that must not be reset wholesale.

Exact next action

Implement a dated dividend cash-flow ledger and provenance-validated PIT scorer rather than extending the proxy.