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