🧪 Fix VIX trend logic and add extensive tests for macro regime short history edge cases
- Fixed `_signal_vix_trend` to correctly return neutral for insufficient history (`< 21`). - Added `test_short_history_is_neutral` to `TestSignalVixTrend`. - Extended coverage for short history and edge cases in `TestSignalCreditSpread`, `TestSignalYieldCurve`, `TestSignalMarketBreadth`, and `TestSignalSectorRotation`. Co-authored-by: aguzererler <6199053+aguzererler@users.noreply.github.com>
This commit is contained in:
parent
5799bb3f00
commit
177d35ede5
|
|
@ -86,6 +86,12 @@ class TestSignalVixTrend:
|
|||
score, desc = self.fn(vix)
|
||||
assert score == 0
|
||||
|
||||
def test_short_history_is_neutral(self):
|
||||
vix = _make_series([20.0] * 20)
|
||||
score, desc = self.fn(vix)
|
||||
assert score == 0
|
||||
assert "insufficient history" in desc
|
||||
|
||||
def test_none_series_is_neutral(self):
|
||||
score, desc = self.fn(None)
|
||||
assert score == 0
|
||||
|
|
@ -110,6 +116,42 @@ class TestSignalCreditSpread:
|
|||
score, desc = self.fn(hyg, lqd)
|
||||
assert score == -1
|
||||
|
||||
def test_short_history_is_neutral(self):
|
||||
hyg = _trending_series(80, 85, 21)
|
||||
lqd = _flat_series(100, 21)
|
||||
score, desc = self.fn(hyg, lqd)
|
||||
assert score == 0
|
||||
assert "insufficient history" in desc
|
||||
|
||||
def test_none_data_is_neutral(self):
|
||||
score, _ = self.fn(None, None)
|
||||
assert score == 0
|
||||
|
||||
|
||||
class TestSignalYieldCurve:
|
||||
def setup_method(self):
|
||||
from tradingagents.dataflows.macro_regime import _signal_yield_curve
|
||||
self.fn = _signal_yield_curve
|
||||
|
||||
def test_flight_to_safety_is_risk_off(self):
|
||||
tlt = _trending_series(100, 110, 30)
|
||||
shy = _flat_series(100, 30)
|
||||
score, desc = self.fn(tlt, shy)
|
||||
assert score == -1
|
||||
|
||||
def test_risk_appetite_is_risk_on(self):
|
||||
tlt = _trending_series(110, 100, 30)
|
||||
shy = _flat_series(100, 30)
|
||||
score, desc = self.fn(tlt, shy)
|
||||
assert score == 1
|
||||
|
||||
def test_short_history_is_neutral(self):
|
||||
tlt = _trending_series(100, 110, 21)
|
||||
shy = _flat_series(100, 21)
|
||||
score, desc = self.fn(tlt, shy)
|
||||
assert score == 0
|
||||
assert "insufficient history" in desc
|
||||
|
||||
def test_none_data_is_neutral(self):
|
||||
score, _ = self.fn(None, None)
|
||||
assert score == 0
|
||||
|
|
@ -140,6 +182,45 @@ class TestSignalMarketBreadth:
|
|||
score, _ = self.fn(spx)
|
||||
assert score == 0 # < 200 points for SMA200
|
||||
|
||||
def test_short_history_is_neutral(self):
|
||||
spx = _make_series([5000.0] * 199)
|
||||
score, desc = self.fn(spx)
|
||||
assert score == 0
|
||||
assert "insufficient history" in desc
|
||||
|
||||
def test_none_data_is_neutral(self):
|
||||
score, _ = self.fn(None)
|
||||
assert score == 0
|
||||
|
||||
|
||||
class TestSignalSectorRotation:
|
||||
def setup_method(self):
|
||||
from tradingagents.dataflows.macro_regime import _signal_sector_rotation
|
||||
self.fn = _signal_sector_rotation
|
||||
|
||||
def test_defensives_leading_is_risk_off(self):
|
||||
defensives = {"XLU": _trending_series(100, 110, 30)}
|
||||
cyclicals = {"XLY": _flat_series(100, 30)}
|
||||
score, desc = self.fn(defensives, cyclicals)
|
||||
assert score == -1
|
||||
|
||||
def test_cyclicals_leading_is_risk_on(self):
|
||||
defensives = {"XLU": _flat_series(100, 30)}
|
||||
cyclicals = {"XLY": _trending_series(100, 110, 30)}
|
||||
score, desc = self.fn(defensives, cyclicals)
|
||||
assert score == 1
|
||||
|
||||
def test_short_history_is_neutral(self):
|
||||
defensives = {"XLU": _trending_series(100, 110, 21)}
|
||||
cyclicals = {"XLY": _flat_series(100, 21)}
|
||||
score, desc = self.fn(defensives, cyclicals)
|
||||
assert score == 0
|
||||
assert "unavailable" in desc
|
||||
|
||||
def test_empty_dicts_is_neutral(self):
|
||||
score, desc = self.fn({}, {})
|
||||
assert score == 0
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Classify macro regime
|
||||
|
|
|
|||
|
|
@ -92,8 +92,8 @@ def _signal_vix_level(vix_price: Optional[float]) -> tuple[int, str]:
|
|||
|
||||
def _signal_vix_trend(vix_series: Optional[pd.Series]) -> tuple[int, str]:
|
||||
"""VIX 5-day SMA vs 20-day SMA: rising VIX = risk-off."""
|
||||
if vix_series is None:
|
||||
return 0, "VIX trend: unavailable (neutral)"
|
||||
if vix_series is None or len(vix_series) < 21:
|
||||
return 0, "VIX trend: insufficient history (neutral)"
|
||||
sma5 = _sma(vix_series, 5)
|
||||
sma20 = _sma(vix_series, 20)
|
||||
if sma5 is None or sma20 is None:
|
||||
|
|
|
|||
Loading…
Reference in New Issue