Skip to content
python.financial
Platform
Backtesting and live-trading framework
License
Conflicting metadata. Repository says GPL-3.0, while PyPI and package metadata say MIT
Pricing
Free and open source
Live trading
Paper and live execution through supported brokers
Best for
Using the same strategy code for historical tests and supported retail brokers across several markets

Lumibot is useful when one Python strategy should run against historical data and later connect to a supported paper or live broker. It covers stocks, options, futures, forex, crypto, and prediction contracts through different combinations of data sources and broker adapters. Shared strategy code reduces translation work, but it does not make simulated and live execution identical.

The project is active. Lumibot 4.5.91 was published on September 6, 2026 with order-durability, managed-research, and crypto fixes.

How Lumibot separates strategy, data, and brokerage

A Strategy defines scheduled logic and uses common order, position, and market-data methods. In a backtest, BacktestingBroker evaluates pending orders against historical bars. In paper or live mode, a broker adapter submits and reconciles orders with the external venue. Live market data can come from the trading broker or a separately configured data source.

The current deployment configuration documents adapters including Alpaca, Tradier, Interactive Brokers, Schwab, Tradovate, ProjectX, Coinbase, Kraken, Bitunix, selected CCXT venues, and Polymarket-related workflows. Support depth differs by adapter, asset type, account region, and broker API. Validate account state, positions, submission, fills, cancellation, and reconciliation with a paper account or minimal-size test before relying on a connector.

Research need Documented source Important boundary
Quick daily stock or ETF prototype Yahoo Finance Daily data and adjusted-history choices are not an execution record
Stocks and options ThetaData, Polygon, DataBento, IBKR REST, or custom Pandas data depending on the instrument Entitlements, history, adjustments, and cost vary by source
Futures DataBento, developing IBKR REST paths, or custom data Simulated margin defaults may not match the broker's current requirement
Crypto Polygon, CCXT, developing IBKR REST paths, or custom data Venue symbols, funding, liquidity, and order behavior differ
Prediction contracts Polymarket history and broker workflow Price bounds, tick sizes, resolution, and market-close rules are venue-specific

Example: a credential-free Pandas backtest

PandasDataBacktesting is the smallest useful way to verify Lumibot's strategy lifecycle without a broker account or a remote data provider. This example uses six synthetic daily bars, buys 10 shares on the first strategy iteration, and sells them on the fourth. Market orders remain pending until the following bar, so the fills occur at 101 and 106 rather than at the bars that submitted the orders.

Install the same release reviewed on this page:

python -m pip install lumibot==4.5.91
from datetime import datetime
import os
from pathlib import Path

os.environ["LUMIBOT_DISABLE_UI"] = "1"

import pandas as pd
from lumibot.backtesting import PandasDataBacktesting
from lumibot.entities import Asset, Data, TradingFee
from lumibot.strategies import Strategy


ASSET = Asset("TEST", asset_type="stock")
INDEX = pd.date_range("2025-01-02", periods=6, freq="B", tz="America/New_York")
BARS = pd.DataFrame(
    {
        "open": [100, 101, 102, 104, 106, 105],
        "high": [101, 102, 104, 106, 107, 106],
        "low": [99, 100, 101, 103, 104, 103],
        "close": [100, 101, 103, 105, 105, 104],
        "volume": [1_000] * 6,
    },
    index=INDEX,
)


class RoundTrip(Strategy):
    fills = 0

    def initialize(self):
        self.sleeptime = "1D"
        self.iteration = 0

    def on_trading_iteration(self):
        self.iteration += 1
        if self.iteration == 1:
            self.submit_order(self.create_order(ASSET, 10, "buy"))
        elif self.iteration == 4:
            self.submit_order(self.create_order(ASSET, 10, "sell"))

    def on_filled_order(self, position, order, price, quantity, multiplier):
        type(self).fills += 1
        print(
            f"fill side={order.side} quantity={quantity} "
            f"price={float(price):.2f}"
        )


