GoshawkVortexAI commited on
Commit
c4d9fb5
Β·
verified Β·
1 Parent(s): ad817d4

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +657 -240
app.py CHANGED
@@ -1,3 +1,9 @@
 
 
 
 
 
 
1
  import gradio as gr
2
  import pandas as pd
3
  import numpy as np
@@ -7,373 +13,784 @@ from sklearn.ensemble import RandomForestRegressor, GradientBoostingRegressor, A
7
  from sklearn.linear_model import Ridge
8
  from sklearn.svm import SVR
9
  from sklearn.preprocessing import StandardScaler
 
 
10
  import matplotlib.pyplot as plt
11
  import matplotlib.patches as mpatches
12
  from matplotlib.gridspec import GridSpec
13
  import matplotlib.ticker as mticker
 
 
14
  import warnings
15
  warnings.filterwarnings('ignore')
16
 
17
- # ─── Stil sabitleri ───────────────────────────────────────────────────────────
18
- BG = "#0A0E17"
19
- PANEL = "#111827"
20
- BORDER = "#1E293B"
21
- ACCENT = "#3B82F6"
22
- GREEN = "#10B981"
23
- RED = "#EF4444"
24
- AMBER = "#F59E0B"
25
- MUTED = "#64748B"
26
- TEXT = "#F1F5F9"
27
- SUBTEXT = "#94A3B8"
28
-
29
- MODEL_COLORS = ["#3B82F6", "#8B5CF6", "#10B981", "#F59E0B", "#EC4899"]
30
-
31
- # ─── Veri & feature mΓΌhendisliği ─────────────────────────────────────────────
32
- def get_forex_data(symbol="EURUSD=X", period="90d"):
 
 
 
 
 
 
 
 
 
 
 
 
 
33
  try:
34
- data = yf.download(symbol, period=period, interval="1d", progress=False)
35
- if data is None or len(data) < 40:
36
  return None
37
- return data
 
 
 
38
  except Exception:
39
  return None
40
 
41
- def create_features(data):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
42
  df = data.copy()
43
- df['Returns'] = df['Close'].pct_change()
44
- df['High_Low'] = df['High'] - df['Low']
45
- df['Close_Open'] = df['Close'] - df['Open']
46
 
47
- for w in [5, 10, 20, 50]:
48
- df[f'SMA_{w}'] = df['Close'].rolling(window=w).mean()
 
 
 
 
 
 
49
 
50
- delta = df['Close'].diff()
51
- gain = (delta.where(delta > 0, 0)).rolling(14).mean()
52
- loss = (-delta.where(delta < 0, 0)).rolling(14).mean()
53
- df['RSI'] = 100 - (100 / (1 + gain / loss))
54
 
55
- exp1 = df['Close'].ewm(span=12, adjust=False).mean()
56
- exp2 = df['Close'].ewm(span=26, adjust=False).mean()
57
- df['MACD'] = exp1 - exp2
58
- df['Signal'] = df['MACD'].ewm(span=9, adjust=False).mean()
59
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
60
  df['BB_middle'] = df['Close'].rolling(20).mean()
61
  df['BB_std'] = df['Close'].rolling(20).std()
62
  df['BB_upper'] = df['BB_middle'] + df['BB_std'] * 2
63
  df['BB_lower'] = df['BB_middle'] - df['BB_std'] * 2
64
- df['BB_width'] = (df['BB_upper'] - df['BB_lower']) / df['BB_middle']
 
 
 
 
 
 
 
 
 
 
 
 
 
 
65
 
66
- df['ATR'] = (df['High'] - df['Low']).rolling(14).mean()
 
 
67
 
 
 
 
 
 
 
 
 
 
68
  for i in range(1, 8):
69
  df[f'Lag_{i}'] = df['Close'].shift(i)
70
 
71
  return df.dropna()
72
 
 
 
 
 
73
  FEATURE_COLS = [
74
- 'Returns','High_Low','Close_Open',
75
- 'SMA_5','SMA_10','SMA_20','SMA_50',
76
- 'RSI','MACD','Signal',
77
- 'BB_middle','BB_upper','BB_lower','BB_width',
78
- 'ATR',
79
- 'Lag_1','Lag_2','Lag_3','Lag_4','Lag_5','Lag_6','Lag_7'
 
 
 
 
 
 
 
 
 
 
 
 
80
  ]
81
 
82
- # ─── Model katmanΔ± ────────────────────────────────────────────────────────────
83
- MODELS_DEF = {
84
- 'Random Forest': RandomForestRegressor(n_estimators=200, random_state=42, n_jobs=-1),
85
- 'Gradient Boosting': GradientBoostingRegressor(n_estimators=200, learning_rate=0.05, random_state=42),
86
- 'AdaBoost': AdaBoostRegressor(n_estimators=150, random_state=42),
87
- 'Ridge': Ridge(alpha=0.5),
88
- 'SVR': SVR(kernel='rbf', C=200, gamma=0.05, epsilon=0.0001),
89
- }
90
-
91
- def train_and_evaluate(df):
92
- X = df[FEATURE_COLS].values
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
93
  y = df['Close'].values
94
- split = -10
95
 
 
96
  X_train, X_test = X[:split], X[split:]
97
  y_train, y_test = y[:split], y[split:]
98
 
99
- scaler = StandardScaler()
100
- Xtr = scaler.fit_transform(X_train)
101
- Xte = scaler.transform(X_test)
 
 
 
 
102
 
103
- trained, test_preds = {}, {}
104
- for name, m in MODELS_DEF.items():
105
- m.fit(Xtr, y_train)
106
- test_preds[name] = m.predict(Xte)
107
  trained[name] = m
108
 
109
- return trained, scaler, test_preds, y_test
110
 
111
- def predict_next(trained, scaler, df):
112
- x = df[FEATURE_COLS].iloc[-1].values.reshape(1, -1)
 
 
113
  xs = scaler.transform(x)
114
- preds = {n: float(m.predict(xs)[0]) for n, m in trained.items()}
115
  ensemble = float(np.mean(list(preds.values())))
116
  return preds, ensemble
117
 
118
- # ─── Grafik ───────────────────────────────────────────────────────────────────
119
- def build_figure(df, next_preds, ensemble_pred, test_preds, y_test):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
120
  plt.rcParams.update({
121
- 'font.family': 'monospace',
122
- 'text.color': TEXT,
123
- 'axes.labelcolor': SUBTEXT,
124
- 'xtick.color': SUBTEXT,
125
- 'ytick.color': SUBTEXT,
126
  'figure.facecolor': BG,
127
- 'axes.facecolor': PANEL,
128
- 'axes.edgecolor': BORDER,
129
- 'grid.color': BORDER,
130
- 'grid.linewidth': 0.5,
131
  })
132
 
133
- fig = plt.figure(figsize=(14, 12))
134
- gs = GridSpec(3, 2, figure=fig, hspace=0.45, wspace=0.35)
 
135
 
136
- ax1 = fig.add_subplot(gs[0, :]) # Fiyat + BB
137
- ax2 = fig.add_subplot(gs[1, 0]) # RSI
138
- ax3 = fig.add_subplot(gs[1, 1]) # MACD
139
- ax4 = fig.add_subplot(gs[2, :]) # Model tahminleri + hata
 
 
140
 
141
- tail = df.tail(45)
142
  idx = np.arange(len(tail))
143
  cp = float(df['Close'].iloc[-1])
144
 
145
- # ── Panel 1: Fiyat ──────────────────────────────────────────────
146
- ax1.fill_between(idx, tail['BB_lower'].values, tail['BB_upper'].values,
147
- alpha=0.08, color=ACCENT, label='Bollinger BandΔ±')
148
- ax1.plot(idx, tail['BB_upper'].values, lw=0.5, color=ACCENT, alpha=0.4)
149
- ax1.plot(idx, tail['BB_lower'].values, lw=0.5, color=ACCENT, alpha=0.4)
150
- ax1.plot(idx, tail['Close'].values, lw=1.8, color=TEXT, label='Kapanış')
151
- ax1.plot(idx, tail['SMA_20'].values, lw=1.1, color=AMBER, alpha=0.8, label='SMA 20', ls='--')
152
- ax1.plot(idx, tail['SMA_50'].values, lw=1.1, color=MUTED, alpha=0.8, label='SMA 50', ls=':')
153
-
154
- # Ensemble tahmini noktasΔ±
155
- arrow_x = len(idx) + 0.5
156
- ax1.annotate(
157
- f'β–Ά {ensemble_pred:.5f}',
158
- xy=(len(idx) - 1, cp),
159
- xytext=(len(idx) + 1, ensemble_pred),
160
- arrowprops=dict(arrowstyle='->', color=GREEN, lw=1.5),
161
- color=GREEN, fontsize=9, fontweight='bold',
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
162
  )
