File size: 4,722 Bytes
669d6a1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
from __future__ import annotations

import argparse
import re
import shutil
import subprocess
from pathlib import Path


ROOT = Path(__file__).resolve().parents[1]
DEFAULT_TERMINAL_DATA = Path(
    r"C:\Users\aksha\AppData\Roaming\MetaQuotes\Terminal\D0E8209F77C8CF37AD8BF550E51FF075"
)
DEFAULT_METAEDITOR = Path(r"C:\Program Files\MetaTrader 5\MetaEditor64.exe")

EA_SOURCE = ROOT / "mql5" / "Experts" / "AFML" / "AFMLLiveONNXEA.mq5"
PRODUCTION_ROOT = ROOT / "production_models" / "live_mt5"


def parse_args() -> argparse.Namespace:
    parser = argparse.ArgumentParser(
        description="Deploy AFML ONNX models and live EA into the local MT5 terminal."
    )
    parser.add_argument("--terminal-data", default=str(DEFAULT_TERMINAL_DATA))
    parser.add_argument("--metaeditor", default=str(DEFAULT_METAEDITOR))
    parser.add_argument("--timeframe", default="M15")
    return parser.parse_args()


def latest_model(symbol: str, timeframe: str) -> Path:
    model_dir = PRODUCTION_ROOT / symbol / timeframe
    candidates = sorted(model_dir.glob("*.onnx"))
    if not candidates:
        raise FileNotFoundError(f"No ONNX models found in {model_dir}")
    return candidates[-1]


def compile_ea(metaeditor: Path, ea_path: Path, log_path: Path) -> tuple[bool, int, str]:
    proc = subprocess.run(
        [str(metaeditor), f"/compile:{ea_path}", f"/log:{log_path}"],
        capture_output=True,
        text=True,
        timeout=120,
    )
    log_text = log_path.read_text(encoding="utf-16", errors="replace") if log_path.exists() else ""
    # MetaEditor may return non-zero even when compile finishes with warnings only.
    # Prefer the compiler summary line when available.
    match = re.search(r"Result:\s*(\d+)\s+errors", log_text)
    if match:
        return int(match.group(1)) == 0, proc.returncode, log_text
    return proc.returncode == 0, proc.returncode, log_text


def write_set_file(path: Path, model_rel_path: str, timeframe: str, magic_number: int) -> None:
    path.write_text(
        "\n".join(
            [
                f"InpOnnxModelFile={model_rel_path}",
                f"InpSignalTimeframe={timeframe}",
                "InpEnableTrading=false",
                "InpCloseOpposite=true",
                "InpFixedLot=0.01",
                "InpBuyThreshold=0.55",
                "InpSellThreshold=0.45",
                "InpStopAtrMultiplier=1.5",
                "InpTakeAtrMultiplier=2.0",
                "InpMaxSpreadPoints=500",
                "InpSlippagePoints=30",
                f"InpMagicNumber={magic_number}",
            ]
        ),
        encoding="utf-8",
    )


def main() -> int:
    args = parse_args()
    terminal_data = Path(args.terminal_data)
    metaeditor = Path(args.metaeditor)

    experts_dir = terminal_data / "MQL5" / "Experts" / "AFML"
    files_dir = terminal_data / "MQL5" / "Files" / "AFML" / "Models"
    presets_dir = terminal_data / "MQL5" / "Profiles" / "Tester"
    experts_dir.mkdir(parents=True, exist_ok=True)
    files_dir.mkdir(parents=True, exist_ok=True)
    presets_dir.mkdir(parents=True, exist_ok=True)

    deployed = []
    for symbol, stable_name in [("BTCUSD", "btcusd_m15_live.onnx"), ("XAUUSD", "xauusd_m15_live.onnx")]:
        source = latest_model(symbol, args.timeframe)
        target = files_dir / stable_name
        shutil.copy2(source, target)
        deployed.append((symbol, source, target))

    ea_target = experts_dir / EA_SOURCE.name
    shutil.copy2(EA_SOURCE, ea_target)

    write_set_file(
        presets_dir / "AFML_BTCUSD_M15.set",
        "AFML\\Models\\btcusd_m15_live.onnx",
        args.timeframe,
        202605031,
    )
    write_set_file(
        presets_dir / "AFML_XAUUSD_M15.set",
        "AFML\\Models\\xauusd_m15_live.onnx",
        args.timeframe,
        202605032,
    )

    log_path = ROOT / "diagnostics" / "metaeditor_compile.log"
    log_path.parent.mkdir(parents=True, exist_ok=True)
    compile_ok = None
    compile_code = None
    compile_log = ""
    if metaeditor.exists():
        compile_ok, compile_code, compile_log = compile_ea(metaeditor, ea_target, log_path)

    print(f"EA deployed to: {ea_target}")
    for symbol, source, target in deployed:
        print(f"{symbol}: {source.name} -> {target}")
    print(f"Preset files: {presets_dir / 'AFML_BTCUSD_M15.set'}")
    print(f"Preset files: {presets_dir / 'AFML_XAUUSD_M15.set'}")

    if compile_code is not None:
        print(f"MetaEditor exit code: {compile_code}")
        if compile_log:
            print("\nCompile log tail:\n")
            print(compile_log[-4000:])

    if compile_ok is None:
        return 0
    return 0 if compile_ok else 1


if __name__ == "__main__":
    raise SystemExit(main())