Files
ForexBot2/config.py
T
Harwinder 2e5d66db1b Add session filter + auto-toggle, extend trade journal diagnostics
Session filter restricts trades to a UTC hour window (default 07-16,
London + NY) — opt-in via --session-filter, with --session-start /
--session-end overrides. Wired into both the backtester (cheap hour
check next to the regime gate) and live mode (downgrades signal to
NEUTRAL/F when current UTC hour is outside the window).

Auto-toggle (--auto-session-filter) tracks rolling mean pnl_pct over
the last N closed trades; when it falls below threshold, the session
filter activates, releasing once expectancy recovers. Default lookback
10 trades, threshold -0.3%.

Trade dataclass extended with diagnostic fields captured at entry:
entry_hour, entry_dow, ADX, ATR%, market_regime, volatility_regime,
mtf_agreement. These let --journal-csv exports surface loss-pattern
clusters when a window underperforms.

Validated on 2024 trend / 2023 chop / 2026 recent windows. Auto-toggle
preserves ~50% of trend edge while still cutting ~25% of recent-regime
losses — pure regime filter is still best on cumulative, so both
session flags ship default-off.
2026-05-07 18:38:49 -04:00

233 lines
7.0 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
# 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"