GoshawkVortexAI commited on
Commit
9d339cb
·
verified ·
1 Parent(s): b0e14ac

Upload 2 files

Browse files
Files changed (2) hide show
  1. app.py +0 -0
  2. knn_calibrate.py +238 -0
app.py ADDED
The diff for this file is too large to render. See raw diff
 
knn_calibrate.py ADDED
@@ -0,0 +1,238 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ KNN KALİBRASYON v2 — rastgele arama + genişleyen pencereli walk-forward + gürültü (null) testi.
3
+
4
+ Kullanım (app.py ile aynı klasörde, internet gerekir):
5
+ python knn_calibrate.py # BTC-USDT 1H, 4000 bar, 120 örnek, 6 dilim
6
+ python knn_calibrate.py BTC-USDT 1H 6000 200 8 # sembol bar toplam_bar örnek dilim
7
+ python knn_calibrate.py BTC-USDT 1H 4000 120 6 --no-null --force-save
8
+
9
+ Neden bu kadar titiz:
10
+ * KNN eğitim gerektirmez (nedensel) -> tek bir geçmiş geçişi tüm dilimler için geçerlidir.
11
+ * WALK-FORWARD: dilim k için parametreler yalnızca k'dan ÖNCE tamamen sonuçlanmış işlemlere bakılarak seçilir,
12
+ sonra dilim k'da test edilir. Birleşik test (OOS) işlemleri "seçim PROSEDÜRÜNÜN" dürüst performansıdır.
13
+ * Seçim ölçütü kâr faktörü tepe değeri değil, ortalama R'nin ALT GÜVEN SINIRI (ort − hata): az işlemli şanslı
14
+ kombinasyonlar cezalandırılır. Win rate hedef değil; başa baş win rate her satırda gösterilir.
15
+ * NULL TESTİ: aynı prosedür, bar blokları karıştırılmış (zaman yapısı yok edilmiş) fiyat üzerinde çalıştırılır.
16
+ Gürültüde de "iyi" görünüyorsa prosedür kendini kandırıyor demektir.
17
+ * knn_params.json YALNIZCA test (OOS) ortalama R'si için %95 bootstrap alt sınırı > 0 ise yazılır (--force-save ile zorlanır).
18
+ """
19
+ import datetime as dt
20
+ import json
21
+ import os
22
+ import sys
23
+ import time
24
+
25
+ os.environ["APP_NO_LAUNCH"] = "1"
26
+ import numpy as np
27
+ import pandas as pd
28
+
29
+ import app
30
+
31
+ MIN_TRAIN_TRADES = 15
32
+ BOOT = 2000
33
+ SPACE = {
34
+ "K": (7, 11, 15, 21, 31, 45), "edge": (0.10, 0.15, 0.20, 0.30, 0.40, 0.50),
35
+ "fwd": (3, 5, 8, 12), "move": (0.10, 0.15, 0.25, 0.35), "pool": (400, 800, 1500),
36
+ "sl": (1.0, 1.5, 2.0, 2.5), "tp": (1.5, 2.0, 2.5, 3.5, 5.0), "thr": (2, 3, 4),
37
+ }
38
+ BASELINE = {"K": 15, "edge": 0.15, "fwd": 5, "move": 0.15, "pool": 1500, "sl": 1.5, "tp": 2.5, "thr": 2}
39
+
40
+
41
+ # ---------------------------------------------------------------------------
42
+ def fetch_history(inst_id="BTC-USDT", bar="1H", total=4000):
43
+ rows, after = [], None
44
+ while len(rows) < total:
45
+ p = {"instId": inst_id, "bar": bar, "limit": 100}
46
+ if after:
47
+ p["after"] = after
48
+ raw = app._get("/api/v5/market/history-candles", p)
49
+ if not raw:
50
+ break
51
+ rows += raw
52
+ after = raw[-1][0] # en eski mum -> sonraki sayfa daha eskiyi getirir
53
+ time.sleep(0.12)
54
+ cols = ["ts", "o", "h", "l", "c", "vol", "volCcy", "volCcyQuote", "confirm"]
55
+ df = pd.DataFrame(rows, columns=cols).drop_duplicates("ts")
56
+ for c in ["o", "h", "l", "c", "vol"]:
57
+ df[c] = df[c].astype(float)
58
+ df["confirm"] = df["confirm"].astype(int)
59
+ df["ts"] = pd.to_datetime(df["ts"].astype(np.int64), unit="ms")
60
+ df = df[df["confirm"] == 1].sort_values("ts").reset_index(drop=True)
61
+ return df.tail(total).reset_index(drop=True)
62
+
63
+
64
+ def shuffle_blocks(df, block=24, seed=0):
65
+ """NULL verisi: bar getirilerini bloklar halinde karıştırır (dağılım korunur, zamansal yapı yok olur)."""
66
+ rng = np.random.default_rng(seed)
67
+ prev = df["c"].shift(1)
68
+ rel = pd.DataFrame({k: df[k] / prev for k in ("o", "h", "l", "c")}).iloc[1:].reset_index(drop=True)
69
+ vol = df["vol"].iloc[1:].reset_index(drop=True)
70
+ idx = np.arange(len(rel))
71
+ blocks = [idx[i:i + block] for i in range(0, len(idx), block)]
72
+ order = np.concatenate([blocks[j] for j in rng.permutation(len(blocks))])
73
+ rel, vol = rel.iloc[order].reset_index(drop=True), vol.iloc[order].reset_index(drop=True)
74
+ c = float(df["c"].iloc[0]) * rel["c"].cumprod()
75
+ pc = np.r_[float(df["c"].iloc[0]), c.to_numpy()[:-1]]
76
+ out = pd.DataFrame({"ts": df["ts"].iloc[1:].reset_index(drop=True), "o": rel["o"] * pc, "h": rel["h"] * pc,
77
+ "l": rel["l"] * pc, "c": c, "vol": vol})
78
+ out["h"] = out[["o", "h", "l", "c"]].max(axis=1)
79
+ out["l"] = out[["o", "h", "l", "c"]].min(axis=1)
80
+ return out
81
+
82
+
83
+ def sample_params(rng, n):
84
+ out = [dict(BASELINE)] # 0. örnek = mevcut varsayılanlar (kıyas için)
85
+ seen = {tuple(BASELINE.values())}
86
+ while len(out) < n:
87
+ p = {k: (type(v[0])(rng.choice(v))) for k, v in SPACE.items()}
88
+ if p["tp"] <= p["sl"] * 0.5 or tuple(p.values()) in seen:
89
+ continue
90
+ seen.add(tuple(p.values()))
91
+ out.append(p)
92
+ return out
93
+
94
+
95
+ def _set_knn(p):
96
+ app.KNN_K, app.KNN_MIN_EDGE, app.KNN_FORWARD = p["K"], p["edge"], p["fwd"]
97
+ app.KNN_MOVE_ATR, app.KNN_POOL_MAX = p["move"], p["pool"]
98
+ app.KNN_TIERS = app._knn_tiers(p["edge"])
99
+
100
+
101
+ def evaluate_all(df, params, start0, log=True):
102
+ """Her parametre kümesi için TÜM zamanın işlemlerini üretir (skorlar KNN anahtarına göre önbelleğe alınır)."""
103
+ n = len(df)
104
+ ind = app.compute_indicators(df)
105
+ cache, results = {}, []
106
+ t0 = time.time()
107
+ for k, p in enumerate(params):
108
+ key = (p["K"], p["edge"], p["fwd"], p["move"], p["pool"])
109
+ if key not in cache:
110
+ _set_knn(p)
111
+ ind["_arr"]["knn_y"] = app.knn_labels(df, ind, forward=p["fwd"], move=p["move"])
112
+ cache[key] = [app._score_at(ind, i) for i in range(start0, n)]
113
+ app.SL_ATR, app.TP_ATR = p["sl"], p["tp"]
114
+ trades = app.simulate_trades(df, ind, start0, cache[key], threshold=p["thr"])
115
+ results.append((p, trades))
116
+ if log and (k + 1) % 20 == 0:
117
+ print(f" {k + 1}/{len(params)} kombinasyon ({time.time() - t0:.0f}s)", flush=True)
118
+ return results
119
+
120
+
121
+ def objective(trades):
122
+ """Ortalama R'nin alt güven sınırı (ort − hata). İşlem azsa None."""
123
+ if len(trades) < MIN_TRAIN_TRADES:
124
+ return None
125
+ r = np.array([t["r"] for t in trades])
126
+ return float(r.mean() - r.std(ddof=1) / np.sqrt(len(r)))
127
+
128
+
129
+ def summarize(trades, seed=1):
130
+ if not trades:
131
+ return None
132
+ r = np.array([t["r"] for t in trades])
133
+ pnl = np.array([t["pnl"] for t in trades])
134
+ gl = abs(pnl[pnl <= 0].sum())
135
+ rng = np.random.default_rng(seed)
136
+ bm = rng.choice(r, size=(BOOT, len(r)), replace=True).mean(axis=1)
137
+ return {"n": len(r), "wr": float((pnl > 0).mean()), "pf": float(pnl[pnl > 0].sum() / gl) if gl > 0 else float("inf"),
138
+ "mean_r": float(r.mean()), "ci_lo": float(np.percentile(bm, 2.5)), "ci_hi": float(np.percentile(bm, 97.5))}
139
+
140
+
141
+ def walk_forward(results, start0, n, folds):
142
+ edges = np.linspace(start0, n, folds + 1).astype(int)
143
+ oos, picks = [], []
144
+ for k in range(1, folds):
145
+ cut = edges[k]
146
+ best = None
147
+ for p, tr in results:
148
+ sc = objective([t for t in tr if t["exit_i"] < cut]) # yalnızca tamamen sonuçlanmış geçmiş
149
+ if sc is not None and (best is None or sc > best[0]):
150
+ best = (sc, p, tr)
151
+ if best is None:
152
+ continue
153
+ seg = [t for t in best[2] if edges[k] <= t["signal_i"] < edges[k + 1]]
154
+ oos += seg
155
+ picks.append((k, best[1], len(seg), summarize(seg)))
156
+ base = [t for t in results[0][1] if t["signal_i"] >= edges[1]]
157
+ return oos, picks, base
158
+
159
+
160
+ def final_pick(results):
161
+ best = None
162
+ for p, tr in results:
163
+ sc = objective(tr)
164
+ if sc is not None and (best is None or sc > best[0]):
165
+ best = (sc, p, tr)
166
+ return best
167
+
168
+
169
+ def procedure(df, n_samples, folds, seed=7, log=True):
170
+ n = len(df)
171
+ start0 = app.WARMUP_BARS + app.KNN_MIN_POOL + max(SPACE["fwd"])
172
+ params = sample_params(np.random.default_rng(seed), n_samples)
173
+ results = evaluate_all(df, params, start0, log)
174
+ oos, picks, base = walk_forward(results, start0, n, folds)
175
+ return {"results": results, "oos": oos, "picks": picks, "base": base, "final": final_pick(results), "n": n, "start0": start0}
176
+
177
+
178
+ def _fmt(s):
179
+ if not s:
180
+ return "—"
181
+ return f"n={s['n']:<3d} WR=%{s['wr'] * 100:4.1f} PF={min(s['pf'], 99):4.2f} ort.R={s['mean_r']:+.3f} [%95 GA {s['ci_lo']:+.3f}…{s['ci_hi']:+.3f}]"
182
+
183
+
184
+ def main():
185
+ args = [a for a in sys.argv[1:] if not a.startswith("--")]
186
+ flags = {a for a in sys.argv[1:] if a.startswith("--")}
187
+ inst = args[0] if len(args) > 0 else "BTC-USDT"
188
+ bar = args[1] if len(args) > 1 else "1H"
189
+ total = int(args[2]) if len(args) > 2 else 4000
190
+ n_samples = int(args[3]) if len(args) > 3 else 120
191
+ folds = int(args[4]) if len(args) > 4 else 6
192
+
193
+ print(f"{inst} {bar}: {total} bar indiriliyor...")
194
+ df = fetch_history(inst, bar, total)
195
+ print(f"{len(df)} bar: {df['ts'].iloc[0]} → {df['ts'].iloc[-1]}")
196
+ print(f"\n[1/2] GERÇEK veri: {n_samples} kombinasyon, {folds} dilimli walk-forward")
197
+ real = procedure(df, n_samples, folds)
198
+ oos, base = summarize(real["oos"]), summarize(real["base"])
199
+ print("\n── Dilim dilim seçilen parametreler (her biri yalnızca geçmişe bakarak) ──")
200
+ for k, p, m, s in real["picks"]:
201
+ print(f" dilim {k}: K={p['K']} edge={p['edge']} fwd={p['fwd']} move={p['move']} pool={p['pool']} SL/TP={p['sl']}/{p['tp']} eşik={p['thr']} → test {_fmt(s)}")
202
+ print("\n── BİRLEŞİK TEST (OOS) ──")
203
+ print(" Kalibre prosedür :", _fmt(oos))
204
+ print(" Sabit varsayılan :", _fmt(base))
205
+
206
+ null = None
207
+ if "--no-null" not in flags:
208
+ print(f"\n[2/2] NULL testi (bar blokları karıştırılmış fiyat; aynı prosedür)")
209
+ nl = procedure(shuffle_blocks(df), n_samples, folds, log=False)
210
+ null = summarize(nl["oos"])
211
+ print(" Null OOS :", _fmt(null))
212
+
213
+ fin = real["final"]
214
+ print("\n── DÜRÜST HÜKÜM ──")
215
+ evidence = bool(oos and oos["ci_lo"] > 0 and oos["n"] >= 30)
216
+ if null and null["mean_r"] > 0.05:
217
+ print(f" ⚠ Null testinde bile ort. R {null['mean_r']:+.3f}: prosedür gürültüden 'kâr' üretiyor, sonuçlara temkinli yaklaş.")
218
+ evidence = evidence and oos["mean_r"] > null["mean_r"] + 0.05
219
+ if evidence:
220
+ print(f" ✔ OOS ort. R %95 alt sınırı > 0 ({oos['ci_lo']:+.3f}) ve ≥30 işlem: istatistiksel kanıt VAR (garanti değil, rejim değişebilir).")
221
+ else:
222
+ print(" ✘ Kalibre edilmiş sistemin OOS kârlılığı için yeterli istatistiksel kanıt YOK." + (" (--force-save: yine de yazılıyor)" if "--force-save" in flags else " Parametreler dosyaya yazılmaz."))
223
+ if fin:
224
+ p = fin[1]
225
+ print(f" Tüm veride en iyi (iyimser, in-sample): K={p['K']} edge={p['edge']} fwd={p['fwd']} move={p['move']} pool={p['pool']} SL/TP={p['sl']}/{p['tp']} eşik={p['thr']} → {_fmt(summarize(fin[2]))}")
226
+ if evidence or "--force-save" in flags:
227
+ out = {"instrument": inst, "bar": bar, "bars": len(df), "created": dt.datetime.now().isoformat(timespec="seconds"),
228
+ "params": {"KNN_K": p["K"], "KNN_MIN_EDGE": p["edge"], "KNN_FORWARD": p["fwd"], "KNN_MOVE_ATR": p["move"],
229
+ "KNN_POOL_MAX": p["pool"], "SL_ATR": p["sl"], "TP_ATR": p["tp"], "SIGNAL_THRESHOLD": float(p["thr"])},
230
+ "oos": oos, "null_oos": null}
231
+ path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "knn_params.json")
232
+ with open(path, "w", encoding="utf-8") as f:
233
+ json.dump(out, f, indent=2, ensure_ascii=False)
234
+ print(f" → {path} yazıldı. app.py bir sonraki açılışta bu parametreleri otomatik yükler.")
235
+
236
+
237
+ if __name__ == "__main__":
238
+ main()