Files
mymt5opp/strategies/gold_scalper_pro/params.py
T
2026-06-26 20:50:07 +08:00

135 lines
4.7 KiB
Python

"""Parse the MT5 optimizer .set file into structured params + search space.
The MT5 optimizer .set format (one line per input):
Name=value||start||min||max||optimize(Y/N)
- ``value`` : the current/last-used value (the frozen baseline).
- ``start`` : the optimization start value (usually == value).
- ``min``/``max`` : the optimization range boundaries.
- ``optimize`` : ``Y`` = included in MT5's grid search, ``N`` = frozen.
This is the authoritative source for the search space (doc 05 §2) — the
broker's own declared ranges, not guesses. We mirror them exactly in the
Optuna ``SearchSpace`` so Python and MT5 explore the same parameter volume.
"""
from __future__ import annotations
import re
from dataclasses import dataclass
from pathlib import Path
from typing import Any
@dataclass
class SetParam:
"""One input from the MT5 optimizer .set."""
name: str
value: Any # current/last-used value (frozen baseline if not optimized)
start: Any # optimization start (usually == value)
min_val: Any # optimization min
max_val: Any # optimization max
optimize: bool # Y = in MT5 grid search, N = frozen
raw_type: str = "" # inferred wire type ("int" / "float" / "bool" / "enum")
# Enum integer mappings (from the EA source, doc 05 §2).
# MT5 stores enums as integers; we keep them as ints and map back to names
# only for human-readable output.
ENUM_SIZING_MODE = {0: "SIZE_FIXED_LOT", 1: "SIZE_RISK_PERCENT"}
ENUM_STOP_MODE = {0: "STOP_ATR", 1: "STOP_POINTS"}
ENUM_TIMEFRAMES = {1: "PERIOD_M1", 5: "PERIOD_M5", 15: "PERIOD_M15",
30: "PERIOD_M30", 60: "PERIOD_H1", 240: "PERIOD_H4",
1440: "PERIOD_D1"}
def parse_set_file(path: str | Path) -> list[SetParam]:
"""Parse an MT5 optimizer ``.set`` (UTF-16-LE) into a list of SetParam.
Handles the MT5-native UTF-16-LE encoding. Lines starting with ``;`` are
comments / group headers. The trailing ``InpComment`` line has no
``||`` fields and is parsed as a plain string value.
"""
p = Path(path)
raw = p.read_bytes()
# Detect BOM / encoding.
if raw[:2] in (b"\xff\xfe", b"\xfe\xff"):
text = raw.decode("utf-16")
else:
text = raw.decode("utf-8", errors="replace")
params: list[SetParam] = []
for line in text.splitlines():
line = line.strip()
if not line or line.startswith(";") or "=" not in line:
continue
name, _, rest = line.partition("=")
name = name.strip()
fields = rest.split("||")
if len(fields) >= 5:
value = _cast(name, fields[0])
start = _cast(name, fields[1])
mn = _cast(name, fields[2])
mx = _cast(name, fields[3])
opt = fields[4].strip().upper() == "Y"
ptype = _infer_type(name, fields[0])
params.append(SetParam(name, value, start, mn, mx, opt, ptype))
else:
# Plain key=value (e.g. InpComment=GoldScalperPro).
value = _cast(name, fields[0])
params.append(SetParam(name, value, value, value, value, False,
_infer_type(name, fields[0])))
return params
def _cast(name: str, raw: str) -> Any:
"""Cast a raw string field to int/float/bool based on name + content."""
s = raw.strip()
if s.lower() in ("true", "false"):
return s.lower() == "true"
# Booleans as 0/1 for enum fields.
if name in ("InpUseBreakEven", "InpUseTrailing", "InpUseSession"):
# In the .set these appear as true/false strings, handled above.
return s
# Try int first (MT5 stores whole-number floats as ints sometimes).
try:
return int(s)
except ValueError:
pass
try:
return float(s)
except ValueError:
pass
return s
def _infer_type(name: str, raw: str) -> str:
"""Infer the wire type for set-file generation."""
s = raw.strip().lower()
if s in ("true", "false"):
return "bool"
if name in ("InpSizingMode", "InpStopMode", "InpTimeframe"):
return "enum"
try:
int(s)
return "int"
except ValueError:
try:
float(s)
return "float"
except ValueError:
return "string"
if __name__ == "__main__":
import sys
set_path = sys.argv[1] if len(sys.argv) > 1 else (
r"C:\Users\Administrator\AppData\Roaming\MetaQuotes\Terminal"
r"\010E047102812FC0C18890992854220E\MQL5\Profiles\Tester\GoldScalperPro.set"
)
for p in parse_set_file(set_path):
flag = "OPT" if p.optimize else "frozen"
print(f" {p.name:24s} = {str(p.value):>10s} [{p.raw_type:6s}] "
f"range=[{p.min_val}..{p.max_val}] {flag}")