GoshawkVortexAI commited on
Commit
9a92ea1
Β·
verified Β·
1 Parent(s): 368137a

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +195 -55
app.py CHANGED
@@ -1,6 +1,6 @@
1
  # ══════════════════════════════════════════════════════════════════════════════
2
- # EUR/USD Β· RIDGE FORECAST ENGINE Β· Ultra-Premium Edition
3
- # Model: Stacked Ridge Regression (L1+L2 optimised, polynomial features)
4
  # Forecast horizon: 1 or 2 days ahead
5
  # TP/SL: ATR-based risk management Β· Signal scoring Β· Backtest
6
  # Data: Yahoo Finance (yfinance)
@@ -15,6 +15,8 @@ from sklearn.linear_model import Ridge, RidgeCV
15
  from sklearn.preprocessing import StandardScaler, PolynomialFeatures
16
  from sklearn.pipeline import Pipeline
17
  from sklearn.metrics import mean_absolute_error
 
 
18
  import matplotlib
19
  matplotlib.use('Agg')
20
  import matplotlib.pyplot as plt
@@ -96,6 +98,43 @@ def compute_rsi(series: pd.Series, period: int = 14) -> pd.Series:
96
  return 100 - (100 / (1 + gain / (loss + 1e-10)))
97
 
98
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
99
  def create_features(data: pd.DataFrame) -> pd.DataFrame:
100
  """
101
  Premium feature set β€” 55+ engineered features designed to minimise MAE
@@ -218,6 +257,16 @@ def create_features(data: pd.DataFrame) -> pd.DataFrame:
218
  df['Volume_Ratio'] = 1.0
219
  df['OBV_Signal'] = 0.0
220
 
 
 
 
 
 
 
 
 
 
 
221
  # ── FIX 4: Direction Composite Signal (boosts directional accuracy) ─────
222
  # Encodes consensus of multiple indicators into a single direction score.
223
  # Positive β†’ bullish bias, Negative β†’ bearish bias.
@@ -299,6 +348,8 @@ FEATURE_COLS = [
299
  'Volume_Ratio', 'OBV_Signal',
300
  # Direction composite (FIX 4)
301
  'Direction_Score', 'Momentum_Consistency',
 
 
302
  ]
303
 
304
 
@@ -311,19 +362,15 @@ FEATURE_COLS = [
311
  def build_ridge_pipeline(alpha: float) -> Pipeline:
312
  """Ridge pipeline with degree-2 interactions (no bias terms in poly)."""
313
  return Pipeline([
314
- ('scaler', StandardScaler()),
315
- ('poly', PolynomialFeatures(degree=2, interaction_only=True,
316
- include_bias=False)),
317
- ('scaler2', StandardScaler(with_mean=False)), # re-scale after poly
318
- ('ridge', Ridge(alpha=alpha, fit_intercept=True)),
319
  ])
320
 
321
 
322
  def select_best_alpha(X_tr, y_tr) -> float:
323
- """
324
- RidgeCV finds optimal alpha from a fine logarithmic grid.
325
- Evaluated on 5-fold time-series split (no shuffle).
326
- """
327
  alphas = np.logspace(-3, 4, 60)
328
  scaler = StandardScaler()
329
  Xs = scaler.fit_transform(X_tr)
@@ -332,54 +379,115 @@ def select_best_alpha(X_tr, y_tr) -> float:
332
  return float(rcv.alpha_)
333
 
334
 
335
- def train_ridge(df: pd.DataFrame, horizon: int = 1):
 
 
 
 
336
  """
