← All ideas
#055NewTrendPosition

Trend long in stocks: Keltner channel and ROC 200 rank

Long NASDAQ 100 stocks when the index is above its 200-day average and the stock closes above a 50-day Keltner channel at 2 ATR. Candidates are ranked by 200-day ROC, stop 15%, trailing stop 25%.

The Algorithmic Advantage · Laurens Bensdorp · Watch video

Markets

Stocks

Timeframe

D1

Data

OHLC, Instrument universe

Rules

Partly formalised

Difficulty

Medium

Status

Untested

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

TradingView needs data or workarounds
EasyLanguage has pitfalls
MetaTrader 5 has pitfalls

Idea in brief

Laurens Bensdorp trades 55 strategies on stocks and divides them into four "legs": trend and mean reversion, each long and short. In the episode Bensdorp showed one trend system in full as an example of simplicity. Market filter: the NASDAQ 100 index closed above its 200-day average. Stock setup: a close above the upper boundary of a 50-day Keltner channel with a width of 2 ATR. According to the author, that is the entire trend component.

By the author's estimate, this filter lets through 20-30 candidates. The author ranks them by their 200-day return (ROC 200) and takes the strongest. The author did not say how many positions to hold. The initial stop is 15% from entry, followed by a 25% trailing stop. No shorts.

Wide percentage stops make the system slow. The 25% trailing stop rises above the initial stop only when the stock has gained more than 13.3% from entry (0.85 / 0.75 = 1.133). Until then the position is protected by the 15% stop.

The idea shares the logic of an upside breakout with the new-high-breakout-ma10-exit card, but the structure is different. There, the level is the 100- or 200-day high, and the exit on the 10-day average closes the trade within days. Here, the level is derived from volatility, the percentage exit holds the position for months, and an important part of the system is choosing between stocks by ROC 200. The same author describes insuring a long portfolio against crashes in index-crash-hedge.

Why it might work

The author explains the rules as follows. A close above the Keltner channel at two ATR means the stock has broken upward out of its normal range of fluctuation. A high 200-day ROC selects stocks that have been rising for a long time and are also volatile. The author's trend leg hunts for rare large moves with positive skew. Mean reversion, by contrast, accumulates small profits and occasionally takes a big hit.

The author does not hold trend shorts on individual stocks because of corporate risk: over a long holding period, news on any day can cause a gap against the position. For the short side the author trades ETFs. The universe for trend comes from indices, and the author explicitly names the built-in survivorship bias: the index itself adds strong stocks and removes weak ones.

The author has a separate idea about ranking. The author keeps a table of parameters for all strategies and does not use ROC 200 in a second system, even if it tests better: otherwise the new system looks for the same thing as the old one. The author's example of a fundamentally different rank is the maximum 14-day RSI.

The episode has no backtest of this system. The explanations above are the author's logic, not verified by us.

Rules

Signal on a single stock (author)

// daily bars, a stock from the NASDAQ 100 index, long only
RegimeOK = NDX_Close > SMA(NDX_Close, 200)   // author: index above its 200-day average
                                             // Finetiq: average type not named, we use a simple one
KC_Mid   = EMA(Close, 50)                    // Finetiq: channel midline not named
KC_Upper = KC_Mid + 2 * ATR(50)              // author: 50-day channel + 2 ATR
                                             // Finetiq: ATR of the same length, Wilder
Setup    = Close > KC_Upper                  // author
ROC200   = Close / Close[200] - 1            // author: rank by 200-day return

Candidate selection and entry (author, number of positions Finetiq)

// after the close, across the whole universe at once
Candidates = stocks where RegimeOK AND Setup AND no position yet
Sort Candidates by ROC200 from highest to lowest       // author

MaxPositions = 10              // Finetiq: the author did not name the number of positions.
                               // The host retold the rule as "top 30"
FreeSlots = MaxPositions - number of open positions
FOR the first FreeSlots stocks from Candidates
    BUY AT NEXT BAR OPEN       // Finetiq: execution at the next day's open

// Finetiq: a switched-off market filter blocks new entries, open positions are managed by the stops

Position size (Finetiq)

// the author did not name a method for this system.
// In MR systems the author sizes by volatility and caps the share of capital per stock
PositionValue = Equity / MaxPositions     // Finetiq: equal weights, 10% of capital
// risk per trade with a 15% stop is about 1.5% of capital, excluding gaps

Exit (author)

InitialStop = EntryPrice * 0.85                // author: 15% stop
TrailStop   = HighestClose(since entry) * 0.75 // author: 25% trailing stop
                                               // Finetiq: from the highest close, not from High
