forked from animatedread/Warrior_EA
127 lines
4.4 KiB
Python
127 lines
4.4 KiB
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)
|