GoshawkVortexAI commited on
Commit
a12c7e5
Β·
verified Β·
1 Parent(s): 150aaaa

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +61 -130
app.py CHANGED
@@ -257,6 +257,19 @@ def create_features(data: pd.DataFrame) -> pd.DataFrame:
257
  np.sign(df['LagRet_3'])
258
  ) / 3.0
259
 
 
 
 
 
 
 
 
 
 
 
 
 
 
260
  return df.dropna()
261
 
262
 
@@ -299,6 +312,10 @@ FEATURE_COLS = [
299
  'Volume_Ratio', 'OBV_Signal',
300
  # Direction composite (FIX 4)
301
  'Direction_Score', 'Momentum_Consistency',
 
 
 
 
302
  ]
303
 
304
 
@@ -322,13 +339,20 @@ def build_ridge_pipeline(alpha: float) -> Pipeline:
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)
 
 
 
 
 
330
  rcv = RidgeCV(alphas=alphas, cv=5, scoring='neg_mean_absolute_error')
331
- rcv.fit(Xs, y_tr)
332
  return float(rcv.alpha_)
333
 
334
 
@@ -336,15 +360,17 @@ 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)]
@@ -355,7 +381,7 @@ def train_ridge(df: pd.DataFrame, horizon: int = 1):
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
@@ -364,87 +390,33 @@ def train_ridge(df: pd.DataFrame, horizon: int = 1):
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,20 +482,6 @@ def signal_score(df: pd.DataFrame, direction: str) -> dict:
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
  # ══════════════════════════════════════════════════════════════════════════════
@@ -543,8 +501,7 @@ def _ax_style(ax, title="", ylabel=""):
543
 
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',
@@ -605,18 +562,6 @@ def build_figure(df, pred_price, horizon, tp_sl,
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),
@@ -753,10 +698,10 @@ def predict_eurusd(horizon: int = 1):
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])
@@ -764,12 +709,8 @@ def predict_eurusd(horizon: int = 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)))
@@ -816,6 +757,7 @@ def predict_eurusd(horizon: int = 1):
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}}",
821
  f" {'GΔ°RİŞ':<24}: {tp_sl['entry']:.5f}",
@@ -825,17 +767,7 @@ def predict_eurusd(horizon: int = 1):
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():
@@ -872,7 +804,6 @@ def predict_eurusd(horizon: int = 1):
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
 
257
  np.sign(df['LagRet_3'])
258
  ) / 3.0
259
 
260
+ # ── Horizon-aware features (2-day dynamics) ───────────────────────────────
261
+ # These explicitly capture 2-day structure to boost multi-day accuracy.
262
+ df['LagRet_1_2sum'] = df['LagRet_1'] + df['LagRet_2'] # 2-day return sum
263
+ df['LagRet_1_2diff'] = df['LagRet_1'] - df['LagRet_2'] # acceleration
264
+ df['Vol_2'] = df['Returns'].rolling(2).std() # 2-day volatility
265
+ df['ROC_2'] = df['Close'].pct_change(2) # 2-day ROC
266
+ df['SMA3_slope'] = df['SMA_3'].diff(2) # short MA 2-day slope
267
+ df['EMA3_slope'] = df['EMA_3'].diff(2) # short EMA 2-day slope
268
+ df['ATR_delta'] = df['ATR'].diff(1) # ATR change (vol expansion/contraction)
269
+ df['RSI14_delta'] = df['RSI_14'].diff(2) # RSI momentum 2-day
270
+ df['MACD_Hist_delta']= df['MACD_Hist'].diff(2) # MACD histogram acceleration
271
+ df['BB_pct20_delta'] = df['BB_pct_20'].diff(2) # BB position change
272
+
273
  return df.dropna()
274
 
275
 
 
312
  'Volume_Ratio', 'OBV_Signal',
313
  # Direction composite (FIX 4)
314
  'Direction_Score', 'Momentum_Consistency',