163
- ax1.axhline(y=cp, color=MUTED, lw=0.8, ls='--', alpha=0.5)
164
-
165
- ax1.set_title('EUR/USD Β· Son 45 GΓΌn + Sonraki GΓΌn Tahmini',
166
- fontsize=11, color=TEXT, pad=10, loc='left', fontweight='bold')
167
- ax1.legend(fontsize=7, framealpha=0.15, labelcolor=SUBTEXT, ncol=4)
168
- ax1.set_ylabel('Fiyat', fontsize=8)
169
- ax1.yaxis.set_major_formatter(mticker.FormatStrFormatter('%.4f'))
170
-
171
- # ── Panel 2: RSI ─────────────────────────────────────────────────
172
- rsi_tail = tail['RSI'].values
173
- ax2.plot(idx, rsi_tail, lw=1.4, color=ACCENT)
174
- ax2.fill_between(idx, rsi_tail, 50, where=(rsi_tail >= 50), alpha=0.15, color=GREEN)
175
- ax2.fill_between(idx, rsi_tail, 50, where=(rsi_tail < 50), alpha=0.15, color=RED)
176
- ax2.axhline(70, color=RED, lw=0.8, ls='--', alpha=0.6)
177
- ax2.axhline(30, color=GREEN, lw=0.8, ls='--', alpha=0.6)
178
- ax2.set_ylim(0, 100)
179
- ax2.set_title('RSI (14)', fontsize=9, color=TEXT, pad=6, loc='left')
180
- ax2.set_ylabel('RSI', fontsize=8)
181
- for level, lbl in [(70, 'OB'), (30, 'OS')]:
182
- ax2.text(len(idx), level, lbl, color=SUBTEXT, fontsize=7, va='center')
183
-
184
- # ── Panel 3: MACD ────────────────────────────────────────────────
185
- macd = tail['MACD'].values
186
- signal = tail['Signal'].values
187
- hist = macd - signal
188
- colors_hist = [GREEN if v >= 0 else RED for v in hist]
189
- ax3.bar(idx, hist, color=colors_hist, alpha=0.5, width=0.8, label='Histogram')
190
- ax3.plot(idx, macd, lw=1.3, color=ACCENT, label='MACD')
191
- ax3.plot(idx, signal, lw=1.1, color=AMBER, label='Signal', ls='--')
192
- ax3.axhline(0, color=MUTED, lw=0.7)
193
- ax3.set_title('MACD (12/26/9)', fontsize=9, color=TEXT, pad=6, loc='left')
194
- ax3.legend(fontsize=7, framealpha=0.1, labelcolor=SUBTEXT)
195
-
196
- # ── Panel 4: Model karşılaştΔ±rmasΔ± ───────────────────────────────
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
197
  names = list(next_preds.keys())
198
  values = [next_preds[n] for n in names]
199
  maes = [float(np.mean(np.abs(test_preds[n] - y_test))) for n in names]
 
200
 
201
- x_pos = np.arange(len(names))
202
- bar_colors = [GREEN if v > cp else RED for v in values]
 
203
 
204
- bars = ax4.bar(x_pos, values, color=bar_colors, alpha=0.75, width=0.55, zorder=3)
205
- ax4.axhline(y=cp, color=TEXT, lw=1.2, ls='--', alpha=0.7, label=f'Mevcut {cp:.5f}', zorder=4)
206
- ax4.axhline(y=ensemble_pred, color=ACCENT, lw=1.5, ls='-', alpha=0.9,
207
- label=f'Ensemble {ensemble_pred:.5f}', zorder=4)
 
 
 
 
208
 
209
  for bar, val, mae in zip(bars, values, maes):
210
- pct = (val - cp) / cp * 100
211
- sign = '+' if pct >= 0 else ''
212
- ax4.text(bar.get_x() + bar.get_width()/2, val + 0.00003,
213
- f'{sign}{pct:.3f}%\nMAE {mae:.5f}',
214
- ha='center', va='bottom', fontsize=7.5, color=TEXT, fontweight='bold')
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
215
 
216
- ax4.set_xticks(x_pos)
217
- ax4.set_xticklabels(names, fontsize=9)
218
- ax4.set_title('5 Model Tahmini vs Mevcut Fiyat', fontsize=9, color=TEXT, pad=6, loc='left')
219
- ax4.legend(fontsize=8, framealpha=0.15, labelcolor=SUBTEXT)
220
- ax4.yaxis.set_major_formatter(mticker.FormatStrFormatter('%.4f'))
221
 
222
- fig.suptitle('EUR/USD Β· ENSEMBLE TAHMΔ°N SΔ°STEMΔ°',
223
- fontsize=13, color=TEXT, fontweight='bold', y=0.98)
224
 
225
- return fig
 
 
226
 
227
- # ─── Ana tahmin fonksiyonu ───────────────────────────────────────────────────
228
  def predict_eurusd():
229
- import traceback
230
  try:
 
231
  data = get_forex_data()
232
  if data is None:
233
- return "❌ Veri indirilemedi.", None
234
-
235
- df = create_features(data)
236
- trained, scaler, test_preds, y_test = train_and_evaluate(df)
237
- next_preds, ensemble = predict_next(trained, scaler, df)
238
-
239
- cp = float(df['Close'].iloc[-1])
240
- ens_chg = (ensemble - cp) / cp * 100
241
- direction = "YUKARI β–²" if ens_chg > 0 else "AŞAĞI β–Ό"
242
- dir_color = "πŸ“ˆ" if ens_chg > 0 else "πŸ“‰"
243
-
244
- rsi_val = float(df['RSI'].iloc[-1])
245
- macd_val = float(df['MACD'].iloc[-1])
246
- sig_val = float(df['Signal'].iloc[-1])
247
- rsi_status = "Aşırı Alım" if rsi_val > 70 else ("Aşırı Satım" if rsi_val < 30 else "Nâtr")
248
- macd_cross = "Boğa" if macd_val > sig_val else "Ayı"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
249
 
250
  lines = [
251
- f"━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━",
252
- f" EUR/USD Β· ENSEMBLE TAHMΔ°N RAPORU",
253
  f" {datetime.now().strftime('%d %b %Y %H:%M:%S')}",
254
- f"━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━",
255
- f"",
256
- f" MEVCUT FΔ°YAT : {cp:.5f}",
257
- f" ENSEMBLE : {ensemble:.5f} ({ens_chg:+.3f}%) {dir_color} {direction}",
258
- f"",
259
- f" ─── TEKNΔ°K DURUM ───────────────────────────",
260
- f" RSI (14) : {rsi_val:.1f} β†’ {rsi_status}",
261
- f" MACD Sinyali : {macd_cross} kesişimi",
262
- f" BB Pozisyonu : {float(df['BB_lower'].iloc[-1]):.5f} ↔ {float(df['BB_upper'].iloc[-1]):.5f}",
263
- f"",
264
- f" ─── MODEL TAHMΔ°NLERΔ° ───────────────────────",
 
 
 
 
 
265
  ]
266
 
267
- for name, val in next_preds.items():
268
- chg = (val - cp) / cp * 100
269
- mae = float(np.mean(np.abs(test_preds[name] - y_test)))
270
- arrow = "β–²" if chg > 0 else "β–Ό"
271
- lines.append(f" {name:<20s}: {val:.5f} ({chg:+.3f}%) {arrow} MAE={mae:.5f}")
272
 
273
  lines += [
274
- f"",
275
- f" ─── BACKTEST (Son 10 GΓΌn) ──────────────────",
 
 
 
 
 
 
 
 
 
 
276
  ]
277
 
278
- ens_test = np.mean([test_preds[n] for n in test_preds], axis=0)
279
- ens_mae = float(np.mean(np.abs(ens_test - y_test)))
280
- ens_rmse = float(np.sqrt(np.mean((ens_test - y_test) ** 2)))
281
- hit_rate = float(np.mean(np.sign(np.diff(np.append([y_test[0]], ens_test))) ==
282
- np.sign(np.diff(y_test)))) * 100
 
 
 
 
283
 