337
- Trains the premium Ridge model.
338
- Target: log-return over `horizon` days (direction-aware).
339
- Returns pipeline, test predictions (absolute), y_test (absolute), avail_cols.
 
 
 
 
340
  """
341
  avail = [c for c in FEATURE_COLS if c in df.columns]
342
  X = df[avail].values
343
  close = df['Close'].values
 
344
 
345
- # --- FIX 1: Use log-return as target (direction-aware, scale-invariant) ---
346
- # log-return = log(close[t+horizon] / close[t])
347
- # This directly encodes direction: positive = UP, negative = DOWN
348
  y_logret = np.log(close[horizon:] / close[:len(close) - horizon])
 
 
349
 
350
- X = X[:len(y_logret)]
351
- close_aligned = close[:len(y_logret)]
 
352
 
353
- split = -15 # 15-day test window for robust backtest
354
- X_train, X_test = X[:split], X[split:]
355
- y_train, y_logret_test = y_logret[:split], y_logret[split:]
 
356
  close_test = close_aligned[split:]
 
357
 
358
- # Find best alpha on training data only
359
  best_alpha = select_best_alpha(X_train, y_train)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
360
 
361
- # Build & fit pipeline
362
- pipe = build_ridge_pipeline(best_alpha)
363
- pipe.fit(X_train, y_train)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
364
 
365
- # Test predictions β†’ convert log-return back to absolute price
366
- logret_hat = pipe.predict(X_test)
367
- pred_abs = close_test * np.exp(logret_hat)
368
- y_test_abs = close_test * np.exp(y_logret_test)
369
 
370
- mae = mean_absolute_error(y_test_abs, pred_abs)
 
 
371
 
372
- return pipe, pred_abs, y_test_abs, avail, best_alpha, mae
373
 
 
 
 
 
374
 
375
- def predict_next_ridge(pipe, df: pd.DataFrame, avail: list,
376
- horizon: int = 1) -> float:
377
- """Predict absolute close price horizon days ahead using log-return."""
378
- cp = float(df['Close'].iloc[-1])
379
- x = df[avail].iloc[-1].values.reshape(1, -1)
380
- logret = float(pipe.predict(x)[0])
381
- # Scale logret by horizon for multi-day forecasts
382
- return cp * np.exp(logret * horizon)
383
 
384
 
385
  # ══════════════════════════════════════════════════════════════════════════════
@@ -464,7 +572,8 @@ def _ax_style(ax, title="", ylabel=""):
464
 
465
 
466
  def build_figure(df, pred_price, horizon, tp_sl,
467
- test_pred_abs, y_test_abs, best_alpha, mae) -> plt.Figure:
 
468
 
469
  plt.rcParams.update({
470
  'font.family': 'monospace',
@@ -517,6 +626,15 @@ def build_figure(df, pred_price, horizon, tp_sl,
517
  (i - 0.25, min(o, c)), 0.5, abs(c - o) + 1e-7,
518
  boxstyle="square,pad=0", linewidth=0, facecolor=color, alpha=0.75, zorder=3))
519
 
 
 
 
 
 
 
 
 
 
520
  # Close line overlay (phosphor glow)
521
  ax_price.plot(idx, tail['Close'].values, lw=1.6, color=ACID, zorder=5, alpha=0.9, label='Close')
522
 
@@ -545,6 +663,14 @@ def build_figure(df, pred_price, horizon, tp_sl,
545
  fontfamily='monospace',
546
  )
547
 
 
 
 
 
 
 
 
 
548
  ax_price.legend(fontsize=7, framealpha=0.1, labelcolor=SUBTEXT,
549
  ncol=3, loc='upper left')
550
  ax_price.yaxis.set_major_formatter(mticker.FormatStrFormatter('%.4f'))
@@ -625,7 +751,7 @@ def build_figure(df, pred_price, horizon, tp_sl,
625
  dir_color = GREEN if tp_sl['direction'] == 'LONG' else WARM
626
 
627
  fig.text(0.055, 0.97,
628
- "EUR/USD Β· RIDGE FORECAST ENGINE Β· ULTRA-PREMIUM",
629
  fontsize=12, color=TEXT, fontweight='bold', va='top',
630
  fontfamily='monospace')
631
  fig.text(0.055, 0.955, dir_label,
@@ -660,15 +786,22 @@ def predict_eurusd(horizon: int = 1):
660
  if len(df) < 40:
661
  return "❌ Yetersiz veri (min 40 satΔ±r gerekli).", None
662
 
663
- # 3. Train Ridge + backtest
664
- pipe, test_pred_abs, y_test_abs, avail, best_alpha, mae = train_ridge(df, horizon)
665
-
666
- # 4. Predict next
667
- pred_price = predict_next_ridge(pipe, df, avail, horizon)
668
 
669
- # 5. TP/SL
670
  cp = float(df['Close'].iloc[-1])
671
  atr = float(df['ATR'].iloc[-1])
 
 
 
 
 
 
 
 
672
  tp_sl = compute_tp_sl(cp, pred_price, atr,
673
  atr_multiplier_sl=1.5, rr_ratio=2.0)
674
 
@@ -677,7 +810,6 @@ def predict_eurusd(horizon: int = 1):
677
 
678
  # 7. Backtest metrics
679
  rmse = float(np.sqrt(np.mean((test_pred_abs - y_test_abs) ** 2)))
680
- # FIX 3: Correct directional accuracy β€” compare pred vs entry price
681
  close_vals = df['Close'].values
682
  n_test = len(test_pred_abs)
683
  entry_prices = close_vals[len(close_vals) - n_test - horizon :
@@ -710,15 +842,19 @@ def predict_eurusd(horizon: int = 1):
710
  W = 54
711
  sep = "═" * W
712
 
 
713
  lines = [
714
  sep,
715
- f" EUR/USD Β· RIDGE FORECAST ENGINE Β· ULTRA-PREMIUM",
716
  f" {datetime.now().strftime('%d %b %Y %H:%M:%S')}",
717
  sep,
718
  "",
719
  f" {'MEVCUT FΔ°YAT':<24}: {cp:.5f}",
720
- f" {f'+{horizon}G TAHMΔ°N (RIDGE)':<24}: {pred_price:.5f} ({chg:+.3f}%)",
721
  f" {'YΓ–N':<24}: {tp_sl['direction']}",
 
 
 
722
  f" {'OPTΔ°MAL ALPHA (RidgeCV)':<24}: {best_alpha:.6f}",
723
  "",
724
  f" {'─── RΔ°SK YΓ–NETΔ°MΔ° (ATR Γ— 1.5 | R:R 1:2)':<{W-2}}",
@@ -754,6 +890,8 @@ def predict_eurusd(horizon: int = 1):
754
  f" {'RMSE':<24}: {rmse:.7f}",
755
  f" {'YΓΆn Doğruluğu':<24}: {hit:.1f}%",
756
  f" {'Feature SayΔ±sΔ±':<24}: {len(avail)}",
 
 
757
  "",
758
  sep,
759
  " ⚠ YalnΔ±zca eğitim amaΓ§lΔ±dΔ±r. Finansal tavsiye değildir.",
@@ -766,6 +904,8 @@ def predict_eurusd(horizon: int = 1):
766
  fig = build_figure(
767
  df, pred_price, horizon, tp_sl,
768
  test_pred_abs, y_test_abs, best_alpha, mae,
 
 
769
  )
770
 
771
  return text, fig
@@ -882,13 +1022,13 @@ HEADER = """
882
  <div>
