ответвлён от animatedread/Warrior_EA
Recovered live config (z20 <= -1.5, exit SMA20 / 10 bars, 3xATR) plus a Garman-Klass vol-regime gate. Expectancy is monotone in the vol regime in IS, OOS and full sample, 4/4 indices; the gate reverses on USDJPY/XAUUSD. D1 2008-2026 survives 2008/2020/2022 (maxDD 2.8%, ret/DD 7.74); the gate halves trades, so it belongs on H4, never D1. reconcile.py matches the EA to the backtest trade by trade; combine_charts.py rebuilds the account curve from per-chart tester runs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
101 строка
3,6 КиБ
Python
101 строка
3,6 КиБ
Python
"""
|
|
THE OUTSTANDING VALIDATION: does the vol-gated dip-z survive a bear market?
|
|
|
|
The H4 result (STRATEGY.md) is built on 2021-2026, which contains no 2008 and no
|
|
2020. This family's losses are concentrated in bear ONSETS, so a sample without
|
|
one proves very little about the drawdown that matters.
|
|
|
|
Broker intraday history does not reach back far enough, so the bear test runs on
|
|
D1, where the indices go back to 2008. The D1 rule is the same shape as the H4
|
|
one; it is a proxy, and the point is not to re-measure the edge but to see what
|
|
the drawdown does when the market actually falls.
|
|
|
|
Reported per calendar year, because an average over 18 years hides exactly the
|
|
thing being looked for.
|
|
"""
|
|
|
|
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
|
|
from run_screen import zscore_entries # noqa: E402
|
|
from vol_filter_test import vol_pctile # noqa: E402
|
|
|
|
STOP_ATR = 3.0
|
|
PROP_DD = 0.05
|
|
IDX = ["SP500", "NAS100", "US30", "DAX40"]
|
|
|
|
|
|
def trades_for(symbols, period, gated, max_bars=10):
|
|
out = []
|
|
for s in symbols:
|
|
try:
|
|
d = bt.load(s, period)
|
|
except Exception: # noqa: BLE001
|
|
continue
|
|
e, xma = zscore_entries(d, 20, -1.5, 0)
|
|
if gated:
|
|
e = e & (np.nan_to_num(vol_pctile(d), nan=-1) >= 0.50)
|
|
tr = bt.simulate(d, e, side=1, exit_ma=xma, max_bars=max_bars, stop_atr=STOP_ATR)
|
|
tr = bt.add_r(tr, d, stop_atr=STOP_ATR)
|
|
for t in tr:
|
|
t["symbol"] = s
|
|
t["exit_t"] = d["ts"][t["exit_i"]]
|
|
out += tr
|
|
out.sort(key=lambda t: t["exit_t"])
|
|
return out
|
|
|
|
|
|
def curve_stats(trades, risk):
|
|
eq = [1.0]
|
|
for t in trades:
|
|
eq.append(eq[-1] * (1.0 + risk * t["r"]))
|
|
eq = np.array(eq)
|
|
peak = np.maximum.accumulate(eq)
|
|
dd = (peak - eq) / peak
|
|
return eq, dd
|
|
|
|
|
|
def per_year(trades, risk):
|
|
"""Year-by-year return and the worst drawdown inside that year."""
|
|
years = sorted({int(str(t["exit_t"])[:4]) for t in trades})
|
|
rows = []
|
|
for y in years:
|
|
sub = [t for t in trades if int(str(t["exit_t"])[:4]) == y]
|
|
if not sub:
|
|
continue
|
|
eq, dd = curve_stats(sub, risk)
|
|
rows.append((y, len(sub), eq[-1] - 1.0, dd.max(),
|
|
float(np.mean([t["ret"] for t in sub])) * 1e4))
|
|
return rows
|
|
|
|
|
|
if __name__ == "__main__":
|
|
risk = 0.0025
|
|
print("=== D1, 4 indices, dip-z (z<=-1.5, exit SMA20, 10 bars, stop 3ATR), "
|
|
f"risk {risk:.2%} ===\n")
|
|
for gated in (False, True):
|
|
tag = "VOL-GATED (pct>=0.50)" if gated else "UNGATED"
|
|
tr = trades_for(IDX, "PERIOD_D1", gated)
|
|
if not tr:
|
|
print(f"{tag}: no trades / no data")
|
|
continue
|
|
eq, dd = curve_stats(tr, risk)
|
|
t0 = min(t["t"] for t in tr)
|
|
t1 = max(t["exit_t"] for t in tr)
|
|
yrs = (t1 - t0) / np.timedelta64(365, "D")
|
|
total = eq[-1] - 1.0
|
|
rdd = total / dd.max() if dd.max() > 1e-9 else np.nan
|
|
print(f"--- {tag} --- {str(t0)[:10]} .. {str(t1)[:10]} "
|
|
f"n={len(tr)} {len(tr)/max(yrs*12,1e-9):.1f}/mo "
|
|
f"total {total:.1%} CAGR {eq[-1]**(1/max(yrs,1e-9))-1:.1%} "
|
|
f"maxDD {dd.max():.1%} ret/DD {rdd:.2f}")
|
|
print(f"{'year':>6}{'n':>5}{'return':>9}{'maxDD':>8}{'bp/trade':>10}")
|
|
for y, n, r, d_, bp in per_year(tr, risk):
|
|
mark = " <-- bear" if y in (2008, 2011, 2015, 2018, 2020, 2022) else ""
|
|
print(f"{y:>6}{n:>5}{r:>9.2%}{d_:>8.2%}{bp:>10.1f}{mark}")
|
|
print()
|