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:
- Buy when price breaks above the 30-period high
- 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
- 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 confirmedHandling Market Data
- 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- 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 typesCalculating the Channel
- 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 dataPlacing Orders
- 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 nextOrderIdMain Loop
- 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