883
  <h1 style="margin:0 0 4px;font-size:1.45rem;font-weight:900;
884
  color:#E8EDF5;letter-spacing:.06em;font-family:monospace;">
885
- EUR/USD &nbsp;Β·&nbsp; RIDGE FORECAST ENGINE
886
  <span style="color:#00FFB2;font-size:.85rem;margin-left:10px;
887
  vertical-align:middle;letter-spacing:.12em;">ULTRA-PREMIUM</span>
888
  </h1>
889
  <p style="margin:0;color:#3A4A5E;font-size:.75rem;font-family:monospace;
890
  letter-spacing:.04em;">
891
- STACKED RIDGE Β· POLYNOMIAL INTERACTIONS Β· RIDGECV ALPHA Β· 75+ FEATURES Β· ATR TP/SL
892
  </p>
893
  </div>
894
  </div>
@@ -922,7 +1062,7 @@ HEADER = """
922
  # GRADIO UI
923
  # ══════════════════════════════════════════════════════════════════════════════
924
 
925
- with gr.Blocks(css=CSS, title="EUR/USD Ridge Forecast Engine") as app:
926
  gr.HTML(HEADER)
927
 
928
  with gr.Row(elem_classes="btn-row"):
 
1
  # ══════════════════════════════════════════════════════════════════════════════
2
+ # EUR/USD Β· ENSEMBLE FORECAST ENGINE Β· Ultra-Premium Edition v2
3
+ # Model: Ensemble Ridge + GBM Β· ATR-normalised target Β· Regime filter Β· Threshold
4
  # Forecast horizon: 1 or 2 days ahead
5
  # TP/SL: ATR-based risk management Β· Signal scoring Β· Backtest
6
  # Data: Yahoo Finance (yfinance)
 
15
  from sklearn.preprocessing import StandardScaler, PolynomialFeatures
16
  from sklearn.pipeline import Pipeline
17
  from sklearn.metrics import mean_absolute_error
18
+ from sklearn.ensemble import GradientBoostingRegressor
19
+ from sklearn.model_selection import TimeSeriesSplit
20
  import matplotlib
21
  matplotlib.use('Agg')
22
  import matplotlib.pyplot as plt
 
98
  return 100 - (100 / (1 + gain / (loss + 1e-10)))
99
 
100
 
101
+ def detect_regime(df: pd.DataFrame) -> pd.Series:
102
+ """
103
+ Market regime detection:
104
+ +1 = TREND (directional, EMA spread wide, ADX-proxy high)
105
+ 0 = NEUTRAL
106
+ -1 = RANGE (mean-reverting, BB squeeze, flat MAs)
107
+ """
108
+ close = df['Close']
109
+ ema8 = close.ewm(span=8, adjust=False).mean()
110
+ ema21 = close.ewm(span=21, adjust=False).mean()
111
+ ema55 = close.ewm(span=55, adjust=False).mean()
112
+
113
+ # Trend strength: normalised EMA spread
114
+ spread = (ema8 - ema55).abs() / (close + 1e-10)
115
+ spread_z = (spread - spread.rolling(40).mean()) / (spread.rolling(40).std() + 1e-10)
116
+
117
+ # Volatility regime: BB width z-score
118
+ mid = close.rolling(20).mean()
119
+ bb_w = (close.rolling(20).std() * 4) / (mid + 1e-10)
120
+ bb_z = (bb_w - bb_w.rolling(40).mean()) / (bb_w.rolling(40).std() + 1e-10)
121
+
122
+ regime = pd.Series(0, index=df.index)
123
+ regime[spread_z > 0.5] = 1 # trending
124
+ regime[bb_z < -0.5] = -1 # range / squeeze
125
+ return regime
126
+
127
+
128
+ def normalise_target_by_atr(y_logret: np.ndarray, atr_vals: np.ndarray) -> np.ndarray:
129
+ """
130
+ Divide log-returns by ATR to make the target volatility-scaled.
131
+ Model learns to predict 'how many ATRs will price move', not raw pips.
132
+ This prevents near-zero predictions when volatility is high.
133
+ """
134
+ atr_norm = atr_vals / (atr_vals.mean() + 1e-10) # relative ATR
135
+ return y_logret / (atr_norm + 1e-10)
136
+
137
+
138
  def create_features(data: pd.DataFrame) -> pd.DataFrame:
139
  """
