72 lines
2.5 KiB
Python
72 lines
2.5 KiB
Python
""".set file generator (doc 07 §2a).
|
|
|
|
A ``.set`` is the EA's inputs serialized as ``key=value`` lines. **MT5 writes
|
|
``.set`` files as UTF-16-LE** — generate yours in the same encoding or the
|
|
tester silently ignores them.
|
|
|
|
The bridge's set generator takes the *same* parameter dict you backtested in
|
|
Python and writes the matching ``.set``. The mapping from Python parameter
|
|
names to EA input names is strategy-specific — keep a small mapping table
|
|
next to the strategy so the two tiers always agree. Watch the enum-valued
|
|
inputs (mode flags, timeframe codes): MT5 inputs are often integers.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
from dataclasses import dataclass
|
|
from pathlib import Path
|
|
from typing import Any, Mapping
|
|
|
|
SET_FILE_ENCODING = "utf-16-le"
|
|
|
|
|
|
@dataclass
|
|
class ParamMapping:
|
|
"""Maps a Python param name to an EA input name (+ enum cast if needed).
|
|
|
|
``cast`` converts a Python value to the EA input's wire form (e.g. a
|
|
timeframe string to its ``ENUM_TIMEFRAMES`` integer, a bool to ``true``/
|
|
``false``). Defaults to identity.
|
|
"""
|
|
|
|
py_name: str
|
|
ea_name: str
|
|
cast: Any = None # callable(value) -> str, optional
|
|
|
|
def to_wire(self, value: Any) -> str:
|
|
v = self.cast(value) if self.cast is not None else value
|
|
if isinstance(v, bool):
|
|
return "true" if v else "false"
|
|
if isinstance(v, float) and v.is_integer():
|
|
return str(int(v))
|
|
return str(v)
|
|
|
|
|
|
def write_set_file(
|
|
params: Mapping[str, Any],
|
|
mappings: list[ParamMapping],
|
|
path: str | Path,
|
|
*,
|
|
extra_lines: list[str] | None = None,
|
|
) -> None:
|
|
"""Write a ``.set`` file (UTF-16-LE) from a Python params dict.
|
|
|
|
Only parameters with a mapping are written — frozen baseline values that
|
|
match the EA's compiled-in defaults can be omitted. ``extra_lines`` lets a
|
|
strategy inject raw ``key=value`` lines that don't have a Python
|
|
counterpart (e.g. EA constants).
|
|
|
|
**Lot-mode guard (doc 05 §4, doc 07 §2a):** make sure the fixed-lot input
|
|
is ``0`` if you intend money mode — a mismatched ``.set`` is the #1 reason
|
|
a verified MT5 number disagrees with Python.
|
|
"""
|
|
out = Path(path)
|
|
out.parent.mkdir(parents=True, exist_ok=True)
|
|
lines: list[str] = ["; Generated by the MT5 bridge (UTF-16-LE)"]
|
|
for m in mappings:
|
|
if m.py_name in params:
|
|
lines.append(f"{m.ea_name}={m.to_wire(params[m.py_name])}")
|
|
if extra_lines:
|
|
lines.extend(extra_lines)
|
|
text = "\n".join(lines) + "\n"
|
|
out.write_bytes(text.encode(SET_FILE_ENCODING))
|