Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
| 1 |
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 2 |
-
# EUR/USD Β·
|
| 3 |
-
# Model:
|
| 4 |
# Forecast horizon: 1 or 2 days ahead
|
| 5 |
# TP/SL: ATR-based risk management Β· Signal scoring Β· Backtest
|
| 6 |
# Data: Yahoo Finance (yfinance)
|
|
@@ -15,8 +15,6 @@ 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 |
-
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,43 +96,6 @@ def compute_rsi(series: pd.Series, period: int = 14) -> pd.Series:
|
|
| 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,16 +218,6 @@ def create_features(data: pd.DataFrame) -> pd.DataFrame:
|
|
| 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,8 +299,6 @@ FEATURE_COLS = [
|
|
| 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,15 +311,19 @@ FEATURE_COLS = [
|
|
| 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',
|
| 366 |
-
('poly',
|
| 367 |
-
|
| 368 |
-
('scaler2', StandardScaler(with_mean=False)),
|
| 369 |
-
('ridge',
|
| 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,115 +332,119 @@ def select_best_alpha(X_tr, y_tr) -> float:
|
|
| 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 |
-
|
| 389 |
-
|
| 390 |
-
|
| 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 |
-
#
|
|
|
|
|
|
|
| 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(
|
| 407 |
-
close_aligned = close[:len(
|
| 408 |
-
atr_aligned = atr_aligned[:len(y_norm)]
|
| 409 |
|
| 410 |
-
split = -15
|
| 411 |
-
X_train, X_test = X[:split],
|
| 412 |
-
y_train,
|
| 413 |
-
y_raw_test = y_logret[len(y_logret)+split:]
|
| 414 |
close_test = close_aligned[split:]
|
| 415 |
-
atr_test = atr_aligned[split:]
|
| 416 |
|
| 417 |
-
#
|
| 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 |
-
#
|
| 440 |
-
|
| 441 |
-
|
| 442 |
-
ensemble_norm = ridge_pred_norm * RIDGE_W + (ridge_pred_norm + gbm_correction) * GBM_W
|
| 443 |
|
| 444 |
-
#
|
| 445 |
-
|
| 446 |
-
|
| 447 |
-
|
| 448 |
-
y_test_abs = close_test * np.exp(y_raw_test)
|
| 449 |
|
| 450 |
-
mae
|
| 451 |
|
| 452 |
-
return
|
| 453 |
-
pred_abs, y_test_abs, avail, best_alpha, mae,
|
| 454 |
-
RIDGE_W, GBM_W)
|
| 455 |
|
| 456 |
|
| 457 |
-
def
|
| 458 |
-
|
| 459 |
-
|
| 460 |
-
|
| 461 |
-
|
| 462 |
-
|
| 463 |
-
|
| 464 |
-
|
| 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 |
-
|
| 475 |
-
|
| 476 |
-
|
| 477 |
|
| 478 |
-
|
|
|
|
| 479 |
|
| 480 |
-
|
| 481 |
-
|
| 482 |
-
|
| 483 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 491 |
|
| 492 |
|
| 493 |
# βββββββββββββββββββββββββββββββββββββββββοΏ½οΏ½ββββββββββββββββββββββββββββββββββββ
|
|
@@ -553,6 +510,20 @@ def signal_score(df: pd.DataFrame, direction: str) -> dict:
|
|
| 553 |
return {'score': min(100, score), 'label': label, 'details': det}
|
| 554 |
|
| 555 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 556 |
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 557 |
# CHART ENGINE β phosphor-green terminal aesthetic
|
| 558 |
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
@@ -573,7 +544,7 @@ def _ax_style(ax, title="", ylabel=""):
|
|
| 573 |
|
| 574 |
def build_figure(df, pred_price, horizon, tp_sl,
|
| 575 |
test_pred_abs, y_test_abs, best_alpha, mae,
|
| 576 |
-
|
| 577 |
|
| 578 |
plt.rcParams.update({
|
| 579 |
'font.family': 'monospace',
|
|
@@ -626,15 +597,6 @@ def build_figure(df, pred_price, horizon, tp_sl,
|
|
| 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 |
|
|
@@ -643,6 +605,18 @@ def build_figure(df, pred_price, horizon, tp_sl,
|
|
| 643 |
ax_price.axhline(tp_sl['sl'], color=RED, lw=1.4, ls='--', alpha=0.9, zorder=6)
|
| 644 |
ax_price.axhline(cp, color=TEXT, lw=0.7, ls='-', alpha=0.35, zorder=4)
|
| 645 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 646 |
r = len(idx) * 0.018
|
| 647 |
for val, lbl, col in [
|
| 648 |
(tp_sl['tp'], f"TP {tp_sl['tp']:.5f} (+{tp_sl['tp_pips']:.0f}p)", GREEN),
|
|
@@ -663,14 +637,6 @@ def build_figure(df, pred_price, horizon, tp_sl,
|
|
| 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,7 +717,7 @@ def build_figure(df, pred_price, horizon, tp_sl,
|
|
| 751 |
dir_color = GREEN if tp_sl['direction'] == 'LONG' else WARM
|
| 752 |
|
| 753 |
fig.text(0.055, 0.97,
|
| 754 |
-
"EUR/USD Β·
|
| 755 |
fontsize=12, color=TEXT, fontweight='bold', va='top',
|
| 756 |
fontfamily='monospace')
|
| 757 |
fig.text(0.055, 0.955, dir_label,
|
|
@@ -786,30 +752,28 @@ def predict_eurusd(horizon: int = 1):
|
|
| 786 |
if len(df) < 40:
|
| 787 |
return "β Yetersiz veri (min 40 satΔ±r gerekli).", None
|
| 788 |
|
| 789 |
-
# 3. Train
|
| 790 |
-
|
| 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 |
-
#
|
| 801 |
-
|
| 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 |
|
| 808 |
-
#
|
| 809 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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,19 +806,15 @@ def predict_eurusd(horizon: int = 1):
|
|
| 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 Β·
|
| 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 (
|
| 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}}",
|
|
@@ -865,7 +825,17 @@ def predict_eurusd(horizon: int = 1):
|
|
| 865 |
f" {'RΔ°SK %':<24}: {tp_sl['risk_pct']:.3f}%",
|
| 866 |
f" {'ATR-14':<24}: {atr:.5f}",
|
| 867 |
"",
|
| 868 |
-
f" {'βββ
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 869 |
f" {'SKOR':<24}: {sig['score']:.0f} / 100 β {sig['label']}",
|
| 870 |
]
|
| 871 |
for k, v in sig['details'].items():
|
|
@@ -890,8 +860,6 @@ def predict_eurusd(horizon: int = 1):
|
|
| 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,8 +872,7 @@ def predict_eurusd(horizon: int = 1):
|
|
| 904 |
fig = build_figure(
|
| 905 |
df, pred_price, horizon, tp_sl,
|
| 906 |
test_pred_abs, y_test_abs, best_alpha, mae,
|
| 907 |
-
|
| 908 |
-
thr_passed=thr_passed,
|
| 909 |
)
|
| 910 |
|
| 911 |
return text, fig
|
|
@@ -1022,13 +989,13 @@ HEADER = """
|
|
| 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 Β·
|
| 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
|
| 1032 |
</p>
|
| 1033 |
</div>
|
| 1034 |
</div>
|
|
@@ -1062,7 +1029,7 @@ HEADER = """
|
|
| 1062 |
# GRADIO UI
|
| 1063 |
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 1064 |
|
| 1065 |
-
with gr.Blocks(css=CSS, title="EUR/USD
|
| 1066 |
gr.HTML(HEADER)
|
| 1067 |
|
| 1068 |
with gr.Row(elem_classes="btn-row"):
|
|
|
|
| 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 |
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 |
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 |
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 |
'Volume_Ratio', 'OBV_Signal',
|
| 300 |
# Direction composite (FIX 4)
|
| 301 |
'Direction_Score', 'Momentum_Consistency',
|
|
|
|
|
|
|
| 302 |
]
|
| 303 |
|
| 304 |
|
|
|
|
| 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 |
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 |
+
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 386 |
+
# FIBONACCI LEVELS (son swing ΓΌzerinden)
|
| 387 |
+
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 388 |
|
| 389 |
+
FIB_RATIOS = [0.0, 0.236, 0.382, 0.500, 0.618, 0.786, 1.0]
|
| 390 |
+
FIB_COLORS = ['#FFB800', '#FF6B35', '#7B61FF', '#00D4FF', '#00FFB2', '#E8EDF5', '#FFB800']
|
| 391 |
|
| 392 |
+
def compute_fibonacci_levels(df: pd.DataFrame, lookback: int = 55) -> dict:
|
| 393 |
+
"""Son `lookback` barda swing high/low bulur, Fibonacci retracement seviyeleri dΓΆner."""
|
| 394 |
+
recent = df.tail(lookback)
|
| 395 |
+
swing_high = float(recent['High'].max())
|
| 396 |
+
swing_low = float(recent['Low'].min())
|
| 397 |
+
diff = swing_high - swing_low
|
| 398 |
+
levels = {f"Fib {int(r*100)}%": swing_high - diff * r for r in FIB_RATIOS}
|
| 399 |
+
levels['_high'] = swing_high
|
| 400 |
+
levels['_low'] = swing_low
|
| 401 |
+
return levels
|
| 402 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 403 |
|
| 404 |
+
def fibonacci_direction_signal(cp: float, fib: dict) -> dict:
|
| 405 |
+
"""
|
| 406 |
+
FiyatΔ±n hangi Fibonacci bΓΆlgesinde olduΔunu tespit eder.
|
| 407 |
+
Destek/direnΓ§ yakΔ±nlΔ±ΔΔ±na gΓΆre yΓΆn skoru (+bias) ΓΌretir.
|
| 408 |
+
"""
|
| 409 |
+
high, low = fib['_high'], fib['_low']
|
| 410 |
+
diff = high - low
|
| 411 |
+
if diff < 1e-10:
|
| 412 |
+
return {'fib_score': 0, 'fib_zone': 'N/A', 'fib_bias': 'NΓTR', 'fib_nearest': 0.0}
|
| 413 |
+
|
| 414 |
+
# Normalize: fiyatΔ±n retracement iΓ§indeki yΓΌzdesi (0=low, 1=high)
|
| 415 |
+
pos = (cp - low) / diff # 0β1 arasΔ±
|
| 416 |
+
|
| 417 |
+
# Hangi seviyeye en yakΔ±n?
|
| 418 |
+
nearest_r = min(FIB_RATIOS, key=lambda r: abs(pos - (1 - r)))
|
| 419 |
+
nearest_price = high - diff * nearest_r
|
| 420 |
+
dist_pct = abs(cp - nearest_price) / diff * 100
|
| 421 |
+
|
| 422 |
+
# YΓΆn bias:
|
| 423 |
+
# Fiyat 0.618 altΔ±nda β destek bΓΆlgesi β LONG bias
|
| 424 |
+
# Fiyat 0.382 ΓΌstΓΌnde β direnΓ§ bΓΆlgesi β SHORT bias
|
| 425 |
+
# %50 bΓΆlgesi nΓΆtr
|
| 426 |
+
if pos < 0.382: # Fiyat altta, gΓΌΓ§lΓΌ destek
|
| 427 |
+
fib_score = +25
|
| 428 |
+
fib_bias = "LONG β² (Destek)"
|
| 429 |
+
elif pos < 0.50: # Alt-orta bΓΆlge
|
| 430 |
+
fib_score = +12
|
| 431 |
+
fib_bias = "LONG β² (ZayΔ±f Destek)"
|
| 432 |
+
elif pos > 0.618: # Fiyat ΓΌstte, gΓΌΓ§lΓΌ direnΓ§
|
| 433 |
+
fib_score = -25
|
| 434 |
+
fib_bias = "SHORT βΌ (DirenΓ§)"
|
| 435 |
+
elif pos > 0.50: # Γst-orta bΓΆlge
|
| 436 |
+
fib_score = -12
|
| 437 |
+
fib_bias = "SHORT βΌ (ZayΔ±f DirenΓ§)"
|
| 438 |
+
else:
|
| 439 |
+
fib_score = 0
|
| 440 |
+
fib_bias = "NΓTR (%50)"
|
| 441 |
+
|
| 442 |
+
return {
|
| 443 |
+
'fib_score': fib_score,
|
| 444 |
+
'fib_zone': f"{pos*100:.1f}% (Fib {int(nearest_r*100)}%'e yakΔ±n, Ξ{dist_pct:.1f}%)",
|
| 445 |
+
'fib_bias': fib_bias,
|
| 446 |
+
'fib_nearest': nearest_price,
|
| 447 |
+
}
|
| 448 |
|
| 449 |
|
| 450 |
# βββββββββββββββββββββββββββββββββββββββββοΏ½οΏ½ββββββββββββββββββββββββββββββββββββ
|
|
|
|
| 510 |
return {'score': min(100, score), 'label': label, 'details': det}
|
| 511 |
|
| 512 |
|
| 513 |
+
def signal_score_with_fib(df: pd.DataFrame, direction: str, fib_signal: dict) -> dict:
|
| 514 |
+
"""signal_score + Fibonacci yΓΆn skoru."""
|
| 515 |
+
base = signal_score(df, direction)
|
| 516 |
+
fs = fib_signal['fib_score']
|
| 517 |
+
aligned = (direction == "LONG" and fs > 0) or (direction == "SHORT" and fs < 0)
|
| 518 |
+
contrib = abs(fs) if aligned else -(abs(fs) // 2)
|
| 519 |
+
new_score = int(np.clip(base['score'] + contrib, 0, 100))
|
| 520 |
+
label = "GΓΓLΓ β
" if new_score >= 75 else ("ORTA β οΈ" if new_score >= 45 else "ZAYIF β")
|
| 521 |
+
base['details']['Fibonacci'] = f"{fib_signal['fib_bias']} β {'+' if contrib >= 0 else ''}{contrib}p"
|
| 522 |
+
base['score'] = new_score
|
| 523 |
+
base['label'] = label
|
| 524 |
+
return base
|
| 525 |
+
|
| 526 |
+
|
| 527 |
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 528 |
# CHART ENGINE β phosphor-green terminal aesthetic
|
| 529 |
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
|
|
|
| 544 |
|
| 545 |
def build_figure(df, pred_price, horizon, tp_sl,
|
| 546 |
test_pred_abs, y_test_abs, best_alpha, mae,
|
| 547 |
+
fib_levels: dict = None) -> plt.Figure:
|
| 548 |
|
| 549 |
plt.rcParams.update({
|
| 550 |
'font.family': 'monospace',
|
|
|
|
| 597 |
(i - 0.25, min(o, c)), 0.5, abs(c - o) + 1e-7,
|
| 598 |
boxstyle="square,pad=0", linewidth=0, facecolor=color, alpha=0.75, zorder=3))
|
| 599 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 600 |
# Close line overlay (phosphor glow)
|
| 601 |
ax_price.plot(idx, tail['Close'].values, lw=1.6, color=ACID, zorder=5, alpha=0.9, label='Close')
|
| 602 |
|
|
|
|
| 605 |
ax_price.axhline(tp_sl['sl'], color=RED, lw=1.4, ls='--', alpha=0.9, zorder=6)
|
| 606 |
ax_price.axhline(cp, color=TEXT, lw=0.7, ls='-', alpha=0.35, zorder=4)
|
| 607 |
|
| 608 |
+
# ββ FIBONACCI levels ββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 609 |
+
if fib_levels:
|
| 610 |
+
fib_draw = [(r, c) for r, c in zip(FIB_RATIOS, FIB_COLORS)]
|
| 611 |
+
for ratio, col in fib_draw:
|
| 612 |
+
key = f"Fib {int(ratio*100)}%"
|
| 613 |
+
if key in fib_levels:
|
| 614 |
+
fval = fib_levels[key]
|
| 615 |
+
ax_price.axhline(fval, color=col, lw=0.7, ls=':', alpha=0.55, zorder=3)
|
| 616 |
+
ax_price.text(0.5, fval, f" {key} {fval:.5f}",
|
| 617 |
+
color=col, fontsize=6.5, va='bottom', alpha=0.75,
|
| 618 |
+
fontfamily='monospace', transform=ax_price.get_yaxis_transform())
|
| 619 |
+
|
| 620 |
r = len(idx) * 0.018
|
| 621 |
for val, lbl, col in [
|
| 622 |
(tp_sl['tp'], f"TP {tp_sl['tp']:.5f} (+{tp_sl['tp_pips']:.0f}p)", GREEN),
|
|
|
|
| 637 |
fontfamily='monospace',
|
| 638 |
)
|
| 639 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 640 |
ax_price.legend(fontsize=7, framealpha=0.1, labelcolor=SUBTEXT,
|
| 641 |
ncol=3, loc='upper left')
|
| 642 |
ax_price.yaxis.set_major_formatter(mticker.FormatStrFormatter('%.4f'))
|
|
|
|
| 717 |
dir_color = GREEN if tp_sl['direction'] == 'LONG' else WARM
|
| 718 |
|
| 719 |
fig.text(0.055, 0.97,
|
| 720 |
+
"EUR/USD Β· RIDGE FORECAST ENGINE Β· ULTRA-PREMIUM",
|
| 721 |
fontsize=12, color=TEXT, fontweight='bold', va='top',
|
| 722 |
fontfamily='monospace')
|
| 723 |
fig.text(0.055, 0.955, dir_label,
|
|
|
|
| 752 |
if len(df) < 40:
|
| 753 |
return "β Yetersiz veri (min 40 satΔ±r gerekli).", None
|
| 754 |
|
| 755 |
+
# 3. Train Ridge + backtest
|
| 756 |
+
pipe, test_pred_abs, y_test_abs, avail, best_alpha, mae = train_ridge(df, horizon)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 757 |
|
| 758 |
+
# 4. Predict next
|
| 759 |
+
pred_price = predict_next_ridge(pipe, df, avail, horizon)
|
|
|
|
| 760 |
|
| 761 |
# 5. TP/SL
|
| 762 |
+
cp = float(df['Close'].iloc[-1])
|
| 763 |
+
atr = float(df['ATR'].iloc[-1])
|
| 764 |
tp_sl = compute_tp_sl(cp, pred_price, atr,
|
| 765 |
atr_multiplier_sl=1.5, rr_ratio=2.0)
|
| 766 |
|
| 767 |
+
# 5b. Fibonacci seviyeleri & yΓΆn sinyali
|
| 768 |
+
fib = compute_fibonacci_levels(df, lookback=55)
|
| 769 |
+
fib_signal = fibonacci_direction_signal(cp, fib)
|
| 770 |
+
|
| 771 |
+
# 6. Signal score (fibonacci dahil)
|
| 772 |
+
sig = signal_score_with_fib(df, tp_sl['direction'], fib_signal)
|
| 773 |
|
| 774 |
# 7. Backtest metrics
|
| 775 |
rmse = float(np.sqrt(np.mean((test_pred_abs - y_test_abs) ** 2)))
|
| 776 |
+
# FIX 3: Correct directional accuracy β compare pred vs entry price
|
| 777 |
close_vals = df['Close'].values
|
| 778 |
n_test = len(test_pred_abs)
|
| 779 |
entry_prices = close_vals[len(close_vals) - n_test - horizon :
|
|
|
|
| 806 |
W = 54
|
| 807 |
sep = "β" * W
|
| 808 |
|
|
|
|
| 809 |
lines = [
|
| 810 |
sep,
|
| 811 |
+
f" EUR/USD Β· RIDGE FORECAST ENGINE Β· ULTRA-PREMIUM",
|
| 812 |
f" {datetime.now().strftime('%d %b %Y %H:%M:%S')}",
|
| 813 |
sep,
|
| 814 |
"",
|
| 815 |
f" {'MEVCUT FΔ°YAT':<24}: {cp:.5f}",
|
| 816 |
+
f" {f'+{horizon}G TAHMΔ°N (RIDGE)':<24}: {pred_price:.5f} ({chg:+.3f}%)",
|
| 817 |
f" {'YΓN':<24}: {tp_sl['direction']}",
|
|
|
|
|
|
|
|
|
|
| 818 |
f" {'OPTΔ°MAL ALPHA (RidgeCV)':<24}: {best_alpha:.6f}",
|
| 819 |
"",
|
| 820 |
f" {'βββ RΔ°SK YΓNETΔ°MΔ° (ATR Γ 1.5 | R:R 1:2)':<{W-2}}",
|
|
|
|
| 825 |
f" {'RΔ°SK %':<24}: {tp_sl['risk_pct']:.3f}%",
|
| 826 |
f" {'ATR-14':<24}: {atr:.5f}",
|
| 827 |
"",
|
| 828 |
+
f" {'βββ FΔ°BONACCΔ° SEVΔ°YELERΔ° (55 Bar)':<{W-2}}",
|
| 829 |
+
f" {'Swing High':<24}: {fib['_high']:.5f}",
|
| 830 |
+
f" {'Swing Low':<24}: {fib['_low']:.5f}",
|
| 831 |
+
f" {'Fiyat Pozisyonu':<24}: {fib_signal['fib_zone']}",
|
| 832 |
+
f" {'Fibonacci Bias':<24}: {fib_signal['fib_bias']}",
|
| 833 |
+
] + [
|
| 834 |
+
f" {' Fib ' + k.split()[1]:<24}: {v:.5f}"
|
| 835 |
+
for k, v in fib.items() if k.startswith('Fib')
|
| 836 |
+
] + [
|
| 837 |
+
"",
|
| 838 |
+
f" {'βββ SΔ°NYAL GΓCΓ (Fibonacci Dahil)':<{W-2}}",
|
| 839 |
f" {'SKOR':<24}: {sig['score']:.0f} / 100 β {sig['label']}",
|
| 840 |
]
|
| 841 |
for k, v in sig['details'].items():
|
|
|
|
| 860 |
f" {'RMSE':<24}: {rmse:.7f}",
|
| 861 |
f" {'YΓΆn DoΔruluΔu':<24}: {hit:.1f}%",
|
| 862 |
f" {'Feature SayΔ±sΔ±':<24}: {len(avail)}",
|
|
|
|
|
|
|
| 863 |
"",
|
| 864 |
sep,
|
| 865 |
" β YalnΔ±zca eΔitim amaΓ§lΔ±dΔ±r. Finansal tavsiye deΔildir.",
|
|
|
|
| 872 |
fig = build_figure(
|
| 873 |
df, pred_price, horizon, tp_sl,
|
| 874 |
test_pred_abs, y_test_abs, best_alpha, mae,
|
| 875 |
+
fib_levels=fib,
|
|
|
|
| 876 |
)
|
| 877 |
|
| 878 |
return text, fig
|
|
|
|
| 989 |
<div>
|
| 990 |
<h1 style="margin:0 0 4px;font-size:1.45rem;font-weight:900;
|
| 991 |
color:#E8EDF5;letter-spacing:.06em;font-family:monospace;">
|
| 992 |
+
EUR/USD Β· RIDGE FORECAST ENGINE
|
| 993 |
<span style="color:#00FFB2;font-size:.85rem;margin-left:10px;
|
| 994 |
vertical-align:middle;letter-spacing:.12em;">ULTRA-PREMIUM</span>
|
| 995 |
</h1>
|
| 996 |
<p style="margin:0;color:#3A4A5E;font-size:.75rem;font-family:monospace;
|
| 997 |
letter-spacing:.04em;">
|
| 998 |
+
STACKED RIDGE Β· POLYNOMIAL INTERACTIONS Β· RIDGECV ALPHA Β· 75+ FEATURES Β· ATR TP/SL
|
| 999 |
</p>
|
| 1000 |
</div>
|
| 1001 |
</div>
|
|
|
|
| 1029 |
# GRADIO UI
|
| 1030 |
# ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 1031 |
|
| 1032 |
+
with gr.Blocks(css=CSS, title="EUR/USD Ridge Forecast Engine") as app:
|
| 1033 |
gr.HTML(HEADER)
|
| 1034 |
|
| 1035 |
with gr.Row(elem_classes="btn-row"):
|