Slippage, Fees, and Fill Models
Read at your own pace. Code execution, quizzes, progress tracking, and the AI tutor are available in the interactive view with JavaScript enabled.
Cost Reality
From replay to believable P&L
Now that we've covered Statistical Arbitrage & Latency Strategies and Backtesting, Simulation & Replay, you can generate signals and replay historical events deterministically.
But a replay is still incomplete until it answers one uncomfortable question:
After fills, fees, rebates, and slippage, did the strategy actually make money?
This lesson tightens the simulator around three practical pieces:
- Fill model: did our order execute, and how much?
- Fee model: what did the venue/broker charge or rebate?
- Slippage model: how far was execution from the price we expected?
This is the last lab step before the course moves toward production readiness. We are not deploying yet; we are making offline evidence harder to fool.

Cost Stack
The price you see is not the price you keep
A simple strategy report might say:
Bought at 100.00
Sold at 100.03
Profit = 0.03 per share
A trading simulator should ask more:
arrival price price when strategy decided
execution price actual simulated fill price
slippage execution price - arrival/reference price
fees/rebates explicit costs from venue/broker
spread paid/earned implicit cost or income from taking/making liquidity
partial fills quantity that actually traded
unfilled risk order quantity left exposed or missed
For HFT-style strategies, small errors matter. A signal that makes 0.4 basis points before costs can become negative after taker fees and adverse slippage.
A good habit: report gross P&L and net P&L separately.
gross_pnl = value before explicit costs
net_pnl = gross_pnl - fees - other modeled costs

Cost Bias Check
A replayed market-making strategy shows strong profits, but the simulator assumes every quote fills whenever the market touches its price and charges no fees.
What is the biggest concern?
Fees In C++
Model explicit costs first
Fees are the easiest cost to make explicit because they are usually rule-based.
A simplified venue schedule might say:
-
Taker fee: you remove liquidity, pay
0.30bps of notional -
Maker rebate: you add liquidity, receive
0.10bps of notional
One basis point, or 1 bps, is 0.01%, so:
bps_cost = notional * bps / 10000
In production, fee schedules can depend on venue, symbol, monthly volume tier, order type, and region. In our lab, keep the model small but explicit.
```cpp
#include <iomanip>
#include <iostream>
#include <string>
struct FeeSchedule {
double takerFeeBps; // positive cost
double makerRebateBps; // positive rebate value
};
double feeForFill(double price, int qty, bool isTaker, const FeeSchedule& fs) {
const double notional = price * qty;
if (isTaker) {
return notional * fs.takerFeeBps / 10000.0;
}
return -notional * fs.makerRebateBps / 10000.0; // negative fee = rebate
}
int main() {
FeeSchedule schedule{0.30, 0.10};
const double price = 100.25;
const int qty = 500;
double takerCost = feeForFill(price, qty, true, schedule);
double makerCost = feeForFill(price, qty, false, schedule);
std::cout << std::fixed << std::setprecision(4);
std::cout << "Notional: $" << price * qty << "\n";
std::cout << "Taker fee: $" << takerCost << "\n";
std::cout << "Maker fee/rebate: $" << makerCost << "\n";
if (makerCost < 0.0) {
std::cout << "Maker fill received a rebate\n";
}
}
```
Slippage By Depth
Market orders walk the book
Slippage is the difference between a reference price and the simulated execution price.
For a buy order, common references include:
- arrival mid: midprice when the strategy decided
- arrival ask: best ask when the order started
- decision price: price attached to the signal
If you send a market buy larger than the best ask size, the order consumes deeper ask levels.
Ask book:
100.02 x 100
100.03 x 100
100.05 x 200
Buy 250 -> fills 100 @ 100.02, 100 @ 100.03, 50 @ 100.05
The average execution price is worse than the first visible ask. That difference is market impact/slippage in miniature.
```cpp
#include <algorithm>
#include <iomanip>
#include <iostream>
#include <vector>
struct Level {
double price;
int qty;
};
int main() {
std::vector<Level> asks = {
{100.02, 100},
{100.03, 100},
{100.05, 200}
};
const int orderQty = 250;
const double arrivalMid = 100.00;
int remaining = orderQty;
int filled = 0;
double notional = 0.0;
std::cout << std::fixed << std::setprecision(4);
for (const auto& level : asks) {
if (remaining == 0) break;
int fillQty = std::min(remaining, level.qty);
remaining -= fillQty;
filled += fillQty;
notional += fillQty * level.price;
std::cout << "fill " << fillQty << " @ " << level.price << "\n";
}
if (filled > 0) {
double avgPrice = notional / filled;
double slippage = avgPrice - arrivalMid;
double slippageBps = slippage / arrivalMid * 10000.0;
std::cout << "\nFilled: " << filled << " of " << orderQty << "\n";
std::cout << "Average execution price: " << avgPrice << "\n";
std::cout << "Slippage vs arrival mid: " << slippageBps << " bps\n";
}
if (remaining > 0) {
std::cout << "Unfilled quantity: " << remaining << "\n";
}
}
```
Touch Means Fill
True or false:
If historical trades touch the price of your passive limit order, a realistic HFT replay should always assume your full order filled.
Fill Assumption Ladder
Choose the model on purpose
A fill model is not one thing. It is a ladder of assumptions.
Most optimistic
1. Touch fill: price touched -> full fill
2. Trade-through fill: market trades beyond your limit
3. Volume-capped fill: fill cannot exceed printed volume
4. Queue-aware fill: volume must clear quantity ahead of you
5. Latency-aware queue fill: order joins queue after simulated delay
Most conservative
You do not always need the most complex model. But you should know what assumption you are making.
For example:
- A high-level execution backtest may start with volume-capped fills.
- A market-making replay should usually avoid pure touch fills.
- A latency strategy should account for order arrival delay.
The goal is not perfect prediction. The goal is to avoid a model that rewards behavior you could not have achieved live.

