Refocus on USD/JPY and add walk-forward backtester with regime filter

- Strip multi-pair plumbing; drop synthetic-data fallback
- Add BacktestConfig + walk-forward replay through the live signal pipeline
- Add metrics module (PF, Sharpe, Sortino, max DD, trade journal)
- Add parallel grid optimizer with disk parquet cache
- Add H4 ADX regime gate (--regime-filter, --regime-min-adx) — cuts chop
  losses ~84% in 2023 H1 while preserving trend edge
This commit is contained in:
Harwinder
2026-05-07 17:15:41 -04:00
parent c04f451c0c
commit 045f4248f3
9 changed files with 1605 additions and 1203 deletions
+80 -78
View File
@@ -1,13 +1,15 @@
"""
Forex Signal Intelligence Platform - Configuration
===================================================
Central configuration for all signal analysis parameters.
Tuned by market regime and validated against historical performance.
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
from typing import Dict, List, Optional
class Timeframe(Enum):
@@ -37,160 +39,160 @@ class MarketSession(Enum):
OVERLAP_LONDON_NY = "london_ny_overlap"
# ── Major Forex Pairs ──────────────────────────────────────────────
MAJOR_PAIRS = [
"EURUSD=X", "GBPUSD=X", "USDJPY=X", "USDCHF=X",
"AUDUSD=X", "USDCAD=X", "NZDUSD=X",
]
CROSS_PAIRS = [
"EURGBP=X", "EURJPY=X", "GBPJPY=X", "AUDJPY=X",
"EURCHF=X", "CADJPY=X",
]
# Yahoo Finance symbol mapping for display
SYMBOL_DISPLAY = {
"EURUSD=X": "EUR/USD", "GBPUSD=X": "GBP/USD",
"USDJPY=X": "USD/JPY", "USDCHF=X": "USD/CHF",
"AUDUSD=X": "AUD/USD", "USDCAD=X": "USD/CAD",
"NZDUSD=X": "NZD/USD", "EURGBP=X": "EUR/GBP",
"EURJPY=X": "EUR/JPY", "GBPJPY=X": "GBP/JPY",
"AUDJPY=X": "AUD/JPY", "EURCHF=X": "EUR/CHF",
"CADJPY=X": "CAD/JPY",
}
# ── 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 - carefully tuned for Forex."""
# EMA periods (fast/medium/slow for trend detection)
"""Technical indicator parameters."""
ema_fast: int = 9
ema_medium: int = 21
ema_slow: int = 50
ema_trend: int = 200
# RSI
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 (12, 26, 9 is standard)
macd_fast: int = 12
macd_slow: int = 26
macd_signal: int = 9
# Bollinger Bands
bb_period: int = 20
bb_std: float = 2.0
# ATR for volatility measurement
atr_period: int = 14
# Stochastic
stoch_k: int = 14
stoch_d: int = 3
stoch_overbought: float = 80.0
stoch_oversold: float = 20.0
# ADX for trend strength
adx_period: int = 14
adx_strong_trend: float = 25.0
adx_very_strong: float = 50.0
# Ichimoku
ichimoku_tenkan: int = 9
ichimoku_kijun: int = 26
ichimoku_senkou_b: int = 52
# Volume profile
volume_ma_period: int = 20
# Pivot points lookback
pivot_lookback: int = 1
# Divergence detection — how many bars back to scan for swing points
divergence_lookback: int = 14
@dataclass
class SignalConfig:
"""Signal scoring and filtering parameters."""
# Minimum confidence to emit a signal (0-100)
"""Signal scoring & filtering."""
min_confidence: float = 55.0
# Weight allocation for composite score
weights: Dict[str, float] = field(default_factory=lambda: {
"trend": 0.25, # EMA alignment, ADX
"momentum": 0.20, # RSI, Stochastic, MACD
"volatility": 0.15, # BB position, ATR regime
"price_action": 0.15, # Candlestick patterns, S/R
"multi_timeframe": 0.15,# Higher TF confirmation
"sentiment": 0.10, # News/calendar events
"trend": 0.25,
"momentum": 0.20,
"volatility": 0.15,
"price_action": 0.15,
"multi_timeframe": 0.15,
"sentiment": 0.10,
})
# Multi-timeframe confirmation requirements
mtf_agreement_threshold: int = 2 # out of 3 timeframes must agree
# Signal cooldown (prevent over-signalling same direction)
mtf_agreement_threshold: int = 2 # of 3 timeframes
cooldown_bars: int = 5
# Risk management defaults
default_risk_pct: float = 1.0 # risk 1% per trade
default_rr_ratio: float = 2.0 # 1:2 risk-reward minimum
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 and sentiment configuration."""
# High-impact event currencies
tracked_currencies: List[str] = field(default_factory=lambda: [
"USD", "EUR", "GBP", "JPY", "CHF", "AUD", "CAD", "NZD"
])
# Event impact levels to monitor
min_impact: str = "medium" # "low", "medium", "high"
# Pre-event buffer (minutes before high-impact event = no new signals)
"""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 volatility window (minutes after event = cautious signals)
post_event_buffer_minutes: int = 15
# News sentiment sources
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
# 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:
"""Overall platform configuration."""
"""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)
# Default pairs to monitor
watch_pairs: List[str] = field(default_factory=lambda: MAJOR_PAIRS)
symbol: str = USDJPY
display_name: str = DISPLAY
pip_value: float = PIP_VALUE
# Analysis timeframes (primary + confirmation)
primary_timeframe: Timeframe = Timeframe.H1
confirmation_timeframes: List[Timeframe] = field(
default_factory=lambda: [Timeframe.M15, Timeframe.H4]
)
# Data source
data_lookback_days: int = 60 # enough for 200 EMA on H1
# Twelve Data API key (overridden by TWELVE_DATA_API_KEY env var)
twelve_data_api_key: str = "f563b22ffdfd4924955decbe1a8c8701"
# Dashboard refresh interval (seconds)
refresh_interval: int = 300 # 5 minutes
refresh_interval: int = 300 # 5 min cache TTL
# Logging
log_level: str = "INFO"
log_signals_to_file: bool = True
signal_log_path: str = "signals.log"