ChandeKrollStop — Chande Kroll Stop
ATR-offset stop levels on both sides of the market, then smoothed: the raw long stop is the
n-bar high minus a multiple of ATR, and the published level is the highest such raw stop over
a second window. Wider and steadier than a plain ATR trail.
Signature
ChandeKrollStop(period, multiplier, stop_period, on=<source>)| Argument | Type | Required | Notes |
|---|---|---|---|
period |
int Param or literal | Yes | Window for the extremes and the ATR. Default 10, bounds 1–200. |
multiplier |
float Param or literal | Yes | ATR offset. Default 3.0, bounds 0.1–10.0. |
stop_period |
int Param or literal | Yes | Smoothing window over the raw stops. Default 9, bounds 1–200. |
on= |
symbol | No | Source symbol. Defaults to the trade symbol. Reads high, low, close. |
Formula
$$ \text{raw long} = \max(H, n) - m,\text{ATR}, \quad \text{raw short} = \min(L, n) + m,\text{ATR} $$ $$ \text{long} = \max(\text{raw long}, s), \quad \text{short} = \min(\text{raw short}, s) $$
Warmup
period + stop_period bars; the ATR seeds cumulatively over its first period bars.
Reading the value
cks.longcks.shortself.trade.close < cks.longMulti-value: cannot be chained onto or crossed.
Example
class CksTrail(Strategy): """Trail a long position on the Chande Kroll long stop.""" trade = Symbol() sizing = FixedCapital()
def execute(self): cks = ChandeKrollStop(10, 3.0, 9, on=self.trade) sma = SMA(50, on=self.trade) if self.trade.close > sma.value and self.trade.is_flat: self.buy(self.trade, reason="Trend entry") if self.trade.close < cks.long and self.trade.is_long: self.sell(self.trade, self.trade.position, reason=f"Stop {cks.long:.2f}")Notes
- Tushar Chande and Stanley Kroll, The New Technical Trader (1994).
- Proven by an assert-via-trade run on SPY daily (both levels matched a full strategy-side replication within 0.05 once the ATR converged; zero alarm trades).
- The long stop can sit above the short stop when the channel is wider than 2 × multiplier × ATR — that is the construction, not an error.