Files
fxhnt/tests/unit/test_vol_target_leverage.py
jgrusewski 9b88805984 feat(paper): vol_target_leverage warmup (no leverage until vol_lookback obs)
Add a `warmup_obs: int = _VOL_LOOKBACK` (60) keyword to vol_target_leverage
and change the early guard from `len(days) < 2` to `len(days) < warmup_obs`.
Returns 1.0 (unlevered) until there are >= warmup_obs book-return observations,
so a thin cold-start window (tiny, unreliable vol estimate) no longer
over-levers. The pv<=1e-12 / non-finite guard is unchanged.

Existing leverage tests use 60-80 obs series so they still lever (verified).

Per the foundation audit (D3, cold-start over-leverage).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-22 16:59:24 +02:00

44 lines
1.9 KiB
Python

import math
from fxhnt.application.paper_book import vol_target_leverage
def _series(vals, start=1):
return {start + i: v for i, v in enumerate(vals)}
def test_no_history_cold_start_is_unlevered():
assert vol_target_leverage({"a": {}}, {"a": {}}) == 1.0 # weights, returns both empty -> 1.0
def test_lower_vol_gives_more_leverage():
lo = vol_target_leverage({"a": 1.0}, {"a": _series([0.002, -0.002] * 40)})
hi = vol_target_leverage({"a": 1.0}, {"a": _series([0.02, -0.02] * 40)})
assert lo > hi
def test_capped_at_max_leverage():
tiny = vol_target_leverage({"a": 1.0}, {"a": _series([1e-6, -1e-6] * 40)}, max_leverage=3.0)
assert tiny == 3.0
def test_formula_matches_book_allocator():
import numpy as np
r = _series([0.01, -0.01, 0.005, -0.005] * 20)
w = {"a": 1.0}
lev = vol_target_leverage(w, {"a": r}, target_vol=0.12, kelly_fraction=0.5,
periods_per_year=365, vol_lookback=60, max_leverage=3.0)
vals = [r[d] for d in sorted(r)][-60:]
pv = float(np.std(vals)); tvd = 0.12 / math.sqrt(365)
assert math.isclose(lev, min(3.0, 0.5 * tvd / pv), rel_tol=1e-9)
def test_nan_in_window_falls_back_to_unlevered():
# a NaN return must NOT pin leverage to max; fall back to 1.0 (unlevered)
r = {1: 0.01, 2: float("nan"), 3: -0.01, 4: 0.02, 5: -0.02}
lev = vol_target_leverage({"a": 1.0}, {"a": r})
assert lev == 1.0
def test_warmup_thin_history_is_unlevered():
# 30 obs (< warmup_obs default 60): cold-start, no leverage regardless of low vol
thin = vol_target_leverage({"a": 1.0}, {"a": _series([1e-6, -1e-6] * 15)}, max_leverage=3.0)
assert thin == 1.0
def test_levers_after_warmup():
# 70 obs (>= warmup_obs default 60) low-vol series: levers up past 1.0 after warmup
warm = vol_target_leverage({"a": 1.0}, {"a": _series([1e-6, -1e-6] * 35)}, max_leverage=3.0)
assert warm > 1.0