Calculate position size based on current volatility (ATR) and a fixed percentage of account equity.
Professional traders never risk a fixed dollar amount or a fixed number of points per trade. They size positions based on criteria that ideally takes in the current volatility of the instrument — because volatility defines how much room a trade needs to breathe relative to market conditions today.
The fixed-point problem is one of the most underappreciated issues in strategy development. Consider the S&P 500: a 100-point stop loss in the year 2000 represented roughly 10% of the index's value (S&P 500 near 1,000). At that level, a 100-point stop would be hit on many ordinary trading days — it was a wide, generous stop for its era. In 2024, with the S&P 500 above 5,000, a 100-point move is less than 2% of the index's value and is comfortably within normal intraday noise. The same 100-point stop that was wide in 2000 and might have taken several months to achieve is now a typical daily move.
A strategy built with a fixed 100-point stop in 2000 and backtested across 20 years of data is broken by design. The stops and targets that worked in the early period become progressively miscalibrated as the instrument's price level and volatility evolve. ATR is one way that can help. It fixes this by anchoring every distance — stop, target, trailing stop — to how much the instrument is actually moving right now. This isn't a silver bullet and there are circumstances where we need to be careful; however, let's first look at how it's constructed.
Position size under ATR-based sizing comes out of a single expression:
``` stop_distance = ATR × atr_multiplier (in price units) cash_per_unit = stop_distance × point_value (what one unit of the instrument loses if the stop hits) position_size = cash_risk_per_trade / cash_per_unit (contracts / shares / lots) ```
Worked example: S&P 500 futures (ES). ATR(14) = 40 points. You choose a 2× ATR stop, so stop distance = 80 points. ES has a point value of $50 per contract, so cash per contract if the stop hits = 80 × $50 = $4,000. If your fixed risk per trade is $200, position size = 200 / 4,000 = 0.05 contracts — which means you cannot trade this signal with a standard ES contract and should either move to the Micro E-mini (MES, $5/point → 0.5 MES contracts, still sub-1) or skip the trade. That kind of answer is exactly what this formula is for: it tells you honestly whether the trade is sizeable at your risk tolerance.
To make the scale problem concrete, here is a comparison of what a 100-point stop has meant on the S&P 500 at different points in history:
| Year | S&P 500 Level | 100-point stop = | ATR(14) approx. | |---|---|---|---| | 2000 | ~1,000 | ~10% of price | ~20–30 pts | | 2008 | ~1,200 | ~8% of price | ~30–60 pts | | 2015 | ~2,000 | ~5% of price | ~20–30 pts | | 2020 | ~3,000 | ~3% of price | ~80–120 pts | | 2024 | ~5,000 | ~2% of price | ~50–80 pts |
In 2000, a 100-point stop was roughly 3–5 ATR — very generous, rarely hit on normal days. In 2024, the same 100-point stop is 1–2 ATR — well within normal daily noise. A system optimised with a 100-point stop in 2000 will generate far more stop-outs in 2024 than it was designed for, not because the strategy logic changed, but because the instrument's scale changed.
The same issue applies to any instrument that trends upward over time: stocks, indices, gold, Bitcoin. A fixed-point stop progressively tightens (relative to the instrument) as price rises.
The formula is simple:
You automatically trade half the size in volatile conditions, protecting the account. In calmer conditions, the stop shrinks and the position size grows — you take larger positions when the market is offering cleaner, lower-noise conditions.
The goal of ATR-based sizing is consistent cash risk per trade, not consistent position size. Every trade risks the same percentage of equity, regardless of:
This is what makes the strategy scale-invariant. It can be applied to different instruments and different time periods without recalibration. The math adjusts automatically.
Markets cycle through volatility regimes. After a major news event or a financial crisis, daily ATR can triple or quadruple compared to normal conditions. During quiet summer trading, ATR might compress to a fraction of its normal value.
A fixed-point strategy treats all these conditions identically. ATR-based sizing responds:
This is why professional systematic traders almost universally use some form of volatility-based sizing. It is not just about being precise — it is about building a strategy that remains valid across the full range of market conditions it will encounter.
When running a backtest with ATR-based sizing, the `strategy.equity` variable gives you the current account value as it evolves. This means position sizes will also evolve as the account grows or shrinks during the backtest — which gives you a realistic simulation of how the strategy would behave in live trading with compounding.
The key inputs to tune are the ATR length (standard is 14), the ATR multiplier for stops (typically 1.5–3.0), and the risk percentage per trade (commonly 0.5–2.0% for systematic strategies).
//@version=6
strategy("ATR Position Sizing", overlay=true, default_qty_type=strategy.fixed, default_qty_value=1)
// Risk parameters
riskPercent = input.float(1.0, "Risk Per Trade (%)", step=0.1)
atrLength = input.int(14, "ATR Length")
atrMult = input.float(2.0, "ATR Stop Multiplier")
// Account info
accountBalance = strategy.equity
// ATR calculation
atrValue = ta.atr(atrLength)
stopDistance = atrValue * atrMult
// Position size calculation
riskAmount = accountBalance * (riskPercent / 100)
positionSize = math.floor(riskAmount / stopDistance)
// Simple entry logic
longCondition = ta.crossover(ta.ema(close, 9), ta.ema(close, 21))
shortCondition = ta.crossunder(ta.ema(close, 9), ta.ema(close, 21))
if (longCondition)
strategy.entry("Long", strategy.long, qty=positionSize)
strategy.exit("Long SL", "Long", stop=close - stopDistance, limit=close + stopDistance * 2)
if (shortCondition)
strategy.entry("Short", strategy.short, qty=positionSize)
strategy.exit("Short SL", "Short", stop=close + stopDistance, limit=close - stopDistance * 2)