Broker API HubMenu

Technique explained as code

Channel breakout

Draw a channel from the highest high and the lowest low of recent days. The rule acts when price closes outside it. Code you can run, and an honest account of how it fails.

Independent and unofficialEducational onlyInvented data onlyCode run

Risk warning: CFDs and spread bets are complex instruments and come with a high risk of losing money rapidly due to leverage. You can lose money on these products, and each provider publishes its own loss figure under FCA rules. You should consider whether you understand how they work and whether you can afford to take the high risk of losing your money. Automated and API trading can amplify losses through bugs, connectivity failures and runaway orders.

Teaching material only. This page explains how a technique works as code and how to test an idea honestly. It is not investment advice, not a recommendation and not an invitation to trade. Every price on it is invented, so no result here says anything about any real market, and nothing here suggests that a technique makes money.

What it is

Take the highest price of the last twenty days and the lowest price of the last ten. Those two lines make a channel that price has stayed inside.

A breakout rule gets in when price closes above the top of the channel and gets out when it closes below the bottom. It is sometimes called a Donchian channel, after the trader who described it.

The idea behind it

A new high means that everyone who bought in the last twenty days is sitting on a gain and no recent seller's price is left above. The thinking is that a market making new highs is more likely to be in a lasting move than one stuck in the middle of its range.

Like the moving average crossover it follows a move that has already started. The difference is that it uses the extremes of the range, not the average, so it reacts to a single strong day.

The code

The detail to watch is which days go into the channel. It is built from the days before yesterday and then compared with yesterday's close. If yesterday were inside its own channel, its close could never be above the top of it.

Data the script reads: invented-prices.csv. Invented values, never a real market. Save it in one folder and run the script from that folder.

The file that was run: channel_breakout.py

# Channel breakout: act when price leaves the range it has been in.
# Prices come from invented-prices.csv: a random walk made by a script, never a real market.
# The result therefore says nothing about any real market. Read it for the method.
import csv

highs, lows, closes = [], [], []
with open("invented-prices.csv", newline="") as f:
    for row in csv.DictReader(f):
        highs.append(float(row["high"]))
        lows.append(float(row["low"]))
        closes.append(float(row["close"]))

# Example values, picked only because they are easy to follow. They are not a suggestion.
ENTRY_DAYS = 20      # the channel that has to be broken to get in
EXIT_DAYS = 10       # a shorter channel on the other side, to get out
COST = 0.10          # points paid on every completed trade, a stand in for the spread

holding = False
entry = 0.0
results = []
START = ENTRY_DAYS + 1

for day in range(START, len(closes)):
    yesterday = day - 1
    # The channel is built from the days BEFORE yesterday, then compared with yesterday's close.
    # Nothing from today is used to decide.
    top = max(highs[yesterday - ENTRY_DAYS:yesterday])
    bottom = min(lows[yesterday - EXIT_DAYS:yesterday])
    price = closes[day]          # act at today's close

    if not holding and closes[yesterday] > top:
        holding, entry = True, price
    elif holding and closes[yesterday] < bottom:
        results.append(price - entry - COST)
        holding = False

if holding:
    results.append(closes[-1] - entry - COST)

wins = [r for r in results if r > 0]
print("Trades:", len(results))
print("Trades that ended above zero:", len(wins))
if wins:
    print("Largest single trade:", round(max(results), 2))
print("Total points after costs:", round(sum(results), 2))
print("Total without the largest trade:", round(sum(results) - max(results), 2))
print("Simply holding over the same days:", round(closes[-1] - closes[START] - COST, 2))

What the run printed

Trades: 14
Trades that ended above zero: 5
Largest single trade: 4.43
Total points after costs: -9.94
Total without the largest trade: -14.37
Simply holding over the same days: -10.82

Output of the run on , copied as printed. Every input to this run was invented, so the result says nothing about any real market.

How to read it

Fourteen trades, five of them above zero. As with most rules of this kind, most breakouts fail: price pokes outside the channel and comes straight back.

The script prints the total with and without the largest single trade. If taking one trade away changes the picture, the result is one event, not a pattern. Here the total is below zero either way.

The total is close to what simply holding did over the same days. The prices are a random walk, so a new high in this file tells you nothing about the next day. The numbers say nothing about any real market.

How it fails or misleads

The page on how a backtest lies shows the mistakes that make any rule look better in a test than it is.

What to try changing

These are programming exercises. Each one changes the code and asks you to explain what you see. None of them is a search for settings to use.

How this page was made

Each script on this page was run with Python 3.12 on Windows, standard library only, from a folder holding the data files listed. No network and no account. Each exited with code 0, and what is shown is everything it printed.

The data files whose names begin with the word invented are written by make_invented_data.py from fixed seeds, so anyone can make the same files again and get the same output. This site is not published if the code or a data file has changed since the run.