Changed
- Dual balance routing (raw vs hysteresis-snapped) - Live and orchestrator flows now carry both
balance_raw(raw wallet balance) andbalance(hysteresis-snapped balance). Sizing/order-shaping paths use snapped balance, while risk/accounting paths use raw balance (including realized-loss gate peak/floor checks, TWEL entry/auto-reduce gating, and auto-unstuck allowance calculations). This applies consistently across live and backtest via Rust orchestrator input. - WEL denominator behavior split by mode - Live now uses a hard fixed denominator for per-symbol WEL (
total_wallet_exposure_limit / config.bot.{pside}.n_positions), removing runtime denominator drift from open-position count. Backtests now exposebacktest.dynamic_wel_by_tradability(defaulttrue): when enabled, WEL uses tradability-aware denominator growth (min(n_positions, n_tradable_max)) based on coins with real candles, and does not shrink after delistings; when disabled, backtests use the same fixed denominator as live. - Bulk price fetch for Hyperliquid -
calc_ideal_ordersnow uses a singleallMidsAPI call to get prices for all symbols instead of individualget_current_closecalls per symbol (1 call vs ~70). Falls back to per-symbol fetches for non-Hyperliquid exchanges or on error. - Sequential margin mode setting for Hyperliquid - Margin mode and leverage API calls are now sequential with a small delay instead of being fired in parallel, reducing API burst on coin changes.
Fixed
- Bybit fill-event qty inflation on duplicate pages -
BybitFetchernow deduplicatesfetch_my_tradesrows by exec id before canonicalization/coalescing, preventing duplicate pagination rows from inflating canonicalqty,fees, and close PnL. - Balance peak drift in wrong direction under hysteresis - Peak reconstruction (
balance + (pnl_cumsum_max - pnl_cumsum_last)) previously used hysteresis-snapped balance in some paths. Since snapped balance can stay stale whilepnl_cumsum_lastchanges fill-by-fill, this made reconstructed peak drift down after profits and up after losses. Peak/PnL-accuracy-sensitive paths now use raw balance (balance_raw) consistently. - Pytest Rust-module bootstrap fallback - Test bootstrap now tries the project venv
passivbot_rustpackage before falling back to the lightweight stub when tests are launched outside the venv, reducing false failures from missing/incorrect Rust module resolution. max_ohlcv_fetches_per_minuteignored when forager slots open - The rate limit config was only applied when all position slots were full. With open slots (the common case), all candidate symbols were fetched without rate limiting, causing 429 errors on Hyperliquid.- Hyperliquid positions+balance double fetch -
fetch_positionsandfetch_balancenow share a single API call via a dedup lock instead of making two identicalclearinghouseStaterequests per execution cycle. - Thundering herd on minute boundary -
get_candlesno longer force-refreshes all symbols simultaneously when a new minute boundary crosses. A 1-candle staleness tolerance prevents the TTL override that caused all symbols to fetch at once. - Candle refresh TTLs aligned to 1-minute finalization - Active candle refresh TTL raised from 10s to 60s and EMA close TTL from 30s to 60s, matching the actual 1-minute candle finalization interval.
- Boot stagger for multi-bot setups - Added
boot_stagger_secondsconfig (default 30s for Hyperliquid) to randomize startup delay, preventing simultaneous API bursts when multiple bots share the same IP. - Warmup and refresh fetch pacing - Added configurable
warmup_fetch_delay_ms(default 200ms for Hyperliquid) with delays between individual symbol fetches during warmup, forager refresh, and active candle refresh loops. - Exponential backoff on 429 errors - WebSocket
watch_ordersuses exponential backoff (up to 30s) on rate limit errors. Execution loop backs off 5s onRateLimitExceeded. Hourlyinit_marketscatches rate limits with 10s recovery. - Fill events pagination abort on repeated rate limits -
HyperliquidFetchernow aborts after 5 consecutive rate limit retries with exponential backoff instead of retrying indefinitely. - EMA bundle and active candle sweep abort on rate limit - Both
_load_orchestrator_ema_bundleandupdate_ohlcvs_1m_for_activesskip remaining symbols when the CandlestickManager's global rate limit backoff is active. - Live close-EMA failure handling in orchestrator feed -
_load_orchestrator_ema_bundle()no longer silently drops failed/non-finite close EMA spans. It now fails loudly when no prior EMA exists, and otherwise reuses the last successfully computed close EMA for that exact symbol/span with explicit[ema]warning logs (including reason, age, and consecutive fallback count). - Required 1h log-range EMA handling in orchestrator feed -
_load_orchestrator_ema_bundle()now fails loudly when requiredh1log-range spans (fromentry_volatility_ema_span_hours) are missing or non-finite, instead of deferring to downstream RustMissingEmaerrors. - EMA bundle fetch stability under lock contention - Orchestrator EMA bundle loading now fetches per-symbol spans serially and drains all symbol task outcomes before re-raising, reducing same-symbol candle-lock contention and eliminating unretrieved sibling-task exception noise.
Added
- Fill events doctor tool - Added
src/tools/fill_events_doctor.pyto audit cached fill events and auto-repair known Bybit duplicate-fill anomalies without requiring exchange API calls. Bybit startup now runs doctor by default (can be disabled withPASSIVBOT_FILL_EVENTS_DOCTOR=off).
Full changelog: v7.8.3...v7.8.4