Files
mt_mt5_backertest/tests/smoke_alias_backtest.py
2026-07-06 21:58:56 +08:00

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()