← All ideas
#005BreakoutPosition

Channel breakout: whichever triggers first, Donchian or Keltner

Entry on a 20-day channel breakout, exit on a 40-day downside breakout. Of two channels, Donchian and Keltner, the one price touches first gives the signal. Antonacci tested it on 48 US sectors over 100 years, Basso trades a 9-day version.

The Algorithmic Advantage · Gary Antonacci · Watch video

Markets

Stocks, Indices, Futures, Crypto

Timeframe

D1

Data

OHLC

Rules

Partly formalised

Difficulty

Medium

Status

Untested

Some rules were added by us and are marked in the text.

TradingView has pitfalls
EasyLanguage has pitfalls
MetaTrader 5 has pitfalls

Idea in brief

A classic breakout has one level: the N-day high. Here there are two levels. Donchian marks the N-day high and low, Keltner draws a band around an average at a distance of several ATRs. They react differently to volatility, and on different days one or the other ends up closer to price. The rule is simple: the signal comes from whichever channel price touches first.

Gary Antonacci and Carlo Zarattini applied this to 48 US industry sectors over a hundred years. A long entry on the first touch of the upper boundary of a 20-day channel. An exit on the first touch of the lower boundary of a 40-day channel: the exit length was doubled to give the trade room and avoid excess turnover. There are no shorts, and position size is normalized to volatility.

Tom Basso uses the same technique with three indicators (Donchian, Bollinger, Keltner) and a short length of 9 days. This version is always in the market and reverses in both directions.

Why it might work

Both authors describe channels as a noise band. Inside it price moves without direction, and there is no point trading there: a range produces only whipsaws. Antonacci treats a move beyond the band as a sign that the move will continue, and the hypothesis is that "a new four-week high promises continuation". Basso compares this to a thermostat that needs a margin of a couple of degrees so it does not click every minute.

The width of Keltner and Bollinger bands depends on volatility. In a quiet market the lines converge, in a nervous one they spread apart automatically. Basso believes this is why such indicators survive changing conditions better than fixed moving average crossovers. The first touch picks whichever noise measure is currently closer to price. Basso says plainly that it does not matter which indicator places the order: all three measure the same noise.

In Antonacci's decomposition of the result, the main contribution to Sharpe came from timing, not from sector selection. Host Rich Brennan explains this by the fact that breakouts skip most small moves and work on the tails of the distribution. We have not verified these explanations.

Rules

Channels

// daily bars
DonchianUpper(N) = highest High over the last N closed bars
DonchianLower(N) = lowest  Low  over the last N closed bars

KeltnerUpper(N) = EMA(Close, N) + K * ATR(N)
KeltnerLower(N) = EMA(Close, N) - K * ATR(N)
// Basso describes Keltner as EMA 21 plus ATR over roughly 21 days, times a multiplier.
// Finetiq: for Antonacci the Keltner length equals the channel length (20 or 40), K = 2 as a starting value

BollingerUpper(N) = SMA(Close, N) + B * StdDev(Close, N)
BollingerLower(N) = SMA(Close, N) - B * StdDev(Close, N)
// Finetiq: B = 2, the width is not named in the video

First touch

// the level closest to price triggers first
FirstUp(N)   = min(DonchianUpper(N), KeltnerUpper(N))
FirstDown(N) = max(DonchianLower(N), KeltnerLower(N))

// Finetiq: a Keltner or Bollinger band can already be beyond price at the close.
// Then the touch has already happened: enter at market at the next open, without a stop order

Core system: Antonacci, long only

IF position = FLAT
    IF Close > KeltnerUpper(20)
        BUY AT NEXT BAR OPEN                  // Finetiq: level already passed at the close
    ELSE
        BUY STOP at FirstUp(20)               // author: first touch of the 20-day channel

IF position = LONG
    IF Close < KeltnerLower(40)
        EXIT AT NEXT BAR OPEN                 // Finetiq
    ELSE
        SELL STOP at FirstDown(40)            // author: first downside touch of the 40-day channel

// no shorts: stocks have an upward drift (author)
// no separate stop-loss, the exit on the 40-day channel plays that role

Position size (author, numbers Finetiq)

Vol    = StdDev(daily returns, 20) * sqrt(252)        // Finetiq: window not named in the video
Weight = TargetVol / Vol / M                          // author: volatility normalization
TargetVol = 10%                                       // author, from memory
M = number of instruments in the basket               // Finetiq: M = 1 for a single ETF
Weight = min(Weight, 1 / M)                           // Finetiq: no leverage
// daily rebalancing (author)
// basket: hold all sectors with a signal, no ranking (author)

