From 745174f0e6cb16c2e6fbbabf9f766b029a85eb65 Mon Sep 17 00:00:00 2001 From: ramseshk <45832522+ramseshk@users.noreply.github.com> Date: Fri, 7 Aug 2026 16:27:06 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20iceberg=20strategy=20=E2=80=94=20bool=20?= =?UTF-8?q?dtype=20+=20consecutive=20spike=20detection?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- backtests/vbt_runner.py | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/backtests/vbt_runner.py b/backtests/vbt_runner.py index 1a9f160..39b0b28 100644 --- a/backtests/vbt_runner.py +++ b/backtests/vbt_runner.py @@ -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)) | \ ((imbalance.shift(1) < -exit_threshold) & (imbalance > -exit_threshold)) 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": # Volume spike detection: large-volume bars signal whale activity 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"]) sell_spike = vol_spike & (df["close"] < df["open"]) - # Consecutive same-direction spikes (>= 2) - buy_consec = buy_spike.rolling(1).sum() >= 1 - sell_consec = sell_spike.rolling(1).sum() >= 1 + buy_consec = buy_spike.rolling(3).sum() >= 2 + sell_consec = sell_spike.rolling(3).sum() >= 2 - entries = buy_consec | sell_consec - exits = entries.shift(5).fillna(False) + entries = (buy_consec | sell_consec).astype(bool) + # Exit when volume spike subsides (not fixed 5-bar hold) + exits = entries.shift(3).fillna(False).astype(bool) & ~entries elif strategy == "funding_arb": entries[:] = False