Spaces:
Sleeping
Sleeping
Update app.py
Browse files
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ı
|
|
|
|
| 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 |
-
|
| 505 |
-
|
| 506 |
elif name == "et":
|
| 507 |
-
|
| 508 |
-
|
| 509 |
else:
|
| 510 |
-
|
| 511 |
-
|
| 512 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 513 |
|
| 514 |
|
| 515 |
-
def
|
| 516 |
-
"""Örtüşmeyen (her `horizon` barda bir)
|
| 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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
|
| 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 |
-
|
| 540 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
# ---
|
| 584 |
-
|
| 585 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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(
|
| 592 |
try:
|
| 593 |
-
auc_base = float(roc_auc_score(
|
| 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 |
-
|
| 687 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
|
|
|
|
|
|
| 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)
|