Files
ForexBot2/config.py
T
Harwinder ce76684a62 Add indicator ablation framework + apply ablation-driven cleanup
Framework:
- TechnicalConfig.disabled_indicators skips named indicators in engine.analyze
- Optimizer supports disable_indicator override for parallel ablation sweeps
- main.py --ablate runs leave-one-out across all 10 directional indicators

Cleanup applied (validated across 2024 trend / 2023 chop / 2026 recent):
- Drop pivot by default (cumulative +9.5% to disable)
- Drop candlestick by default (+3.4% cumulative)
- Regime-gate ichimoku: 1.3x in trending, 0.2x in ranging (was a chop villain)
- Trim RSI weight 1.0 -> 0.3 (cumulative noise across regimes)

Result: +10.60% on 2024 trend (+33.24% -> +43.84%, Sharpe 2.17 -> 2.77),
+15.71% on 2023 chop (-18.27% -> -2.56%). Recent regime still adverse.
2026-05-07 17:33:07 -04:00

217 lines
6.2 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
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
# 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"