Donchian Channel Quick Tutorial

Implementing a simple algorithm to learn the Interactive Broker (IBKR) API.

One of the best ways for me to learn a new programming language or API, is to take very simple to understand concepts/models and implement them.

The simplicity allows me to concentrate on actually learning the language, and not just spending multiple hours on trying to understand what a complex model is even supposed to do before any code is written.

What is a Donchian Channel?

The Donchian Channel consists of three lines:

  • Upper Band: The highest price over the last N periods
  • Lower Band: The lowest price over the last N periods
  • Middle Line: The average of upper and lower bands

When price breaks above the upper band, it signals potential upward momentum. When it breaks below the lower band, it suggests downward momentum.

Its logic is very similar to the moving average methods that are also a popular entry into financial analysis. Except the 'averages' now have an upper and a lower band.

Basic strategy

Following our simplicity guideline, the Donchian Channel will simply tell us:

  1. Buy when price breaks above the 30-period high
  2. Sell when price breaks below the 30-period low

Will it make any money? No.

Will it be easy to implement? Yes.

Implementation

Connecting to the Broker

  1. We need to establish a connection to Interactive Brokers and start the event loop. The connection runs on a separate thread so our main program can continue executing while waiting for market data callbacks.
INITIALIZE trading engine
CONNECT to broker (host, port, clientId)
START event loop on background thread
WAIT until connection confirmed

Handling Market Data

  1. Once connected, we request historical price bars. The broker sends data asynchronously through callbacks, so we store each bar in a DataFrame as it arrives.
FUNCTION fetchHistoricalData(requestId, contract)
    CREATE empty DataFrame [timestamp, high, low, close]
    REQUEST data from broker (1 day, 1-minute bars)
    WAIT for population
    RETURN DataFrame
  1. When a new bar arrives via callback, we add it to our stored data:
ON_RECEIVED_BAR(requestId, bar):
    df = storedData[requestId]
    ADD bar to df at timestamp
    CONVERT columns to numeric types

Calculating the Channel

  1. With price data in hand, we calculate the three channel lines. The upper band is the rolling maximum of highs; the lower band is the rolling minimum of lows.
FUNCTION calculateDonchianChannel(data, period=30):
    data["upper"] = ROLLING_MAX(data["high"], period)
    data["lower"] = ROLLING_MIN(data["low"], period)
    data["middle"] = (data["upper"] + data["lower"]) / 2
    RETURN data

Placing Orders

  1. When we detect a breakout, we submit a market order. We track the next valid order ID to ensure each order gets a unique identifier.
FUNCTION placeMarketOrder(contract, action, quantity):
    order = NEW Order(action, "MARKET", quantity)
    SUBMIT order with nextOrderId
    INCREMENT nextOrderId

Main Loop

  1. The trading loop ties everything together: fetch data, calculate channels, check for breakouts, and execute trades.
WHILE connected:
    data = fetchHistoricalData(contract)
    
    IF LENGTH(data) < 30:
        CONTINUE
    
    channel = calculateDonchianChannel(data)
    
    lastPrice = LAST(data.close)
    upper = LAST(channel.upper)
    lower = LAST(channel.lower)
    
    IF lastPrice >= upper:
        placeMarketOrder(contract, "BUY", 10)
    
    ELSE IF lastPrice <= lower:
        placeMarketOrder(contract, "SELL", 10)

Conclusion

As said before, the purpose of these tutorials are to familiarize myself with the main structure of the IBKR API and the Donchian Channel.

Breaking things down to the simplest level is a great way of speeding up the learning process.

If you are interested to see the actual python code, it is in my public git repo: https://github.com/MyQuantJourney/MyQuantJourney.git