Frequently Asked Questions (FAQ)¶
This page answers common questions about Lumibot. If you’re new to Lumibot, start with the Getting Started guide.
DateTime and Timing¶
Why can’t I use datetime.now() in my strategy?¶
Short answer: During backtesting, datetime.now() returns the real current time, not the simulated time. Your strategy will think it’s 2024 when it’s actually simulating trades from 2020.
Solution: Always use self.get_datetime() instead:
# WRONG - will break backtesting
current_time = datetime.now()
# CORRECT - works in both backtesting and live trading
current_time = self.get_datetime()
Why is my historical data delayed or not up-to-date?¶
get_historical_prices() returns completed bars and may be delayed by up to a minute. For real-time price data, use get_last_price() or get_quote() instead.
# For historical analysis (may be 1 minute delayed)
bars = self.get_historical_prices(asset, 20, "minute")
# For real-time price (latest tick)
price = self.get_last_price(asset)
# For bid/ask spread and detailed market data
quote = self.get_quote(asset)
if quote is not None:
self.log_message(f"Bid: {quote.bid}, Ask: {quote.ask}, Mid: {quote.mid_price}")
Variables and State¶
Why do my variables reset between trading iterations?¶
Local variables are reset each iteration. Use self.vars for persistent state:
# WRONG - resets each iteration
def on_trading_iteration(self):
count = 0
count += 1 # Always 1
# CORRECT - persists between iterations
def on_trading_iteration(self):
if not hasattr(self.vars, "count"):
self.vars.count = 0
self.vars.count += 1 # Actually increments
Why can’t I set self.name or other attributes on the strategy?¶
Never assign arbitrary attributes directly on self (like self.name, self.asset, self.symbol). These can collide with Lumibot internals and crash your strategy.
# WRONG - will crash or override framework behavior
def initialize(self):
self.name = "MyBot" # Collides with internal name
self.asset = Asset("SPY") # May conflict with internal methods
# CORRECT - use self.vars
def initialize(self):
self.vars.strategy_label = "MyBot"
self.vars.target_asset = Asset("SPY", asset_type=Asset.AssetType.STOCK)
Why does ‘from __future__ import annotations’ break my strategy?¶
This import breaks Lumibot’s type checking system and causes backtests to crash. Never use it.
# NEVER DO THIS - will crash your strategy
from __future__ import annotations
# Just remove this import - it's not needed
Options Trading¶
Why do I get None when calling get_chains()?¶
Not all option expiries and strikes are available. Always check if the result is None before using it:
chains = self.get_chains(underlying_asset)
if chains is None:
self.log_message("No options chains available")
return
# Use OptionsHelper for reliable expiration finding
expiry = self.options_helper.get_expiration_on_or_after_date(target_date, chains, "call")
Why does get_greeks() return None?¶
Greeks may be unavailable for illiquid options. Always check for None but don’t return early - let your strategy continue:
greeks = self.get_greeks(option_asset, underlying_price=price)
if greeks is not None:
delta = greeks.get("delta")
self.log_message(f"Delta: {delta}")
# Execute Greeks-dependent logic here
else:
self.log_message("Greeks unavailable - option may be illiquid", color="yellow")
# Strategy continues regardless
Why do my option calculations seem off by 100x?¶
Options are multiplied by 100. A $1.50 option premium costs $150 per contract:
option_price = 1.50 # Premium per share
actual_cost = option_price * 100 # $150 per contract
# To buy $10,000 worth of options at $1.50 premium:
contracts = int(10000 / (option_price * 100)) # = 66 contracts
Crypto Trading¶
Why does my crypto strategy stop trading at 4pm?¶
Crypto markets are 24/7, but Lumibot defaults to stock market hours. Add this to initialize:
def initialize(self):
self.set_market("24/7") # REQUIRED for crypto
self.sleeptime = "15S" # Optional: run every 15 seconds
How do I close a crypto futures position?¶
For Asset.AssetType.CRYPTO_FUTURE, you must use close_position() instead of submit_order():
# WRONG - opens a new position on the other side
order = self.create_order(futures_asset, quantity, "sell")
self.submit_order(order)
# CORRECT - actually closes the position
self.close_position(futures_asset)
Orders and Positions¶
Why isn’t my position updated immediately after submit_order()?¶
Positions update at the start of each trading iteration. Check on the next iteration:
# submit_order() is async - position won't update immediately
self.submit_order(order)
# WRONG - will still show old position
position = self.get_position(asset)
# CORRECT - wait for next iteration to confirm
# In next on_trading_iteration():
position = self.get_position(asset)
if position is None:
self.log_message("Exit confirmed, position is closed")
Why does get_last_price() return None?¶
Price data may be unavailable for illiquid assets. Always check:
price = self.get_last_price(asset)
if price is None:
self.log_message(f"No price data for {asset.symbol}", color="red")
return
# Now safe to use price
Why does get_positions() return USD?¶
Cash is treated as a position. Filter it out:
positions = self.get_positions()
for position in positions:
if position.asset.symbol == "USD" and position.asset.asset_type == Asset.AssetType.FOREX:
continue # Skip cash position
# Process real positions here
Brokers¶
Why shouldn’t I mention specific brokers in my strategy code?¶
Lumibot strategies should be broker-agnostic. Broker configuration is handled via environment variables at deployment time, not in strategy code. This allows the same strategy to work with any supported broker.
# WRONG - mentioning specific broker
# "This strategy works with Interactive Brokers..."
# CORRECT - broker-agnostic code
# Strategy logic only - broker set at deployment
Debugging and Logging¶
Why should I use self.log_message() instead of print()?¶
self.log_message() integrates with Lumibot’s logging system and supports colors:
# Available colors: white, red, green, blue, yellow
self.log_message("Position opened", color="green")
self.log_message("Warning: low volume", color="yellow")
self.log_message("Error: no price data", color="red")
How should I use markers and lines for debugging?¶
Use add_line() for continuous data (indicators), add_marker() for infrequent events (signals):
# add_line - for continuous data like moving averages
self.add_line("SMA_20", sma_value, color="blue", asset=my_asset)
# add_marker - for infrequent events like buy/sell signals
# NEVER add markers every iteration - it crashes the chart!
if crossover_detected:
self.add_marker("Buy Signal", price, color="green", symbol="arrow-up", asset=my_asset)
Warning
add_line() and add_marker() do NOT have a text parameter. Use detail_text for hover text.
Backtesting¶
What data source should I use for options backtesting?¶
Yahoo does not support options. Use ThetaData or Polygon:
# For options backtesting
from lumibot.backtesting import ThetaDataBacktesting
# or
from lumibot.backtesting import PolygonDataBacktesting
What data source should I use for futures backtesting?¶
Only DataBento supports futures:
from lumibot.backtesting import DataBentoDataBacktesting
# Use flat fees for futures (typical: $0.50 per contract)
from lumibot.entities import TradingFee
trading_fee = TradingFee(flat_fee=0.50)
Why does my minute-level backtest fail with “no data”?¶
Most data sources limit minute-level data to 2 years. Set your start date accordingly:
# For minute-level backtests, use < 2 years of data
from datetime import datetime, timedelta
end_date = datetime.now()
start_date = end_date - timedelta(days=600) # ~1.5 years, safe margin