The MI sample builder used `MathAbs(labelBarOffset)` as a padding, causing rows from offset and non-offset builds to be paired with a double shift. This broke the positive control, failed the 5× gate, and voided all reported mutual‑information figures. Replace with the fixed `MiShiftPad` constant to ensure builds enumerate the same set of bars and row-k alignment is preserved. Add `BatchOptionsTotal()` to `CNeuronBatchNormOCL` and split the packed BN weight array in the learning report into separate norms for the outgoing dense matrix, gamma, beta, running statistics, and Adam moment buffers. This turns an ambiguous single‑norm reading into precise diagnostics that distinguish weight divergence from scaling issues.
136 lines
6 KiB
Python
136 lines
6 KiB
Python
"""Attribution: did the honest engine kill the context finding, or did I change the trade?
|
|
|
|
`test_context2.py` found no dose-response (pooled slope -0.025, t -0.96) where the original
|
|
run found +0.0425 at t +3.18. But it changed two things at once - the fill engine AND the
|
|
trade - so the failure cannot yet be pinned on either. This isolates them by running the
|
|
ORIGINAL LPS configuration, unchanged, on the new engine:
|
|
|
|
breakout close beyond a qualified range edge at bar i
|
|
retest first bar j within tol*ATR of the broken edge that still closes beyond it,
|
|
abandoned if price closes back through the edge (wait up to 60 bars)
|
|
entry MARKET at the open of bar j+1 <- no level, so no fill artifact ever
|
|
stop the retest bar's own extreme, 0.10 ATR beyond
|
|
target entry + 2 * risk
|
|
|
|
That is exactly what produced +0.0425. The only difference is that the outcome now races on
|
|
the M1 bid/ask book instead of M5 mid bars with an average spread bolted on.
|
|
|
|
slope stays near +0.04 -> the engine is fine and my shallow-limit variant broke it
|
|
slope collapses -> the original finding depended on the old harness
|
|
|
|
Both are worth knowing and only this comparison can tell them apart.
|
|
"""
|
|
import numpy as np, sys, time
|
|
sys.stdout.reconfigure(encoding='utf-8', errors='replace')
|
|
import fills, book, wyckoff
|
|
|
|
SYMS = ('EURUSD', 'USDJPY', 'XAUUSD', 'SP500')
|
|
|
|
|
|
def events(sym, tf, theta=0.60, tol=0.35, wait=60, H=200, kR=2.0, htf=200,
|
|
bk=None, f=None):
|
|
"""The original LPS trade, market-entered, on the M1 bid/ask book."""
|
|
bk = bk or fills.Book(sym)
|
|
f = f or book.frame(sym, tf, bk)
|
|
step = book.TF_SEC[tf] // 60
|
|
h, l, c = f.h, f.l, f.c
|
|
n = f.n
|
|
atr = f.atr(14)
|
|
L, hi_, lo_ = book.find_ranges(h, l, atr, theta=theta)
|
|
up = (L > 0) & (c > hi_); dn = (L > 0) & (c < lo_)
|
|
fire = np.nonzero(up | dn)[0]
|
|
fire = fire[(fire > max(300, htf + 5)) & (fire < n - wait - 5)]
|
|
if not len(fire):
|
|
return None
|
|
dirs = np.where(up[fire], 1, -1)
|
|
|
|
rows, busy = [], -1
|
|
for q in range(len(fire)):
|
|
i = int(fire[q])
|
|
if i <= busy:
|
|
continue
|
|
dd = int(dirs[q]); Lq = int(L[i])
|
|
s = i - Lq
|
|
if s < 1:
|
|
continue
|
|
top, bot = hi_[i], lo_[i]
|
|
lvl = top if dd > 0 else bot
|
|
j = -1
|
|
for k in range(1, wait + 1):
|
|
b_ = i + k
|
|
if b_ >= n - 2:
|
|
break
|
|
near = (l[b_] <= lvl + tol * atr[i]) if dd > 0 else (h[b_] >= lvl - tol * atr[i])
|
|
if near and ((c[b_] > lvl) if dd > 0 else (c[b_] < lvl)):
|
|
j = b_; break
|
|
if (c[b_] < lvl - tol * atr[i]) if dd > 0 else (c[b_] > lvl + tol * atr[i]):
|
|
break
|
|
if j < 0:
|
|
continue
|
|
e = j + 1
|
|
if e >= n - 1:
|
|
continue
|
|
ext = l[j] if dd > 0 else h[j]
|
|
stop = ext - dd * 0.10 * atr[i]
|
|
ag = wyckoff.traces(f, s, i, top, bot, dd, int(np.sign(c[i] - c[i - htf])))
|
|
rows.append((e, dd, stop, ag, Lq))
|
|
busy = e + Lq
|
|
if len(rows) < 100:
|
|
return None
|
|
E = np.array([r[0] for r in rows]); D = np.array([r[1] for r in rows])
|
|
ST = np.array([r[2] for r in rows], float); AG = np.array([r[3] for r in rows])
|
|
start = f.i0[E]
|
|
#--- entry price is not known until the fill, so the target must be built from it;
|
|
#--- run once to get the fill, then set the target at kR x the realised risk
|
|
ent = np.where(D > 0, bk.ao[start], bk.bo[start])
|
|
risk = np.abs(ent - ST)
|
|
ok = risk > 4 * f.spread[E]
|
|
if ok.sum() < 100:
|
|
return None
|
|
E, D, ST, AG, start, ent, risk = (v[ok] for v in (E, D, ST, AG, start, ent, risk))
|
|
out = fills.simulate(bk, start, D, ST, ent + D * kR * risk, H * step, entry=fills.MARKET)
|
|
if out is None:
|
|
return None
|
|
sel = np.nonzero(out['filled'])[0][out['kept']]
|
|
ind = book.nonoverlap(out['idx'], out['exit_idx'] - out['idx'])
|
|
return dict(R=out['R'][ind], ag=AG[sel][ind], t=bk.t[out['idx'][ind]],
|
|
n=int(ind.sum()), amb=out['ambiguous'], unres=out['unresolved'])
|
|
|
|
|
|
if __name__ == '__main__':
|
|
syms = [s for s in sys.argv[1:] if s in SYMS] or list(SYMS)
|
|
print("=== ORIGINAL LPS CONFIG, NEW ENGINE - attribution run ===")
|
|
print(" market entry at the next bar's open, stop at the retest extreme, 2R target.")
|
|
print(" the old harness gave slope +0.0425 R/trace at t +3.18.\n")
|
|
print(f" {'sym':>7}{'tf':>5}{'n':>6} " + "".join(f"{k:>12}" for k in range(6))
|
|
+ f"{'slope':>9}{'t':>7}{'expR':>9}")
|
|
PR, PA, PT, cells = [], [], [], []
|
|
for tf in ('M15', 'H1'):
|
|
for s in syms:
|
|
a = events(s, tf)
|
|
if a is None:
|
|
print(f" {s:>7}{tf:>5} - too few"); continue
|
|
R, ag = a['R'], a['ag']
|
|
txt = [f"{R[ag==k].mean():+6.3f}({int((ag==k).sum()):>4})"
|
|
if (ag == k).sum() >= 25 else f"{'-':>12}" for k in range(6)]
|
|
sl, tt = book.slope_t(R, ag)
|
|
cells.append(sl)
|
|
print(f" {s:>7}{tf:>5}{a['n']:>6} " + "".join(txt)
|
|
+ f"{sl:>+9.4f}{tt:>+7.2f}{R.mean():>+9.4f}")
|
|
PR.append(R); PA.append(ag); PT.append(a['t'])
|
|
if PR:
|
|
R = np.concatenate(PR); ag = np.concatenate(PA); T = np.concatenate(PT)
|
|
sl, tt = book.slope_t(R, ag)
|
|
print(f"\n POOLED n={len(R):,} base expR {R.mean():+.4f}"
|
|
f" slope {sl:+.4f} R/trace t {tt:+.2f}"
|
|
f" positive-slope cells {sum(1 for x in cells if x>0)}/{len(cells)}")
|
|
for k in range(6):
|
|
m = ag == k
|
|
if m.sum() < 25:
|
|
continue
|
|
se = R[m].std(ddof=1) / np.sqrt(m.sum())
|
|
o = np.argsort(T[m])
|
|
q = [float(x.mean()) for x in np.array_split(R[m][o], 4)]
|
|
print(f" {k} agree n={int(m.sum()):>5} expR {R[m].mean():+7.4f}"
|
|
f" +/- {se:.4f} quarters " + "".join(f"{x:>+8.3f}" for x in q)
|
|
+ f" {sum(1 for x in q if x>0)}/4")
|