140
  Premium feature set β€” 55+ engineered features designed to minimise MAE
 
257
  df['Volume_Ratio'] = 1.0
258
  df['OBV_Signal'] = 0.0
259
 
260
+ # ── Regime features ──────────────────────────────────────────────────────
261
+ df['Regime'] = detect_regime(df)
262
+ df['Regime_Trend'] = (df['Regime'] == 1).astype(float)
263
+ df['Regime_Range'] = (df['Regime'] == -1).astype(float)
264
+
265
+ # BB squeeze: width below 20th percentile of last 60 days
266
+ bb20 = df['BB_width_20'] if 'BB_width_20' in df.columns else (
267
+ df['Close'].rolling(20).std() * 4 / (df['Close'].rolling(20).mean() + 1e-10))
268
+ df['BB_Squeeze'] = (bb20 < bb20.rolling(60).quantile(0.20)).astype(float)
269
+
270
  # ── FIX 4: Direction Composite Signal (boosts directional accuracy) ─────
271
  # Encodes consensus of multiple indicators into a single direction score.
272
  # Positive β†’ bullish bias, Negative β†’ bearish bias.
 
348
  'Volume_Ratio', 'OBV_Signal',
349
  # Direction composite (FIX 4)
350
  'Direction_Score', 'Momentum_Consistency',