Costed Fill Report
Turn fills into a report
Once the simulator emits fills, the next step is accounting.
A useful per-run report should include:
- total bought and sold quantity
- average buy and sell price
- gross cash movement
- explicit fees/rebates
- net cash movement
- remaining position
This is intentionally small, but it mirrors the accounting boundary you need before trusting a replay result.
```cpp
#include <iomanip>
#include <iostream>
#include <vector>
struct Fill {
char side; // 'B' for buy, 'S' for sell
double price;
int qty;
double fee; // positive cost, negative rebate
};
int main() {
std::vector<Fill> fills = {
{'B', 100.02, 100, 0.3001},
{'B', 100.03, 100, 0.3001},
{'B', 100.05, 50, 0.1501},
{'S', 100.08, 200, 0.6005}
};
int position = 0;
int bought = 0;
int sold = 0;
double buyNotional = 0.0;
double sellNotional = 0.0;
double fees = 0.0;
double cash = 0.0;
for (const auto& f : fills) {
fees += f.fee;
if (f.side == 'B') {
position += f.qty;
bought += f.qty;
buyNotional += f.price * f.qty;
cash -= f.price * f.qty;
} else {
position -= f.qty;
sold += f.qty;
sellNotional += f.price * f.qty;
cash += f.price * f.qty;
}
}
double netCash = cash - fees;
std::cout << std::fixed << std::setprecision(4);
std::cout << "Bought qty: " << bought << " avg="
<< (bought ? buyNotional / bought : 0.0) << "\n";
std::cout << "Sold qty: " << sold << " avg="
<< (sold ? sellNotional / sold : 0.0) << "\n";
std::cout << "Remaining position: " << position << "\n";
std::cout << "Gross cash: $" << cash << "\n";
std::cout << "Fees: $" << fees << "\n";
std::cout << "Net cash: $" << netCash << "\n";
}
```
Cost Formula Blank
Fill in the missing term:
net_pnl = gross_pnl - ______
Use the simple word this lesson used for explicit venue/broker costs.
Replay Exit Checklist
What a cost-aware replay should output
Before you leave the Signals and Replay section, your lab should be able to explain a strategy result without hand-waving.
A solid replay report can answer:
- What orders did the strategy try to send?
- Which orders filled, partially filled, or missed?
- What fill model was used?
- What fee schedule was applied?
- What was the arrival/reference price for slippage?
- What was gross P&L versus net P&L?
- How much position remained at the end?
A useful final line might look like:
fills=184 fill_rate=62.4% gross_pnl=18.42 fees=7.15 net_pnl=11.27 max_position=80
That single line is not the whole story, but it forces the simulator to expose the assumptions that matter.
Next, the course moves from offline evidence to the operational question: how do we run, monitor, and tune a system safely in production-like conditions?

One Pager Cheat Sheet
One Pager Cheat Sheet
- A strategy backtest is not believable until fills, fees, and slippage are modeled explicitly.
- Separate gross P&L from net P&L so tiny strategies cannot hide behind missing costs.
- Unrealistic fills and missing costs can turn a losing strategy into a fake winner.
- A fee model converts venue rules into explicit per-fill costs or rebates.
- Slippage appears when execution happens away from the reference price, especially when size walks through book depth.
- A price touch is evidence of possible interaction, not proof of a full fill.
- Fill models range from optimistic touch fills to latency-aware queue simulation; choose the assumption deliberately.
- A replay should turn simulated fills into an accounting report with position, gross cash, fees, and net cash.
- At minimum, net P&L subtracts explicit fees from gross P&L.
- A cost-aware replay report should expose fills, fill assumptions, fees, slippage reference, net P&L, and remaining risk.