Spaces:
Sleeping
Sleeping
Update backtest_engine.py
Browse files- backtest_engine.py +67 -59
backtest_engine.py
CHANGED
|
@@ -285,68 +285,42 @@ class HeavyDutyBacktester:
|
|
| 285 |
l = df["low"].astype(np.float64)
|
| 286 |
v = df["volume"].astype(np.float64) if "volume" in df.columns else pd.Series(np.zeros(len(df)), index=df.index)
|
| 287 |
|
| 288 |
-
#
|
| 289 |
-
|
| 290 |
-
|
| 291 |
-
|
| 292 |
-
|
| 293 |
-
|
| 294 |
-
|
| 295 |
-
try:
|
| 296 |
-
df['MFI'] = ta.mfi(h, l, c, v, length=14).fillna(50)
|
| 297 |
-
except:
|
| 298 |
-
df['MFI'] = 50.0
|
| 299 |
-
|
| 300 |
-
# EMAs
|
| 301 |
-
for span in [9, 20, 21, 50, 200]:
|
| 302 |
-
df[f"ema{span}"] = c.ewm(span=span, adjust=False).mean()
|
| 303 |
-
|
| 304 |
-
# BBANDS
|
| 305 |
-
if len(df) < 30:
|
| 306 |
-
df["lower_bb"] = c
|
| 307 |
-
df["upper_bb"] = c
|
| 308 |
-
df["bb_width"] = 0.0
|
| 309 |
-
df["bb_pct"] = 0.5
|
| 310 |
-
else:
|
| 311 |
-
bb = ta.bbands(c, length=20, std=2.0)
|
| 312 |
-
if bb is None or (isinstance(bb, pd.DataFrame) and bb.shape[1] < 3):
|
| 313 |
-
lower, upper, width, pct = self._safe_bbands(c, 20, 2.0)
|
| 314 |
-
df["lower_bb"], df["upper_bb"], df["bb_width"], df["bb_pct"] = lower, upper, width, pct
|
| 315 |
-
else:
|
| 316 |
-
if isinstance(bb, pd.DataFrame):
|
| 317 |
-
col_lower = [x for x in bb.columns if "BBL" in x]
|
| 318 |
-
col_upper = [x for x in bb.columns if "BBU" in x]
|
| 319 |
-
col_width = [x for x in bb.columns if "BBB" in x]
|
| 320 |
-
col_pct = [x for x in bb.columns if "BBP" in x]
|
| 321 |
-
if col_lower and col_upper:
|
| 322 |
-
df["lower_bb"] = bb[col_lower[0]]
|
| 323 |
-
df["upper_bb"] = bb[col_upper[0]]
|
| 324 |
-
else:
|
| 325 |
-
lower, upper, width, pct = self._safe_bbands(c, 20, 2.0)
|
| 326 |
-
df["lower_bb"], df["upper_bb"] = lower, upper
|
| 327 |
-
df["bb_width"] = bb[col_width[0]] if col_width else (df["upper_bb"] - df["lower_bb"]) / (c.abs() + 1e-12)
|
| 328 |
-
df["bb_pct"] = bb[col_pct[0]] if col_pct else (c - df["lower_bb"]) / ((df["upper_bb"] - df["lower_bb"]) + 1e-12)
|
| 329 |
-
else:
|
| 330 |
-
lower, upper, width, pct = self._safe_bbands(c, 20, 2.0)
|
| 331 |
-
df["lower_bb"], df["upper_bb"], df["bb_width"], df["bb_pct"] = lower, upper, width, pct
|
| 332 |
|
| 333 |
-
#
|
| 334 |
-
|
| 335 |
-
|
| 336 |
-
df["MACD"] = macd.iloc[:, 0]
|
| 337 |
-
df["MACD_h"] = macd.iloc[:, 1]
|
| 338 |
-
df["MACD_s"] = macd.iloc[:, 2]
|
| 339 |
-
else:
|
| 340 |
-
df["MACD"] = 0.0
|
| 341 |
-
df["MACD_h"] = 0.0
|
| 342 |
-
df["MACD_s"] = 0.0
|
| 343 |
|
| 344 |
-
#
|
|
|
|
| 345 |
df["RSI"] = ta.rsi(c, length=14).fillna(50)
|
|
|
|
|
|
|
| 346 |
df["ATR"] = ta.atr(h, l, c, length=14).fillna(0)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 347 |
|
|
|
|
| 348 |
adx_df = ta.adx(h, l, c, length=14)
|
| 349 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 350 |
|
| 351 |
try:
|
| 352 |
df["CHOP"] = ta.chop(h, l, c, length=14).fillna(50)
|
|
@@ -358,19 +332,53 @@ class HeavyDutyBacktester:
|
|
| 358 |
except:
|
| 359 |
df["vwap"] = c
|
| 360 |
|
| 361 |
-
# ✅ FIX: Add CCI (Titan expects features like 5m_CCI)
|
| 362 |
try:
|
| 363 |
df["CCI"] = ta.cci(h, l, c, length=20).fillna(0)
|
| 364 |
except:
|
| 365 |
df["CCI"] = 0.0
|
| 366 |
|
| 367 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
| 368 |
df["EMA_9_dist"] = (c / (df["ema9"] + 1e-12)) - 1
|
| 369 |
df["EMA_21_dist"] = (c / (df["ema21"] + 1e-12)) - 1
|
| 370 |
df["EMA_50_dist"] = (c / (df["ema50"] + 1e-12)) - 1
|
| 371 |
df["EMA_200_dist"] = (c / (df["ema200"] + 1e-12)) - 1
|
| 372 |
df["VWAP_dist"] = (c / (df["vwap"] + 1e-12)) - 1
|
| 373 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 374 |
|
| 375 |
mean_vol = v.rolling(50).mean() + 1e-9
|
| 376 |
df["rel_vol"] = v / mean_vol
|
|
|
|
| 285 |
l = df["low"].astype(np.float64)
|
| 286 |
v = df["volume"].astype(np.float64) if "volume" in df.columns else pd.Series(np.zeros(len(df)), index=df.index)
|
| 287 |
|
| 288 |
+
# ----------------------------------------------------
|
| 289 |
+
# ✅ [GEM-FIX] جسر التوافق مع Oracle & Titan
|
| 290 |
+
# ----------------------------------------------------
|
| 291 |
+
|
| 292 |
+
# 1. Oracle: Slope
|
| 293 |
+
try: df['slope'] = ta.slope(c, length=7).fillna(0)
|
| 294 |
+
except: df['slope'] = 0.0
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 295 |
|
| 296 |
+
# 2. Titan: MFI
|
| 297 |
+
try: df['MFI'] = ta.mfi(h, l, c, v, length=14).fillna(50)
|
| 298 |
+
except: df['MFI'] = 50.0
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 299 |
|
| 300 |
+
# 3. Oracle Mapping (LowerCase Aliases)
|
| 301 |
+
# يحسب المكتبة RSI، ونحن ننسخها إلى rsi لأن النموذج يطلبها صغيرة
|
| 302 |
df["RSI"] = ta.rsi(c, length=14).fillna(50)
|
| 303 |
+
df["rsi"] = df["RSI"] # Alias for Oracle
|
| 304 |
+
|
| 305 |
df["ATR"] = ta.atr(h, l, c, length=14).fillna(0)
|
| 306 |
+
df["ATR_pct"] = (df["ATR"] / (c + 1e-12)) * 100
|
| 307 |
+
df["atr_pct"] = df["ATR_pct"] # Alias for Oracle
|
| 308 |
+
|
| 309 |
+
# 4. Oracle: Volume Z-Score (vol_z)
|
| 310 |
+
vol_mean = v.rolling(20).mean()
|
| 311 |
+
vol_std = v.rolling(20).std()
|
| 312 |
+
df["vol_z"] = ((v - vol_mean) / (vol_std + 1e-9)).fillna(0) # For Oracle
|
| 313 |
|
| 314 |
+
# 5. Titan: Trend Strong (Approx for lower TFs, perfect for 1d)
|
| 315 |
adx_df = ta.adx(h, l, c, length=14)
|
| 316 |
+
if adx_df is not None and not adx_df.empty:
|
| 317 |
+
df["ADX"] = adx_df.iloc[:, 0].fillna(0)
|
| 318 |
+
df["Trend_Strong"] = np.where(df["ADX"] > 25, 1, 0) # For Titan 1d
|
| 319 |
+
else:
|
| 320 |
+
df["ADX"] = 0.0
|
| 321 |
+
df["Trend_Strong"] = 0
|
| 322 |
+
|
| 323 |
+
# ----------------------------------------------------
|
| 324 |
|
| 325 |
try:
|
| 326 |
df["CHOP"] = ta.chop(h, l, c, length=14).fillna(50)
|
|
|
|
| 332 |
except:
|
| 333 |
df["vwap"] = c
|
| 334 |
|
|
|
|
| 335 |
try:
|
| 336 |
df["CCI"] = ta.cci(h, l, c, length=20).fillna(0)
|
| 337 |
except:
|
| 338 |
df["CCI"] = 0.0
|
| 339 |
|
| 340 |
+
# EMAs
|
| 341 |
+
for span in [9, 20, 21, 50, 200]:
|
| 342 |
+
df[f"ema{span}"] = c.ewm(span=span, adjust=False).mean()
|
| 343 |
+
|
| 344 |
+
# Derived Features
|
| 345 |
df["EMA_9_dist"] = (c / (df["ema9"] + 1e-12)) - 1
|
| 346 |
df["EMA_21_dist"] = (c / (df["ema21"] + 1e-12)) - 1
|
| 347 |
df["EMA_50_dist"] = (c / (df["ema50"] + 1e-12)) - 1
|
| 348 |
df["EMA_200_dist"] = (c / (df["ema200"] + 1e-12)) - 1
|
| 349 |
df["VWAP_dist"] = (c / (df["vwap"] + 1e-12)) - 1
|
| 350 |
+
|
| 351 |
+
# BBANDS
|
| 352 |
+
if len(df) < 30:
|
| 353 |
+
df["lower_bb"] = c; df["upper_bb"] = c; df["bb_width"] = 0.0; df["bb_pct"] = 0.5
|
| 354 |
+
else:
|
| 355 |
+
bb = ta.bbands(c, length=20, std=2.0)
|
| 356 |
+
if bb is not None and isinstance(bb, pd.DataFrame):
|
| 357 |
+
col_w = [x for x in bb.columns if "BBB" in x]
|
| 358 |
+
col_p = [x for x in bb.columns if "BBP" in x]
|
| 359 |
+
col_l = [x for x in bb.columns if "BBL" in x]
|
| 360 |
+
col_u = [x for x in bb.columns if "BBU" in x]
|
| 361 |
+
|
| 362 |
+
df["bb_width"] = bb[col_w[0]] if col_w else 0.0
|
| 363 |
+
df["bb_pct"] = bb[col_p[0]] if col_p else 0.5
|
| 364 |
+
df["lower_bb"] = bb[col_l[0]] if col_l else c
|
| 365 |
+
df["upper_bb"] = bb[col_u[0]] if col_u else c
|
| 366 |
+
|
| 367 |
+
# Aliases for Titan
|
| 368 |
+
df["BB_w"] = df["bb_width"]
|
| 369 |
+
df["BB_p"] = df["bb_pct"]
|
| 370 |
+
else:
|
| 371 |
+
df["lower_bb"] = c; df["upper_bb"] = c; df["bb_width"] = 0.0; df["bb_pct"] = 0.5
|
| 372 |
+
df["BB_w"] = 0.0; df["BB_p"] = 0.5
|
| 373 |
+
|
| 374 |
+
# MACD
|
| 375 |
+
macd = ta.macd(c)
|
| 376 |
+
if macd is not None and not macd.empty:
|
| 377 |
+
df["MACD"] = macd.iloc[:, 0]
|
| 378 |
+
df["MACD_h"] = macd.iloc[:, 1]
|
| 379 |
+
df["MACD_s"] = macd.iloc[:, 2]
|
| 380 |
+
else:
|
| 381 |
+
df["MACD"] = 0.0; df["MACD_h"] = 0.0; df["MACD_s"] = 0.0
|
| 382 |
|
| 383 |
mean_vol = v.rolling(50).mean() + 1e-9
|
| 384 |
df["rel_vol"] = v / mean_vol
|