Skip to content
Level 4 · Trading System ResearcherLessonPart 28 · page 3 of 828 min
28Minutes
7AFL functions
6Sources
StandardRequires
AFL functions taught here7

Costs: Commissions, Slippage and the Spread

A backtest with no costs is not an optimistic backtest. It is a simulation of a different activity — one where trading is free — and the results transfer to real trading exactly as well as results from a frictionless-physics problem transfer to a real bicycle.

This lesson is about the three separate things people lump together as “costs”, where each one belongs in AmiBroker, and — the part that matters most — how to find out whether your result is a statement about the market or a statement about your cost assumption.

They behave differently, they enter the simulation at different places, and confusing them produces a model that is wrong in ways you cannot see.

What it is Scales with Where it belongs
Commission A fee your broker charges Trades, or shares, or value CommissionMode / CommissionAmount
Spread The gap between bid and ask; you cross half of it to get filled Every trade, both ways The price arrays
Slippage Getting a worse price than you aimed at — queue position, movement between decision and fill, your own size Every trade, and with size The price arrays

The distinction that matters: a commission is a fee deducted from the account; the spread and slippage are a worse price. They are not interchangeable, because a worse price changes the trade’s own arithmetic — including where a percentage stop sits relative to your entry — and a fee does not.

SetOption( "CommissionMode", n ) selects how commission is charged, and the documented modes are:

Mode Meaning
0 Use the portfolio manager’s commission table
1 Percent of trade value
2 Currency amount per trade
3 Currency amount per share or contract

SetOption( "CommissionAmount", x ) then supplies the amount for modes 1 to 3.

Fragment — not a complete formula

SetOption( "CommissionMode", 1 ); // percent of trade value
SetOption( "CommissionAmount", 0.10 ); // 0.10% per trade

Commission is charged on entry and on exit, so a round trip pays it twice. The backtest report totals it under Total commissions paid, which is the first number to look at when a result seems too good: if commissions are a rounding error against gross profit, either your holding period is long or you have not set them.

There is one cost that lives in the Settings dialog and belongs there: SetOption( "InterestRate", x ) applies interest to uninvested cash. It is not a trading cost — it is a financing assumption — but leaving it at a non-zero default while claiming your system produced a return is a straightforward misattribution. Set it deliberately, and to zero if you are testing the rules rather than a cash-management policy.

Fragment — not a complete formula

// A worse price, not a fee: buys fill higher, sells fill lower.
BuyPrice = Open * ( 1 + SlippagePercent / 100 );
SellPrice = Open * ( 1 - SlippagePercent / 100 );
ShortPrice = Open * ( 1 - SlippagePercent / 100 );
CoverPrice = Open * ( 1 + SlippagePercent / 100 );

All four arrays, in the correct direction on each: you always pay more to get in on the side you are entering and receive less to get out.

The User’s Guide itself uses this pattern when explaining why price arrays cannot carry timing information: “in several scenarios you may want to define buyprice as open + slippage and sellprice as close − slippage”. It is the ordinary idiom, not a trick.

The clamping caveat, which is not conservative

Section titled “The clamping caveat, which is not conservative”

During backtesting AmiBroker checks every assigned price against the bar’s High–Low range and silently adjusts it — a price above the High becomes the High, a price below the Low becomes the Low.

Most of the time this protects you. Here it does the opposite. On a bar whose open equals its high, Open * 1.0005 is above the High, so it is clamped back down to the High — and the slippage you deliberately added is quietly removed. The same happens to a sell on a bar that opened at its low.

SetOption( "PriceBoundChecking", False ) disables the check. That is almost never what you want, because it also lets genuinely impossible fills through. The better response is to know how often it happens on your data:

Why a constant percentage is a simplification, and in which direction

Section titled “Why a constant percentage is a simplification, and in which direction”

Real slippage is not a constant. It grows with your size relative to what actually trades, it grows when the market is moving fast, and it is worst on exactly the days your system most wants to trade — which is the direction that flatters the backtest.

A constant is still worth having, because a stated crude assumption beats an unstated sophisticated one. Just be clear which way it is wrong.

Why costs punish short holding periods hardest

Section titled “Why costs punish short holding periods hardest”

This is the single most useful piece of arithmetic in the lesson, and it explains why so many short-term systems look wonderful before costs and hopeless after.

Round-trip cost is roughly fixed per trade. Gross profit per trade is roughly proportional to how far price moved while you held it. So the drag on your annual return is:

Pseudocode — not valid AFL

annual drag = round-trip cost × round trips per year

Halve the holding period and — for the same rules, the same edge per unit of time — you double the number of round trips and therefore double the drag, while the gross profit per trade halves.

The same cost, three holding periods

  1. Round-trip cost: 0.30% (0.20% commission + 0.10% slippage)Held constant. Only the trading frequency changes.
  2. Average hold 100 bars — roughly 2.5 round trips a yearAnnual drag about 0.75%. A gross edge of 8% a year survives comfortably.
  3. Average hold 20 bars — roughly 12 round trips a yearAnnual drag about 3.6%. The same 8% gross edge is now half gone.
  4. Average hold 3 bars — roughly 80 round trips a yearAnnual drag about 24%. No plausible gross edge survives this.

