636f0304ae
- 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 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
199 lines
5.5 KiB
Python
199 lines
5.5 KiB
Python
"""
|
||
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
|
||
|
||
|
||
@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
|
||
|
||
# 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"
|