278 lines
11 KiB
Python
278 lines
11 KiB
Python
# -*- coding: utf-8 -*-
|
|
"""P2.5: build_features_p2 — training feed dengan CORRECTED semantics (FEATURE_CONTRACT v1.0).
|
|
|
|
Perbedaan vs train_model.build_features:
|
|
1. f0-f2 : bias = tf_bias_asof(E_ea), E_ea = bar HTF tertutup terakhir pada tc = t+900
|
|
(bukan as-of open dgn lag-1; bukan window tertua-200)
|
|
2. f3-f5, f8-f9 : struktur dari SLICE cache 700 berakhir di bar row, begin=100
|
|
(persis EA ProcessStructure)
|
|
3. f6/f14/f15/f17 : sw_high/sw_low = pivot swing terakhir di [t-649, t-50] (0 bila kosong)
|
|
4. f10/f11 : pasangan consecutive-list + tol = EQ_TOL_ATR * ATR(row) + window [t-649, t-50]
|
|
5. f7 (sweep), f12/f13 (delta), f16 (mom), ATR : sama dgn build_features (verified)
|
|
"""
|
|
import os
|
|
import sys
|
|
import numpy as np
|
|
|
|
HERE = os.path.dirname(os.path.abspath(__file__))
|
|
SRC_TM = os.path.normpath(os.path.join(HERE, "..", "..", "..", "SniperGold_ML"))
|
|
if SRC_TM not in sys.path:
|
|
sys.path.insert(0, SRC_TM)
|
|
import train_model as TM
|
|
|
|
|
|
def _pivots_vec(h, l, length):
|
|
"""Pivot detection vectorized (semantik EA IsPivotHigh/Low: equal allowed)."""
|
|
n = len(h)
|
|
is_ph = np.ones(n, dtype=bool)
|
|
is_pl = np.ones(n, dtype=bool)
|
|
for k in range(1, length + 1):
|
|
is_ph &= (h >= np.roll(h, k)) & (h >= np.roll(h, -k))
|
|
is_pl &= (l <= np.roll(l, k)) & (l <= np.roll(l, -k))
|
|
is_ph[:length] = False
|
|
is_ph[n - length:] = False
|
|
is_pl[:length] = False
|
|
is_pl[n - length:] = False
|
|
return is_ph, is_pl
|
|
|
|
|
|
def build_structure_fast(o, h, l, c, length, internal, swing_at, begin=100):
|
|
"""build_structure dgn pivot vectorized (output: trend/last_break/choch)."""
|
|
n = len(c)
|
|
is_ph, is_pl = _pivots_vec(h, l, length)
|
|
trend = np.zeros(n, dtype=int)
|
|
last_break = np.full(n, -1, dtype=int)
|
|
choch_dir = np.zeros(n, dtype=int)
|
|
choch_bar = np.full(n, -1, dtype=int)
|
|
up_target, dn_target = float("inf"), -float("inf")
|
|
up_bar, dn_bar = -1, -1
|
|
cur_trend = 0
|
|
cur_cd, cur_cb = 0, -1
|
|
cur_lb = -1
|
|
for i in range(begin, n):
|
|
p = i - length
|
|
if p >= length and (is_ph[p] or is_pl[p]):
|
|
if is_ph[p]:
|
|
up_target, up_bar = h[p], p
|
|
if is_pl[p]:
|
|
dn_target, dn_bar = l[p], p
|
|
broke = False
|
|
if up_bar >= 0 and c[i] > up_target:
|
|
choch = (cur_trend < 0)
|
|
allow = (not internal) or swing_at[i] >= 0
|
|
if internal and allow and choch:
|
|
cur_cd, cur_cb = 1, i
|
|
cur_trend = 1
|
|
up_target, up_bar = float("inf"), -1
|
|
cur_lb = i
|
|
broke = True
|
|
if dn_bar >= 0 and c[i] < dn_target:
|
|
choch = (cur_trend > 0)
|
|
allow = (not internal) or swing_at[i] <= 0
|
|
if internal and allow and choch:
|
|
cur_cd, cur_cb = -1, i
|
|
cur_trend = -1
|
|
dn_target, dn_bar = -float("inf"), -1
|
|
cur_lb = i
|
|
broke = True
|
|
trend[i] = cur_trend
|
|
last_break[i] = i if broke else cur_lb
|
|
choch_dir[i] = cur_cd
|
|
choch_bar[i] = cur_cb
|
|
return dict(trend=trend, last_break=last_break, choch_dir=choch_dir,
|
|
choch_bar=choch_bar)
|
|
|
|
|
|
def bias_series(hh, hl, hc, lag=0):
|
|
"""bias[k] = BTTF window 200 bar terbaru berakhir k (k inert). tf_bias_asof(k)."""
|
|
n = len(hc)
|
|
out = np.zeros(n, dtype=int)
|
|
for k in range(n):
|
|
start = max(0, k - 199)
|
|
out[k] = TM.tf_bias_asof(hh, hl, hc, k) if k >= 0 else 0
|
|
return out
|
|
|
|
|
|
def build_features_p2(t, o, h, l, c, v, htf, idxs=None):
|
|
"""Corrected training feed utk bar indeks idxs (default semua).
|
|
|
|
htf: dict {'D1': (hh, hl, hc, ht), 'H4': ..., 'H1': ...}
|
|
Return F (n,19) untuk bar yg diminta (idxs), atau (n,19) utk semua.
|
|
"""
|
|
n = len(c)
|
|
A = np.maximum(TM.atr_series(h, l, c), 1e-9)
|
|
|
|
# ---- HTF bias: precompute series sekali ----
|
|
bs = {}
|
|
for key, per in (("D1", 86400), ("H4", 14400), ("H1", 3600)):
|
|
hh, hl, hc, ht_ = htf[key]
|
|
bs[key] = bias_series(hh, hl, hc, lag=0)
|
|
|
|
# ---- full-feed pivot lists (swing + internal) utk sw_high/low, EQH/EQL, sweep ----
|
|
sw_at = np.zeros(n, dtype=int)
|
|
sw_full = TM.build_structure(o, h, l, c, TM.SWING_LEN, False, sw_at, begin=100)
|
|
inn_full = TM.build_structure(o, h, l, c, TM.INTERNAL_LEN, True, sw_full["trend"].copy(), begin=100)
|
|
sp = sw_full["pivots"]
|
|
piv = np.array([(p, pr, 1 if ih else 0) for (p, pr, ih) in sp], dtype=np.float64)
|
|
ph = piv[piv[:, 2] == 1]
|
|
pl = piv[piv[:, 2] == 0]
|
|
|
|
# sweep (f7) — full feed (verified 0.0000)
|
|
sweep_dir = np.zeros(n, dtype=int)
|
|
sweep_bar = np.full(n, -1)
|
|
cur_sd, cur_sb = 0, -1
|
|
for (p, lvl, is_high) in inn_full["pivots"]:
|
|
last = min(n - 1, p + TM.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
|
|
|
|
# EQH/EQL pairs (consecutive full-list; valid bila keduanya di window [r-649, r-50])
|
|
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) < TM.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))
|
|
|
|
if idxs is None:
|
|
idxs = np.arange(n)
|
|
|
|
F = np.zeros((len(idxs), 19))
|
|
for ri, r in enumerate(idxs):
|
|
# ---- f0-f2: HTF bias as-of close ----
|
|
tc = t[r] + 900
|
|
for fi, (key, per) in enumerate((("D1", 86400), ("H4", 14400), ("H1", 3600))):
|
|
hh_, hl_, hc_, ht_ = htf[key]
|
|
e = int(np.searchsorted(ht_, tc - per, side="right")) - 1
|
|
F[ri, fi] = int(bs[key][e]) if 0 <= e < len(hc_) else 0
|
|
|
|
# ---- struktur windowed (slice 700, begin=100) ----
|
|
s = max(0, r - 699)
|
|
o_w, h_w, l_w, c_w = o[s:r + 1], h[s:r + 1], l[s:r + 1], c[s:r + 1]
|
|
sw_at_w = np.zeros(len(c_w), dtype=int)
|
|
sw_w = build_structure_fast(o_w, h_w, l_w, c_w, TM.SWING_LEN, False, sw_at_w, begin=100)
|
|
inn_w = build_structure_fast(o_w, h_w, l_w, c_w, TM.INTERNAL_LEN, True,
|
|
sw_w["trend"].copy(), begin=100)
|
|
sw_t, sw_lb = int(sw_w["trend"][-1]), int(sw_w["last_break"][-1])
|
|
in_t, in_lb = int(inn_w["trend"][-1]), int(inn_w["last_break"][-1])
|
|
in_cd, in_cb = int(inn_w["choch_dir"][-1]), int(inn_w["choch_bar"][-1])
|
|
|
|
# f5 chart bias (EA ComputeAll)
|
|
b5 = in_t if (in_lb >= sw_lb and in_t != 0) else sw_t
|
|
if b5 == 0:
|
|
b5 = sw_t if sw_t != 0 else in_t
|
|
|
|
# ---- sw_high/sw_low windowed ----
|
|
lo_r = r - 649
|
|
hi_r = r - 50
|
|
sw_high = 0.0
|
|
sw_low = 0.0
|
|
jh = int(np.searchsorted(ph[:, 0], hi_r, side="right")) - 1
|
|
if jh >= 0 and ph[jh, 0] >= lo_r:
|
|
sw_high = float(ph[jh, 1])
|
|
jl = int(np.searchsorted(pl[:, 0], hi_r, side="right")) - 1
|
|
if jl >= 0 and pl[jl, 0] >= lo_r:
|
|
sw_low = float(pl[jl, 1])
|
|
|
|
# ---- f6/f14/f15/f17 ----
|
|
rng = sw_high - sw_low
|
|
eq_pos = 2.0 * (c[r] - sw_low) / rng - 1.0 if (sw_high > 0 and sw_low > 0 and rng > 0) else 0.0
|
|
d_high = max(-10.0, min(10.0, (sw_high - c[r]) / A[r])) if sw_high > 0 else 0.0
|
|
d_low = max(-10.0, min(10.0, (c[r] - sw_low) / A[r])) if sw_low > 0 else 0.0
|
|
r_atr = rng / A[r] if rng > 0 else 0.0
|
|
|
|
# ---- f9 chochOK (bandingkan dlm indeks absolut) ----
|
|
in_cb_abs = in_cb + s if in_cb >= 0 else -1
|
|
choch_ok = 1.0 if (in_cd != 0 and in_cb_abs >= sweep_bar[r] and in_cd == sweep_dir[r]) else 0.0
|
|
|
|
# ---- f10/f11 EQH/EQL (window + ATR row) ----
|
|
eqh = 0
|
|
eql = 0
|
|
tol = TM.EQ_TOL_ATR * A[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():
|
|
eqh = 1
|
|
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():
|
|
eql = 1
|
|
|
|
# ---- f12/f13 delta ----
|
|
kk = min(TM.DELTA_BARS, r - 1)
|
|
s_d = 0.0
|
|
tv = 0.0
|
|
if kk > 0:
|
|
for j in range(r - kk, r):
|
|
rj = h[j] - l[j]
|
|
if rj <= 0:
|
|
rj = 0.01 # _Point XAUUSD (EA: rng<=0 -> _Point)
|
|
body = abs(c[j] - o[j])
|
|
uw = h[j] - max(o[j], c[j])
|
|
lw = min(o[j], c[j]) - l[j]
|
|
bb, ss = (body + lw, uw) if c[j] >= o[j] else (lw, body + uw)
|
|
tt = bb + ss
|
|
if tt <= 0:
|
|
tt = 0.01
|
|
s_d += (bb - ss) / tt * v[j]
|
|
tv += v[j]
|
|
d_dir = 1 if s_d > 0 else (-1 if s_d < 0 else 0)
|
|
d_mag = max(-1.0, min(1.0, s_d / tv)) if tv > 0 else 0.0
|
|
|
|
# ---- f16 mom ----
|
|
mom = (c[r] - c[r - 20]) / A[r] if r >= 21 else 0.0
|
|
|
|
F[ri] = [F[ri, 0], F[ri, 1], F[ri, 2], sw_t, in_t, b5, eq_pos, sweep_dir[r],
|
|
in_cd, choch_ok, eqh, eql, d_dir, d_mag, d_high, d_low, mom, r_atr, 0.0]
|
|
|
|
F[:, 18] = TM.confluence_feature(F[:, :18])
|
|
return F
|
|
|
|
|
|
# ----------------------------------------------------------------------
|
|
if __name__ == "__main__":
|
|
import sys
|
|
import datetime as dt
|
|
HERE = os.path.dirname(os.path.abspath(__file__))
|
|
DATA = os.path.normpath(os.path.join(HERE, "..", "..", "..", "..",
|
|
"Files", "AlgoForge", "Data"))
|
|
|
|
def load_npz(name):
|
|
z = np.load(os.path.join(DATA, name + ".npz"))
|
|
return (z["time"].astype(np.int64), z["open"].astype(np.float64),
|
|
z["high"].astype(np.float64), z["low"].astype(np.float64),
|
|
z["close"].astype(np.float64), z["tick_volume"].astype(np.float64))
|
|
|
|
t, o, h, l, c, v = load_npz("XAUUSD_M15")
|
|
keep = t >= int(dt.datetime(2017, 1, 1, tzinfo=dt.timezone.utc).timestamp())
|
|
t, o, h, l, c, v = t[keep], o[keep], h[keep], l[keep], c[keep], v[keep]
|
|
htf = {}
|
|
for key in ("D1", "H4", "H1"):
|
|
ht_, ho_, hh_, hl_, hc_, hv_ = load_npz("XAUUSD_" + key)
|
|
htf[key] = (hh_, hl_, hc_, ht_)
|
|
# test subset
|
|
idxs = np.arange(100000, min(100200, len(c)))
|
|
F = build_features_p2(t, o, h, l, c, v, htf, idxs=idxs)
|
|
print("F shape:", F.shape)
|
|
print("sample row 0:", F[0].round(4))
|