GoshawkVortexAI commited on
Commit
7c3dffa
·
verified ·
1 Parent(s): fafa9aa

Upload app.py

Browse files
Files changed (1) hide show
  1. app.py +382 -0
app.py ADDED
@@ -0,0 +1,382 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import warnings
2
+ warnings.filterwarnings("ignore")
3
+
4
+ import numpy as np
5
+ import pandas as pd
6
+ import yfinance as yf
7
+ import gradio as gr
8
+ import plotly.graph_objects as go
9
+
10
+ from sklearn.impute import SimpleImputer
11
+ from sklearn.preprocessing import StandardScaler
12
+ from sklearn.pipeline import Pipeline
13
+ from sklearn.neighbors import KNeighborsClassifier, NearestNeighbors
14
+ from sklearn.metrics import balanced_accuracy_score
15
+
16
+ APP_TITLE = "Goshawk Adaptive BTCUSD Engine"
17
+ HORIZON = 5
18
+ FEE_DEFAULT = 0.05
19
+ SLIPPAGE_DEFAULT = 0.03
20
+
21
+
22
+ def download_btc(period="5y"):
23
+ df = yf.download("BTC-USD", period=period, interval="1d",
24
+ auto_adjust=True, progress=False)
25
+ if df is None or df.empty:
26
+ raise ValueError("BTC verisi alınamadı. İnternet/veri sağlayıcısını kontrol edin.")
27
+ if isinstance(df.columns, pd.MultiIndex):
28
+ df.columns = df.columns.get_level_values(0)
29
+ df = df[["Open", "High", "Low", "Close", "Volume"]].dropna().copy()
30
+ df.index = pd.to_datetime(df.index)
31
+ return df
32
+
33
+
34
+ def calc_atr(df, n=14):
35
+ pc = df.Close.shift(1)
36
+ tr = pd.concat([(df.High-df.Low), (df.High-pc).abs(),
37
+ (df.Low-pc).abs()], axis=1).max(axis=1)
38
+ return tr.ewm(alpha=1/n, adjust=False).mean()
39
+
40
+
41
+ def rsi(s, n=14):
42
+ d = s.diff()
43
+ up = d.clip(lower=0).ewm(alpha=1/n, adjust=False).mean()
44
+ dn = (-d.clip(upper=0)).ewm(alpha=1/n, adjust=False).mean()
45
+ rs = up / dn.replace(0, np.nan)
46
+ return 100 - 100/(1+rs)
47
+
48
+
49
+ def feature_engineering(df):
50
+ c, v = df.Close, df.Volume
51
+ x = pd.DataFrame(index=df.index)
52
+ for n in [1, 2, 3, 5, 10, 20, 50]:
53
+ x[f"return_{n}"] = c.pct_change(n)
54
+ for n in [5, 10, 20, 50]:
55
+ ema = c.ewm(span=n, adjust=False).mean()
56
+ x[f"ema_distance_{n}"] = c/ema - 1
57
+ x["rsi"] = rsi(c)/100
58
+ atr = calc_atr(df)
59
+ x["atr_pct"] = atr/c
60
+ x["range_pct"] = (df.High-df.Low)/c
61
+ x["body_pct"] = (df.Close-df.Open)/df.Open
62
+ x["volatility_10"] = c.pct_change().rolling(10).std()
63
+ x["volatility_20"] = c.pct_change().rolling(20).std()
64
+ x["volume_z"] = (v-v.rolling(20).mean())/v.rolling(20).std().replace(0, np.nan)
65
+ x["volume_return"] = v.pct_change()
66
+ macd = c.ewm(span=12, adjust=False).mean()-c.ewm(span=26, adjust=False).mean()
67
+ x["macd_pct"] = macd/c
68
+ return x.replace([np.inf, -np.inf], np.nan), atr
69
+
70
+
71
+ def make_labels(df, atr, horizon=HORIZON, threshold=0.20):
72
+ # Label is based only on the close-to-close forward return; features are lagged/current.
73
+ fwd = df.Close.shift(-horizon)/df.Close - 1
74
+ band = threshold*(atr/df.Close)*np.sqrt(horizon)
75
+ y = np.where(fwd > band, 1, np.where(fwd < -band, -1, 0))
76
+ return pd.Series(y, index=df.index), fwd
77
+
78
+
79
+ def build_pipeline(k):
80
+ return Pipeline([
81
+ ("imputer", SimpleImputer(strategy="median")),
82
+ ("scale", StandardScaler()),
83
+ ("knn", KNeighborsClassifier(n_neighbors=k, weights="distance"))
84
+ ])
85
+
86
+
87
+ def barrier_outcome(df, start_i, side, stop_atr, target_atr, horizon=HORIZON):
88
+ entry = float(df.Close.iloc[start_i])
89
+ atr = float(calc_atr(df).iloc[start_i])
90
+ if not np.isfinite(atr) or atr <= 0:
91
+ return None
92
+ if side == 1:
93
+ stop, target = entry-stop_atr*atr, entry+target_atr*atr
94
+ else:
95
+ stop, target = entry+stop_atr*atr, entry-target_atr*atr
96
+ end = min(start_i+horizon, len(df)-1)
97
+ for j in range(start_i+1, end+1):
98
+ hi, lo = float(df.High.iloc[j]), float(df.Low.iloc[j])
99
+ if side == 1:
100
+ hit_sl, hit_tp = lo <= stop, hi >= target
101
+ else:
102
+ hit_sl, hit_tp = hi >= stop, lo <= target
103
+ # Conservative assumption if both barriers are touched in one daily candle.
104
+ if hit_sl and hit_tp:
105
+ return -stop_atr*atr/entry
106
+ if hit_sl:
107
+ return -stop_atr*atr/entry
108
+ if hit_tp:
109
+ return target_atr*atr/entry
110
+ final = float(df.Close.iloc[end])
111
+ return side*(final-entry)/entry
112
+
113
+
114
+ def optimize_barriers(df, train_end, side, fee_pct, slip_pct):
115
+ # Select SL/TP from matured historical examples only.
116
+ sl_grid = [0.8, 1.0, 1.2, 1.5, 1.8, 2.0, 2.5, 3.0]
117
+ rr_grid = [1.0, 1.25, 1.5, 2.0, 2.5, 3.0, 4.0]
118
+ first = max(60, train_end-450)
119
+ candidates = []
120
+ cost = 2*(fee_pct+slip_pct)/100
121
+ for sl in sl_grid:
122
+ for rr in rr_grid:
123
+ outcomes = []
124
+ for i in range(first, train_end):
125
+ # Only use examples whose full forward horizon is inside training history.
126
+ if i+HORIZON >= train_end:
127
+ continue
128
+ r = barrier_outcome(df, i, side, sl, sl*rr)
129
+ if r is not None:
130
+ outcomes.append(r-cost)
131
+ if len(outcomes) < 35:
132
+ continue
133
+ arr = np.asarray(outcomes)
134
+ # Penalize unstable settings; maximize average net return per trade.
135
+ score = arr.mean() - 0.25*arr.std()/np.sqrt(len(arr))
136
+ candidates.append((score, sl, rr, len(arr), arr.mean(), (arr > 0).mean()))
137
+ if not candidates:
138
+ return 1.5, 2.0, None
139
+ best = max(candidates, key=lambda z: z[0])
140
+ return best[1], best[2], best
141
+
142
+
143
+ def regime_name(row):
144
+ if row["volatility_20"] > 0.045:
145
+ vol = "Yüksek volatilite"
146
+ elif row["volatility_20"] < 0.018:
147
+ vol = "Düşük volatilite"
148
+ else:
149
+ vol = "Orta volatilite"
150
+ if row["ema_distance_20"] > 0.025:
151
+ trend = "Yukarı trend"
152
+ elif row["ema_distance_20"] < -0.025:
153
+ trend = "Aşağı trend"
154
+ else:
155
+ trend = "Yatay / kararsız"
156
+ return f"{trend} · {vol}"
157
+
158
+
159
+ def run_engine(period, equity, max_risk, min_risk, max_leverage,
160
+ fee_pct, slip_pct, train_window, k):
161
+ try:
162
+ df = download_btc(period)
163
+ if len(df) < 300:
164
+ raise ValueError("En az 300 günlük mum gerekli.")
165
+ # Ignore today's unfinished daily candle by default.
166
+ df = df.iloc[:-1].copy()
167
+ X, atr = feature_engineering(df)
168
+ y, fwd = make_labels(df, atr)
169
+ data = X.copy()
170
+ data["target"] = y
171
+ data["future"] = fwd
172
+ valid = data.dropna(subset=["target", "future"])
173
+ valid = valid.dropna(subset=list(X.columns), how="all")
174
+ if len(valid) < 180:
175
+ raise ValueError("Model eğitimi için yeterli temiz örnek yok.")
176
+
177
+ # Latest known candle's feature row; training labels are already matured.
178
+ current_x = X.iloc[[-1]]
179
+ train_end = len(valid)
180
+ window = min(int(train_window), train_end-HORIZON-1)
181
+ train = valid.iloc[-window:]
182
+ if len(train) <= int(k):
183
+ raise ValueError("K komşu sayısı eğitim örneklerinden küçük.")
184
+
185
+ model = build_pipeline(int(k))
186
+ model.fit(train[X.columns], train.target.astype(int))
187
+ probs = model.predict_proba(current_x[X.columns])[0]
188
+ classes = model.named_steps["knn"].classes_
189
+ p = {int(c): float(v) for c, v in zip(classes, probs)}
190
+ p_long, p_short, p_flat = p.get(1, 0), p.get(-1, 0), p.get(0, 0)
191
+ direction = 1 if p_long >= p_short else -1
192
+ direction_prob = max(p_long, p_short)
193
+ edge = abs(p_long-p_short)
194
+
195
+ # KNN neighborhood agreement and chronological holdout score.
196
+ imputer, scaler = model.named_steps["imputer"], model.named_steps["scale"]
197
+ z_train = scaler.transform(imputer.transform(train[X.columns]))
198
+ z_now = scaler.transform(imputer.transform(current_x[X.columns]))
199
+ nn = NearestNeighbors(n_neighbors=int(k)).fit(z_train)
200
+ distances, indices = nn.kneighbors(z_now)
201
+ analogs = train.iloc[indices[0]]
202
+ agreement = float((analogs.target.astype(int) == direction).mean())
203
+
204
+ test_n = min(90, max(30, len(valid)//6))
205
+ hist_train = valid.iloc[:-test_n]
206
+ hist_test = valid.iloc[-test_n:]
207
+ eval_model = build_pipeline(int(k))
208
+ eval_model.fit(hist_train[X.columns], hist_train.target.astype(int))
209
+ pred = eval_model.predict(hist_test[X.columns])
210
+ holdout_bacc = balanced_accuracy_score(hist_test.target.astype(int), pred)
211
+
212
+ sl_mult, rr_mult, opt_stats = optimize_barriers(
213
+ df, len(df)-HORIZON, direction, float(fee_pct), float(slip_pct)
214
+ )
215
+ entry = float(df.Close.iloc[-1])
216
+ current_atr = float(atr.iloc[-1])
217
+ stop_dist = sl_mult*current_atr
218
+ tp_dist = stop_dist*rr_mult
219
+ if direction == 1:
220
+ stop, target = entry-stop_dist, entry+tp_dist
221
+ else:
222
+ stop, target = entry+stop_dist, entry-tp_dist
223
+
224
+ # Adaptive risk: base on model edge, neighbor agreement, validation quality,
225
+ # and current drawdown proxy (recent 30-day peak-to-trough close drawdown).
226
+ close = df.Close
227
+ recent = close.tail(30)
228
+ dd = float(recent.iloc[-1]/recent.cummax().iloc[-1]-1)
229
+ quality = np.clip((holdout_bacc-0.33)/0.20, 0, 1)
230
+ conviction = np.clip((direction_prob-0.34)/0.35, 0, 1)
231
+ neighbor_quality = np.clip((agreement-0.33)/0.50, 0, 1)
232
+ risk_factor = 0.20 + 0.35*quality + 0.25*conviction + 0.20*neighbor_quality
233
+ if dd < -0.12:
234
+ risk_factor *= 0.35
235
+ elif dd < -0.07:
236
+ risk_factor *= 0.60
237
+ risk_pct = float(min_risk) + (float(max_risk)-float(min_risk))*risk_factor
238
+ risk_pct = float(np.clip(risk_pct, min_risk, max_risk))
239
+
240
+ # No trade when evidence is weak or validation is poor.
241
+ trade_ok = (
242
+ direction_prob >= 0.43 and edge >= 0.12 and agreement >= 0.55
243
+ and holdout_bacc >= 0.36 and p_flat < 0.60
244
+ )
245
+ risk_cash = float(equity)*risk_pct/100
246
+ cost_rate = 2*(float(fee_pct)+float(slip_pct))/100
247
+ cost_per_btc = stop_dist + entry*cost_rate
248
+ btc_qty = risk_cash/cost_per_btc if cost_per_btc > 0 else 0
249
+ notional = btc_qty*entry
250
+ max_notional = float(equity)*float(max_leverage)
251
+ leverage_limited = notional > max_notional
252
+ if leverage_limited:
253
+ btc_qty = max_notional/entry
254
+ notional = btc_qty*entry
255
+ actual_risk = btc_qty*cost_per_btc
256
+ lot_size_btc = 1.0 # Change to broker's contract specification.
257
+ lots = btc_qty/lot_size_btc
258
+ margin = notional/max(float(max_leverage), 1)
259
+
260
+ if not trade_ok:
261
+ signal = "WAIT — İŞLEM AÇMA"
262
+ btc_qty = notional = lots = margin = actual_risk = 0.0
263
+ else:
264
+ signal = "LONG" if direction == 1 else "SHORT"
265
+
266
+ if opt_stats:
267
+ opt_text = (
268
+ f"Optimizasyon örnek sayısı: {opt_stats[3]} · "
269
+ f"Geçmiş ortalama net getiri/işlem: {opt_stats[4]*100:.3f}% · "
270
+ f"Geçmiş pozitif sonuç oranı: {opt_stats[5]*100:.1f}%"
271
+ )
272
+ else:
273
+ opt_text = "TP/SL optimizasyonu için yeterli olgunlaşmış örnek bulunamadı."
274
+
275
+ report = f"""
276
+ # {signal}
277
+
278
+ **Referans tarih:** {df.index[-1].date()} · **Referans fiyat:** ${entry:,.2f}
279
+
280
+ ## Modelin öğrendikleri
281
+ | Ölçüm | Sonuç |
282
+ |---|---:|
283
+ | P(up) | {p_long:.1%} |
284
+ | P(flat) | {p_flat:.1%} |
285
+ | P(down) | {p_short:.1%} |
286
+ | En yakın komşu uyumu | {agreement:.1%} |
287
+ | Kronolojik holdout balanced accuracy | {holdout_bacc:.3f} |
288
+ | Piyasa rejimi | {regime_name(X.iloc[-1])} |
289
+
290
+ ## Otomatik TP / SL
291
+ | Parametre | Değer |
292
+ |---|---:|
293
+ | Stop mesafesi | {sl_mult:.2f} ATR |
294
+ | Hedef mesafesi | {rr_mult:.2f}R |
295
+ | Stop-loss | ${stop:,.2f} |
296
+ | Take-profit | ${target:,.2f} |
297
+ | ATR(14) | ${current_atr:,.2f} |
298
+ | TP/SL geçmiş optimizasyonu | {opt_text} |
299
+
300
+ ## Adaptif pozisyon ve risk
301
+ | Parametre | Değer |
302
+ |---|---:|
303
+ | Hesap büyüklüğü | ${equity:,.2f} |
304
+ | Öğrenilmiş risk | {risk_pct:.2f}% |
305
+ | Risk bütçesi | ${risk_cash:,.2f} |
306
+ | BTC miktarı | {btc_qty:.6f} |
307
+ | Nominal pozisyon | ${notional:,.2f} |
308
+ | Tahmini teminat | ${margin:,.2f} |
309
+ | Tahmini stop kaybı + maliyet | ${actual_risk:,.2f} |
310
+ | Son 30 günlük kapanış DD | {dd:.2%} |
311
+ | Kaldıraç sınırı uygulandı mı? | {"Evet" if leverage_limited else "Hayır"} |
312
+
313
+ Risk yüzdesi model kalitesi, komşu uyumu ve yakın dönem drawdown'a göre seçildi.
314
+ """
315
+
316
+ fig = go.Figure(data=[go.Candlestick(
317
+ x=df.index[-120:], open=df.Open.tail(120), high=df.High.tail(120),
318
+ low=df.Low.tail(120), close=df.Close.tail(120), name="BTCUSD"
319
+ )])
320
+ if trade_ok:
321
+ fig.add_hline(y=stop, line_dash="dash", annotation_text="SL")
322
+ fig.add_hline(y=target, line_dash="dash", annotation_text="TP")
323
+ fig.update_layout(title="BTCUSD Daily", height=520,
324
+ xaxis_rangeslider_visible=False)
325
+
326
+ neighbor_table = analogs[["target", "future"]].copy()
327
+ neighbor_table["target"] = neighbor_table.target.map({-1:"DOWN",0:"FLAT",1:"UP"})
328
+ neighbor_table["future"] = neighbor_table.future.map(lambda z: f"{z:.2%}")
329
+ neighbor_table.index = neighbor_table.index.strftime("%Y-%m-%d")
330
+ neighbor_table = neighbor_table.reset_index().rename(
331
+ columns={"index":"Tarih","target":"Sınıf","future":"5 günlük getiri"}
332
+ )
333
+ disclaimer = (
334
+ "**Araştırma prototipi — otomatik emir göndermez.** Model her çalıştırmada "
335
+ "indirilen geçmiş veriyi yeniden eğitir; canlı öğrenme/online learning değildir. "
336
+ "TP/SL seçimi geçmiş örneklerde optimize edilir ve overfitting riski taşır. "
337
+ "KNN skorları kalibre edilmiş olasılık değildir. Günlük OHLC mumlarında aynı "
338
+ "gün TP ve SL birlikte görülürse muhafazakâr olarak SL varsayılır. Yahoo BTC-USD "
339
+ "verisi broker CFD fiyatı değildir. Lot büyüklüğü, komisyon, spread, funding, "
340
+ "swap, marjin ve minimum miktarı broker şartlarına göre doğrulayın. Gerçek "
341
+ "hesapta kullanmadan önce out-of-sample walk-forward, paper trading ve stres "
342
+ "testleri zorunludur."
343
+ )
344
+ return report, fig, neighbor_table, disclaimer
345
+
346
+ except Exception as exc:
347
+ return f"# Hata\n{type(exc).__name__}: {exc}", None, pd.DataFrame(), \
348
+ "Veri bağlantısını ve parametreleri kontrol edin."
349
+
350
+
351
+ with gr.Blocks(title=APP_TITLE, theme=gr.themes.Soft()) as app:
352
+ gr.Markdown(
353
+ "# GOSHAWK — Adaptive BTCUSD KNN Risk Engine\n"
354
+ "Günlük veride KNN yön tahmini, geçmiş bariyer sonuçlarından adaptif TP/SL "
355
+ "seçimi ve model kalitesine göre dinamik risk/pozisyon boyutlandırma."
356
+ )
357
+ with gr.Row():
358
+ with gr.Column():
359
+ period = gr.Dropdown(["2y", "5y", "max"], value="5y", label="Veri geçmişi")
360
+ train_window = gr.Slider(250, 1500, value=900, step=50, label="Eğitim penceresi")
361
+ k = gr.Slider(3, 31, value=9, step=2, label="KNN komşu sayısı")
362
+ equity = gr.Number(value=10000, label="Hesap büyüklüğü (USD)")
363
+ with gr.Column():
364
+ min_risk = gr.Slider(0.1, 0.5, value=0.1, step=0.05, label="Minimum risk (%)")
365
+ max_risk = gr.Slider(0.5, 2.0, value=0.75, step=0.05, label="Maksimum risk (%)")
366
+ max_leverage = gr.Slider(1, 10, value=3, step=1, label="Azami kaldıraç")
367
+ fee_pct = gr.Number(value=FEE_DEFAULT, label="Tek yön komisyon (%)")
368
+ slip_pct = gr.Number(value=SLIPPAGE_DEFAULT, label="Tek yön slippage (%)")
369
+ run = gr.Button("Veriyi öğren ve analiz et", variant="primary")
370
+ output = gr.Markdown()
371
+ chart = gr.Plot()
372
+ neighbors = gr.Dataframe(label="En yakın geçmiş piyasa örnekleri", interactive=False)
373
+ disclaimer = gr.Markdown()
374
+ run.click(
375
+ run_engine,
376
+ inputs=[period, equity, max_risk, min_risk, max_leverage,
377
+ fee_pct, slip_pct, train_window, k],
378
+ outputs=[output, chart, neighbors, disclaimer]
379
+ )
380
+
381
+ if __name__ == "__main__":
382
+ app.launch(server_name="0.0.0.0", server_port=7860)