ответвлён от animatedread/Warrior_EA
133 строки
5,4 КиБ
Python
133 строки
5,4 КиБ
Python
"""
|
|
Move catalogue (2026-10-02): how many big moves exist, in BOTH directions, at EVERY scale,
|
|
and how much of each is left after the real spread. Hindsight is used ONLY to label legs;
|
|
nothing here is a feature. Reads the fleet terminal's .hcc read-only.
|
|
|
|
Scale ladder: zigzag legs whose reversal is k x (causal local vol) for k in K_LADDER, so a move
|
|
is "big" relative to its own context, at several scales at once (no single threshold).
|
|
Cost: median broker spread (points) x point size, per year.
|
|
|
|
usage: python moves_catalogue.py SP500 XAUUSD ... [--tf 60]
|
|
"""
|
|
from __future__ import annotations
|
|
import os, sys
|
|
import numpy as np
|
|
import pandas as pd
|
|
|
|
sys.path.insert(0, os.path.dirname(__file__))
|
|
import hcc
|
|
|
|
ROOT = (r"C:\Users\admin\AppData\Roaming\MetaQuotes\Terminal\10CE948A1DFC9A8C27E56E827008EBD4"
|
|
r"\bases\FivePercentOnline-Real\history")
|
|
K_LADDER = (2.0, 4.0, 8.0, 16.0)
|
|
VOL_N = 100
|
|
|
|
|
|
def sym_name(d):
|
|
return os.path.basename(d)
|
|
|
|
|
|
def load(symbol: str) -> pd.DataFrame:
|
|
d = os.path.join(ROOT, symbol)
|
|
parts = []
|
|
for f in sorted(os.listdir(d)):
|
|
if f.endswith(".hcc"):
|
|
try:
|
|
a = hcc.read_hcc(os.path.join(d, f))
|
|
except PermissionError: # current-year file is locked by the running terminal
|
|
print(f" (skipped locked {sym_name(d)}/{f})"); continue
|
|
if len(a):
|
|
parts.append(a)
|
|
a = np.concatenate(parts)
|
|
a = a[np.argsort(a["t"], kind="stable")]
|
|
df = pd.DataFrame({"o": a["o"], "h": a["h"], "l": a["l"], "c": a["c"], "tv": a["tv"], "sp": a["sp"]},
|
|
index=pd.to_datetime(a["t"], unit="s"))
|
|
return df[~df.index.duplicated()]
|
|
|
|
|
|
def point_size(c: np.ndarray) -> float:
|
|
d = np.abs(np.diff(c[-200000:]))
|
|
d = d[d > 0]
|
|
return float(np.percentile(d, 1)) if len(d) else 1e-5
|
|
|
|
|
|
def resample(m1: pd.DataFrame, minutes: int) -> pd.DataFrame:
|
|
r = m1.resample(f"{minutes}min")
|
|
out = pd.DataFrame({"o": r.o.first(), "h": r.h.max(), "l": r.l.min(), "c": r.c.last(),
|
|
"tv": r.tv.sum(), "sp": r.sp.median(), "n": r.o.count()})
|
|
return out[out.n >= max(1, minutes // 10)].drop(columns="n")
|
|
|
|
|
|
def local_vol(b: pd.DataFrame) -> np.ndarray:
|
|
pc = b.c.shift(1)
|
|
tr = np.maximum(b.h - b.l, np.maximum((b.h - pc).abs(), (b.l - pc).abs()))
|
|
return tr.rolling(VOL_N, min_periods=VOL_N // 2).mean().shift(1).to_numpy() # causal
|
|
|
|
|
|
def zigzag(h, l, vol, k):
|
|
"""Completed legs (start_idx, end_idx, dir, size) between confirmed pivots; confirmation = reversal of k*vol."""
|
|
n = len(h)
|
|
i0 = next((i for i in range(n) if np.isfinite(vol[i])), None)
|
|
legs = []
|
|
if i0 is None:
|
|
return legs
|
|
hi_i = lo_i = i0
|
|
d = 0
|
|
piv_i, piv_p = None, None # last confirmed pivot
|
|
for i in range(i0 + 1, n):
|
|
thr = k * vol[i]
|
|
if d == 0:
|
|
if h[i] > h[hi_i]: hi_i = i
|
|
if l[i] < l[lo_i]: lo_i = i
|
|
if h[hi_i] - l[lo_i] >= thr:
|
|
if hi_i > lo_i: # rose first: lo is the first pivot, now going up
|
|
piv_i, piv_p, d = lo_i, l[lo_i], 1
|
|
else:
|
|
piv_i, piv_p, d = hi_i, h[hi_i], -1
|
|
ext_i = i
|
|
ext_i = hi_i if d == 1 else lo_i
|
|
elif d == 1:
|
|
if h[i] >= h[ext_i]: ext_i = i
|
|
elif h[ext_i] - l[i] >= thr: # up-leg confirmed
|
|
legs.append((piv_i, ext_i, 1, h[ext_i] - piv_p))
|
|
piv_i, piv_p, d, ext_i = ext_i, h[ext_i], -1, i
|
|
else:
|
|
if l[i] <= l[ext_i]: ext_i = i
|
|
elif h[i] - l[ext_i] >= thr: # down-leg confirmed
|
|
legs.append((piv_i, ext_i, -1, piv_p - l[ext_i]))
|
|
piv_i, piv_p, d, ext_i = ext_i, l[ext_i], 1, i
|
|
return legs
|
|
|
|
|
|
def main(symbols, tf):
|
|
for sym in symbols:
|
|
m1 = load(sym)
|
|
yr = m1.groupby(m1.index.year).size()
|
|
print(f"\n===== {sym} M1 rows/year: " + " ".join(f"{y}:{n // 1000}k" for y, n in yr.items()))
|
|
pt = point_size(m1.c.to_numpy())
|
|
b = resample(m1, tf)
|
|
vol = local_vol(b)
|
|
cost = (b.sp.fillna(b.sp.median()) * pt).to_numpy() # spread in price units, per bar
|
|
h, l, c = b.h.to_numpy(), b.l.to_numpy(), b.c.to_numpy()
|
|
years = b.index.year.to_numpy()
|
|
rows = []
|
|
for k in K_LADDER:
|
|
for (s, e, d, size) in zigzag(h, l, vol, k):
|
|
rows.append((k, years[s], d, size, size / max(cost[s], pt), e - s))
|
|
t = pd.DataFrame(rows, columns=["k", "year", "dir", "size", "x_spread", "bars"])
|
|
if t.empty:
|
|
print("no legs"); continue
|
|
g = t.groupby(["k", "dir"]).agg(n=("size", "size"), med_x_spread=("x_spread", "median"),
|
|
share_ge3=("x_spread", lambda s: (s >= 3).mean()),
|
|
med_bars=("bars", "median"))
|
|
print(f"point~{pt:g} tf={tf}m bars={len(b)}")
|
|
print(g.round(2).to_string())
|
|
pv = t[t.k == 4.0].pivot_table(index="year", columns="dir", values="size", aggfunc="size").fillna(0).astype(int)
|
|
print("legs/year at k=4 (dir -1 / +1):"); print(pv.T.to_string())
|
|
|
|
|
|
if __name__ == "__main__":
|
|
args = [a for a in sys.argv[1:] if not a.startswith("--")]
|
|
tf = int(sys.argv[sys.argv.index("--tf") + 1]) if "--tf" in sys.argv else 60
|
|
args = [a for a in args if not a.isdigit()]
|
|
main(args, tf)
|