← Back to Home
Impulse-Driven Breakouts with Volatility-Gated Exits

Impulse-Driven Breakouts with Volatility-Gated Exits

A breakout is only worth chasing when momentum and trend agree. The Elder Impulse framework blends a rising EMA slope with a strengthening MACD histogram to define impulse. A Bollinger-band pierce provides structural confirmation, a long-trend SMA filters direction, and exits are prioritized: take-profit at distance, volatility collapse to avoid dead tape, and an ATR trail to protect gains.

Mechanics

Impulse block: EMA slope and MACD histogram slope in the same direction
Structure: close beyond Bollinger bands
Trend filter: price vs. SMA
Risk: ATR-based trailing stop plus fixed ATR take-profit
Disengage: volatility collapse via rolling standard deviation normalized by price

Main Logic (strategy loop only)

def next(self):
    if self.order:
        return

    if not self.position:
        ema_up   = self.impulse_ema[0] > self.impulse_ema[-1]
        hist_up  = self.macd_histo[0]  > self.macd_histo[-1]
        ema_dn   = not ema_up
        hist_dn  = not hist_up

        bull_imp = ema_up and hist_up
        bear_imp = ema_dn and hist_dn

        uptrend  = self.data.close[0] > self.trend_sma[0]
        downtrend= self.data.close[0] < self.trend_sma[0]

        bb_up    = self.data.close[0] > self.bband.top[0]
        bb_dn    = self.data.close[0] < self.bband.bot[0]

        if bull_imp and uptrend and bb_up:
            self.order = self.buy()
        elif bear_imp and downtrend and bb_dn:
            self.order = self.sell()

    else:
        vol_collapse = (self.stddev[0] / self.data.close[0]) < self.p.vol_exit_threshold

        if self.position.size > 0:  # long
            # 1) take-profit
            if self.data.high[0] >= self.take_profit_price:
                self.order = self.close(); return
            # 2) volatility collapse
            if vol_collapse:
                self.order = self.close(); return
            # 3) ATR trail
            self.highest_price_since_entry = max(self.highest_price_since_entry, self.data.high[0])
            new_stop = self.highest_price_since_entry - self.atr[0] * self.p.atr_stop_multiplier
            self.stop_price = max(self.stop_price, new_stop)
            if self.data.close[0] < self.stop_price:
                self.order = self.close()

        elif self.position.size < 0:  # short
            # 1) take-profit
            if self.data.low[0] <= self.take_profit_price:
                self.order = self.close(); return
            # 2) volatility collapse
            if vol_collapse:
                self.order = self.close(); return
            # 3) ATR trail
            self.lowest_price_since_entry = min(self.lowest_price_since_entry, self.data.low[0])
            new_stop = self.lowest_price_since_entry + self.atr[0] * self.p.atr_stop_multiplier
            self.stop_price = min(self.stop_price, new_stop)
            if self.data.close[0] > self.stop_price:
                self.order = self.close()

Why it holds up

Impulse alignment reduces false breakouts; the band pierce ensures structural expansion; the trend gate enforces directional bias; and the exit hierarchy captures convexity while cutting dead time when volatility compresses.

Tuning guide

Increase bb_devfactor to curb shallow pierces, or lengthen bb_period to emphasize slower expansions. Tighten vol_exit_threshold to leave coils earlier; widen atr_tp_multiplier for fatter tails; adjust atr_stop_multiplier to trade off staying power vs. giveback.

Rolling Backtest

Pasted image 20250815215724.png

Overall, this is a high-volatility, high-return breakout profile with an excellent hit rate and relatively modest drawdowns given the aggressive upside capture.