QuantConnect/Lean · error · AssertionError

Expected the portfolio to have holdings and to have {self.tr

Error message

Expected the portfolio to have holdings and to have {self.trade_count} trades, but had {self.transactions.orders_count}

What it means

On algorithm end, OrderTicketAssignmentDemoAlgorithm asserts the portfolio holds a position (self.portfolio.invested) and that the number of orders submitted equals the number of trades the consolidator fired (self.trade_count == self.transactions.orders_count). A failure means either no position was ever opened (orders not filled) or the order count diverged (orders duplicated, rejected, or the consolidator fired a different number of times than expected).

Source

Thrown at Algorithm.Python/OrderTicketAssignmentDemoAlgorithm.py:53

        self.ticket = self.market_order(self._symbol, 1, asynchronous=True)
        self.debug(f"{self.time}: Buy: Price {bar.price}, order_id: {self.ticket.order_id}")
        self.trade_count += 1

    def on_order_event(self, order_event: OrderEvent):
        # We cannot access self.ticket directly because it is assigned asynchronously:
        # this order event could be triggered before self.ticket is assigned.
        ticket = order_event.ticket
        if ticket is None:
            raise AssertionError("Expected order ticket in order event to not be null")
        if order_event.status == OrderStatus.SUBMITTED and self.ticket is not None:
            raise AssertionError("Field self.ticket not expected no be assigned on the first order event")

        self.debug(ticket.to_string())

    def on_end_of_algorithm(self):
        # Just checking that orders were placed
        if not self.portfolio.invested or self.trade_count != self.transactions.orders_count:
            raise AssertionError(f"Expected the portfolio to have holdings and to have {self.trade_count} trades, but had {self.transactions.orders_count}")

View on GitHub (pinned to d2c3659f87)

Solutions

  1. Extend the backtest date range so every hourly consolidator bar fires and orders fill.
  2. Confirm SPY minute/second data exists for the full period so consolidators trigger.
  3. Check order tickets for rejections/cancellations inflating or deflating orders_count.
  4. Ensure the consolidator registration and trade_count increment stay in sync (one increment per order).

Example fix

# before: end date too short -> under-filled
self.set_end_date(2013, 10, 8)
# after: enough bars for consolidator fills and invested portfolio
self.set_end_date(2013, 10, 11)
Defensive patterns

Strategy: validation

Validate before calling

# Verify holdings and order count with diagnostics before asserting
if not self.portfolio.invested:
    self.log(f'not invested; orders={self.transactions.orders_count} trades={self.trade_count}')
if self.trade_count != self.transactions.orders_count:
    pending = [o for o in self.transactions.get_orders() if o.status not in (OrderStatus.FILLED,)]
    self.log(f'pending/unfilled orders: {pending}')
if not self.portfolio.invested or self.trade_count != self.transactions.orders_count:
    raise AssertionError(...)

Prevention

When it happens

Trigger: on_end_of_algorithm with self.portfolio.invested False, or self.trade_count != self.transactions.orders_count. Caused by asynchronous market orders not filling within the backtest window, the hourly consolidator firing fewer/more times than expected, or orders being rejected/canceled so orders_count differs from trade_count.

Common situations: Backtest end date too short for all hourly bars to fire; the equity (SPY) data missing bars so the consolidator under-fires; asynchronous orders pending/unfilled at termination; a duplicate order path inflating orders_count.

Related errors


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