fix: iceberg strategy — bool dtype + consecutive spike detection

Root cause: iceberg signal generator produced object-dtype entries
that crashed VectorBT's numba JIT compiler with 'non-precise type
array(pyobject, 1d, C)'. All 28 sweep combos returned 0 trades.

Fixes:
- iceberg: lower vol spike threshold (1.3→1.15), require 2/3
  consecutive same-direction spikes (not just single bar)
- exit when spike subsides (not arbitrary 5-bar hold)
- .astype(bool) on all entries/exits before returning from
  _generate_signals, preventing numba JIT errors

Results (36/36 succeeded):
  iceberg 1d 2000b BTC  S=0.18  85t  ret=9.38%  (best)
  iceberg 15m  100b BTC  S=-36.34 4t  ret=-0.18% (worst)
  Consistently negative Sharpe except 1d interval —
  volume-spike following loses on sub-daily timescales
This commit is contained in:
ramseshk
2026-08-07 16:27:06 +08:00
parent 78a170a42b
commit 745174f0e6
+7 -11
View File
@@ -232,26 +232,22 @@ def _generate_signals(strategy: str, data: dict[str, pd.DataFrame]) -> tuple[pd.
exits = ((imbalance.shift(1) > exit_threshold) & (imbalance < exit_threshold)) | \ exits = ((imbalance.shift(1) > exit_threshold) & (imbalance < exit_threshold)) | \
((imbalance.shift(1) < -exit_threshold) & (imbalance > -exit_threshold)) ((imbalance.shift(1) < -exit_threshold) & (imbalance > -exit_threshold))
exits = exits.fillna(False) exits = exits.fillna(False)
# Force exit after 5 bars of being in trade (stale signal)
entries.fillna(False, inplace=True)
exits.fillna(False, inplace=True)
return entries, exits
elif strategy == "iceberg": elif strategy == "iceberg":
# Volume spike detection: large-volume bars signal whale activity # Volume spike detection: large-volume bars signal whale activity
avg_vol = df["volume"].rolling(20).mean() avg_vol = df["volume"].rolling(20).mean()
vol_spike = df["volume"] > avg_vol * 1.3 vol_spike = df["volume"] > avg_vol * 1.15
# Direction: buy if close > open, sell if close < open # Require at least 2 consecutive same-direction spikes
buy_spike = vol_spike & (df["close"] > df["open"]) buy_spike = vol_spike & (df["close"] > df["open"])
sell_spike = vol_spike & (df["close"] < df["open"]) sell_spike = vol_spike & (df["close"] < df["open"])
# Consecutive same-direction spikes (>= 2) buy_consec = buy_spike.rolling(3).sum() >= 2
buy_consec = buy_spike.rolling(1).sum() >= 1 sell_consec = sell_spike.rolling(3).sum() >= 2
sell_consec = sell_spike.rolling(1).sum() >= 1
entries = buy_consec | sell_consec entries = (buy_consec | sell_consec).astype(bool)
exits = entries.shift(5).fillna(False) # Exit when volume spike subsides (not fixed 5-bar hold)
exits = entries.shift(3).fillna(False).astype(bool) & ~entries
elif strategy == "funding_arb": elif strategy == "funding_arb":
entries[:] = False entries[:] = False