Add EUR/USD support with modular per-pair signal producer pattern
- Register EUR/USD in config (pip 0.0001, EurUsdStrategyConfig) - Create pair_signals/ module: registry + dispatcher with per-pair config factories (xauusd.py, eurusd.py) for easy add/remove - EUR/USD gets forex-tuned regime weights, London+NY session hours, XAU strategy gates disabled - Add defense-in-depth display_name guards in signal_aggregator - Rename telegram_bot.py → testing_telegram_bot.py with separate bot token, both pairs with dedicated button rows - Add EUR/USD to news engine affected pairs map - 9 new tests (146 total, all passing) - Zero changes to engine.py, data_provider.py, main.py, backtest/ Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
+115
-10
@@ -88,8 +88,8 @@ print("\n═══ Module 1: config.py ═══")
|
||||
from config import (
|
||||
Timeframe, SignalStrength, MarketSession,
|
||||
PlatformConfig, TechnicalConfig, SignalConfig, NewsConfig,
|
||||
FilterConfig, XauStrategyConfig,
|
||||
XAUUSD, PAIR_INFO,
|
||||
FilterConfig, XauStrategyConfig, EurUsdStrategyConfig,
|
||||
XAUUSD, EURUSD, PAIR_INFO,
|
||||
)
|
||||
|
||||
|
||||
@@ -1313,12 +1313,12 @@ def _():
|
||||
|
||||
|
||||
# ══════════════════════════════════════════════════════════════════════
|
||||
# MODULE 7: telegram_bot.py
|
||||
# MODULE 7: testing_telegram_bot.py
|
||||
# ══════════════════════════════════════════════════════════════════════
|
||||
|
||||
print("\n═══ Module 7: telegram_bot.py ═══")
|
||||
print("\n═══ Module 7: testing_telegram_bot.py ═══")
|
||||
|
||||
from telegram_bot import (
|
||||
from testing_telegram_bot import (
|
||||
_escape_md, _signal_label, _mtf_line,
|
||||
format_signal_message, _format_scan_summary,
|
||||
_seconds_until_boundary, _check_level_breach,
|
||||
@@ -1491,7 +1491,7 @@ def _():
|
||||
|
||||
@test("SCAN_BUTTONS and SCAN_LOOKUP consistency")
|
||||
def _():
|
||||
assert len(SCAN_BUTTONS) == 5 # 4 TFs + "Scan All"
|
||||
assert len(SCAN_BUTTONS) == 10 # 5 per pair (4 TFs + All) × 2 pairs
|
||||
for btn in SCAN_BUTTONS:
|
||||
assert btn["cb"] in SCAN_LOOKUP
|
||||
assert "symbol" in btn
|
||||
@@ -1531,12 +1531,12 @@ def _():
|
||||
|
||||
@test("_format_stats_dashboard returns waiting message when empty")
|
||||
def _():
|
||||
import telegram_bot
|
||||
old_stats = telegram_bot._signal_stats.copy()
|
||||
telegram_bot._signal_stats.clear()
|
||||
import testing_telegram_bot
|
||||
old_stats = testing_telegram_bot._signal_stats.copy()
|
||||
testing_telegram_bot._signal_stats.clear()
|
||||
text = _format_stats_dashboard()
|
||||
assert "Waiting" in text or "waiting" in text.lower()
|
||||
telegram_bot._signal_stats.update(old_stats)
|
||||
testing_telegram_bot._signal_stats.update(old_stats)
|
||||
|
||||
|
||||
# ══════════════════════════════════════════════════════════════════════
|
||||
@@ -1653,6 +1653,111 @@ def _():
|
||||
assert r1.market_regime == r2.market_regime
|
||||
|
||||
|
||||
# ══════════════════════════════════════════════════════════════════════
|
||||
# MODULE 9: EUR/USD pair support
|
||||
# ══════════════════════════════════════════════════════════════════════
|
||||
|
||||
print("\n═══ Module 9: EUR/USD pair support ═══")
|
||||
|
||||
|
||||
@test("PAIR_INFO includes EUR/USD with correct pip_value")
|
||||
def _():
|
||||
assert EURUSD in PAIR_INFO
|
||||
assert PAIR_INFO[EURUSD]["pip_value"] == 0.0001
|
||||
assert PAIR_INFO[EURUSD]["is_jpy_pair"] is False
|
||||
assert PAIR_INFO[EURUSD]["display"] == "EUR/USD"
|
||||
|
||||
|
||||
@test("EurUsdStrategyConfig defaults are sensible")
|
||||
def _():
|
||||
cfg = EurUsdStrategyConfig()
|
||||
assert cfg.enabled is True
|
||||
assert 0 in cfg.blocked_hours_utc
|
||||
assert 5 not in cfg.allowed_weekdays # No Saturday
|
||||
assert 6 not in cfg.allowed_weekdays # No Sunday
|
||||
assert cfg.min_atr_threshold == 0.0003
|
||||
|
||||
|
||||
@test("PlatformConfig has eur_strategy field")
|
||||
def _():
|
||||
config = PlatformConfig()
|
||||
assert hasattr(config, "eur_strategy")
|
||||
assert isinstance(config.eur_strategy, EurUsdStrategyConfig)
|
||||
|
||||
|
||||
@test("EUR/USD config factory produces correct config")
|
||||
def _():
|
||||
from pair_signals.eurusd import make_config
|
||||
config = make_config(Timeframe.H1)
|
||||
assert config.symbol == EURUSD
|
||||
assert config.display_name == "EUR/USD"
|
||||
assert config.pip_value == 0.0001
|
||||
assert config.xau_strategy.enabled is False
|
||||
assert config.technical.regime_weights_override
|
||||
assert "trending" in config.technical.regime_weights_override
|
||||
assert "ranging" in config.technical.regime_weights_override
|
||||
|
||||
|
||||
@test("XAU/USD config factory unchanged")
|
||||
def _():
|
||||
from pair_signals.xauusd import make_config
|
||||
config = make_config(Timeframe.H1)
|
||||
assert config.symbol == XAUUSD
|
||||
assert config.display_name == "XAU/USD"
|
||||
assert config.pip_value == 0.10
|
||||
assert config.xau_strategy.enabled is True
|
||||
|
||||
|
||||
@test("pair_signals registry includes both pairs")
|
||||
def _():
|
||||
from pair_signals import get_supported_pairs
|
||||
pairs = get_supported_pairs()
|
||||
assert XAUUSD in pairs
|
||||
assert EURUSD in pairs
|
||||
|
||||
|
||||
@test("EUR/USD signal analysis produces valid report (mocked data)")
|
||||
def _():
|
||||
from pair_signals.eurusd import make_config
|
||||
from signal_aggregator import SignalAggregator
|
||||
config = make_config(Timeframe.H1)
|
||||
config.backtest.session_filter = False
|
||||
agg = SignalAggregator(config)
|
||||
df = make_ohlcv(n=300, start_price=1.0800, trend=0.00005)
|
||||
dfs = {
|
||||
Timeframe.H1: df,
|
||||
Timeframe.M15: df,
|
||||
Timeframe.H4: df,
|
||||
}
|
||||
report = agg.analyze_from_dataframes(symbol=EURUSD, dfs=dfs, use_news=False)
|
||||
assert report.display_name == "EUR/USD"
|
||||
assert report.signal_quality in ("A+", "A", "B", "C", "D", "F")
|
||||
assert not any("SELL FILTER" in w for w in report.warnings)
|
||||
|
||||
|
||||
@test("XAU sell gate does not apply to EUR/USD")
|
||||
def _():
|
||||
from pair_signals.eurusd import make_config
|
||||
from signal_aggregator import SignalAggregator
|
||||
config = make_config(Timeframe.H1)
|
||||
config.backtest.session_filter = False
|
||||
agg = SignalAggregator(config)
|
||||
df = make_ohlcv(n=300, start_price=1.0800, trend=-0.0005, seed=99)
|
||||
dfs = {Timeframe.H1: df, Timeframe.M15: df, Timeframe.H4: df}
|
||||
report = agg.analyze_from_dataframes(symbol=EURUSD, dfs=dfs, use_news=False)
|
||||
sell_filter_warnings = [w for w in report.warnings if "SELL FILTER" in w]
|
||||
assert len(sell_filter_warnings) == 0
|
||||
|
||||
|
||||
@test("NewsEngine._get_affected_pairs includes EUR/USD for EUR and USD events")
|
||||
def _():
|
||||
from news_engine import NewsEngine
|
||||
assert "EUR/USD" in NewsEngine._get_affected_pairs("EUR")
|
||||
assert "EUR/USD" in NewsEngine._get_affected_pairs("USD")
|
||||
assert "XAU/USD" in NewsEngine._get_affected_pairs("USD")
|
||||
assert "EUR/USD" not in NewsEngine._get_affected_pairs("GBP")
|
||||
|
||||
|
||||
# ══════════════════════════════════════════════════════════════════════
|
||||
# SUMMARY
|
||||
# ══════════════════════════════════════════════════════════════════════
|
||||
|
||||
Reference in New Issue
Block a user