GoshawkVortexAI commited on
Commit
4055ccb
·
verified ·
1 Parent(s): 50e3a31

Upload app.py

Browse files
Files changed (1) hide show
  1. app.py +354 -0
app.py ADDED
@@ -0,0 +1,354 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import warnings
2
+ warnings.filterwarnings("ignore")
3
+
4
+ from datetime import datetime, timezone
5
+ import numpy as np
6
+ import pandas as pd
7
+ import yfinance as yf
8
+ import gradio as gr
9
+ import plotly.graph_objects as go
10
+
11
+ from sklearn.pipeline import Pipeline
12
+ from sklearn.preprocessing import StandardScaler
13
+ from sklearn.impute import SimpleImputer
14
+ from sklearn.neighbors import KNeighborsClassifier, NearestNeighbors
15
+ from sklearn.metrics import accuracy_score, balanced_accuracy_score, confusion_matrix
16
+
17
+ APP_TITLE = "BTCUSD KNN Daily Risk Engine"
18
+
19
+ def rsi(series, period=14):
20
+ delta = series.diff()
21
+ gain = delta.clip(lower=0).ewm(alpha=1 / period, adjust=False).mean()
22
+ loss = (-delta.clip(upper=0)).ewm(alpha=1 / period, adjust=False).mean()
23
+ rs = gain / loss.replace(0, np.nan)
24
+ return 100 - (100 / (1 + rs))
25
+
26
+ def atr(df, period=14):
27
+ prev_close = df["Close"].shift(1)
28
+ tr = pd.concat([
29
+ df["High"] - df["Low"],
30
+ (df["High"] - prev_close).abs(),
31
+ (df["Low"] - prev_close).abs()
32
+ ], axis=1).max(axis=1)
33
+ return tr.ewm(alpha=1 / period, adjust=False).mean()
34
+
35
+ def download_data(period):
36
+ df = yf.download("BTC-USD", period=period, interval="1d",
37
+ auto_adjust=True, progress=False)
38
+ if df is None or df.empty:
39
+ raise ValueError("Yahoo Finance veri döndürmedi. Daha sonra tekrar deneyin.")
40
+ if isinstance(df.columns, pd.MultiIndex):
41
+ df.columns = df.columns.get_level_values(0)
42
+ needed = ["Open", "High", "Low", "Close", "Volume"]
43
+ df = df[needed].copy().dropna()
44
+ df.index = pd.to_datetime(df.index)
45
+ return df
46
+
47
+ def make_features(df):
48
+ x = pd.DataFrame(index=df.index)
49
+ close = df["Close"]
50
+ ret = close.pct_change()
51
+ for n in [1, 2, 3, 5, 10, 20]:
52
+ x[f"ret_{n}"] = close.pct_change(n)
53
+ x["rsi_14"] = rsi(close, 14) / 100.0
54
+ a = atr(df, 14)
55
+ x["atr_pct"] = a / close
56
+ x["range_pct"] = (df["High"] - df["Low"]) / close
57
+ x["body_pct"] = (df["Close"] - df["Open"]) / df["Open"]
58
+ for n in [5, 10, 20, 50]:
59
+ ema = close.ewm(span=n, adjust=False).mean()
60
+ x[f"ema_gap_{n}"] = close / ema - 1
61
+ ema12 = close.ewm(span=12, adjust=False).mean()
62
+ ema26 = close.ewm(span=26, adjust=False).mean()
63
+ macd = ema12 - ema26
64
+ signal = macd.ewm(span=9, adjust=False).mean()
65
+ x["macd_hist_pct"] = (macd - signal) / close
66
+ x["volatility_10"] = ret.rolling(10).std()
67
+ x["volatility_20"] = ret.rolling(20).std()
68
+ vol_mean = df["Volume"].rolling(20).mean()
69
+ vol_std = df["Volume"].rolling(20).std()
70
+ x["volume_z20"] = (df["Volume"] - vol_mean) / vol_std.replace(0, np.nan)
71
+ x["volume_change"] = df["Volume"].pct_change()
72
+ return x.replace([np.inf, -np.inf], np.nan), a
73
+
74
+ def build_model(k):
75
+ return Pipeline([
76
+ ("imputer", SimpleImputer(strategy="median")),
77
+ ("scaler", StandardScaler()),
78
+ ("knn", KNeighborsClassifier(
79
+ n_neighbors=k, weights="distance", metric="minkowski", p=2
80
+ ))
81
+ ])
82
+
83
+ def fmt_pct(v):
84
+ return f"{v * 100:.2f}%"
85
+
86
+ def run_engine(period, k, train_window, threshold_atr, atr_stop,
87
+ reward_risk, equity, risk_pct, max_leverage,
88
+ contract_size, fee_pct, slippage_pct, use_latest):
89
+ try:
90
+ df = download_data(period)
91
+ if len(df) < 180:
92
+ raise ValueError("Yeterli günlük veri yok. En az 180 mum gerekli.")
93
+
94
+ # Yahoo'nun son günlük mumu henüz kapanmamış olabilir.
95
+ if not use_latest and len(df) > 2:
96
+ df = df.iloc[:-1].copy()
97
+
98
+ X, atr_series = make_features(df)
99
+ next_ret = df["Close"].shift(-1) / df["Close"] - 1
100
+ next_atr_pct = atr_series / df["Close"]
101
+ band = threshold_atr * next_atr_pct
102
+
103
+ # Üç sınıf: -1 aşağı, 0 nötr, +1 yukarı.
104
+ y = pd.Series(
105
+ np.where(next_ret > band, 1, np.where(next_ret < -band, -1, 0)),
106
+ index=df.index
107
+ )
108
+
109
+ data = X.copy()
110
+ data["target"] = y
111
+ data["future_return"] = next_ret
112
+ data = data.replace([np.inf, -np.inf], np.nan)
113
+ feature_cols = list(X.columns)
114
+
115
+ # Son satırın ileriye dönük hedefi bilinmez; yalnızca eğitimden çıkarılır.
116
+ trainable = data.dropna(subset=["target", "future_return"])
117
+ trainable = trainable.dropna(subset=feature_cols, how="all")
118
+ if len(trainable) < max(100, int(k) + 30):
119
+ raise ValueError("KNN eğitimi için yeterli temiz örnek bulunamadı.")
120
+
121
+ # Son kapanmış mumun özellikleriyle tahmin.
122
+ current_x = X.iloc[[-1]]
123
+ if current_x.isna().all(axis=1).iloc[0]:
124
+ raise ValueError("Son mum için özellik üretilemedi.")
125
+
126
+ window = min(int(train_window), len(trainable))
127
+ train = trainable.iloc[-window:]
128
+ if len(train) <= int(k):
129
+ raise ValueError("Komşu sayısı eğitim örneklerinden küçük olmalı.")
130
+
131
+ model = build_model(int(k))
132
+ model.fit(train[feature_cols], train["target"].astype(int))
133
+ probs = model.predict_proba(current_x[feature_cols])[0]
134
+ classes = model.named_steps["knn"].classes_
135
+ prob_map = {int(c): float(p) for c, p in zip(classes, probs)}
136
+ p_down = prob_map.get(-1, 0.0)
137
+ p_flat = prob_map.get(0, 0.0)
138
+ p_up = prob_map.get(1, 0.0)
139
+
140
+ # Yakın geçmiş örnekleri: aynı özellik uzayında en yakın K gözlem.
141
+ transformed_train = model.named_steps["scaler"].transform(
142
+ model.named_steps["imputer"].transform(train[feature_cols])
143
+ )
144
+ transformed_now = model.named_steps["scaler"].transform(
145
+ model.named_steps["imputer"].transform(current_x[feature_cols])
146
+ )
147
+ nn = NearestNeighbors(n_neighbors=int(k), metric="euclidean")
148
+ nn.fit(transformed_train)
149
+ distances, indices = nn.kneighbors(transformed_now)
150
+ neighbors = train.iloc[indices[0]].copy()
151
+ neighbors["distance"] = distances[0]
152
+ mean_neighbor_return = float(neighbors["future_return"].mean())
153
+ median_neighbor_return = float(neighbors["future_return"].median())
154
+
155
+ # Chronological holdout sanity check: train on past, test on later samples.
156
+ eval_n = min(120, max(30, len(trainable) // 5))
157
+ if len(trainable) > eval_n + 100:
158
+ hist_train = trainable.iloc[:-eval_n]
159
+ hist_test = trainable.iloc[-eval_n:]
160
+ eval_model = build_model(int(k))
161
+ eval_model.fit(hist_train[feature_cols], hist_train["target"].astype(int))
162
+ pred = eval_model.predict(hist_test[feature_cols])
163
+ acc = accuracy_score(hist_test["target"].astype(int), pred)
164
+ bacc = balanced_accuracy_score(hist_test["target"].astype(int), pred)
165
+ eval_text = (
166
+ f"Son {eval_n} örneklik kronolojik holdout — "
167
+ f"Accuracy: {acc:.3f} | Balanced accuracy: {bacc:.3f}. "
168
+ "Bu sonuç gelecekteki performans garantisi değildir."
169
+ )
170
+ else:
171
+ eval_text = "Holdout değerlendirmesi için yeterli geçmiş örnek yok."
172
+
173
+ entry = float(df["Close"].iloc[-1])
174
+ current_atr = float(atr_series.iloc[-1])
175
+ if not np.isfinite(current_atr) or current_atr <= 0:
176
+ raise ValueError("ATR hesaplanamadı.")
177
+
178
+ # Basit karar filtresi; nötr sınıf yüksekse işlem açma.
179
+ if p_up > p_down and p_up >= 0.45 and p_flat < 0.55:
180
+ side = "LONG"
181
+ stop = entry - atr_stop * current_atr
182
+ target = entry + reward_risk * (entry - stop)
183
+ confidence = p_up
184
+ elif p_down > p_up and p_down >= 0.45 and p_flat < 0.55:
185
+ side = "SHORT"
186
+ stop = entry + atr_stop * current_atr
187
+ target = entry - reward_risk * (stop - entry)
188
+ confidence = p_down
189
+ else:
190
+ side = "WAIT / NO TRADE"
191
+ stop = np.nan
192
+ target = np.nan
193
+ confidence = max(p_up, p_down)
194
+
195
+ risk_cash = float(equity) * float(risk_pct) / 100.0
196
+ stop_distance = abs(entry - stop) if np.isfinite(stop) else np.nan
197
+
198
+ # fee_pct ve slippage_pct her yön için varsayılır; yaklaşık round-trip maliyet.
199
+ round_trip_cost_rate = 2 * (float(fee_pct) + float(slippage_pct)) / 100.0
200
+ if side != "WAIT / NO TRADE":
201
+ stop_pct = stop_distance / entry
202
+ cost_per_btc = stop_distance + entry * round_trip_cost_rate
203
+ btc_qty = risk_cash / cost_per_btc if cost_per_btc > 0 else 0.0
204
+ notional = btc_qty * entry
205
+ leverage_used = notional / float(equity) if equity > 0 else np.nan
206
+ if leverage_used > float(max_leverage):
207
+ btc_qty = (float(equity) * float(max_leverage)) / entry
208
+ notional = btc_qty * entry
209
+ effective_risk = btc_qty * cost_per_btc
210
+ sizing_note = (
211
+ f"Kaldıraç sınırı nedeniyle miktar kısıldı. "
212
+ f"Gerçekleşen yaklaşık stop riski: {effective_risk:.2f} "
213
+ f"({effective_risk / equity * 100:.2f}%)."
214
+ )
215
+ else:
216
+ effective_risk = btc_qty * cost_per_btc
217
+ sizing_note = "Pozisyon miktarı risk bütçesine göre hesaplandı."
218
+ lots = btc_qty / float(contract_size)
219
+ margin_est = notional / float(max_leverage)
220
+ else:
221
+ btc_qty = notional = lots = margin_est = 0.0
222
+ effective_risk = 0.0
223
+ sizing_note = "Model filtresi yeterli yönsel avantaj bulamadı; işlem açma."
224
+
225
+ # Fiyat grafiği ve ATR seviyeleri.
226
+ plot_df = df.tail(120)
227
+ fig = go.Figure()
228
+ fig.add_trace(go.Candlestick(
229
+ x=plot_df.index, open=plot_df["Open"], high=plot_df["High"],
230
+ low=plot_df["Low"], close=plot_df["Close"], name="BTC-USD"
231
+ ))
232
+ if np.isfinite(stop):
233
+ fig.add_hline(y=stop, line_dash="dash", annotation_text="Stop")
234
+ fig.add_hline(y=target, line_dash="dash", annotation_text="Take profit")
235
+ fig.update_layout(
236
+ title="BTC-USD — Daily candles (last 120)",
237
+ xaxis_title="Date", yaxis_title="USD",
238
+ height=520, xaxis_rangeslider_visible=False
239
+ )
240
+
241
+ neighbor_view = neighbors[["target", "future_return", "distance"]].copy()
242
+ neighbor_view["target"] = neighbor_view["target"].map(
243
+ {-1: "DOWN", 0: "FLAT", 1: "UP"}
244
+ )
245
+ neighbor_view["future_return"] = neighbor_view["future_return"].map(fmt_pct)
246
+ neighbor_view["distance"] = neighbor_view["distance"].map(lambda z: f"{z:.3f}")
247
+ neighbor_view.index = neighbor_view.index.strftime("%Y-%m-%d")
248
+ neighbor_view = neighbor_view.reset_index().rename(columns={
249
+ "index": "Neighbor date", "target": "Historical class",
250
+ "future_return": "Next-day return", "distance": "Distance"
251
+ })
252
+
253
+ report = f"""
254
+ ## {side}
255
+
256
+ **Son kullanılan mum:** {df.index[-1].date()}
257
+ **Kapanış / referans giriş:** ${entry:,.2f}
258
+
259
+ ### KNN sınıf olasılıkları
260
+ - Yukarı: **{p_up:.1%}**
261
+ - Nötr: **{p_flat:.1%}**
262
+ - Aşağı: **{p_down:.1%}**
263
+ - Seçilen yönün olasılığı: **{confidence:.1%}**
264
+
265
+ ### Plan seviyeleri
266
+ - Stop-loss: **{('$' + format(stop, ',.2f')) if np.isfinite(stop) else '—'}**
267
+ - Take-profit: **{('$' + format(target, ',.2f')) if np.isfinite(target) else '—'}**
268
+ - ATR(14): **${current_atr:,.2f}**
269
+ - Planlanan R:R: **1 : {reward_risk:.2f}**
270
+
271
+ ### Pozisyon boyutu
272
+ - Hesap özsermayesi: **${equity:,.2f}**
273
+ - Risk bütçesi: **${risk_cash:,.2f} ({risk_pct:.2f}%)**
274
+ - Tahmini BTC miktarı: **{btc_qty:.6f} BTC**
275
+ - Tahmini lot: **{lots:.6f} lot** (1 lot = {contract_size} BTC)
276
+ - Nominal pozisyon: **${notional:,.2f}**
277
+ - Tahmini teminat (@ max leverage): **${margin_est:,.2f}**
278
+ - Tahmini stop riski + maliyet: **${effective_risk:,.2f}**
279
+ - {sizing_note}
280
+
281
+ ### Komşu örnekler
282
+ - En yakın K örneğin ortalama ertesi gün getirisi: **{fmt_pct(mean_neighbor_return)}**
283
+ - Medyan ertesi gün getirisi: **{fmt_pct(median_neighbor_return)}**
284
+
285
+ ### Model kontrolü
286
+ {eval_text}
287
+ """
288
+
289
+ notes = (
290
+ "Önemli: Bu bir araştırma/prototip karar destek aracıdır; yatırım tavsiyesi veya "
291
+ "kârlılık garantisi değildir. KNN olasılıkları kalibre edilmiş gerçek olasılıklar "
292
+ "olarak yorumlanmamalıdır. Günlük BTC-USD verisi Yahoo Finance'tan gelir; "
293
+ "broker CFD fiyatı, spread, swap, funding, minimum lot, lot sözleşmesi ve "
294
+ "marjin kuralları farklı olabilir. Emir vermeden önce broker şartlarıyla "
295
+ "miktarı yeniden doğrulayın. Stop emirlerinde gap/slippage nedeniyle kayıp "
296
+ "hesaplanandan yüksek olabilir. SHORT işlemlerde ayrıca borçlanma/funding "
297
+ "maliyetleri bulunabilir."
298
+ )
299
+ return report, fig, neighbor_view, notes
300
+
301
+ except Exception as e:
302
+ return f"## Hata\n{type(e).__name__}: {e}", None, pd.DataFrame(), (
303
+ "Veri bağlantısını, parametreleri ve internet erişimini kontrol edin."
304
+ )
305
+
306
+ with gr.Blocks(title=APP_TITLE, theme=gr.themes.Soft()) as demo:
307
+ gr.Markdown(
308
+ "# BTCUSD KNN — 1 Günlük Risk & Pozisyon Motoru\n"
309
+ "KNN benzer geçmiş günleri kullanarak yön sınıfı tahmini üretir; ATR tabanlı "
310
+ "SL/TP ve risk bütçesine göre BTC miktarı hesaplar."
311
+ )
312
+ with gr.Row():
313
+ with gr.Column(scale=1):
314
+ period = gr.Dropdown(
315
+ ["2y", "5y", "max"], value="5y", label="Geçmiş veri aralığı"
316
+ )
317
+ k = gr.Slider(3, 31, value=9, step=2,
318
+ label="K komşu sayısı")
319
+ train_window = gr.Slider(200, 1500, value=750, step=50,
320
+ label="Eğitim penceresi (gün)")
321
+ threshold_atr = gr.Slider(0.0, 1.0, value=0.25, step=0.05,
322
+ label="Sınıf eşiği (ATR oranı)")
323
+ atr_stop = gr.Slider(0.5, 5.0, value=2.0, step=0.1,
324
+ label="Stop mesafesi (ATR)")
325
+ reward_risk = gr.Slider(0.5, 5.0, value=2.0, step=0.1,
326
+ label="Risk / ödül oranı")
327
+ use_latest = gr.Checkbox(
328
+ value=False, label="Son günlük mumu kullan (kapanmamış olabilir)"
329
+ )
330
+ with gr.Column(scale=1):
331
+ equity = gr.Number(value=10000, label="Hesap özsermayesi (USD)")
332
+ risk_pct = gr.Slider(0.1, 3.0, value=0.5, step=0.1,
333
+ label="İşlem başına risk (%)")
334
+ max_leverage = gr.Slider(1, 20, value=3, step=1,
335
+ label="Azami kaldıraç (pozisyon limiti)")
336
+ contract_size = gr.Number(value=1.0, label="1 lot kaç BTC?")
337
+ fee_pct = gr.Number(value=0.05, label="Tek yön komisyon (%)")
338
+ slippage_pct = gr.Number(value=0.03, label="Tek yön kayma varsayımı (%)")
339
+ run = gr.Button("Analiz et", variant="primary")
340
+
341
+ result = gr.Markdown()
342
+ chart = gr.Plot(label="BTCUSD fiyat ve risk seviyeleri")
343
+ neighbors = gr.Dataframe(label="KNN en yakın geçmiş örnekleri", interactive=False)
344
+ disclaimer = gr.Markdown()
345
+ run.click(
346
+ fn=run_engine,
347
+ inputs=[period, k, train_window, threshold_atr, atr_stop, reward_risk,
348
+ equity, risk_pct, max_leverage, contract_size, fee_pct,
349
+ slippage_pct, use_latest],
350
+ outputs=[result, chart, neighbors, disclaimer]
351
+ )
352
+
353
+ if __name__ == "__main__":
354
+ demo.launch(server_name="0.0.0.0", server_port=7860)