149 lines
5.7 KiB
Python
149 lines
5.7 KiB
Python
# -*- coding: utf-8 -*-
|
|
"""P2.5/13: PREDICTION PARITY — Python model vs MQL5 model (freeze, no retrain).
|
|
|
|
- Runtime : mode-0 dump EA FIXED (prob pd fitur corrected) = AlgoForge_bt_prob_...
|
|
- Python : forward pass identik dgn SGML_Logit/Prob dari arrays freeze .mqh
|
|
diterapkan pd fitur corrected (build_features_p2).
|
|
Ukur: max/mean |dp|, mismatch count/rate (eps 1e-9).
|
|
"""
|
|
import os
|
|
import re
|
|
import sys
|
|
import csv
|
|
import datetime as dt
|
|
|
|
import numpy as np
|
|
|
|
HERE = os.path.dirname(os.path.abspath(__file__))
|
|
sys.path.insert(0, HERE)
|
|
import build_features_p2 as BFP # noqa: E402
|
|
|
|
DATA = os.path.normpath(os.path.join(HERE, "..", "..", "..", "..",
|
|
"Files", "AlgoForge", "Data"))
|
|
MQH = os.path.normpath(os.path.join(HERE, "..", "..", "..", "..",
|
|
"Include", "SniperGold_ML.mqh"))
|
|
PROB_AGENT = r"D:\TradingTerminal\HFM Metatrader 5\Tester\Agent-127.0.0.1-3000\MQL5\Files\AlgoForge_bt_prob_XAUUSD_M15.csv"
|
|
PROB = os.path.join(HERE, "AlgoForge_bt_prob_fixed_XAUUSD_M15.csv")
|
|
|
|
|
|
def log(msg):
|
|
print(msg, flush=True)
|
|
|
|
|
|
def load_npz(name):
|
|
z = np.load(os.path.join(DATA, name + ".npz"))
|
|
return (z["time"].astype(np.int64), z["open"].astype(np.float64),
|
|
z["high"].astype(np.float64), z["low"].astype(np.float64),
|
|
z["close"].astype(np.float64), z["tick_volume"].astype(np.float64))
|
|
|
|
|
|
def parse_ea_time(s):
|
|
return int(dt.datetime.strptime(s, "%Y.%m.%d %H:%M")
|
|
.replace(tzinfo=dt.timezone.utc).timestamp())
|
|
|
|
|
|
def parse_arr(text, name, n):
|
|
pat = re.compile(re.escape(name) + r"\[[^\]]*\](\[[^\]]*\])?=\{(.*)\};", re.S)
|
|
m = pat.search(text)
|
|
vals = [float(x) for x in re.findall(r"[-+]?[0-9]*\.?[0-9]+(?:[eE][-+]?[0-9]+)?", m.group(2))]
|
|
return np.array(vals[:n])
|
|
|
|
|
|
def main():
|
|
if not os.path.exists(PROB):
|
|
import shutil
|
|
shutil.copy2(PROB_AGENT, PROB)
|
|
log(f"copied mode-0 dump -> {PROB}")
|
|
|
|
txt = open(MQH, encoding="utf-8").read()
|
|
MEAN = parse_arr(txt, "SGML_MEAN", 19)
|
|
STD = parse_arr(txt, "SGML_STD", 19)
|
|
W1 = parse_arr(txt, "SGML_W1", 19 * 12).reshape(19, 12)
|
|
B1 = parse_arr(txt, "SGML_B1", 12)
|
|
W2L = parse_arr(txt, "SGML_W2L", 12)
|
|
B2L = float(re.search(r"SGML_B2L=([-0-9.eE+]+)", txt).group(1))
|
|
W2S = parse_arr(txt, "SGML_W2S", 12)
|
|
B2S = float(re.search(r"SGML_B2S=([-0-9.eE+]+)", txt).group(1))
|
|
m = re.search(r"MathExp\(-\(o\*([-0-9.eE+]+)\+([-0-9.eE+]+)\)\)", txt)
|
|
calL = (float(m.group(1)), float(m.group(2)))
|
|
m2 = re.search(r"MathExp\(-\(o\*([-0-9.eE+]+)\+\-?([-0-9.eE+]+)\)\)", txt)
|
|
# short calibration ada di baris kedua; parse manual
|
|
sh = re.findall(r"MathExp\(-\(o\*([-0-9.eE+]+)\+([-0-9.eE+]+)\)\)", txt)
|
|
calS = (float(sh[1][0]), float(sh[1][1]))
|
|
log(f"calL={calL} calS={calS}")
|
|
|
|
def prob_long(f):
|
|
z = np.where(STD > 0, (f - MEAN) / STD, 0.0)
|
|
h = np.maximum(0.0, B1 + z @ W1)
|
|
o = B2L + h @ W2L
|
|
return 1.0 / (1.0 + np.exp(-(o * calL[0] + calL[1])))
|
|
|
|
def prob_short(f):
|
|
z = np.where(STD > 0, (f - MEAN) / STD, 0.0)
|
|
h = np.maximum(0.0, B1 + z @ W1)
|
|
o = B2S + h @ W2S
|
|
return 1.0 / (1.0 + np.exp(-(o * calS[0] + calS[1])))
|
|
|
|
# load data + features corrected
|
|
t, o, h, l, c, v = load_npz("XAUUSD_M15")
|
|
keep = t >= int(dt.datetime(2017, 1, 1, tzinfo=dt.timezone.utc).timestamp())
|
|
t, o, h, l, c, v = t[keep], o[keep], h[keep], l[keep], c[keep], v[keep]
|
|
htf = {}
|
|
for key in ("D1", "H4", "H1"):
|
|
ht_, ho_, hh_, hl_, hc_, hv_ = load_npz("XAUUSD_" + key)
|
|
htf[key] = (hh_, hl_, hc_, ht_)
|
|
|
|
# load mode-0 dump
|
|
rows = []
|
|
with open(PROB, encoding="utf-8-sig") as f:
|
|
rdr = csv.reader(f, delimiter="\t")
|
|
next(rdr, None)
|
|
for r in rdr:
|
|
if len(r) < 5:
|
|
continue
|
|
try:
|
|
tt = parse_ea_time(r[0].strip())
|
|
pl = float(r[3])
|
|
ps = float(r[4])
|
|
except ValueError:
|
|
continue
|
|
rows.append((tt, pl, ps))
|
|
log(f"mode-0 rows={len(rows)}")
|
|
|
|
py_idx = {int(tt): i for i, tt in enumerate(t)}
|
|
joined = [(py_idx[tt], pl, ps, tt) for (tt, pl, ps) in rows if tt in py_idx]
|
|
log(f"joined={len(joined)}")
|
|
idxs = np.array([j[0] for j in joined], dtype=np.int64)
|
|
log("compute features corrected...")
|
|
F = BFP.build_features_p2(t, o, h, l, c, v, htf, idxs=idxs)
|
|
log("forward python model...")
|
|
pl_py = prob_long(F)
|
|
ps_py = prob_short(F)
|
|
pl_ea = np.array([j[1] for j in joined])
|
|
ps_ea = np.array([j[2] for j in joined])
|
|
|
|
dl = np.abs(pl_py - pl_ea)
|
|
ds = np.abs(ps_py - ps_ea)
|
|
eps = 6e-5 # toleransi rounding CSV mode-0 (5 desimal)
|
|
eps_strict = 1e-9
|
|
log("")
|
|
log("=== PREDICTION PARITY (Python vs MQL5 runtime, freeze model) ===")
|
|
log(f" LONG : max|dp|={dl.max():.3e} mean|dp|={dl.mean():.3e}")
|
|
log(f" SHORT: max|dp|={ds.max():.3e} mean|dp|={ds.mean():.3e}")
|
|
log(f" LONG strict(1e-9) mismatch={int((dl > eps_strict).sum())}/{len(dl)} "
|
|
f"(mayoritas = rounding CSV 5 desimal)")
|
|
log(f" LONG tol(6e-5) mismatch={int((dl > eps).sum())}/{len(dl)} rate={float((dl > eps).mean()):.6f}")
|
|
log(f" SHORT tol(6e-5) mismatch={int((ds > eps).sum())}/{len(ds)} rate={float((ds > eps).mean()):.6f}")
|
|
for lab, d in (("LONG", dl), ("SHORT", ds)):
|
|
over = np.nonzero(d > eps)[0]
|
|
if len(over):
|
|
log(f" {lab} rows dgn |dp|>6e-5:")
|
|
for k in over[:12]:
|
|
log(f" {dt.datetime.fromtimestamp(joined[k][3], dt.timezone.utc)} "
|
|
f"py={pl_py[k] if lab=='LONG' else ps_py[k]:.6f} "
|
|
f"ea={pl_ea[k] if lab=='LONG' else ps_ea[k]:.6f} d={d[k]:.2e}")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|