Spaces:
Sleeping
Sleeping
File size: 5,863 Bytes
cbde68f | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 | #!/usr/bin/env python3
"""C1 validation: 40f baseline vs 40f + fracdiff features."""
import sys
from pathlib import Path
ROOT = Path(__file__).resolve().parent.parent
sys.path.insert(0, str(ROOT))
try:
from dotenv import load_dotenv; load_dotenv(ROOT / ".env")
except ImportError: pass
import warnings; warnings.filterwarnings("ignore")
import numpy as np
import pandas as pd
import json
from sklearn.ensemble import RandomForestClassifier
from models.predictor import FEATURE_COLUMNS
BASELINE = FEATURE_COLUMNS # 40 features
FRACDIFF = FEATURE_COLUMNS + ["fracdiff_close_35", "fracdiff_close_40"]
STOCKS = ["2330", "0050", "2317", "2454", "2881"]
LABEL_HORIZON = 5
LABEL_THRESH = 0.02
MIN_TRAIN = 240
STEP = 21
RF = dict(n_estimators=200, max_depth=6, min_samples_leaf=10,
class_weight="balanced", random_state=42, n_jobs=-1)
def fetch_df(stock_no):
from services.predictor_service import _fetch_with_cache
from indicators.technical import add_all_indicators, add_cross_asset_tw
from data.institutional_flow import add_institutional_flow
from data.margin_flow import add_margin_flow
from data.fetcher import fetch_cross_asset_tw
df = _fetch_with_cache(stock_no, months=24)
if df is None or df.empty: return None
df = add_all_indicators(df)
df = add_institutional_flow(df, stock_no)
df = add_margin_flow(df, stock_no)
start, end = str(df["date"].min()), str(df["date"].max())
result = fetch_cross_asset_tw(start, end)
taiex, usdtwd = result[0], result[1]
sox = result[2] if len(result) > 2 else None
tnx = result[3] if len(result) > 3 else None
df = add_cross_asset_tw(df, taiex, usdtwd, sox_close=sox, tnx_close=tnx)
return df
def make_labels(close):
fwd = close.shift(-LABEL_HORIZON)
ret = (fwd - close) / close
return np.where(ret > LABEL_THRESH, 1, np.where(ret < -LABEL_THRESH, -1, 0))
def walk_forward(feat, cols):
avail = [c for c in cols if c in feat.columns]
labels = feat["_label"].values
X_all = feat[avail].fillna(0).values
n = len(feat)
y_true_all, y_pred_all = [], []
cutoff = MIN_TRAIN
while cutoff + STEP + LABEL_HORIZON <= n:
X_tr, y_tr = X_all[:cutoff], labels[:cutoff]
test_end = min(cutoff + STEP, n - LABEL_HORIZON)
X_te, y_te = X_all[cutoff:test_end], labels[cutoff:test_end]
if len(np.unique(y_tr)) < 2 or len(X_te) == 0:
cutoff += STEP; continue
clf = RandomForestClassifier(**RF)
clf.fit(X_tr, y_tr)
y_pred = clf.predict(X_te)
y_true_all.extend(y_te.tolist())
y_pred_all.extend(y_pred.tolist())
cutoff += STEP
if not y_true_all: return {}
y_true = np.array(y_true_all)
y_pred = np.array(y_pred_all)
dir_mask = y_pred != 0
acc = (y_true == y_pred).mean() * 100
dir_acc = (y_true[dir_mask] == y_pred[dir_mask]).mean() * 100 if dir_mask.sum() > 0 else float("nan")
up_mask = y_pred == 1
up_prec = (y_true[up_mask] == 1).mean() * 100 if up_mask.sum() > 0 else float("nan")
return {"accuracy": round(acc, 1), "dir_accuracy": round(dir_acc, 1),
"up_precision": round(up_prec, 1), "n_predictions": len(y_true),
"n_features": len(avail)}
def main():
from models.predictor import _build_features
results = {}
hdr = f"{'Stock':>6} {'Set':>10} {'Acc%':>5} {'Dir%':>5} {'UpPrec%':>7} {'Feat':>4}"
print(f"\n{hdr}\n{'-'*len(hdr)}")
agg = {"baseline": [], "fracdiff": []}
for stock_no in STOCKS:
print(f" computing {stock_no}...", end="\r", flush=True)
df = fetch_df(stock_no)
if df is None or df.empty: continue
feat = _build_features(df)
# Pass fracdiff columns through from df (not built by _build_features)
for col in ("fracdiff_close_35", "fracdiff_close_40"):
if col in df.columns:
feat[col] = df[col].values
close = df.set_index("date")["close"] if "date" in df.columns else df["close"]
feat["_label"] = make_labels(close)
results[stock_no] = {}
for name, cols in [("baseline", BASELINE), ("fracdiff", FRACDIFF)]:
r = walk_forward(feat, cols)
results[stock_no][name] = r
if r:
print(f"{stock_no:>6} {name:>10} {r['accuracy']:>5.1f} {r['dir_accuracy']:>5.1f} {r['up_precision']:>7.1f} {r['n_features']:>4}")
agg[name].append(r)
if results[stock_no].get("baseline") and results[stock_no].get("fracdiff"):
rb, rf = results[stock_no]["baseline"], results[stock_no]["fracdiff"]
dd = rf["dir_accuracy"] - rb["dir_accuracy"]
print(f"{'':>6} {'delta':>10} {'':>5} {dd:>+5.1f}")
print()
def mean_field(rows, f):
vals = [r[f] for r in rows if not (isinstance(r.get(f), float) and np.isnan(r.get(f, float("nan"))))]
return round(sum(vals)/len(vals), 1) if vals else float("nan")
print("=== AGGREGATE ===")
agg_summary = {}
for name in ("baseline", "fracdiff"):
rows = agg[name]
s = {k: mean_field(rows, k) for k in ("accuracy", "dir_accuracy", "up_precision")}
agg_summary[name] = s
print(f" {name:>10}: acc={s['accuracy']}% dir={s['dir_accuracy']}% up_prec={s['up_precision']}%")
winner = "fracdiff" if agg_summary["fracdiff"]["dir_accuracy"] > agg_summary["baseline"]["dir_accuracy"] else "baseline"
passed = agg_summary["fracdiff"]["dir_accuracy"] >= 31.5
print(f"\n Winner: {winner} | passed={passed}")
out = {"results": results, "aggregate": agg_summary, "winner": winner,
"pass_threshold": 31.5, "passed": passed}
Path("docs").mkdir(exist_ok=True)
with open("docs/c1_fracdiff_result.json", "w") as f:
json.dump(out, f, indent=2)
return out
if __name__ == "__main__":
main()
|