"""
Unit tests for RegimeDetector
Tests automatic market regime detection
"""
import pytest
import sys
from pathlib import Path
# Add parent directory to path
sys.path.insert(0, str(Path(__file__).parent.parent))
from mcp_server.regime_detector import RegimeDetector
class TestRegimeDetector:
"""Test suite for RegimeDetector"""
def test_strong_bull_detection(self):
"""Test strong bull regime detection"""
btc_analysis = {
"composite_signal": {
"signal": "STRONG_BUY",
"confidence": 0.75,
"score": 15 # High positive score
},
"timeframes": {
"4h": {
"indicators": {
"adx": {"adx": 32},
"atr": {"atr_14": 500}
},
"trend": {
"direction": "uptrend",
"strength": "very_strong"
}
},
"1d": {
"current_price": 50000,
"trend": {
"direction": "uptrend",
"strength": "strong"
}
}
}
}
regime = RegimeDetector.detect(btc_analysis)
# Check structure
assert "type" in regime
assert "confidence" in regime
assert "metrics" in regime
assert "description" in regime
assert "trading_implications" in regime
# For strong bull conditions
# Note: Weekly change is heuristic-based, so can't guarantee exact regime
# But should not error and should return valid structure
assert regime["type"] in ["strong_bull", "sideways", "uncertain"]
assert 0 <= regime["confidence"] <= 1
def test_strong_bear_detection(self):
"""Test strong bear regime detection"""
btc_analysis = {
"composite_signal": {
"signal": "STRONG_SELL",
"confidence": 0.80,
"score": -15
},
"timeframes": {
"4h": {
"indicators": {
"adx": {"adx": 35},
"atr": {"atr_14": 600}
},
"trend": {
"direction": "downtrend",
"strength": "very_strong"
}
},
"1d": {
"current_price": 45000,
"trend": {
"direction": "downtrend",
"strength": "strong"
}
}
}
}
regime = RegimeDetector.detect(btc_analysis)
assert regime["type"] in ["strong_bear", "sideways", "uncertain"]
assert "confidence" in regime
assert regime["metrics"]["adx"] == 35
def test_sideways_detection(self):
"""Test sideways regime detection"""
btc_analysis = {
"composite_signal": {
"signal": "HOLD",
"confidence": 0.50,
"score": 0
},
"timeframes": {
"4h": {
"indicators": {
"adx": {"adx": 15}, # Low ADX = no trend
"atr": {"atr_14": 200}
},
"trend": {
"direction": "sideways",
"strength": "weak"
}
},
"1d": {
"current_price": 48000,
"trend": {
"direction": "sideways",
"strength": "weak"
}
}
}
}
regime = RegimeDetector.detect(btc_analysis)
# With low ADX and sideways trend, should detect sideways
assert regime["type"] in ["sideways", "uncertain"]
assert regime["metrics"]["adx"] == 15
def test_uncertain_detection(self):
"""Test uncertain regime detection for mixed signals"""
btc_analysis = {
"composite_signal": {
"signal": "HOLD",
"confidence": 0.45,
"score": 3
},
"timeframes": {
"4h": {
"indicators": {
"adx": {"adx": 22}, # Medium ADX
"atr": {"atr_14": 300}
},
"trend": {
"direction": "sideways",
"strength": "moderate"
}
},
"1d": {}
}
}
regime = RegimeDetector.detect(btc_analysis)
# Should be uncertain or sideways
assert regime["type"] in ["uncertain", "sideways"]
def test_regime_metrics_extraction(self):
"""Test that all metrics are properly extracted"""
btc_analysis = {
"composite_signal": {
"signal": "BUY",
"confidence": 0.65,
"score": 8
},
"timeframes": {
"4h": {
"indicators": {
"adx": {"adx": 28},
"atr": {"atr_14": 400}
},
"trend": {
"direction": "uptrend",
"strength": "strong"
}
},
"1d": {}
}
}
regime = RegimeDetector.detect(btc_analysis)
metrics = regime["metrics"]
assert "btc_weekly_change_pct" in metrics
assert "adx" in metrics
assert "signal" in metrics
assert "signal_confidence" in metrics
assert "volatility" in metrics
assert "atr_14" in metrics
assert metrics["adx"] == 28
assert metrics["signal"] == "BUY"
assert metrics["signal_confidence"] == 0.65
def test_volatility_classification(self):
"""Test volatility classification logic"""
# High volatility (ATR >1.5x average)
vol = RegimeDetector._classify_volatility(160, 100)
assert vol == "high"
# Exactly 1.5x is elevated (boundary case)
vol = RegimeDetector._classify_volatility(150, 100)
assert vol == "elevated"
# Elevated volatility
vol = RegimeDetector._classify_volatility(125, 100)
assert vol == "elevated"
# Normal volatility
vol = RegimeDetector._classify_volatility(100, 100)
assert vol == "normal"
# Low volatility
vol = RegimeDetector._classify_volatility(85, 100)
assert vol == "low"
# Very low volatility
vol = RegimeDetector._classify_volatility(65, 100)
assert vol == "very_low"
def test_error_handling(self):
"""Test graceful error handling with bad input"""
# Empty analysis - should gracefully fall back to uncertain
regime = RegimeDetector.detect({})
assert regime["type"] == "uncertain"
# Note: Graceful fallback doesn't add "error" field, it's a feature!
# Missing critical fields - still returns uncertain
regime = RegimeDetector.detect({"composite_signal": {}})
assert regime["type"] == "uncertain"
assert regime["confidence"] >= 0.30 # Has some confidence
def test_confidence_ranges(self):
"""Test confidence values are in valid range"""
test_cases = [
{
"composite_signal": {"signal": "STRONG_BUY", "confidence": 0.75, "score": 15},
"timeframes": {"4h": {"indicators": {"adx": {"adx": 32}, "atr": {"atr_14": 500}}, "trend": {"direction": "uptrend", "strength": "very_strong"}}}
},
{
"composite_signal": {"signal": "HOLD", "confidence": 0.50, "score": 0},
"timeframes": {"4h": {"indicators": {"adx": {"adx": 15}, "atr": {"atr_14": 200}}, "trend": {"direction": "sideways", "strength": "weak"}}}
}
]
for analysis in test_cases:
regime = RegimeDetector.detect(analysis)
assert 0 <= regime["confidence"] <= 1
assert regime["confidence"] >= 0.30 # Minimum confidence
if __name__ == "__main__":
pytest.main([__file__, "-v"])