Files
fxhnt/tests/unit/test_tstrend_current_weights.py
jgrusewski 33a80bbbe6 fix(paper-snap): faithful ts-trend current_weights + review fixes
ts-trend current_weights now mirrors TrendRunner.run() per-name vol-scaled
sizing (p = sig * target_vol_daily / rv, long/flat, min_history=130 gate,
conditional gross-cap rescale) instead of forcing equal weight — paper book
magnitudes match the live book. Test asserts inverse-vol ordering, down/flat=0,
short-history exclusion, gross <= cap (binding + non-binding).

- Test the empty-{} sleeve skip path in paper-snapshot integration test.
- DRY: hoist shared _as_of_day/_iso into application/_date_axis.py; import in
  stablecoin_runner, unlock_runner, ts_trend_strategy.
- Doc: note xsfunding current_weights as_of is non-binding (live snapshot);
  note book_allocator.current_sleeve_weights mirrors un-gated inverse-vol path.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 20:30:05 +02:00

77 lines
2.5 KiB
Python

import math
from fxhnt.application.ts_trend_strategy import TsTrendForward
N = 200 # >= min_history (130) + vol_window so every long-enough symbol qualifies
def _smooth_up():
# gentle, low-vol uptrend (small daily increments, no wobble)
return {i: 100.0 + i * 0.5 for i in range(N)}
def _choppy_up():
# same net uptrend but high realized vol: alternating overshoot around the trend line
return {i: 100.0 + i * 0.5 + (3.0 if i % 2 == 0 else -3.0) for i in range(N)}
def _down():
return {i: float(2 * N - i) for i in range(N)} # strictly descending -> -1 signal
def _short_up():
# up-trending but only 50 closes (< min_history) -> must be excluded
return {i: 100.0 + i * 0.5 for i in range(50)}
def _panel():
return {
"SMOOTHUP": _smooth_up(),
"CHOPPYUP": _choppy_up(),
"DOWN": _down(),
"SHORTUP": _short_up(),
}
def test_inverse_vol_smaller_weight_for_higher_vol():
fwd = TsTrendForward(_panel, lookbacks=(20, 60, 120), gross_cap=10.0)
w = fwd.current_weights("epoch:199")
# (a) higher-vol up-trender gets the SMALLER weight (inverse-vol sizing)
assert w["CHOPPYUP"] < w["SMOOTHUP"]
assert w["SMOOTHUP"] > 0
def test_down_and_flat_symbol_zero():
fwd = TsTrendForward(_panel, lookbacks=(20, 60, 120), gross_cap=10.0)
w = fwd.current_weights("epoch:199")
# (b) down/flat symbol carries no weight
assert w.get("DOWN", 0.0) == 0.0
def test_short_history_excluded():
fwd = TsTrendForward(_panel, lookbacks=(20, 60, 120), gross_cap=10.0)
w = fwd.current_weights("epoch:199")
# (c) symbol with < min_history closes is excluded
assert "SHORTUP" not in w
def test_gross_within_cap():
# gross_cap small enough to bind -> gross is rescaled to exactly the cap
fwd = TsTrendForward(_panel, lookbacks=(20, 60, 120), gross_cap=0.01)
w = fwd.current_weights("epoch:199")
gross = sum(abs(v) for v in w.values())
assert gross <= 0.01 + 1e-9
assert gross > 0 # something is held
# gross_cap large enough NOT to bind -> sums to LESS than the cap (vol-scaled, not equal-weight)
fwd2 = TsTrendForward(_panel, lookbacks=(20, 60, 120), gross_cap=1000.0)
w2 = fwd2.current_weights("epoch:199")
gross2 = sum(abs(v) for v in w2.values())
assert gross2 <= 1000.0
assert not math.isclose(gross2, 1000.0)
def test_empty_when_no_history():
fwd = TsTrendForward(_panel, lookbacks=(20, 60, 120), gross_cap=1.0)
assert fwd.current_weights("epoch:5") == {}