SniperGold_ML/ml/p3/smc_semantic/smc_semantic_common.py

199 lines
7.7 KiB
Python

# -*- coding: utf-8 -*-
"""SMC SEMANTIC COMMON — infra bersama utk P3 SMC Semantic Golden Dataset (Fase: Liquidity Sweep).
Reuse p3_common (load data 2017+, F cache P2.6, ATR, provenance) + train_model
(build_structure) — TIDAK membuat framework duplikat.
Mengimplementasikan SEMANTIK KODE SAAT INI secara eksak (FEATURE_CONTRACT v1.0,
parity-verified thd EA AlgoForge_Backtest_Baseline.mq5 / v4.4/v4.5):
f7 sweep_dir : DetectLiquidityGrabs — pivot INTERNAL (len=5), GRAB_WINDOW=8,
wick menembus level + close kembali (REJECTION ADA), state persist.
f10 eqh_swept : DetectEQ — pasangan swing high berurutan (len=50),
|dp| <= 0.10*ATR(row), swept bila ada bar setelah p2 dgn high>pr2
(REJECTION TIDAK ADA — dipilih via AUC, lihat DESAIN_MTF_v45.md).
f11 eql_swept : DetectEQ — sama utk lows (REJECTION TIDAK ADA).
Disiplin P3: OBSERVE -> HYPOTHESIS -> TEST -> RECORD -> CLASSIFY.
Tidak ada definisi Sweep baru; hanya mengukur apa yang dilakukan kode sekarang.
"""
import os
import sys
import json
import hashlib
import numpy as np
HERE = os.path.dirname(os.path.abspath(__file__))
sys.path.insert(0, os.path.normpath(os.path.join(HERE, ".."))) # ml/p3
sys.path.insert(0, os.path.normpath(os.path.join(HERE, "..", "..", "..", "..",
"SniperGold_ML"))) # Shared Projects\SniperGold_ML
import p3_common as P3 # data + F cache + ATR + provenance
import train_model as TM # build_structure + constants
# ---- konstanta FEATURE_CONTRACT v1.0 (sumber: train_model.py / EA #define) ----
SWING_LEN = TM.SWING_LEN # 50 (InpSwingLen, v4.4)
INTERNAL_LEN = TM.INTERNAL_LEN # 5 (InpInternalLen, v4.4)
EQ_TOL_ATR = TM.EQ_TOL_ATR # 0.10 (InpEQThreshold, v4.4)
EQ_BARS = TM.EQ_BARS # 3 (InpEQBarsConfirm, v4.4)
GRAB_WINDOW = TM.GRAB_WINDOW # 8 (InpGrabWindow, v4.4)
SEQ_WINDOW = 40 # InpSeqWindow (v4.4/v4.5): sweep -> CHoCH -> entry chain
# = event validity/expiry utk f7 (P3-S.0 f7 lifecycle fix)
ATR_PERIOD = TM.ATR_PERIOD # 14
# window pivot valid absolut utk f10/f11 & sw_high/sw_low (FEATURE_CONTRACT §0)
W_LO = 649
W_HI = 50
DECISION_TF = "M15"
SYMBOL = "XAUUSD"
def load_data():
"""t,o,h,l,c,v (M15, 2017+) + htf dict (D1/H4/H1) — sama dgn P2.6/P3."""
return P3.load_data()
def load_F():
"""F (n,19) corrected P2.6 cache — utk cross-check & stratifikasi sampling."""
return P3.load_F()
def atr_series(h, l, c):
return P3.atr_series(h, l, c)
def build_structures(o, h, l, c):
"""Swing (len=50) + internal (len=5) pivot lists, full-feed, begin=100.
Semantik identik build_features_p2 (parity-verified 1e-9 thd EA)."""
n = len(c)
sw_at = np.zeros(n, dtype=int)
sw_full = TM.build_structure(o, h, l, c, SWING_LEN, False, sw_at, begin=100)
inn_full = TM.build_structure(o, h, l, c, INTERNAL_LEN, True,
sw_full["trend"].copy(), begin=100)
return sw_full, inn_full
def sweep_state(h, l, c, inn_pivots):
"""f7 sweep_dir + sweep_bar per bar (eksak build_features_p2 / EA DetectLiquidityGrabs).
Returns (sweep_dir int[n], sweep_bar int[n]).
NOTE: ini state LEGACY (persist tanpa batas umur) — bukan output f7 final."""
n = len(c)
sweep_dir = np.zeros(n, dtype=int)
sweep_bar = np.full(n, -1, dtype=int)
cur_sd, cur_sb = 0, -1
for (p, lvl, is_high) in inn_pivots:
last = min(n - 1, p + GRAB_WINDOW)
for b in range(p + 1, last + 1):
if is_high and h[b] > lvl and c[b] < lvl:
if b > cur_sb:
cur_sd, cur_sb = -1, b
break
if (not is_high) and l[b] < lvl and c[b] > lvl:
if b > cur_sb:
cur_sd, cur_sb = 1, b
break
if cur_sb >= 0:
sweep_dir[cur_sb:] = cur_sd
sweep_bar[cur_sb:] = cur_sb
return sweep_dir, sweep_bar
def f7_lifecycle(sweep_dir, sweep_bar):
"""P3-S.0 corrected f7: event lifecycle dgn expiration SEQ_WINDOW (40 bar).
NO_SWEEP -> SWEEP_ONSET -> EXPIRED -> NO_SWEEP.
f7[r] = sweep_dir[r] jika grab onset di sweep_bar[r] dan (r - sweep_bar[r]) <= SEQ_WINDOW,
selain itu 0. Trigger condition TIDAK diubah — hanya membatasi umur state sbg event.
Return int[n]."""
n = len(sweep_dir)
out = np.zeros(n, dtype=int)
for r in range(n):
sb = int(sweep_bar[r])
if sb >= 0 and (r - sb) <= SEQ_WINDOW:
out[r] = int(sweep_dir[r])
return out
def eq_pairs(h, l, sp):
"""EQH/EQL pasangan berurutan (p1,p2,first_cross,|dp|) — eksak build_features_p2.
sp = sw_full['pivots'] : list (p, price, is_high_int)."""
n = len(h)
pairs_h = [] # (p1, p2, first_cross, |dp|)
pairs_l = []
for k in range(1, len(sp)):
p1, pr1, ih1 = sp[k - 1]
p2, pr2, ih2 = sp[k]
if abs(p2 - p1) < EQ_BARS:
continue
if ih1 and ih2:
q = np.where(h[p2 + 1:] > pr2)[0]
fc = p2 + 1 + q[0] if len(q) else n + 1
pairs_h.append((p1, p2, fc, abs(pr2 - pr1)))
if (not ih1) and (not ih2):
q = np.where(l[p2 + 1:] < pr2)[0]
fc = p2 + 1 + q[0] if len(q) else n + 1
pairs_l.append((p1, p2, fc, abs(pr2 - pr1)))
pairs_h = np.array(pairs_h, dtype=np.float64) if pairs_h else np.zeros((0, 4))
pairs_l = np.array(pairs_l, dtype=np.float64) if pairs_l else np.zeros((0, 4))
return pairs_h, pairs_l
def eq_state_at(r, pairs_h, pairs_l, A):
"""f10/f11 state + detail pair paling baru yg memenuhi window [r-649, r-50],
first_cross<=r, |dp|<=tol (tol=0.10*ATR(r)). Returns dict."""
lo_r, hi_r = r - W_LO, r - W_HI
tol = EQ_TOL_ATR * A[r]
out = {"eqh": 0, "eql": 0, "pair_h": None, "pair_l": None,
"tol_atr": float(tol), "lo_r": int(lo_r), "hi_r": int(hi_r)}
if len(pairs_h):
mh = ((pairs_h[:, 0] >= lo_r) & (pairs_h[:, 1] <= hi_r) &
(pairs_h[:, 2] <= r) & (pairs_h[:, 3] <= tol))
if mh.any():
sel = pairs_h[mh]
k = int(np.argmax(sel[:, 1])) # pair dgn p2 paling baru
out["eqh"] = 1
out["pair_h"] = [int(sel[k, 0]), int(sel[k, 1]), int(sel[k, 2]),
float(sel[k, 3])]
if len(pairs_l):
ml = ((pairs_l[:, 0] >= lo_r) & (pairs_l[:, 1] <= hi_r) &
(pairs_l[:, 2] <= r) & (pairs_l[:, 3] <= tol))
if ml.any():
sel = pairs_l[ml]
k = int(np.argmax(sel[:, 1]))
out["eql"] = 1
out["pair_l"] = [int(sel[k, 0]), int(sel[k, 1]), int(sel[k, 2]),
float(sel[k, 3])]
return out
def active_grab_at(r, sweep_dir, sweep_bar, h, l, c):
"""Detail grab internal paling baru yg aktif di bar r (f7).
Returns dict atau None bila tidak ada grab aktif."""
sb = int(sweep_bar[r])
if sb < 0:
return None
d = int(sweep_dir[r])
return {"sweep_bar": sb, "dir": d,
"sweep_dir_state": d}
def provenance():
p = P3.provenance()
p["SMC_SEMANTIC_PHASE"] = "LIQUIDITY_SWEEP"
p["contract"] = {
"SWING_LEN": SWING_LEN, "INTERNAL_LEN": INTERNAL_LEN,
"EQ_TOL_ATR": EQ_TOL_ATR, "EQ_BARS": EQ_BARS,
"GRAB_WINDOW": GRAB_WINDOW, "ATR_PERIOD": ATR_PERIOD,
"window": f"[r-{W_LO}, r-{W_HI}]",
}
return p
def save_json(name, obj):
outdir = os.path.join(HERE, "output")
os.makedirs(outdir, exist_ok=True)
path = os.path.join(outdir, name)
with open(path, "w", encoding="utf-8") as f:
json.dump(obj, f, indent=2, default=float)
print(f" [saved] {path}")
return path