ответвлён от animatedread/Warrior_EA
- Introduced `FeatureScale.mqh` with `FeatSquash` function for stateless feature scaling. - Added `RegimeMath.mqh` class for regime arithmetic, including efficiency and variance calculations. - Documented the Mind trading logic in `MIND.md`, detailing the trading process and modes. - Created `VOLNORM_PLAN.md` and `VOLNORM_RESULTS.md` for tick-volume normalization testing. - Implemented `read_book.py` for analyzing trade book data and correlations. - Developed `volnorm.py` for testing tick-volume normalization with new and old methods.
94 строки
3,8 КиБ
Python
94 строки
3,8 КиБ
Python
"""
|
|
Read the Mind's trade book (Common\\Files\\Warrior_EA\\Mind\\book_<SYMBOL>_<TF>.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()
|