PerpForge
Get started

Concept · The simulator

Slippage

The gap between the price you expected to fill at and the price you actually filled at — caused by the market moving against you between the moment you placed the order and the moment it was executed.

Slippage

The gap between the price you expected to fill at and the price you actually filled at — caused by the market moving against you between the moment you placed the order and the moment it was executed.

In plain English

You see BTC at $30,000 and click "buy." By the time the exchange processes your order, BTC is at $30,012. That $12 gap is slippage. It always pushes your fill price in the unfavorable direction: higher for buys, lower for sells.

Slippage has two root causes:

  1. Market impact: your order is large relative to the available liquidity at that price level. You consume the book, and successive fills land at progressively worse prices. More relevant for large positions than retail-sized ones.
  2. Price velocity: the market moves between your intent and your execution, especially in volatile or fast-moving markets.

Two flavors in simulation

Config-knob slippage — a fixed bps (basis points; 1 bps = 0.01%) penalty applied per fill, e.g. "add 3 bps to every buy, subtract 3 bps from every sell." This approximates the average expected slippage without modeling the underlying cause. TradingView's Strategy Tester and 3Commas both expose this as a user-facing config option.

Realistic slippage from order book depth — derived from historical L2 (level-2 order book — the full ladder of bids and asks at every price level, captured every 100ms or so) data. You simulate: "given my order size and the available book depth at this timestamp, where would I actually have filled?" No hosted perp-capable backtest tool currently does this — it requires a fundamentally different data pipeline.

What this engine currently does (the gap)

fill-resolver.ts:54: slippageBps: 0 on every backtest fill. The field exists in the Fill interface and is returned in every resolved fill — it simply always returns zero. The plumbing for a config-knob implementation already exists; only the value is hardcoded.

// fill-resolver.ts:54 (current backtest path)
return {
  fillPrice: price,
  slippageBps: 0,   // field exists — always zero
  feeBps: cfg.feeBps,
  ...
}

Competitive context (as of 2026-06-10)

Tool Slippage modeled? Type
TradingView Strategy Tester Yes Config-knob fixed bps
3Commas Yes Config-knob fixed bps
PerpForge No slippageBps: 0 always
Any hosted perp backtest tool No Requires L2 — nobody has it

Config-knob slippage is a catch-up gap for PerpForge — both major competitors have it and users may notice its absence. Realistic L2-based slippage is a frontier feature no tool offers.

Compare maker taker fees: no competitor models it, making it a frontier feature. Slippage is in the opposite category — an existing gap against established tools.

Why it matters for this fleet

Slippage pushes real-account costs above simulator costs for every strategy, at every timeframe. Unlike funding rate (which penalizes hold-heavy strategies disproportionately), config-knob slippage affects all strategies proportionally — it is a uniform friction increase.

However: the current fleet's strategies hold for 1h–4h on average. Slippage is most painful for high-frequency strategies (scalpers) where it can consume most of the per-trade target. For swing strategies with 2–10% targets, a 3–5 bps slippage config has a meaningful but not dominant effect on results.

The dead VWAP model — it already exists

A working real order-book depth-walk model exists in apps/backend/src/orderbook/fill-calculator.ts (calculateFill / vwapDepthWalk). It walks actual order-book levels, computes a VWAP (volume-weighted average price — a fill spread across multiple price levels as liquidity is consumed), and reports slippageBps. It is called on the source === 'live' branch in fill-resolver.ts.

Live strategy evaluation has been inert since Phase 213. Every backtest result is source === 'backtest', which hardcodes slippageBps: 0. The slippage machinery is not missing — it is bypassed.

This model cannot replay history (no historical L2 data was ever captured), but it is the calibration oracle: collect live order-book snapshots going forward, measure realized slippage vs order size, fit the k parameter, apply that parameterization to historical candle data.

The three-rung parametric ladder (all use only OHLCV)

Since no historical L2 data exists, backtest slippage must be parametric — a calibrated formula applied to each fill. Three rungs of increasing fidelity, all using candle data already in Postgres:

Rung Formula Lift Limitation
1 — config knob fillPrice × (1 ± slippageBps/10k) Kills zero-slippage bias; afternoon of work; plumbing half-built Flat rate — same charge on a deep BTC book as a thin altcoin
2 — volatility-scaled k × (candleRange / close) × 10k Charges more on the wide breakout candles where EMA-cross entries cluster Still blind to order size
3 — Almgren (target) k × σ × √(orderNotional / candleVolumeNotional) Size-aware: distinguishes a $500 from a $50k order on a thin altcoin Requires fitting k from live data

σ = volatility in bps (approximated from candle range or ATR — average true range); candleVolumeNotional = candle volume already stored.

The Almgren-Chriss square-root market impact model (2001) is the industry standard for parametric slippage. It captures the key intuition: moving a large order through a thin book costs more than moving a small order through a deep one, and the relationship scales with the square root of size.

Build order: Rung 1 now (kills the zero-slippage optimism with no structural change), design config + trade-row fields toward Rung 3, Rung 2 as intermediate.

Stop-loss exits deserve extra adverse slippage. Real stops gap through the level — filling exactly at the stop price is a classic optimistic backtest assumption. Rung 1+ should apply an additional penalty on exit_reason: 'stop_loss' fills.

The leaderboard-reordering implication

Zero-slippage flatters high-turnover strategies most (slippage paid per fill). The leaderboard selects from a large variant pool — the winner is disproportionately high-frequency, high-fill-count. That is the exact cohort zero-slippage inflates. Adding realistic slippage will reorder the leaderboard toward low-turnover survivors. That reordering is not a side effect — it is the honest result.

How to add Rung 1 (config-knob)

Minimal change: add slippageBps to SimulationConfig, pass it into fill-resolver.ts, and apply it as fillPrice × (1 + slippageBps/10000) for buys and fillPrice × (1 - slippageBps/10000) for sells, before the fill price is returned. The price adjustment must reach computePnl via the fill price — the slippageBps display field alone does not dent PnL.

Related

Sources

  • wiki/qa-sessions/2026-06-29-session.md#q3 (first asked here)
  • apps/backend/src/evaluation/position/fill-resolver.ts:54
  • growth/research/market-competitors.md §4 (competitive landscape)

Related concepts

See it in a real result →

Put it to the test

Does your idea have a real edge, or just a big number?

Spawn your variant, run it on the same engine, and read the edge-significance verdict — before you risk real money.

Test your own idea — free →Free account, no card