351
+ # Regime
352
+ 'Regime', 'Regime_Trend', 'Regime_Range', 'BB_Squeeze',
353
  ]
354
 
355
 
 
362
  def build_ridge_pipeline(alpha: float) -> Pipeline:
363
  """Ridge pipeline with degree-2 interactions (no bias terms in poly)."""
364
  return Pipeline([
365
+ ('scaler', StandardScaler()),
366
+ ('poly', PolynomialFeatures(degree=2, interaction_only=True,
367
+ include_bias=False)),
368
+ ('scaler2', StandardScaler(with_mean=False)),
369
+ ('ridge', Ridge(alpha=alpha, fit_intercept=True)),
370
  ])
371
 
372
 
373
  def select_best_alpha(X_tr, y_tr) -> float:
 
 
 
 
374
  alphas = np.logspace(-3, 4, 60)
375
  scaler = StandardScaler()
376
  Xs = scaler.fit_transform(X_tr)
 
379
  return float(rcv.alpha_)
380
 
381
 
382
+ # ══════════════════════════════════════════════════════════════════════════════
383
+ # ENSEMBLE MODEL: Ridge + Gradient Boosting
384
+ # ══════════════════════════════════════════════════════════════════════════════
385
+
386
+ def train_ensemble(df: pd.DataFrame, horizon: int = 1):
387
  """
388
+ Four upgrades over baseline:
389
+ A) ATR-normalised log-return target β†’ prevents near-zero predictions
390
+ B) Regime-conditioned training β†’ model sees market context
391
+ C) GBM secondary model β†’ captures nonlinear patterns
392
+ D) Ensemble blend (Ridge 55% + GBM 45%)
393
+ Final signal passes through a threshold filter: if predicted move
394
+ is < 0.3Γ— ATR the signal is tagged WEAK (no trade recommended).
395
  """
396
  avail = [c for c in FEATURE_COLS if c in df.columns]
397
  X = df[avail].values
398
  close = df['Close'].values
399
+ atr = df['ATR'].values if 'ATR' in df.columns else np.full(len(close), 1e-4)
400
 
401
+ # ── A. ATR-normalised log-return target ───────────────────────────────────
 
 
402
  y_logret = np.log(close[horizon:] / close[:len(close) - horizon])
403
+ atr_aligned = atr[:len(y_logret)]
404
+ y_norm = normalise_target_by_atr(y_logret, atr_aligned) # scaled target
405
 
406
+ X = X[:len(y_norm)]
407
+ close_aligned = close[:len(y_norm)]
408
+ atr_aligned = atr_aligned[:len(y_norm)]
409
 
410
+ split = -15
411
+ X_train, X_test = X[:split], X[split:]
412
+ y_train, y_norm_test = y_norm[:split], y_norm[split:]
413
+ y_raw_test = y_logret[len(y_logret)+split:]
414
  close_test = close_aligned[split:]
415
+ atr_test = atr_aligned[split:]
416
 
417
+ # ── B. Ridge on full training set ────────────────────────────────────────
418
  best_alpha = select_best_alpha(X_train, y_train)
