# Executable Methodology Audit

## Purpose

This document records which parts of the public investment methodology are actually represented in executable source code, which are only specified, and where the implementation disagrees with the specification.

The objective is not to maximize the number of items labeled `implemented`. The objective is to make it difficult for the public description to outrun the code.

**Audit basis:** repository `main` at `d268bb7dfc5b2c4a418bfcc086d2635878f15039` plus the public-methodology work derived from it and the subsequent #302 MA200 contract correction.

## Status vocabulary

- **Implemented:** executable code represents the described behavior.
- **Implemented with caveat:** executable code exists, but a material limitation or contract mismatch prevents a stronger statement.
- **Specified:** canonical documentation requires the behavior, but this audit does not establish an executable enforcement path.
- **Optional implementation:** executable support exists but is not required for every relevant run.
- **Evidence not established:** implementation existence is not evidence that the strategy produces good investment outcomes.

## Decision path

### Historical information boundary: implemented

`ops/pipeline.py::slice_histories_as_of()` excludes observations with availability timestamps later than the historical `decision_ts`. Rows without a usable availability timestamp are excluded rather than treated as contemporaneously known.

This supports the public statement that historical replay has an explicit anti-lookahead data boundary.

It does **not** establish that every upstream dataset has perfect historical-vintage metadata. That remains an evidence/data-quality question.

### Regime score: implemented

`decisioning/regime_score.py` implements a bounded composite regime score with:

- trend weight: 40%;
- volatility weight: 40%;
- rates/yield-curve weight: 20%;
- VIX-based stress and crisis thresholds;
- EMA smoothing;
- discrete risk-on, neutral, risk-off, and crisis states;
- hysteresis state intended to reduce regime thrashing.

The volatility and yield-curve components are represented directly in executable code.

The canonical trend contract is now represented directly as `(spy_price / spy_ma200) - 1` when a positive 200-day moving average is available. The allocator does not substitute the shorter MA50 signal into `trend_ma200`. If MA200 is missing or invalid, the trend component contributes a neutral `0.0` rather than silently changing methodology.

Focused regression contracts deliberately supply an MA50 and MA200 that point in opposite directions, so a future return to the old MA50 path fails visibly. They also assert the neutral fallback when MA200 is missing or invalid.

### Bucket tilts: implemented

`decisioning/bucket_tilts.py` implements regime-dependent target changes for equity, fixed income, credit, gold, commodities, and cash buckets. The module contains explicit neutral weights, risk-on/risk-off/crisis deltas, bucket bounds, optional smoothing, and renormalization.

The direction described publicly is therefore represented in code:

- stronger risk conditions can increase equity/risk exposures;
- weaker/stressed conditions can increase defensive/cash/gold exposure;
- targets remain bounded.

The existence of these rules does not establish that the tilt magnitudes are optimal.

### Within-bucket scoring: implemented

`decisioning/within_bucket.py` implements:

- 3-month return as the default momentum feature;
- 12-month drawdown as a penalty feature;
- a 60% momentum / 40% drawdown composite score;
- score smoothing;
- softmax conversion from scores to relative weights;
- per-asset caps;
- churn controls.

This is stronger than the earlier manifest state of merely `specified`.

### Policy wiring into the allocator: implemented

The allocator stage in `ops/pipeline.py` constructs the decision engine from `create_default_policy()` rather than relying only on unrelated default constants. It wires policy values into runtime configuration, including:

- bucket bounds;
- safe-sink handling;
- equity, duration, credit, and aggregate risk-asset caps;
- regime smoothing;
- softmax temperature;
- prior-holding preference / trade threshold controls;
- no-trade band;
- turnover cap.

This is important for auditability because the public policy exporter and the executable allocator share the same policy object.

### Portfolio exposure caps: implemented

`decisioning/allocator.py` applies configured equity, credit, duration, and aggregate risk-asset caps at the bucket level. When a cap binds, excess can be routed to the configured safe-sink bucket.

### Turnover controls: implemented

`decisioning/turnover.py` implements:

- an absolute no-trade band;
- a portfolio turnover cap;
- proportional scaling of proposed changes when the cap is exceeded;
- per-asset trade details explaining whether changes were blocked or scaled.

The default policy currently specifies a 1 percentage-point no-trade band and a 35% maximum turnover value. The executable controller computes this measure as the sum of absolute portfolio-weight deltas. The public methodology package now uses the unambiguous label `sum of absolute portfolio-weight changes`; older internal comments that call the same measure `two-way turnover` should eventually be normalized, but they no longer determine the public terminology.

### Fail-closed behavior: implemented at multiple layers

The policy object includes fail-closed integrity behavior and optional hold-prior override support. `ops/pipeline.py` also treats failed stages and integrity aborts as non-actionable.

Public wording should describe the exact circumstances under which `hold_prior` is allowed rather than suggesting that every failure always produces identical behavior.

## Evaluation path

### Core performance and risk metrics: implemented

`backtest/metrics.py` implements total return, CAGR, volatility, downside volatility, Sharpe, Sortino, maximum and average drawdown, positive-period rate, turnover/cost diagnostics, and exposure/concentration metrics.

### Static baselines: implemented

`backtest/baselines.py` implements a static 60/40 baseline and diversified static baseline, plus a simple trend baseline implementation.

### Matched strategy/baseline timing and costs: implemented

`backtest/engine.py` generates one rebalance schedule and uses those same rebalance events for strategy and baseline evaluation. Baselines use the same `ReturnWindowCalculator` and the same configured `CostModel` as the primary strategy.

This supports a public statement that the evaluation engine is designed to avoid giving the strategy easier timing or friction assumptions than its baseline comparisons.

### Trading-cost stress and timing sensitivity: implemented

`backtest/engine.py` runs robustness variants that can include:

- execution timing shifted by minus one trading day;
- execution timing shifted by plus one trading day;
- 2x cost/slippage stress;
- 3x cost/slippage stress.

### Walk-forward windows and embargo: optional implementation

`backtest/walk_forward.py` implements development, out-of-sample, and optional holdout window definitions, including embargo configuration and plan validation.

However, `BacktestConfig.walk_forward` is optional. The engine only creates a walk-forward plan when this configuration is supplied.

Therefore the correct current claim is:

> The backtest framework supports versioned walk-forward plans, out-of-sample windows, holdouts, and embargo checks.

The current code audit does **not** support the stronger claim:

> Every backtest result produced by the repository is necessarily walk-forward validated.

A future publication gate should require walk-forward configuration for any result promoted as decision-useful strategy evidence.

## Evidence state

None of the implementation findings above establish:

- superior expected returns;
- a causal benefit from the regime tilts;
- superiority to passive investing;
- a long live track record;
- suitability for a particular investor;
- independent third-party verification.

Those require evidence artifacts governed by `evidence-standard.md` and the append-only evidence ledger.

## Concrete follow-up from this audit

1. Normalize older internal turnover comments to the public `sum of absolute portfolio-weight changes` terminology when those files are next touched.
2. Add a publication gate requiring walk-forward configuration before a historical simulation can be labeled decision-useful evidence.
3. Keep `generated-policy.json` under a drift test so policy numbers shown publicly cannot silently diverge from `create_default_policy()`.
4. Update `methodology-manifest.json` only when code evidence supports each status change.
