QuantConnect/Lean · error · AssertionError
Algorithm should have been invested at the end of the algori
Error message
Algorithm should have been invested at the end of the algorithm
What it means
Assertion in PEP8StyleBasicAlgorithm.on_end_of_algorithm that the portfolio holds a position at algorithm end. The algorithm calls self.set_holdings(self.spy, 1) once in on_data when not invested, so if the strategy never took (and kept) a position, this fails. It is a sanity check that the single intended trade actually executed and stuck.
Source
Thrown at Algorithm.Python/PEP8StyleBasicAlgorithm.py:40
self.spy = self.add_equity("SPY", Resolution.MINUTE, extended_market_hours=False, fill_forward=True).symbol
# Test accessing a constant (QCAlgorithm.MaxTagsCount)
self.debug("MaxTagsCount: " + str(self.MAX_TAGS_COUNT))
def on_data(self, slice):
if not self.portfolio.invested:
self.set_holdings(self.spy, 1)
self.debug("Purchased Stock")
def on_order_event(self, order_event):
self.log(f"{self.time} :: {order_event}")
def on_end_of_algorithm(self):
self.log("Algorithm ended!")
if not self.portfolio.invested:
raise AssertionError("Algorithm should have been invested at the end of the algorithm")
# let's do some logging to do more pep8 style testing
self.log("-----------------------------------------------------------------------------------------")
self.log(f"{self.spy.value} last price: {self.securities[self.spy].price}")
self.log(f"{self.spy.value} holdings: "
f"{self.securities[self.spy].holdings.quantity}@{self.securities[self.spy].holdings.price}="
f"{self.securities[self.spy].holdings.holdings_value}")
self.log("-----------------------------------------------------------------------------------------")
View on GitHub (pinned to d2c3659f87)
Solutions
- Confirm the security has data for the backtest window: check self.securities[self.spy].has_data and that history is non-empty for the start/end dates.
- Verify market hours and that the backtest range spans at least one open session for the symbol's exchange.
- Check the order events (self.transactions.get_orders()) for rejections or invalid-status fills and inspect their messages.
- Ensure set_holdings weight and leverage permit the trade (lower the weight or call self.set_security(self.spy) / adjust BuyingPowerModel).
- Confirm the symbol/market resolves (e.g. add_equity('SPY') maps to the expected market) and that data is present on disk for that range.
Example fix
# before
def on_data(self, slice):
if not self.portfolio.invested:
self.set_holdings(self.spy, 1)
self.debug("Purchased Stock")
# after (guard against no-data and verify the order was accepted)
def on_data(self, slice):
if not self.portfolio.invested and self.securities[self.spy].has_data:
ticket = self.set_holdings(self.spy, 1)
if ticket.status == OrderStatus.INVALID:
self.error(f"Order invalid: {ticket.tag}")
else:
self.debug("Submitted purchase") Defensive patterns
Strategy: validation
Validate before calling
# Before asserting invested, confirm a fill actually occurred
orders = self.transactions.get_orders(lambda o: o.symbol == self.spy)
filled = any(o.status == OrderStatus.FILLED for o in orders)
if not self.portfolio.invested and not filled:
self.debug("No SPY fill occurred; will not assert invested")
else:
assert self.portfolio.invested, "Expected to be invested" Type guard
def security_has_data(algorithm, symbol):
"""True when the security has received data and can be traded."""
sec = algorithm.securities[symbol]
return sec.has_data and sec.is_tradable Prevention
- Check self.securities[symbol].has_data before placing the entry order.
- Inspect the order ticket status after set_holdings; log OrderStatus.INVALID tags.
- Ensure the backtest range spans a trading session for the symbol's exchange.
When it happens
Trigger: self.portfolio.invested is False at on_end_of_algorithm. This happens when on_data's set_holdings call never filled: no minute bars were received for SPY, the order was rejected/invalid, buying power was insufficient, the fill never arrived before backtest end, or the position was opened then fully closed by a later action.
Common situations: Backtest date range has no data for the security (wrong symbol/market, delisted, holiday-only range). Set_holdings with weight 1 fails due to leverage/buying-power limits. The security was added but data feed produced no slices. A fill model or brokerage rejected the order. Market hours meant no fills occurred in the short window.
Related errors
- OrderEvent LimitPrice is Not expected to be 0 for LimitOrder
- OrderEvent StopPrice is Not expected to be 0 for StopMarketO
- OrderEvent Ticket was not set
- OrderEvent.ORDER_ID and order_event.ticket.order_id do not m
- Stock was not assigned
AI-assisted analysis of QuantConnect/Lean@d2c3659f87 (2026-08-13).
Data as JSON: /api/errors/4d12ec405ec92dc4.
Report an issue: GitHub.