2026-08-22 07:23:37 +07:00 | | | # -*- 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)
|
2026-08-22 07:46:00 +07:00 | | | SEQ_WINDOW = 40 # InpSeqWindow (v4.4/v4.5): sweep -> CHoCH -> entry chain
|
| | | # = event validity/expiry utk f7 (P3-S.0 f7 lifecycle fix)
|
2026-08-22 07:23:37 +07:00 | | | 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).
|
2026-08-22 07:46:00 +07:00 | | | Returns (sweep_dir int[n], sweep_bar int[n]).
|
| | | NOTE: ini state LEGACY (persist tanpa batas umur) — bukan output f7 final."""
|
2026-08-22 07:23:37 +07:00 | | | 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
|
| | |
|
| | |
|
2026-08-22 07:46:00 +07:00 | | | 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
|
| | |
|
| | |
|
2026-08-22 07:23:37 +07:00 | | | 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
|