Breakout Strategy — QQQ

Turtle Trading with Choppiness Index & Volume Filters

Strategy Logic

The Turtle Strategy used for this project is a long/short trend-following system that follows the framework of Donchain Channel. We are doing the following: if the market reaches a multi-week high or low this indicates a new potential start of a directional trend. The strategy takes a long position of 75 shares if we are at a 2 week high, and a 75 share short position when at a 2 week low. Before entering the trade however, we need to ensure we aren’t entering a false breakout, 2 main filters need to be satisfied simultaneously. Firstly, the Choppiness index needs to confirm that we aren’t in a choppy environment, that we are actually in a trending market. Secondly, volume needs to be above its 20 day average to confirm that there is actually participation behind this movement. Once we have entered the trades, positions are managed with a stop loss of 3x the Average True range from entry and also a Donchain based exit when price reverses through the opposite channel boundary. Lastly there is also a hard time out of 20 days as the Max holding period. We are using a rolling window, and features with no look ahead bias.


Asset Selection

I chose QQQ (Invesco Nasdaq 100 Etf) for several reasons. Firstly, because it has the highest average daily dollar volume of any US equity etf, this means that we would have less slippage in our entries and exits of 75 shares. Secondly, historically the Nasdaq has shown very strong momentum characteristics, this ideal for the Donchain breakout system. Lastly, QQQ is diversified, this means that no single stock or name event can distort the breakout signals too much. For example, I didn’t pick SPY because its lower volatility and and produces smaller and fewer breakouts.


Breakout Detection Function

The breakout detection function detect_breakout() evaluates a single price bar and returns one of three values: +1 (enter long), -1 (enter short), or 0 (no trade). It takes three inputs: the current bar’s data, the Donchian lookback period N, and whether the most recently completed trade was profitable.

The function checks conditions in this order:

Condition 1 · Donchian Channel Breakout

The current bar’s closing price must exceed the highest high of the previous 20 bars this is the long signal or fall below the lowest low of the previous 20 bars (the short signal). The channel is always calculated on lagged data, today’s bar is never included in its own channel calculation, it wouldn’t make sense and we would never get a correct signal if we included it.

Filter 1 · Choppiness Index < 65

The Choppiness Index, it was developed by E.W. Dreiss using Benoit Mandelbrot’s fractal geometry, it basically measures whether a price series is behaving like a directional trend or random noise. It is calculated as: 100 × log10(sum of ATR over N periods / (N-period high − N-period low)) / log10(N). Values above 61.8 indicate a choppy, consolidating market where breakouts statistically fail and whipsaws dominate. I moved it to 65 to allow a small buffer because i wasn’t getting any trades before (it was too strict). So if CHOP ≥ 65 the signal is suppressed entirely.

Filter 2 · Volume Confirmation

Today’s volume must exceed its 20-day simple moving average. Because a breakout on below-average volume reflects thin liquidity rather than genuine directional conviction. On the other hand, real breakouts, that produce sustained trends, attract above-average participation. This filter rejects low-conviction signals before a position is entered.

All parameter values are defined as named constants at the top of the notebook:

Constant Value Role
N_ENTRY 20 Donchian channel lookback for entry
N_EXIT 20 Donchian channel lookback for exit
ATR_PERIOD 14 ATR lookback for stop-loss
ATR_STOP_MULT 3.0 Stop distance multiplier
CHOP_PERIOD 14 Choppiness Index lookback
CHOP_THRESHOLD 65.0 Maximum CHOP to allow entry
VOL_MA_PERIOD 20 Volume moving average period
VOL_MULT 1.0 Minimum volume ratio to allow entry
TIMEOUT_DAYS 20 Maximum holding period in trading days
POSITION_SIZE 75 Fixed shares per trade
START_CASH 100,000 Starting account capital ($)
RISK_FREE_RATE 0.0375 Annual risk-free rate for Sharpe calculation

Some of the assumptions

Entry execution: Signals are generated at the close of day T. The position is entered at the open of day T+1. This is conservative and realistic, no same-bar entry.

Stop-loss execution: The stop is treated as a hard intraday level. If the day’s low (for longs) or high (for shorts) crosses the stop price, the trade exits at the stop price on that day.

Look-ahead bias prevention: As mentioned, all indicator lookbacks use .shift(1), so today’s bar is never used in calculating today’s signal. The backtest loop is strictly sequential, processing one day at a time.

Walk-forward approach: The backtest rolls forward through time day by day. Each signal is generated using only data available up to and including the prior trading day. There is no training/test split optimization applied, N=20 is fixed based on the classic Turtle Trading parameter, this is just a rolling window.


Performance Metrics

