Common Mistakes and How to Avoid Them¶
This page documents the most common mistakes made when writing Lumibot strategies, along with the correct patterns to use instead.
Critical Mistakes (Will Break Your Strategy)¶
Using datetime.now() Instead of self.get_datetime()¶
Impact: Backtest results will be completely wrong. Your strategy will think it’s the current date instead of the simulated date.
# WRONG
current_time = datetime.now()
target_date = datetime.today() + timedelta(days=30)
# CORRECT
current_time = self.get_datetime()
target_date = self.get_datetime() + timedelta(days=30)
Adding ‘from __future__ import annotations’¶
Impact: Causes immediate crash during backtesting.
# NEVER DO THIS - WILL CRASH YOUR STRATEGY
from __future__ import annotations
Simply remove this import. It’s not needed and will break everything.
Assigning Attributes Directly on self¶
Impact: Overrides Lumibot internals, causing crashes or unexpected behavior.
# WRONG - collides with framework
def initialize(self):
self.name = "MyBot"
self.asset = Asset("SPY")
self.symbol = "SPY"
# CORRECT - use self.vars
def initialize(self):
self.vars.strategy_label = "MyBot"
self.vars.target_asset = Asset("SPY", asset_type=Asset.AssetType.STOCK)
self.vars.target_symbol = "SPY"
Forgetting set_market(“24/7”) for Crypto¶
Impact: Crypto bot stops trading at 4pm EST every day.
# WRONG - crypto bot stops at 4pm
def initialize(self):
self.sleeptime = "1M"
# CORRECT - crypto trades 24/7
def initialize(self):
self.set_market("24/7") # REQUIRED for crypto
self.sleeptime = "1M"
Data Mistakes¶
Not Checking if get_last_price() Returns None¶
Impact: Crashes with NoneType error when data is unavailable.
# WRONG - will crash on None
price = self.get_last_price(asset)
quantity = self.portfolio_value / price # Crashes if price is None
# CORRECT - always check for None
price = self.get_last_price(asset)
if price is None:
self.log_message(f"No price for {asset.symbol}", color="red")
return
quantity = self.portfolio_value / price
Using get_historical_prices() for Real-Time Data¶
Impact: Strategy uses stale data, missing current price movements.
# WRONG - historical data can be 1 minute delayed
bars = self.get_historical_prices(asset, 1, "minute")
current_price = bars.df.iloc[-1]["close"]
# CORRECT - use get_last_price for real-time
current_price = self.get_last_price(asset)
# BEST - use get_quote for bid/ask
quote = self.get_quote(asset)
if quote and quote.bid and quote.ask:
mid_price = quote.mid_price
Returning Early When get_greeks() is None¶
Impact: Strategy stops running for the entire iteration just because one option has no Greeks.
# WRONG - blocks entire strategy
greeks = self.get_greeks(option_asset)
if greeks is None:
return # Strategy stops here!
# CORRECT - continue with other logic
greeks = self.get_greeks(option_asset)
if greeks is not None:
delta = greeks.get("delta")
# Use delta here
else:
self.log_message("Greeks unavailable, skipping delta check", color="yellow")
# Strategy continues with other logic...
Options Mistakes¶
Manually Selecting Option Expirations¶
Impact: Selected expiration may not have tradeable data during backtesting.
# WRONG - expiration may not exist
target_expiry = self.get_datetime() + timedelta(days=30)
option = Asset("SPY", asset_type=Asset.AssetType.OPTION,
expiration=target_expiry.date(), strike=400, right="call")
# CORRECT - use OptionsHelper
chains = self.get_chains(underlying_asset)
target_expiry = self.get_datetime() + timedelta(days=30)
valid_expiry = self.options_helper.get_expiration_on_or_after_date(
target_expiry, chains, "call"
)
Forgetting Options are 100x Multiplied¶
Impact: Position sizing is off by 100x.
# WRONG - buys 100x too many contracts
option_price = 1.50 # $1.50 premium
contracts = 10000 / option_price # Wrong: 6666 contracts!
# CORRECT - account for multiplier
option_price = 1.50
actual_cost_per_contract = option_price * 100 # $150
contracts = int(10000 / actual_cost_per_contract) # Correct: 66 contracts
Using get_last_price() for Options Pricing¶
Impact: Stale or missing prices for illiquid options.
# WRONG - last trade can be very stale for options
price = self.get_last_price(option_asset)
# CORRECT - use quote for bid/ask
quote = self.get_quote(option_asset)
if quote and quote.bid and quote.ask:
fair_price = quote.mid_price
else:
self.log_message("No valid quote for option", color="yellow")
Order Mistakes¶
Expecting Immediate Position Updates After submit_order()¶
Impact: Strategy logic based on outdated position data.
# WRONG - position hasn't updated yet
self.submit_order(order)
position = self.get_position(asset) # Still shows old data!
# CORRECT - check on next iteration
self.submit_order(order)
# In the NEXT on_trading_iteration():
position = self.get_position(asset) # Now updated
Using submit_order() to Close Crypto Futures¶
Impact: Opens a new position instead of closing existing one.
# WRONG - opens opposite position instead of closing
order = self.create_order(futures_asset, quantity, "sell")
self.submit_order(order)
# CORRECT - use close_position
self.close_position(futures_asset)
Using Deprecated take_profit_price/stop_loss_price¶
Impact: May cause order errors or unexpected behavior.
# WRONG - deprecated parameters
order = self.create_order(asset, 100, "buy",
take_profit_price=110,
stop_loss_price=90)
# CORRECT - use secondary_ parameters
order = self.create_order(asset, 100, "buy",
order_class=Order.OrderClass.BRACKET,
secondary_limit_price=110, # Take profit
secondary_stop_price=90) # Stop loss
Visualization Mistakes¶
Adding Markers Every Iteration¶
Impact: Chart crashes or becomes unusable due to thousands of markers.
# WRONG - adds marker every iteration
def on_trading_iteration(self):
self.add_marker("Price", price, color="blue") # Chart explodes!
# CORRECT - markers only for significant events
def on_trading_iteration(self):
if signal_detected: # Only when something happens
self.add_marker("Buy Signal", price, color="green", asset=my_asset)
# Use add_line for continuous data
self.add_line("SMA_20", sma_value, color="blue", asset=my_asset)
Using ‘text’ Parameter in add_marker/add_line¶
Impact: TypeError crash - there is no ‘text’ parameter.
# WRONG - causes TypeError
self.add_marker("Signal", price, text="Buy now!")
# CORRECT - use detail_text for hover text
self.add_marker("Signal", price, detail_text="Buy signal triggered", asset=my_asset)
Forgetting to Pass asset Parameter¶
Impact: Indicators appear in separate subplot instead of overlaying price chart.
# WRONG - indicator in separate subplot
self.add_line("SMA_20", sma_value, color="blue")
# CORRECT - overlays on asset's price chart
self.add_line("SMA_20", sma_value, color="blue", asset=spy_asset)
Code Organization Mistakes¶
Hardcoding API Keys¶
Impact: Security risk and deployment problems.
# WRONG - hardcoded fallback
api_key = os.getenv('PERPLEXITY_API_KEY', 'your_api_key_here')
# CORRECT - None fallback
api_key = os.getenv('PERPLEXITY_API_KEY')
if api_key is None:
self.log_message("PERPLEXITY_API_KEY not set", color="red")
Setting Parameters in Multiple Places¶
Impact: Confusing, parameters override each other unpredictably.
# WRONG - parameters set in multiple places
class MyStrategy(Strategy):
parameters = {"symbol": "SPY"}
if __name__ == "__main__":
strategy = MyStrategy(parameters={"symbol": "AAPL"}) # Which one wins?
# CORRECT - one place only
class MyStrategy(Strategy):
parameters = {
"symbol": "SPY",
"period": 20
}
# Never override parameters elsewhere
Using try/except to Hide Errors¶
Impact: Bugs are hidden, making debugging nearly impossible.
# WRONG - hides real errors
try:
price = self.get_last_price(asset)
quantity = self.portfolio_value / price
except:
pass # What went wrong? No idea!
# CORRECT - explicit error handling
price = self.get_last_price(asset)
if price is None:
self.log_message(f"No price for {asset.symbol}", color="red")
return
quantity = self.portfolio_value / price
Using sleep() in Strategy¶
Impact: Blocks the entire bot, preventing important code from running.
# WRONG - blocks everything
def on_trading_iteration(self):
self.submit_order(order)
time.sleep(5) # Bot frozen for 5 seconds!
# CORRECT - check conditions next iteration
def on_trading_iteration(self):
if not hasattr(self.vars, "order_time"):
self.submit_order(order)
self.vars.order_time = self.get_datetime()
return
elapsed = self.get_datetime() - self.vars.order_time
if elapsed > timedelta(seconds=5):
# Now do the next step
pass