""".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))