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

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +70 -19
app.py CHANGED
@@ -218,6 +218,45 @@ def create_features(data: pd.DataFrame) -> pd.DataFrame:
218
  df['Volume_Ratio'] = 1.0
219
  df['OBV_Signal'] = 0.0
220
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
221
  return df.dropna()
222
 
223
 
@@ -258,6 +297,8 @@ FEATURE_COLS = [
258
  'Range_Pct5', 'Range_Pct20',
259
  # Volume
260
  'Volume_Ratio', 'OBV_Signal',
 
 
261
  ]
262
 
263
 
@@ -294,25 +335,24 @@ def select_best_alpha(X_tr, y_tr) -> float:
294
  def train_ridge(df: pd.DataFrame, horizon: int = 1):
295
  """
296
  Trains the premium Ridge model.
297
- Target: price delta after `horizon` days.
298
  Returns pipeline, test predictions (absolute), y_test (absolute), avail_cols.
299
  """
300
  avail = [c for c in FEATURE_COLS if c in df.columns]
301
  X = df[avail].values
302
  close = df['Close'].values
303
 
304
- # Build delta target aligned to horizon
305
- y_delta = np.zeros(len(close) - horizon)
306
- for h in range(1, horizon + 1):
307
- y_delta += close[h:len(close) - horizon + h] - close[:len(close) - horizon]
308
- y_delta /= horizon # average delta per day (smoother target)
309
 
310
- X = X[:len(y_delta)]
311
- close_aligned = close[:len(y_delta)]
312
 
313
  split = -15 # 15-day test window for robust backtest
314
  X_train, X_test = X[:split], X[split:]
315
- y_train, y_delta_test = y_delta[:split], y_delta[split:]
316
  close_test = close_aligned[split:]
317
 
318
  # Find best alpha on training data only
@@ -322,10 +362,10 @@ def train_ridge(df: pd.DataFrame, horizon: int = 1):
322
  pipe = build_ridge_pipeline(best_alpha)
323
  pipe.fit(X_train, y_train)
324
 
325
- # Test predictions β†’ absolute price
326
- delta_hat = pipe.predict(X_test)
327
- pred_abs = close_test + delta_hat
328
- y_test_abs = close_test + y_delta_test
329
 
330
  mae = mean_absolute_error(y_test_abs, pred_abs)
331
 
@@ -334,11 +374,12 @@ def train_ridge(df: pd.DataFrame, horizon: int = 1):
334
 
335
  def predict_next_ridge(pipe, df: pd.DataFrame, avail: list,
336
  horizon: int = 1) -> float:
337
- """Predict absolute close price horizon days ahead."""
338
  cp = float(df['Close'].iloc[-1])
339
  x = df[avail].iloc[-1].values.reshape(1, -1)
340
- delta = float(pipe.predict(x)[0])
341
- return cp + delta * horizon
 
342
 
343
 
344
  # ══════════════════════════════════════════════════════════════════════════════
@@ -636,9 +677,19 @@ def predict_eurusd(horizon: int = 1):
636
 
637
  # 7. Backtest metrics
638
  rmse = float(np.sqrt(np.mean((test_pred_abs - y_test_abs) ** 2)))
639
- hit = float(np.mean(
640
- np.sign(np.diff(test_pred_abs)) == np.sign(np.diff(y_test_abs))
641
- )) * 100
 
 
 
 
 
 
 
 
 
 
642
 
643
  # 8. Indicators
644
  rsi = float(df['RSI_14'].iloc[-1])
 
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.
224
+ direction_score = pd.Series(0.0, index=df.index)
225
+
226
+ # RSI signal: oversold=bullish, overbought=bearish
227
+ if 'RSI_14' in df.columns:
228
+ direction_score += (50 - df['RSI_14']) / 50 # +1 at RSI=0, -1 at RSI=100
229
+
230
+ # MACD histogram direction
231
+ if 'MACD_Hist' in df.columns:
232
+ direction_score += np.sign(df['MACD_Hist'])
233
+
234
+ # Stoch cross: K above D = bullish
235
+ if 'Stoch_Cross' in df.columns:
236
+ direction_score += np.sign(df['Stoch_Cross'])
237
+
238
+ # Price vs SMA21
239
+ if 'Dist_SMA21' in df.columns:
240
+ direction_score += np.sign(df['Dist_SMA21'])
241
+
242
+ # EMA cross
243
+ if 'EMA3_EMA21_Cross' in df.columns:
244
+ direction_score += np.sign(df['EMA3_EMA21_Cross'])
245
+
246
+ # ROC momentum
247
+ if 'ROC_5' in df.columns:
248
+ direction_score += np.sign(df['ROC_5'])
249
+
250
+ df['Direction_Score'] = direction_score / 6.0 # normalise to [-1, +1]
251
+
252
+ # Momentum consistency: are recent returns all same sign?
253
+ if 'LagRet_1' in df.columns and 'LagRet_2' in df.columns and 'LagRet_3' in df.columns:
254
+ df['Momentum_Consistency'] = (
255
+ np.sign(df['LagRet_1']) +
256
+ np.sign(df['LagRet_2']) +
257
+ np.sign(df['LagRet_3'])
258
+ ) / 3.0
259
+
260
  return df.dropna()
261
 
262
 
 
297
  'Range_Pct5', 'Range_Pct20',
298
  # Volume
299
  'Volume_Ratio', 'OBV_Signal',
300
+ # Direction composite (FIX 4)
301
+ 'Direction_Score', 'Momentum_Consistency',
302
  ]
303
 
304
 
 
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
 
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
 
 
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
  # ══════════════════════════════════════════════════════════════════════════════
 
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 :
684
+ len(close_vals) - horizon]
685
+ if len(entry_prices) == n_test:
686
+ pred_dirs = np.sign(test_pred_abs - entry_prices)
687
+ actual_dirs = np.sign(y_test_abs - entry_prices)
688
+ hit = float(np.mean(pred_dirs == actual_dirs)) * 100
689
+ else:
690
+ hit = float(np.mean(
691
+ np.sign(np.diff(test_pred_abs)) == np.sign(np.diff(y_test_abs))
692
+ )) * 100
693
 
694
  # 8. Indicators
695
  rsi = float(df['RSI_14'].iloc[-1])