-4.73%
Total Return
Simple return on the $100,000 starting capital over the full backtest period.
-2.84%
Annual GMRR
Geometric Mean Rate of Return annualised. Compounds daily log returns to give a true annualised growth rate.
-1.23
Sharpe Ratio (rf = 3.75%)
Excess return above the risk-free rate per unit of volatility. Below zero means the strategy did not compensate for its risk.
37.5%
Win Rate
3 of 8 trades closed profitably. Consistent with classic Turtle Trading, the system is designed to win rarely but win large.
-$492
Avg Return Per Trade
Average dollar P&L across all 8 completed trades. Negative because losing trades (-$1,739 avg) exceeded winning trades (+$1,253 avg).
-6.28%
Max Drawdown
Largest peak-to-trough decline in NAV. Reflects the strategy's worst losing streak, capital stayed mostly protected.

Additional metrics:

Metric Value
Profit Factor 0.39
Avg Winning Trade +$1,253
Avg Losing Trade -$1,739
Avg Days Held 12.5
Annual Volatility 5.33%
Backtest Period Aug 2024 – Apr 2026
Total Trades 8

Equity Curve


Drawdown


Trade Outcome Analysis

Every trade is classified into one of three outcomes. The timeout period is 20 trading days. The stop-loss is set at 3 × ATR(14) below entry for longs and above entry for shorts.

Success Trade closed profitably — either via timeout or Donchian exit in profit Stop-loss 3×ATR stop triggered before a profitable exit could occur Timeout 20-day holding limit reached, position exited at market close

As we can see, out of the 8 trades: 3 were stopped out (red), 3 closed profitably (green), and 2 closed as losing timeouts (yellow). The 2 yellow trades both lost money, one was exited by the Donchian reversal signal at a loss, and one timed out after 20 days still at negative. All 3 profitable trades went on for the full 20-day holding period before exiting, meaning the strategy’s gains came entirely from holding and not the exit signal.


Trade Blotter

Complete record of all trades. Entry and exit timestamps, prices, position size, direction, and outcome are included.

Download Blotter CSV


Market Comparison

Strategy vs QQQ (per-trade log returns)

Okay so, what we can see here is that each point represent a trade, those to the left of the y axis are the unprofitable, and those to the right are the profitable. The regression line has a clear negative slope. So when the strategy lost money, QQ was rising vs when the strategy made money qqq was flat or falling. This clearly reflects the strategy short bias ( we made 5 shorts vs 3 long trades). So these results make sense. And sicnce SPY and QQQ are highly correlated the relationship with SPY is almost the same.

Strategy vs SPY (per-trade log returns)


Analysis & Commentary

Did the strategy work?

Clearly not,the strategy lost 4.73% over the backtest period while QQQ itself appreciated significantly. However we didnt actually lose that much money($4,730 on $100,000) and the maximum drawdown of 6.28% indicates that capital was largely preserved. The strategy didn’t execute that many trades, it was very conservative.

Why did the losing trades lose?

All four losing trades were stopped out within 2 to 15 days. The structural problem is that entry occurs the morning after the breakout signal, by which point the initial explosive move has already occurred. What follows is often a short-term mean-reversion as traders take profits off the breakout, which hits the stop before the real trend has a chance to develop. The two largest losses, August 2024 and April 2025, both occurred during sudden volatility spikes driven by macro events (the yen carry trade unwind and the tariff shock respectively). In both cases QQQ made a sharp directional move that generated a signal, then immediately reversed as the event stabilized. These are basically regime-change episodes where trend-following signals produced false positives.

Why did the winning trades win?

All three profitable trades were held for the full 20-day timeout. So, the strategy never found a clean Donchian exit on a winner ,it simply held long enough vs having a clean exit. This means that either my exit signal wasn’t accurate at all or that the edge in this case comes from position holding rather then the exit mechanism ### What does the alpha/beta comparison show? As we saw, both charts show a clear negative beta versus QQQ and SPY. Which means that when the strategy made money, the market was falling, and when the strategy lost money, the market was rising. This is directly a consequence of the fact that we had 5 shorts and only 3 longs. This means that the strategy provides a hedge against market downturns, but duting our period, the market was rallying thats why our returns are negative. We can also so that alpha, is close to zero, which means that the strategy generated no excess returns above what its market exposure alone would predict. The performance was driven by direction bets.

How would you improve the model?

I think that the most impactful improvement would be to look at the larger macro trends aswell. Lets only take a daily Donchain breakout signal if the weekly trend also agrees, so if the weekly close is above the 10 weekly moving average for long entries and below for short entries. This would allow us to filter out breakouts that are fighting the larger prevailing trend, this is what happened in several of our losing trades. We entered during weeks when QQQ’s weekly trend was still clearly going up, making those breakouts less likely from the start. Maybe another improvement could be a volatility regime filter, we would skip signals when the realized volatility ove the past 5 days is more than 2 std dev above its 60 day mean. However we might lose the count of trades we do, since its already producing a short number, so there is a tradeoff here.