419
+ ridge_pipe = build_ridge_pipeline(best_alpha)
420
+ ridge_pipe.fit(X_train, y_train)
421
+ ridge_pred_norm = ridge_pipe.predict(X_test)
422
+
423
+ # ── C. GBM on residuals (boosting approach) ───────────────────────────────
424
+ # Fit GBM on training residuals so it learns what Ridge misses
425
+ ridge_train_pred = ridge_pipe.predict(X_train)
426
+ residuals_train = y_train - ridge_train_pred
427
+
428
+ scaler_gbm = StandardScaler()
429
+ X_tr_sc = scaler_gbm.fit_transform(X_train)
430
+ X_te_sc = scaler_gbm.transform(X_test)
431
+
432
+ gbm = GradientBoostingRegressor(
433
+ n_estimators=120, max_depth=3, learning_rate=0.05,
434
+ subsample=0.8, min_samples_leaf=5, random_state=42,
435
+ )
436
+ gbm.fit(X_tr_sc, residuals_train)
437
+ gbm_correction = gbm.predict(X_te_sc)
438
+
439
+ # ── D. Ensemble blend ────────────────────────────────────────────────────
440
+ RIDGE_W = 0.55
441
+ GBM_W = 0.45
442
+ ensemble_norm = ridge_pred_norm * RIDGE_W + (ridge_pred_norm + gbm_correction) * GBM_W
443
+
444
+ # Convert normalised prediction back to log-return, then to price
445
+ atr_norm_test = atr_test / (atr_aligned.mean() + 1e-10)
446
+ logret_hat = ensemble_norm * atr_norm_test
447
+ pred_abs = close_test * np.exp(logret_hat)
448
+ y_test_abs = close_test * np.exp(y_raw_test)
449
+
450
+ mae = mean_absolute_error(y_test_abs, pred_abs)
451
 
452
+ return (ridge_pipe, gbm, scaler_gbm, atr_aligned.mean(),
453
+ pred_abs, y_test_abs, avail, best_alpha, mae,
454
+ RIDGE_W, GBM_W)
455
+
456
+
457
+ def predict_next_ensemble(ridge_pipe, gbm, scaler_gbm, atr_mean,
458
+ df: pd.DataFrame, avail: list,
459
+ horizon: int = 1,
460
+ ridge_w: float = 0.55,
461
+ gbm_w: float = 0.45) -> tuple:
462
+ """
463
+ Returns (pred_price, logret, signal_strength, threshold_passed).
464
+ signal_strength = |predicted move| / ATR (how many ATRs away)
465
+ threshold_passed = True if signal_strength >= 0.30
466
+ """
467
+ cp = float(df['Close'].iloc[-1])
468
+ atr = float(df['ATR'].iloc[-1]) if 'ATR' in df.columns else 1e-4
469
+ x = df[avail].iloc[-1].values.reshape(1, -1)
470
 
471
+ # Ridge prediction (normalised)
472
+ ridge_norm = float(ridge_pipe.predict(x)[0])
 
 
473
 
474
+ # GBM correction on residual
475
+ x_sc = scaler_gbm.transform(x)
476
+ gbm_corr = float(gbm.predict(x_sc)[0])
477
 
478
+ ensemble_norm = ridge_norm * ridge_w + (ridge_norm + gbm_corr) * gbm_w
479
 
480
+ # Denormalise
481
+ atr_norm = atr / (atr_mean + 1e-10)
482
+ logret = ensemble_norm * atr_norm * horizon
483
+ pred_price = cp * np.exp(logret)
484
 
485
+ # ── Sinyal eşiği ─────────────────────────────────────────────────────────
486
+ signal_strength = abs(pred_price - cp) / (atr + 1e-10)
487
+ THRESHOLD = 0.30 # minimum 0.30 ATR hareketi gerekiyor
488
+ threshold_passed = signal_strength >= THRESHOLD
489
+
490
+ return pred_price, logret, signal_strength, threshold_passed
 
 
491
 
492
 
493
  # ══════════════════════════════════════════════════════════════════════════════
 
572
 
573
 
574
  def build_figure(df, pred_price, horizon, tp_sl,
575
+ test_pred_abs, y_test_abs, best_alpha, mae,
576
+ regime_val=0, sig_strength=0.0, thr_passed=True) -> plt.Figure:
577
 
