Warrior_EA/research/breadth.py

94 lines
4.3 KiB
Python
Raw Permalink Normal View History

"""Five more instruments, to settle whether the context slope is a small real effect or nothing.
On the honest engine the Wyckoff context slope came in at +0.0239 R/trace, t +1.37, with 6 of
8 cells keeping the sign. That is the awkward middle: too weak to trade, too consistent to
dismiss. The only way past it is more independent instruments, and `sqxbars.py` has just made
five available that share no data path with the four already used - FTSE, UK100, WTI (two
feeds) and USDCAD.
WHAT IS APPROXIMATED HERE, AND WHY IT IS ACCEPTABLE
---------------------------------------------------
These files carry M1 bars with no ask, so the bid/ask book is SYNTHESISED by applying a
relative spread. That is a real approximation and it is only defensible for this particular
question, because the thing being measured is a SLOPE across context buckets and the cost was
already measured to be almost uncorrelated with the context score (+0.0008 R per trace,
t +0.85, against an effect that would have to be ~50x larger). A cost assumption shifts every
bucket together; it cannot manufacture or hide a slope.
The BASE level is a different matter and is quoted as approximate throughout.
Spreads are set as a FRACTION of price rather than in points, which also makes the whole
thing invariant to the decimal scale - convenient, because these symbols have no reference
series to calibrate a scale against, and every result here is in ATR units or R-multiples
anyway. Values are taken from the four instruments where the real spread IS known:
EURUSD 0.45 bp, USDJPY 0.64 bp, SP500 1.3 bp, XAUUSD 2.1 bp - and rounded UP, so the base is
pessimistic rather than flattering.
"""
import numpy as np, sys, os
import book, sqxbars
#--- relative half-spread inputs, in basis points of price, rounded up from measured peers
SPREAD_BP = {
'GBRIDXGBP_dukascopy__the5ers': 1.5, # FTSE 100 cash index
'UK100_the5ers': 1.5,
'LIGHTCMDUSD_dukascopy__the5ers': 4.0, # WTI crude
'XTIUSD_the5ers': 4.0,
'USDCAD_dukascopy__the5ers': 1.0,
}
NICE = {
'GBRIDXGBP_dukascopy__the5ers': 'FTSE100',
'UK100_the5ers': 'UK100',
'LIGHTCMDUSD_dukascopy__the5ers': 'WTI_d',
'XTIUSD_the5ers': 'WTI_5',
'USDCAD_dukascopy__the5ers': 'USDCAD',
}
class MidBook:
"""A fills.Book built from mid-only M1 bars by applying a relative spread.
Quacks like fills.Book. The spread is multiplicative so it scales with price over a
20-year sample instead of being a constant that is generous in 2012 and absurd in 2026.
"""
def __init__(self, bars, bp):
t, o, h, l, c = (bars[:, k] for k in range(5))
self.t = t.astype(np.int64)
self.n = len(t)
e = 0.5 * bp * 1e-4
self.bo, self.bh, self.bl, self.bc = o * (1 - e), h * (1 - e), l * (1 - e), c * (1 - e)
self.ao, self.ah, self.al, self.ac = o * (1 + e), h * (1 + e), l * (1 + e), c * (1 + e)
def index_at(self, t_ms):
return np.searchsorted(self.t, np.asarray(t_ms, np.int64), 'left')
def get(sym, tf, decimals=6):
"""-> (MidBook, Frame). Decimals are irrelevant to every statistic computed on these."""
bars, _ = sqxbars.load(sym, 'M1', decimals=decimals, verbose=False)
#--- drop duplicate and out-of-order minutes; a resample assumes a sorted, unique index
t = bars[:, 0].astype(np.int64)
keep = np.concatenate(([True], np.diff(t) > 0))
bars = bars[keep]
bk = MidBook(bars, SPREAD_BP.get(sym, 1.5))
t, o, h, l, c, v, i0, sp = book._resample(bk, tf)
return bk, book.Frame(NICE.get(sym, sym), tf, t, o, h, l, c, v, i0, sp)
if __name__ == '__main__':
sys.stdout.reconfigure(encoding='utf-8', errors='replace')
import datetime as dt
print("=== BREADTH INSTRUMENTS ===")
print(f" {'symbol':>9}{'M1 bars':>12}{'H1 bars':>10} range"
f"{'':>14}{'median px':>12}{'spread/ATR H1':>15}")
for s in SPREAD_BP:
try:
bk, f = get(s, 'H1')
except Exception as ex:
print(f" {NICE.get(s,s):>9} FAILED: {ex}")
continue
a = dt.datetime.fromtimestamp(f.t[0] / 1000, dt.UTC)
b = dt.datetime.fromtimestamp(f.t[-1] / 1000, dt.UTC)
print(f" {NICE[s]:>9}{bk.n:>12,}{f.n:>10,} {a:%Y-%m-%d}..{b:%Y-%m-%d}"
f"{np.median(f.c):>12.2f}"
f"{np.nanmedian(f.spread/np.maximum(f.atr(),1e-12)):>15.4f}")