"""Past structure + current structure, combined - the books' actual operational scheme. Two results make this the test worth running: the CONTEXT score is real +0.046 R per agreeing trace, positive slope in 8/8 cells the SHAKEOUT base is hopeless -0.33 at zero agreement, so context cannot rescue it A modifier worth +0.05 R per trace only matters bolted to a trigger whose base expectancy is already near zero. The LPS retest at the PRICE edge is the only trigger measured here that qualifies: EURUSD H1 came in at -0.041, USDJPY H1 at -0.060. Three or four agreeing traces would be worth +0.14 to +0.18 on top. So: same LPS trade, same market order at the next bar's open, same 2R target - scored by the same five Wyckoff traces, read off the range that produced the breakout. And the same pre-specified test: expR must RISE MONOTONICALLY with agreement. The slope is the result; the top bucket is not, because picking it is what mining looks like. """ import numpy as np, sys sys.stdout.reconfigure(encoding='utf-8', errors='replace') from test_retail import load_bars, race_px from test_cause_effect import atr_of, find_ranges from test_context import trend_t SYMS = ('EURUSD', 'USDJPY', 'XAUUSD', 'SP500') def traces(h, l, c, vol, s, i, top, bot, dd, htf_sig): """The five traces of book 2 2.3 / 7.1, each +1 if it agrees with direction dd.""" mid = 0.5 * (top + bot) Lq = i - s th = max(Lq // 3, 2) segA, segB = slice(s, s + th), slice(s + th, s + 2 * th) segC = slice(s + 2 * th, i) t1 = 1 if (h[segA].max() - mid) > (mid - l[segA].min()) else -1 t2 = 1 if (h[segB].max() - mid) > (mid - l[segB].min()) else -1 t3 = 0 if len(c[segC]): if t2 > 0: t3 = 1 if l[segC].min() > bot + 0.25 * (top - bot) else -1 else: t3 = -1 if h[segC].max() < top - 0.25 * (top - bot) else 1 rr = max(h[i] - l[i], 1e-12) clspos = (c[i] - l[i]) / rr if dd > 0 else (h[i] - c[i]) / rr vavg = vol[s:i].mean() if i > s else vol[i] t4 = 1 if (clspos > 0.6 and vol[i] > 1.2 * max(vavg, 1e-12)) else -1 t5 = 1 if htf_sig == dd else -1 return sum(1 for x in (t1 * dd, t2 * dd, t3 * dd, t4, t5) if x > 0) def run(sym, tf='H1', theta=0.60, tol=0.35, wait=60, H=200, path_tf='M5', kR=2.0, htf=200): a1, I1 = load_bars(sym, tf) g = lambda k: a1[:, I1[k]] o, h, l, c = g('open'), g('high'), g('low'), g('close') vol = g('ticks'); spm = g('spread_mean') t1a = a1[:, I1['time']].astype(np.int64) n = len(c) atr = atr_of(h, l, c, 14); atr = np.concatenate([[atr[0]], atr[:-1]]) L, hi_, lo_ = find_ranges(h, l, c, atr, theta=theta) a2, I2 = load_bars(sym, path_tf) ph, pl, pc = a2[:, I2['high']], a2[:, I2['low']], a2[:, I2['close']] pmap = np.searchsorted(a2[:, I2['time']], t1a) step = 12 if tf == 'H1' else (3 if tf == 'M15' else 1) HH = H * step 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 #--- the retest: back to within tol*ATR of the broken edge, closing beyond it 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 pi = int(pmap[e]) if pi + HH >= len(ph): continue ent = o[e] ext = l[j] if dd > 0 else h[j] stop = ext - dd * 0.10 * atr[i] risk = abs(ent - stop) if risk <= 2 * spm[e]: continue ag = traces(h, l, c, vol, s, i, top, bot, dd, int(np.sign(c[i] - c[i - htf]))) rows.append((e, pi, dd, ent, stop, risk, spm[e], ag)) busy = e + Lq if len(rows) < 100: return None A = lambda k: np.array([r[k] for r in rows]) E, P, D, EN, ST, RK, SP, AG = (A(k) for k in range(8)) r = race_px(ph, pl, P, D, ST, EN + D * kR * RK, HH) R = np.where(r > 0, kR, np.where(r < 0, -1.0, 0.0)) un = r == 0 if un.any(): qq = np.minimum(P[un] + HH, len(pc) - 1) R[un] = (pc[qq] - EN[un]) * D[un] / RK[un] return R - SP / RK, AG, t1a[E] if __name__ == '__main__': syms = [s for s in sys.argv[1:] if s in SYMS] or list(SYMS) print("=== LPS RETEST x WYCKOFF CONTEXT ===") print(" the one trigger with a base near zero, scored by the five traces.\n") print(f" {'symbol':>7}{'tf':>5}{'n':>6} " + "".join(f"{k:>10}" for k in range(6)) + f"{'slope':>8}{'t':>7}") PR, PA, PT = [], [], [] for tf in ('M15', 'H1'): for s in syms: out = run(s, tf) if out is None: print(f" {s:>7}{tf:>5} - too few"); continue R, AG, T = out cells = [f"{R[AG==k].mean():+6.2f}({int((AG==k).sum()):>3})" if (AG == k).sum() >= 25 else f"{'-':>10}" for k in range(6)] sl, tt = trend_t(R, AG) print(f" {s:>7}{tf:>5}{len(R):>6} " + "".join(f"{x:>10}" for x in cells) + f"{sl:>+8.3f}{tt:>+7.2f}") PR.append(R); PA.append(AG); PT.append(T) if PR: R = np.concatenate(PR); AG = np.concatenate(PA); T = np.concatenate(PT) sl, tt = trend_t(R, AG) print(f"\n POOLED n={len(R):,} slope {sl:+.4f} R/trace t {tt:+.2f}") for k in range(6): m = AG == k if m.sum() >= 25: se = R[m].std(ddof=1) / np.sqrt(m.sum()) o = np.argsort(T[m]) f = [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.3f}" f" +/- {se:.3f} folds " + "".join(f"{x:+7.2f}" for x in f) + f" {sum(1 for x in f if x>0)}/4")