SniperGold_ML/ml/p3/temporal_diagnostic.py

185 lines
6.9 KiB
Python

# -*- coding: utf-8 -*-
"""P3.1d TEMPORAL DIAGNOSTIC — SniperGold_ML.
Diagnostic ONLY. Answers the fourth research question:
"Apakah informasi yang hilang pada static feature memang bersifat temporal?"
No neural sequence model is used. Evidence built from:
* feature autocorrelation (lags 1..10) for continuous features
* state persistence & transition matrices for discrete features
* run-length of persistent states
* LAGGED INFORMATION: univariate AUC of feature[t-k] vs label[t]
(k = 0..5) -> if delayed information is retained, temporal dependency exists
* label conditional persistence: P(Y_t=1 | Y_{t-k}=1) for k = 1..24
* state-age analysis: does P(Y=1) change with the age of the current
HTF-bias / swing-trend run? (i.e., is "state age" itself informative?)
All metrics are descriptive; nothing is promoted to a model.
"""
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
CONT_FEATURES = [6, 13, 14, 15, 16, 17, 18]
DISC_FEATURES = [0, 1, 2, 3, 4, 5, 7, 8, 9, 10, 11, 12]
LAGS = [1, 2, 3, 5, 8, 12, 16, 24]
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)
report = {"provenance": P3.provenance(),
"dataset_hash": P3.dataset_hash(F, lab, A, t)}
# ---- 1) feature autocorrelation (continuous) ----
print("=== FEATURE AUTOCORRELATION (continuous, lags 1..10) ===")
ac = {}
for j in CONT_FEATURES:
x = F[:, j].astype(float)
xc = x - x.mean()
var = (xc * xc).mean()
row = {}
for k in range(1, 11):
row[str(k)] = float((xc[:-k] * xc[k:]).mean() / var) if var > 0 else 0.0
ac[j] = row
print(f" {P3.FEAT_NAMES[j]:<16s} " +
" ".join(f"l{k}:{row[str(k)]:+.3f}" for k in (1, 3, 5, 10)))
report["feature_autocorr"] = {P3.FEAT_NAMES[j]: ac[j] for j in CONT_FEATURES}
# ---- 2) discrete state persistence ----
print("\n=== DISCRETE STATE PERSISTENCE (P(state_t == state_{t-1})) ===")
dp = {}
for j in DISC_FEATURES:
xs = F[:, j]
dp[j] = float((xs[1:] == xs[:-1]).mean())
print(f" {P3.FEAT_NAMES[j]:<16s} persist={dp[j]:.4f}")
report["discrete_persistence"] = {P3.FEAT_NAMES[j]: dp[j] for j in DISC_FEATURES}
# ---- 3) run lengths of persistent states ----
print("\n=== RUN LENGTH (bars) of non-zero states ===")
rl = {}
for j in (0, 1, 2, 3, 4, 5, 7, 8):
xs = F[:, j]
runs = []
cur = 0
curv = 0
for vv in xs:
if vv != 0 and vv == curv:
cur += 1
else:
if cur:
runs.append(cur)
cur = 1 if vv != 0 else 0
curv = vv
if cur:
runs.append(cur)
rl[j] = {"mean": float(np.mean(runs)) if runs else 0.0,
"median": float(np.median(runs)) if runs else 0.0,
"p95": float(np.percentile(runs, 95)) if runs else 0.0,
"max": float(np.max(runs)) if runs else 0.0}
print(f" {P3.FEAT_NAMES[j]:<16s} run mean={rl[j]['mean']:.1f} "
f"med={rl[j]['median']:.0f} p95={rl[j]['p95']:.0f} max={rl[j]['max']:.0f}")
report["run_length"] = {P3.FEAT_NAMES[j]: rl[j] for j in rl}
# ---- 4) LAGGED INFORMATION: AUC(feature[t-k], label[t]) ----
print("\n=== LAGGED INFORMATION: univariate AUC(feature[t-k] vs label[t]) ===")
key_feats = [0, 5, 7, 9, 16, 18]
lag_auc = {}
for j in key_feats:
row = {}
for k in LAGS:
# feature value k bars before each labeled bar
xk = F[midx - k, j]
valid = midx - k >= 0
if valid.sum() < 1000:
row[str(k)] = None
continue
row[str(k)] = float(TM.auc(ypos[valid].astype(int), xk[valid]))
lag_auc[j] = row
print(f" {P3.FEAT_NAMES[j]:<16s} " +
" ".join(f"k{k}:{(row[str(k)] if row[str(k)] is not None else float('nan')):.4f}"
for k in LAGS))
report["lagged_auc"] = {P3.FEAT_NAMES[j]: lag_auc[j] for j in key_feats}
# ---- 5) label conditional persistence ----
print("\n=== LABEL CONDITIONAL PERSISTENCE: P(Y_t=1 | Y_{t-k}=1) ===")
lcp = {}
li = midx # labeled bar indices (ascending)
yv = y
p1_uncond = float((yv == 1).mean())
for k in (1, 2, 3, 5, 8, 12, 16, 24):
# find pairs (i, i-k) both labeled with Y_{i-k}=1
# labeled bars are not contiguous; use absolute time index mapping
pos_idx = li[yv == 1]
n_prev = 0
n_same = 0
# vectorized: for each labeled bar, check if bar-k bars ago was labeled +1
valid = li - k >= 0
prev_lab = np.full(len(li), 0)
# map bar index -> label
labmap = np.zeros(n, dtype=int)
labmap[li] = yv
prev = labmap[li - k]
n_prev = int((prev == 1).sum())
n_same = int(((prev == 1) & (yv == 1)).sum())
p = float(n_same / n_prev) if n_prev else float("nan")
lcp[str(k)] = {"n_prev_pos": n_prev, "P_same": p,
"unconditional_P1": float(p1_uncond),
"lift": float(p / p1_uncond) if n_prev else float("nan")}
print(f" k={k:>2}: P(Y_t=1 | Y_{{t-{k}}}=1)={p:.4f} (uncond {p1_uncond:.4f}, "
f"lift={p / p1_uncond:.3f})")
report["label_cond_persistence"] = lcp
# ---- 6) STATE-AGE ANALYSIS: P(Y=1) vs age of current run ----
print("\n=== STATE-AGE ANALYSIS: P(Y=1) by age of current HTF-bias run ===")
age = {}
for j in (0, 1, 5):
xs = F[:, j]
# run age at each bar (bars since run started)
age_arr = np.zeros(n, dtype=int)
cur_age = 0
prev = 0
for i in range(n):
if xs[i] != 0 and xs[i] == prev:
cur_age += 1
else:
cur_age = 1 if xs[i] != 0 else 0
age_arr[i] = cur_age
prev = xs[i]
# P(Y=1 | age bucket) on labeled bars
buckets = [(1, 3), (4, 10), (11, 30), (31, 100), (101, 10 ** 9)]
rows = []
for lo, hi in buckets:
m = (age_arr[midx] >= lo) & (age_arr[midx] <= hi)
if m.sum() < 100:
continue
rows.append({"age_bucket": f"{lo}-{hi}", "n": int(m.sum()),
"P1": float(ypos[m].mean())})
print(f" {P3.FEAT_NAMES[j]:<16s} age {lo:>3}-{hi:<4}: "
f"P(Y=1)={ypos[m].mean():.4f} n={int(m.sum())}")
age[j] = rows
report["state_age"] = {P3.FEAT_NAMES[j]: age[j] for j in age}
P3.save_json("temporal_diagnostic.json", report)
print("\nTemporal diagnostic selesai. Output: ml/p3/output/temporal_diagnostic.json")
if __name__ == "__main__":
main()