284
  lines += [
285
- f" Ensemble MAE : {ens_mae:.6f}",
286
- f" Ensemble RMSE : {ens_rmse:.6f}",
287
- f" Yân Doğruluğu : {hit_rate:.1f}%",
288
- f"",
289
- f"━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━",
 
 
290
  f" ⚠ YalnΔ±zca eğitim amaΓ§lΔ±dΔ±r. Finansal tavsiye değildir.",
291
- f"━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━",
292
  ]
293
 
294
  output_text = "\n".join(lines)
295
- fig = build_figure(df, next_preds, ensemble, test_preds, y_test)
 
 
296
 
297
  return output_text, fig
298
 
299
- except Exception as e:
300
  return f"❌ Hata:\n{traceback.format_exc()}", None
301
 
302
- # ─── CSS ─────────────────────────────────────────────────────────────────────
 
 
 
 
303
  CSS = """
 
304
  body, .gradio-container {
305
  background: #0A0E17 !important;
306
- font-family: 'JetBrains Mono', 'Fira Code', monospace !important;
307
  color: #F1F5F9 !important;
308
  }
309
- .gr-button-primary {
310
- background: linear-gradient(135deg, #1D4ED8, #3B82F6) !important;
311
  border: none !important;
312
  color: #fff !important;
313
  font-weight: 700 !important;
314
- letter-spacing: 0.05em !important;
315
- padding: 12px 28px !important;
316
  border-radius: 6px !important;
317
- transition: opacity .2s !important;
 
318
  }
319
- .gr-button-primary:hover { opacity: 0.85 !important; }
320
- .gr-textbox textarea {
321
- background: #111827 !important;
322
  color: #94A3B8 !important;
323
  border: 1px solid #1E293B !important;
324
  font-family: 'JetBrains Mono', monospace !important;
325
- font-size: 12.5px !important;
326
- line-height: 1.7 !important;
327
- }
328
- .gr-box, .gr-panel {
329
- background: #111827 !important;
330
- border: 1px solid #1E293B !important;
331
  }
 
 
332
  footer { display: none !important; }
333
  """
334
 
335
- HEADER_HTML = """
336
  <div style="
337
- background: linear-gradient(135deg, #0A0E17 0%, #111827 100%);
338
  border: 1px solid #1E293B;
339
  border-radius: 10px;
340
- padding: 28px 32px 20px;
341
- margin-bottom: 8px;
342
  ">
343
- <div style="display:flex; align-items:center; gap:14px; margin-bottom:10px;">
344
- <span style="font-size:2rem;">πŸ’Ή</span>
345
  <div>
346
- <h1 style="margin:0; font-size:1.5rem; font-weight:800; color:#F1F5F9; letter-spacing:.04em;">
347
- EUR/USD Β· ENSEMBLE FORECAST ENGINE
348
  </h1>
349
- <p style="margin:4px 0 0; color:#64748B; font-size:.82rem; font-family:monospace;">
350
- Random Forest Β· Gradient Boosting Β· AdaBoost Β· Ridge Β· SVR
 
351
  </p>
352
  </div>
353
  </div>
354
- <div style="display:flex; gap:20px; flex-wrap:wrap; margin-top:14px;">
355
- <span style="background:#1E293B; border-radius:4px; padding:4px 12px; font-size:.78rem; color:#94A3B8;">πŸ“‘ Yahoo Finance β€” 90g</span>
356
- <span style="background:#1E293B; border-radius:4px; padding:4px 12px; font-size:.78rem; color:#94A3B8;">πŸ“ 22 Teknik GΓΆsterge</span>
357
- <span style="background:#1E293B; border-radius:4px; padding:4px 12px; font-size:.78rem; color:#94A3B8;">πŸ§ͺ 10-GΓΌn Backtest</span>
358
- <span style="background:#1E293B; border-radius:4px; padding:4px 12px; font-size:.78rem; color:#94A3B8;">⚑ Tek Tıkla Güncelle</span>
 
 
359
  </div>
360
  </div>
361
  """
362
 
363
- # ─── Gradio arayΓΌzΓΌ ──────────────────────────────────────────────────────────
364
- with gr.Blocks(css=CSS, title="EUR/USD Ensemble Forecast") as app:
365
- gr.HTML(HEADER_HTML)
 
 
 
366
 
367
  with gr.Row():
368
  predict_btn = gr.Button("⟳ TAHMΔ°N YAP / YENΔ°LE", variant="primary", scale=1)
369
 
370
  with gr.Row():
371
- with gr.Column(scale=1):
372
  output_text = gr.Textbox(
373
  label="RAPOR",
374
- lines=30,
375
  interactive=False,
376
- show_copy_button=True,
377
  )
378
  with gr.Column(scale=2):
379
  output_plot = gr.Plot(label="GRAFΔ°KLER")
 
1
+ # ══════════════════════════════════════════════════════════════════════════════
2
+ # EUR/USD Β· ENSEMBLE FORECAST ENGINE Β· Premium Edition
3
+ # 5 Model: Random Forest Β· Gradient Boosting Β· AdaBoost Β· Ridge Β· SVR
4
+ # TP/SL hesaplama Β· ATR tabanlΔ± risk yΓΆnetimi Β· Backtest analizi
5
+ # ══════════════════════════════════════════════════════════════════════════════
6
+
7
  import gradio as gr
8
  import pandas as pd
9
  import numpy as np
 
13
  from sklearn.linear_model import Ridge
14
  from sklearn.svm import SVR
15
  from sklearn.preprocessing import StandardScaler
16
+ import matplotlib
17
+ matplotlib.use('Agg')
18
  import matplotlib.pyplot as plt
19
  import matplotlib.patches as mpatches
20
  from matplotlib.gridspec import GridSpec
21
  import matplotlib.ticker as mticker
22
+ from matplotlib.lines import Line2D
23
+ import traceback
24
  import warnings
25
  warnings.filterwarnings('ignore')
26
 
27
+ # ══════════════════════════════════════════════════════════════════════════════
28
+ # RENK PALETΔ°
29
+ # ══════════════════════════════════════════════════════════════════════════════
30
+ BG = "#0A0E17"
31
+ PANEL = "#0F1623"
32
+ PANEL2 = "#111827"
33
+ BORDER = "#1E293B"
34
+ ACCENT = "#3B82F6"
35
+ ACCENT2 = "#6366F1"
36
+ GREEN = "#10B981"
37
+ RED = "#EF4444"
38
+ AMBER = "#F59E0B"
39
+ PURPLE = "#8B5CF6"
40
+ PINK = "#EC4899"
41
+ MUTED = "#64748B"
42
+ TEXT = "#F1F5F9"
43
+ SUBTEXT = "#94A3B8"
44
+
45
+ MODEL_COLORS = [ACCENT, PURPLE, GREEN, AMBER, PINK]
46
+
47
+ # ══════════════════════════════════════════════════════════════════════════════
48
+ # VERΔ° KATMANI
49
+ # ══════════════════════════════════════════════════════════════════════════════
50
+
51
+ def get_forex_data(symbol: str = "EURUSD=X", period: str = "120d") -> pd.DataFrame | None:
52
+ """
53
+ Yahoo Finance'ten gΓΌnlΓΌk OHLCV verisi indirir.
54
+ 120 gΓΌnlΓΌk pencere β€” SMA-50 ve lag-7 iΓ§in yeterli.
55
+ """
56
  try:
57
+ raw = yf.download(symbol, period=period, interval="1d", progress=False, auto_adjust=True)
58
+ if raw is None or len(raw) < 60:
59
  return None
60
+ # MultiIndex sΓΌtunlarΔ±nΔ± dΓΌzelt
61
+ if isinstance(raw.columns, pd.MultiIndex):
62
+ raw.columns = raw.columns.get_level_values(0)
63
+ return raw
64
  except Exception:
65
  return None
66
 
