MultiNameVwapReversionBasketLS
Hypotheses
Multi-Name Intraday VWAP-Reversion BASKET, Long-Short (5 High-Volatility BINANCE USD-M Perps — SOL/AVAX/LINK/DOGE/XRP — Run the DEPLOYED SOL VWAP-Reversion Winner's EXACT Recipe INDEPENDENTLY Per Name and Aggregate, Diversifying the One Intraday Survivor's Marginal Single-Name Edge Into a Robust Portfolio, Timeout-Safe O(1) Rolling VWAP, 2-Parameter)
Hypotheses
This deliberately does NOT introduce a new signal — every new signal class is empirically dead this session (single-name & cross-instrument order flow all fee-died decisively; funding decayed and now overfits; premium-basis and order-book both timed out; options blocked; baskets drawdown/overfit). Instead it takes the SINGLE intraday mechanism that actually SURVIVED to paper — the deployed SOL 15m VWAP-reversion (fade MODERATE z-score deviations from a rolling VWAP, trend-gate OFF, frequently-firing) — and attacks its one weakness (marginal single-name Sharpe ~0) with the one lever that reliably raises risk-adjusted return without changing a working signal: DIVERSIFICATION. It runs that identical, proven recipe INDEPENDENTLY on 5 high-volatility, deep-history USD-M perps (SOLUSDT, AVAXUSDT, LINKUSDT, DOGEUSDT, XRPUSDT), each sized to equal risk, aggregating 5 largely-uncorrelated intraday-reversion streams into one book. Because the per-name edge is already proven marginally positive and the streams' noise is largely idiosyncratic, the aggregate Sharpe should exceed any single name's (the standard breadth effect), potentially converting SOL's marginal result into a promotable portfolio. High-vol names are chosen so moderate reversions (~0.2–0.4%) clear the ~0.10% USD-M round trip (the fee arithmetic the deployed SOL version already satisfies). It is computationally trivial and TIMEOUT-SAFE (rolling VWAP + z-score are O(1) per bar — no tick data, no history rescans, avoiding the smoke-test timeouts that killed my order-book and premium probes). NOT a new indicator (L30's caution is about re-labeled NEW signals; this reuses a DEPLOYED survivor), NOT order flow (fee-dead), NOT funding/options. Multi-instrument (scope diversity), LONG-SHORT (fills the 85%-long imbalance), short-horizon (fills the 7.8% bucket). 2 core parameters, shared across all names (VWAP window, entry z) — deliberately NOT per-name-tuned, to resist the overfit/deflated-Sharpe deaths.
Hypotheses
I verified the reported cause did not apply before changing anything: the indicator path was already O(1) per bar (deque + running sums, no array rebuilds, no sort/min, no supplementary series at all), and all five legs have complete 15-MINUTE catalog coverage through 2026-09-02, so neither history rescanning nor on-demand collection explains the 300s. The one thing that genuinely scaled with the run was engine round-trips: 5 legs x ~20k bars = ~100k callbacks, each issuing positions_open() for its own name plus an orders_open() check, and every entry attempt calling _gross_notional() (positions_open for ALL five names again) and get_account_equity() twice — get_account_equity rebuilds the account balance dict on each call — roughly 1M cache/account round-trips per backtest. Those are now hoisted into a per-timestamp snapshot (_refresh) that rebuilds the position map, equity and gross notional once per bar timestamp, cutting round-trips by roughly an order of magnitude; the redundant orders_open() stacking check is dropped because the base template already blocks stacked entries with its own in-flight guard. One behavioural improvement falls out: a submitted entry's notional is added to the cached gross immediately, so the portfolio cap now also counts same-timestamp entries that have not yet reached the cache — a strictly tighter cap than before. Signal, entry/exit rules, sizing, risk caps and all parameters are otherwise unchanged.
Hypotheses
backtest_failed:timeout: the full backtest could not run 3 times — run_subprocess_impl_inner(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/opt/strategy-factory/src/backtesting/subprocess_runner.py", line 365, in _run_subprocess_impl_inner
raise RuntimeError(
RuntimeError: Subprocess backtest exceeded max wall time 7200s
Pipeline/data failure, not a verdict on the strategy; revive once the cause is fixed.
Implementation
Long-short 15-minute VWAP-reversion basket across 5 high-volatility Binance USD-M perps (SOL, AVAX, LINK, DOGE, XRP), running the deployed SOL VWAP-reversion recipe independently per name with the same two shared parameters (20-bar rolling VWAP window, entry z 1.5) — no per-name tuning. Each name fades moderate z-score deviations of close from its rolling volume-weighted mean (long below, short above, skipping blow-off moves beyond z 4), exiting on reversion inside z 0.3, a 1.5% take-profit, a 1.0% stop, or a 12-bar time stop. Positions are 6% of equity per name under a hard 35%-of-equity cap on TOTAL open notional across all five, so correlated same-direction clustering cannot compound; leverage 1.0. VWAP and z-score are maintained incrementally with deques and running sums (O(1) per bar).
Verification Results
sandbox_timeout: Verification failed (Layer 3 — sandbox backtest): smoke test exceeded the 300s wall-clock limit on a ~20000-bar window (208 days of SOLUSDT.BINANCE-15-MINUTE-LAST-EXTERNAL). This almost always means per-bar work that scales with history — e.g. rescanning the full funding/supplementary series, or rebuilding a list and calling min()/sorted() inside calculate_signal()/on_bar() on every bar. Precompute sorted timestamp arrays ONCE in __init__ and use bisect, or cache lookups keyed by timestamp, so per-bar cost is O(log n) not O(n).
Iteration History
Verification failed (Layer 3 — sandbox backtest): smoke test exceeded the 300s wall-clock limit. This almost always means per-bar work that scales with history — e.g. rescanning the full funding/supplementary series, or rebuilding a list and calling min()/sorted() inside calculate_signal()/on_bar() on every bar. Precompute sorted timestamp arrays ONCE in __init__ and use bisect, or cache lookups keyed by timestamp, so per-bar cost is O(log n) not O(n).
Iteration History
Verification failed (Layer 4 — QA review):
- Account LIQUIDATION / total wipeout: total_return -100.0%, liquidated=true on 2025-10-14, max_drawdown 100% (CI to 100.6%), kurtosis 94.9, tail_ratio 0.0019, largest_loss -$2,740 vs avg_loss $72 (~38x). The protective stop (2%) and TP are evaluated only on 15-minute bar closes in _exit_now, so intrabar/gap moves blow through the stop; combined with 5 highly-correlated majors at per_name_frac 0.20 = 100% gross notional clustering same-direction, drawdowns compound to a blown account — the opposite of the hypothesis's breadth premise.
- Edge below fees on a decisive 16,474-trade sample: avg_trade_return_pct -0.0045% (negative, far below the ~0.10% round trip), profit_factor 0.62, turnover 1242, total_impact_usd $62,901. Win_rate 0.64 but avg_win $25 vs avg_loss $72 — the inverted reward/risk of a VWAP fade. This is the dead high-turnover VWAP-fade class (L6: SOL VWAP fade -88%); replicating it across 5 names multiplied the fee bleed.
- The breadth/diversification premise is empirically false: benchmark_correlation 0.382, beta 1.28, and correlated drawdown drove the liquidation. Five correlated crypto majors provide no idiosyncratic diversification, so the intended mechanism is not realized.
Backtest and paper results are hypothetical. Trading involves risk of loss.