89 lines
3.8 KiB
Python
89 lines
3.8 KiB
Python
|
|
"""Is "breakouts underperform nearby random entries" a finding, or a property of the control?
|
||
|
|
|
||
|
|
The drift run reported real minus local control at -0.045 R, negative for longs AND shorts,
|
||
|
|
and it did not go away when the barriers were re-sized from a slow ATR. The tempting reading
|
||
|
|
is that breakout continuation is anti-predictive and the fade is worth +0.045.
|
||
|
|
|
||
|
|
Before believing that, look at what the control actually is. A breakout bar is BY
|
||
|
|
CONSTRUCTION a local price extreme - price has just traded beyond the top of a range that
|
||
|
|
contained it for many bars. A control bar drawn uniformly from +/-250 bars around it is
|
||
|
|
therefore drawn from a window in which the real entry sits near the high (for an upward
|
||
|
|
breakout) or near the low (for a downward one).
|
||
|
|
|
||
|
|
Within any window, buying at the top and selling at the bottom is the worst possible entry
|
||
|
|
for a mean-reverting series. So real-minus-control would be negative for both directions even
|
||
|
|
if the breakout carried no information at all. The two arms differ in ENTRY PRICE, not only
|
||
|
|
in timing.
|
||
|
|
|
||
|
|
THE CHECK
|
||
|
|
---------
|
||
|
|
For each event, the percentile of the entry price within the +/-250 bar window, for the real
|
||
|
|
arm and the control arm, oriented so that HIGH means "a worse place to enter for this trade's
|
||
|
|
direction". A fair control sits near 50 for both. If the real arm sits near 90 while the
|
||
|
|
control sits near 50, the -0.045 is the control's geometry and there is nothing to fade.
|
||
|
|
"""
|
||
|
|
import numpy as np, sys
|
||
|
|
sys.stdout.reconfigure(encoding='utf-8', errors='replace')
|
||
|
|
import fills, book, wyckoff
|
||
|
|
|
||
|
|
SYMS = ('EURUSD', 'USDJPY', 'XAUUSD', 'SP500')
|
||
|
|
|
||
|
|
|
||
|
|
def pct_in_window(c, bars, span):
|
||
|
|
"""Percentile of c[bar] within c[bar-span : bar+span], per event."""
|
||
|
|
out = np.empty(len(bars))
|
||
|
|
n = len(c)
|
||
|
|
for k, b in enumerate(bars):
|
||
|
|
lo, hi = max(0, b - span), min(n, b + span + 1)
|
||
|
|
w = c[lo:hi]
|
||
|
|
out[k] = 100.0 * (w < c[b]).mean()
|
||
|
|
return out
|
||
|
|
|
||
|
|
|
||
|
|
def run(sym, tf='H1', span=250, seed=3):
|
||
|
|
bk = fills.Book(sym)
|
||
|
|
f = book.frame(sym, tf, bk)
|
||
|
|
ev = wyckoff.breakouts(f, score=False)
|
||
|
|
if ev is None:
|
||
|
|
return None
|
||
|
|
rng = np.random.default_rng(seed)
|
||
|
|
rows = []
|
||
|
|
for d0 in (+1, -1):
|
||
|
|
m = ev['d'] == d0
|
||
|
|
if m.sum() < 150:
|
||
|
|
continue
|
||
|
|
bars = ev['i'][m] + 1
|
||
|
|
bars = bars[(bars > 300) & (bars < f.n - 5)]
|
||
|
|
ctl = np.clip(bars + rng.integers(-span, span + 1, len(bars)), 301, f.n - 6)
|
||
|
|
pr = pct_in_window(f.c, bars, span)
|
||
|
|
pc = pct_in_window(f.c, ctl, span)
|
||
|
|
#--- orient so HIGH = a worse place to enter for this direction
|
||
|
|
if d0 < 0:
|
||
|
|
pr, pc = 100 - pr, 100 - pc
|
||
|
|
rows.append((d0, len(bars), pr.mean(), pc.mean()))
|
||
|
|
return rows
|
||
|
|
|
||
|
|
|
||
|
|
if __name__ == '__main__':
|
||
|
|
syms = [s for s in sys.argv[1:] if s in SYMS] or list(SYMS)
|
||
|
|
print("=== IS THE LOCAL CONTROL FAIR? ===")
|
||
|
|
print(" entry-price percentile within the +/-250 bar window, oriented so HIGH = a")
|
||
|
|
print(" worse entry for that direction. A fair control sits near 50 on both arms.\n")
|
||
|
|
print(f" {'sym':>7}{'tf':>4}{'side':>6}{'n':>6}"
|
||
|
|
f"{'real pct':>10}{'ctrl pct':>10}{'difference':>12}")
|
||
|
|
d = []
|
||
|
|
for sym in syms:
|
||
|
|
for tf in ('H1', 'H4'):
|
||
|
|
r = run(sym, tf)
|
||
|
|
if not r:
|
||
|
|
continue
|
||
|
|
for d0, n, a, b in r:
|
||
|
|
d.append(a - b)
|
||
|
|
print(f" {sym:>7}{tf:>4}{'long' if d0>0 else 'short':>6}{n:>6}"
|
||
|
|
f"{a:>10.1f}{b:>10.1f}{a-b:>+12.1f}")
|
||
|
|
d = np.array(d)
|
||
|
|
print(f"\n mean real-minus-control percentile: {d.mean():+.1f} points "
|
||
|
|
f"({int((d>0).sum())}/{len(d)} cells worse)")
|
||
|
|
print("\n A large positive number means the two arms are not comparable: the breakout")
|
||
|
|
print(" arm is entering at a systematically worse price within the same window, so")
|
||
|
|
print(" real-minus-control measures the control's geometry, not the signal.")
|