Backtester: EUR/USD support with per-pair configs and default spread
- EURUSD aliases in PAIR_ALIASES - main() builds PlatformConfig via pair_signals.PAIR_PRODUCERS factories (per-pair regime weights/gates/sessions); unregistered pairs keep the inline construction. XAU factory is asdict-identical to inline (enforced by test) so XAU runs are unchanged. - --spread default now resolves from PAIR_INFO default_spread (XAU $0.30, EUR 0.0001) instead of a hardcoded gold value - EUR strategy tag: renamed EUR_PB→EUR, detected in both bullish and bearish reasons[0] (SELL setups mark the bearish list) Smoke-validated: 30-day EUR M5 run produces EUR-tagged trades with 1-pip round-trip cost charged.
This commit is contained in:
+32
-15
@@ -48,6 +48,7 @@ if str(PROJECT_ROOT) not in sys.path:
|
|||||||
sys.path.insert(0, str(PROJECT_ROOT))
|
sys.path.insert(0, str(PROJECT_ROOT))
|
||||||
|
|
||||||
from config import ( # noqa: E402
|
from config import ( # noqa: E402
|
||||||
|
EURUSD,
|
||||||
PAIR_INFO,
|
PAIR_INFO,
|
||||||
PlatformConfig,
|
PlatformConfig,
|
||||||
SignalStrength,
|
SignalStrength,
|
||||||
@@ -96,6 +97,9 @@ PAIR_ALIASES = {
|
|||||||
"XAUUSD": XAUUSD,
|
"XAUUSD": XAUUSD,
|
||||||
"XAU/USD": XAUUSD,
|
"XAU/USD": XAUUSD,
|
||||||
"GOLD": XAUUSD,
|
"GOLD": XAUUSD,
|
||||||
|
"EURUSD": EURUSD,
|
||||||
|
"EUR/USD": EURUSD,
|
||||||
|
"EUR": EURUSD,
|
||||||
}
|
}
|
||||||
|
|
||||||
# Bar duration in seconds — used to compute "fully-closed" cutoffs so
|
# Bar duration in seconds — used to compute "fully-closed" cutoffs so
|
||||||
@@ -1632,8 +1636,8 @@ def run_backtest(
|
|||||||
tag = "XAU_PB"
|
tag = "XAU_PB"
|
||||||
elif "XAU STRATEGY" in first_bull:
|
elif "XAU STRATEGY" in first_bull:
|
||||||
tag = "XAU"
|
tag = "XAU"
|
||||||
elif "EUR STRATEGY" in first_bull:
|
elif "EUR STRATEGY" in first_bull or "EUR STRATEGY" in first_bear:
|
||||||
tag = "EUR_PB"
|
tag = "EUR"
|
||||||
elif "GBP STRATEGY" in first_bull:
|
elif "GBP STRATEGY" in first_bull:
|
||||||
tag = "GBP_PB"
|
tag = "GBP_PB"
|
||||||
|
|
||||||
@@ -2034,7 +2038,7 @@ def _parse_args(argv: Optional[List[str]] = None) -> argparse.Namespace:
|
|||||||
description="GPU-accelerated backtester for ForexBot2",
|
description="GPU-accelerated backtester for ForexBot2",
|
||||||
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
|
formatter_class=argparse.ArgumentDefaultsHelpFormatter,
|
||||||
)
|
)
|
||||||
p.add_argument("--pair", required=True, help="XAUUSD | XAU/USD | GOLD")
|
p.add_argument("--pair", required=True, help="XAUUSD | GOLD | EURUSD")
|
||||||
p.add_argument("--days", type=int, default=180, help="Days of history (ignored if --start/--end set)")
|
p.add_argument("--days", type=int, default=180, help="Days of history (ignored if --start/--end set)")
|
||||||
p.add_argument("--start", help="ISO date YYYY-MM-DD (overrides --days)")
|
p.add_argument("--start", help="ISO date YYYY-MM-DD (overrides --days)")
|
||||||
p.add_argument("--end", help="ISO date YYYY-MM-DD (defaults to today)")
|
p.add_argument("--end", help="ISO date YYYY-MM-DD (defaults to today)")
|
||||||
@@ -2047,10 +2051,11 @@ def _parse_args(argv: Optional[List[str]] = None) -> argparse.Namespace:
|
|||||||
help="Override TechnicalConfig.swing_sr_lookback (0 = use config default)")
|
help="Override TechnicalConfig.swing_sr_lookback (0 = use config default)")
|
||||||
p.add_argument("--quality", default="A+,A,B,C", help="Comma-separated allowed grades")
|
p.add_argument("--quality", default="A+,A,B,C", help="Comma-separated allowed grades")
|
||||||
p.add_argument("--strategies", default="",
|
p.add_argument("--strategies", default="",
|
||||||
help="Comma-separated strategy tags to include (XAU,EUR_PB,GBP_PB,GENERIC). Empty = all.")
|
help="Comma-separated strategy tags to include (XAU,XAU_PB,EUR,GENERIC). Empty = all.")
|
||||||
p.add_argument("--no-cache", action="store_true", help="Skip parquet cache (force API fetch)")
|
p.add_argument("--no-cache", action="store_true", help="Skip parquet cache (force API fetch)")
|
||||||
p.add_argument("--spread", type=float, default=0.30,
|
p.add_argument("--spread", type=float, default=None,
|
||||||
help="Round-trip cost in price units per trade (XAU ~$0.30; 0 disables)")
|
help="Round-trip cost in price units per trade (default: pair's "
|
||||||
|
"default_spread from PAIR_INFO — XAU $0.30, EUR 0.0001; 0 disables)")
|
||||||
p.add_argument("--export-csv", help="Optional path to dump per-trade CSV (incl. indicator breakdown)")
|
p.add_argument("--export-csv", help="Optional path to dump per-trade CSV (incl. indicator breakdown)")
|
||||||
p.add_argument("--detail", action="store_true",
|
p.add_argument("--detail", action="store_true",
|
||||||
help="Print full per-trade reasoning (indicators, signals, agreement, why entered)")
|
help="Print full per-trade reasoning (indicators, signals, agreement, why entered)")
|
||||||
@@ -2097,6 +2102,9 @@ def main(argv: Optional[List[str]] = None) -> int:
|
|||||||
else:
|
else:
|
||||||
start = end - timedelta(days=args.days)
|
start = end - timedelta(days=args.days)
|
||||||
|
|
||||||
|
spread = (args.spread if args.spread is not None
|
||||||
|
else PAIR_INFO[pair].get("default_spread", 0.30))
|
||||||
|
|
||||||
bt_cfg = BacktestConfig(
|
bt_cfg = BacktestConfig(
|
||||||
pair=pair,
|
pair=pair,
|
||||||
start=start,
|
start=start,
|
||||||
@@ -2109,21 +2117,30 @@ def main(argv: Optional[List[str]] = None) -> int:
|
|||||||
use_cache=not args.no_cache,
|
use_cache=not args.no_cache,
|
||||||
quality_filter=[q.strip() for q in args.quality.split(",") if q.strip()],
|
quality_filter=[q.strip() for q in args.quality.split(",") if q.strip()],
|
||||||
strategies_only=[s.strip() for s in args.strategies.split(",") if s.strip()],
|
strategies_only=[s.strip() for s in args.strategies.split(",") if s.strip()],
|
||||||
spread=args.spread,
|
spread=spread,
|
||||||
)
|
)
|
||||||
|
|
||||||
logger.info(
|
logger.info(
|
||||||
f"Backend: {'CuPy GPU' if _GPU_AVAILABLE else 'NumPy CPU fallback'} | "
|
f"Backend: {'CuPy GPU' if _GPU_AVAILABLE else 'NumPy CPU fallback'} | "
|
||||||
f"Pair: {PAIR_INFO[pair]['display']} | Window: {start.date()} → {end.date()}"
|
f"Pair: {PAIR_INFO[pair]['display']} | Window: {start.date()} → {end.date()} | "
|
||||||
|
f"Spread: {spread}"
|
||||||
)
|
)
|
||||||
|
|
||||||
platform_cfg = PlatformConfig(
|
# Per-pair tuned configs come from the pair_signals factories (regime
|
||||||
symbol=pair,
|
# weights, strategy gates, sessions). Unregistered pairs fall back to
|
||||||
display_name=PAIR_INFO[pair]["display"],
|
# the inline construction.
|
||||||
pip_value=PAIR_INFO[pair]["pip_value"],
|
from pair_signals import PAIR_PRODUCERS # noqa: E402 (after sys.path fix)
|
||||||
primary_timeframe=primary_tf,
|
factory = PAIR_PRODUCERS.get(pair)
|
||||||
confirmation_timeframes=confirm_tfs,
|
if factory is not None:
|
||||||
)
|
platform_cfg = factory(primary_tf, confirm_tfs)
|
||||||
|
else:
|
||||||
|
platform_cfg = PlatformConfig(
|
||||||
|
symbol=pair,
|
||||||
|
display_name=PAIR_INFO[pair]["display"],
|
||||||
|
pip_value=PAIR_INFO[pair]["pip_value"],
|
||||||
|
primary_timeframe=primary_tf,
|
||||||
|
confirmation_timeframes=confirm_tfs,
|
||||||
|
)
|
||||||
if args.swing_sr_lookback > 0:
|
if args.swing_sr_lookback > 0:
|
||||||
platform_cfg.technical.swing_sr_lookback = args.swing_sr_lookback
|
platform_cfg.technical.swing_sr_lookback = args.swing_sr_lookback
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user