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
