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.valuechop < 38.2chop > 61.8Example
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).