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:
harwinder
2026-05-18 12:18:55 -04:00
parent b61818330b
commit 3146a632d4
8 changed files with 314 additions and 51 deletions
+115 -10
View File
@@ -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
# ══════════════════════════════════════════════════════════════════════