578
  plt.rcParams.update({
579
  'font.family': 'monospace',
 
626
  (i - 0.25, min(o, c)), 0.5, abs(c - o) + 1e-7,
627
  boxstyle="square,pad=0", linewidth=0, facecolor=color, alpha=0.75, zorder=3))
628
 
629
+ # ── Regime background shading ─────────────────────────────────────────────
630
+ if 'Regime' in tail.columns:
631
+ for i, (_, row) in enumerate(tail.iterrows()):
632
+ rv = int(row.get('Regime', 0))
633
+ if rv == 1: # trend
634
+ ax_price.axvspan(i-0.5, i+0.5, alpha=0.06, color=ACID, zorder=0)
635
+ elif rv == -1: # range
636
+ ax_price.axvspan(i-0.5, i+0.5, alpha=0.06, color=ACID3, zorder=0)
637
+
638
  # Close line overlay (phosphor glow)
639
  ax_price.plot(idx, tail['Close'].values, lw=1.6, color=ACID, zorder=5, alpha=0.9, label='Close')
640
 
 
663
  fontfamily='monospace',
664
  )
665
 
666
+ # Threshold / signal strength label
667
+ thr_color = GREEN if thr_passed else RED
668
+ thr_text = f"{'βœ…' if thr_passed else 'β›”'} Sinyal GΓΌcΓΌ: {sig_strength:.2f}Γ— ATR"
669
+ ax_price.text(0.01, 0.04, thr_text, transform=ax_price.transAxes,
670
+ fontsize=7.5, color=thr_color, fontweight='bold',
671
+ fontfamily='monospace', zorder=9,
672
+ bbox=dict(facecolor=PANEL, edgecolor=thr_color, alpha=0.7, pad=3))
673
+
674
  ax_price.legend(fontsize=7, framealpha=0.1, labelcolor=SUBTEXT,
675
  ncol=3, loc='upper left')
676
  ax_price.yaxis.set_major_formatter(mticker.FormatStrFormatter('%.4f'))
 
751
  dir_color = GREEN if tp_sl['direction'] == 'LONG' else WARM
752
 
753
  fig.text(0.055, 0.97,
754
+ "EUR/USD Β· ENSEMBLE FORECAST ENGINE Β· ULTRA-PREMIUM",
755
  fontsize=12, color=TEXT, fontweight='bold', va='top',
756
  fontfamily='monospace')
