דלגו לתוכן
פתיחת RaamTrade

NormalizedRange — Normalized Range

תוכן זה אינו זמין עדיין בשפה שלך.

The Donchian channel width as a percentage of the channel midpoint. The same measurement as PriceChannelWidth, rescaled so it means the same thing on every instrument.

NormalizedRange is the identifier a strategy writes; Normalized Range is the label the chart’s indicator picker shows. Single value — .value is the only accessor.

Signature

NormalizedRange(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

Over the last N values of the source, with hi = max, lo = min:

$$ \text{mid} = \frac{\text{hi} + \text{lo}}{2}, \qquad \text{NormalizedRange} = \frac{\text{hi} - \text{lo}}{\max(\text{mid},\ 0.0001)} \times 100 $$

The result is a percentage: 3.0 means the last N closes spanned 3% of their own midpoint. The denominator is floored at a small epsilon so a midpoint at or near zero cannot divide by zero.

Warmup

period bars. Before the window fills the indicator returns 0 — unlike the raw channel indicators, which report their partial-window value from bar one. A strategy never observes it (execute() waits until every indicator is ready); it is what the chart plots on the leading bars.

Reading the value

nr.value # percent, e.g. 3.0 means a 3%-wide channel
nr.value_at(1)
nr < 2 # quiet
nr > 8 # wide
nr.crossed_above(5)

Example

class QuietRangeEntry(Strategy):
"""Enter trends only while the normalized channel is tight."""
period = Param(20, min=2, max=200)
calm = Param(4, min=1, max=50)
trade = Symbol()
sizing = FixedCapital()
def execute(self):
nr = NormalizedRange(self.period, on=self.trade)
sma = SMA(self.period, on=self.trade)
self.stop_loss(2.0)
if nr.value < self.calm and self.trade.close > sma.value and self.trade.is_flat:
self.buy(self.trade, reason=f"Calm channel ({nr.value:.1f}%)")
if nr.value > self.calm * 3 and self.trade.is_long:
self.sell(self.trade, self.trade.position, reason="Range expanded")

Notes

  • This is the volatility measure to optimize over. A threshold like “below 3%” transfers between symbols and between eras; the same threshold in price units does not.
  • The value is already a percentage — do not multiply by 100 again.
  • Careful when chaining. The epsilon floor protects against a zero midpoint, not a negative one. Chained onto a source that goes negative (an oscillator, a spread), a negative midpoint is clamped to 0.0001 and the reading explodes to an enormous positive number. Chain onto price-like sources.
  • Two closes set the whole value, as with the raw channel width. For a measure that weighs every bar, use StdDev or ATR; for annualized volatility, VolatilityStdDev.