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.
103 lines
4.7 KiB
Python
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.")
|