67
+
68
+ def compute_atr(df: pd.DataFrame, period: int = 14) -> pd.Series:
69
+ """True Range tabanlΔ± ATR hesabΔ±."""
70
+ high = df['High']
71
+ low = df['Low']
72
+ prev_close = df['Close'].shift(1)
73
+ tr = pd.concat([
74
+ high - low,
75
+ (high - prev_close).abs(),
76
+ (low - prev_close).abs(),
77
+ ], axis=1).max(axis=1)
78
+ return tr.rolling(period).mean()
79
+
80
+
81
+ def compute_stoch(df: pd.DataFrame, k: int = 14, d: int = 3):
82
+ """Stochastic Oscillator %K ve %D."""
83
+ low_min = df['Low'].rolling(k).min()
84
+ high_max = df['High'].rolling(k).max()
85
+ pct_k = 100 * (df['Close'] - low_min) / (high_max - low_min + 1e-10)
86
+ pct_d = pct_k.rolling(d).mean()
87
+ return pct_k, pct_d
88
+
89
+
90
+ def compute_cci(df: pd.DataFrame, period: int = 20) -> pd.Series:
91
+ """Commodity Channel Index."""
92
+ tp = (df['High'] + df['Low'] + df['Close']) / 3
93
+ sma = tp.rolling(period).mean()
94
+ mad = tp.rolling(period).apply(lambda x: np.mean(np.abs(x - x.mean())), raw=True)
95
+ return (tp - sma) / (0.015 * mad + 1e-10)
96
+
97
+
98
+ def compute_williams_r(df: pd.DataFrame, period: int = 14) -> pd.Series:
99
+ """Williams %R."""
100
+ high_max = df['High'].rolling(period).max()
101
+ low_min = df['Low'].rolling(period).min()
102
+ return -100 * (high_max - df['Close']) / (high_max - low_min + 1e-10)
103
+
104
+
105
+ def create_features(data: pd.DataFrame) -> pd.DataFrame:
106
+ """
107
+ 22 teknik gΓΆsterge + 7 lag feature ΓΌretir.
108
+ Toplam: 29 input feature.
109
+ """
110
  df = data.copy()
 
 
 
111
 
112
+ # ── Temel getiri & mum ΓΆzellikleri ───────────────────────────────────────
113
+ df['Returns'] = df['Close'].pct_change()
114
+ df['Log_Returns'] = np.log(df['Close'] / df['Close'].shift(1))
115
+ df['High_Low'] = df['High'] - df['Low']
116
+ df['Close_Open'] = df['Close'] - df['Open']
117
+ df['Body_Ratio'] = df['Close_Open'].abs() / (df['High_Low'] + 1e-10)
118
+ df['Upper_Shadow'] = df['High'] - df[['Close', 'Open']].max(axis=1)
119
+ df['Lower_Shadow'] = df[['Close', 'Open']].min(axis=1) - df['Low']
120
 
121
+ # ── Hareketli ortalamalar ─────────────────────────────────────────────────
122
+ for w in [5, 10, 20, 50]:
123
+ df[f'SMA_{w}'] = df['Close'].rolling(w).mean()
124
+ df[f'EMA_{w}'] = df['Close'].ewm(span=w, adjust=False).mean()
125
 
126
+ # SMA mesafe oranlarΔ±
127
+ df['Price_SMA5_Ratio'] = df['Close'] / df['SMA_5']
128
+ df['Price_SMA20_Ratio'] = df['Close'] / df['SMA_20']
129
+ df['SMA5_SMA20_Cross'] = df['SMA_5'] - df['SMA_20']
130
 
131
+ # ── RSI ───────────────────────────────────────────────────────────────────
132
+ delta = df['Close'].diff()
133
+ gain = delta.where(delta > 0, 0.0).rolling(14).mean()
134
+ loss = (-delta.where(delta < 0, 0.0)).rolling(14).mean()
135
+ rs = gain / (loss + 1e-10)
136
+ df['RSI'] = 100 - (100 / (1 + rs))
137
+
138
+ # ── MACD ──────────────────────────────────────────────────────────────────
139
+ ema12 = df['Close'].ewm(span=12, adjust=False).mean()
140
+ ema26 = df['Close'].ewm(span=26, adjust=False).mean()
141
+ df['MACD'] = ema12 - ema26
142
+ df['MACD_Signal'] = df['MACD'].ewm(span=9, adjust=False).mean()
143
+ df['MACD_Hist'] = df['MACD'] - df['MACD_Signal']
144
+
145
+ # ── Bollinger BantlarΔ± ────────────────────────────────────────────────────
146
  df['BB_middle'] = df['Close'].rolling(20).mean()
147
  df['BB_std'] = df['Close'].rolling(20).std()
148
  df['BB_upper'] = df['BB_middle'] + df['BB_std'] * 2
149
  df['BB_lower'] = df['BB_middle'] - df['BB_std'] * 2
150
+ df['BB_width'] = (df['BB_upper'] - df['BB_lower']) / (df['BB_middle'] + 1e-10)
151
+ df['BB_pct'] = (df['Close'] - df['BB_lower']) / (df['BB_upper'] - df['BB_lower'] + 1e-10)
152
+
153
+ # ── ATR ───────────────────────────────────────────────────────────────────
154
+ df['ATR'] = compute_atr(df, 14)
155
+ df['ATR_Pct'] = df['ATR'] / df['Close']
156
+
157
+ # ── Stochastic ────────────────────────────────────────────────────────────
158
+ df['Stoch_K'], df['Stoch_D'] = compute_stoch(df)
159
+
160
+ # ── CCI ───────────────────────────────────────────────────────────────────
161
+ df['CCI'] = compute_cci(df, 20)
162
+
163
+ # ── Williams %R ───────────────────────────────────────────────────────────
164
+ df['WilliamsR'] = compute_williams_r(df, 14)
165
 
166
+ # ── Volatilite ────────────────────────────────────────────────────────────
167
+ df['Volatility_5'] = df['Returns'].rolling(5).std()
168
+ df['Volatility_20'] = df['Returns'].rolling(20).std()
169
 
170
+ # ── Volume (varsa) ────────────────────────────────────────────────────────
171
+ if 'Volume' in df.columns:
172
+ df['Volume_MA'] = df['Volume'].rolling(10).mean()
173
+ df['Volume_Ratio'] = df['Volume'] / (df['Volume_MA'] + 1e-10)
174
+ else:
175
+ df['Volume_MA'] = 0.0
176
+ df['Volume_Ratio'] = 1.0
177
+
178
+ # ── Lag features ──────────────────────────────────────────────────────────
179
  for i in range(1, 8):
180
  df[f'Lag_{i}'] = df['Close'].shift(i)
181
 
182
  return df.dropna()
183
 
184
+
185
+ # ══════════════════════════════════════════════════════════════════════════════
186
+ # FEATURE KOLONLARI
187
+ # ══════════════════════════════════════════════════════════════════════════════
188
  FEATURE_COLS = [
189
+ # Temel
190
+ 'Returns', 'Log_Returns', 'High_Low', 'Close_Open',
191
+ 'Body_Ratio', 'Upper_Shadow', 'Lower_Shadow',
192
+ # SMA / EMA
193
+ 'SMA_5', 'SMA_10', 'SMA_20', 'SMA_50',
194
+ 'EMA_5', 'EMA_10', 'EMA_20',
195
+ 'Price_SMA5_Ratio', 'Price_SMA20_Ratio', 'SMA5_SMA20_Cross',
196
+ # Momentum
197
+ 'RSI', 'MACD', 'MACD_Signal', 'MACD_Hist',
198
+ 'Stoch_K', 'Stoch_D', 'CCI', 'WilliamsR',
199
+ # Volatilite / BB
200
+ 'BB_middle', 'BB_upper', 'BB_lower', 'BB_width', 'BB_pct',
201
+ 'ATR', 'ATR_Pct',
202
+ 'Volatility_5', 'Volatility_20',
203
+ # Volume
204
+ 'Volume_Ratio',
205
+ # Lag
206
+ 'Lag_1', 'Lag_2', 'Lag_3', 'Lag_4', 'Lag_5', 'Lag_6', 'Lag_7',
207
  ]
208
 
209
+ # ══════════════════════════════════════════════════════════════════════════════
210
+ # MODEL KATMANI
211
+ # ══════════════════════════════════════════════════════════════════════════════
212
+
213
+ def build_models() -> dict:
214
+ """Her çağrıda taze model ârnekleri dândürür (paralel eğitim güvenliği)."""
215
+ return {
216
+ 'Random Forest': RandomForestRegressor(
217
+ n_estimators=300, max_depth=12,
218
+ min_samples_leaf=3, random_state=42, n_jobs=-1),
219
+ 'Gradient Boosting': GradientBoostingRegressor(
220
+ n_estimators=300, learning_rate=0.04,
221
+ max_depth=5, subsample=0.8, random_state=42),
222
+ 'AdaBoost': AdaBoostRegressor(
223
+ n_estimators=200, learning_rate=0.08, random_state=42),
224
+ 'Ridge': Ridge(alpha=0.3),
225
+ 'SVR': SVR(kernel='rbf', C=500, gamma='scale', epsilon=0.00005),
226
+ }
227
+
228
+
229
+ def train_and_evaluate(df: pd.DataFrame):
230
+ """
231
+ Modelleri eğitir; son 10 günü test seti olarak kullanır.
232
+ Dânüş: trained_models, scaler, test_preds (dict), y_test
233
+ """
234
+ avail = [c for c in FEATURE_COLS if c in df.columns]
235
+ X = df[avail].values
236
  y = df['Close'].values
 
