wbt 0.4.0

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

> Release date: 2026-06-25
> Crate tag: `crate-v0.4.0` · Python tag: `v0.4.0`

## Summary

MINOR release. **BREAKING** change to `is_good_strategy`'s decision rules and
return-dict schema (SKZ-9), plus a fuller strategy-verdict section in the HTML
report covering both `history` and `recent` modes.

## Breaking: `is_good_strategy` decision rules

### `history` mode

- Default `min_year_days` raised **120 → 200**.
- Each complete calendar year (≥ `min_year_days`) now passes if **any one** of
  three holds: absolute return > 0, **or** vol-normalized long excess > 0, **or**
  that year's long-excess max drawdown < `max_dd_threshold`. All complete years
  passing ⇒ `is_good`.
- The previous full-sample excess-drawdown hard gate is **removed**; drawdown is
  now computed per-year and folded into the yearly OR.
- When alpha is degenerate, the two alpha-derived branches do not participate and
  `is_good` is forced `false` (unchanged contract).

### `recent` mode

- The tail window passes the return side if **any one** of three holds: absolute
  return > 0, **or** vol-normalized long excess > 0, **or** recent long-excess max
  drawdown < `max_dd_threshold`.
- The only retained AND hard gate: recent max drawdown **strictly less than** the
  history max drawdown computed after excluding the recent window.

### Return-dict schema changes

- `history`: removed `history_alpha_max_drawdown` and `cond_history_dd_passed`.
  Each `yearly_metrics` entry now carries `alpha_max_drawdown` (that year's excess
  drawdown).
- `recent`: key names unchanged, but `cond_recent_dd_passed` now means only
  "strictly below history drawdown", and the `< threshold` check moved into the
  `cond_recent_return_passed` OR.

### Downstream (skz) sync

`describe_review_failure` `reason` text changed; any reference to the removed
`history_alpha_max_drawdown` / `cond_history_dd_passed` keys must move to
`yearly_metrics[].alpha_max_drawdown` or be dropped. The `is_good` contract itself
is unchanged.

## Report: full strategy verdict (history + recent)

`generate_backtest_report` previously rendered only the `history` verdict. The
"策略审核" tab now shows both a `history` and a `recent` verdict card, plus a
collapsible "查看详细判定信息" panel (native `<details>`) listing every decision
field for both modes.

## Tests

- `src/core/is_good_strategy.rs`: rewritten judge tests covering each of the three
  history OR branches in isolation, the per-year AND, the recent OR, and the
  strict-history equality boundary (equal ⇒ False).
- `python/tests/test_is_good_strategy.py`: updated key set; per-year drawdown
  assertion.