Отслеживать
1
0
Ответвление
У вас уже есть ответвление Warrior_EA
2
ответвлён от animatedread/Warrior_EA
Warrior_EA/research/fx_round2.py
AnimateDread 6192393511 research(fx): forex and metals - thirteen registered families, nothing passed
Hypotheses were registered in FX_PLAN.md before each round. Trend,
breakout, cross reversion, hour seasonality, month-end USD, carry-cross
dip-buy, metals dip-buy and flight-to-safety all fail the bar.

The weekend-gap fade looked like the best result of the project on bar
data (OOS t 20, 28/28 pairs) and loses on real ticks (EURCHF PF 0.52,
AUDNZD PF 0.53): the Sunday-open spread is as wide as the gap.
WarriorGapFade is kept as the research artifact that proved it and is
flagged DO NOT TRADE.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-23 13:24:57 -04:00

127 строки
4,4 КиБ
Python

"""
Round 2 of the forex/metals search (FX_PLAN.md, registered before running).
python fx_round2.py X1|X2|X3|X4
X2 DEVIATION, recorded rather than hidden: broker SP500 H4 history starts in
2021, so the registered 2016 split is impossible on H4. X2 is therefore run
twice: H4 2021-2026 split at 2024 (the split every index result used), and D1
2008-2026 split at 2016 (the registered split, one timeframe up).
"""
from __future__ import annotations
import sys
import numpy as np
sys.path.insert(0, __file__.rsplit("\\", 1)[0] if "\\" in __file__ else ".")
import backtest as bt # noqa: E402
import fx_screen as fs # noqa: E402
from run_screen import zscore_entries # noqa: E402
from vol_filter_test import vol_pctile # noqa: E402
RISK = ["AUDJPY", "NZDJPY", "CADJPY", "AUDUSD", "NZDUSD", "USDJPY"]
METALS = ["XAUUSD", "XAGUSD", "XPTUSD", "XPDUSD", "XAUEUR"]
def as_rows(trades):
return [dict(i=t["entry_i"], j=t["exit_i"], t=t["t"], side=1, ret=t["ret"],
gross=t["gross"], bars=t["bars"]) for t in trades]
def dipz(d, gated):
e, xma = zscore_entries(d, 20, -1.5, 0)
if gated:
e = e & (np.nan_to_num(vol_pctile(d), nan=-1) >= 0.50)
return as_rows(bt.simulate(d, e, side=1, exit_ma=xma, max_bars=10, stop_atr=3.0))
def run_dipz(syms, label):
for gated in (False, True):
rows, allt = [], []
for s in syms:
d = fs.load(s, "H4")
tr = dipz(d, gated)
allt.append(tr)
rows.append(fs.score(s, d, tr))
fs.pooled(allt, f"{label} gate={'on' if gated else 'off'}")
fs.report(f"{label} long-only z-dip H4, gate {'ON' if gated else 'off'}", rows, 2 * len(syms))
def x2(tf, split, spx_file):
"""SP500 dip signal -> long the risk cross, filled at the cross's next open."""
import run_screen as rs
old = bt.COMMON
spx = bt.load("SP500", spx_file)
e, _ = rs.zscore_entries(spx, 20, -1.5, 0)
if tf == "H4":
e = e & (np.nan_to_num(vol_pctile(spx), nan=-1) >= 0.50)
sig_t = set(spx["ts"][e].astype("datetime64[s]").tolist())
fs_split = fs.SPLIT
fs.SPLIT = np.datetime64(split)
rows, allt = [], []
for s in RISK:
d = fs.load(s, tf)
ent = np.array([t in sig_t for t in d["ts"].astype("datetime64[s]").tolist()])
m = bt.sma(d["c"], 20)
tr = as_rows(bt.simulate(d, ent, side=1, exit_ma=m, max_bars=10, stop_atr=3.0))
lo = np.datetime64(str(spx["ts"][0])[:10])
tr = [t for t in tr if t["t"] >= lo]
allt.append(tr)
rows.append(fs.score(s, d, tr))
fs.pooled(allt, f"X2 {tf}")
fs.report(f"X2 SP500-dip -> long risk FX, {tf}, split {split}", rows, len(RISK))
fs.SPLIT = fs_split
bt.COMMON = old
def x3(k):
"""Weekend gap fade on H1."""
rows, allt = [], []
for s in fs.ALL:
d = fs.load(s, "H1")
o, h, l, c, ts = d["o"], d["h"], d["l"], d["c"], d["ts"]
a = bt.atr(h, l, c, 14)
day = ts.astype("datetime64[D]")
gapi = np.where((ts[1:] - ts[:-1]) > np.timedelta64(36, "h"))[0] + 1
tr = []
for i in gapi:
if not np.isfinite(a[i - 1]) or a[i - 1] <= 0:
continue
gap = o[i] - c[i - 1]
if abs(gap) < k * a[i - 1]:
continue
side = -int(np.sign(gap))
entry, target = o[i], c[i - 1]
stop = entry - side * 3.0 * a[i - 1]
px, j = None, i
while j < len(c) and day[j] == day[i]:
if (side > 0 and l[j] <= stop) or (side < 0 and h[j] >= stop):
px = stop
break
if (side > 0 and h[j] >= target) or (side < 0 and l[j] <= target):
px = target
break
j += 1
if px is None:
j = j - 1
px = c[j]
tr.append(fs.trade(d, i, j, side, entry, px))
allt.append(tr)
rows.append(fs.score(s, d, tr))
fs.pooled(allt, f"X3 k={k}")
fs.report(f"X3 weekend gap fade, k={k}", rows, 2 * len(fs.ALL))
if __name__ == "__main__":
w = sys.argv[1]
if w == "X1":
run_dipz(RISK, "X1 risk crosses")
elif w == "X4":
run_dipz(METALS, "X4 metals")
elif w == "X2":
x2("H4", "2024-01-01", "PERIOD_H4")
x2("D1", "2016-01-01", "PERIOD_D1")
elif w == "X3":
for k in (0.5, 1.0):
x3(k)