forked from animatedread/Warrior_EA
81 lines
3.5 KiB
Python
81 lines
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.")
|