StochasticMomentumIndex — Stochastic Momentum Index
Where the close sits relative to the midpoint of the range rather than its low, double smoothed. Because the reference is the middle, the SMI is centred on zero and reads −100 to +100 — an easier scale to reason about than the stochastic’s 0–100.
Signature
StochasticMomentumIndex(period, smooth1, smooth2, signal_period, on=<source>)| Argument | Type | Required | Notes |
|---|---|---|---|
period |
int Param or literal | Yes | Range lookback. Default 10, bounds 1–400. |
smooth1 |
int Param or literal | Yes | First EMA smoothing. Default 3, bounds 1–100. |
smooth2 |
int Param or literal | Yes | Second EMA smoothing. Default 3, bounds 1–100. |
signal_period |
int Param or literal | Yes | EMA of the SMI. Default 3, bounds 1–100. |
on= |
symbol | No | Source symbol. Defaults to the trade symbol. Reads high, low, close. |
Formula
$$ \text{SMI} = 200,\frac{\text{EMA}(\text{EMA}(C - \tfrac{HH+LL}{2}))}{\text{EMA}(\text{EMA}(HH - LL))} $$
Warmup
period bars for the range plus the smoothing chains; converges over roughly
period + 3 × smooth1.
Reading the value
smi.smismi.signalsmi.smi < -40Multi-value: cannot be chained onto or crossed.
Example
class SmiReversion(Strategy): """Fade an SMI extreme once it turns back through its signal.""" trade = Symbol() sizing = FixedCapital()
def execute(self): smi = StochasticMomentumIndex(10, 3, 3, 3, on=self.trade) if smi.smi < -40 and smi.smi > smi.signal and self.trade.is_flat: self.buy(self.trade, reason=f"SMI {smi.smi:.0f} turning") if smi.smi > 40 and self.trade.is_long: self.sell(self.trade, self.trade.position, reason="SMI stretched")Notes
- William Blau (1993). Four parameters is at the budget ceiling — sweep
periodfirst. - Proven by an assert-via-trade run on SPY daily (stayed inside its ±200 bound on every bar of a 140-bar run and tracked the range midpoint correctly; zero alarm trades).