Risk Per Trade and Stop Distance
Ask a trader what their risk is on a position and you will usually be told the size of the position. That is the amount at stake if the company disappears overnight, which is not the number anyone is actually managing. The number being managed is the loss you have decided in advance to accept before you get out, and it is a different quantity entirely — one you can set to whatever you like, on any instrument, at any price, by choosing the share count.
By the end of this lesson you should be able to convert a risk budget and a stop distance into a share count, express that conversion in AFL so AmiBroker applies it on the correct bar, explain why risk-based sizing produces very different position weights from equal allocation, and say precisely which part of the loss the stop does not control.
Three numbers, one multiplication
Section titled “Three numbers, one multiplication”Risk-based sizing needs three inputs and produces one output.
- Equity. The account value at the moment of the decision. In an AmiBroker portfolio backtest this is the single portfolio equity: available cash plus the value of every simultaneously open position.
- Risk fraction. The share of that equity you are prepared to lose on this one position if the exit works as intended. Written as r, usually somewhere between a quarter of a per cent and two per cent.
- Stop distance. The distance in price points between the intended entry and the price at which you have decided the idea was wrong. Written as D.
The share count follows:
shares = (equity × r) / D
That is the entire mechanism. A larger stop distance buys fewer shares; a smaller one buys more; the product of shares and stop distance is the same in every position, which is the point.
Where a position size comes from
- SignalA candidate appears
- Exit levelWhere the idea is wrong
- Stop distanceEntry minus that level
- Risk budgetEquity × r
- Share countBudget ÷ distance
A worked example
Section titled “A worked example”Take an account of 100,000, a risk fraction of one per cent — a budget of 1,000 per position — and a rule that no single position may exceed twenty per cent of equity. Three candidates signal on the same day.
| Candidate | Price | Stop distance | As % of price | Shares wanted | Position value | Dollar risk |
|---|---|---|---|---|---|---|
| A | 20.00 | 1.50 | 7.5% | 666 | 13,320 | 999 |
| B | 20.00 | 0.40 | 2.0% | 2,500 | 50,000 | 1,000 |
| C | 250.00 | 15.00 | 6.0% | 66 | 16,500 | 990 |
A and C get very different amounts of money — 13,320 and 16,500 — and almost exactly the same amount of risk. That is the rule working.
B is the interesting one. Its stop is tight, so the rule wants a 50,000 position: half the account in one name, to risk one per cent of it. The twenty per cent cap cuts that to 1,000 shares, and the risk falls with it, to 400. A cap on position size is also a cap on the risk rule, and once the cap binds you are no longer running a one per cent risk policy on that trade. Both figures above are illustrative arithmetic on made-up prices, not observations from any market.
The stop has to exist before the size does
Section titled “The stop has to exist before the size does”A stop distance is not an optional refinement of a sizing rule. It is the denominator. If you have not decided where the position stops being a position, the equation has nothing to divide by, and the honest value of D is the entire share price — because that is what you have actually agreed to lose.
This has a practical consequence for how you write systems. The exit has to be defined at the same moment as the entry, in the same formula, from information available on the signal bar. A stop you will “decide later, when you see how it trades” cannot size anything, and a backtest of it is a backtest of a different system from the one you would run.
Where the distance comes from is a genuine design choice with no universally correct answer. The three usual families:
- Structural. Below a recent swing low, a support level, or the low of the last n bars. The distance is set by where the market has recently turned, so it varies with the shape of the chart rather than with a formula’s parameter.
- Volatility-based. A multiple of Average True Range or of standard deviation. The distance scales with how much the instrument normally moves. The next lesson is entirely about this family.
- Fixed percentage. A flat “eight per cent below entry”. Simple, and it quietly assumes every instrument in the universe has the same normal daily movement, which no universe does.
The formula below uses the structural version, so you can see the mechanism separated from the volatility question.
Risk-based sizing and equal weight answer different questions
Section titled “Risk-based sizing and equal weight answer different questions”Equal weight — every position the same fraction of equity — is the most common sizing rule in retail backtests, partly because it is one line of AFL. It equalises money. Risk-based sizing equalises loss if the stop is hit. On the same three candidates, at a tenth of equity each:
| Candidate | Shares | Position value | % of equity | Dollar risk | Risk as % of equity |
|---|---|---|---|---|---|
| A | 500 | 10,000 | 10% | 750 | 0.75% |
| B | 500 | 10,000 | 10% | 200 | 0.20% |
| C | 40 | 10,000 | 10% | 600 | 0.60% |
Same money, and risk varying by a factor of nearly four across three positions. Neither table is right and the other wrong. They are answers to different questions:
- Equal weight asks how should capital be allocated? It is the natural rule if you think of your positions as a portfolio of holdings.
- Risk-based sizing asks how much should each idea be allowed to cost when it fails? It is the natural rule if you think of your positions as a series of bets with defined losses.
Risk-based sizing has one property that matters more than the philosophy: it makes the losing trades comparable. A backtest whose losses are all roughly the same size is far easier to reason about than one where a single position happened to be in a volatile symbol and contributed a third of the drawdown. Whether that improves the results is an empirical question you will answer in this part’s lab.
Saying it in AFL
Section titled “Saying it in AFL”What we are building
Section titled “What we are building”A complete long-only portfolio backtest in which the stop sits below the lowest low of the last twenty bars and every position is sized so that reaching that stop costs one per cent of portfolio equity. The formula has to solve one problem that is not obvious: AFL cannot see the portfolio equity while it runs.
The complete formula
Section titled “The complete formula”Complete runnable AFL
// risk-based-position-size.afl// Part 34 - Risk Per Trade and Stop Distance//// Sizes every position so that, if the stop is reached, the loss is the same// fixed percentage of portfolio equity whatever the instrument. The stop is// placed below the lowest low of the last StopLookback bars, so the distance// comes from price structure rather than from a fixed percentage of price.//// ASSUMPTIONS - change any of these and every number the report shows changes:// - Daily bars, split- and dividend-adjusted end-of-day data.// - Signals are read on the close of the signal bar. Every order is filled// at the NEXT bar's open. SetTradeDelays(1,1,1,1) enforces that.// - Commission 0.1% of trade value, charged on entry and on exit.// - Slippage is NOT modelled. Add it before you believe any figure this// produces; Part 28 shows how to fold it into the trade price.// - Stops are checked against the bar's High-Low range and filled AT the// stop level (ExitAtStop = 1). That is the optimistic assumption: a gap// through the stop fills far worse. Set ExitAtStop = 2 to exit on the// next bar's open instead, which is the pessimistic reading of the same// stop, and compare the two reports.// - No liquidity cap is set in the formula. Set "Limit trade size as % of// entry bar volume" on Settings -> Portfolio before you trust the fills.// - Long only. No short side is defined, so none is tested.//// How to run it: Formula Editor -> Send to Analysis -> Apply to: a watch list// you fixed in advance -> Range: a date range you fixed in advance -> Backtest.
// ---- Account and execution ------------------------------------------------MaxPositions = 10;
SetOption( "InitialEquity", 100000 );SetOption( "MaxOpenPositions", MaxPositions );SetOption( "AllowPositionShrinking", True );SetOption( "CommissionMode", 1 ); // 1 = commission as percent of tradeSetOption( "CommissionAmount", 0.1 ); // 0.1% each waySetOption( "ActivateStopsImmediately", True ); // we enter on the open
SetTradeDelays( 1, 1, 1, 1 );BuyPrice = Open;SellPrice = Open;RoundLotSize = 1; // whole shares only
// ---- Risk policy ----------------------------------------------------------RiskPercent = 1.0; // percent of portfolio equity risked if the stop is hitStopLookback = 20; // bars used to find the structural lowStopBufferPct = 0.5; // stop sits this far below that low, in percentMinStopPct = 1.0; // refuse to size off a stop closer than this, in percentMaxSizePercent = 20; // no single position may exceed this share of equity
// ---- Universe filter ------------------------------------------------------// Turnover, not share volume, because turnover is comparable across price// levels. A position you could not have filled is not evidence of anything.MinTurnover = 2000000;Liquid = MA( Close * Volume, 50 ) > MinTurnover;
// ---- Signals --------------------------------------------------------------TrendPeriod = 200;EntryPeriod = 50;
Trend = Close > MA( Close, TrendPeriod );Buy = Cross( Close, MA( Close, EntryPeriod ) ) AND Trend AND Liquid;Sell = Cross( MA( Close, EntryPeriod ), Close );
// When more symbols signal than there are free slots, something has to choose.// This is the User's Guide's own example: prefer the less extended candidate.// It is a real decision that changes results - Part 28 covers ranking properly.PositionScore = 100 - RSI( 14 );
// ---- Stop distance --------------------------------------------------------StopReference = LLV( Low, StopLookback );StopLevel = StopReference * ( 1 - StopBufferPct / 100 );MinDistance = Close * MinStopPct / 100;StopDistance = Max( Close - StopLevel, MinDistance );
// ---- Size from risk and stop ----------------------------------------------// Shares = ( Equity * RiskPercent / 100 ) / StopDistance.// The formula cannot see portfolio equity, so express the same quantity as a// percentage of equity: position value / equity = RiskPercent * Price / Stop.SizePercent = RiskPercent * Close / StopDistance;SizePercent = Min( SizePercent, MaxSizePercent );
// SetTradeDelays shifts Buy/Sell/Short/Cover and NOTHING else. The backtester// reads PositionSize and the stop amount on the bar the trade is entered, which// with a one-bar delay is the bar AFTER the signal. Shifting both arrays by one// bar makes the size and the stop the ones computed on the signal bar, using// only information that existed when the signal was given.// Ref() leaves Null on the first bar; Nz() replaces it. No trade can happen// there anyway, because the trend filter needs TrendPeriod bars of history.SizeAtEntry = Nz( Ref( SizePercent, -1 ), 0 );StopAtEntry = Nz( Ref( StopDistance, -1 ), 1 );
SetPositionSize( SizeAtEntry, spsPercentOfEquity );ApplyStop( stopTypeLoss, stopModePoint, StopAtEntry, 1 );How it works
Section titled “How it works”The formula has four sections, and only the last two are about risk.
The account and execution section fixes everything that would otherwise be inherited from whatever the Settings dialog happened to contain. Stating the initial equity, the position limit, the commission model and the trade delays in the formula means the assumptions travel with the code and appear in the report when you include the formula in it.
The signals section is a plain trend-following rule: a fifty-bar moving-average cross, gated by price above its two-hundred-bar average and by a turnover floor. It is deliberately ordinary. Nothing in this lesson depends on it being good.
The stop distance section takes the lowest low of the last twenty bars, drops it by half
a per cent so ordinary noise does not clip the stop, and measures the distance from the
close. Max() enforces a floor of one per cent of price, because a symbol that has been
pinned in a narrow range for twenty bars produces a distance close to zero, and dividing a
risk budget by something close to zero asks for a position larger than the account.
The sizing section does the conversion. AFL runs before the backtest, symbol by symbol, with no knowledge of what the portfolio will be worth when the trade is entered — so the share count cannot be computed directly. What can be computed is the percentage of equity that produces the wanted risk, because that percentage does not depend on the equity at all:
Fragment — not a complete formula
SizePercent = RiskPercent * Close / StopDistance;SetPositionSize( SizePercent, spsPercentOfEquity );spsPercentOfEquity tells the backtester to interpret the number as a percentage of
portfolio equity, and the backtester supplies the equity at the moment the trade is entered.
The User’s Guide’s own portfolio example uses exactly this construction for volatility-based
sizing.
The last part of that section is the one people get wrong. SetTradeDelays() shifts
Buy, Sell, Short and Cover — and nothing else. The documented mechanism is literally
Buy = Ref( Buy, -buydelay ) applied inside the backtester after your formula has run. The
price arrays, PositionSize and PositionScore are not shifted. So with a one-bar delay,
the trade is entered on the bar after the signal, and the backtester reads the position
size standing on that bar. Left uncorrected, your one per cent risk is computed from a
stop distance the signal never saw. Shifting both arrays by hand fixes it:
Fragment — not a complete formula
SizeAtEntry = Nz( Ref( SizePercent, -1 ), 0 );StopAtEntry = Nz( Ref( StopDistance, -1 ), 1 );Functions worth a closer look
Section titled “Functions worth a closer look”SetPositionSize( size, method )— sets the trade size and how to read it.spsPercentOfEquitymeans percent of portfolio equity;spsSharesmeans a share count;spsPercentOfPositionis for scaling in and out only. Calling it writes into the same underlying variable as a plainPositionSize = …assignment, so a later assignment silently overwrites it.ApplyStop( type, mode, amount, exitatstop, … )— the built-in stops, from formula level. Here:stopTypeLosswithstopModePoint, soamountis a distance in price points, and an array is allowed, which is what lets the distance differ per symbol and per bar. The fourth argument is not a Boolean: 0 checks only the trade price, 1 checks the bar’s high–low range and exits at the stop level, 2 checks high–low but exits on the next bar at the regular trade price.Nz( x, valueifnull )— replaces Null.Ref( x, -1 )leaves Null on the first bar, and a Null propagating into the sizing array is not something you want to debug later.SetOption( "AllowPositionShrinking", True )— decides what happens when the wanted size exceeds available cash: shrink the position, or refuse the trade. It changes the trade count, so record which one you used.
What you should see
Section titled “What you should see”Test it
Section titled “Test it”Run the backtest with the result list set to Detailed log (Settings → Report → Result list shows). Pick one entry and check it by hand:
- Note the entry date, the entry price and the share count from the log.
- Go to the chart, find the bar before the entry, and read the close and the lowest low of the twenty bars ending there.
- Compute the stop distance: that low, times 0.995, subtracted from the close.
- Multiply the share count by the stop distance. The result should be close to one per cent of the portfolio equity shown in the log at that bar.
It will not be exact. Whole-share rounding moves it a little, and the entry filled at the next bar’s open rather than at the close the sizing used — if the symbol gapped up overnight, the same money bought fewer shares and the realised risk is slightly below budget. A discrepancy of a few per cent is the arithmetic working. A discrepancy of a factor of two means the shift by one bar is missing.
Common errors
Section titled “Common errors”Two more that are easy to miss:
- No floor on the stop distance. Symptom: one or two positions dominate the equity
curve, and the Detailed log shows position size shrinking rescuing an absurd request.
Cause: a near-zero denominator. The
Max()floor exists for this. - Assuming the cap does not bind. Symptom: the realised risk on some trades is well
below the budget and you cannot see why. Cause:
MaxSizePercenttruncated the request. This is the cap doing its job, but you should know when it happens rather than discover it later — the Detailed log shows the requested and the actual size.
Take it further
Section titled “Take it further”Add a second ApplyStop() call with stopTypeNBar so a position that has gone nowhere for
thirty bars is released, and compare the two reports. A time stop does not change the risk
per trade at all, but it changes how long capital sits in positions that are not working,
which changes Exposure % and therefore every metric divided by it.
Gap risk: the part the stop does not control
Section titled “Gap risk: the part the stop does not control”A stop is an instruction to leave, not an assurance about the price at which you will leave. Between the close and the next open there is no trading, and the next open can be anywhere.
Return to candidate A: 666 shares at 20.00, stop at 18.50, intended loss 999. The company reports overnight and the share opens at 15.00. The stop triggers, the position is closed near the open, and the loss is 666 × 5.00 = 3,330 — three and a third times the budget, and 3.3% of the account rather than 1%. This is an illustration constructed to make the point, not a market observation, but the mechanism is entirely ordinary: earnings, guidance withdrawals, regulatory decisions, index deletions, and every trading halt that reopens at a different price.
Your backtest will usually not show you this.
Three consequences follow, and they are why the rest of this part exists.
- The risk per trade is a floor on the loss, not a ceiling. Sizing controls the loss in the ordinary case and does nothing in the extraordinary one.
- Gaps are correlated. A sector-wide announcement gaps every holding in that sector at once. Ten positions each risking one per cent are not risking ten per cent in total if they can all gap through their stops on the same morning. The next lesson but one is about exactly this.
- A stated risk rule is a claim about a distribution, and it is only accurate in the middle of it. A “two per cent rule” is a statement about the typical loss. It is not a statement about the worst one, and it is emphatically not a description of what happens when several positions fail together. Part 30 catalogues the backtesting errors that hide this; the two lessons after this one deal with the portfolio and psychological sides of it.
Position size is the product of a risk budget and a stop distance, and the arithmetic is a single division. Expressed as a percentage of equity — the form AmiBroker’s backtester can actually use — it becomes the risk fraction divided by the stop distance as a fraction of price, which is why tight stops produce concentrated positions rather than cautious ones.
You can now write that rule in AFL, including the correction that most published examples
omit: SetTradeDelays() moves only the signal arrays, so the sizing array and the stop
amount have to be shifted by hand or they will be read a bar late.
And you know the limit of the whole apparatus. The stop distance sets the loss in the
ordinary case. Overnight gaps, halted trading and correlated shocks all deliver losses the
rule never authorised, and the default ExitAtStop setting hides most of that from the
report. Sizing is the first line of defence, not the only one.
Check your understanding
Sources for this lesson
5 verified · checked 2026-09-01
- 01AFL Function Reference — SetPositionSizeamibroker.com/guide/afl/setpositionsize.html2026-08-31
- 02AFL Function Reference — ApplyStopamibroker.com/guide/afl/applystop.html2026-08-31
- 03AFL Function Reference — SetTradeDelaysamibroker.com/guide/afl/settradedelays.html2026-08-31
- 04AmiBroker User's Guide — Portfolio-level backtesting§ Setting up position sizeamibroker.com/guide/h_portfolio.html2026-08-31
- 05AmiBroker User's Guide — System test settings window§ General tab, Portfolio tabamibroker.com/guide/w_settings.html2026-09-01
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.