Warrior_EA/research/test_drift.py

121 lines
5.4 KiB
Python
Raw Permalink Normal View History

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