forked from chiki2bum2/SniperGold_ML
185 lines
6.9 KiB
Python
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()
|