QuantConnect/Lean · error · ArgumentException

Total must be > 0 for Euclidean Projection onto the Simplex.

Error message

Total must be > 0 for Euclidean Projection onto the Simplex.

What it means

MeanReversionPortfolioConstructionModel.normalize() projects a weight vector onto the L1 simplex (sum-to-total) via the Duchi et al. algorithm. A non-positive 'total' makes the projection undefined (it cannot normalize onto a negative/zero-mass simplex), so it raises ArgumentException before running the sort/cumsum logic.

Source

Thrown at Algorithm.Framework/Portfolio/MeanReversionPortfolioConstructionModel.py:165

        for symbol in symbols:
            if symbol not in self.symbol_data:
                self.symbol_data[symbol] = self.MeanReversionSymbolData(algorithm, symbol, self.window_size, self.resolution)

    def SimplexProjection(self, vector, total=1):
        """Normalize the updated portfolio into weight vector:
        v_{t+1} = arg min || v - v_{t+1} || ^ 2
        Implementation from:
        Duchi, J., Shalev-Shwartz, S., Singer, Y., & Chandra, T. (2008, July). 
            Efficient projections onto the l 1-ball for learning in high dimensions.
            In Proceedings of the 25th international conference on Machine learning 
            (pp. 272-279).
        Args:
            vector: unnormalized weight vector
            total: total weight of output, default to be 1, making it a probabilistic simplex
        """
        if total <= 0:
            raise ArgumentException("Total must be > 0 for Euclidean Projection onto the Simplex.")
            
        vector = np.asarray(vector)

        # Sort v into u in descending order
        mu = np.sort(vector)[::-1]
        sv = np.cumsum(mu)

        rho = np.where(mu > (sv - total) / np.arange(1, len(vector) + 1))[0][-1]
        theta = (sv[rho] - total) / (rho + 1)
        w = (vector - theta)
        w[w < 0] = 0
        return w

    class MeanReversionSymbolData:
        def __init__(self, algo, symbol, window_size, resolution):
            # Indicator of price
            self.Identity = algo.Identity(symbol, resolution)
            # Moving average indicator for mean reversion level

View on GitHub (pinned to d2c3659f87)

Solutions

  1. Do not override the 'total' argument; let it default to 1 so weights sum to 100%.
  2. If computing total dynamically, clamp/guard it to a positive minimum before calling normalize().
  3. Ensure the algorithm has positive total portfolio value / budget before rebalancing.

Example fix

# before
total = sum_of_signed_targets  # could be <= 0
w = self.normalize(vector, total)

# after
total = sum_of_signed_targets
if total <= 0:
    raise ValueError('normalize requires positive total')
w = self.normalize(vector, total)
Defensive patterns

Strategy: validation

Validate before calling

def safe_normalize(model, vector, total=1):
    if total is None or total <= 0:
        total = 1.0
    return model.normalize(vector, total)

Type guard

def valid_total(total) -> bool:
    return isinstance(total, (int, float)) and total > 0

Prevention

When it happens

Trigger: normalize(vector, total) is called with total <= 0. In normal use total defaults to 1; this fires only if a caller overrides total with zero or a negative number, or passes a degenerate budget.

Common situations: Subclass overriding normalize() and forwarding a computed total that became 0/negative (e.g., a sum of signed targets that cancelled out), or passing a budget parameter derived from total portfolio value when that value is zero/under-margin.

Related errors


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