# Real backtest smoke test for the symbol alias resolver. # Runs two backtests via the project's pipeline (INIGenerator + MT5AutoRunner): # 1) XAUUSD (no alias) -> INI Symbol=XAUUSD # 2) XAUUSD (alias -> XAUUSDc) -> INI Symbol=XAUUSDc # Success: two .htm reports in C:\Users\Administrator\Desktop\mt5-backtest\reports, # one with Symbol=XAUUSD, one with Symbol=XAUUSDc. import os import sys import re import shutil HERE = os.path.dirname(os.path.abspath(__file__)) PROJECT_ROOT = os.path.normpath(os.path.join(HERE, "..")) SCRIPTS = os.path.join(PROJECT_ROOT, "scripts") EXAMPLES_ROOT = os.path.normpath(os.path.join(PROJECT_ROOT, "..", "examples")) TARGET_REPORTS = r"C:\Users\Administrator\Desktop\mt5-backtest\reports" MT5_PATH = r"C:\Program Files\MetaTrader 5 IC Markets Global\terminal64.exe" MT5_DATA_DIR = r"C:\Users\Administrator\AppData\Roaming\MetaQuotes\Terminal\010E047102812FC0C18890992854220E" MT5_TESTER_DIR = os.path.join(MT5_DATA_DIR, "MQL5", "Profiles", "Tester") EA_NAME = "MACD_MA_XAUUSD_5M" EA_BASENAME = f"{EA_NAME}.ex5" EA_FULL_PATH = os.path.join(MT5_DATA_DIR, "MQL5", "Experts", EA_BASENAME) SET_SRC = os.path.join(EXAMPLES_ROOT, "configs", f"{EA_NAME}.set") SET_DEST_NAME = "MACD_MA_XAUUSD_5M_smoke.set" SET_DEST_PATH = os.path.join(MT5_TESTER_DIR, SET_DEST_NAME) os.makedirs(TARGET_REPORTS, exist_ok=True) shutil.copy2(SET_SRC, SET_DEST_PATH) sys.path.insert(0, SCRIPTS) from ini_generator import INIGenerator from mt5_auto_runner import MT5AutoRunner def build_cfg(aliases, out_ini_dir): return { "backtest_settings": { "currency": "USD", "date_range": {"from": "2026.05.01", "to": "2026.06.05"}, "deposit": 10000, "execution_delay": 0, "forward_date": "", "forward_mode": 0, "leverage": "1:100", "model": 0, "optimization": 0, "replace_report": True, "shutdown_terminal": True, "symbols": ["XAUUSD"], "timeframes": ["H1"], "visual": 0, "symbol_aliases": aliases or {}, }, "eas": [ {"name": EA_NAME, "filename": EA_FULL_PATH, "set_file": SET_DEST_NAME}, ], "mt5_settings": { "terminal_path": MT5_PATH, "data_dir": MT5_DATA_DIR, "ini_dir": out_ini_dir, "reports_dir": TARGET_REPORTS, }, } def patch_ini_to_inline(ini_path): """Patch the INI to use relative Expert + inline [TesterInputs] (the format MT5 actually auto-starts with on this terminal).""" with open(ini_path, encoding="utf-8") as f: text = f.read() text = text.replace(f"Expert={EA_FULL_PATH}", f"Expert={EA_BASENAME}") text = text.replace(f"ExpertParameters={SET_DEST_NAME}", "ExpertParameters=") with open(SET_DEST_PATH, encoding="utf-8", errors="ignore") as f: set_text = f.read() if not text.rstrip().endswith("[TesterInputs]"): text = text.rstrip() + "\n\n[TesterInputs]\n" + set_text with open(ini_path, "w", encoding="utf-8") as f: f.write(text) def run_one(label, aliases, out_dir): cfg = build_cfg(aliases=aliases, out_ini_dir=out_dir) gen = INIGenerator(cfg) ini_files = gen.generate_ini_files() assert len(ini_files) == 1, f"{label}: expected 1 INI, got {len(ini_files)}" ini = ini_files[0] patch_ini_to_inline(ini) with open(ini, encoding="utf-8") as f: sym_line = next(l for l in f if l.startswith("Symbol=")) print(f"[{label}] INI Symbol line: {sym_line.strip()}") runner = MT5AutoRunner(cfg) res = runner._execute_single_ini(ini, timeout_min=15) print(f"[{label}] runner status: {res['status']}, report_path: {res.get('report_path')}") return ini, res def main(): tmp_root = os.path.join(PROJECT_ROOT, "config", "generated_smoke") if os.path.exists(tmp_root): shutil.rmtree(tmp_root) os.makedirs(tmp_root, exist_ok=True) out1 = os.path.join(tmp_root, "no_alias"); os.makedirs(out1, exist_ok=True) out2 = os.path.join(tmp_root, "with_alias"); os.makedirs(out2, exist_ok=True) print("=== run 1: no alias (should pick XAUUSD) ===") ini1, r1 = run_one("no_alias", aliases=None, out_dir=out1) print() print("=== run 2: alias XAUUSD -> XAUUSDc (should pick XAUUSDc) ===") ini2, r2 = run_one("with_alias", aliases={"XAUUSD": "XAUUSDc"}, out_dir=out2) print() print("=== summary ===") print(f" INI1 ({os.path.basename(ini1)}) -> {r1['status']}, {r1.get('report_path')}") print(f" INI2 ({os.path.basename(ini2)}) -> {r2['status']}, {r2.get('report_path')}") reports = sorted([f for f in os.listdir(TARGET_REPORTS) if f.startswith('MACD_MA_XAUUSD_5M_') and f.endswith('.htm')]) print(f"\nReports in {TARGET_REPORTS}:") found = {"XAUUSD": [], "XAUUSDc": []} for r in reports: path = os.path.join(TARGET_REPORTS, r) size = os.path.getsize(path) # MT5 saves reports as UTF-16 LE with BOM text = open(path, encoding='utf-16', errors='ignore').read() m = re.search(r'交易品种:\s*]*>([^<]+)', text) sym = m.group(1) if m else "?" found[sym].append(r) print(f" - {r} ({size} bytes) symbol={sym}") print(f"\nDistinct symbols found: {sorted({k for k, v in found.items() if v})}") if not found["XAUUSD"]: raise AssertionError("No report with Symbol=XAUUSD") if not found["XAUUSDc"]: raise AssertionError("No report with Symbol=XAUUSDc") print("SUCCESS: both XAUUSD and XAUUSDc reports present in target dir.") if __name__ == "__main__": main()