254 lines
8.5 KiB
Python
254 lines
8.5 KiB
Python
"""
|
|||
|
|
Test script for Dynamic H1 Bias System.
|
||
|
|
Verifies the multi-indicator scoring logic works correctly.
|
||
|
|
"""
|
||
|
|
|
||
|
|
import sys
|
||
|
|
from pathlib import Path
|
||
|
|
|
||
|
|
# Add project root to path
|
||
|
|
sys.path.insert(0, str(Path(__file__).parent.parent))
|
||
|
|
|
||
|
|
# Fix Windows console encoding
|
||
|
|
import os
|
||
|
|
if os.name == 'nt':
|
||
|
|
os.system('chcp 65001 >nul 2>&1')
|
||
|
|
|
||
|
|
import polars as pl
|
||
|
|
|
||
|
|
|
||
|
|
def test_candle_bias_calculation():
|
||
|
|
"""Test the candle bias counting logic."""
|
||
|
|
print("\n" + "=" * 60)
|
||
|
|
print("Testing Candle Bias Calculation")
|
||
|
|
print("=" * 60)
|
||
|
|
|
||
|
|
# Test case 1: 4 bullish out of 5 (should return +1)
|
||
|
|
df_bullish = pl.DataFrame({
|
||
|
|
"open": [100, 101, 102, 103, 104],
|
||
|
|
"close": [101, 102, 103, 104, 105], # 5 bullish candles
|
||
|
|
})
|
||
|
|
|
||
|
|
bullish_count = sum(1 for row in df_bullish.tail(5).iter_rows(named=True) if row["close"] > row["open"])
|
||
|
|
result = 1 if bullish_count >= 3 else (-1 if (5 - bullish_count) >= 3 else 0)
|
||
|
|
print(f"OK Bullish candles (5/5): result={result} (expected +1)")
|
||
|
|
assert result == 1, "Bullish bias failed"
|
||
|
|
|
||
|
|
# Test case 2: 4 bearish out of 5 (should return -1)
|
||
|
|
df_bearish = pl.DataFrame({
|
||
|
|
"open": [105, 104, 103, 102, 101],
|
||
|
|
"close": [104, 103, 102, 101, 100], # 5 bearish candles
|
||
|
|
})
|
||
|
|
|
||
|
|
bearish_count = sum(1 for row in df_bearish.tail(5).iter_rows(named=True) if row["close"] > row["open"])
|
||
|
|
result = 1 if bearish_count >= 3 else (-1 if (5 - bearish_count) >= 3 else 0)
|
||
|
|
print(f"OK Bearish candles (0/5): result={result} (expected -1)")
|
||
|
|
assert result == -1, "Bearish bias failed"
|
||
|
|
|
||
|
|
# Test case 3: 2 bullish, 3 bearish (should return -1)
|
||
|
|
df_mixed = pl.DataFrame({
|
||
|
|
"open": [100, 101, 102, 103, 104],
|
||
|
|
"close": [99, 100, 103, 102, 105], # 2 bullish, 3 bearish
|
||
|
|
})
|
||
|
|
|
||
|
|
bullish_count = sum(1 for row in df_mixed.tail(5).iter_rows(named=True) if row["close"] > row["open"])
|
||
|
|
result = 1 if bullish_count >= 3 else (-1 if (5 - bullish_count) >= 3 else 0)
|
||
|
|
print(f"OK Mixed candles (2/5 bullish): result={result} (expected -1)")
|
||
|
|
assert result == -1, "Mixed bias failed"
|
||
|
|
|
||
|
|
print("OK All candle bias tests passed!\n")
|
||
|
|
|
||
|
|
|
||
|
|
def test_regime_weights():
|
||
|
|
"""Test regime-based weight selection."""
|
||
|
|
print("=" * 60)
|
||
|
|
print("Testing Regime Weight Selection")
|
||
|
|
print("=" * 60)
|
||
|
|
|
||
|
|
def get_weights(regime):
|
||
|
|
regime_lower = regime.lower()
|
||
|
|
if "low" in regime_lower or "ranging" in regime_lower:
|
||
|
|
return {
|
||
|
|
"ema_trend": 0.15,
|
||
|
|
"ema_cross": 0.15,
|
||
|
|
"rsi": 0.30,
|
||
|
|
"macd": 0.25,
|
||
|
|
"candles": 0.15,
|
||
|
|
}
|
||
|
|
elif "high" in regime_lower or "trending" in regime_lower:
|
||
|
|
return {
|
||
|
|
"ema_trend": 0.30,
|
||
|
|
"ema_cross": 0.25,
|
||
|
|
"rsi": 0.10,
|
||
|
|
"macd": 0.25,
|
||
|
|
"candles": 0.10,
|
||
|
|
}
|
||
|
|
else:
|
||
|
|
return {
|
||
|
|
"ema_trend": 0.25,
|
||
|
|
"ema_cross": 0.20,
|
||
|
|
"rsi": 0.20,
|
||
|
|
"macd": 0.20,
|
||
|
|
"candles": 0.15,
|
||
|
|
}
|
||
|
|
|
||
|
|
# Test low volatility
|
||
|
|
weights_low = get_weights("Low Volatility")
|
||
|
|
assert weights_low["rsi"] == 0.30, "Low vol RSI weight incorrect"
|
||
|
|
assert sum(weights_low.values()) == 1.0, "Low vol weights don't sum to 1.0"
|
||
|
|
print(f"OK Low volatility weights: RSI={weights_low['rsi']}, EMA_trend={weights_low['ema_trend']}")
|
||
|
|
|
||
|
|
# Test high volatility
|
||
|
|
weights_high = get_weights("High Volatility")
|
||
|
|
assert weights_high["ema_trend"] == 0.30, "High vol EMA trend weight incorrect"
|
||
|
|
assert sum(weights_high.values()) == 1.0, "High vol weights don't sum to 1.0"
|
||
|
|
print(f"OK High volatility weights: EMA_trend={weights_high['ema_trend']}, RSI={weights_high['rsi']}")
|
||
|
|
|
||
|
|
# Test medium volatility
|
||
|
|
weights_med = get_weights("Medium Volatility")
|
||
|
|
assert sum(weights_med.values()) == 1.0, "Med vol weights don't sum to 1.0"
|
||
|
|
print(f"OK Medium volatility weights: balanced ({weights_med['ema_trend']}, {weights_med['rsi']})")
|
||
|
|
|
||
|
|
print("OK All regime weight tests passed!\n")
|
||
|
|
|
||
|
|
|
||
|
|
def test_scoring_logic():
|
||
|
|
"""Test the weighted scoring calculation."""
|
||
|
|
print("=" * 60)
|
||
|
|
print("Testing Weighted Scoring Logic")
|
||
|
|
print("=" * 60)
|
||
|
|
|
||
|
|
# Test case 1: All bullish signals in high volatility
|
||
|
|
signals_bull = {
|
||
|
|
"ema_trend": 1,
|
||
|
|
"ema_cross": 1,
|
||
|
|
"rsi": 1,
|
||
|
|
"macd": 1,
|
||
|
|
"candles": 1,
|
||
|
|
}
|
||
|
|
weights_high = {
|
||
|
|
"ema_trend": 0.30,
|
||
|
|
"ema_cross": 0.25,
|
||
|
|
"rsi": 0.10,
|
||
|
|
"macd": 0.25,
|
||
|
|
"candles": 0.10,
|
||
|
|
}
|
||
|
|
score = sum(signals_bull[k] * weights_high[k] for k in signals_bull)
|
||
|
|
bias = "BULLISH" if score >= 0.3 else ("BEARISH" if score <= -0.3 else "NEUTRAL")
|
||
|
|
print(f"OK All bullish + high vol: score={score:.2f}, bias={bias} (expected BULLISH)")
|
||
|
|
assert score == 1.0, "All bullish score should be 1.0"
|
||
|
|
assert bias == "BULLISH", "All bullish bias should be BULLISH"
|
||
|
|
|
||
|
|
# Test case 2: All bearish signals in low volatility
|
||
|
|
signals_bear = {k: -1 for k in signals_bull}
|
||
|
|
weights_low = {
|
||
|
|
"ema_trend": 0.15,
|
||
|
|
"ema_cross": 0.15,
|
||
|
|
"rsi": 0.30,
|
||
|
|
"macd": 0.25,
|
||
|
|
"candles": 0.15,
|
||
|
|
}
|
||
|
|
score = sum(signals_bear[k] * weights_low[k] for k in signals_bear)
|
||
|
|
bias = "BULLISH" if score >= 0.3 else ("BEARISH" if score <= -0.3 else "NEUTRAL")
|
||
|
|
print(f"OK All bearish + low vol: score={score:.2f}, bias={bias} (expected BEARISH)")
|
||
|
|
assert score == -1.0, "All bearish score should be -1.0"
|
||
|
|
assert bias == "BEARISH", "All bearish bias should be BEARISH"
|
||
|
|
|
||
|
|
# Test case 3: Mixed signals (should be near neutral)
|
||
|
|
signals_mixed = {
|
||
|
|
"ema_trend": 1,
|
||
|
|
"ema_cross": -1,
|
||
|
|
"rsi": 0,
|
||
|
|
"macd": 1,
|
||
|
|
"candles": -1,
|
||
|
|
}
|
||
|
|
weights_med = {
|
||
|
|
"ema_trend": 0.25,
|
||
|
|
"ema_cross": 0.20,
|
||
|
|
"rsi": 0.20,
|
||
|
|
"macd": 0.20,
|
||
|
|
"candles": 0.15,
|
||
|
|
}
|
||
|
|
score = sum(signals_mixed[k] * weights_med[k] for k in signals_mixed)
|
||
|
|
bias = "BULLISH" if score >= 0.3 else ("BEARISH" if score <= -0.3 else "NEUTRAL")
|
||
|
|
print(f"OK Mixed signals + med vol: score={score:.2f}, bias={bias} (expected NEUTRAL)")
|
||
|
|
assert -0.3 < score < 0.3, "Mixed signals should be in neutral zone"
|
||
|
|
assert bias == "NEUTRAL", "Mixed signals bias should be NEUTRAL"
|
||
|
|
|
||
|
|
# Test case 4: Key test from plan — Price above EMA but bearish RSI+MACD+candles
|
||
|
|
signals_key = {
|
||
|
|
"ema_trend": 1, # Price > EMA21 (old system would say BULLISH)
|
||
|
|
"ema_cross": 1, # EMA9 > EMA21
|
||
|
|
"rsi": -1, # RSI < 45 (bearish)
|
||
|
|
"macd": -1, # MACD bearish
|
||
|
|
"candles": -1, # Bearish candles
|
||
|
|
}
|
||
|
|
# Use high volatility weights (trending)
|
||
|
|
score = sum(signals_key[k] * weights_high[k] for k in signals_key)
|
||
|
|
bias = "BULLISH" if score >= 0.3 else ("BEARISH" if score <= -0.3 else "NEUTRAL")
|
||
|
|
print(f"OK Price>EMA but bearish momentum: score={score:.2f}, bias={bias}")
|
||
|
|
print(f" -> Old system would say BULLISH, new system says {bias}")
|
||
|
|
|
||
|
|
print("OK All scoring logic tests passed!\n")
|
||
|
|
|
||
|
|
|
||
|
|
def test_strength_calculation():
|
||
|
|
"""Test bias strength categorization."""
|
||
|
|
print("=" * 60)
|
||
|
|
print("Testing Bias Strength Calculation")
|
||
|
|
print("=" * 60)
|
||
|
|
|
||
|
|
test_cases = [
|
||
|
|
(0.85, "strong"),
|
||
|
|
(0.65, "moderate"),
|
||
|
|
(0.45, "weak"),
|
||
|
|
(0.25, "weak"),
|
||
|
|
(-0.75, "strong"),
|
||
|
|
(-0.55, "moderate"),
|
||
|
|
(-0.35, "weak"),
|
||
|
|
]
|
||
|
|
|
||
|
|
for score, expected_strength in test_cases:
|
||
|
|
abs_score = abs(score)
|
||
|
|
if abs_score >= 0.7:
|
||
|
|
strength = "strong"
|
||
|
|
elif abs_score >= 0.5:
|
||
|
|
strength = "moderate"
|
||
|
|
else:
|
||
|
|
strength = "weak"
|
||
|
|
print(f"OK Score {score:+.2f} -> {strength} (expected: {expected_strength})")
|
||
|
|
assert strength == expected_strength, f"Strength mismatch for score {score}"
|
||
|
|
|
||
|
|
print("OK All strength tests passed!\n")
|
||
|
|
|
||
|
|
|
||
|
|
def run_all_tests():
|
||
|
|
"""Run all H1 dynamic bias tests."""
|
||
|
|
print("\n" + "=" * 60)
|
||
|
|
print("DYNAMIC H1 BIAS SYSTEM - TEST SUITE")
|
||
|
|
print("=" * 60)
|
||
|
|
|
||
|
|
try:
|
||
|
|
test_candle_bias_calculation()
|
||
|
|
test_regime_weights()
|
||
|
|
test_scoring_logic()
|
||
|
|
test_strength_calculation()
|
||
|
|
|
||
|
|
print("=" * 60)
|
||
|
|
print("OK ALL TESTS PASSED!")
|
||
|
|
print("=" * 60)
|
||
|
|
return True
|
||
|
|
except AssertionError as e:
|
||
|
|
print(f"\nFAIL TEST FAILED: {e}")
|
||
|
|
return False
|
||
|
|
except Exception as e:
|
||
|
|
print(f"\nFAIL ERROR: {e}")
|
||
|
|
import traceback
|
||
|
|
traceback.print_exc()
|
||
|
|
return False
|
||
|
|
|
||
|
|
|
||
|
|
if __name__ == "__main__":
|
||
|
|
success = run_all_tests()
|
||
|
|
sys.exit(0 if success else 1)
|