237
 
238
+ split = -10
239
  X_train, X_test = X[:split], X[split:]
240
  y_train, y_test = y[:split], y[split:]
241
 
242
+ scaler = StandardScaler()
243
+ X_tr_sc = scaler.fit_transform(X_train)
244
+ X_te_sc = scaler.transform(X_test)
245
+
246
+ models = build_models()
247
+ trained = {}
248
+ test_preds = {}
249
 
250
+ for name, m in models.items():
251
+ m.fit(X_tr_sc, y_train)
252
+ test_preds[name] = m.predict(X_te_sc)
 
253
  trained[name] = m
254
 
255
+ return trained, scaler, test_preds, y_test, avail
256
 
257
+
258
+ def predict_next(trained: dict, scaler, df: pd.DataFrame, avail: list):
259
+ """Son satΔ±rΔ±n feature'larΔ±ndan ertesi gΓΌn tahmini ΓΌretir."""
260
+ x = df[avail].iloc[-1].values.reshape(1, -1)
261
  xs = scaler.transform(x)
262
+ preds = {n: float(m.predict(xs)[0]) for n, m in trained.items()}
263
  ensemble = float(np.mean(list(preds.values())))
264
  return preds, ensemble
265
 
266
+
267
+ # ══════════════════════════════════════════════════════════════════════════════
268
+ # TP / SL HESAPLAMA (ATR tabanlΔ± risk yΓΆnetimi)
269
+ # ══════════════════════════════════════════════════════════════════════════════
270
+
271
+ def compute_tp_sl(
272
+ current_price: float,
273
+ ensemble_pred: float,
274
+ atr: float,
275
+ atr_multiplier_sl: float = 1.5,
276
+ rr_ratio: float = 2.0,
277
+ ) -> dict:
278
+ """
279
+ ATR tabanlΔ± TP / SL hesaplar.
280
+
281
+ YΓΆn: ensemble > current β†’ LONG | ensemble < current β†’ SHORT
282
+
283
+ SL = current Β± (ATR Γ— atr_multiplier_sl)
284
+ TP = current Β± (|SL mesafesi| Γ— rr_ratio)
285
+
286
+ Dânüş: direction, entry, sl, tp, sl_pips, tp_pips, rr, risk_pct
287
+ """
288
+ direction = "LONG" if ensemble_pred > current_price else "SHORT"
289
+ sl_dist = atr * atr_multiplier_sl
290
+ tp_dist = sl_dist * rr_ratio
291
+
292
+ if direction == "LONG":
293
+ sl = current_price - sl_dist
294
+ tp = current_price + tp_dist
295
+ else:
296
+ sl = current_price + sl_dist
297
+ tp = current_price - tp_dist
298
+
299
+ pip_size = 0.0001 # EUR/USD standart pip
300
+ sl_pips = sl_dist / pip_size
301
+ tp_pips = tp_dist / pip_size
302
+ risk_pct = (sl_dist / current_price) * 100
303
+
304
+ return {
305
+ 'direction' : direction,
306
+ 'entry' : current_price,
307
+ 'sl' : sl,
308
+ 'tp' : tp,
309
+ 'sl_pips' : sl_pips,
310
+ 'tp_pips' : tp_pips,
311
+ 'rr' : rr_ratio,
312
+ 'risk_pct' : risk_pct,
313
+ 'atr' : atr,
314
+ }
315
+
316
+
317
+ def compute_signal_score(df: pd.DataFrame, direction: str) -> dict:
318
+ """
319
+ RSI Β· MACD Β· Stoch Β· CCI Β· BB pozisyonuna bakarak
320
+ sinyal gΓΌcΓΌnΓΌ 0–100 arasΔ±nda puanlar.
321
+ """
322
+ row = df.iloc[-1]
323
+ score = 0
324
+ details = {}
325
+
326
+ rsi = float(row['RSI'])
327
+ if direction == "LONG":
328
+ if rsi < 70:
329
+ pts = max(0, (70 - rsi) / 70 * 25)
330
+ score += pts
331
+ details['RSI'] = f"{rsi:.1f} β€” boğa bΓΆlgesi (+{pts:.0f}p)"
332
+ else:
333
+ details['RSI'] = f"{rsi:.1f} β€” aşırΔ± alΔ±m (0p)"
334
+ else:
335
+ if rsi > 30:
336
+ pts = max(0, (rsi - 30) / 70 * 25)
337
+ score += pts
338
+ details['RSI'] = f"{rsi:.1f} β€” ayΔ± bΓΆlgesi (+{pts:.0f}p)"
339
+ else:
340
+ details['RSI'] = f"{rsi:.1f} β€” aşırΔ± satΔ±m (0p)"
341
+
342
+ macd = float(row['MACD'])
343
+ mhist = float(row['MACD_Hist'])
344
+ if direction == "LONG":
345
+ pts = 20 if (macd > 0 and mhist > 0) else (10 if macd > 0 else 0)
346
+ else:
347
+ pts = 20 if (macd < 0 and mhist < 0) else (10 if macd < 0 else 0)
348
+ score += pts
349
+ details['MACD'] = f"macd={macd:.5f} hist={mhist:.5f} (+{pts}p)"
350
+
351
+ stk = float(row['Stoch_K'])
352
+ std = float(row['Stoch_D'])
353
+ if direction == "LONG":
354
+ pts = 20 if (stk < 80 and stk > std) else (10 if stk < 80 else 0)
355
+ else:
356
+ pts = 20 if (stk > 20 and stk < std) else (10 if stk > 20 else 0)
357
+ score += pts
358
+ details['Stoch'] = f"K={stk:.1f} D={std:.1f} (+{pts}p)"
359
+
360
+ cci = float(row['CCI'])
361
+ if direction == "LONG":
362
+ pts = 20 if cci > -100 else 5
363
+ else:
364
+ pts = 20 if cci < 100 else 5
365
+ score += pts
366
+ details['CCI'] = f"{cci:.1f} (+{pts}p)"
367
+
368
+ bb_pct = float(row['BB_pct'])
369
+ if direction == "LONG":
370
+ pts = 15 if bb_pct < 0.5 else 5
371
+ else:
372
+ pts = 15 if bb_pct > 0.5 else 5
373
+ score += pts
374
+ details['BB%'] = f"{bb_pct:.2f} (+{pts}p)"
375
+
376
+ label = ("GΓœΓ‡LÜ βœ…" if score >= 75
377
+ else "ORTA ⚠" if score >= 45
378
+ else "ZAYIF ❌")
379
+
380
+ return {'score': min(100, score), 'label': label, 'details': details}
381
+
382
+
383
+ # ══════════════════════════════════════════════════════════════════════════════
384
+ # GRAFΔ°K MOTORU
385
+ # ══════════════════════════════════════════════════════════════════════════════
386
+
387
+ def _set_ax_style(ax, title: str = "", ylabel: str = ""):
388
+ ax.set_facecolor(PANEL)
389
+ ax.tick_params(colors=SUBTEXT, labelsize=7)
390
+ for spine in ax.spines.values():
391
+ spine.set_edgecolor(BORDER)
392
+ ax.grid(True, color=BORDER, linewidth=0.4, alpha=0.6)
393
+ if title:
394
+ ax.set_title(title, fontsize=8.5, color=TEXT, pad=7, loc='left', fontweight='bold')
395
+ if ylabel:
396
+ ax.set_ylabel(ylabel, fontsize=7, color=SUBTEXT)
397
+
398
+
399
+ def build_figure(
400
+ df: pd.DataFrame,
401
+ next_preds: dict,
402
+ ensemble_pred: float,
403
+ test_preds: dict,
404
+ y_test: np.ndarray,
405
+ tp_sl: dict,
406
+ ) -> plt.Figure:
407
+
408
  plt.rcParams.update({
409
+ 'font.family' : 'monospace',
410
+ 'text.color' : TEXT,
 
 
 
411
  'figure.facecolor': BG,
412
+ 'axes.facecolor' : PANEL,
413
+ 'axes.edgecolor' : BORDER,
414
+ 'grid.color' : BORDER,
415
+ 'grid.linewidth' : 0.4,
416
  })
