""" Read the Mind's trade book (Common\\Files\\Warrior_EA\\Mind\\book__.csv) the way a trader reads a journal after a run. python research/read_book.py [book.csv ...] # default: every book in the Common folder For each context field: Spearman rank correlation with R, and the mean R / win rate of the low, mid and high thirds. With ~29 fields and a few hundred trades, ONE field clearing p < 0.05 is what chance alone produces; the table prints the count expected by luck so a single "significant" line is not mistaken for a finding. Fields are ranked by |rho|; nothing here selects a rule - a rule found in this table is a hypothesis for the NEXT run, checked out of sample. """ from __future__ import annotations import glob import os import sys import numpy as np import pandas as pd from scipy.stats import spearmanr COMMON = os.path.expandvars(r"%APPDATA%\MetaQuotes\Terminal\Common\Files\Warrior_EA\Mind") NA = -999.0 BASE = {"position", "open_time", "close_time", "side", "entry", "exit", "sl", "lots", "risk_money", "net", "r", "mae_r", "mfe_r", "bars", "reason", "scale", "review_p", "review_n", "story"} def load(paths): frames = [] for p in paths: d = pd.read_csv(p) d["book"] = os.path.basename(p) frames.append(d) return pd.concat(frames, ignore_index=True) def psr(r: np.ndarray) -> float: from math import erf, sqrt n = len(r) if n < 10 or r.std(ddof=1) == 0: return float("nan") sr = r.mean() / r.std(ddof=1) m = r - r.mean() skew = (m ** 3).mean() / (m ** 2).mean() ** 1.5 kurt = (m ** 4).mean() / (m ** 2).mean() ** 2 den = 1 - skew * sr + (kurt - 1) / 4 * sr ** 2 z = sr * sqrt(n - 1) / sqrt(den) return 0.5 * (1 + erf(z / sqrt(2))) def main(): paths = sys.argv[1:] or sorted(glob.glob(os.path.join(COMMON, "book_*.csv"))) if not paths: sys.exit(f"no books under {COMMON}") d = load(paths) r = d["r"].to_numpy() print(f"{len(d)} trades from {len(paths)} book(s): " + ", ".join(sorted(set(d.book)))) print(f"mean R {r.mean():+.3f} win {100*(r>0).mean():.1f}% PSR(0) {psr(r):.3f} " f"MAE mean {d.mae_r.mean():+.2f}R MFE mean {d.mfe_r.mean():+.2f}R " f"risk scale mean {d.scale.mean():.2f}") print(f"exit reasons: {d.reason.value_counts().to_dict()}\n") fields = [c for c in d.columns if c not in BASE and c != "book"] rows = [] for f in fields: x = d[f].to_numpy(dtype=float) ok = x != NA if ok.sum() < 30 or np.nanstd(x[ok]) == 0: continue rho, p = spearmanr(x[ok], r[ok]) lo, hi = np.percentile(x[ok], [33.3, 66.7]) parts = [] for name, m in (("lo", ok & (x <= lo)), ("mid", ok & (x > lo) & (x < hi)), ("hi", ok & (x >= hi))): parts.append((m.sum(), r[m].mean() if m.any() else np.nan, 100 * (r[m] > 0).mean() if m.any() else np.nan)) rows.append((f, ok.sum(), rho, p, parts)) rows.sort(key=lambda t: -abs(t[2])) print(f"{'field':16s} {'n':>5s} {'rho':>7s} {'p':>7s} | {'lo: n meanR win%':>22s} | {'mid':>22s} | {'hi':>22s}") for f, n, rho, p, parts in rows: cells = " | ".join(f"{a:4d} {b:+6.3f} {c:5.1f}" for a, b, c in parts) print(f"{f:16s} {n:5d} {rho:+7.3f} {p:7.3f} | {cells}") print(f"\n{len(rows)} fields tested: about {0.05*len(rows):.1f} would clear p<0.05 by luck alone. " f"Treat anything short of p < {0.05/max(len(rows),1):.4f} (Bonferroni) as a lead, not a result.") if "review_p" in d and d.review_p.notna().sum() >= 20: v = d.dropna(subset=["review_p"]) rho, p = spearmanr(v.review_p, v.r) print(f"\nThe journal's own forecast (review_p) vs realised R on {len(v)} trades: rho {rho:+.3f}, p {p:.3f}" f" - it must be positive out of sample before SIZE is worth switching on.") if __name__ == "__main__": main()