146 lines
5 KiB
Python
146 lines
5 KiB
Python
|
|
# -*- coding: utf-8 -*-
|
||
|
|
"""P3.1e REGIME DIAGNOSTIC — SniperGold_ML.
|
||
|
|
|
||
|
|
Diagnostic ONLY. Answers the fifth research question:
|
||
|
|
"Apakah relationship feature -> label berubah berdasarkan market state?"
|
||
|
|
|
||
|
|
Regime proxies use EXISTING semantics only (no new regime model):
|
||
|
|
* ATR percentile : rolling percentile rank of ATR over trailing 500 bars
|
||
|
|
* Realized vol : std of 20-bar log returns (trailing)
|
||
|
|
* Trend proxy : |f16 mom20_atr| magnitude (already a contract feature)
|
||
|
|
* Range proxy : f17 range_atr (already a contract feature)
|
||
|
|
|
||
|
|
Analysis:
|
||
|
|
* P(Y=1) / P(Y=-1) per regime tercile (low/med/high)
|
||
|
|
* feature x regime: univariate AUC of key features within each tercile
|
||
|
|
-> does the feature->label relationship change across regimes?
|
||
|
|
* regime x SMC state: P(Y=1) for selected combos
|
||
|
|
|
||
|
|
No MS-GARCH / HMM / regime gate is built.
|
||
|
|
"""
|
||
|
|
import os
|
||
|
|
import sys
|
||
|
|
import json
|
||
|
|
|
||
|
|
import numpy as np
|
||
|
|
|
||
|
|
HERE = os.path.dirname(os.path.abspath(__file__))
|
||
|
|
if HERE not in sys.path:
|
||
|
|
sys.path.insert(0, HERE)
|
||
|
|
import p3_common as P3
|
||
|
|
import train_model as TM
|
||
|
|
|
||
|
|
KEY_FEATS = [0, 5, 7, 9, 16, 18]
|
||
|
|
|
||
|
|
|
||
|
|
def terciles(x):
|
||
|
|
q33, q66 = np.quantile(x, [1 / 3, 2 / 3])
|
||
|
|
lo = x <= q33
|
||
|
|
hi = x >= q66
|
||
|
|
md = ~lo & ~hi
|
||
|
|
return lo, md, hi
|
||
|
|
|
||
|
|
|
||
|
|
def main():
|
||
|
|
t, o, h, l, c, v, htf = P3.load_data()
|
||
|
|
F = P3.load_F()
|
||
|
|
A = P3.atr_series(h, l, c)
|
||
|
|
n = len(c)
|
||
|
|
lab = P3.make_label(c, A)
|
||
|
|
labeled = lab != 0
|
||
|
|
midx = np.where(labeled)[0]
|
||
|
|
y = lab[midx]
|
||
|
|
ypos = (y == 1).astype(int)
|
||
|
|
|
||
|
|
# ---- regime proxies ----
|
||
|
|
atr_pct = np.zeros(n)
|
||
|
|
for i in range(500, n):
|
||
|
|
atr_pct[i] = (A[i - 500:i] <= A[i]).mean()
|
||
|
|
atr_pct[:500] = 0.5
|
||
|
|
|
||
|
|
lr = np.zeros(n)
|
||
|
|
lr[1:] = np.log(c[1:] / np.maximum(c[:-1], 1e-12))
|
||
|
|
rv = np.zeros(n)
|
||
|
|
for i in range(20, n):
|
||
|
|
rv[i] = lr[i - 19:i + 1].std(ddof=1)
|
||
|
|
rv[:20] = np.nan
|
||
|
|
rv = np.nan_to_num(rv, nan=np.nanmedian(rv[20:]))
|
||
|
|
|
||
|
|
proxies = {
|
||
|
|
"ATR_percentile": atr_pct,
|
||
|
|
"RealizedVol20": rv,
|
||
|
|
"TrendAbs_mom20(f16)": np.abs(F[:, 16]),
|
||
|
|
"RangeATR(f17)": F[:, 17],
|
||
|
|
}
|
||
|
|
|
||
|
|
report = {"provenance": P3.provenance(),
|
||
|
|
"dataset_hash": P3.dataset_hash(F, lab, A, t),
|
||
|
|
"regimes": {}}
|
||
|
|
|
||
|
|
print("=== REGIME DIAGNOSTIC (tercile low/med/high) ===")
|
||
|
|
for pname, px in proxies.items():
|
||
|
|
lo, md, hi = terciles(px)
|
||
|
|
r = {}
|
||
|
|
print(f"\n[{pname}]")
|
||
|
|
for rname, m in (("low", lo), ("med", md), ("high", hi)):
|
||
|
|
ml = m[midx]
|
||
|
|
if ml.sum() < 100:
|
||
|
|
continue
|
||
|
|
p1 = float(ypos[ml].mean())
|
||
|
|
pm1 = float((y[ml] == -1).mean())
|
||
|
|
r[rname] = {"n": int(ml.sum()), "P1": p1, "Pm1": pm1,
|
||
|
|
"lift_P1": float(p1 / ypos.mean())}
|
||
|
|
print(f" {rname:>4}: n={int(ml.sum()):>6} P(+1)={p1:.4f} "
|
||
|
|
f"P(-1)={pm1:.4f} lift={p1 / ypos.mean():.3f}")
|
||
|
|
for j in KEY_FEATS:
|
||
|
|
xs = F[midx, j][ml]
|
||
|
|
if len(np.unique(xs)) < 3:
|
||
|
|
continue
|
||
|
|
a = TM.auc(ypos[ml].astype(int), xs)
|
||
|
|
r[rname]["auc_" + P3.FEAT_NAMES[j]] = float(a)
|
||
|
|
print(" univariate AUC by regime (long label):")
|
||
|
|
for fname in [P3.FEAT_NAMES[j] for j in KEY_FEATS]:
|
||
|
|
vals = []
|
||
|
|
for rname in ("low", "med", "high"):
|
||
|
|
vv = r.get(rname, {}).get("auc_" + fname)
|
||
|
|
vals.append(vv if vv is not None else float("nan"))
|
||
|
|
print(" %-16s low=%.4f med=%.4f high=%.4f" % (fname, vals[0], vals[1], vals[2]))
|
||
|
|
report["regimes"][pname] = r
|
||
|
|
|
||
|
|
# ---- regime x SMC state combos ----
|
||
|
|
print("\n=== REGIME x SMC STATE: P(Y=1) selected combos ===")
|
||
|
|
atr_lo, atr_md, atr_hi = terciles(atr_pct)
|
||
|
|
f9 = F[:, 9]
|
||
|
|
f7 = F[:, 7]
|
||
|
|
f18 = F[:, 18]
|
||
|
|
combos = {
|
||
|
|
"BUY_cand & ATR_low": (f9 == 1) & (f7 > 0) & atr_lo,
|
||
|
|
"BUY_cand & ATR_high": (f9 == 1) & (f7 > 0) & atr_hi,
|
||
|
|
"SELL_cand & ATR_low": (f9 == 1) & (f7 < 0) & atr_lo,
|
||
|
|
"SELL_cand & ATR_high": (f9 == 1) & (f7 < 0) & atr_hi,
|
||
|
|
"conf>=60 & ATR_low": (f18 >= 60) & atr_lo,
|
||
|
|
"conf>=60 & ATR_high": (f18 >= 60) & atr_hi,
|
||
|
|
"MTF_bull & ATR_low": (F[:, 0] > 0) & (F[:, 1] > 0) & (F[:, 2] > 0) & atr_lo,
|
||
|
|
"MTF_bull & ATR_high": (F[:, 0] > 0) & (F[:, 1] > 0) & (F[:, 2] > 0) & atr_hi,
|
||
|
|
}
|
||
|
|
comb = {}
|
||
|
|
for cname, mask in combos.items():
|
||
|
|
ml = mask[midx]
|
||
|
|
if ml.sum() < 100:
|
||
|
|
comb[cname] = {"n": int(ml.sum()), "note": "too few"}
|
||
|
|
print(f" {cname:<24} n={int(ml.sum())} (too few)")
|
||
|
|
continue
|
||
|
|
p1 = float(ypos[ml].mean())
|
||
|
|
pm1 = float((y[ml] == -1).mean())
|
||
|
|
comb[cname] = {"n": int(ml.sum()), "P1": p1, "Pm1": pm1,
|
||
|
|
"lift_P1": float(p1 / ypos.mean())}
|
||
|
|
print(f" {cname:<24} n={int(ml.sum()):>6} P(+1)={p1:.4f} "
|
||
|
|
f"P(-1)={pm1:.4f} lift={p1 / ypos.mean():.3f}")
|
||
|
|
report["regime_x_smc"] = comb
|
||
|
|
|
||
|
|
P3.save_json("regime_diagnostic.json", report)
|
||
|
|
print("\nRegime diagnostic selesai. Output: ml/p3/output/regime_diagnostic.json")
|
||
|
|
|
||
|
|
|
||
|
|
if __name__ == "__main__":
|
||
|
|
main()
|