AlgoDaily

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 Reality

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 Stack

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.30 bps of notional
  • Maker rebate: you add liquidity, receive 0.10 bps 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.

Fill Assumption Ladder

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?

Replay Exit Checklist

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.
Open interactive view →