417
 
418
+ fig = plt.figure(figsize=(16, 15))
419
+ gs = GridSpec(4, 2, figure=fig, hspace=0.52, wspace=0.32,
420
+ left=0.06, right=0.97, top=0.94, bottom=0.04)
421
 
422
+ ax_price = fig.add_subplot(gs[0, :]) # Fiyat + BB + TP/SL
423
+ ax_rsi = fig.add_subplot(gs[1, 0]) # RSI
424
+ ax_macd = fig.add_subplot(gs[1, 1]) # MACD
425
+ ax_stoch = fig.add_subplot(gs[2, 0]) # Stochastic
426
+ ax_cci = fig.add_subplot(gs[2, 1]) # CCI
427
+ ax_models = fig.add_subplot(gs[3, :]) # Model karşılaştırması
428
 
429
+ tail = df.tail(50)
430
  idx = np.arange(len(tail))
431
  cp = float(df['Close'].iloc[-1])
432
 
433
+ # ── PANEL 1: Fiyat + BB + TP/SL ─────────────────────────────────────────
434
+ _set_ax_style(ax_price, 'EUR/USD Β· Son 50 GΓΌn + Sonraki GΓΌn Tahmini (TP/SL)', 'Fiyat')
435
+ ax_price.fill_between(idx,
436
+ tail['BB_lower'].values, tail['BB_upper'].values,
437
+ alpha=0.07, color=ACCENT)
438
+ ax_price.plot(idx, tail['BB_upper'].values, lw=0.6, color=ACCENT, alpha=0.35, ls='--')
439
+ ax_price.plot(idx, tail['BB_lower'].values, lw=0.6, color=ACCENT, alpha=0.35, ls='--')
440
+ ax_price.plot(idx, tail['BB_middle'].values, lw=0.7, color=ACCENT, alpha=0.5, ls=':')
441
+ ax_price.plot(idx, tail['Close'].values, lw=1.8, color=TEXT, label='Kapanış', zorder=5)
442
+ ax_price.plot(idx, tail['SMA_20'].values, lw=1.0, color=AMBER, alpha=0.8, label='SMA 20', ls='--')
443
+ ax_price.plot(idx, tail['SMA_50'].values, lw=1.0, color=MUTED, alpha=0.7, label='SMA 50', ls=':')
444
+
445
+ # TP / SL yatay Γ§izgiler
446
+ last_x = len(idx) - 1
447
+ ax_price.axhline(y=tp_sl['tp'], color=GREEN, lw=1.3, ls='--', alpha=0.9, zorder=4)
448
+ ax_price.axhline(y=tp_sl['sl'], color=RED, lw=1.3, ls='--', alpha=0.9, zorder=4)
449
+ ax_price.axhline(y=cp, color=TEXT, lw=0.7, ls='-', alpha=0.4, zorder=3)
450
+
451
+ r_margin = len(idx) * 0.02
452
+ ax_price.text(last_x + r_margin, tp_sl['tp'],
453
+ f" TP {tp_sl['tp']:.5f} (+{tp_sl['tp_pips']:.0f}p)",
454
+ color=GREEN, fontsize=7.5, va='center', fontweight='bold')
455
+ ax_price.text(last_x + r_margin, tp_sl['sl'],
456
+ f" SL {tp_sl['sl']:.5f} (-{tp_sl['sl_pips']:.0f}p)",
457
+ color=RED, fontsize=7.5, va='center', fontweight='bold')
458
+ ax_price.text(last_x + r_margin, cp,
459
+ f" GİRİŞ {cp:.5f}",
460
+ color=TEXT, fontsize=7.5, va='center')
461
+
462
+ # Ensemble ok
463
+ ax_price.annotate(
464
+ f"β–Ί ENS {ensemble_pred:.5f}",
465
+ xy=(last_x, cp),
466
+ xytext=(last_x + len(idx) * 0.04, ensemble_pred),
467
+ arrowprops=dict(arrowstyle='->', color=ACCENT2, lw=1.4, connectionstyle='arc3,rad=0.2'),
468
+ color=ACCENT2, fontsize=8, fontweight='bold', zorder=6,
469
  )
470
+
471
+ ax_price.legend(fontsize=7, framealpha=0.15, labelcolor=SUBTEXT, ncol=5, loc='upper left')
472
+ ax_price.yaxis.set_major_formatter(mticker.FormatStrFormatter('%.4f'))
473
+
474
+ # ── PANEL 2: RSI ─────────────────────────────────────────────────────────
475
+ _set_ax_style(ax_rsi, 'RSI (14)', 'RSI')
476
+ rsi_v = tail['RSI'].values
477
+ ax_rsi.plot(idx, rsi_v, lw=1.4, color=ACCENT)
478
+ ax_rsi.fill_between(idx, rsi_v, 70, where=(rsi_v >= 70), alpha=0.18, color=RED)
479
+ ax_rsi.fill_between(idx, rsi_v, 30, where=(rsi_v <= 30), alpha=0.18, color=GREEN)
480
+ ax_rsi.fill_between(idx, rsi_v, 50, where=((rsi_v > 50) & (rsi_v < 70)), alpha=0.07, color=GREEN)
481
+ ax_rsi.fill_between(idx, rsi_v, 50, where=((rsi_v < 50) & (rsi_v > 30)), alpha=0.07, color=RED)
482
+ for lvl, lbl, c in [(70, 'OB', RED), (50, '50', MUTED), (30, 'OS', GREEN)]:
483
+ ax_rsi.axhline(lvl, color=c, lw=0.7, ls='--', alpha=0.5)
484
+ ax_rsi.text(len(idx), lvl, f' {lbl}', color=c, fontsize=6.5, va='center')
485
+ ax_rsi.set_ylim(0, 100)
486
+
487
+ # ── PANEL 3: MACD ─────────────────────────────────────────────────────────
488
+ _set_ax_style(ax_macd, 'MACD (12 / 26 / 9)')
489
+ macd_v = tail['MACD'].values
490
+ sig_v = tail['MACD_Signal'].values
491
+ hist_v = macd_v - sig_v
492
+ hcolors = [GREEN if v >= 0 else RED for v in hist_v]
493
+ ax_macd.bar(idx, hist_v, color=hcolors, alpha=0.45, width=0.75, label='Histogram')
494
+ ax_macd.plot(idx, macd_v, lw=1.3, color=ACCENT, label='MACD')
495
+ ax_macd.plot(idx, sig_v, lw=1.1, color=AMBER, label='Signal', ls='--')
496
+ ax_macd.axhline(0, color=MUTED, lw=0.7)
497
+ ax_macd.legend(fontsize=6.5, framealpha=0.1, labelcolor=SUBTEXT, ncol=3)
498
+
499
+ # ── PANEL 4: Stochastic ───────────────────────────────────────────────────
500
+ _set_ax_style(ax_stoch, 'Stochastic (14,3)', '%')
501
+ stk_v = tail['Stoch_K'].values
502
+ std_v = tail['Stoch_D'].values
503
+ ax_stoch.plot(idx, stk_v, lw=1.3, color=ACCENT, label='%K')
504
+ ax_stoch.plot(idx, std_v, lw=1.0, color=AMBER, label='%D', ls='--')
505
+ ax_stoch.fill_between(idx, stk_v, 80, where=(stk_v >= 80), alpha=0.12, color=RED)
506
+ ax_stoch.fill_between(idx, stk_v, 20, where=(stk_v <= 20), alpha=0.12, color=GREEN)
507
+ for lvl, c in [(80, RED), (20, GREEN)]:
508
+ ax_stoch.axhline(lvl, color=c, lw=0.7, ls='--', alpha=0.5)
509
+ ax_stoch.set_ylim(-5, 105)
510
+ ax_stoch.legend(fontsize=6.5, framealpha=0.1, labelcolor=SUBTEXT, ncol=2)
511
+
512
+ # ── PANEL 5: CCI ──────────────────────────────────────────────────────────
513
+ _set_ax_style(ax_cci, 'CCI (20)')
514
+ cci_v = tail['CCI'].values
515
+ ax_cci.plot(idx, cci_v, lw=1.3, color=PURPLE)
516
+ ax_cci.fill_between(idx, cci_v, 100, where=(cci_v >= 100), alpha=0.12, color=RED)
517
+ ax_cci.fill_between(idx, cci_v, -100, where=(cci_v <= -100), alpha=0.12, color=GREEN)
518
+ for lvl, c in [(100, RED), (0, MUTED), (-100, GREEN)]:
519
+ ax_cci.axhline(lvl, color=c, lw=0.7, ls='--', alpha=0.5)
520
+
521
+ # ── PANEL 6: Model karşılaştΔ±rmasΔ± ────────────────────────────────────────
522
+ _set_ax_style(ax_models, '5 Model Tahmini vs Giriş Fiyatı · (ATR TP/SL dahil)', 'Fiyat')
523
  names = list(next_preds.keys())
