Skip to content
Open RaamTrade

PriceChannelWidth — Price Channel Width

The width of the close-based Donchian channel: highest close minus lowest close over the last N bars. A volatility measure with no smoothing in it at all.

PriceChannelWidth is the identifier a strategy writes; Price Channel Width is the label the chart’s indicator picker shows. Single value — .value is the only accessor.

Signature

PriceChannelWidth(period, on=<source>)
Argument Type Required Notes
period int Param or literal Yes Lookback window. Default 20, bounds 2–200.
on= symbol, OHLCV field, or indicator No Source. Defaults to the trade symbol’s close.

Formula

$$ \text{Width}t = \max(\text{close}{t-N+1..t}) - \min(\text{close}_{t-N+1..t}) $$

Always ≥ 0, in the source’s units (normally price). It is the distance between DonchianHigh and DonchianLow over the same window.

Warmup

period bars — that is when the buffer is full and execute() starts.

The value has no dead zone: both branches of the seeded source return max(buffer) − min(buffer), so the leading bars plot the width of a partial window. That width starts near zero and can only grow as the window fills, which makes the left edge of the plot slope upward for reasons that have nothing to do with the market.

Reading the value

width.value # price units
width.value / self.trade.close # fraction of price — portable across instruments
width.value_at(1)
width > width.value_at(10) # is the range expanding?

Example

class WidthFilteredBreakout(Strategy):
"""Take breakouts only out of a channel that has gone quiet."""
period = Param(20, min=2, max=200)
max_width = Param(5, min=1, max=50)
trade = Symbol()
sizing = FixedCapital()
def execute(self):
width = PriceChannelWidth(self.period, on=self.trade)
upper = DonchianHigh(self.period, on=self.trade)
self.stop_loss(2.0)
width_pct = width.value / self.trade.close * 100
breakout_level = upper.value_at(1)
if width_pct < self.max_width and self.trade.close > breakout_level and self.trade.is_flat:
self.buy(self.trade, reason=f"Breakout from a {width_pct:.1f}% channel")
if self.trade.close < breakout_level and self.trade.is_long:
self.sell(self.trade, self.trade.position, reason="Lost the channel high")

Notes

  • Unbounded and price-scaled. A width of 4.0 is a wide channel on a $20 stock and a rounding error on a $4,000 index. Divide by price before comparing instruments — or use NormalizedRange, which is exactly this measure divided by the channel midpoint.
  • Two closes decide the entire value; the rest of the window is ignored. That makes it jump when an extreme leaves the window, in a way ATR and StdDev never do.
  • Close-based, so it measures the range of settled prices. It will read narrower than the true high-low range whenever wicks are doing the work.
  • A contracting width is the classic “squeeze” setup: quiet range, then a breakout. That is the shape the example above trades.