Stop = MAX(InitialStop, TrailStop)
SELL STOP at Stop                              // Finetiq: intraday stop order
// variant to test: IF Close < Stop THEN EXIT AT NEXT BAR OPEN

Variant B. Channel exit (author, levels Finetiq)

// the author does not want all strategies to have a 25% trailing stop.
// Examples of simple "price went down" exits: 200-day Bollinger Bands,
// a 100-day Keltner channel, a Donchian channel
IF Close < SMA(Close, 200)                THEN EXIT AT NEXT BAR OPEN   // Finetiq: midline of the 200 bands
IF Close < EMA(Close, 100) - 2 * ATR(100) THEN EXIT AT NEXT BAR OPEN   // Finetiq: lower boundary, width 2
// the author did not specify which boundary counts as the signal

Variant C. Rotational exit (author, frequency Finetiq)

// the rank sets both the entry and the exit
Rank = the stock's place by ROC200 among all stocks in the universe
IF RegimeOK AND Rank <= 10 AND no position  THEN BUY AT NEXT BAR OPEN   // author: entry into the top 10
IF Rank > 10                                THEN EXIT AT NEXT BAR OPEN  // author: dropped out of the top 10
// Finetiq: the rank is recalculated daily; the market filter is kept from the main system
// an exit does not mean the stock is falling: a stronger one was found (author)

Stock selection (separate rule)

// universe: NASDAQ 100 constituents as of each bar's date, including stocks removed later
// author: the S&P 500 also works, as does a basket of stocks from one sector, for example energy
// Finetiq: a stock removed from the index gets no new entries, an open position is managed by the stops
// the signal is calculated on each stock separately, ranking across the whole universe

Parameters

Parameter Value Source
Universe NASDAQ 100 stocks (variant: S&P 500) author
Market filter index above its 200-day average author
Filter average type simple Finetiq
Keltner channel 50 days, width 2 ATR author
Channel midline and ATR EMA 50, Wilder ATR 50 Finetiq
Ranking 200-day ROC, descending author
Number of positions 10 Finetiq
Position size equal weights Finetiq
Initial stop 15% from entry author
Trailing stop 25% author
Trailing stop base highest close since entry Finetiq
B: exit types Bollinger Bands 200, Keltner channel 100, Donchian channel author
B: exit levels midline, lower boundary at 2 ATR Finetiq
C: rotation entry into the top 10 by ROC 200, exit on dropping out author
C: recalculation frequency every day Finetiq

What to test

  1. Rank versus random selection. Run the same signal with three ways of choosing candidates: highest ROC 200, random order, lowest ROC 200. If random selection comes close to ROC 200, the result comes from the filter and the exit, not from the rank. Add a rank by RSI(14), which the author gives as a fundamentally different one.
  2. Parameter neighborhood. Channel of 40, 50, 60 days and width of 1.5, 2, 2.5 ATR; ROC 150, 200, 250; stop 10, 15, 20%; trailing stop 20, 25, 30%. Look at the shape of the results across the grid, not at the best point.
  3. Number of positions. 5, 10 and 20 positions on the same signals. The author did not name the number, and concentration and the share of missed candidates depend on it.
  4. Survivorship bias. Compare a test on historical NASDAQ 100 constituents with a test on today's list. The difference shows how much of the result came from the list itself. Separately check the rule for stocks removed from the index while a position is open.
  5. Exits in a crash. A 25% trailing stop, the channel exit and the rotational exit on the same universe. Count how many positions closed within a single week in March 2020 and in 2022. The author keeps different exits precisely so that they do not all fire at once in a decline.
  6. Periods without growth. 2000-2002, 2008 and 2022 separately. The author considers the 1995-2024 sample biased toward growth.
  7. Live drawdown. Multiply the test's maximum drawdown by two, as the author advises. Check whether you can withstand such a drawdown with the chosen number of positions.

Platform notes

TradingView (Pine Script)

  • A strategy trades only the chart symbol. Ranking by ROC 200 across stocks and a position limit cannot be reproduced in a Pine backtest. The signal with the index filter and stops can be tested on a single stock, but that is a different system: without the rank there are more entries.
  • Index filter on a daily chart: request.security("NASDAQ:NDX", "D", close > ta.sma(close, 200)). The timeframe matches the chart, so there is no lookahead here.
  • Keltner channel implementations differ: the midline can be an SMA or an EMA, and the width can be based on ATR or on the average range. Calculate the channel with an explicit formula. ta.atr is already Wilder-smoothed.
  • Calculate the 25% trailing stop as a price and pass it to strategy.exit via stop. The trail_points and trail_offset parameters are set in ticks.
  • Check the adjustment for dividends and splits: ROC 200 and the levels of the percentage stops depend on it.