315
+ # Horizon-aware (2-day dynamics)
316
+ 'LagRet_1_2sum', 'LagRet_1_2diff', 'Vol_2', 'ROC_2',
317
+ 'SMA3_slope', 'EMA3_slope', 'ATR_delta',
318
+ 'RSI14_delta', 'MACD_Hist_delta', 'BB_pct20_delta',
319
  ]
320
 
321
 
 
339
  def select_best_alpha(X_tr, y_tr) -> float:
340
  """
341
  RidgeCV finds optimal alpha from a fine logarithmic grid.
342
+ FIXED: alpha is selected on poly-expanded features (same space as the
343
+ actual Ridge model), not on raw features. This eliminates alpha
344
+ scale mismatch that degraded 2-day accuracy.
345
  """
346
+ sc1 = StandardScaler()
347
+ poly = PolynomialFeatures(degree=2, interaction_only=True, include_bias=False)
348
+ sc2 = StandardScaler(with_mean=False)
349
+ Xp = sc2.fit_transform(poly.fit_transform(sc1.fit_transform(X_tr)))
350
+
351
+ # After poly expansion the feature count is ~n*(n-1)/2 β€” ridge needs
352
+ # higher alphas to stay regularised at that scale.
353
+ alphas = np.logspace(0, 6, 60)
354
  rcv = RidgeCV(alphas=alphas, cv=5, scoring='neg_mean_absolute_error')
355
+ rcv.fit(Xp, y_tr)
356
  return float(rcv.alpha_)
357
 
358
 
 
360
  """
361
  Trains the premium Ridge model.
362
  Target: log-return over `horizon` days (direction-aware).
363
+
364
+ FIXES applied:
365
+ - Log-return target correctly encodes h-day return (not 1-day)
366
+ - Residual bias correction: calibrates systematic over/under-shoot
367
+ - Returns pipeline, test predictions (absolute), y_test (absolute), avail_cols.
368
  """
369
  avail = [c for c in FEATURE_COLS if c in df.columns]
370
  X = df[avail].values
371
  close = df['Close'].values
372
 
373
+ # h-day log-return β€” model learns this directly, no horizon scaling needed
 
 
374
  y_logret = np.log(close[horizon:] / close[:len(close) - horizon])
375
 
376
  X = X[:len(y_logret)]
 
381
  y_train, y_logret_test = y_logret[:split], y_logret[split:]
382
  close_test = close_aligned[split:]
383
 
384
+ # Find best alpha on poly-expanded training data
385
  best_alpha = select_best_alpha(X_train, y_train)
386
 
387
  # Build & fit pipeline
 
390
 
391
  # Test predictions β†’ convert log-return back to absolute price
392
  logret_hat = pipe.predict(X_test)
393
+
394
+ # Residual bias correction: remove mean systematic bias from log-return predictions
395
+ # This improves directional accuracy without leaking future info (computed on train residuals)
396
+ train_preds = pipe.predict(X_train)
397
+ residual_bias = float(np.mean(y_train - train_preds)) # mean error on train set
398
+ logret_hat_corrected = logret_hat + residual_bias
399
+
400
+ pred_abs = close_test * np.exp(logret_hat_corrected)
401
  y_test_abs = close_test * np.exp(y_logret_test)
402
 
403
  mae = mean_absolute_error(y_test_abs, pred_abs)
404
 
405
+ return pipe, pred_abs, y_test_abs, avail, best_alpha, mae, residual_bias
406
 
407
 
408
  def predict_next_ridge(pipe, df: pd.DataFrame, avail: list,
409
+ horizon: int = 1, residual_bias: float = 0.0) -> float:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
410
  """
411
+ Predict absolute close price horizon days ahead using log-return.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
412
 
413
+ FIXED: Removed `* horizon` scaling β€” model already learns h-day log-return.
414
+ Applies residual_bias correction from training set for calibrated prediction.
415
+ """
416
+ cp = float(df['Close'].iloc[-1])
417
+ x = df[avail].iloc[-1].values.reshape(1, -1)
418
+ logret = float(pipe.predict(x)[0]) + residual_bias
419
+ return cp * np.exp(logret)
420
 
