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.
131 lines
5.8 KiB
Python
131 lines
5.8 KiB
Python
"""Did charging one average spread INVENT the context slope?
|
|
|
|
An honest fill model should move the LEVEL of every bucket by the cost and leave the SHAPE
|
|
alone - cost is not supposed to know what the Wyckoff traces said. Yet switching engines cut
|
|
the context slope from +0.0425 (t +3.18) to +0.0239 (t +1.37). Something in the cost is
|
|
correlated with the score.
|
|
|
|
There is an obvious candidate, and it is the thing the bid/ask bars were built to expose:
|
|
EURUSD's p95 spread is 4x its median. Every earlier test charged a single average and
|
|
assumed those hours away. If high-agreement setups happen disproportionately in the wide-
|
|
spread hours - news, thin liquidity, the open - then the old model undercharged exactly the
|
|
bucket the hypothesis cared about, and manufactured part of the slope out of a billing error.
|
|
|
|
THE TEST
|
|
--------
|
|
For the same LPS events, regress the REALISED cost (spread at the fill minute / risk) on the
|
|
agreement count. If the slope of cost-on-agreement is materially non-zero, an average-spread
|
|
model biases the dose-response by that amount per trace, and the direction of the bias says
|
|
which way.
|
|
|
|
Also reported: what the OLD model would have charged (one median spread for the symbol) so
|
|
the two can be compared as money rather than as an argument.
|
|
"""
|
|
import numpy as np, sys
|
|
sys.stdout.reconfigure(encoding='utf-8', errors='replace')
|
|
import fills, book, wyckoff, test_lps2
|
|
|
|
SYMS = ('EURUSD', 'USDJPY', 'XAUUSD', 'SP500')
|
|
|
|
|
|
def costs(sym, tf, **kw):
|
|
"""Same events as the attribution run, but returning the cost actually paid."""
|
|
bk = fills.Book(sym)
|
|
f = book.frame(sym, tf, bk)
|
|
a = test_lps2.events(sym, tf, bk=bk, f=f, **kw)
|
|
if a is None:
|
|
return None
|
|
#--- re-derive the fill minutes so the spread can be read where it was really paid
|
|
return a, bk, f
|
|
|
|
|
|
def run(sym, tf):
|
|
bk = fills.Book(sym)
|
|
f = book.frame(sym, tf, bk)
|
|
step = book.TF_SEC[tf] // 60
|
|
h, l, c = f.h, f.l, f.c
|
|
atr = f.atr(14)
|
|
L, hi_, lo_ = book.find_ranges(h, l, atr, theta=0.60)
|
|
up = (L > 0) & (c > hi_); dn = (L > 0) & (c < lo_)
|
|
fire = np.nonzero(up | dn)[0]
|
|
fire = fire[(fire > 305) & (fire < f.n - 65)]
|
|
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, 61):
|
|
b_ = i + k
|
|
if b_ >= f.n - 2:
|
|
break
|
|
near = (l[b_] <= lvl + 0.35 * atr[i]) if dd > 0 else (h[b_] >= lvl - 0.35 * atr[i])
|
|
if near and ((c[b_] > lvl) if dd > 0 else (c[b_] < lvl)):
|
|
j = b_; break
|
|
if (c[b_] < lvl - 0.35 * atr[i]) if dd > 0 else (c[b_] > lvl + 0.35 * atr[i]):
|
|
break
|
|
if j < 0:
|
|
continue
|
|
e = j + 1
|
|
if e >= f.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 - 200])))
|
|
rows.append((e, dd, stop, ag))
|
|
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]
|
|
ent = np.where(D > 0, bk.ao[start], bk.bo[start])
|
|
risk = np.abs(ent - ST)
|
|
ok = risk > 4 * f.spread[E]
|
|
E, D, ST, AG, start, risk = (v[ok] for v in (E, D, ST, AG, start, risk))
|
|
sp_real = (bk.ac - bk.bc)[start] # what was actually paid, that minute
|
|
sp_med = np.median(bk.ac - bk.bc) # what the old model charged, always
|
|
return dict(ag=AG, cost_real=sp_real / risk, cost_old=sp_med / risk,
|
|
sp_real=sp_real, sp_med=sp_med, n=len(AG))
|
|
|
|
|
|
if __name__ == '__main__':
|
|
syms = [s for s in sys.argv[1:] if s in SYMS] or list(SYMS)
|
|
print("=== IS THE SPREAD CORRELATED WITH THE CONTEXT SCORE? ===")
|
|
print(" if cost rises with agreement, an average-spread model undercharges the")
|
|
print(" bucket the hypothesis cares about and inflates the dose-response.\n")
|
|
print(f" {'sym':>7}{'tf':>5}{'n':>6}{'spread p50':>12}{'p95':>9}{'p95/p50':>9}"
|
|
f"{'cost real':>11}{'cost old':>10}{'d(cost)/trace':>15}{'t':>7}")
|
|
PA, PC, PO = [], [], []
|
|
for tf in ('M15', 'H1'):
|
|
for s in syms:
|
|
r = run(s, tf)
|
|
if r is None:
|
|
print(f" {s:>7}{tf:>5} - too few"); continue
|
|
sl, tt = book.slope_t(r['cost_real'], r['ag'])
|
|
print(f" {s:>7}{tf:>5}{r['n']:>6}{r['sp_med']:>12.5f}"
|
|
f"{np.quantile(r['sp_real'],0.95):>9.5f}"
|
|
f"{np.quantile(r['sp_real'],0.95)/r['sp_med']:>9.2f}"
|
|
f"{r['cost_real'].mean():>11.4f}{r['cost_old'].mean():>10.4f}"
|
|
f"{sl:>+15.5f}{tt:>+7.2f}")
|
|
PA.append(r['ag']); PC.append(r['cost_real']); PO.append(r['cost_old'])
|
|
if PA:
|
|
ag = np.concatenate(PA); cr = np.concatenate(PC); co = np.concatenate(PO)
|
|
sl, tt = book.slope_t(cr, ag)
|
|
so, to = book.slope_t(co, ag)
|
|
print(f"\n POOLED n={len(ag):,}")
|
|
print(f" realised cost slope {sl:+.5f} R/trace t {tt:+.2f} mean {cr.mean():.4f}")
|
|
print(f" old flat cost slope {so:+.5f} R/trace t {to:+.2f} mean {co.mean():.4f}")
|
|
print(f" BIAS an average-spread model puts into the dose-response:"
|
|
f" {so - sl:+.5f} R per trace")
|
|
for k in range(6):
|
|
m = ag == k
|
|
if m.sum() >= 25:
|
|
print(f" {k} agree n={int(m.sum()):>5} realised cost {cr[m].mean():.4f}"
|
|
f" flat {co[m].mean():.4f} undercharged by {cr[m].mean()-co[m].mean():+.4f}")
|