wbt 0.6.0

Weight-based backtesting engine for quantitative trading
# wbt v0.5.0

> Release date: 2026-07-15
> Crate tag: `crate-v0.5.0` · Python tag: `v0.5.0`

## Summary

MINOR release. **BREAKING CHANGE** (SKZ-195): all return / net-value accumulation
inside `wbt` is unified to **simple interest** (`Σr`). Previously the yearly
return and the `is_good_strategy` yearly/recent metrics used **compound**
(`∏(1+r)-1`) while the main stats (absolute return, max drawdown, equity curve)
used simple interest — the two conventions were mixed within a single backtest.
After this release there is **no compound (`∏(1+r)` / net-value) accumulation
anywhere in the framework**; everything is single-interest, consistent with
`daily_performance`'s absolute return and the `np.cumsum` equity curve.

Public API signatures are **unchanged**; only the numeric convention changes.

## BREAKING CHANGE: compound → simple interest

### `WeightBacktest.yearly_return()`

- Yearly return per `(year, symbol)` changed from compound `∏(1+r)-1` to simple
  `Σr`. Schema (`year, symbol, return`) is unchanged; values differ.

  Old → new (example, daily returns `[0.10, 0.10]` in one year):
  - old (compound): `1.10 * 1.10 - 1 = 0.21`
  - new (simple):   `0.10 + 0.10 = 0.20`

### `is_good_strategy()` (both `history` and `recent` modes)

- `abs_return` / `alpha_return` (per-year, in `yearly_metrics`) and
  `recent_abs_return` / `recent_alpha_return`: compound `∏(1+r)-1` → simple `Σr`.
- `alpha_max_drawdown`, `history_alpha_max_drawdown`,
  `recent_alpha_max_drawdown`, and `history_alpha_max_drawdown_excl_recent`:
  changed from compound net-value drawdown (`nav *= 1+r`, `dd = (peak-nav)/peak`)
  to **simple cumsum-underwater** (`cum += r`, `dd = peak - cum`), identical in
  semantics to `daily_performance::calc_underwater`.
- Note: simple-interest drawdown is an **absolute value in return space** and has
  **no 1.0 upper bound** (unlike a compound net-value ratio). Callers comparing
  drawdown against thresholds should keep this in mind; a threshold of `1.0` no
  longer means "effectively disabled".
- Return-dict **keys are unchanged**; only the numeric convention of the values
  above changes. Verdicts near thresholds may therefore differ from v0.4.x.

### Migration

- If you persisted or charted `yearly_return()` / `is_good_strategy()` numbers
  from v0.4.x, recompute them under v0.5.0 — old compound values are not
  comparable to new simple values.
- Any downstream (skz) gate that hard-coded a compound-scale drawdown threshold
  (e.g. `max_alpha_dd_threshold` assuming a ≤1.0 ratio) should re-tune to the
  return-space scale.

## Removed: vista comparison artifacts

The library no longer relates to `vista`. Removed:
- `python/scripts/compare_yearly_return_with_vista.py`
- `python/tests/test_compare_yearly_return_with_vista_script.py`

These were comparison-only tooling and not part of the public API.

## Tests

- `src/core/yearly_return.rs`, `src/core/is_good_strategy.rs`: unit tests updated
  to simple-interest expected values (`known_curve` drawdown `0.15`,
  cum-zero-with-real-dd `0.5`, yearly/recent `Σr`). `history_year_passes_via_abs_return_only`
  raises its `max_alpha_dd_threshold` bypass `1.0 → 1e9` (test sentinel only; the
  `judge` gate logic is unchanged).
- `python/tests/test_yearly_return.py`: expected formula updated to `sum()`.
- Local: `cargo fmt` / `cargo clippy -D warnings` clean, `cargo test --lib`
  220 passed; `pytest` 289 passed. Full CI (ubuntu/macos/windows × py3.10–3.13)
  green on `main`.

## §4 LLM full-repo review

- Scope: full diff `crate-v0.4.3..HEAD` (`src/core/{yearly_return,is_good_strategy}.rs`,
  `python/wbt/backtest.py`, tests, vista removals), plus cold-read of
  `daily_performance.rs`.
- Model: independent review agent.
- Conclusion: **APPROVE for release** — no blocking findings. Verified:
  (1) `local_max_drawdown_abs` exactly mirrors `calc_underwater` (same
  `NEG_INFINITY` peak init so day-0 never registers drawdown; `max(peak-cum) ≡
  max(-underwater)`; empty→0; non-finite→NaN preserved); (2) no compound
  `∏(1+r)` / `nav*=1+r` / `.prod()` accumulation remains in `src/` (only
  historical-note doc comments mention it); (3) the `1.0→1e9` change is a test
  sentinel, production `judge` logic unchanged; (4) no off-by-one / sign / empty /
  NaN regressions in the changed functions.
- Known issues: none.