Here is the test that separates a result about the market from a result about your assumptions. Do not change the rules. Change only the cost, over a range that spans your honest uncertainty about it, and look at what happens to the conclusion.

Complete runnable AFL

cost-model.afl
// cost-model.afl
// Part 28 - Costs: Commissions, Slippage and the Spread
//
// The same signals as position-sizing-bench.afl, with every cost assumption
// written down, named, and optimisable. Run it once as a portfolio BACKTEST to
// see the result at your chosen costs, then as an OPTIMIZATION over the two
// cost parameters to see how fast the result decays as costs rise. A system
// whose conclusion flips between 0 and 20 basis points of slippage is a system
// whose conclusion is about the cost assumption, not about the market.
//
// ASSUMPTIONS
// Fill price Next bar's open, moved against us by SlippagePercent on every
// side of every trade. This is a crude but honest stand-in for
// crossing the spread plus market impact.
// Commission Percent of trade value, charged on entry AND on exit, via the
// documented CommissionMode = 1.
// Delays One bar on every signal.
// Financing None. Short borrow costs, dividends and interest on idle cash
// are not modelled; the Settings interest rate still applies to
// uninvested cash unless you set it to zero.
// Liquidity Not modelled. Slippage here is a constant, whereas real
// slippage grows with size relative to volume.
//
// Nothing below produces an expected return. It produces a simulated result
// under a stated set of assumptions, and its whole purpose is to show how much
// that result depends on them.
// ------------------------------------------------------------ 1. the account
SetOption( "InitialEquity", 100000 );
SetOption( "AllowPositionShrinking", True );
SetOption( "MinPosValue", 1000 );
RoundLotSize = 1;
PosQty = 10;
SetOption( "MaxOpenPositions", PosQty );
SetPositionSize( 100 / PosQty, spsPercentOfEquity );
SetTradeDelays( 1, 1, 1, 1 );
// -------------------------------------------------------- 2. the cost inputs
// Both are expressed in percent, both default to numbers that are plausible for
// a retail account in a liquid market, and both are deliberately visible at the
// top of the file rather than buried in Settings where you will forget them.
SlippagePercent = Optimize( "Slippage % per side", 0.05, 0.00, 0.40, 0.05 );
CommissionPercent = Optimize( "Commission % per trade", 0.10, 0.00, 0.30, 0.05 );
// CommissionMode: 0 = portfolio manager commission table, 1 = percent of trade,
// 2 = dollars per trade, 3 = dollars per share or contract.
SetOption( "CommissionMode", 1 );
SetOption( "CommissionAmount", CommissionPercent );
// ------------------------------------------------------- 3. slippage as price
// Slippage is not a fee, so it does not belong in the commission setting. It is
// a worse price, so it belongs in the price arrays: buys fill higher than the
// reference price, sells fill lower.
//
// CAVEAT worth knowing before you trust these fills. During backtesting
// AmiBroker checks each assigned price against the bar's High-Low range and
// silently clamps anything outside it - up to Low, down to High. On a bar whose
// open equals its high, the slippage you asked for on a buy is therefore
// quietly removed. That is the opposite of conservative, and it is why the
// costs lesson asks you to measure how often it happens.
BuyPrice = Open * ( 1 + SlippagePercent / 100 );
SellPrice = Open * ( 1 - SlippagePercent / 100 );
ShortPrice = Open * ( 1 - SlippagePercent / 100 );
CoverPrice = Open * ( 1 + SlippagePercent / 100 );
// ------------------------------------------------------------ 4. the signals
FastMa = MA( Close, 20 );
SlowMa = MA( Close, 100 );
Buy = Cross( FastMa, SlowMa );
Sell = Cross( SlowMa, FastMa );
PositionScore = MA( Close * Volume, 50 );
// ------------------------------------------------------ 5. how to read it
// In the report, read Avg. Bars Held alongside the return figures. Round-trip
// cost is roughly fixed per trade, so the annual drag is that cost multiplied
// by how many round trips a year the system makes. Halving the holding period
// doubles the drag. A system that survives at 5 basis points and dies at 20 is
// telling you its edge is smaller than the uncertainty in your own cost
// estimate.

Download cost-model.afl83 lines

Both cost inputs are wrapped in Optimize(), so the same file is a single backtest at your chosen figures and — run as an Optimization — a map of the whole cost surface.

Run it as a Backtest first, at your best estimate. Record the headline figures.

Then run it as an Optimization over both cost parameters and sort the result table by CAR/MaxDD or by Net Profit. You are looking for one thing only:

