Strategies
SOL Maker-Execution Intraday Reversion, Long-Short (Single-Instrument BINANCE Perp, 15m Bars, The Fee-Optimized Version of the DEPLOYED SOL Reversion Winner — Enter AND Exit With Passive MAKER Limits at 0.02%/side to Cut the Round-Trip From ~0.10% Taker to ~0.04% Maker, NO Trend Filter, Frequently-Firing, 3-Parameter)
Outcome: Abandoned
SolMakerVwapReversionLS15m
Backtest gate: - metrics unreliable: 7699 trades / 2041 data days is too little evidence (need >=5 trades over >=10 days) — increase signal frequency or widen the window
Hypothesis
Implements the hypothesis exactly: the deployed SOL intraday VWAP-reversion edge, same name and same signal, with the execution cost halved by quoting passively on both entry and profit exit (post_only=True entries guarantee maker; the exit rests at the anchor and is only marketable when price has already overshot in our favour). No trend filter is added (explicit L7 anti-pattern), the design stays 3-knob (band_k, stop_atr, vwap_window) and frequently-firing. This iteration is a minimal edit of previous_code: the signal, band, sizing, stop, time-stop, hysteresis and buffer logic are untouched (Layers 1-2 stay green); the only addition is on_order_filled(), which cancels the still-working opposite entry quote the moment a fill lands and immediately posts the maker take-profit, closing the intra-bar window in which the second two-sided quote could flip the book or an orphan quote could linger after a take-profit fill. Venue kept on BINANCE USD-M (long+short required, 0.02% maker), leverage 1.0 since sizing does not need amplified notional.
Negative expectancy on a decisive 9,694-trade / 6-year sample: the account liquidated (total_return -100%, liquidation 2020-12-06), profit_factor 0.357, Sharpe -2.24, expectancy -$50/trade, and avg_trade_return_pct is -0.029% (BELOW ZERO, not merely below the maker floor). The whole hypothesis rests on halving the round-trip cost, but there is no positive gross edge for the cheaper maker execution to rescue — the reversion signal is net-negative. The structural R:R is adverse (avg_loss ~5x avg_win; ~1 ATR maker target vs 2 ATR taker stop needs >66.7% wins, and the 64% win rate is not enough), so the tail taker stop/time-stop losers dominate. Optimizing band_k/stop_atr/vwap_window cannot invert an expectancy that is below zero. QA's edge concern is borne out on full history, and this is a repeat of the iteration-1 abandon. abandon_class=negative_expectancy.
Implementation
Long-short intraday VWAP-reversion on SOLUSDT.BINANCE USD-M perp, 15m bars, executed with PASSIVE MAKER limit orders. Anchor = rolling 96-bar (1 day) volume-weighted average price; band = band_k x ATR(14). While flat the strategy rests TWO-SIDED post-only quotes (BUY at vwap - band, SELL at vwap + band) so impatient takers who overshoot fair value fill us at 0.02% maker instead of paying 0.05% taker. Once filled, a maker limit take-profit rests at the VWAP anchor (the reversion target). Only risk exits are taker: a market stop when price runs stop_atr x ATR beyond the entry, and a 32-bar (8h) time stop. Re-quote hysteresis (state change / missing quotes / anchor drift > 0.25 ATR) keeps order churn low, incremental rolling OHLCV buffers keep per-bar cost O(window), and an on_order_filled reconcile cancels leftover quotes and posts the exit the instant a fill lands. No trend filter, single instrument, pure OHLCV, path-independent capped sizing (50% of min(base_capital, equity)).
Verification Results
Left to the optimizer: band_k and stop_atr should be jointly searched so the maker take-profit distance and the taker stop distance produce a break-even-achievable R:R at the observed hit rate.
Default parameterization gives an adverse ~1:2 reward:risk (take-profit at VWAP anchor is ~1 ATR from entry; taker stop is stop_atr=2 ATR beyond entry), so gross break-even requires >66.7% wins. This is structural to the default draw, not a defect, but it is why the smoke test loses despite a 68% win rate.
Analyst should treat the maker-fill realism as a known caveat when reviewing full-history results; consider whether a fill-probability/queue haircut is warranted before paper.
avg_holding_bars=0.0 indicates entry maker limit and VWAP take-profit maker limit are being filled within the same bar in the bar-based backtest when the bar range spans both levels. For a strategy whose entire thesis is capturing the 0.04% maker vs 0.10% taker cost spread, this optimistic intra-bar maker-fill assumption may overstate realized edge versus live passive fills that are not guaranteed.
Backtest Review
- Sharpe
- -2.24
- Total return
- -100.00%
- Max drawdown
- 100.00%
- Trades
- 9694
- Win rate
- 64.4%
- Profit factor
- 0.36
Faithful, well-engineered maker-execution / re-quote-hysteresis implementation with a large (9,694-trade) statistically decisive sample across all regimes
Correct venue economics reasoning (maker 0.02% vs taker 0.05%)
Account LIQUIDATED (total_return -100%, liquidation 2020-12-06); profit_factor 0.357, Sharpe -2.24, Sortino -2.23, information_ratio -4.27 — all deeply negative
avg_trade_return_pct -0.029% is NEGATIVE, i.e. the per-trade edge is below zero before the maker cost advantage can even matter — the cheaper round-trip cannot rescue a mechanism with no gross edge
Adverse structural R:R: avg_loss ($220) ~5x avg_win ($43), largest_loss -$11,780; the ~1 ATR maker target against a 2 ATR taker stop + 8h time-stop needs >66.7% wins to break even, and the taker stop/time-stop tail losers swamp the many small maker wins (QA concern confirmed on full history)
Analysis
Backtest gate: - metrics unreliable: 7699 trades / 2041 data days is too little evidence (need >=5 trades over >=10 days) — increase signal frequency or widen the window
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).
Backtest and paper results are hypothetical. Trading involves risk of loss.