Skip to content
Level 4 · Trading System ResearcherLessonPart 30 · page 5 of 828 min
28Minutes
12AFL functions
5Sources
StandardRequires
AFL functions taught here12

Position Sizing and Portfolio Errors

Position sizing gets less attention than entry rules and does more damage. An entry rule that is slightly wrong produces a slightly worse result. A sizing rule that is wrong produces a result that no account could have achieved at all, and it does so without any of the numbers looking strange.

Error 1: compounding into positions the market could not absorb

Section titled “Error 1: compounding into positions the market could not absorb”

The default sizing rule for a portfolio backtest is a percentage of equity:

Fragment — not a complete formula

SetPositionSize( 100 / PosQty, spsPercentOfEquity );

Nothing in that line knows anything about the instrument. It knows about your account.

So as the backtest compounds, every position grows. A system that turns £100,000 into £800,000 over fifteen years is, by the end, requesting positions eight times larger in exactly the same instruments — and if some of those instruments were thin at the start, they are now being asked to absorb orders that dwarf their entire daily turnover.

The result is a beautiful exponential equity curve whose later half is arithmetically impossible.

Complete runnable AFL

participation-capped-sizing.afl
// participation-capped-sizing.afl
// Part 30 - Position Sizing and Portfolio Errors
//
// PURPOSE
// The same rule, sized three ways, so you can watch a position-sizing choice
// turn a plausible result into an impossible one. Mode 0 is the mistake:
// percent-of-current-equity sizing with no reference to how much of the
// symbol actually traded. Modes 1 and 2 are two honest repairs, each with a
// different cost.
//
// Run the same date range three times, changing only "Sizing mode", and
// compare Net Profit, Max. system % drawdown and - most importantly - the
// share counts in the trade list against the volume printed on the entry bar.
//
// ============================ ASSUMPTIONS =============================
// Universe whatever the Analysis window is applied to.
// Periodicity Daily.
// Decision bar signal evaluated on the bar's Close.
// Fill next bar's Open, delays 1 on all four signals.
// Slippage SlippagePct on every fill, in percent, both directions.
// Commission percent of trade value, both legs (CommissionMode 1).
// Liquidity 50-bar average turnover measured up to the decision bar.
// Participation ParticipationPct of that average turnover is the largest
// position this formula is willing to call realistic. This
// is a modelling assumption, not a market fact; there is no
// universally correct number, and it should be justified in
// your research log rather than inherited from a blog.
// Stops none. Exits are rule exits, so every fill comes from the
// price arrays below and nothing is filled at a level the
// backtester chose for you.
// ======================================================================
//
// Position sizing changes the size of losses as well as gains. Nothing here is
// advice about how much to risk; it is a demonstration of what a sizing rule
// does to a simulation.
SizingMode = Param( "Sizing mode: 0 uncapped, 1 filtered, 2 hard cap", 0, 0, 2, 1 );
AssumedAccount = Param( "Assumed account size", 100000, 10000, 10000000, 10000 );
PosQty = Param( "Max open positions", 10, 1, 50, 1 );
ParticipationPct = Param( "Max % of average daily turnover", 1, 0.05, 25, 0.05 );
BreakoutPeriod = Param( "Breakout lookback (bars)", 50, 10, 250, 5 );
ExitPeriod = Param( "Exit lookback (bars)", 20, 5, 100, 5 );
CommissionPct = Param( "Commission per trade (%)", 0.10, 0, 1.00, 0.01 );
SlippagePct = Param( "Slippage per fill (%)", 0.15, 0, 2.00, 0.01 );
// ---------------------------------------------------------------------
// 1. Portfolio settings
// ---------------------------------------------------------------------
SetOption( "InitialEquity", AssumedAccount );
SetOption( "MaxOpenPositions", PosQty );
SetOption( "AllowPositionShrinking", True );
SetOption( "CommissionMode", 1 );
SetOption( "CommissionAmount", CommissionPct );
// Percent-of-equity sizing uses the CURRENT (intraday) equity by default, which
// means a position can be sized using money that only exists because of a gain
// booked on the very same bar. Switching this on sizes from the previous bar's
// closing equity instead, which is the more defensible of the two.
SetOption( "UsePrevBarEquityForPosSizing", True );
SetTradeDelays( 1, 1, 1, 1 );
BuyPrice = Open * ( 1 + SlippagePct / 100 );
SellPrice = Open * ( 1 - SlippagePct / 100 );
ShortPrice = Open * ( 1 - SlippagePct / 100 );
CoverPrice = Open * ( 1 + SlippagePct / 100 );
// ---------------------------------------------------------------------
// 2. The rule. Identical in all three modes, so any difference in the report
// is caused by sizing alone.
// ---------------------------------------------------------------------
Turnover = MA( Close * Volume, 50 );
BreakoutLevel = Ref( HHV( High, BreakoutPeriod ), -1 );
ExitLevel = Ref( LLV( Low, ExitPeriod ), -1 );
Buy = Close > BreakoutLevel AND Volume > 0;
Sell = Close < ExitLevel;
PositionScore = Turnover;
// ---------------------------------------------------------------------
// 3. Sizing. This is the only thing the three modes disagree about.
//
// BaseDollars is what an equal-weight allocation of the ASSUMED account
// would put into one position. CapDollars is the largest position that
// ParticipationPct of the symbol's own average turnover would support.
// The ratio between them is the number the mistake ignores.
// ---------------------------------------------------------------------
BaseDollars = AssumedAccount / PosQty;
CapDollars = ( ParticipationPct / 100 ) * Turnover;
WithinCap = BaseDollars <= CapDollars;
if( SizingMode == 0 )
{
// THE MISTAKE. Equal-weight percent of equity, no liquidity condition at
// all. As equity compounds, each position grows, and on a thin symbol the
// requested share count can exceed everything that traded that day.
PositionSize = -100 / PosQty;
}
else if( SizingMode == 1 )
{
// REPAIR 1 - refuse the trade. Symbols that cannot absorb the intended
// position at the assumed account size are simply not traded. Percent-of
// -equity sizing is kept, so the test still compounds, and the cap is only
// approximate once equity has moved far from the assumed account.
Buy = Buy AND WithinCap;
PositionSize = -100 / PosQty;
}
else
{
// REPAIR 2 - cap the size. A position is the smaller of the equal-weight
// allocation and the participation cap, expressed in currency. Positive
// PositionSize values are documented as a dollar amount. This removes
// compounding, which is a real cost: the test now answers "what would this
// rule have done at a fixed account size", not "what would it have
// compounded to".
PositionSize = Min( BaseDollars, CapDollars );
}
// ---------------------------------------------------------------------
// 4. Chart view: the participation ratio, drawn so you can see which symbols
// and which periods the assumption is doing all the work in.
// ---------------------------------------------------------------------
_SECTION_BEGIN( "Participation view" );
ParticipationRatio = 100 * SafeDivide( BaseDollars, Turnover, 0 );
// One line, one meaning: how far the intended position sits above the cap, in
// percentage points of average daily turnover. Anything above zero is a
// position this formula considers too large to have been filled quietly.
CapExcess = ParticipationRatio - ParticipationPct;
Plot( Close, "Close", colorDefault, styleCandle );
Plot( CapExcess, "Intended size above participation cap (pp of turnover)",
colorRed, styleLine | styleOwnScale );
_SECTION_END();

