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.")
|