""" ForexBot2 — Configuration (USD/JPY focus) ========================================== Single-pair signal & backtest configuration. All knobs that govern indicator behavior, signal scoring, news handling, and the walk-forward backtester live here. """ import os from dataclasses import dataclass, field from enum import Enum from typing import Dict, List, Optional class Timeframe(Enum): M1 = "1min" M5 = "5min" M15 = "15min" M30 = "30min" H1 = "1h" H4 = "4h" D1 = "1d" W1 = "1wk" class SignalStrength(Enum): STRONG_BUY = 2 BUY = 1 NEUTRAL = 0 SELL = -1 STRONG_SELL = -2 class MarketSession(Enum): SYDNEY = "sydney" TOKYO = "tokyo" LONDON = "london" NEW_YORK = "new_york" OVERLAP_LONDON_NY = "london_ny_overlap" # ── Single Focus: USD/JPY ───────────────────────────────────────── USDJPY = "USDJPY=X" DISPLAY = "USD/JPY" PIP_VALUE = 0.01 # JPY pairs use 0.01 per pip @dataclass class TechnicalConfig: """Technical indicator parameters.""" ema_fast: int = 9 ema_medium: int = 21 ema_slow: int = 50 ema_trend: int = 200 rsi_period: int = 14 rsi_overbought: float = 70.0 rsi_oversold: float = 30.0 rsi_neutral_upper: float = 60.0 rsi_neutral_lower: float = 40.0 macd_fast: int = 12 macd_slow: int = 26 macd_signal: int = 9 bb_period: int = 20 bb_std: float = 2.0 atr_period: int = 14 stoch_k: int = 14 stoch_d: int = 3 stoch_overbought: float = 80.0 stoch_oversold: float = 20.0 adx_period: int = 14 adx_strong_trend: float = 25.0 adx_very_strong: float = 50.0 ichimoku_tenkan: int = 9 ichimoku_kijun: int = 26 ichimoku_senkou_b: int = 52 volume_ma_period: int = 20 pivot_lookback: int = 1 divergence_lookback: int = 14 # Ablation control — names listed here are skipped during analyze(). # Use lowercase keys: ema_trend, rsi, macd, bollinger, stochastic, # adx, ichimoku, divergence, candlestick, pivot, atr, volume. # # Defaults disable indicators that empirically hurt P&L on USD/JPY across # 2024-trend / 2023-chop / 2026-recent ablation sweeps (see project memory): # - pivot: net +9.5% to disable across 3 windows # - candlestick: net +3.4% to disable disabled_indicators: List[str] = field( default_factory=lambda: ["pivot", "candlestick"] ) ALL_INDICATORS: List[str] = [ "ema_trend", "rsi", "macd", "bollinger", "stochastic", "adx", "ichimoku", "divergence", "candlestick", "pivot", ] @dataclass class SignalConfig: """Signal scoring & filtering.""" min_confidence: float = 55.0 weights: Dict[str, float] = field(default_factory=lambda: { "trend": 0.25, "momentum": 0.20, "volatility": 0.15, "price_action": 0.15, "multi_timeframe": 0.15, "sentiment": 0.10, }) mtf_agreement_threshold: int = 2 # of 3 timeframes cooldown_bars: int = 5 default_risk_pct: float = 1.0 default_rr_ratio: float = 2.0 atr_sl_multiplier: float = 1.5 # SL distance = 1.5 × ATR @dataclass class NewsConfig: """Economic calendar & sentiment.""" tracked_currencies: List[str] = field(default_factory=lambda: ["USD", "JPY"]) min_impact: str = "medium" pre_event_buffer_minutes: int = 30 post_event_buffer_minutes: int = 15 rss_feeds: List[str] = field(default_factory=lambda: [ "https://www.forexlive.com/feed/", "https://www.fxstreet.com/rss", ]) @dataclass class BacktestConfig: """Walk-forward backtest configuration.""" # Data window — None means "all available history" start_date: Optional[str] = None # "YYYY-MM-DD" end_date: Optional[str] = None # "YYYY-MM-DD" max_bars: int = 5000 # Twelve Data ceiling per request # Account & sizing initial_balance: float = 10_000.0 risk_per_trade_pct: float = 1.0 # % of equity risked per trade # Execution realism use_spread_cost: bool = True slippage_pips: float = 0.5 # added to spread on entry & exit one_position_only: bool = True sl_first_on_tie: bool = True # if SL & TP both inside bar, SL hits first # Signal gates min_trade_grade: str = "B" # "A+", "A", "B", "C", "D", "F" min_abs_score: float = 0.0 # |final_score| must exceed this to enter # Sanity-test knob — flip BUY↔SELL to see if the engine has reversed edge invert_signal: bool = False # Regime gate — only trade when a higher-TF trend is confirmed regime_filter: bool = False regime_filter_timeframe: Timeframe = Timeframe.H4 regime_filter_min_adx: float = 25.0 # Session gate — only trade during a specified UTC hour window. # 2026 diagnostic backtest showed 07-15 UTC (London + NY) had # 47% WR / +127 pips while off-hours were 0% WR / -571 pips. session_filter: bool = False session_start_hour: int = 7 # UTC, inclusive session_end_hour: int = 16 # UTC, exclusive — so trades fire on 07..15 # Auto-toggle: enable session filter ONLY when recent realised trades # show adverse expectancy (mean pnl_pct over the last N closed trades # below threshold). Releases automatically once expectancy recovers. # Designed so trending regimes keep all hours, while adverse/chop # regimes auto-restrict to liquid sessions. auto_session_filter: bool = False auto_session_lookback_trades: int = 10 auto_session_threshold_pct: float = -0.3 # Parallelism (used by Optimizer; single-run backtest is sequential) parallel_workers: int = field(default_factory=lambda: os.cpu_count() or 4) # Optimizer optimize: bool = False param_grid: Dict[str, list] = field(default_factory=lambda: { # 2 × 2 × 3 × 2 × 2 = 48 combos "atr_sl_multiplier": [1.5, 2.5], "default_rr_ratio": [1.5, 2.5], "min_abs_score": [15.0, 35.0, 60.0], "min_trade_grade": ["B", "A"], "invert_signal": [False, True], }) # Cache directory for fetched OHLCV (parquet) cache_dir: str = ".cache" @dataclass class PlatformConfig: """Top-level configuration.""" technical: TechnicalConfig = field(default_factory=TechnicalConfig) signals: SignalConfig = field(default_factory=SignalConfig) news: NewsConfig = field(default_factory=NewsConfig) backtest: BacktestConfig = field(default_factory=BacktestConfig) symbol: str = USDJPY display_name: str = DISPLAY pip_value: float = PIP_VALUE primary_timeframe: Timeframe = Timeframe.H1 confirmation_timeframes: List[Timeframe] = field( default_factory=lambda: [Timeframe.M15, Timeframe.H4] ) data_lookback_days: int = 60 # enough for 200 EMA on H1 twelve_data_api_key: str = "f563b22ffdfd4924955decbe1a8c8701" refresh_interval: int = 300 # 5 min cache TTL log_level: str = "INFO" log_signals_to_file: bool = True signal_log_path: str = "signals.log"