524
  values = [next_preds[n] for n in names]
525
  maes = [float(np.mean(np.abs(test_preds[n] - y_test))) for n in names]
526
+ x_pos = np.arange(len(names))
527
 
528
+ bcolors = [GREEN if v > cp else RED for v in values]
529
+ bars = ax_models.bar(x_pos, values, color=bcolors, alpha=0.70,
530
+ width=0.55, zorder=3, edgecolor=BORDER, linewidth=0.5)
531
 
532
+ ax_models.axhline(y=cp, color=TEXT, lw=1.4, ls='--', alpha=0.7,
533
+ label=f'Giriş {cp:.5f}', zorder=4)
534
+ ax_models.axhline(y=ensemble_pred, color=ACCENT2, lw=1.6, ls='-', alpha=0.9,
535
+ label=f'Ensemble {ensemble_pred:.5f}', zorder=4)
536
+ ax_models.axhline(y=tp_sl['tp'], color=GREEN, lw=1.0, ls=':', alpha=0.7,
537
+ label=f"TP {tp_sl['tp']:.5f}", zorder=3)
538
+ ax_models.axhline(y=tp_sl['sl'], color=RED, lw=1.0, ls=':', alpha=0.7,
539
+ label=f"SL {tp_sl['sl']:.5f}", zorder=3)
540
 
541
  for bar, val, mae in zip(bars, values, maes):
542
+ pct = (val - cp) / cp * 100
543
+ sign = '+' if pct >= 0 else ''
544
+ label = f"{sign}{pct:.3f}%\nMAE {mae:.5f}"
545
+ y_off = 0.00003 if val >= cp else -0.00006
546
+ va = 'bottom' if val >= cp else 'top'
547
+ ax_models.text(bar.get_x() + bar.get_width() / 2,
548
+ val + y_off, label,
549
+ ha='center', va=va, fontsize=7, color=TEXT, fontweight='bold')
550
+
551
+ ax_models.set_xticks(x_pos)
552
+ ax_models.set_xticklabels(names, fontsize=8.5, color=TEXT)
553
+ ax_models.legend(fontsize=7.5, framealpha=0.15, labelcolor=SUBTEXT, ncol=4)
554
+ ax_models.yaxis.set_major_formatter(mticker.FormatStrFormatter('%.4f'))
555
+
556
+ # ── Ana başlΔ±k ────────────────────────────────────────────────────────────
557
+ direction_label = f"{'β–² LONG' if tp_sl['direction'] == 'LONG' else 'β–Ό SHORT'} Β· " \
558
+ f"TP +{tp_sl['tp_pips']:.0f}p Β· SL -{tp_sl['sl_pips']:.0f}p Β· " \
559
+ f"R:R 1:{tp_sl['rr']}"
560
+ dir_color = GREEN if tp_sl['direction'] == 'LONG' else RED
561
+
562
+ fig.text(0.06, 0.97, "EUR/USD Β· ENSEMBLE FORECAST ENGINE",
563
+ fontsize=13, color=TEXT, fontweight='bold', va='top')
564
+ fig.text(0.06, 0.955, direction_label,
565
+ fontsize=9, color=dir_color, va='top')
566
+ fig.text(0.97, 0.97, datetime.now().strftime('%d %b %Y %H:%M'),
567
+ fontsize=8, color=SUBTEXT, va='top', ha='right')
568
 
569
+ return fig
 
 
 
 
570
 
 
 
571
 
572
+ # ══════════════════════════════════════════════════════════════════════════════
573
+ # ANA TAHMΔ°N FONKSΔ°YONU
574
+ # ══════════════════════════════════════════════════════════════════════════════
575
 
 
576
  def predict_eurusd():
 
577
  try:
578
+ # 1. Veri
579
  data = get_forex_data()
580
  if data is None:
581
+ return "❌ Veri indirilemedi. İnternet bağlantınızı kontrol edin.", None
582
+
583
+ # 2. Feature mühendisliği
584
+ df = create_features(data)
585
+ if len(df) < 20:
586
+ return "❌ Yeterli veri yok (min 20 satır gerekli).", None
587
+
588
+ # 3. Eğitim & backtest
589
+ trained, scaler, test_preds, y_test, avail = train_and_evaluate(df)
590
+
591
+ # 4. Tahmin
592
+ next_preds, ensemble = predict_next(trained, scaler, df, avail)
593
+
594
+ # 5. TP / SL
595
+ cp = float(df['Close'].iloc[-1])
596
+ atr = float(df['ATR'].iloc[-1])
597
+ tp_sl = compute_tp_sl(
598
+ current_price = cp,
599
+ ensemble_pred = ensemble,
600
+ atr = atr,
601
+ atr_multiplier_sl = 1.5,
602
+ rr_ratio = 2.0,
603
+ )
604
+
605
+ # 6. Sinyal gΓΌcΓΌ
606
+ sig = compute_signal_score(df, tp_sl['direction'])
607
+
608
+ # 7. Backtest metrikleri
609
+ ens_test = np.mean([test_preds[n] for n in test_preds], axis=0)
610
+ ens_mae = float(np.mean(np.abs(ens_test - y_test)))
611
+ ens_rmse = float(np.sqrt(np.mean((ens_test - y_test) ** 2)))
612
+ hit_arr = np.sign(np.diff(np.append([y_test[0]], ens_test))) == np.sign(np.diff(y_test))
613
+ hit_rate = float(np.mean(hit_arr)) * 100
614
+
615
+ # 8. Teknik durum
616
+ rsi_val = float(df['RSI'].iloc[-1])
617
+ macd_val = float(df['MACD'].iloc[-1])
618
+ mhist_val = float(df['MACD_Hist'].iloc[-1])
619
+ stk_val = float(df['Stoch_K'].iloc[-1])
620
+ std_val = float(df['Stoch_D'].iloc[-1])
621
+ cci_val = float(df['CCI'].iloc[-1])
622
+ wr_val = float(df['WilliamsR'].iloc[-1])
623
+ bb_pct_val = float(df['BB_pct'].iloc[-1])
624
+ vol20_val = float(df['Volatility_20'].iloc[-1]) * 100
625
+ atr_pct = float(df['ATR_Pct'].iloc[-1]) * 100
626
+
627
+ rsi_lbl = "AşırΔ± AlΔ±m ⚠" if rsi_val > 70 else ("AşırΔ± SatΔ±m ⚠" if rsi_val < 30 else "NΓΆtr βœ“")
628
+ macd_lbl = "Boğa β–²" if macd_val > 0 else "AyΔ± β–Ό"
629
+ stk_lbl = "%K > %D β–²" if stk_val > std_val else "%K < %D β–Ό"
630
+ ens_chg = (ensemble - cp) / cp * 100
631
+
632
+ # ── Rapor metni ───────────────────────────────────────────────────────
633
+ W = 52 # satır genişliği
634
+ sep = "━" * W
635
+ sep2 = "─" * W
636
 
637
  lines = [
638
+ sep,
639
+ f" EUR/USD Β· ENSEMBLE FORECAST ENGINE",
640
  f" {datetime.now().strftime('%d %b %Y %H:%M:%S')}",
641
+ sep,
642
+ "",
643
+ f" {'MEVCUT FΔ°YAT':<22}: {cp:.5f}",
644
+ f" {'ENSEMBLE TAHMΔ°N':<22}: {ensemble:.5f} ({ens_chg:+.3f}%)",
645
+ f" {'YΓ–N':<22}: {tp_sl['direction']}",
646
+ "",
647
+ f" {'─── RΔ°SK YΓ–NETΔ°MΔ° (ATR Γ— 1.5 / R:R 1:2)':<{W-2}}",
648
+ f" {'GİRİŞ':<22}: {tp_sl['entry']:.5f}",
649
+ f" {'TAKE PROFIT (TP)':<22}: {tp_sl['tp']:.5f} (+{tp_sl['tp_pips']:.0f} pip)",
650
+ f" {'STOP LOSS (SL)':<22}: {tp_sl['sl']:.5f} (-{tp_sl['sl_pips']:.0f} pip)",
651
+ f" {'R:R ORANI':<22}: 1 : {tp_sl['rr']:.1f}",
652
+ f" {'RISK (%)':<22}: {tp_sl['risk_pct']:.3f}%",
653
+ f" {'ATR (14)':<22}: {atr:.5f} ({atr_pct:.3f}%)",
654
+ "",
655
+ f" {'─── SΔ°NYAL GÜCÜ':<{W-2}}",
656
+ f" {'SKOR':<22}: {sig['score']:.0f} / 100 β†’ {sig['label']}",
657
  ]
