GoshawkVortexAI commited on
Commit
55a236a
·
verified ·
1 Parent(s): 60ab4aa

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +105 -28
app.py CHANGED
@@ -18,6 +18,7 @@ Not: Eğitim/araştırma amaçlıdır, yatırım tavsiyesi değildir.
18
 
19
  import asyncio
20
  import json
 
21
  import os
22
 
23
  import gradio as gr
@@ -493,55 +494,89 @@ def _select(Xtr, ftr, k, thr=0.95):
493
  return keep
494
 
495
 
496
- def _fit_predict(Xtr, ytr, Xte, w=None, ftr=None, k=0):
497
- """Ensemble: HGB + ExtraTrees + Logistic -> olasılık ortalaması (k>0 ise fold-içi özellik seçimi)."""
 
498
  if k and ftr is not None:
499
  sel = _select(Xtr, ftr, k)
500
  Xtr, Xte = Xtr[sel], Xte[sel]
501
- ps = []
502
  for name, m in _models().items():
503
  if name == "lr":
504
- m.fit(Xtr.fillna(0), ytr, logisticregression__sample_weight=w)
505
- ps.append(m.predict_proba(Xte.fillna(0))[:, 1])
506
  elif name == "et":
507
- m.fit(Xtr.fillna(0), ytr, sample_weight=w)
508
- ps.append(m.predict_proba(Xte.fillna(0))[:, 1])
509
  else:
510
- m.fit(Xtr, ytr, sample_weight=w)
511
- ps.append(m.predict_proba(Xte)[:, 1])
512
- return np.mean(ps, axis=0)
 
 
 
 
513
 
514
 
515
- def _backtest(p, fwd, horizon, thr, fee):
516
- """Örtüşmeyen (her `horizon` barda bir) işlemlerle net getiri istatistikleri."""
517
  p, fwd = p[::horizon], fwd[::horizon]
518
  pos = np.where(p > thr, 1, np.where(p < 1 - thr, -1, 0))
519
  pnl = pos * fwd - np.abs(pos) * fee
520
- n = int((pos != 0).sum())
 
 
 
 
521
  if n < 20:
522
  return {"n": n, "mean": 0.0, "t": 0.0, "hit": np.nan, "pf": np.nan}
523
- tr = pnl[pos != 0]
524
  gp, gl = tr[tr > 0].sum(), -tr[tr < 0].sum()
525
  return {"n": n, "mean": float(tr.mean()),
526
  "t": float(tr.mean() / (tr.std() / np.sqrt(n) + 1e-12)),
527
  "hit": float((tr > 0).mean()), "pf": float(gp / gl) if gl > 0 else np.inf}
528
 
529
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
530
  def _walk(X, Y, F, W, horizon, n_folds, k):
531
- """Purged expanding-window walk-forward; OOS olasılıkları ve satır indeksleri."""
532
  n = len(X)
533
  edges = np.linspace(int(n * 0.4), n, n_folds + 1).astype(int)
534
- oos_p, oos_idx = [], []
535
  for j in range(n_folds):
536
  tr_end, te0, te1 = edges[j] - horizon, edges[j], edges[j + 1]
537
  if tr_end < 80:
538
  continue
539
- oos_p.append(_fit_predict(X.iloc[:tr_end], Y.iloc[:tr_end], X.iloc[te0:te1],
540
- W[:tr_end], F[:tr_end], k))
 
 
 
 
 
541
  oos_idx.extend(range(te0, te1))
 
542
  if not oos_p:
543
  raise ValueError("Walk-forward için yeterli veri yok. Mum sayısını artır.")
544
- return np.concatenate(oos_p), np.array(oos_idx)
 
545
 
546
 
