rotation-risk-audit.md 4.6 KB

Rotation Risk Audit

Audit date: 2026-05-08

Candidate audited: trend_basket-1h-lb720-tr1440-bt2880-rb168-top2-mm0.000-bm0.000-vw336-vc0.0300-lev0.75-exp0.50-vt0.2000

Source row: reports/strategy-expansion/rotation-risk-top.csv, row 1.

Verdict: passed.

The prior audit finding is fixed. trade_stats now uses the same executed-weight basis as the core equity curve, charges internal volatility-target turnover inside active trade segments, and charges the actual close-row turnover when a segment flattens. The generated rotation-risk-* reports are consistent with the repaired implementation.

Recomputed Candidate Metrics

metric reported recomputed status
total_return 1.520238100611786 1.520238100611786 match
annualized_return 0.1564147619507854 0.1564147619507854 match
max_drawdown 0.0771828375342993 0.07718283753429936 match
calmar 2.0265484782323027 2.0265484782323027 match
turnover_per_year 6.024310117968542 6.024310117968542 match
trades 45 45 match
win_rate 0.5777777777777777 0.5777777777777777 match
profit_factor 7.384619055503101 7.384619055503101 match

Data window: 2019-12-25 00:00 UTC to 2026-05-03 15:00 UTC, 55,720 hourly rows, BTC/ETH universe.

First nonzero signal: 2020-04-30 00:00 UTC.

First executed exposure: 2020-04-30 01:00 UTC.

Checks

Trade Statistics Fee Basis

Passed.

scripts/refine_expansion_rotation_risk.py:131 computes trade statistics from executed = weights.shift(1).fillna(0.0), matching the equity curve execution basis at scripts/refine_expansion_rotation_risk.py:123.

For the audited candidate:

  • total executed turnover: 38.31832596610607
  • entry turnover inside trade accounting: 12.914991340355758
  • internal volatility-target turnover inside active segments: 16.27442631232471
  • close-row turnover after actual segment flattening: 9.128908313425487
  • symbol turnover accounted by trade_stats: 38.318325966105995

The current reported profit_factor is 7.384619055503101. Recomputing the old entry/exit-only fee model gives 7.4386055102893875, confirming the regenerated report is not using the prior faulty basis.

No synthetic liquidation fee is added for positions still open at the final data row, because the core equity curve also does not force a final liquidation. This keeps trade_stats aligned with the equity curve.

Generated Report Consistency

Passed.

Batch recomputation of all 30 rows in rotation-risk-top.csv matched the current script output:

  • max trades diff: 0
  • max win_rate diff: 0.0
  • max profit_factor diff: 8.881784197001252e-16
  • max core metric diff: 4.440892098500626e-16
  • max turnover_per_year diff: 8.881784197001252e-16

Report files were written after the script modification:

  • script mtime: 2026-05-08 01:26:30 +0800
  • rotation-risk-total.csv mtime: 2026-05-08 01:33:19 +0800
  • rotation-risk-top.csv mtime: 2026-05-08 01:33:19 +0800
  • rotation-risk-horizons.csv mtime: 2026-05-08 01:33:19 +0800
  • rotation-risk-monthly.csv mtime: 2026-05-08 01:33:19 +0800
  • rotation-risk-report.md mtime: 2026-05-08 01:33:19 +0800

Lookahead

Passed.

Signal construction in scripts/search_expansion_rotation.py:173 uses current and historical closes for momentum, trend, BTC trend, BTC momentum, and BTC volatility. scripts/refine_expansion_rotation_risk.py:123 then applies weights.shift(1) before returns are multiplied in the equity curve.

The volatility-target scale at scripts/refine_expansion_rotation_risk.py:115 uses controlled.shift(1) and realized returns through the current row to size weights for that row; the equity curve executes those weights one row later. Therefore the current bar's return is not applied to a weight that was sized using that same return.

The first nonzero signal appears at 2020-04-30 00:00 UTC; the first executed exposure appears at 2020-04-30 01:00 UTC.

Return Slicing

Passed.

horizon_rows slices the already compounded equity curve and computes each horizon as horizon.iloc[-1] / horizon.iloc[0] - 1, so recent horizons are not reset to initial equity. monthly_rows chains start_equity from the prior month end.

Relevant paths:

  • horizon slicing: scripts/search_expansion_rotation.py:250
  • monthly chaining: scripts/search_expansion_rotation.py:269

Final Decision

Passed.

The repaired trade_stats fee/turnover basis is aligned with the equity curve for executed weights, internal volatility-target turnover, and actual close-row turnover. The core equity curve remains free of same-row lookahead under the audited execution model.