757
  fig.text(0.055, 0.955, dir_label,
 
786
  if len(df) < 40:
787
  return "❌ Yetersiz veri (min 40 satΔ±r gerekli).", None
788
 
789
+ # 3. Train Ensemble (Ridge + GBM) + backtest
790
+ (ridge_pipe, gbm, scaler_gbm, atr_mean,
791
+ test_pred_abs, y_test_abs, avail, best_alpha, mae,
792
+ ridge_w, gbm_w) = train_ensemble(df, horizon)
 
793
 
794
+ # 4. Predict next (ensemble)
795
  cp = float(df['Close'].iloc[-1])
796
  atr = float(df['ATR'].iloc[-1])
797
+ pred_price, logret_val, sig_strength, thr_passed = predict_next_ensemble(
798
+ ridge_pipe, gbm, scaler_gbm, atr_mean, df, avail, horizon, ridge_w, gbm_w)
799
+
800
+ # 4b. Regime
801
+ regime_val = int(df['Regime'].iloc[-1]) if 'Regime' in df.columns else 0
802
+ regime_lbl = {1: "TREND β–Ά", 0: "NΓ–TR β—†", -1: "RANGE β—€"}.get(regime_val, "?")
803
+
804
+ # 5. TP/SL
805
  tp_sl = compute_tp_sl(cp, pred_price, atr,
806
  atr_multiplier_sl=1.5, rr_ratio=2.0)
807
 
 
810
 
811
  # 7. Backtest metrics
812
  rmse = float(np.sqrt(np.mean((test_pred_abs - y_test_abs) ** 2)))
 
813
  close_vals = df['Close'].values
814
  n_test = len(test_pred_abs)
815
  entry_prices = close_vals[len(close_vals) - n_test - horizon :
 
842
  W = 54
843
  sep = "═" * W
844
 
845
+ thr_lbl = "βœ… AKTΔ°F SΔ°NYAL" if thr_passed else "β›” ZAYIF SΔ°NYAL (trade yok)"
846
  lines = [
847
  sep,
848
+ f" EUR/USD Β· ENSEMBLE FORECAST ENGINE Β· ULTRA-PREMIUM",
849
  f" {datetime.now().strftime('%d %b %Y %H:%M:%S')}",
850
  sep,
851
  "",
852
  f" {'MEVCUT FΔ°YAT':<24}: {cp:.5f}",
853
+ f" {f'+{horizon}G TAHMΔ°N (ENSEMBLE)':<24}: {pred_price:.5f} ({chg:+.3f}%)",
854
  f" {'YΓ–N':<24}: {tp_sl['direction']}",
855
+ f" {'REJΔ°M':<24}: {regime_lbl}",
856
+ f" {'SΔ°NYAL GÜCÜ (ATRΓ—)':<24}: {sig_strength:.2f}x β†’ {thr_lbl}",
857
+ f" {'LOG-RETURN TAHMΔ°N':<24}: {logret_val*100:+.4f}%",
858
  f" {'OPTΔ°MAL ALPHA (RidgeCV)':<24}: {best_alpha:.6f}",
859
  "",
860
  f" {'─── RΔ°SK YΓ–NETΔ°MΔ° (ATR Γ— 1.5 | R:R 1:2)':<{W-2}}",
 
890
  f" {'RMSE':<24}: {rmse:.7f}",
891
  f" {'YΓΆn Doğruluğu':<24}: {hit:.1f}%",
892
  f" {'Feature SayΔ±sΔ±':<24}: {len(avail)}",
893
+ f" {'Ensemble (Ridge/GBM)':<24}: {ridge_w*100:.0f}% / {gbm_w*100:.0f}%",
894
+ f" {'Eşik (ATRΓ—0.30)':<24}: {'GeΓ§ti βœ…' if thr_passed else 'GEΓ‡EMEDΔ° β›”'}",
895
  "",
896
  sep,
897
  " ⚠ YalnΔ±zca eğitim amaΓ§lΔ±dΔ±r. Finansal tavsiye değildir.",
 
904
  fig = build_figure(
905
  df, pred_price, horizon, tp_sl,
906
  test_pred_abs, y_test_abs, best_alpha, mae,
907
+ regime_val=regime_val, sig_strength=sig_strength,
908
+ thr_passed=thr_passed,
909
  )
910
 
911
  return text, fig
 
1022
  <div>
1023
  <h1 style="margin:0 0 4px;font-size:1.45rem;font-weight:900;
1024
  color:#E8EDF5;letter-spacing:.06em;font-family:monospace;">
1025
+ EUR/USD &nbsp;Β·&nbsp; ENSEMBLE FORECAST ENGINE
1026
  <span style="color:#00FFB2;font-size:.85rem;margin-left:10px;
1027
  vertical-align:middle;letter-spacing:.12em;">ULTRA-PREMIUM</span>
1028
  </h1>
1029
  <p style="margin:0;color:#3A4A5E;font-size:.75rem;font-family:monospace;
1030
  letter-spacing:.04em;">
1031
+ RIDGE+GBM ENSEMBLE Β· ATR-NORM TARGET Β· REGIME FILTER Β· SIGNAL THRESHOLD Β· 80+ FEATURES
1032
  </p>
1033
  </div>
1034
  </div>
 
1062
  # GRADIO UI
1063
  # ══════════════════════════════════════════════════════════════════════════════
1064
 
1065
+ with gr.Blocks(css=CSS, title="EUR/USD Ensemble Forecast Engine") as app:
1066
  gr.HTML(HEADER)
1067
 
1068
  with gr.Row(elem_classes="btn-row"):