547
  def predict(df: pd.DataFrame, horizon: int, fee: float = 0.0006, n_folds: int = 5, ext=None,
@@ -580,17 +615,21 @@ def predict(df: pd.DataFrame, horizon: int, fee: float = 0.0006, n_folds: int =
580
  # ve büyük hareketlere daha fazla ağırlık ver.
581
  W = np.clip(np.abs(F) / (TM + 1e-12), 0.05, 3.0)
582
 
583
- # --- Purged walk-forward (expanding window), fold-içi özellik seçimiyle ---
584
- oos_p, oos_idx = _walk(X, Y, F, W, horizon, n_folds, max_feat)
585
- yo, fo = Y.values[oos_idx], F[oos_idx]
 
 
 
 
586
 
587
  # Teşhis (ablation): yalnızca temel (fiyat/hacim) özelliklerle aynı test
588
  base_cols = [c for c in cols if not c.startswith(("ts_", "dv_"))]
589
  auc_base = None
590
  if len(base_cols) < len(cols):
591
- pb, ib = _walk(X[base_cols], Y, F, W, horizon, n_folds, max_feat)
592
  try:
593
- auc_base = float(roc_auc_score(Y.values[ib], pb))
594
  except ValueError:
595
  auc_base = 0.5
596
 
@@ -600,14 +639,33 @@ def predict(df: pd.DataFrame, horizon: int, fee: float = 0.0006, n_folds: int =
600
  auc = float(roc_auc_score(yo, oos_p))
601
  except ValueError:
602
  auc = 0.5
 
 
 
 
603
 
604
- # Eşik taraması (yalnızca OOS üzerinde, işlem sayısı ≥ 30 şartıyla)
605
  best = {"thr": 0.55, **_backtest(oos_p, fo, horizon, 0.55, fee)}
606
  for thr in (0.52, 0.54, 0.56, 0.58, 0.60, 0.62):
607
  r = _backtest(oos_p, fo, horizon, thr, fee)
608
  if r["n"] >= 30 and r["mean"] > best["mean"]:
609
  best = {"thr": thr, **r}
610
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
611
  # Son model: tüm veriyle, canlı tahmin
612
  final_p = _fit_predict(X, Y, feats.iloc[[-1]][cols].ffill().fillna(0), W, F, max_feat)
613
  p_up = float(final_p[0])
@@ -620,6 +678,7 @@ def predict(df: pd.DataFrame, horizon: int, fee: float = 0.0006, n_folds: int =
620
  "center": center, "low": center - move, "high": center + move,
621
  "thr": best["thr"], "bt": best, "n_oos": len(oos_p), "fee": fee,
622
  "n_ext": n_ext, "n_feat": len(cols), "auc_base": auc_base, "max_feat": max_feat,
 
623
  }
624
 
625
 
@@ -683,8 +742,20 @@ def run(symbol, timeframe, exchange, horizon, limit, use_deriv=True, max_feat=40
683
 
684
  p, thr, bt = res["p_up"], res["thr"], res["bt"]
685
  signal = "LONG" if p > thr else "SHORT" if p < 1 - thr else "NÖTR"
686
- has_edge = res["auc"] > 0.52 and bt["mean"] > 0 and bt["t"] > 1.5 and bt["n"] >= 30
687
- edge = "✅ var (OOS, ücret sonrası)" if has_edge else "⚠️ kanıtlanamadı"
 
 
 
 
 
 
 
 
 
 
 
 
688
  md = f"""### {symbol} · {timeframe} · {int(horizon)} bar sonrası
689
  | | |
690
  |---|---|
@@ -697,9 +768,15 @@ def run(symbol, timeframe, exchange, horizon, limit, use_deriv=True, max_feat=40
697
  | Ort. net getiri / işlem | {bt['mean']:.3%} (t = {bt['t']:.2f}, ücret {res['fee']:.2%}/işlem) |
698
  | Özellik sayısı | {res['n_feat']} (Tickscope/türev: {res['n_ext']}) → fold başına en fazla {res['max_feat']} seçilir |
699
  | AUC yalnızca temel özelliklerle | {('%.3f' % res['auc_base']) if res['auc_base'] is not None else '—'} (ek veri kaynakları yardım ediyor mu?) |
 
 
 
 
700
  | Model edge | {edge} |
701
 
702
- *Walk-forward (purged) out-of-sample sonuçlar. Eşik OOS'ta seçildiği için hafif iyimser olabilir. Eğitim/araştırma amaçlıdır; yatırım tavsiyesi değildir.*"""
 
 
703
 
704
  # Grafik
705
  tail = df.tail(150)
 
18
 
19
  import asyncio
20
  import json
21
+ import math
22
  import os
23
 
24
  import gradio as gr
 
494
  return keep
495
 
496
 
497
+ def _fit_predict(Xtr, ytr, Xte, w=None, ftr=None, k=0, with_train=False):
498
+ """Ensemble: HGB + ExtraTrees + Logistic -> olasılık ortalaması.
499
+ k>0: fold-içi özellik seçimi. with_train: eğitim verisi üzerindeki olasılıkları da döndür (aşırı uyum ölçümü için)."""
500
  if k and ftr is not None:
501
  sel = _select(Xtr, ftr, k)
502
  Xtr, Xte = Xtr[sel], Xte[sel]
503
+ ps, pt = [], []
504
  for name, m in _models().items():
505
  if name == "lr":
506
+ A, B = Xtr.fillna(0), Xte.fillna(0)
507
+ m.fit(A, ytr, logisticregression__sample_weight=w)
508
  elif name == "et":
509
+ A, B = Xtr.fillna(0), Xte.fillna(0)
510
+ m.fit(A, ytr, sample_weight=w)
511
  else:
512
+ A, B = Xtr, Xte
513
+ m.fit(A, ytr, sample_weight=w)
514
+ ps.append(m.predict_proba(B)[:, 1])
515
+ if with_train:
516
+ pt.append(m.predict_proba(A)[:, 1])
517
+ p = np.mean(ps, axis=0)
518
+ return (p, np.mean(pt, axis=0)) if with_train else p
519
 
520
 
521
+ def _trades(p, fwd, horizon, thr, fee):
522
+ """Örtüşmeyen (her `horizon` barda bir) işlemlerin net getirileri."""
523
  p, fwd = p[::horizon], fwd[::horizon]
524
  pos = np.where(p > thr, 1, np.where(p < 1 - thr, -1, 0))
525
  pnl = pos * fwd - np.abs(pos) * fee
526
+ return pnl[pos != 0]
527
+
528
+
529
+ def _stats(tr):
530
+ n = len(tr)
531
  if n < 20:
532
  return {"n": n, "mean": 0.0, "t": 0.0, "hit": np.nan, "pf": np.nan}
 
533
  gp, gl = tr[tr > 0].sum(), -tr[tr < 0].sum()
534
  return {"n": n, "mean": float(tr.mean()),
535
  "t": float(tr.mean() / (tr.std() / np.sqrt(n) + 1e-12)),
536
  "hit": float((tr > 0).mean()), "pf": float(gp / gl) if gl > 0 else np.inf}
537
 
538
 
539
+ def _backtest(p, fwd, horizon, thr, fee):
540
+ return _stats(_trades(p, fwd, horizon, thr, fee))
541
+
542
+
543
+ def _nested(p, f, sizes, horizon, fee, grid=(0.52, 0.54, 0.56, 0.58, 0.60, 0.62)):
544
+ """Dürüst backtest: eşik her fold için YALNIZCA önceki fold'ların OOS'unda seçilir, sonra o fold'a uygulanır."""
545
+ bounds = np.cumsum([0] + list(sizes))
546
+ trades = []
547
+ for j in range(1, len(sizes)):
548
+ pp, ff = p[:bounds[j]], f[:bounds[j]]
549
+ best_thr, best_m = 0.55, -np.inf
550
+ for thr in (0.55,) + tuple(grid):
551
+ r = _backtest(pp, ff, horizon, thr, fee)
552
+ if r["n"] >= 30 and r["mean"] > best_m:
553
+ best_thr, best_m = thr, r["mean"]
554
+ trades.append(_trades(p[bounds[j]:bounds[j + 1]], f[bounds[j]:bounds[j + 1]], horizon, best_thr, fee))
555
+ return _stats(np.concatenate(trades) if trades else np.array([]))
556
+
557
+
558
  def _walk(X, Y, F, W, horizon, n_folds, k):
559
+ """Purged expanding-window walk-forward. Döner: OOS olasılık, satır indeksi, fold boyutları, ort. train-test AUC farkı."""
560
  n = len(X)
561
  edges = np.linspace(int(n * 0.4), n, n_folds + 1).astype(int)
562
+ oos_p, oos_idx, sizes, gaps = [], [], [], []
563
  for j in range(n_folds):
564
  tr_end, te0, te1 = edges[j] - horizon, edges[j], edges[j + 1]
565
  if tr_end < 80:
566
  continue
567
+ p, ptr = _fit_predict(X.iloc[:tr_end], Y.iloc[:tr_end], X.iloc[te0:te1],
568
+ W[:tr_end], F[:tr_end], k, with_train=True)
569
+ try:
570
+ gaps.append(roc_auc_score(Y.iloc[:tr_end], ptr) - roc_auc_score(Y.iloc[te0:te1], p))
571
+ except ValueError:
572
+ pass
573
+ oos_p.append(p)
574
  oos_idx.extend(range(te0, te1))
575
+ sizes.append(te1 - te0)
576
  if not oos_p:
577
  raise ValueError("Walk-forward için yeterli veri yok. Mum sayısını artır.")
578
+ return (np.concatenate(oos_p), np.array(oos_idx), sizes,
579
+ float(np.mean(gaps)) if gaps else float("nan"))
580
 
581
 
582
  def predict(df: pd.DataFrame, horizon: int, fee: float = 0.0006, n_folds: int = 5, ext=None,
 
615
  # ve büyük hareketlere daha fazla ağırlık ver.
616
  W = np.clip(np.abs(F) / (TM + 1e-12), 0.05, 3.0)
617
 
618
+ # --- Geliştirme (ilk %80) / hiç dokunulmayan hold-out (son %20) ---
619
+ n_all = len(X)
620
+ n_dev = int(n_all * 0.8)
621
+ Xd, Yd, Fd, Wd = X.iloc[:n_dev], Y.iloc[:n_dev], F[:n_dev], W[:n_dev]
622
+
623
+ oos_p, oos_idx, sizes, gap = _walk(Xd, Yd, Fd, Wd, horizon, n_folds, max_feat)
624
+ yo, fo = Yd.values[oos_idx], Fd[oos_idx]
625
 
626
  # Teşhis (ablation): yalnızca temel (fiyat/hacim) özelliklerle aynı test
627
  base_cols = [c for c in cols if not c.startswith(("ts_", "dv_"))]
628
  auc_base = None
629
  if len(base_cols) < len(cols):
630
+ pb, ib, _, _ = _walk(Xd[base_cols], Yd, Fd, Wd, horizon, n_folds, max_feat)
631
  try:
632
+ auc_base = float(roc_auc_score(Yd.values[ib], pb))
633
  except ValueError:
634
  auc_base = 0.5
635
 
 
639
  auc = float(roc_auc_score(yo, oos_p))
640
  except ValueError:
641
  auc = 0.5
642
+ # AUC anlamlılığı: H0 (AUC=0.5) altında SE ≈ sqrt(1/(3·n_eff)); örtüşen etiketler için n_eff = n/ufuk
643
+ n_eff = max(len(oos_p) / horizon, 2.0)
644
+ z = (auc - 0.5) / math.sqrt(1.0 / (3.0 * n_eff))
645
+ auc_p = 0.5 * math.erfc(z / math.sqrt(2.0))
646
 
647
+ # Eşik taraması (yalnızca OOS üzerinde, işlem sayısı ≥ 30 şartıyla) — iyimser
648
  best = {"thr": 0.55, **_backtest(oos_p, fo, horizon, 0.55, fee)}
649
  for thr in (0.52, 0.54, 0.56, 0.58, 0.60, 0.62):
650
  r = _backtest(oos_p, fo, horizon, thr, fee)
651
  if r["n"] >= 30 and r["mean"] > best["mean"]:
652
  best = {"thr": thr, **r}
653
 
654
+ # Dürüst (nested) backtest: eşik hiç görmediği fold'a uygulanır
655
+ nested = _nested(oos_p, fo, sizes, horizon, fee)
656
+
657
+ # Hold-out: gelişitirmede hiç kullanılmayan son %20; seçilen eşikle bir kez değerlendirilir
658
+ ho = None
659
+ tr_end = n_dev - horizon
660
+ if tr_end >= 200 and n_all - n_dev >= 100:
661
+ ph = _fit_predict(X.iloc[:tr_end], Y.iloc[:tr_end], X.iloc[n_dev:], W[:tr_end], F[:tr_end], max_feat)
662
+ try:
663
+ auc_h = float(roc_auc_score(Y.values[n_dev:], ph))
664
+ except ValueError:
665
+ auc_h = 0.5
666
+ ho = {"auc": auc_h, "bars": len(ph),
667
+ **_stats(_trades(ph, F[n_dev:], horizon, best["thr"], fee))}
668
+
669
  # Son model: tüm veriyle, canlı tahmin
670
  final_p = _fit_predict(X, Y, feats.iloc[[-1]][cols].ffill().fillna(0), W, F, max_feat)
671
  p_up = float(final_p[0])
 
678
  "center": center, "low": center - move, "high": center + move,
679
  "thr": best["thr"], "bt": best, "n_oos": len(oos_p), "fee": fee,
680
  "n_ext": n_ext, "n_feat": len(cols), "auc_base": auc_base, "max_feat": max_feat,
681
+ "gap": gap, "auc_p": auc_p, "nested": nested, "holdout": ho,
682
  }
683
 
684
 
 
742
 
743
  p, thr, bt = res["p_up"], res["thr"], res["bt"]
744
  signal = "LONG" if p > thr else "SHORT" if p < 1 - thr else "NÖTR"
745
+ nest, ho = res["nested"], res["holdout"]
746
+ checks = [
747
+ ("AUC > 0.52 ve p < 0.05", res["auc"] > 0.52 and res["auc_p"] < 0.05),
748
+ ("Train–test AUC farkı < 0.20 (aşırı uyum düşük)", res["gap"] < 0.20),
749
+ ("Nested net getiri > 0 ve t > 1.5 (≥30 işlem)", nest["n"] >= 30 and nest["mean"] > 0 and nest["t"] > 1.5),
750
+ ("Hold-out AUC > 0.50", ho is not None and ho["auc"] > 0.50),
751
+ ("Hold-out net getiri > 0 (≥20 işlem)", ho is not None and ho["n"] >= 20 and ho["mean"] > 0),
752
+ ]
753
+ passed = sum(ok for _, ok in checks)
754
+ edge = f"✅ {passed}/5 kriter" if passed >= 4 else f"⚠️ {passed}/5 kriter — kanıtlanamadı"
755
+ checks_md = "**Aşırı uyum / güvenilirlik göstergesi**\n\n" + "\n".join(
756
+ f"- {'✅' if ok else '❌'} {name}" for name, ok in checks)
757
+ ho_txt = (f"AUC {ho['auc']:.3f} · {ho['n']} işlem · net {ho['mean']:.3%} (t = {ho['t']:.2f}) · {ho['bars']} bar"
758
+ if ho is not None else "veri yetersiz")
759
  md = f"""### {symbol} · {timeframe} · {int(horizon)} bar sonrası
760
  | | |
761
  |---|---|
 
768
  | Ort. net getiri / işlem | {bt['mean']:.3%} (t = {bt['t']:.2f}, ücret {res['fee']:.2%}/işlem) |
769
  | Özellik sayısı | {res['n_feat']} (Tickscope/türev: {res['n_ext']}) → fold başına en fazla {res['max_feat']} seçilir |
770
  | AUC yalnızca temel özelliklerle | {('%.3f' % res['auc_base']) if res['auc_base'] is not None else '—'} (ek veri kaynakları yardım ediyor mu?) |
771
+ | Train–test AUC farkı (aşırı uyum) | {res['gap']:.3f} |
772
+ | AUC anlamlılığı (tek yönlü p) | {res['auc_p']:.3f} |
773
+ | Nested (dürüst) net getiri / işlem | {nest['mean']:.3%} (t = {nest['t']:.2f}, {nest['n']} işlem) |
774
+ | Hold-out (dokunulmamış son %20) | {ho_txt} |
775
  | Model edge | {edge} |
776
 
777
+ {checks_md}
778
+
779
+ *Üstteki OOS/backtest satırları geliştirme kısmında (ilk %80) walk-forward ve eşik OOS'ta seçildiği için iyimserdir; güvenilir olanlar nested ve hold-out satırlarıdır. Eğitim/araştırma amaçlıdır; yatırım tavsiyesi değildir.*"""
780
 
781
  # Grafik
782
  tail = df.tail(150)