At what cost does the conclusion change?

  • If the system is still worth taking at double your estimated costs, the conclusion is robust to the cost assumption. That does not make it right — it removes one way of being wrong.
  • If it survives at 5 basis points of slippage and dies at 20, then the result is a statement about your slippage estimate. Since nobody knows their true slippage to within 15 basis points, you do not have a finding.

If you have no idea what your costs are, do not model zero. Model something defensible and state it. As a starting point for a retail account trading liquid shares:

  • Commission: whatever your broker’s schedule actually says, converted to a percentage of a typical trade size for your account. A flat fee is a much bigger percentage on a small position, which is itself worth knowing.
  • Spread: half the typical quoted spread, per side. Measure it on your own instruments during the session you would actually trade — not at the open, and not at the close.
  • Slippage beyond the spread: at least a few basis points on liquid names, more on anything thin, and more still on stop orders, which by construction fill when the market is moving away from you.

And then apply the sensitivity test above, because the point of an honest minimum is not that it is correct — it is that it gives you a place to start varying from.

Commission is a fee and belongs in CommissionMode / CommissionAmount, charged on both entry and exit. Spread and slippage are a worse price and belong in the four price arrays, in the correct direction on each side — remembering that AmiBroker silently clamps any assigned price back inside the bar’s High–Low range, which quietly returns your slippage on bars that opened at an extreme. Cost drag scales with the number of round trips, so “Avg. Bars Held” is a cost statistic. And the only way to know whether a result is about the market or about your assumptions is to vary the assumption across your honest uncertainty and see whether the conclusion moves.

Check your understanding

Question 1. Why does slippage belong in the price arrays rather than in the commission amount?
Show the answer and why

Answer: Because slippage is a worse price rather than a fee — it changes the trade's own arithmetic, including where a percentage stop sits relative to the entry

A fee is deducted from the account. A worse price changes the entry itself, so everything computed from the entry — the stop distance, the position value, the percentage return of the trade — moves with it. Commission, incidentally, is charged on entry and on exit, not once per round trip.

Question 2. You set BuyPrice = Open * 1.0005 to model slippage. On a bar where Open equals High, what does AmiBroker actually fill at?
Show the answer and why

Answer: The High — because assigned prices outside the bar's High-Low range are silently clamped, which removes the slippage you added

Price-bound checking clamps an assigned price into the bar range. Usually protective; here it silently makes the fill better than you specified. SetOption( "PriceBoundChecking", False ) disables it but also permits genuinely impossible fills, so the better response is to measure how often Open == High on your data.

Question 3. Two systems have the same gross edge per unit of time and pay the same 0.30% round-trip cost. System A holds 100 bars on average, system B holds 10. Which statements follow? Select all that apply.
Show the answer and why

Answer: System B pays roughly ten times the annual cost drag, System B's average gross profit per trade is smaller, so the same fixed cost consumes a larger share of it, "Avg. Bars Held" should be read alongside the return figures rather than skipped

Cost is roughly fixed per round trip, so drag scales with trading frequency, and gross profit per trade scales with holding period — the effect works from both ends. What does not follow is that B is worse: B might have a much larger edge per unit time. The arithmetic tells you what B has to overcome, not whether it does.

Question 4. A backtest is clearly profitable at 5 basis points of slippage and clearly unprofitable at 20. What is the correct conclusion?
Show the answer and why

Answer: The result is a statement about the slippage estimate rather than about the market, and nobody knows their true slippage to within 15 basis points

The conclusion flips inside the range of your own uncertainty about the input, so the test has not distinguished the hypothesis from the assumption. Larger positions make slippage worse, not better, because slippage grows with size relative to turnover.

Question 5. Which of these costs are NOT modelled by SetOption( "CommissionMode", 1 ) plus slippage in the price arrays? Select all that apply.
Show the answer and why

Answer: Borrow cost and availability for short positions, Taxes, Market impact that grows with position size relative to turnover

Commission is charged on both entry and exit, so the exit leg is covered. Borrow, taxes and size-dependent impact are all absent — which is fine as long as they are written into the stated assumptions rather than silently ignored.

Sources for this lesson

6 verified · checked 2026-08-31

  1. 01AFL Function Reference — SetOption§ CommissionMode, CommissionAmount, PriceBoundCheckingamibroker.com/guide/afl/setoption.html2026-08-31
  2. 02AFL Function Reference — GetOptionamibroker.com/guide/afl/getoption.html2026-08-31
  3. 03AmiBroker User's Guide — Portfolio-level backtesting§ Price arrays do not provide timing informationamibroker.com/guide/h_portfolio.html2026-08-31
  4. 04AmiBroker User's Guide — Back-testing your trading ideasamibroker.com/guide/h_backtest.html2026-08-31
  5. 05AmiBroker User's Guide — Backtest report§ Total commissions paidamibroker.com/guide/w_report.html2026-08-31
  6. 06AFL Function Reference — Optimizeamibroker.com/guide/afl/optimize.html2026-08-31

Every technical claim on this page was checked against the official AmiBroker documentation on the date shown. Where the course disagrees with folklore, the source is how you can tell which one to trust.