Skip to content
Open RaamTrade

ChoppinessIndex — Choppiness Index

How much of the summed true range went nowhere, on a 0–100 log scale. Near 100 the market is chopping in place; near 0 it is covering ground in one direction. Direction-blind, like ADX, but built from range efficiency rather than directional movement.

Signature

ChoppinessIndex(period, on=<source>)
Argument Type Required Notes
period int Param or literal Yes Window. Default 14, bounds 2–400.
on= symbol No Source symbol. Defaults to the trade symbol. Reads high, low, close.

Formula

$$ \text{CHOP} = 100,\frac{\log_{10}!\big(\sum TR_n / (\max H_n - \min L_n)\big)}{\log_{10}(n)} $$

Warmup

period bars; 100 (maximally choppy — the safe reading) until then, and 100 on a zero-range window.

Reading the value

chop.value
chop < 38.2
chop > 61.8

Example

class ChopGate(Strategy):
"""Breakouts only when the market has stopped chopping."""
trade = Symbol()
sizing = FixedCapital()
def execute(self):
chop = ChoppinessIndex(14, on=self.trade)
dh = DonchianHighTrue(20, on=self.trade)
if chop.value < 38.2 and self.trade.close >= dh.value_at(1) and self.trade.is_flat:
self.buy(self.trade, reason=f"CHOP {chop.value:.0f}")
if chop.value > 61.8 and self.trade.is_long:
self.sell(self.trade, self.trade.position, reason="Back to chop")

Notes

  • E.W. Dreiss. The 38.2/61.8 thresholds are Fibonacci folklore, not part of the formula.
  • math.log(x)/math.log(10) inside (RaamCode has no log10); the ratio is clamped at 1 so the value never goes negative on degenerate data.
  • Proven by an assert-via-trade run on SPY daily (matched a strategy-side reference to 0.01 on every bar; zero alarm trades).