tmp = Path("/tmp/lumibot-pandas-output")
tmp.mkdir(exist_ok=True)
result = RoundTrip.backtest(
    PandasDataBacktesting,
    datetime(2025, 1, 2),
    datetime(2025, 1, 10),
    pandas_data=[Data(ASSET, BARS, timestep="day")],
    budget=10_000,
    benchmark_asset=None,
    risk_free_rate=0,
    buy_trading_fees=[TradingFee(flat_fee=1)],
    sell_trading_fees=[TradingFee(flat_fee=1)],
    show_plot=False,
    show_tearsheet=False,
    save_tearsheet=False,
    save_stats_file=False,
    show_progress_bar=False,
    stats_file=str(tmp / "stats.csv"),
    trades_file=str(tmp / "trades.csv"),
    logfile=str(tmp / "run.log"),
)
print(f"total_return={result['total_return'] * 100:.2f}%")
print(f"fills={RoundTrip.fills}")

With Lumibot 4.5.91, the relevant output is:

fill side=buy quantity=10.0 price=101.00
fill side=sell quantity=10.0 price=106.00
total_return=0.48%
fills=2

The $48 gain is hand-checkable: (106 - 101) * 10 - $1 buy fee - $1 sell fee. Setting risk_free_rate=0 and omitting a benchmark prevents performance analysis from fetching an external series. LUMIBOT_DISABLE_UI prevents generated reports from opening a browser.

This run checks the custom-data path, lifecycle callbacks, next-bar market fills, positions, fees, and account-value calculation. It does not test a broker adapter, remote data, exchange calendars beyond the supplied timestamps, spreads, slippage, partial fills, liquidity, or live reconciliation. The rising prices are constructed for inspection and provide no evidence of a tradable edge.

Same strategy code does not mean the same fills

The backtesting broker processes pending orders at each new bar. run_backtest() accepts explicit trading-fee and slippage models, but neither appears automatically calibrated to a user's venue and order size. Bar data cannot reproduce queue priority, network latency, hidden liquidity, rejected orders, or market impact. Options also require assumptions about exercise, assignment, spreads, and data quality.

Using the same strategy class is still valuable because signal and risk logic need not be reimplemented. However, the data source, clock, broker state, order lifecycle, and failure modes change between modes. Confirm timezone and calendar behavior, point-in-time universe membership, corporate actions, warm-up data, buying power, short availability, and all fees before comparing results.

Data and broker support should be tested as a matrix

The backtesting guide lists different recommended sources by asset class. Some paths remain explicitly under development. For example, the public IBKR REST backtesting guide says second-level and tick-level history are not yet a fully supported end-to-end open-source workflow. A broker that works for live orders is not automatically the correct source for a historical test.

Likewise, a provider's current API access does not establish historical coverage or point-in-time correctness. Archive raw inputs, provider settings, adjustment flags, calendars, and the installed Lumibot version. For paid feeds, check the provider's current terms and price directly instead of relying on a fixed third-party number.

The published license information conflicts

Lumibot's license information is inconsistent. The repository's current LICENSE file, GitHub label, and README say GPL-3.0. PyPI and setup.py say MIT. These are very different licenses, so confirm the terms before using it commercially.

Do not assume either label resolves the conflict for redistribution, linking, or a proprietary service. Check the exact source or wheel being used and obtain clarification from the maintainers or legal counsel before making a licensing decision. The optional managed BotSpot service is a separate commercial offering and should not be treated as part of the open-source license.

Live-operation safeguards

Paper trade first. Use dedicated accounts or subaccounts, least-privilege credentials with withdrawals disabled where possible, conservative exposure and order-size limits, and independent monitoring of balances, positions, open orders, fills, and stale data. Plan for rate limits, token expiry, disconnects, duplicate events, partial fills, process restarts, and disagreements between local and broker state.

When to choose Lumibot

Choose Lumibot when its supported data-and-broker combination matches the asset and a shared Python strategy API is more valuable than exchange-level simulation. Verify the exact adapter rather than choosing from a headline broker count. Consider NautilusTrader when tick ordering and detailed execution events drive the research question. Resolve the license inconsistency before adopting Lumibot in a distribution-sensitive project.

Choose which optional services may run. You can change these settings at any time.