Warrior_EA/research/test_drift.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

121 lines
5.4 KiB
Python

"""The only base that has ever been positive - and whether anything can be bolted onto it.
Every trigger tested in this project starts at -0.15 to -0.33 R, so the context modifier has
never had anything to lift. But one thing HAS survived every test: drift. SP500 and gold rose
for the whole sample, the engine null re-measured it independently (random longs beat random
shorts by +0.08 to +0.10 R), and [[project_anomaly_families_tested]] found it was the single
family to clear cost.
So the question step 2 really asks is: **does a long-only breakout on a drifting instrument
give a base at or above zero, and does anything add to it?**
THREE ARMS, AND THE SECOND IS THE ONE THAT MATTERS
--------------------------------------------------
REAL range breakout in direction d, market entry at the next bar's open
LOCAL the same trade at a RANDOM bar within +/-250 bars - same instrument, same regime,
same drift rate, same barrier geometry, same n. Only the breakout is gone.
GLOBAL a random bar anywhere in the sample
If REAL - LOCAL is zero, the breakout contributes nothing and whatever expectancy the trade
has is exposure, not timing. That is the honest way to report a drift-powered result: it is
beta, and beta is available by buying and holding without paying a spread 3,000 times.
EURUSD and USDJPY are included as NEGATIVE CONTROLS. They barely drifted, so if the "edge"
is drift they should show nothing, and if a cell there lights up while the mechanism says it
should not, the mechanism is wrong rather than the cell being lucky.
"""
import numpy as np, sys, time
sys.stdout.reconfigure(encoding='utf-8', errors='replace')
import fills, book, wyckoff
SYMS = ('EURUSD', 'USDJPY', 'XAUUSD', 'SP500')
MRISK, KR, H = 3.0, 2.0, 200
def trade(bk, f, bars, d, mrisk, kR, H, step):
"""Market entry at the open of `bars`, stop mrisk*ATR away, target kR*risk."""
atr = f.atr(14)
ok = (bars > 300) & (bars < f.n - 5) & np.isfinite(atr[bars]) & (atr[bars] > 0)
bars, d = bars[ok], d[ok]
start = f.i0[bars]
ent = np.where(d > 0, bk.ao[start], bk.bo[start])
risk = mrisk * atr[bars]
o = fills.simulate(bk, start, d, ent - d * risk, ent + d * kR * risk, H * step,
entry=fills.MARKET)
if o is None:
return None
o['indep'] = book.nonoverlap(o['idx'], o['exit_idx'] - o['idx'])
o['bars'] = bars[np.nonzero(o['filled'])[0][o['kept']]]
return o
def run(sym, tf, mrisk=MRISK, kR=KR, seed=3, span=250):
bk = fills.Book(sym)
f = book.frame(sym, tf, bk)
step = book.TF_SEC[tf] // 60
ev = wyckoff.breakouts(f)
if ev is None:
return None
rng = np.random.default_rng(seed)
out = {}
for d0, tag in ((+1, 'long'), (-1, 'short')):
m = ev['d'] == d0
if m.sum() < 150:
continue
bars = ev['i'][m] + 1
ag = ev['ag'][m]
real = trade(bk, f, bars, np.full(m.sum(), d0), mrisk, kR, H, step)
loc = trade(bk, f,
np.clip(bars + rng.integers(-span, span + 1, len(bars)), 301, f.n - 6),
np.full(m.sum(), d0), mrisk, kR, H, step)
glo = trade(bk, f, rng.integers(301, f.n - 6, len(bars)),
np.full(m.sum(), d0), mrisk, kR, H, step)
if real is None:
continue
sel = np.nonzero(real['filled'])[0][real['kept']]
out[tag] = dict(real=real, loc=loc, glo=glo, ag=ag[sel])
return out
def stat(o):
if o is None:
return np.array([0.0])
return o['R'][o['indep']]
def diff_t(a, b):
se = np.sqrt(a.var(ddof=1) / len(a) + b.var(ddof=1) / len(b))
return (a.mean() - b.mean()) / max(se, 1e-12)
if __name__ == '__main__':
syms = [s for s in sys.argv[1:] if s in SYMS] or list(SYMS)
print("=== DRIFT AS A BASE: does the breakout add anything to the exposure? ===")
print(f" stop {MRISK} ATR, target {KR}R, market entry. EURUSD/USDJPY are the")
print(" negative controls - little drift, so they should show little.\n")
print(f" {'sym':>7}{'tf':>4}{'side':>6}{'n':>6}{'REAL':>9}{'t':>6}"
f"{'local':>9}{'global':>9}{'R-local':>9}{'t':>6}{'ctx slope':>11}{'t':>6}")
rows = []
for sym in syms:
for tf in ('H1', 'H4'):
r = run(sym, tf)
if not r:
continue
for tag, v in r.items():
A = stat(v['real']); Lc = stat(v['loc']); G = stat(v['glo'])
sl, tt = book.slope_t(v['real']['R'][v['real']['indep']],
v['ag'][v['real']['indep']])
ex = A.mean() - Lc.mean()
rows.append((sym, tf, tag, ex, A.mean(), sl))
print(f" {sym:>7}{tf:>4}{tag:>6}{len(A):>6}{A.mean():>+9.4f}"
f"{book.tstat(A):>+6.2f}{Lc.mean():>+9.4f}{G.mean():>+9.4f}"
f"{ex:>+9.4f}{diff_t(A, Lc):>+6.2f}{sl:>+11.4f}{tt:>+6.2f}")
if rows:
ex = np.array([r[3] for r in rows])
base = np.array([r[4] for r in rows])
print(f"\n breakout excess over local control: mean {ex.mean():+.4f}, "
f"positive {int((ex>0).sum())}/{len(ex)}")
print(f" base expR: mean {base.mean():+.4f}, positive {int((base>0).sum())}/{len(base)}")
for tag in ('long', 'short'):
b = np.array([r[4] for r in rows if r[2] == tag])
print(f" {tag:>5}: base mean {b.mean():+.4f} positive {int((b>0).sum())}/{len(b)}")