Spaces:
Running
Running
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # 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 = """ | |
| <div style=" | |
| background: linear-gradient(160deg,#0A0E17 0%,#0F1623 60%,#111827 100%); | |
| border: 1px solid #1E293B; | |
| border-radius: 10px; | |
| padding: 28px 32px 22px; | |
| margin-bottom: 12px; | |
| "> | |
| <div style="display:flex;align-items:center;gap:16px;margin-bottom:12px;"> | |
| <span style="font-size:2.4rem;line-height:1;">πΉ</span> | |
| <div> | |
| <h1 style="margin:0;font-size:1.6rem;font-weight:900;color:#F1F5F9;letter-spacing:.05em;"> | |
| EUR/USD Β· ENSEMBLE FORECAST ENGINE | |
| </h1> | |
| <p style="margin:5px 0 0;color:#64748B;font-size:.8rem;font-family:monospace;"> | |
| Random Forest Β· Gradient Boosting Β· | |
| AdaBoost Β· Ridge Β· SVR | |
| </p> | |
| </div> | |
| </div> | |
| <div style="display:flex;gap:10px;flex-wrap:wrap;margin-top:4px;"> | |
| <span style="background:#1E293B;border-radius:4px;padding:5px 14px;font-size:.76rem;color:#94A3B8;">π‘ Yahoo Finance 120g</span> | |
| <span style="background:#1E293B;border-radius:4px;padding:5px 14px;font-size:.76rem;color:#94A3B8;">π 42 Feature</span> | |
| <span style="background:#1E293B;border-radius:4px;padding:5px 14px;font-size:.76rem;color:#94A3B8;">π§ͺ 10-GΓΌn Backtest</span> | |
| <span style="background:#1E293B;border-radius:4px;padding:5px 14px;font-size:.76rem;color:#94A3B8;">π― ATR TP/SL</span> | |
| <span style="background:#1E293B;border-radius:4px;padding:5px 14px;font-size:.76rem;color:#94A3B8;">π 5 Teknik OsilatΓΆr</span> | |
| <span style="background:#1E293B;border-radius:4px;padding:5px 14px;font-size:.76rem;color:#94A3B8;">β‘ Tek TΔ±kla GΓΌncelle</span> | |
| </div> | |
| </div> | |
| """ | |
| # ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # 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() | |