Download participation-capped-sizing.afl136 lines

The same rule, sized three ways. Run the same date range three times, changing only SizingMode, and compare Net Profit, maximum system drawdown, and — most importantly — the share counts in the trade list against the volume printed on each entry bar.

Mode 0 — the mistake. Percent of equity, no reference to liquidity at all. This is what most backtests do by default.

Mode 1 — refuse the trade. Symbols that cannot absorb the intended position at the assumed account size are simply not traded:

Fragment — not a complete formula

BaseDollars = AssumedAccount / PosQty;
CapDollars = ( ParticipationPct / 100 ) * Turnover;
WithinCap = BaseDollars <= CapDollars;
Buy = Buy AND WithinCap;
PositionSize = -100 / PosQty;

Compounding is kept, so the test still answers “what would this have grown to” — but the cap is only approximate once equity has moved far from the assumed account size.

Mode 2 — cap the size. The position is the smaller of the equal-weight allocation and the participation cap, expressed in currency:

Fragment — not a complete formula

PositionSize = Min( BaseDollars, CapDollars );

A positive PositionSize is documented as a dollar value, which is what makes this work. The cost is real and must be stated: this removes compounding. The test now answers “what would this rule have done at a fixed account size”, which is a different and more modest question.

Error 2: sizing from equity you did not have yet

