43 行
2.7 KiB
Python
43 行
2.7 KiB
Python
"""
| |||
PRE-REGISTERED (written before the run, trial N=12): does restricting H4 dip entries to the LAST bar
| |||
of the trading day beat all-bar entries? Same config (z20<-1.5, exit at mean, 3 ATR stop, <=10 bars),
| |||
no new thresholds. Survives only if last-bar ret/DD >= all-bar in the pooled book AND in >=3 of 4
| |||
indices (per-trade bp) AND in >=4 of 6 calendar years 2021-26 (per-trade bp), keeping >=40% of trades.
| |||
Also reports holding period / exit mix (descriptive).
| |||
"""
| |||
import sys, numpy as np, pandas as pd
| |||
sys.path.insert(0, ".")
| |||
import backtest as bt, macro_gates as mg
| |||
from run_screen import zscore_entries
| |||
def book(period, last_only):
| |||
out = []
| |||
for s in mg.IDX:
| |||
d = bt.load(s, period); e, xma = zscore_entries(d, 20, -1.5, 0)
| |||
ts = pd.DatetimeIndex(d["ts"])
| |||
if last_only:
| |||
e = e & (pd.Series(ts).groupby(ts.normalize()).transform("max") == pd.Series(ts)).to_numpy()
| |||
tr = bt.add_r(bt.simulate(d, e, 1, xma, 10, 3.0), d, 3.0)
| |||
for t in tr: t["symbol"] = s; t["exit_t"] = d["ts"][t["exit_i"]]
| |||
out += tr
| |||
return out
| |||
A, L = book("PERIOD_H4", False), book("PERIOD_H4", True)
| |||
print("HOLDING / EXITS (H4):")
| |||
for n, tr in (("all-bar", A), ("last-bar", L)):
| |||
b = np.array([t["bars"] for t in tr]); r = pd.Series([t["reason"] for t in tr]).value_counts(normalize=True).round(2).to_dict()
| |||
print(f" {n:<9} n={len(tr)} mean {b.mean():.1f} bars (~{b.mean()*4/24:.1f} d) median {np.median(b):.0f} exits {r}")
| |||
D = book("PERIOD_D1", False); b = np.array([t["bars"] for t in D])
| |||
print(f" D1 book n={len(D)} mean {b.mean():.1f} days median {np.median(b):.0f} exits {pd.Series([t['reason'] for t in D]).value_counts(normalize=True).round(2).to_dict()}")
| |||
sa, sl = mg.window_stats(A, "2021-01-01", "2030-01-01"), mg.window_stats(L, "2021-01-01", "2030-01-01")
| |||
print(f"\nPOOLED all-bar {sa['n']} {sa['bp']:.1f}bp rdd {sa['rdd']:.2f} DD {sa['dd']:.1%} | last-bar {sl['n']} {sl['bp']:.1f}bp rdd {sl['rdd']:.2f} DD {sl['dd']:.1%}")
| |||
win_i = 0
| |||
for s in mg.IDX:
| |||
x = [t["ret"]*1e4 for t in A if t["symbol"]==s]; y = [t["ret"]*1e4 for t in L if t["symbol"]==s]
| |||
print(f" {s:<7} all {len(x):>4} {np.mean(x):6.1f}bp | last {len(y):>4} {np.mean(y):6.1f}bp"); win_i += np.mean(y) >= np.mean(x)
| |||
win_y = 0
| |||
for y in range(2021, 2027):
| |||
f = lambda tr: [t["ret"]*1e4 for t in tr if t["t"].astype("datetime64[Y]").astype(int)+1970 == y]
| |||
x, z = f(A), f(L); ok = len(z) > 0 and np.mean(z) >= np.mean(x); win_y += ok
| |||
print(f" {y}: all {len(x):>3} {np.mean(x):6.1f}bp | last {len(z):>3} {np.mean(z):6.1f}bp")
| |||
keep = len(L)/len(A)
| |||
print(f"\nrdd ok {sl['rdd']>=sa['rdd']} indices {win_i}/4 years {win_y}/6 kept {keep:.0%} ->",
| |||
"SURVIVES" if sl['rdd']>=sa['rdd'] and win_i>=3 and win_y>=4 and keep>=.4 else "REJECTED")
|