421
 
422
  # ══════════════════════════════════════════════════════════════════════════════
 
482
  return {'score': min(100, score), 'label': label, 'details': det}
483
 
484
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
485
  # ══════════════════════════════════════════════════════════════════════════════
486
  # CHART ENGINE β€” phosphor-green terminal aesthetic
487
  # ══════════════════════════════════════════════════════════════════════════════
 
501
 
502
 
503
  def build_figure(df, pred_price, horizon, tp_sl,
504
+ test_pred_abs, y_test_abs, best_alpha, mae) -> plt.Figure:
 
505
 
506
  plt.rcParams.update({
507
  'font.family': 'monospace',
 
562
  ax_price.axhline(tp_sl['sl'], color=RED, lw=1.4, ls='--', alpha=0.9, zorder=6)
563
  ax_price.axhline(cp, color=TEXT, lw=0.7, ls='-', alpha=0.35, zorder=4)
564
 
 
 
 
 
 
 
 
 
 
 
 
 
565
  r = len(idx) * 0.018
566
  for val, lbl, col in [
567
  (tp_sl['tp'], f"TP {tp_sl['tp']:.5f} (+{tp_sl['tp_pips']:.0f}p)", GREEN),
 
698
  return "❌ Yetersiz veri (min 40 satΔ±r gerekli).", None
699
 
700
  # 3. Train Ridge + backtest
701
+ pipe, test_pred_abs, y_test_abs, avail, best_alpha, mae, residual_bias = train_ridge(df, horizon)
702
 
703
  # 4. Predict next
704
+ pred_price = predict_next_ridge(pipe, df, avail, horizon, residual_bias)
705
 
706
  # 5. TP/SL
707
  cp = float(df['Close'].iloc[-1])
 
709
  tp_sl = compute_tp_sl(cp, pred_price, atr,
710
  atr_multiplier_sl=1.5, rr_ratio=2.0)
711
 
712
+ # 6. Signal score
713
+ sig = signal_score(df, tp_sl['direction'])
 
 
 
 
714
 
715
  # 7. Backtest metrics
716
  rmse = float(np.sqrt(np.mean((test_pred_abs - y_test_abs) ** 2)))
 
757
  f" {f'+{horizon}G TAHMΔ°N (RIDGE)':<24}: {pred_price:.5f} ({chg:+.3f}%)",
758
  f" {'YΓ–N':<24}: {tp_sl['direction']}",
759
  f" {'OPTΔ°MAL ALPHA (RidgeCV)':<24}: {best_alpha:.6f}",
760
+ f" {'BIAS DÜZELTME (log-ret)':<24}: {residual_bias:+.7f}",
761
  "",
762
  f" {'─── RΔ°SK YΓ–NETΔ°MΔ° (ATR Γ— 1.5 | R:R 1:2)':<{W-2}}",
763
  f" {'GΔ°RİŞ':<24}: {tp_sl['entry']:.5f}",
 
767
  f" {'RΔ°SK %':<24}: {tp_sl['risk_pct']:.3f}%",
768
  f" {'ATR-14':<24}: {atr:.5f}",
769
  "",
770
+ f" {'─── SΔ°NYAL GÜCÜ':<{W-2}}",
 
 
 
 
 
 
 
 
 
 
771
  f" {'SKOR':<24}: {sig['score']:.0f} / 100 β†’ {sig['label']}",
772
  ]
773
  for k, v in sig['details'].items():
 
804
  fig = build_figure(
805
  df, pred_price, horizon, tp_sl,
806
  test_pred_abs, y_test_abs, best_alpha, mae,
 
807
  )
808
 
809
  return text, fig