Warrior_EA/research/test_context2.py
AnimateDread f1b7dcf7f3 fix: correct MI sample alignment and improve BN weight diagnostic report
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.
2026-08-02 08:12:47 -04:00

103 lines
4.7 KiB
Python

"""The one replicated finding, re-tried on an engine that has been proved correct.
The Wyckoff CONTEXT score - five cumulative traces from book 2 sections 2.3 and 7.1 - is the
only thing in this project that has replicated: +0.045 R per agreeing trace, on two unrelated
triggers, right sign in 15 of 16 cells. It was measured with market entries, so it never had
the fill bug that killed four other results. But it was measured on M5 mid bars with an
average spread bolted on, and it has never been seen through `fills.py`.
Two questions, in this order:
1. does the dose-response slope survive an honest bid/ask fill?
2. at the configuration where the BASE is near zero, does base + context clear zero?
Question 2 is the whole point. Context is a modifier worth ~+0.045 R per trace and every
trigger measured so far starts at -0.15 to -0.33, so it has never had anything to lift. The
depth curve says the base is least bad on the SHALLOW side, and the cost ladder says a wider
stop divides the cost, so the configuration is chosen by MECHANISM and fixed before looking:
phi = 0.50 shallow retest - the adverse-selection curve's best side
mrisk = 2.0 stop at 2 ATR, so spread/risk is roughly halved
kR = 2.0 unchanged from the work being replicated
H = 200 unchanged
wait = 40 unchanged
Nothing below is tuned. The prediction is the same single pre-specified one: expR must RISE
MONOTONICALLY with the number of agreeing traces. The slope is the result. The top bucket is
NOT the result - picking it is what mining looks like - but its LEVEL is what decides whether
any of this is tradeable, so it is reported separately and honestly.
"""
import numpy as np, sys, time
sys.stdout.reconfigure(encoding='utf-8', errors='replace')
import fills, book, wyckoff
SYMS = ('EURUSD', 'USDJPY', 'XAUUSD', 'SP500')
PHI, MRISK, KR, H, WAIT = 0.50, 2.0, 2.0, 200, 40
def arm(sym, tf, phi=PHI, mrisk=MRISK, kR=KR, bk=None, f=None, ev=None):
bk = bk or fills.Book(sym)
f = f or book.frame(sym, tf, bk)
ev = ev if ev is not None else wyckoff.breakouts(f)
if ev is None:
return None
o = wyckoff.retest(sym, tf, phi, mrisk=mrisk, kR=kR, wait=WAIT, H=H,
bk=bk, f=f, ev=ev)
if o is None:
return None
ind = o['indep']
return dict(R=o['R'][ind], ag=o['ag'][ind], t=bk.t[o['idx'][ind]], n=int(ind.sum()))
def buckets(R, ag, lo=0, hi=6):
out = []
for k in range(lo, hi):
m = ag == k
out.append((k, int(m.sum()), R[m].mean() if m.sum() else np.nan,
book.tstat(R[m]) if m.sum() > 2 else 0.0))
return out
if __name__ == '__main__':
syms = [s for s in sys.argv[1:] if s in SYMS] or list(SYMS)
print("=== CONTEXT DOSE-RESPONSE, HONEST FILLS ===")
print(f" phi={PHI} mrisk={MRISK} kR={KR} H={H} wait={WAIT} - fixed before looking")
print(" prediction: expR rises monotonically with agreeing traces.\n")
print(f" {'sym':>7}{'tf':>4}{'n':>6} " + "".join(f"{k:>12}" for k in range(6))
+ f"{'slope':>9}{'t':>7}")
PR, PA, PT, cells = [], [], [], []
for tf in ('H1', 'H4'):
for s in syms:
a = arm(s, tf)
if a is None or a['n'] < 150:
print(f" {s:>7}{tf:>4} - too few")
continue
R, ag = a['R'], a['ag']
txt = []
for k, m, mu, _ in buckets(R, ag):
txt.append(f"{mu:+6.3f}({m:>4})" if m >= 25 else f"{'-':>12}")
sl, tt = book.slope_t(R, ag)
cells.append(sl)
print(f" {s:>7}{tf:>4}{a['n']:>6} " + "".join(txt)
+ f"{sl:>+9.4f}{tt:>+7.2f}")
PR.append(R); PA.append(ag); PT.append(a['t'])
if not PR:
sys.exit()
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):,} slope {sl:+.4f} R/trace t {tt:+.2f}"
f" cells with positive slope {sum(1 for x in cells if x>0)}/{len(cells)}")
print(f" {'agree':>7}{'n':>7}{'expR':>9}{'se':>8}{'t':>7} chronological quarters")
for k, m, mu, t in buckets(R, ag):
sel = ag == k
if sel.sum() < 25:
continue
se = R[sel].std(ddof=1) / np.sqrt(sel.sum())
o = np.argsort(T[sel])
q = [float(x.mean()) for x in np.array_split(R[sel][o], 4)]
print(f" {k:>7}{int(sel.sum()):>7}{mu:>+9.4f}{se:>8.4f}{t:>+7.2f} "
+ "".join(f"{x:>+8.3f}" for x in q)
+ f" {sum(1 for x in q if x>0)}/4")
print("\n The slope is the test. The LEVEL of the top buckets is what decides whether")
print(" base + context clears zero - and that is the question this configuration was")
print(" built to answer.")