MultiCharts and TradeStation (EasyLanguage)

  • Ranking and the position limit are done in a portfolio module: Portfolio Maestro in TradeStation, Portfolio Trader in MultiCharts. You need historical index constituents including removed stocks, which standard data usually lacks.
  • AvgTrueRange is a simple average of TrueRange. For Wilder ATR, calculate it yourself: ATRw = ATRw[1] + (TrueRange - ATRw[1]) / 50. Ready-made Keltner channel indicators are built in different ways, so check the formula.
  • Set the stop as a price: Sell next bar at MaxList(EntryPrice * 0.85, HiClose * 0.75) stop, where HiClose is your variable for the highest close. The order lives for one bar, so send it on every bar. SetDollarTrailing works in money and gives a different level.
  • In the single-chart version, the index is added as a second data stream: Close of Data2. For SMA 200 and ROC 200, load extra history, since the strategy does not trade during the first MaxBarsBack bars.

MetaTrader 5 (MQL5)

  • An EA can rank stocks from Market Watch, but all index stocks must be available at the broker. The broker has no history of index composition, so the test will run on today's list.
  • A Nasdaq-100 CFD (USTEC, NAS100) usually follows the futures and trades almost around the clock, and its daily bar closes on server time. A filter based on it is approximate. Take the value from the closed bar: iClose(symbol, PERIOD_D1, 1).
  • Stock CFD history is often shorter than exchange history. SMA 200 and ROC 200 are calculated on the same history: the first trade needs about 200 bars plus extra bars to warm up the EMA 50 and the Wilder ATR 50.
  • The position is held for months. A swap is charged on CFDs every night, so check that the tester accounts for it.

Where the idea can break

  • The author showed the system as an example of simplicity. The episode has no results, test period or number of trades.
  • The number of positions, the sizing, the channel formula and the trailing stop base were added by us. The result depends heavily on the number of positions.
  • Survivorship bias. The author calls it built into the index. Historical NASDAQ 100 constituents are usually paid data, and a test on today's list inflates the result.
  • In a sharp decline the 15% stop and the 25% trailing stop fire on almost all stocks at once, and gaps make the loss larger than calculated. The author says that in a crash such exits get stopped out together.
  • The trend leg's result rests on a few large moves. A short test may not contain them, and the author considers 1995-2024 a favorable period for longs.
  • The author warns that the rank that was best in history does not have to stay the best. Choosing a rank to fit the best test turns this system into overfitting.

Sources

  • 025 - Laurens Bensdorp - Balancing 55 Supermodels

    The Algorithmic Advantage · Laurens Bensdorp · 2024-09-02

    • 05:10Four legs: trend and mean reversion, long and short
    • 11:31In MR systems: volatility-based sizing with a cap on the share of capital
    • 18:20Trend only on S&P 500 and NASDAQ 100 stocks, survivorship bias in the index
    • 52:09NASDAQ 100 system: index filter and Keltner channel
    • 52:5020-30 candidates, ROC 200 rank, stop 15%, trailing stop 25%
    • 53:48Long only: shorting stocks carries corporate risk
    • 54:13Ranking and the non-correlation matrix
    • 56:31Exit: 25% trailing stop or channel down
    • 57:31Short-term trend on a 20-day channel
    • 58:241964-1982 and the biased 1995-2024 sample
    • 59:04Rotational exit when dropping out of the top 10
    • 1:32:02Live drawdown twice the test drawdown
    • 1:38:03Half of capital in trend, half in mean reversion

Author's claims

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

  • The author trades 55 strategies on stocks. Capital is split roughly equally between trend systems and mean reversion systems.
  • For roughly the last three years, the author's trend systems have traded only stocks from the S&P 500 and NASDAQ 100. According to the author, survivorship bias is built into these indices: strong stocks are added, weak ones are removed.
  • After the index filter and the Keltner channel, the author estimates that about 20-30 candidates remain. The episode gives no return, drawdown or test period for this system.
  • From 1964 to 1982, according to the author, the S&P 500 went nowhere for 18 years with a 50% drawdown. The author considers the 1995-2024 sample biased toward growth.
  • Even with careful testing, the author expects the maximum drawdown in live trading to be twice as large as in the test.

Related ideas

Updated: 2026-09-11