Section titled “Error 2: sizing from equity you did not have yet”

By default, percent-of-current-equity sizing uses the current (intraday) equity. The documentation for UsePrevBarEquityForPosSizing states it directly:

False (default value) means: use current (intraday) equity to perform position sizing, True means: use previous bar closing equity to perform position sizing.

The consequence: on a day when your existing positions gained, the new position can be sized using money that only exists because of a gain booked on that very same bar — a gain you could not have known about when the order was placed.

Fragment — not a complete formula

// The more defensible of the two.
SetOption( "UsePrevBarEquityForPosSizing", True );

The effect is usually small per trade, and it accumulates in one direction: bigger positions on up days, which is precisely the correlation that flatters a backtest.

Error 3: ignoring cash and margin constraints

Section titled “Error 3: ignoring cash and margin constraints”

Four settings decide whether the backtest is spending money it has.

Setting The error when it is wrong
AccountMargin Left below 100 by accident, the test is leveraged and you did not decide to be
AllowPositionShrinking Off means unaffordable signals are skipped; on means they are taken smaller. Both are legitimate, and they produce different trade lists
MinPosValue Not set means the test can take positions of £40, which no broker’s commission structure survives
MinShares Same problem in share terms

Ten positions is not ten bets when the ten are the same bet.

A system that enters on a market-wide condition will frequently hold ten positions in the same sector, entered on the same day, on the same signal. The portfolio backtest models this correctly — it is your interpretation that is wrong if you read “ten positions” as “diversified”.

The symptoms are visible in the report:

  • Maximum system drawdown much larger than any individual position’s stop distance would suggest
  • An equity curve with few deep valleys rather than many shallow ones
  • Days on which most open positions move together

Part 34’s concentration lesson measures it. The point here is that it is a sizing error: you sized as though the positions were independent, and they were not.

Error 5: assuming a 2% risk rule makes you safe

Section titled “Error 5: assuming a 2% risk rule makes you safe”

“Risk no more than 2% of equity per trade” is the most widely repeated position-sizing rule there is. It is a reasonable starting point and it is not a safety guarantee, for four separate reasons.

It assumes the stop holds. 2% risk means 2% if you get out at your stop. A gap through it is unbounded, and — per the previous lesson — a backtest with ExitAtStop = 1 has never shown you a single one.

It is per trade, not per day. Ten correlated positions each risking 2% is a 20% risk to a single market-wide move, not a 2% one. The rule constrains the wrong unit.

It says nothing about frequency. Risking 2% per trade forty times a year is a completely different exposure from risking 2% four times a year, and the rule as usually stated does not mention this.

It assumes your stop distance is meaningful. A 2% risk with a stop placed one tick away is a large position that will be stopped out constantly; the same 2% with a very wide stop is a small position that rarely stops. The rule fixes the risk and lets the position size float — which means it is really a statement about your stop, and the stop is doing all the work.

Symptoms, in the order you are likely to meet them:

The equity curve is smoothly exponential. Real portfolio equity is lumpy. A curve that looks like a clean compound-interest plot usually means positions are scaling with equity into instruments that could not have absorbed them.

