Отслеживать
1
0
Ответвление
У вас уже есть ответвление Warrior_EA
2
ответвлён от animatedread/Warrior_EA
Warrior_EA/research/deep_test.py
AnimateDread ff46cfd57e research(dipz): the vol-gated dip-buy on four indices - screens, bear test, reconciliation
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>
2026-09-23 13:24:57 -04:00

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()