# ══════════════════════════════════════════════════════════════════════════════ # EUR/USD · ENSEMBLE FORECAST ENGINE · Premium Edition # 5 Model: Random Forest · Gradient Boosting · AdaBoost · Ridge · SVR # TP/SL hesaplama · ATR tabanlı risk yönetimi · Backtest analizi # ══════════════════════════════════════════════════════════════════════════════ import gradio as gr import pandas as pd import numpy as np from datetime import datetime, timedelta import yfinance as yf from sklearn.ensemble import RandomForestRegressor, GradientBoostingRegressor, AdaBoostRegressor from sklearn.linear_model import Ridge from sklearn.svm import SVR from sklearn.preprocessing import StandardScaler import matplotlib matplotlib.use('Agg') import matplotlib.pyplot as plt import matplotlib.patches as mpatches from matplotlib.gridspec import GridSpec import matplotlib.ticker as mticker from matplotlib.lines import Line2D import traceback import warnings warnings.filterwarnings('ignore') # ══════════════════════════════════════════════════════════════════════════════ # RENK PALETİ # ══════════════════════════════════════════════════════════════════════════════ BG = "#0A0E17" PANEL = "#0F1623" PANEL2 = "#111827" BORDER = "#1E293B" ACCENT = "#3B82F6" ACCENT2 = "#6366F1" GREEN = "#10B981" RED = "#EF4444" AMBER = "#F59E0B" PURPLE = "#8B5CF6" PINK = "#EC4899" MUTED = "#64748B" TEXT = "#F1F5F9" SUBTEXT = "#94A3B8" MODEL_COLORS = [ACCENT, PURPLE, GREEN, AMBER, PINK] # ══════════════════════════════════════════════════════════════════════════════ # VERİ KATMANI # ══════════════════════════════════════════════════════════════════════════════ def get_forex_data(symbol: str = "EURUSD=X", period: str = "120d") -> pd.DataFrame | None: """ Yahoo Finance'ten günlük OHLCV verisi indirir. 120 günlük pencere — SMA-50 ve lag-7 için yeterli. """ try: raw = yf.download(symbol, period=period, interval="1d", progress=False, auto_adjust=True) if raw is None or len(raw) < 60: return None # MultiIndex sütunlarını düzelt if isinstance(raw.columns, pd.MultiIndex): raw.columns = raw.columns.get_level_values(0) return raw except Exception: return None def compute_atr(df: pd.DataFrame, period: int = 14) -> pd.Series: """True Range tabanlı ATR hesabı.""" high = df['High'] low = df['Low'] prev_close = df['Close'].shift(1) tr = pd.concat([ high - low, (high - prev_close).abs(), (low - prev_close).abs(), ], axis=1).max(axis=1) return tr.rolling(period).mean() def compute_stoch(df: pd.DataFrame, k: int = 14, d: int = 3): """Stochastic Oscillator %K ve %D.""" low_min = df['Low'].rolling(k).min() high_max = df['High'].rolling(k).max() pct_k = 100 * (df['Close'] - low_min) / (high_max - low_min + 1e-10) pct_d = pct_k.rolling(d).mean() return pct_k, pct_d def compute_cci(df: pd.DataFrame, period: int = 20) -> pd.Series: """Commodity Channel Index.""" tp = (df['High'] + df['Low'] + df['Close']) / 3 sma = tp.rolling(period).mean() mad = tp.rolling(period).apply(lambda x: np.mean(np.abs(x - x.mean())), raw=True) return (tp - sma) / (0.015 * mad + 1e-10) def compute_williams_r(df: pd.DataFrame, period: int = 14) -> pd.Series: """Williams %R.""" high_max = df['High'].rolling(period).max() low_min = df['Low'].rolling(period).min() return -100 * (high_max - df['Close']) / (high_max - low_min + 1e-10) def create_features(data: pd.DataFrame) -> pd.DataFrame: """ 22 teknik gösterge + 7 lag feature üretir. Toplam: 29 input feature. """ df = data.copy() # ── Temel getiri & mum özellikleri ─────────────────────────────────────── df['Returns'] = df['Close'].pct_change() df['Log_Returns'] = np.log(df['Close'] / df['Close'].shift(1)) df['High_Low'] = df['High'] - df['Low'] df['Close_Open'] = df['Close'] - df['Open'] df['Body_Ratio'] = df['Close_Open'].abs() / (df['High_Low'] + 1e-10) df['Upper_Shadow'] = df['High'] - df[['Close', 'Open']].max(axis=1) df['Lower_Shadow'] = df[['Close', 'Open']].min(axis=1) - df['Low'] # ── Hareketli ortalamalar ───────────────────────────────────────────────── for w in [5, 10, 20, 50]: df[f'SMA_{w}'] = df['Close'].rolling(w).mean() df[f'EMA_{w}'] = df['Close'].ewm(span=w, adjust=False).mean() # SMA mesafe oranları df['Price_SMA5_Ratio'] = df['Close'] / df['SMA_5'] df['Price_SMA20_Ratio'] = df['Close'] / df['SMA_20'] df['SMA5_SMA20_Cross'] = df['SMA_5'] - df['SMA_20'] # ── RSI ─────────────────────────────────────────────────────────────────── delta = df['Close'].diff() gain = delta.where(delta > 0, 0.0).rolling(14).mean() loss = (-delta.where(delta < 0, 0.0)).rolling(14).mean() rs = gain / (loss + 1e-10) df['RSI'] = 100 - (100 / (1 + rs)) # ── MACD ────────────────────────────────────────────────────────────────── ema12 = df['Close'].ewm(span=12, adjust=False).mean() ema26 = df['Close'].ewm(span=26, adjust=False).mean() df['MACD'] = ema12 - ema26 df['MACD_Signal'] = df['MACD'].ewm(span=9, adjust=False).mean() df['MACD_Hist'] = df['MACD'] - df['MACD_Signal'] # ── Bollinger Bantları ──────────────────────────────────────────────────── df['BB_middle'] = df['Close'].rolling(20).mean() df['BB_std'] = df['Close'].rolling(20).std() df['BB_upper'] = df['BB_middle'] + df['BB_std'] * 2 df['BB_lower'] = df['BB_middle'] - df['BB_std'] * 2 df['BB_width'] = (df['BB_upper'] - df['BB_lower']) / (df['BB_middle'] + 1e-10) df['BB_pct'] = (df['Close'] - df['BB_lower']) / (df['BB_upper'] - df['BB_lower'] + 1e-10) # ── ATR ─────────────────────────────────────────────────────────────────── df['ATR'] = compute_atr(df, 14) df['ATR_Pct'] = df['ATR'] / df['Close'] # ── Stochastic ──────────────────────────────────────────────────────────── df['Stoch_K'], df['Stoch_D'] = compute_stoch(df) # ── CCI ─────────────────────────────────────────────────────────────────── df['CCI'] = compute_cci(df, 20) # ── Williams %R ─────────────────────────────────────────────────────────── df['WilliamsR'] = compute_williams_r(df, 14) # ── Volatilite ──────────────────────────────────────────────────────────── df['Volatility_5'] = df['Returns'].rolling(5).std() df['Volatility_20'] = df['Returns'].rolling(20).std() # ── Volume (varsa) ──────────────────────────────────────────────────────── if 'Volume' in df.columns: df['Volume_MA'] = df['Volume'].rolling(10).mean() df['Volume_Ratio'] = df['Volume'] / (df['Volume_MA'] + 1e-10) else: df['Volume_MA'] = 0.0 df['Volume_Ratio'] = 1.0 # ── Lag features ────────────────────────────────────────────────────────── for i in range(1, 8): df[f'Lag_{i}'] = df['Close'].shift(i) return df.dropna() # ══════════════════════════════════════════════════════════════════════════════ # FEATURE KOLONLARI # ══════════════════════════════════════════════════════════════════════════════ FEATURE_COLS = [ # Temel 'Returns', 'Log_Returns', 'High_Low', 'Close_Open', 'Body_Ratio', 'Upper_Shadow', 'Lower_Shadow', # SMA / EMA 'SMA_5', 'SMA_10', 'SMA_20', 'SMA_50', 'EMA_5', 'EMA_10', 'EMA_20', 'Price_SMA5_Ratio', 'Price_SMA20_Ratio', 'SMA5_SMA20_Cross', # Momentum 'RSI', 'MACD', 'MACD_Signal', 'MACD_Hist', 'Stoch_K', 'Stoch_D', 'CCI', 'WilliamsR', # Volatilite / BB 'BB_middle', 'BB_upper', 'BB_lower', 'BB_width', 'BB_pct', 'ATR', 'ATR_Pct', 'Volatility_5', 'Volatility_20', # Volume 'Volume_Ratio', # Lag 'Lag_1', 'Lag_2', 'Lag_3', 'Lag_4', 'Lag_5', 'Lag_6', 'Lag_7', ] # ══════════════════════════════════════════════════════════════════════════════ # MODEL KATMANI # ══════════════════════════════════════════════════════════════════════════════ def build_models() -> dict: """Her çağrıda taze model örnekleri döndürür (paralel eğitim güvenliği).""" return { 'Random Forest': RandomForestRegressor( n_estimators=300, max_depth=12, min_samples_leaf=3, random_state=42, n_jobs=-1), 'Gradient Boosting': GradientBoostingRegressor( n_estimators=300, learning_rate=0.04, max_depth=5, subsample=0.8, random_state=42), 'AdaBoost': AdaBoostRegressor( n_estimators=200, learning_rate=0.08, random_state=42), 'Ridge': Ridge(alpha=0.3), 'SVR': SVR(kernel='rbf', C=500, gamma='scale', epsilon=0.00005), } def train_and_evaluate(df: pd.DataFrame): """ Modelleri eğitir; son 10 günü test seti olarak kullanır. Dönüş: trained_models, scaler, test_preds (dict), y_test """ avail = [c for c in FEATURE_COLS if c in df.columns] X = df[avail].values y = df['Close'].values split = -10 X_train, X_test = X[:split], X[split:] y_train, y_test = y[:split], y[split:] scaler = StandardScaler() X_tr_sc = scaler.fit_transform(X_train) X_te_sc = scaler.transform(X_test) models = build_models() trained = {} test_preds = {} for name, m in models.items(): m.fit(X_tr_sc, y_train) test_preds[name] = m.predict(X_te_sc) trained[name] = m return trained, scaler, test_preds, y_test, avail def predict_next(trained: dict, scaler, df: pd.DataFrame, avail: list): """Son satırın feature'larından ertesi gün tahmini üretir.""" x = df[avail].iloc[-1].values.reshape(1, -1) xs = scaler.transform(x) preds = {n: float(m.predict(xs)[0]) for n, m in trained.items()} ensemble = float(np.mean(list(preds.values()))) return preds, ensemble # ══════════════════════════════════════════════════════════════════════════════ # TP / SL HESAPLAMA (ATR tabanlı risk yönetimi) # ══════════════════════════════════════════════════════════════════════════════ def compute_tp_sl( current_price: float, ensemble_pred: float, atr: float, atr_multiplier_sl: float = 1.5, rr_ratio: float = 2.0, ) -> dict: """ ATR tabanlı TP / SL hesaplar. Yön: ensemble > current → LONG | ensemble < current → SHORT SL = current ± (ATR × atr_multiplier_sl) TP = current ± (|SL mesafesi| × rr_ratio) Dönüş: direction, entry, sl, tp, sl_pips, tp_pips, rr, risk_pct """ direction = "LONG" if ensemble_pred > current_price else "SHORT" sl_dist = atr * atr_multiplier_sl tp_dist = sl_dist * rr_ratio if direction == "LONG": sl = current_price - sl_dist tp = current_price + tp_dist else: sl = current_price + sl_dist tp = current_price - tp_dist pip_size = 0.0001 # EUR/USD standart pip sl_pips = sl_dist / pip_size tp_pips = tp_dist / pip_size risk_pct = (sl_dist / current_price) * 100 return { 'direction' : direction, 'entry' : current_price, 'sl' : sl, 'tp' : tp, 'sl_pips' : sl_pips, 'tp_pips' : tp_pips, 'rr' : rr_ratio, 'risk_pct' : risk_pct, 'atr' : atr, } def compute_signal_score(df: pd.DataFrame, direction: str) -> dict: """ RSI · MACD · Stoch · CCI · BB pozisyonuna bakarak sinyal gücünü 0–100 arasında puanlar. """ row = df.iloc[-1] score = 0 details = {} rsi = float(row['RSI']) if direction == "LONG": if rsi < 70: pts = max(0, (70 - rsi) / 70 * 25) score += pts details['RSI'] = f"{rsi:.1f} — boğa bölgesi (+{pts:.0f}p)" else: details['RSI'] = f"{rsi:.1f} — aşırı alım (0p)" else: if rsi > 30: pts = max(0, (rsi - 30) / 70 * 25) score += pts details['RSI'] = f"{rsi:.1f} — ayı bölgesi (+{pts:.0f}p)" else: details['RSI'] = f"{rsi:.1f} — aşırı satım (0p)" macd = float(row['MACD']) mhist = float(row['MACD_Hist']) if direction == "LONG": pts = 20 if (macd > 0 and mhist > 0) else (10 if macd > 0 else 0) else: pts = 20 if (macd < 0 and mhist < 0) else (10 if macd < 0 else 0) score += pts details['MACD'] = f"macd={macd:.5f} hist={mhist:.5f} (+{pts}p)" stk = float(row['Stoch_K']) std = float(row['Stoch_D']) if direction == "LONG": pts = 20 if (stk < 80 and stk > std) else (10 if stk < 80 else 0) else: pts = 20 if (stk > 20 and stk < std) else (10 if stk > 20 else 0) score += pts details['Stoch'] = f"K={stk:.1f} D={std:.1f} (+{pts}p)" cci = float(row['CCI']) if direction == "LONG": pts = 20 if cci > -100 else 5 else: pts = 20 if cci < 100 else 5 score += pts details['CCI'] = f"{cci:.1f} (+{pts}p)" bb_pct = float(row['BB_pct']) if direction == "LONG": pts = 15 if bb_pct < 0.5 else 5 else: pts = 15 if bb_pct > 0.5 else 5 score += pts details['BB%'] = f"{bb_pct:.2f} (+{pts}p)" label = ("GÜÇLÜ ✅" if score >= 75 else "ORTA ⚠" if score >= 45 else "ZAYIF ❌") return {'score': min(100, score), 'label': label, 'details': details} # ══════════════════════════════════════════════════════════════════════════════ # GRAFİK MOTORU # ══════════════════════════════════════════════════════════════════════════════ def _set_ax_style(ax, title: str = "", ylabel: str = ""): ax.set_facecolor(PANEL) ax.tick_params(colors=SUBTEXT, labelsize=7) for spine in ax.spines.values(): spine.set_edgecolor(BORDER) ax.grid(True, color=BORDER, linewidth=0.4, alpha=0.6) if title: ax.set_title(title, fontsize=8.5, color=TEXT, pad=7, loc='left', fontweight='bold') if ylabel: ax.set_ylabel(ylabel, fontsize=7, color=SUBTEXT) def build_figure( df: pd.DataFrame, next_preds: dict, ensemble_pred: float, test_preds: dict, y_test: np.ndarray, tp_sl: dict, ) -> plt.Figure: plt.rcParams.update({ 'font.family' : 'monospace', 'text.color' : TEXT, 'figure.facecolor': BG, 'axes.facecolor' : PANEL, 'axes.edgecolor' : BORDER, 'grid.color' : BORDER, 'grid.linewidth' : 0.4, }) fig = plt.figure(figsize=(16, 15)) gs = GridSpec(4, 2, figure=fig, hspace=0.52, wspace=0.32, left=0.06, right=0.97, top=0.94, bottom=0.04) ax_price = fig.add_subplot(gs[0, :]) # Fiyat + BB + TP/SL ax_rsi = fig.add_subplot(gs[1, 0]) # RSI ax_macd = fig.add_subplot(gs[1, 1]) # MACD ax_stoch = fig.add_subplot(gs[2, 0]) # Stochastic ax_cci = fig.add_subplot(gs[2, 1]) # CCI ax_models = fig.add_subplot(gs[3, :]) # Model karşılaştırması tail = df.tail(50) idx = np.arange(len(tail)) cp = float(df['Close'].iloc[-1]) # ── PANEL 1: Fiyat + BB + TP/SL ───────────────────────────────────────── _set_ax_style(ax_price, 'EUR/USD · Son 50 Gün + Sonraki Gün Tahmini (TP/SL)', 'Fiyat') ax_price.fill_between(idx, tail['BB_lower'].values, tail['BB_upper'].values, alpha=0.07, color=ACCENT) ax_price.plot(idx, tail['BB_upper'].values, lw=0.6, color=ACCENT, alpha=0.35, ls='--') ax_price.plot(idx, tail['BB_lower'].values, lw=0.6, color=ACCENT, alpha=0.35, ls='--') ax_price.plot(idx, tail['BB_middle'].values, lw=0.7, color=ACCENT, alpha=0.5, ls=':') ax_price.plot(idx, tail['Close'].values, lw=1.8, color=TEXT, label='Kapanış', zorder=5) ax_price.plot(idx, tail['SMA_20'].values, lw=1.0, color=AMBER, alpha=0.8, label='SMA 20', ls='--') ax_price.plot(idx, tail['SMA_50'].values, lw=1.0, color=MUTED, alpha=0.7, label='SMA 50', ls=':') # TP / SL yatay çizgiler last_x = len(idx) - 1 ax_price.axhline(y=tp_sl['tp'], color=GREEN, lw=1.3, ls='--', alpha=0.9, zorder=4) ax_price.axhline(y=tp_sl['sl'], color=RED, lw=1.3, ls='--', alpha=0.9, zorder=4) ax_price.axhline(y=cp, color=TEXT, lw=0.7, ls='-', alpha=0.4, zorder=3) r_margin = len(idx) * 0.02 ax_price.text(last_x + r_margin, tp_sl['tp'], f" TP {tp_sl['tp']:.5f} (+{tp_sl['tp_pips']:.0f}p)", color=GREEN, fontsize=7.5, va='center', fontweight='bold') ax_price.text(last_x + r_margin, tp_sl['sl'], f" SL {tp_sl['sl']:.5f} (-{tp_sl['sl_pips']:.0f}p)", color=RED, fontsize=7.5, va='center', fontweight='bold') ax_price.text(last_x + r_margin, cp, f" GİRİŞ {cp:.5f}", color=TEXT, fontsize=7.5, va='center') # Ensemble ok ax_price.annotate( f"► ENS {ensemble_pred:.5f}", xy=(last_x, cp), xytext=(last_x + len(idx) * 0.04, ensemble_pred), arrowprops=dict(arrowstyle='->', color=ACCENT2, lw=1.4, connectionstyle='arc3,rad=0.2'), color=ACCENT2, fontsize=8, fontweight='bold', zorder=6, ) ax_price.legend(fontsize=7, framealpha=0.15, labelcolor=SUBTEXT, ncol=5, loc='upper left') ax_price.yaxis.set_major_formatter(mticker.FormatStrFormatter('%.4f')) # ── PANEL 2: RSI ───────────────────────────────────────────────────────── _set_ax_style(ax_rsi, 'RSI (14)', 'RSI') rsi_v = tail['RSI'].values ax_rsi.plot(idx, rsi_v, lw=1.4, color=ACCENT) ax_rsi.fill_between(idx, rsi_v, 70, where=(rsi_v >= 70), alpha=0.18, color=RED) ax_rsi.fill_between(idx, rsi_v, 30, where=(rsi_v <= 30), alpha=0.18, color=GREEN) ax_rsi.fill_between(idx, rsi_v, 50, where=((rsi_v > 50) & (rsi_v < 70)), alpha=0.07, color=GREEN) ax_rsi.fill_between(idx, rsi_v, 50, where=((rsi_v < 50) & (rsi_v > 30)), alpha=0.07, color=RED) for lvl, lbl, c in [(70, 'OB', RED), (50, '50', MUTED), (30, 'OS', GREEN)]: ax_rsi.axhline(lvl, color=c, lw=0.7, ls='--', alpha=0.5) ax_rsi.text(len(idx), lvl, f' {lbl}', color=c, fontsize=6.5, va='center') ax_rsi.set_ylim(0, 100) # ── PANEL 3: MACD ───────────────────────────────────────────────────────── _set_ax_style(ax_macd, 'MACD (12 / 26 / 9)') macd_v = tail['MACD'].values sig_v = tail['MACD_Signal'].values hist_v = macd_v - sig_v hcolors = [GREEN if v >= 0 else RED for v in hist_v] ax_macd.bar(idx, hist_v, color=hcolors, alpha=0.45, width=0.75, label='Histogram') ax_macd.plot(idx, macd_v, lw=1.3, color=ACCENT, label='MACD') ax_macd.plot(idx, sig_v, lw=1.1, color=AMBER, label='Signal', ls='--') ax_macd.axhline(0, color=MUTED, lw=0.7) ax_macd.legend(fontsize=6.5, framealpha=0.1, labelcolor=SUBTEXT, ncol=3) # ── PANEL 4: Stochastic ─────────────────────────────────────────────────── _set_ax_style(ax_stoch, 'Stochastic (14,3)', '%') stk_v = tail['Stoch_K'].values std_v = tail['Stoch_D'].values ax_stoch.plot(idx, stk_v, lw=1.3, color=ACCENT, label='%K') ax_stoch.plot(idx, std_v, lw=1.0, color=AMBER, label='%D', ls='--') ax_stoch.fill_between(idx, stk_v, 80, where=(stk_v >= 80), alpha=0.12, color=RED) ax_stoch.fill_between(idx, stk_v, 20, where=(stk_v <= 20), alpha=0.12, color=GREEN) for lvl, c in [(80, RED), (20, GREEN)]: ax_stoch.axhline(lvl, color=c, lw=0.7, ls='--', alpha=0.5) ax_stoch.set_ylim(-5, 105) ax_stoch.legend(fontsize=6.5, framealpha=0.1, labelcolor=SUBTEXT, ncol=2) # ── PANEL 5: CCI ────────────────────────────────────────────────────────── _set_ax_style(ax_cci, 'CCI (20)') cci_v = tail['CCI'].values ax_cci.plot(idx, cci_v, lw=1.3, color=PURPLE) ax_cci.fill_between(idx, cci_v, 100, where=(cci_v >= 100), alpha=0.12, color=RED) ax_cci.fill_between(idx, cci_v, -100, where=(cci_v <= -100), alpha=0.12, color=GREEN) for lvl, c in [(100, RED), (0, MUTED), (-100, GREEN)]: ax_cci.axhline(lvl, color=c, lw=0.7, ls='--', alpha=0.5) # ── PANEL 6: Model karşılaştırması ──────────────────────────────────────── _set_ax_style(ax_models, '5 Model Tahmini vs Giriş Fiyatı · (ATR TP/SL dahil)', 'Fiyat') names = list(next_preds.keys()) values = [next_preds[n] for n in names] maes = [float(np.mean(np.abs(test_preds[n] - y_test))) for n in names] x_pos = np.arange(len(names)) bcolors = [GREEN if v > cp else RED for v in values] bars = ax_models.bar(x_pos, values, color=bcolors, alpha=0.70, width=0.55, zorder=3, edgecolor=BORDER, linewidth=0.5) ax_models.axhline(y=cp, color=TEXT, lw=1.4, ls='--', alpha=0.7, label=f'Giriş {cp:.5f}', zorder=4) ax_models.axhline(y=ensemble_pred, color=ACCENT2, lw=1.6, ls='-', alpha=0.9, label=f'Ensemble {ensemble_pred:.5f}', zorder=4) ax_models.axhline(y=tp_sl['tp'], color=GREEN, lw=1.0, ls=':', alpha=0.7, label=f"TP {tp_sl['tp']:.5f}", zorder=3) ax_models.axhline(y=tp_sl['sl'], color=RED, lw=1.0, ls=':', alpha=0.7, label=f"SL {tp_sl['sl']:.5f}", zorder=3) for bar, val, mae in zip(bars, values, maes): pct = (val - cp) / cp * 100 sign = '+' if pct >= 0 else '' label = f"{sign}{pct:.3f}%\nMAE {mae:.5f}" y_off = 0.00003 if val >= cp else -0.00006 va = 'bottom' if val >= cp else 'top' ax_models.text(bar.get_x() + bar.get_width() / 2, val + y_off, label, ha='center', va=va, fontsize=7, color=TEXT, fontweight='bold') ax_models.set_xticks(x_pos) ax_models.set_xticklabels(names, fontsize=8.5, color=TEXT) ax_models.legend(fontsize=7.5, framealpha=0.15, labelcolor=SUBTEXT, ncol=4) ax_models.yaxis.set_major_formatter(mticker.FormatStrFormatter('%.4f')) # ── Ana başlık ──────────────────────────────────────────────────────────── direction_label = f"{'▲ LONG' if tp_sl['direction'] == 'LONG' else '▼ SHORT'} · " \ f"TP +{tp_sl['tp_pips']:.0f}p · SL -{tp_sl['sl_pips']:.0f}p · " \ f"R:R 1:{tp_sl['rr']}" dir_color = GREEN if tp_sl['direction'] == 'LONG' else RED fig.text(0.06, 0.97, "EUR/USD · ENSEMBLE FORECAST ENGINE", fontsize=13, color=TEXT, fontweight='bold', va='top') fig.text(0.06, 0.955, direction_label, fontsize=9, color=dir_color, va='top') fig.text(0.97, 0.97, datetime.now().strftime('%d %b %Y %H:%M'), fontsize=8, color=SUBTEXT, va='top', ha='right') return fig # ══════════════════════════════════════════════════════════════════════════════ # ANA TAHMİN FONKSİYONU # ══════════════════════════════════════════════════════════════════════════════ def predict_eurusd(): try: # 1. Veri data = get_forex_data() if data is None: return "❌ Veri indirilemedi. İnternet bağlantınızı kontrol edin.", None # 2. Feature mühendisliği df = create_features(data) if len(df) < 20: return "❌ Yeterli veri yok (min 20 satır gerekli).", None # 3. Eğitim & backtest trained, scaler, test_preds, y_test, avail = train_and_evaluate(df) # 4. Tahmin next_preds, ensemble = predict_next(trained, scaler, df, avail) # 5. TP / SL cp = float(df['Close'].iloc[-1]) atr = float(df['ATR'].iloc[-1]) tp_sl = compute_tp_sl( current_price = cp, ensemble_pred = ensemble, atr = atr, atr_multiplier_sl = 1.5, rr_ratio = 2.0, ) # 6. Sinyal gücü sig = compute_signal_score(df, tp_sl['direction']) # 7. Backtest metrikleri ens_test = np.mean([test_preds[n] for n in test_preds], axis=0) ens_mae = float(np.mean(np.abs(ens_test - y_test))) ens_rmse = float(np.sqrt(np.mean((ens_test - y_test) ** 2))) hit_arr = np.sign(ens_test[1:] - ens_test[:-1]) == np.sign(np.diff(y_test)) hit_rate = float(np.mean(hit_arr)) * 100 # 8. Teknik durum rsi_val = float(df['RSI'].iloc[-1]) macd_val = float(df['MACD'].iloc[-1]) mhist_val = float(df['MACD_Hist'].iloc[-1]) stk_val = float(df['Stoch_K'].iloc[-1]) std_val = float(df['Stoch_D'].iloc[-1]) cci_val = float(df['CCI'].iloc[-1]) wr_val = float(df['WilliamsR'].iloc[-1]) bb_pct_val = float(df['BB_pct'].iloc[-1]) vol20_val = float(df['Volatility_20'].iloc[-1]) * 100 atr_pct = float(df['ATR_Pct'].iloc[-1]) * 100 rsi_lbl = "Aşırı Alım ⚠" if rsi_val > 70 else ("Aşırı Satım ⚠" if rsi_val < 30 else "Nötr ✓") macd_lbl = "Boğa ▲" if macd_val > 0 else "Ayı ▼" stk_lbl = "%K > %D ▲" if stk_val > std_val else "%K < %D ▼" ens_chg = (ensemble - cp) / cp * 100 # ── Rapor metni ─────────────────────────────────────────────────────── W = 52 # satır genişliği sep = "━" * W sep2 = "─" * W lines = [ sep, f" EUR/USD · ENSEMBLE FORECAST ENGINE", f" {datetime.now().strftime('%d %b %Y %H:%M:%S')}", sep, "", f" {'MEVCUT FİYAT':<22}: {cp:.5f}", f" {'ENSEMBLE TAHMİN':<22}: {ensemble:.5f} ({ens_chg:+.3f}%)", f" {'YÖN':<22}: {tp_sl['direction']}", "", f" {'─── RİSK YÖNETİMİ (ATR × 1.5 / R:R 1:2)':<{W-2}}", f" {'GİRİŞ':<22}: {tp_sl['entry']:.5f}", f" {'TAKE PROFIT (TP)':<22}: {tp_sl['tp']:.5f} (+{tp_sl['tp_pips']:.0f} pip)", f" {'STOP LOSS (SL)':<22}: {tp_sl['sl']:.5f} (-{tp_sl['sl_pips']:.0f} pip)", f" {'R:R ORANI':<22}: 1 : {tp_sl['rr']:.1f}", f" {'RISK (%)':<22}: {tp_sl['risk_pct']:.3f}%", f" {'ATR (14)':<22}: {atr:.5f} ({atr_pct:.3f}%)", "", f" {'─── SİNYAL GÜCÜ':<{W-2}}", f" {'SKOR':<22}: {sig['score']:.0f} / 100 → {sig['label']}", ] for ind, detail in sig['details'].items(): lines.append(f" {ind:<20}: {detail}") lines += [ "", f" {'─── TEKNİK GÖSTERGELER':<{W-2}}", f" {'RSI (14)':<22}: {rsi_val:.2f} → {rsi_lbl}", f" {'MACD':<22}: {macd_val:.5f} → {macd_lbl}", f" {'MACD Histogram':<22}: {mhist_val:.5f}", f" {'Stochastic K/D':<22}: {stk_val:.1f} / {std_val:.1f} → {stk_lbl}", f" {'CCI (20)':<22}: {cci_val:.1f}", f" {'Williams %R':<22}: {wr_val:.1f}", f" {'BB Pozisyonu (%)':<22}: {bb_pct_val:.3f}", f" {'Volatilite (20g σ)':<22}: {vol20_val:.4f}%", "", f" {'─── MODEL TAHMİNLERİ':<{W-2}}", ] for name, val in next_preds.items(): chg = (val - cp) / cp * 100 mae = float(np.mean(np.abs(test_preds[name] - y_test))) rmse = float(np.sqrt(np.mean((test_preds[name] - y_test) ** 2))) ar = "▲" if chg > 0 else "▼" lines.append( f" {name:<22}: {val:.5f} ({chg:+.3f}%) {ar}" f" MAE={mae:.5f} RMSE={rmse:.5f}" ) lines += [ "", f" {'─── BACKTEST (Son 10 Gün)':<{W-2}}", f" {'Ensemble MAE':<22}: {ens_mae:.6f}", f" {'Ensemble RMSE':<22}: {ens_rmse:.6f}", f" {'Yön Doğruluğu':<22}: {hit_rate:.1f}%", "", sep, f" ⚠ Yalnızca eğitim amaçlıdır. Finansal tavsiye değildir.", sep, ] output_text = "\n".join(lines) # 9. Grafik fig = build_figure(df, next_preds, ensemble, test_preds, y_test, tp_sl) return output_text, fig except Exception: return f"❌ Hata:\n{traceback.format_exc()}", None # ══════════════════════════════════════════════════════════════════════════════ # CSS & HTML # ══════════════════════════════════════════════════════════════════════════════ CSS = """ * { box-sizing: border-box; } body, .gradio-container { background: #0A0E17 !important; font-family: 'JetBrains Mono', 'Fira Code', 'Cascadia Code', monospace !important; color: #F1F5F9 !important; } button.lg.primary { background: linear-gradient(135deg, #1D4ED8 0%, #4F46E5 100%) !important; border: none !important; color: #fff !important; font-weight: 700 !important; letter-spacing: .06em !important; padding: 14px 36px !important; border-radius: 6px !important; font-size: 1rem !important; transition: filter .2s !important; } button.lg.primary:hover { filter: brightness(1.15) !important; } .gr-textbox textarea, textarea { background: #0F1623 !important; color: #94A3B8 !important; border: 1px solid #1E293B !important; font-family: 'JetBrains Mono', monospace !important; font-size: 12px !important; line-height: 1.8 !important; border-radius: 6px !important; } label span { color: #64748B !important; font-size: .78rem !important; } .gr-panel, .gr-box { background: #111827 !important; border: 1px solid #1E293B !important; } footer { display: none !important; } """ HEADER = """
💹

EUR/USD  ·  ENSEMBLE FORECAST ENGINE

Random Forest  ·  Gradient Boosting  ·  AdaBoost  ·  Ridge  ·  SVR

📡 Yahoo Finance 120g 📐 42 Feature 🧪 10-Gün Backtest 🎯 ATR TP/SL 📊 5 Teknik Osilatör ⚡ Tek Tıkla Güncelle
""" # ══════════════════════════════════════════════════════════════════════════════ # GRADIO ARAYÜZÜ # ══════════════════════════════════════════════════════════════════════════════ with gr.Blocks(css=CSS, title="EUR/USD Ensemble Forecast Engine") as app: gr.HTML(HEADER) with gr.Row(): predict_btn = gr.Button("⟳ TAHMİN YAP / YENİLE", variant="primary", scale=1) with gr.Row(): with gr.Column(scale=1, min_width=380): output_text = gr.Textbox( label="RAPOR", lines=38, interactive=False, ) with gr.Column(scale=2): output_plot = gr.Plot(label="GRAFİKLER") predict_btn.click( fn=predict_eurusd, inputs=[], outputs=[output_text, output_plot], ) if __name__ == "__main__": app.launch()