Almost all the profit is in the last third of the test. Expected under compounding, and also exactly what a participation error looks like. Distinguish them by checking the share counts.

The trade list contains share counts you would not place. Sort the trade list by size and look at the largest twenty entries against the volume on those bars. This is the direct test, and it takes five minutes.

The result changes dramatically with PosQty. Sensitivity to slot count is normal; a dramatic sensitivity often means one or two very large positions carried the result.

The drawdown is smaller than the sum of your per-trade risks on a bad day. Then either the positions are less correlated than they look, or the stops are being filled at prices that were not available.

Percent-of-equity sizing knows about your account and nothing about the instrument, so a compounding backtest grows positions into markets that cannot absorb them — and the error hides itself, because the impossible trades are the profitable late ones. Sizing from intraday equity uses money booked on the same bar. AccountMargin, AllowPositionShrinking, MinPosValue and MinShares decide whether the test is spending money it has. Ten correlated positions are one position. And a 2% risk rule constrains a single trade under the assumption that the stop holds — it is not a statement about your portfolio, your correlation, your gaps, or your trading frequency.

Check your understanding

Question 1. Why is the compounding-into-illiquid-positions error described as self-concealing?
Show the answer and why

Answer: Because the impossible trades are the late, large ones that contribute most of the profit — so the more damage the error does, the better the result looks

The error grows with the account, so its worst instances are also its most profitable ones. Nothing in the report flags them, and the researcher has the least incentive to go looking. Sorting the trade list by share count and comparing against entry-bar volume is the five-minute test.

Question 2. Mode 2 caps the position with PositionSize = Min( BaseDollars, CapDollars ). What is the documented meaning of a positive PositionSize, and what does this cost?
Show the answer and why

Answer: A dollar value — and it removes compounding, so the test now answers "what would this rule have done at a fixed account size"

The SetPositionSize documentation states that values above 0 encode a dollar value. Fixing the position in currency terms removes the compounding, which is a real and stateable cost of the repair rather than a free improvement.

Question 3. What does SetOption( "UsePrevBarEquityForPosSizing", True ) change?
Show the answer and why

Answer: It uses the previous bar's closing equity for sizing instead of the current intraday equity, so a position cannot be sized using a gain booked on the same bar

The documented default is False, meaning current intraday equity. The effect of leaving it there is small per trade and accumulates in one direction: bigger positions on up days, which flatters the result.

Question 4. Which criticisms of "risk 2% of equity per trade" are accurate? Select all that apply.
Show the answer and why

Answer: It assumes the stop is honoured, so a gap through it is unbounded, It constrains a single trade, not a day — ten correlated positions each risking 2% expose 20% to one market-wide move, It says nothing about how often you trade

The rule is a reasonable constraint on one quantity. The error is reading it as a statement about portfolio safety, which requires separate constraints on correlation, total exposure, gap risk and frequency.

Question 5. A backtest produces a smoothly exponential equity curve with most profit in the final third. What should you check first?
Show the answer and why

Answer: The share counts of the largest late trades against the volume on their entry bars

That profile is consistent with ordinary compounding and equally consistent with positions that grew past what the instruments could absorb. Only the share-count-versus-volume check distinguishes them, and it is quicker than any other diagnostic here.

Sources for this lesson

5 verified · checked 2026-09-01

  1. 01AFL Function Reference — SetPositionSizeamibroker.com/guide/afl/setpositionsize.html2026-08-31
  2. 02AFL Function Reference — SetOption§ UsePrevBarEquityForPosSizing, AccountMargin, MinPosValue, AllowPositionShrinkingamibroker.com/guide/afl/setoption.html2026-08-31
  3. 03AmiBroker User's Guide — Settings window§ Use previous bar equity for position sizingamibroker.com/guide/w_settings.html2026-09-01
  4. 04AmiBroker User's Guide — Portfolio-level backtestingamibroker.com/guide/h_portfolio.html2026-08-31
  5. 05AFL Function Reference — SafeDivideamibroker.com/guide/afl/safedivide.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.