Warrior_EA/research/check_fill.py

87 lines
3.9 KiB
Python
Raw Permalink Normal View History

research: RETRACTION - the retail-fade edge was a fill-timing artifact The +0.097 R EURUSD result in c9b489e is wrong. So is the +0.108 R pooled edge in c2dd9eb and the selection model in 821f16d, which used the same labels. THE BUG. The mirror test entered at the retail trigger price e2 - a STOP order level - but started the outcome race at the OPEN of the fill bar. Price at that open is on the far side of e2 by construction; that is why the order is a stop order. So the race began before price had reached the entry, which handed the fade a free run toward its target and pushed its stop further away than it really was. Retail's side carries the same bias with the sign reversed, so the DIFFERENCE - which is exactly how 'edge' was computed - was inflated twice over. Found by generalising the trigger to swing-extreme breakouts, per the user's suggestion. That version returned +0.7 R at t +110, which is not a result, and it has the identical structure: enter at a level, measure from the bar open. WITH AN HONEST INTRABAR FILL (first M5 bar that actually trades at the entry), every EURUSD cell inverts: bar open honest fill H1 pin +0.0953 -0.0096 H1 pin +0.0559 -0.0573 H1 inside +0.0612 -0.0395 M15 pin +0.0407 -0.0135 The 4/4 walk-forward held because the bias was present in every fold. A walk-forward validates against regime change, not against a broken fill model. The tell was there and I walked past it: sweep_entry() was the ONE test that modelled the fill properly, and it was the ONE test that came out negative. When one arm of a suite disagrees with the rest, check what it does differently before believing the majority. So the standing conclusion returns to what it was: retail setups are close to a coin flip that pays the spread, and there is nothing in them to harvest. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-01 23:17:55 -04:00
"""Does the EURUSD fade survive a HONEST intrabar fill?
The mirror test entered at the retail trigger price e2 but started the outcome race at the
OPEN of the fill bar. Price at that open is on the far side of e2 - that is why the entry is
a stop order in the first place. So the race began before price had actually reached the
entry, handing the fade a free run toward its target and pushing the stop further away than
it really was.
That is a bias in the fade's favour and it is the same shape as the one that produced +0.7 R
in the swing test. It has to be measured, not argued about: find the first M5 bar that
actually trades AT e2, start the race there, and see what is left.
"""
import numpy as np, sys
sys.stdout.reconfigure(encoding='utf-8', errors='replace')
from test_retail import setups, triggered, race_px, load_bars
SYMS = ('EURUSD', 'USDJPY', 'XAUUSD', 'SP500')
def run(sym, tf, H=200, ms=(1.0,), path_tf='M5', honest=True):
ev, o, h, l, c, spm, tick = setups(sym, tf)
a1, I1 = load_bars(sym, tf)
t1 = a1[:, I1['time']].astype(np.int64)
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']], t1)
step = 12 if tf == 'H1' else 3
HH = H * step
out = []
for name, idx, d, ent, stp in ev:
ok, fill = triggered(h, l, idx, d, ent)
if ok.sum() < 100:
continue
i2, d2, e2, s2 = fill[ok], d[ok], ent[ok], stp[ok]
risk0 = np.abs(e2 - s2)
g = risk0 > 2 * spm[i2]
i2, d2, e2, risk0 = (v[g] for v in (i2, d2, e2, risk0))
pi = np.clip(pmap[i2], 0, len(ph) - 1)
start = pi
if honest:
#--- first M5 bar INSIDE the fill bar that actually trades at the entry level
start = np.full(len(pi), -1, np.int64)
live = np.ones(len(pi), bool)
for k in range(0, step + 2):
j = pi + k
m = live & (j < len(ph))
if not m.any():
break
hit = np.where(d2[m] > 0, ph[j[m]] >= e2[m], pl[j[m]] <= e2[m])
w = np.nonzero(m)[0][hit]
start[w] = j[w]; live[w] = False
good = start >= 0
i2, d2, e2, risk0, start = (v[good] for v in (i2, d2, e2, risk0, start))
keep = start + HH < len(ph)
i2, d2, e2, risk0, start = (v[keep] for v in (i2, d2, e2, risk0, start))
if len(start) < 200:
continue
sp = spm[np.maximum(i2 - 1, 0)]
dd = -d2 # the fade
for m_ in ms:
R0 = np.maximum(m_ * risk0, 2 * sp)
r = race_px(ph, pl, start, dd, e2 - dd * R0, e2 + dd * R0, HH)
R = np.where(r > 0, 1.0, np.where(r < 0, -1.0, 0.0))
un = r == 0
if un.any():
q = np.minimum(start[un] + HH, len(pc) - 1)
R[un] = (pc[q] - e2[un]) * dd[un] / R0[un]
R = R - sp / R0
se = R.std(ddof=1) / np.sqrt(len(R))
f = np.array_split(R, 4)
out.append((name, m_, len(R), R.mean(), R.mean() / max(se, 1e-12),
[float(x.mean()) for x in f]))
return out
if __name__ == '__main__':
syms = [s for s in sys.argv[1:] if s in SYMS] or ['EURUSD']
for honest, lbl in ((False, 'race from the BAR OPEN (what the mirror test did)'),
(True, 'race from the ACTUAL FILL (honest)')):
print(f"\n=== {lbl} ===")
print(f" {'symbol':>7}{'tf':>5} {'setup':<8}{'n':>7}{'fade expR':>11}{'t':>8}"
f" {'Q1':>7}{'Q2':>7}{'Q3':>7}{'Q4':>7}")
for tf in ('M15', 'H1'):
for s in syms:
for r in run(s, tf, honest=honest):
print(f" {s:>7}{tf:>5} {r[0]:<8}{r[2]:>7}{r[3]:>+11.4f}{r[4]:>+8.2f}"
+ "".join(f"{x:>+7.3f}" for x in r[5]))