83 lines
2.9 KiB
Python
83 lines
2.9 KiB
Python
"""tester.ini generator (doc 07 §2b).
|
|
|
|
A small INI tells the tester *what* to run: expert, symbol, period, tick
|
|
model, date range, deposit, leverage, report name, and ``ShutdownTerminal=1``
|
|
so the terminal closes itself when done (the bridge then knows it's finished).
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import configparser
|
|
from dataclasses import dataclass
|
|
from pathlib import Path
|
|
from typing import Optional
|
|
|
|
DEFAULT_MT5_INSTALL = r"C:\Program Files\MetaTrader 5 IC Markets Global"
|
|
|
|
# Tick models (doc 07 §2b):
|
|
# 0 = Every tick based on real ticks (highest accuracy, slowest)
|
|
# 1 = Every tick (generated from M1)
|
|
# 2 = 1-minute OHLC (good for non-tick-sensitive, fast) — routine verification
|
|
# 4 = Open prices only (rough, fastest)
|
|
MODEL_OHLC = 2
|
|
MODEL_EVERY_TICK = 1
|
|
MODEL_REAL_TICKS = 0
|
|
MODEL_OPEN_PRICES = 4
|
|
|
|
|
|
@dataclass
|
|
class TesterConfig:
|
|
"""Inputs for one tester run (doc 07 §2b).
|
|
|
|
``FromDate`` / ``ToDate`` are formatted ``YYYY.MM.DD`` for MT5. The login
|
|
section lives separately (kept out of code via the env file, doc 07 §5).
|
|
"""
|
|
|
|
expert: str # e.g. "Experts\\MyEA.ex5"
|
|
symbol: str
|
|
period: str = "H1" # chart timeframe (≤ the EA's signal timeframe)
|
|
model: int = MODEL_OHLC # tick model
|
|
from_date: str = "2024.01.01" # YYYY.MM.DD
|
|
to_date: str = "2026.01.01"
|
|
deposit: float = 10000.0
|
|
leverage: int = 100
|
|
report: str = "report_myrun" # output HTML name (no extension)
|
|
shutdown_terminal: bool = True
|
|
set_file: Optional[str] = None # path to the .set, or None to inline
|
|
login: Optional[int] = None # from env, never hard-coded
|
|
password: Optional[str] = None # from env, never hard-coded
|
|
server: Optional[str] = None # broker server
|
|
|
|
|
|
def write_tester_ini(cfg: TesterConfig, path: str | Path) -> str:
|
|
"""Write a ``tester.ini`` for ``terminal64.exe /config:`` and return its path."""
|
|
p = Path(path)
|
|
p.parent.mkdir(parents=True, exist_ok=True)
|
|
cp = configparser.ConfigParser()
|
|
cp.optionxform = str # preserve case (MT5 keys are case-sensitive)
|
|
cp["Tester"] = {
|
|
"Expert": cfg.expert,
|
|
"Symbol": cfg.symbol,
|
|
"Period": cfg.period,
|
|
"Model": str(cfg.model),
|
|
"FromDate": cfg.from_date,
|
|
"ToDate": cfg.to_date,
|
|
"Deposit": str(cfg.deposit),
|
|
"Leverage": str(cfg.leverage),
|
|
"Report": cfg.report,
|
|
"ShutdownTerminal": "1" if cfg.shutdown_terminal else "0",
|
|
}
|
|
if cfg.set_file:
|
|
cp["Tester"]["TestReplaceExpert"] = "0"
|
|
common: dict[str, str] = {}
|
|
if cfg.login is not None:
|
|
common["Login"] = str(cfg.login)
|
|
if cfg.password is not None:
|
|
common["Password"] = cfg.password
|
|
if cfg.server is not None:
|
|
common["Server"] = cfg.server
|
|
if common:
|
|
cp["Common"] = common
|
|
with p.open("w", encoding="utf-8") as f:
|
|
cp.write(f)
|
|
return str(p)
|