Отслеживать
1
0
Ответвление
У вас уже есть ответвление Warrior_EA
2
ответвлён от animatedread/Warrior_EA
Warrior_EA/research/fx_unsup.py
2026-10-05 21:58:23 -04:00

82 строки
5,1 КиБ
Python

"""
Unsupervised pattern search on FOREX only (no long-bias drift), 2026-10-05. PRE-REGISTERED before any result:
data: EURUSD GBPUSD USDJPY AUDUSD USDCAD H1, tick volume, 2000-2024 (2025+ sealed).
sample: every 12th bar per pair (non-overlapping 12-bar forward return).
features at bar t (causal): last 32 close-to-close log returns / ATR14-return-scale (shape), 32 relative tick volumes
(same-hour median of prior 20 sessions), hour-of-day NOT included. Standardised, no labels used.
models (fit on TRAIN 2000-2012 only): (A) PCA12 + KMeans K=24 ; (B) MLP autoencoder (bottleneck 8) + KMeans K=24.
cluster -> direction = sign of its TRAIN mean 12-bar forward return (price units / ATR). Clusters kept only if |t_train|>2.
VALID 2013-2017 must agree in sign; TEST 2018-2024 is read ONCE. Net cost = COST_BP round trip.
Success = pooled train-signed TEST mean net of cost > 0 with t > 2, and more clusters pass than chance (~2.5%).
"""
import os, sys, numpy as np, pandas as pd
from sklearn.decomposition import PCA
from sklearn.cluster import KMeans
from sklearn.neural_network import MLPRegressor
from sklearn.preprocessing import StandardScaler
sys.path.insert(0, os.path.dirname(__file__))
import discover as D
import wyckoff_scan as S
PAIRS = ["EURUSD", "GBPUSD", "USDJPY", "AUDUSD", "USDCAD"]; W, H, STEP, K = 32, 12, 12, 24
rows = []
for s in PAIRS:
b = D.load_h1(s); b = b[(b.index < D.SEAL) & (b.index.year >= 2000)]
c = b.c.to_numpy(float); r = np.r_[0, np.diff(np.log(c))]
sc = pd.Series(np.abs(r)).rolling(14).mean().to_numpy() + 1e-12
rv = S.rel_volume(b)
idx = np.arange(300, len(b) - H - 1, STEP)
for t in idx:
if not np.isfinite(rv[t - W + 1:t + 1]).all():
continue
x = np.r_[r[t - W + 1:t + 1] / sc[t], np.log(np.clip(rv[t - W + 1:t + 1], .05, 20))]
fwd = (c[t + H] - c[t + 1]) / c[t] / sc[t] # entry next close-ish, ATR-return units
cost = D.COST_BP[s] * 1e-4 / sc[t] * 2 # round trip in same units (sc is per-bar |ret|)
rows.append((s, b.index[t], fwd, cost, x))
df = pd.DataFrame(rows, columns=["sym", "t", "fwd", "cost", "x"])
X = np.vstack(df.x.to_numpy()); y = df.fwd.to_numpy(); cst = df.cost.to_numpy(); yr = df.t.dt.year.to_numpy()
tr, va, te = yr <= 2012, (yr >= 2013) & (yr <= 2017), yr >= 2018
print("samples", len(df), "train", tr.sum(), "valid", va.sum(), "test", te.sum())
tt = lambda a: a.mean() / (a.std(ddof=1) / np.sqrt(len(a))) if len(a) > 5 and a.std() > 0 else np.nan
scal = StandardScaler().fit(X[tr]); Z = np.clip(scal.transform(X), -6, 6)
emb = {}
p = PCA(12, random_state=0).fit(Z[tr]); emb["PCA12"] = p.transform(Z)
ae = MLPRegressor(hidden_layer_sizes=(48, 8, 48), activation="tanh", max_iter=60, random_state=0, early_stopping=True).fit(Z[tr], Z[tr])
def enc(Zs):
h = np.tanh(Zs @ ae.coefs_[0] + ae.intercepts_[0]); return np.tanh(h @ ae.coefs_[1] + ae.intercepts_[1])
emb["AE8"] = enc(Z)
print("AE recon R2 (test)", round(1 - ((ae.predict(Z[te]) - Z[te]) ** 2).sum() / ((Z[te] - Z[te].mean(0)) ** 2).sum(), 3))
for name, E in emb.items():
km = KMeans(K, n_init=5, random_state=0).fit(E[tr]); lab = km.predict(E)
keep, tot_te = [], []
print(f"\n== {name}")
print("cl n_tr mean_tr t_tr | n_va mean_va | n_te mean_te(signed,net) t_te")
for k in range(K):
m = lab == k; a = y[tr & m]
if len(a) < 100: continue
d = np.sign(a.mean()); ttr = tt(a)
v = (y[va & m] * d - cst[va & m]); e = (y[te & m] * d - cst[te & m])
flag = abs(ttr) > 2 and v.mean() > 0
if flag: keep.append(k); tot_te.append(e)
print(f"{k:2d} {len(a):5d} {a.mean():+.4f} {ttr:+6.2f} | {len(v):4d} {v.mean():+.4f} | {len(e):4d} {e.mean():+.4f} {tt(e):+5.2f} {'<-- kept' if flag else ''}")
if tot_te:
e = np.concatenate(tot_te); print(f"KEPT {keep}: pooled TEST net signed mean {e.mean():+.4f} t {tt(e):+.2f} n {len(e)}")
else:
print("no cluster passed train+valid")
# chance reference: random labels, same procedure
rng = np.random.default_rng(1); ref = []
for _ in range(200):
l2 = rng.integers(0, K, len(y)); kp = []
for k in range(K):
m = l2 == k; a = y[tr & m]
if len(a) < 100: continue
d = np.sign(a.mean())
if abs(tt(a)) > 2 and (y[va & m] * d - cst[va & m]).mean() > 0: kp.append((y[te & m] * d - cst[te & m]))
if kp: ref.append(np.concatenate(kp).mean())
print(f"random-label reference: kept something in {len(ref)}/200 runs, mean of their TEST net {np.mean(ref) if ref else float('nan'):+.4f}")
# gross diagnostic (not part of the pre-registered test): train-signed ALL clusters, TEST, before cost
for name, E in emb.items():
km = KMeans(K, n_init=5, random_state=0).fit(E[tr]); lab = km.predict(E)
d = np.array([np.sign(y[tr & (lab == k)].mean()) if (tr & (lab == k)).sum() > 100 else 0 for k in range(K)])[lab]
g = (y * d)[te & (d != 0)]; c_ = cst[te & (d != 0)]
print(f"{name} gross TEST signed mean {g.mean():+.4f} t {tt(g):+.2f} ; mean cost {c_.mean():.3f} (units: per-bar ATR-return)")