Problem Description
Solution
Debugging a trading algorithm in QuantConnect involves a combination of logging, data visualization, and utilizing specific event handlers to track the internal state of the LEAN engine. Because the engine processes data sequentially (event-driven), you must inspect the state at specific points in time (e.g., when data arrives or when an order is filled).
Here are the best ways to debug your strategy, followed by a code example demonstrating these techniques.
1. Logging and Debug Messages
The most direct way to inspect your algorithm is by outputting messages to the console or log file.
self.debug(message): Sends messages to the console immediately. Use this for real-time feedback during development.self.log(message): Appends messages to a permanent log file. Use this for analyzing execution flow after a backtest completes.self.error(message): Highlights critical issues in red in the console.
2. Visual Debugging (Plotting)
Numerical logs can be hard to parse. Plotting indicator values alongside asset prices helps verify if your logic (e.g., a crossover) aligns with visual reality.
self.plot("Chart Name", "Series Name", value): Plots custom data points.
3. Utilizing Event Handlers
Errors often occur during state transitions (e.g., an order is placed but not filled, or a security is removed from the universe). Implementing specific event handlers allows you to catch these changes.
on_order_event: Debug why orders are filled, canceled, or invalid.on_securities_changed: Verify which assets are currently in your universe.on_assignment_order_event: Crucial for debugging options strategies.
4. Isolating Data Issues
Before acting on data, ensure it exists. Accessing missing data is a common source of runtime errors.
- Check
if symbol in sliceorif slice.contains_key(symbol)before accessing price data inon_data.
5. Local Debugging (Lean CLI)
For complex logic, the web IDE's print debugging may be insufficient. Using the Lean CLI allows you to run the engine locally in VS Code or PyCharm. This enables you to use breakpoints, step through code line-by-line, and inspect variable states interactively.
Debugging Implementation Example
The following algorithm demonstrates how to implement these debugging techniques. It logs universe changes, plots price data, and inspects order events.
from AlgorithmImports import *
class DebuggingExampleAlgorithm(QCAlgorithm):
def initialize(self):
self.set_start_date(2023, 1, 1)
self.set_end_date(2023, 2, 1)
self.set_cash(100000)
# 1. Add a security
self.symbol = self.add_equity("SPY", Resolution.MINUTE).symbol
# 2. Initialize indicators
self.ema = self.ema(self.symbol, 20, Resolution.MINUTE)
# DEBUG: Confirm initialization complete
self.debug(f"Algorithm Initialized. Symbol: {self.symbol}")
def on_data(self, slice: Slice):
# 3. Data Safety Check: Ensure data exists before accessing
if self.symbol not in slice.bars:
return
bar = slice.bars[self.symbol]
# 4. Visual Debugging: Plot price vs EMA to verify logic visually
if self.ema.is_ready:
self.plot("Trade Analysis", "Price", bar.close)
self.plot("Trade Analysis", "EMA", self.ema.current.value)
# Simple logic to trigger orders for demonstration
if not self.portfolio.invested and self.ema.is_ready:
if bar.close > self.ema.current.value:
# DEBUG: Log the condition that triggered the trade
self.debug(f"Buy Signal: Price {bar.close} > EMA {self.ema.current.value}")
self.set_holdings(self.symbol, 1.0)
def on_order_event(self, order_event: OrderEvent):
# 5. Order Debugging: Track the lifecycle of an order
# This helps identify if orders are being rejected or partially filled
order = self.transactions.get_order_by_id(order_event.order_id)
if order_event.status == OrderStatus.FILLED:
self.debug(f"FILLED: {order_event.symbol} at {order_event.fill_price} | Fee: {order_event.order_fee}")
elif order_event.status == OrderStatus.INVALID:
self.error(f"ORDER INVALID: {order_event.message}")
def on_securities_changed(self, changes: SecurityChanges):
# 6. Universe Debugging: Track what is entering/leaving the algorithm
for security in changes.added_securities:
self.debug(f"Universe Added: {security.symbol}")
for security in changes.removed_securities:
self.debug(f"Universe Removed: {security.symbol}")
def on_end_of_algorithm(self):
# 7. Final State Inspection
self.debug(f"Final Portfolio Value: {self.portfolio.total_portfolio_value}")
self.debug(f"Total Trades: {self.trade_builder.closed_trades.count}")
Q&A: Common Debugging Scenarios
Q: Why is my algorithm not placing any trades?
A: Use self.debug() inside your entry logic if statement. If the message doesn't appear, the condition is never met. Also, check on_order_event to see if orders are being generated but rejected (e.g., due to insufficient buying power or invalid parameters).
Q: Why does my code crash with "KeyError" in on_data?
A: You are likely trying to access data for a symbol that isn't in the current slice. Always check if symbol in slice: or if slice.contains_key(symbol): before accessing data, as not all assets trade in every minute/second.
Q: How do I debug variable values without flooding the logs?
A: Avoid putting self.debug() in on_data without a filter. Instead, log only when a specific condition is met (e.g., a signal fires) or use a timer to log only once per day/hour.
Q: Can I debug historical data issues?
A: Yes. If you suspect data is incorrect, use self.log(f"{self.time} {symbol} {price}") and compare the output log against a trusted external data source (like TradingView) for that specific timestamp.