Variant B. Basso: 9 days, three channels, always in the market

// index futures, always long or short, reversals in both directions (author)
// Basso uses the same 9-day combination on bitcoin and ether micro futures, the direction there is not specified
N = 9                                                          // author, for all three indicators

UpperLevel = min(DonchianUpper(9), BollingerUpper(9), KeltnerUpper(9))
LowerLevel = max(DonchianLower(9), BollingerLower(9), KeltnerLower(9))
// author: the line closest to price places the order, and it does not matter which indicator that is

IF position = SHORT OR position = FLAT
    BUY STOP at UpperLevel
IF position = LONG OR position = FLAT
    SELL SHORT STOP at LowerLevel
// Finetiq: the same rule for a level already passed at the close

// size: Basso's general principle
Units = Equity * 0.5% / (|EntryPrice - LowerLevel| * PointValue)   // for a long
// author: in a winning position the risk can grow to 1%, beyond that the position is trimmed

Variant C. Basso: three-day Donchian

// about 26–27 futures, always in the market (author)
BUY STOP at DonchianUpper(3)
SELL SHORT STOP at DonchianLower(3)
// the lower line is the long's trailing stop, no separate trailing stop is needed (author)
// inside days do not break the channel and are ignored automatically (author)

Parameters

Parameter Value Source
Entry channel (A) 20 days, Donchian and Keltner author
Exit channel (A) 40 days, Donchian and Keltner author
Trigger rule first touch of either channel author
Direction (A) long only author
Keltner multiplier K 2 Finetiq
Keltner center and width EMA and ATR of the same length Finetiq (Basso uses EMA 21 and ATR around 21)
Size (A) volatility normalization, target about 10% author
Rebalancing (A) daily author
Volatility window 20 days Finetiq
Level passed at the close market entry at the next open Finetiq
Length (B) 9 days, three indicators author
Bollinger width B 2 standard deviations Finetiq
Direction (B, C) always in the market, reversals author
Length (C) 3 days, Donchian only author
Risk per position (B, C) 0.5% of capital, up to 1% in a winning position author (Basso's general principle)

What to test

  1. First touch versus a single channel. Three runs of variant A: Donchian 20/40 only, Keltner 20/40 only, first touch. If the combination is not better than the better of the two, the second channel only adds trades.
  2. Keltner multiplier. K = 1.5, 2, 2.5, 3. With a small K the Keltner band is almost always closer to price and effectively replaces Donchian. Record which line gave the signal in each trade and look at the shares.
  3. The 20/40 asymmetry. Compare 20/40 with 20/20 and 20/10. The author doubled the exit length to reduce turnover. Check what this costs in profit given back and in drawdown.
  4. Daily rebalancing. Normalization every day versus rebalancing only when a weight drifts by more than 20%. The author admits that the daily version noticeably increases the number of trades.
  5. Neighborhood of Basso's 9 days. 7, 9, 12, 15 days on an index future with full costs. The system is always in the market and reverses often, so costs are decisive here.
  6. Sector versus index. Variant A on one sector ETF versus the same rule on the S&P 500. If the result on the broad index is similar, timing does most of the work, as the author says.
  7. Market entry after a close beyond the band. Count the share of such entries and the average slippage from the level. On gaps the entry price is noticeably worse than in an idealized test at the level.

Platform notes

TradingView (Pine Script)

  • ta.kc computes the center and width with its own implementation, and Keltner implementations differ. To match the rules, build the band yourself: ta.ema(close, 20) + k * ta.atr(20). ta.atr uses Wilder smoothing.
  • A buy stop order below the current price fills at the open of the next bar. If the Keltner band is already below price, strategy.entry(..., stop = firstUp) turns into a market entry. Handle this case explicitly, otherwise the entry will look like a breakout of the level.
  • ta.stdev is the population standard deviation by default, like StdDev in EasyLanguage and iBands in MQL5. Bollinger bands on the three platforms will match at the same length.
  • A strategy trades one symbol. A basket of 48 sectors with common normalization cannot be tested in Pine. Variant A is tested on a single ETF, and the basket is assembled outside TradingView.

MultiCharts and TradeStation (EasyLanguage)

  • Build Keltner yourself: XAverage(Close, 20) + K * ATRw, where ATRw is Wilder's ATR calculated manually. AvgTrueRange is a simple average of TrueRange, and bands built with it will differ. Pick one ATR version and keep it on all platforms.
  • Buy next bar at FirstUp stop lives for one bar. Send it on every bar. If the level is below the open of the next bar, the order fills at the open.
  • For futures (variants B and C), the daily bar close in TradeStation is the settlement, while a 1440-minute bar closes on the last trade (Kevin Davey, The Algorithmic Advantage #036). A three-day channel on the two bar types gives different levels almost every day.
  • A sector basket with volatility normalization is tested in the portfolio module: Portfolio Trader in MultiCharts, Portfolio Maestro in TradeStation.

MetaTrader 5 (MQL5)

  • There is no built-in Keltner. Take the average via iMA(..., MODE_EMA, ...) and calculate ATR yourself: iATR is a simple average of TR.
  • Not every broker offers sector ETFs. CFDs on ETFs accrue swaps, and a variant A position is held for weeks and months.
  • Daily normalization runs into the lot step SYMBOL_VOLUME_STEP. On a small account the weight changes in steps, and some rebalances do not happen.
  • For variants B and C, on a netting account a reversal is a single deal of double volume, on a hedging account the old position has to be closed explicitly.

Where the idea can break

  • The Keltner multiplier and the Bollinger width are not named in the video. They determine which channel triggers first, that is, the essence of the rule. Antonacci admits to not remembering the paper's details, so the exact settings have to be taken from the paper itself.
  • The century of data consists of industry indices, not tradable securities. Sector ETF history is short, and the author tested ETFs separately.
  • The author openly shows the unevenness: the century-long table has excellent and weak periods, and regimes last 15–20 years. A test on 5–10 years can fall entirely into a weak regime.
  • Hedging with a short improved Sharpe and drawdown but reduced the total return, so the model was kept long only. Nothing protects the long version against drawdowns in a bear market.
  • Basso gives no result figures. Versions B and C are described as Basso's current strategies, without statistics and without band widths.
  • Daily volatility normalization increases turnover. On a small account and on CFDs, costs can eat the difference from simple buy and hold.

Sources

  • 026 - Gary Antonacci II - New Models & Research Updates

    The Algorithmic Advantage · Gary Antonacci · 2024-09-13

    • 03:44Data: industry indices from Ken French's website
    • 05:33Two models: Donchian and Keltner channels
    • 06:29Entry on 20 days, exit on 40 days
    • 06:45The channel touched first triggers
    • 07:30All sectors with a signal, volatility-based sizing
    • 09:33A new four-week high as a continuation hypothesis
    • 10:53Volatility normalization, daily rebalancing
    • 12:59Costs and sector ETFs
    • 17:08Long only, a hedge did not add return
    • 20:33Timing as the main contributor to Sharpe
  • 023 - Tom Basso - The Calm Within the Chaos

    The Algorithmic Advantage · Tom Basso · 2024-08-09

    • 26:14Risk 0.5% per position, up to 1% in a winning one
    • 35:58Crypto futures: 9-day set, first touch
    • 55:52Keltner: EMA 21 and ATR times a multiplier
    • 56:47Noise band: a range is not traded
    • 1:23:55Index timing: 9 days, always in the market
    • 1:25:073-day Donchian on 26–27 futures
    • 1:26:30The lower Donchian line is the trailing stop
    • 1:27:02The line closest to price places the order

Author's claims

These figures and statements are the author's. We have not verified them.

  • Paper by Gary Antonacci and Carlo Zarattini, "A Century of Profitable Industry Trends": 48 US industry sectors, annual results from 1924 to 2024. According to the author, after transaction costs the result is noticeably better than buy and hold of the S&P 500 and is confirmed on SPDR sector ETFs.
  • In the decomposition of the result into market, industry and timing contributions (section 4.3 of the paper), the main source of the maximum Sharpe was the timing model, that is, the channel rules themselves.
  • Position sizes are normalized to volatility with daily rebalancing. The author quotes the target level from memory: possibly 10%.
  • Tom Basso trades a 9-day version on index futures and on CME micro futures for bitcoin and ether, and a 3-day Donchian on 26–27 futures. According to Basso, before the episode was recorded (August 2024) the 3-day version captured the drop in stocks and the rallies in bonds and cocoa well.

Related ideas

Updated: 2026-09-10