Warrior_EA/research/grid_summary.py

81 行
3.5 KiB
Python

"""
Summarise a set of WarriorDipZ tester runs: the report's MARK-TO-MARKET equity
drawdown plus journal-derived cadence, swap drag and an IS/OOS split.
python grid_summary.py claude_g4_c0 claude_g4_c10 ...
Screen (the account's actual rule): cadence >= 2/mo, equity maxDD <= 5%,
ret/DD >= 2 -- with ret/DD computed on the MARK-TO-MARKET drawdown, never the
exit-based one, which understates concurrent losses.
"""
from __future__ import annotations
import re
import sys
import numpy as np
TERM = r"C:\Users\admin\AppData\Roaming\MetaQuotes\Terminal\10CE948A1DFC9A8C27E56E827008EBD4"
COMMON = r"C:\Users\admin\AppData\Roaming\MetaQuotes\Terminal\Common\Files"
DEPOSIT = 100000.0
def report(name):
t = open(rf"{TERM}\{name}.htm", encoding="utf-16").read()
cells = [re.sub(r"\s+", " ", re.sub(r"<[^>]+>", "", c)).strip()
for c in re.findall(r"<td[^>]*>(.*?)</td>", t, flags=re.S)]
out = {}
for i, c in enumerate(cells):
nxt = next((x for x in cells[i + 1:i + 4] if x), "")
if c == "Total Net Profit:":
out["net"] = float(nxt.replace(" ", ""))
elif c == "Equity Drawdown Maximal:":
out["eqdd"] = float(re.search(r"\(([\d.]+)%\)", nxt).group(1)) / 100
elif c == "Profit Factor:":
out["pf"] = float(nxt)
return out
def journal(name):
raw = np.genfromtxt(rf"{COMMON}\dipz_trades_{name}.csv", delimiter=",",
skip_header=1, dtype=str, encoding="ansi")
ts = lambda s: np.datetime64(s[:10].replace(".", "-") + "T" + s[11:] + ":00") # noqa: E731
return [dict(sym=r[1], t=ts(r[2]), x=ts(r[5]), net=float(r[7]), gross=float(r[9]),
swap=float(r[10])) for r in raw]
def seg_stats(rows):
rows = sorted(rows, key=lambda r: r["x"])
q = DEPOSIT + np.concatenate([[0], np.cumsum([r["net"] for r in rows])])
dd = ((np.maximum.accumulate(q) - q) / np.maximum.accumulate(q)).max()
return q[-1] / DEPOSIT - 1, dd
if __name__ == "__main__":
names = sys.argv[1:]
print(f"{'run':<16}{'n':>5}{'/mo':>6}{'net':>8}{'CAGR':>7}{'eqDD':>7}{'ret/DD':>8}{'PF':>6}"
f"{'swap':>8}{'swap%':>7} {'IS ret/DDx':>10}{'OOS ret/DDx':>12} screen")
for nm in names:
try:
rp, js = report(nm), journal(nm)
except Exception as e: # noqa: BLE001
print(f"{nm:<16} unreadable: {e}")
continue
yrs = (max(r["x"] for r in js) - min(r["t"] for r in js)) / np.timedelta64(365, "D")
tot = rp["net"] / DEPOSIT
cagr = (1 + tot) ** (1 / yrs) - 1
rdd = tot / rp["eqdd"]
swap = sum(r["swap"] for r in js)
gross = sum(r["gross"] for r in js)
split = np.datetime64("2024-01-01")
is_t, is_d = seg_stats([r for r in js if r["t"] < split])
oo_t, oo_d = seg_stats([r for r in js if r["t"] >= split])
permo = len(js) / (yrs * 12)
fails = [k for k, bad in (("cadence", permo < 2), ("eqDD", rp["eqdd"] > 0.05),
("ret/DD", rdd < 2)) if bad]
print(f"{nm:<16}{len(js):>5}{permo:>6.1f}{tot:>+8.1%}{cagr:>+7.1%}{rp['eqdd']:>7.2%}"
f"{rdd:>8.2f}{rp['pf']:>6.2f}{swap:>8.0f}{swap / gross if gross else 0:>7.0%}"
f" {is_t / is_d if is_d else float('nan'):>10.2f}{oo_t / oo_d if oo_d else float('nan'):>12.2f}"
f" {'PASS' if not fails else 'fail:' + ','.join(fails)}")
print("\nIS/OOS ret/DDx use the exit-based drawdown inside each window (split 2024-01-01);"
" the headline ret/DD uses the tester's mark-to-market equity drawdown.")