Virtual stop-loss
Background
Binance USD-M Futures auto-cancels resting reduce-only LIMIT orders when a position shrinks below the total resting reduce-only quantity (the matching engine enforces the invariant Σ resting reduceOnly ≤ position). This is not Self-Trade Prevention; it's the exchange enforcing the invariant.
When multiple strategies trade the same symbol on one Binance account in one-way (BOTH) position mode and one strategy's STOP_MARKET SL fires inside the matching engine, the position shrinks atomically with no chance for the bot to clean up sibling strategies' TPs first. Sibling TPs become surplus and Binance auto-cancels them. The sibling positions are left unprotected — only the (now-fired) SL gone — until the operator notices and intervenes.
Solution
Move SL evaluation to the bot. The runner watches the book-ticker against each open position's virtual SL level. On crossing, the runner:
- Cancels every reduce-only LIMIT TP belonging to the firing position concurrently, awaiting all acks.
- Sends a MARKET reduce-only close for the same quantity.
Step 1 removes the strategy's own TPs from the book before the position shrinks. Step 2 then shrinks the position. The Σ resting reduceOnly ≤ position invariant holds throughout, so the auto-cancel never fires.
Time →
cancel TP1 ─┐
cancel TP2 ─┼─ awaited ──→ MARKET reduce-only close → position drops
cancel TPN ─┘ ↑
book reduceOnly is already 0 hereTrade-offs vs exchange-side STOP_MARKET
| Property | STOP_MARKET (legacy) | Virtual SL |
|---|---|---|
| Trigger atomicity | inside engine — instant | bot-side, ~30–60 ms latency |
| Survives bot disconnect | yes | no — SL cannot fire while the WS feed is down |
| Mid-trigger cancel of sibling LIMITs | impossible | guaranteed |
| Reduce-only-surplus auto-cancel risk | high in multi-strategy setups | none in normal operation |
| Slippage on hard crashes | low | slightly higher |
Configuration
Set virtualSl on the strat entry (runner-level flag, not a strategy parameter):
{
"name": "vsl_15S",
"type": "shot",
"virtualSl": true
}Default is false (legacy STOP_MARKET). Recommendation: enable for all strategies on a given symbol or none — mixing modes lets the legacy STOP_MARKET path still trigger the cascade we are eliminating.
Failure modes & WS connectivity
The virtual SL is checked against every book-ticker. If the WS feed stops delivering ticks, the SL cannot fire until the WS recovers — there is no exchange-side STOP_MARKET backstop. Connectivity loss surfaces through the existing WS reconnect / fatal path (auto-pause on disconnect, fatal escalation after repeated reconnect failures), not a separate staleness alert.
Limitations
- Mixing virtual and legacy SL on the same symbol can re-introduce the cascade when the legacy strategy's
STOP_MARKETfires. - Bot crash loses virtual SL state. The strategy must re-emit exits on its next on-fill /
SetExitsafter restart for the SL to be re-armed. No startup reconciliation is performed for virtual SLs in this build. - The 30–60 ms cancel-then-MARKET window adds slippage relative to engine-side triggers.
Backtesting and paper
- Paper (
isEmulator: true): inherits the fix automatically. Runs through the same runner as live, sovirtual_sl: 1triggers the same cancel-then-MARKET sequence against the paper adapter. The WS-staleness watchdog is harmless in paper because the paper adapter synthesises ticker events itself. - Backtest (
tradectl backtest ...): thevirtual_slparam is silently inert. Backtests model a single strategy against a single position and cannot reproduce the cross-strategy reduce-only-surplus cascade that virtual SL fixes. Reported P&L will be marginally optimistic vs live by the cancel→MARKET latency cost (~30–60 ms slippage on SL fires); use paper for accurate validation before flipping the flag in production.
