# Rate Models Audit — Round 2

Second pass over all rate models to verify accuracy and detect potential bugs or inconsistencies.

---

## 1. Implementation Verification

### 1.1 BaseRateModel (base.py)

| Check | Status |
|-------|--------|
| Parameter validation (required, optional, unknown) | OK |
| `from_config` TOML `from`→`frm` translation | OK |
| `to_config` `frm`→`from` translation | OK |
| `get_metadata()` returns correct types for deterministic/constant | OK (class attributes) |
| Docstring: class attributes vs @property for metadata | OK (documented) |

### 1.2 Built-in Models (builtin.py, _builtin_impl.py)

| Model | Status | Notes |
|-------|--------|-------|
| Trailing30, Optimistic, Conservative | OK | Fixed presets from rates.py |
| UserRateModel | OK | Values validated for length 4 |
| HistoricalRateModel | OK | `to` defaults to `frm` when absent |
| HistoricalAverageRateModel | OK | Geometric mean from getRatesDistributions |
| GaussianRateModel | OK | rate_seed popped before base validation |
| LognormalRateModel | OK | Arithmetic→log-space conversion correct |
| HistolognormalRateModel | OK | Log-space params from raw history |
| HistogaussianRateModel | OK | Covariance from getRatesDistributions |

**Lognormal edge case**: If user passes `values` with any element ≤ -100 (i.e. mean return ≤ -100%), the formula `(stdev / (1.0 + means))` can produce division by zero when means = -1.0. Unlikely in practice; consider adding validation.

### 1.3 DataFrameRateModel (dataframe.py)

| Check | Status |
|-------|--------|
| deterministic, constant as class attributes | OK |
| n_years example in metadata | OK ("40") |
| Programmatic-only note in description | OK |
| Column validation, row count, offset | OK |
| in_percent handling | OK |
| Redundant validation: `offset < 0` checked after `offset = int(... or 0)` | Minor: offset could be negative if passed as -1; check is correct |

### 1.4 BootstrapSORRateModel (bootstrap_sor.py)

| Check | Status |
|-------|--------|
| IID, block, circular, stationary bootstrap | OK |
| Crisis weighting | OK |
| `_choice` with/without probs | OK |
| Block bootstrap: start_probs from _base_weights | OK |
| Circular: wrap-around indexing | OK |
| Stationary: geometric block length | OK |

**Minor**: `_build_sampling_weights` line 174–175: `weights /= total` followed by `weights /= weights.sum()` is redundant (second is no-op). No functional impact.

### 1.5 VARRateModel (var_model.py)

| Check | Status |
|-------|--------|
| OLS fitting Y = X @ B | OK |
| Spectral shrinkage for stationarity | OK |
| Cholesky of residual covariance | OK |
| Warm start: y_0 = sample mean | OK (reasonable for stationary VAR) |
| Simulation loop | OK |

### 1.6 GARCHDCCRateModel (garch_dcc.py)

| Check | Status |
|-------|--------|
| GARCH(1,1) per asset | OK |
| DCC on standardized residuals | OK |
| Generation: GARCH + DCC updates | OK |
| _normalize_Q, _pd_cholesky | OK |

---

## 2. Data and Column Consistency

| Source | Columns | Match |
|--------|---------|-------|
| rates.csv | year, S&P 500, Bonds Baa, T-Notes, Inflation | OK |
| REQUIRED_RATE_COLUMNS | S&P 500, Bonds Baa, T-Notes, Inflation | OK |
| base.py docstring | S&P 500, Bonds Baa, T-Notes, Inflation | OK |
| README.md (line 51) | Corporate Baa, T Bonds | **Inconsistent** — should be "Bonds Baa, T-Notes" |

---

## 3. Loader and Constants Sync

| Registry | Models | Match |
|----------|--------|-------|
| loader._RATE_MODEL_REGISTRY | 14 models | OK |
| constants.HISTORICAL_RANGE_METHODS | 6 methods | OK |
| constants.STOCHASTIC_METHODS | 7 methods | OK |
| constants.FIXED_TYPE_UI | 5 methods | OK |
| constants.VARYING_TYPE_UI | 8 methods | OK |

---

## 4. Config Flow (plan_bridge → setRates → model)

| Step | Status |
|------|--------|
| plan_bridge pops rate_seed, reproducible_rates before from_config | OK |
| plan.setReproducible(seed=rate_seed) | OK |
| setRates passes seed= to ModelClass | OK |
| HISTORICAL_RANGE_METHODS: injects `to` when absent | OK |
| dataframe: n_years injected by setRates | OK |
| rate_seed in model config: popped by Gaussian/Lognormal/Histo* | OK |

---

## 5. Potential Issues Summary

### Low priority

1. **README.md**: Column names "Corporate Baa, T Bonds" should be "Bonds Baa, T-Notes" for consistency with constants.
2. **BootstrapSORRateModel**: Redundant `weights /= weights.sum()` in _build_sampling_weights (cosmetic).
3. **LognormalRateModel**: Edge case when means ≤ -100% could cause division by zero; consider validating `all(means > -100)` or similar.

### No issues found

- Mathematical correctness of lognormal, GARCH-DCC, VAR
- Seed handling and reproducibility
- TOML serialization (from_config, to_config)
- Plan integration

---

## 6. Recommendations

1. Fix README.md column name inconsistency (Corporate Baa → Bonds Baa, T Bonds → T-Notes).
2. Optionally add validation in LognormalRateModel for means > -100% (or document the constraint).
3. Optionally remove redundant `weights /= weights.sum()` in BootstrapSORRateModel for clarity.
