How to Backtest a Python Trading Strategy with LumiBot¶
Backtesting is a vital step in validating your trading strategies using historical data. With LumiBot, you can backtest strategies across various data sources such as ThetaData (our recommended vendor), Polygon.io, Yahoo Finance, Polymarket CLOB prediction-contract history, or even your own custom CSV files. This guide will walk you through each step of backtesting, explain the data sources, and introduce the files that LumiBot generates during backtesting.
What BotSpot MCP adds
Use Claude, ChatGPT, Codex, Cursor, VS Code or another MCP-compatible assistant? Connect the free BotSpot MCP for:
Skills: build LumiBot strategies.
Free data: news, economics and SEC filings.
Backtests: run tests and read charts.
Cloud hosting: low-cost scheduled bots.
Community: share bots and discuss trades.
Marketplace: discover and publish strategies.
Broker linking: connect accounts through BotSpot.
Note
Why Backtest?
Backtesting allows you to see how your strategies would have performed in the past, helping you identify weaknesses or strengths before deploying them in live markets.
Installing LumiBot¶
Before you begin, make sure LumiBot is installed on your machine. You can install LumiBot using the following command:
pip install lumibot
To upgrade to the latest version of LumiBot, run:
pip install lumibot --upgrade
Once installed, you can use an IDE like Visual Studio Code (VS Code) or PyCharm to write and test your code.
Tip
Quick Setup for VS Code
Download and install Visual Studio Code from the official website: https://code.visualstudio.com/.
Open VS Code and install the Python extension by going to Extensions and searching for Python.
Create a new project folder for LumiBot.
Open a terminal in VS Code and install LumiBot using pip install lumibot.
You’re ready to start backtesting with LumiBot!
Tip
Want hosted backtests instead?
BotSpot can help you create or revise Lumibot code with AI, run supported backtests on hosted data, compare multiple variants in parallel, and inspect charts, trades, logs, artifacts, and audit history without wiring the full local environment first.
Choosing a Data Source¶
Historical data is separate from the broker you use for paper or live trading.
Set BACKTESTING_DATA_SOURCE to select a historical provider without changing
your strategy. This setting overrides a data-source class passed in Python.
See Choose your backtest data for the
selection rules and setup examples.
LumiBot supports several data sources for backtesting, each suited for different asset types and backtesting needs. Here’s an overview of the available sources:
1. ThetaData (Recommended)
Deep historical coverage for U.S. equities and options with SIP-quality filtering.
Offers free tiers plus paid plans with higher rate limits and multi-year history.
Important
Get Your ThetaData Account
Sign up at ThetaData. Use the promo code BotSpot10 for 10% off the first order—ThetaData uses this code to credit BotSpot for the referral.
For more details, see the ThetaData Backtesting section.
2. Yahoo Finance
Free stock and ETF data for daily trading backtests.
Suitable for longer-term strategies but not ideal for intraday backtesting.
For more details, see the Yahoo Backtesting section.
3. Polygon.io
Offers intraday and end-of-day data for stocks, options, forex, and cryptocurrency.
Provides up to two years of free data; paid plans offer more advanced features and faster data retrieval.
Best suited for existing workflows; new LumiBot users should consider ThetaData first for the BotSpot10 promo and deeper coverage.
For more details, see the Polygon.io Backtesting section.
4. Pandas (CSV or Other Custom Data)
Allows for full flexibility by using your own custom datasets (e.g., CSV, database exports).
Ideal for advanced users but requires more manual configuration.
For more details, see the Pandas Backtesting section.
5. Polymarket CLOB
Uses real Polymarket CLOB price-history data for prediction-contract close-price bars.
Trades CLOB outcome tokens as
prediction_contractassets priced between0and1.Uses LumiBot’s normal market and limit backtesting fills from loaded Polymarket bars while enforcing prediction-contract price bounds, tick-size rules, minimum order size rules, market-close behavior, and resolved-market settlement when metadata is available.
Keeps prediction-contract backtests on Polymarket/Pandas bars so they do not fall through to stock, Yahoo, or IBKR data paths.
For more details, see the Polymarket broker guide.
6. Kalshi
KalshiBacktesting(PandasData) loads current or archived completed candles for
the existing prediction_contract Asset with outcome="yes" or "no".
Pass assets=[yes_asset, no_asset] and timestep="minute", "hour" or
"day" to normal YourStrategy.run_backtest(KalshiBacktesting, start, end, ...);
BACKTESTING_DATA_SOURCE=kalshi selects the same class. Use normal Strategy
price, quote, history, order and position methods. Public market reads need no
trading key. pandas_data= and matching market_metadata= support offline
replay and earlier signal warm-up.
BacktestingBroker fills marketable orders against observed candle-end bid/ask,
nets opposite outcomes with cash collateral, and uses actual settlement timing.
Future results and unfinished bars are withheld; no end-of-test payout is
invented. Fee rates and balance precision are configurable estimates; explicit
normal Strategy TradingFee lists override them. Candle data cannot reconstruct
historical depth, queue priority, all vintage fees or cross-event margin.
See the repository’s docs/KALSHI_ACCOUNT_CONNECTION.md for configuration,
settlement, fill assumptions, evidence and remaining qualification.
Running a Backtest with Polygon.io¶
Once you’ve selected your data source and built your strategy, you can run your backtest using the run_backtest function. This function requires:
Data source: (e.g., Yahoo, Polygon.io, ThetaData, or Pandas)
Start and end dates: The period you want to test.
Additional parameters: Strategy-specific parameters.
Here’s an example of a backtest using Polygon.io:
Important
You Must Use Your Polygon.io API Key
Make sure to replace “YOUR_POLYGON_API_KEY” with your actual API key from Polygon.io for the backtest to work.
from datetime import datetime
from lumibot.backtesting import PolygonDataBacktesting
from lumibot.strategies import Strategy
class MyStrategy(Strategy):
parameters = {
"symbol": "AAPL",
}
def initialize(self):
self.sleeptime = "1D" # Sleep for 1 day between iterations
def on_trading_iteration(self):
if self.first_iteration:
symbol = self.parameters["symbol"]
price = self.get_last_price(symbol)
qty = self.portfolio_value / price
order = self.create_order(symbol, quantity=qty, side="buy")
self.submit_order(order)
if __name__ == "__main__":
polygon_api_key = "YOUR_POLYGON_API_KEY" # Replace with your actual Polygon.io API key
backtesting_start = datetime(2025, 1, 1)
backtesting_end = datetime(2025, 5, 1)
result = MyStrategy.run_backtest(
PolygonDataBacktesting,
backtesting_start,
backtesting_end,
benchmark_asset="SPY",
polygon_api_key=polygon_api_key # Pass the Polygon.io API key here
)
Optional: Using Environment Variables for Backtest Configuration¶
Instead of specifying backtesting_start, backtesting_end, and data sources in your code, you can set these environment variables:
IS_BACKTESTINGBACKTESTING_STARTBACKTESTING_ENDBACKTESTING_DATA_SOURCE
If they are set, LumiBot will automatically pick them up. For example:
Variable |
Description |
Example |
|---|---|---|
IS_BACKTESTING |
Read only by runners that explicitly check it. Setting it to “False” does not turn a |
False |
BACKTESTING_START |
Start date in the format “YYYY-MM-DD”. |
2025-01-01 |
BACKTESTING_END |
End date in the format “YYYY-MM-DD”. |
2025-05-01 |
BACKTESTING_DATA_SOURCE |
Selects the historical provider and overrides the class in code. Use |
Polygon |
Below is a short example showing how you might rely entirely on environment variables and omit any explicit date or data source definitions in code. Set BACKTESTING_DATA_SOURCE=Polygon in your environment to use Polygon.io (API key still required):
from lumibot.strategies import Strategy
class MyStrategy(Strategy):
parameters = {
"symbol": "AAPL",
}
def initialize(self):
self.sleeptime = "1D"
def on_trading_iteration(self):
if self.first_iteration:
symbol = self.parameters["symbol"]
price = self.get_last_price(symbol)
qty = self.portfolio_value / price
order = self.create_order(symbol, quantity=qty, side="buy")
self.submit_order(order)
if __name__ == "__main__":
# Set in environment: BACKTESTING_DATA_SOURCE=Polygon
# Set in environment: BACKTESTING_START=2025-01-01
# Set in environment: BACKTESTING_END=2025-05-01
polygon_api_key = "YOUR_POLYGON_API_KEY" # Still required for Polygon
result = MyStrategy.run_backtest(
None, # Auto-selects from BACKTESTING_DATA_SOURCE env var
polygon_api_key=polygon_api_key
)
For more information about running backtests, refer to the Backtesting Function section.
Tip
For a full list of supported configuration environment variables (audit telemetry, remote cache, profiling, etc.), see Environment Variables.
Files Generated from Backtesting¶
When you run a backtest in LumiBot, several important files are generated. These files provide detailed insights into the performance and behavior of the strategy. Each file is crucial for understanding different aspects of your strategy’s execution.
Tearsheet HTML¶
LumiBot generates a detailed Tearsheet HTML file that provides visual and statistical analysis of your strategy’s performance. The tear sheet includes:
Equity curve
Performance metrics (e.g., Sharpe ratio, drawdown)
Benchmark comparisons
For more information, see the Tearsheet HTML section.
Trades Files¶
The Trades File logs every trade executed during the backtest, including:
The asset traded
The quantity of the trade
The price at which the trade was executed
The timestamp of the trade
This file is essential for reviewing your strategy’s trading behavior and identifying any potential issues or optimizations.
For more information on how to interpret the Trades Files, see the Trades Files section.
Indicators Files¶
The Indicators File logs any technical indicators used in your strategy, such as moving averages, RSI, or custom indicators. This file helps you understand how your strategy responded to market conditions based on specific indicators.
For more information, see the Indicators Files section.
Conclusion¶
LumiBot’s backtesting feature provides a powerful framework for validating your strategies across multiple data sources. By following this guide, you can quickly set up your environment, choose a data source, and begin backtesting with confidence.
For further details on each data source and the files generated during backtesting, refer to the individual sections listed above.
Frequently asked questions¶
How do I run a backtest with LumiBot?¶
Install LumiBot with pip install lumibot, write a Strategy class, and
call MyStrategy.run_backtest() with a data source, a start date, and an end
date. You can also pass extra settings such as benchmark_asset="SPY". The
Polygon.io example above shows a complete script.
Which data source should I use for backtesting?¶
ThetaData is the recommended vendor for U.S. stocks and options. Yahoo Finance is free and works well for daily stock and ETF backtests. Polygon.io covers stocks, options, forex, and crypto. Pandas lets you load your own CSV files, and Polymarket CLOB covers prediction contracts.
Is there a ThetaData promo code?¶
Yes. Use the promo code BotSpot10 when you sign up at
ThetaData to get 10% off your first order.
ThetaData uses the code to credit BotSpot for the referral.
Can I backtest for free?¶
Yes. Yahoo Finance data is free and works for daily stock and ETF backtests. Polygon.io offers up to two years of free data, and ThetaData has free tiers. You can also backtest with your own CSV files through Pandas.
Can I use Yahoo Finance for intraday backtests?¶
Yahoo Finance suits longer term strategies that trade on daily bars. It is not a good fit for intraday backtests. For intraday stock or options data, use a provider such as ThetaData or Polygon.io.
Do I need a Polygon.io API key to backtest?¶
Yes, to use Polygon.io data you need your own API key. Pass it as
polygon_api_key to run_backtest(). The key is still required when you
pick Polygon through the BACKTESTING_DATA_SOURCE environment variable.
Does BACKTESTING_DATA_SOURCE override the data source in my code?¶
Yes. When BACKTESTING_DATA_SOURCE is set to a provider, it overrides the
data source class you pass in Python. Set it to none to keep the class from
your code. See Choose your backtest data.
Can I set backtest dates without changing my code?¶
Yes. Set BACKTESTING_START and BACKTESTING_END in the format
YYYY-MM-DD. LumiBot uses them when your code does not pass dates.
What files does a LumiBot backtest create?¶
A backtest creates a tearsheet HTML report with an equity curve, performance metrics, and a benchmark comparison. It also writes a trades file that lists every trade and an indicators file with the indicators your strategy recorded.
Can I backtest Polymarket prediction markets?¶
Yes. The Polymarket CLOB data source uses real Polymarket price history and
trades outcome tokens as prediction_contract assets priced between 0
and 1. See the Polymarket broker guide.