143 lines
5.6 KiB
Python
143 lines
5.6 KiB
Python
# 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'交易品种:</td>\s*<td[^>]*><b>([^<]+)</b>', 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() |