ATRPercent — ATR Percent
Wilder’s ATR divided by price, times 100. Thirty points of ATR means nothing across instruments; 1.2% means the same thing everywhere — which makes this the right input to volatility-scaled position sizing and cross-instrument comparisons.
Signature
ATRPercent(period, on=<source>)| Argument | Type | Required | Notes |
|---|---|---|---|
period |
int Param or literal | Yes | ATR length. Default 14, bounds 1–100. |
on= |
symbol | No | Source symbol. Defaults to the trade symbol. Reads high, low, close. |
Formula
$$ \text{ATR%} = 100,\frac{\text{ATR}(n)}{C} $$
Warmup
The ATR seeds as a cumulative average over the first period bars, then Wilder smoothing;
converges over roughly 3n bars.
Reading the value
atp.valueatp.value > 2.0Example
class VolScaledEntry(Strategy): """Only trade when daily volatility is inside a chosen band.""" period = Param(14, min=1, max=100) trade = Symbol() sizing = FixedCapital()
def execute(self): atp = ATRPercent(self.period, on=self.trade) sma = SMA(50, on=self.trade) if atp.value < 2.0 and self.trade.close > sma.value and self.trade.is_flat: self.buy(self.trade, reason=f"Calm tape, ATR {atp.value:.2f} pct") if atp.value > 3.0 and self.trade.is_long: self.sell(self.trade, self.trade.position, reason="Volatility spike")Notes
- Wilder’s ATR (1978), normalised by the close — the same construction TA-Lib calls NATR (which lands in Tier 2 under that name for members who search for it).
- Proven by an assert-via-trade run on SPY daily (converged to an independent ATR replication within 0.02 once warm; zero alarm trades).
- The recursion seeds from the first bar rather than from a warm-up window, so early values
are not comparable with pandas_ta’s
natr(14), which seeds from an average of the firstn; measured on a 300-bar tape the two agree to 1e-6 from bar 149 onward.