---
title: "Slippage and Costs"
description: "Commission on spot, maker and taker fees on perps, and flat slippage, each declared in the file so a shared package carries its own assumptions. The accounting…"
order: 83
section: "strategies"
---

<!-- source: docs/indicators/strategies/slippage-and-costs.md; generated by packages/cli/scripts/gen-indicator-docs.ts, do not edit -->

# Slippage and Costs

Commission on spot, maker and taker fees on perps, and flat slippage, each declared in the file so a shared package carries its own assumptions. The accounting is the kScript (legacy) engine's; the book-estimate slippage model is the one piece of it this release refuses rather than approximates.

## Commission (spot)

On `instrument: "spot"` (the default), `commissionPercent` is charged on every fill as a percent of the fill's notional, on entries and exits alike. Fees reduce equity immediately and are reported per trade (`fees` on each trade record) and in the totals as `stats.feesPaid`.

## Maker and taker fees (perps)

On `instrument: "perps"`, fills are charged `makerFeePercent` or `takerFeePercent` instead of commission, by how the fill reached the market:

- **Taker** (`takerFeePercent`): market-crossing fills. Market entries, stop entries, protective stops, trailing stops, signal exits, `closeAll`, liquidation, and the flattening leg of a reversal a market or stop entry triggered.
- **Maker** (`makerFeePercent`): limit-bound fills. Limit entries, take-profit limit legs, and the flattening leg of a reversal a limit entry triggered.
- `stats.feesPaid` is exactly `makerFeesPaid + takerFeesPaid` on perps.

```typescript
// Perps maker-fee routing: a resting limit entry pays the maker rate, the market close pays the taker rate.
import { input, line, lower, ohlcv, output } from "./sdk/declare";
import { in_close } from "./gen/inputs";
import { emitRow, out_equity } from "./gen/outputs";
import { strategy } from "./gen/strategy";
import { Cross, Sma } from "./sdk/ta";

strategy({ initialCapital: 10000, instrument: "perps", leverage: 2, makerFeePercent: 0.1, takerFeePercent: 0.05, funding: "off", qtyType: "fixed", qtyValue: 1, pyramiding: 1 });
input("close", ohlcv.close);
output("equity", line, lower, { description: "Strategy equity" });

const fastMa = new Sma(4);
const slowMa = new Sma(9);
const cross = new Cross();
let close: f64 = NaN;
let crossed: i32 = 0;

export function init(): void {}

export function state(): i32 {
  close = in_close();
  const fast = fastMa.update(close);
  const slow = slowMa.update(close);
  if (isNaN(fast) || isNaN(slow)) return 0;
  crossed = cross.update(fast, slow);
  return 1;
}

export function finalize(): void {
  if (crossed == 1) strategy.long("Long").limit(close * 0.999).send();
  if (crossed == -1) strategy.close("Long");
  out_equity(strategy.equity());
  emitRow();
}

export function reset(): void {
  fastMa.reset();
  slowMa.reset();
  cross.reset();
  close = NaN;
  crossed = 0;
}
```

The resting limit just under the market is a maker fill when a later bar trades through it (`makerFeesPaid` grows); the market close crosses the market and pays the taker rate (`takerFeesPaid` grows); `feesPaid` is their exact sum.

### One fee schedule per instrument

Declaring the other instrument's fee settings never double-charges; the build warns and the broker ignores them:

- Perps plus a nonzero `commissionPercent`: `STRATEGY_PERPS_COMMISSION_IGNORED`, fees come from the maker and taker rates.
- Spot plus `makerFeePercent` or `takerFeePercent`: `STRATEGY_SPOT_MAKER_TAKER_IGNORED`, the run charges commission only. Declare one of the pair and the warning names just that one; `onLiquidation` on spot warns the same way.

Funding on perps is a holding cashflow, not a fill cost, and has its own stats and series; see [perps](perps.md).

## Flat slippage

`slippageBps` applies adversely to every fill that crosses the market: market entries, stop entries, protective stops and trailing stops. Buys fill at `price * (1 + bps / 10000)`, sells mirror. Limit fills, including take-profit legs, are exempt. Slippage is priced into the fill itself: the trade records carry the slipped price, and there is no separate slippage line to subtract, which is why the backtest card's slippage column reads `0` on this lane while its header echoes the declared rate.

Flat slippage is honest about being a constant: it neither grows with your order size nor tightens on liquid pairs.

## The book estimate

kScript's `slippageModel="bookEstimate"` walks recorded order-book depth to price market-crossing fills by size. This release attaches no order book to any host, so a package declaring `slippageModel: "bookEstimate"` is refused by name at validation (`wrun_strategy_slippage_model_unsupported`) rather than silently priced at the flat rate. Declare `"fixed"` with a `slippageBps` that is realistic for the pair; the book-aware model is a named next step.

## The backtest lane

`om backtest @scope/name` takes no cost flags: `--fee-bps`, `--slippage-bps`, `--latency-bars` and a book size are refused by name (`wrun_strategy_costs_in_sheet`), because a package with its own broker settings would otherwise carry two sets of assumptions. Change the declaration, or link a setting to a param and pass params.

## Practical guidance

- Set `slippageBps` to what a taker actually pays on the pair, and `commissionPercent` or the maker and taker rates to your tier. Most retail rules die here.
- Size matters: a strategy that trades a fraction of equity and one that trades at 10x see very different fee bills on the same signals, because fees are a percent of notional.
- Read `feesPaid` against `netProfit` before reading the win rate: a strategy that crossed the spread 34 times in a quarter has paid for it.