658
 
659
+ for ind, detail in sig['details'].items():
660
+ lines.append(f" {ind:<20}: {detail}")
 
 
 
661
 
662
  lines += [
663
+ "",
664
+ f" {'─── TEKNΔ°K GΓ–STERGELER':<{W-2}}",
665
+ f" {'RSI (14)':<22}: {rsi_val:.2f} β†’ {rsi_lbl}",
666
+ f" {'MACD':<22}: {macd_val:.5f} β†’ {macd_lbl}",
667
+ f" {'MACD Histogram':<22}: {mhist_val:.5f}",
668
+ f" {'Stochastic K/D':<22}: {stk_val:.1f} / {std_val:.1f} β†’ {stk_lbl}",
669
+ f" {'CCI (20)':<22}: {cci_val:.1f}",
670
+ f" {'Williams %R':<22}: {wr_val:.1f}",
671
+ f" {'BB Pozisyonu (%)':<22}: {bb_pct_val:.3f}",
672
+ f" {'Volatilite (20g Οƒ)':<22}: {vol20_val:.4f}%",
673
+ "",
674
+ f" {'─── MODEL TAHMΔ°NLERΔ°':<{W-2}}",
675
  ]
676
 
677
+ for name, val in next_preds.items():
678
+ chg = (val - cp) / cp * 100
679
+ mae = float(np.mean(np.abs(test_preds[name] - y_test)))
680
+ rmse = float(np.sqrt(np.mean((test_preds[name] - y_test) ** 2)))
681
+ ar = "β–²" if chg > 0 else "β–Ό"
682
+ lines.append(
683
+ f" {name:<22}: {val:.5f} ({chg:+.3f}%) {ar}"
684
+ f" MAE={mae:.5f} RMSE={rmse:.5f}"
685
+ )
686
 
687
  lines += [
688
+ "",
689
+ f" {'─── BACKTEST (Son 10 GΓΌn)':<{W-2}}",
690
+ f" {'Ensemble MAE':<22}: {ens_mae:.6f}",
691
+ f" {'Ensemble RMSE':<22}: {ens_rmse:.6f}",
692
+ f" {'Yân Doğruluğu':<22}: {hit_rate:.1f}%",
693
+ "",
694
+ sep,
695
  f" ⚠ YalnΔ±zca eğitim amaΓ§lΔ±dΔ±r. Finansal tavsiye değildir.",
696
+ sep,
697
  ]
698
 
699
  output_text = "\n".join(lines)
700
+
701
+ # 9. Grafik
702
+ fig = build_figure(df, next_preds, ensemble, test_preds, y_test, tp_sl)
703
 
704
  return output_text, fig
705
 
706
+ except Exception:
707
  return f"❌ Hata:\n{traceback.format_exc()}", None
708
 
709
+
710
+ # ════════════════���═════════════════════════════════════════════════════════════
711
+ # CSS & HTML
712
+ # ══════════════════════════════════════════════════════════════════════════════
713
+
714
  CSS = """
715
+ * { box-sizing: border-box; }
716
  body, .gradio-container {
717
  background: #0A0E17 !important;
718
+ font-family: 'JetBrains Mono', 'Fira Code', 'Cascadia Code', monospace !important;
719
  color: #F1F5F9 !important;
720
  }
721
+ button.lg.primary {
722
+ background: linear-gradient(135deg, #1D4ED8 0%, #4F46E5 100%) !important;
723
  border: none !important;
724
  color: #fff !important;
725
  font-weight: 700 !important;
726
+ letter-spacing: .06em !important;
727
+ padding: 14px 36px !important;
728
  border-radius: 6px !important;
729
+ font-size: 1rem !important;
730
+ transition: filter .2s !important;
731
  }
732
+ button.lg.primary:hover { filter: brightness(1.15) !important; }
733
+ .gr-textbox textarea, textarea {
734
+ background: #0F1623 !important;
735
  color: #94A3B8 !important;
736
  border: 1px solid #1E293B !important;
737
  font-family: 'JetBrains Mono', monospace !important;
738
+ font-size: 12px !important;
739
+ line-height: 1.8 !important;
740
+ border-radius: 6px !important;
 
 
 
741
  }
742
+ label span { color: #64748B !important; font-size: .78rem !important; }
743
+ .gr-panel, .gr-box { background: #111827 !important; border: 1px solid #1E293B !important; }
744
  footer { display: none !important; }
745
  """
746
 
747
+ HEADER = """
748
  <div style="
749
+ background: linear-gradient(160deg,#0A0E17 0%,#0F1623 60%,#111827 100%);
750
  border: 1px solid #1E293B;
751
  border-radius: 10px;
752
+ padding: 28px 32px 22px;
753
+ margin-bottom: 12px;
754
  ">
755
+ <div style="display:flex;align-items:center;gap:16px;margin-bottom:12px;">
756
+ <span style="font-size:2.4rem;line-height:1;">πŸ’Ή</span>
757
  <div>
758
+ <h1 style="margin:0;font-size:1.6rem;font-weight:900;color:#F1F5F9;letter-spacing:.05em;">
759
+ EUR/USD &nbsp;Β·&nbsp; ENSEMBLE FORECAST ENGINE
760
  </h1>
761
+ <p style="margin:5px 0 0;color:#64748B;font-size:.8rem;font-family:monospace;">
762
+ Random Forest &nbsp;Β·&nbsp; Gradient Boosting &nbsp;Β·&nbsp;
763
+ AdaBoost &nbsp;Β·&nbsp; Ridge &nbsp;Β·&nbsp; SVR
764
  </p>
765
  </div>
766
  </div>
767
+ <div style="display:flex;gap:10px;flex-wrap:wrap;margin-top:4px;">
768
+ <span style="background:#1E293B;border-radius:4px;padding:5px 14px;font-size:.76rem;color:#94A3B8;">πŸ“‘ Yahoo Finance 120g</span>
769
+ <span style="background:#1E293B;border-radius:4px;padding:5px 14px;font-size:.76rem;color:#94A3B8;">πŸ“ 42 Feature</span>
770
+ <span style="background:#1E293B;border-radius:4px;padding:5px 14px;font-size:.76rem;color:#94A3B8;">πŸ§ͺ 10-GΓΌn Backtest</span>
771
+ <span style="background:#1E293B;border-radius:4px;padding:5px 14px;font-size:.76rem;color:#94A3B8;">🎯 ATR TP/SL</span>
772
+ <span style="background:#1E293B;border-radius:4px;padding:5px 14px;font-size:.76rem;color:#94A3B8;">πŸ“Š 5 Teknik OsilatΓΆr</span>
773
+ <span style="background:#1E293B;border-radius:4px;padding:5px 14px;font-size:.76rem;color:#94A3B8;">⚑ Tek Tıkla Güncelle</span>
774
  </div>
775
  </div>
776
  """
777
 
778
+ # ══════════════════════════════════════════════════════════════════════════════
779
+ # GRADIO ARAYÜZÜ
780
+ # ══════════════════════════════════════════════════════════════════════════════
781
+
782
+ with gr.Blocks(css=CSS, title="EUR/USD Ensemble Forecast Engine") as app:
783
+ gr.HTML(HEADER)
784
 
785
  with gr.Row():
786
  predict_btn = gr.Button("⟳ TAHMΔ°N YAP / YENΔ°LE", variant="primary", scale=1)
787
 
788
  with gr.Row():
789
+ with gr.Column(scale=1, min_width=380):
790
  output_text = gr.Textbox(
791
  label="RAPOR",
792
+ lines=38,
793
  interactive=False,
 
794
  )
795
  with gr.Column(scale=2):
796
  output_plot = gr.Plot(label="GRAFΔ°KLER")