QuantConnect/Lean · error · AssertionError

Algorithm should have been invested at the end of the…

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.

Solutions

  1. 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.
  2. Verify market hours and that the backtest range spans at least one open session for the symbol's exchange.
  3. Check the order events (self.transactions.get_orders()) for rejections or invalid-status fills and inspect their messages.
  4. Ensure set_holdings weight and leverage permit the trade (lower the weight or call self.set_security(self.spy) / adjust BuyingPowerModel).
  5. 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

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


AI-assisted analysis of QuantConnect/Lean@d2c3659f87 (2026-08-13). Data as JSON: /api/errors/4d12ec405ec92dc4. Report an issue: GitHub.

Appendix: 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)