# Cardinality-Constrained Portfolio This reference answers one operational question: how do we limit the number of nonzero final holdings without accidentally limiting the number of trades needed to reach them? Every numerical example here is controlled synthetic teaching data. ## Frozen reference variant The canonical problem is a one-period, long-only final-holdings cardinality problem. Let: - x ∈ R^n be post-decision portfolio weights; - y_i ∈ {0,1} indicate whether final holding i is selected; - K be the maximum number of selected final holdings; - μ be expected simple returns for the same period; - Σ be a symmetric covariance matrix for that period; - λ ≥ 0 be the risk-aversion coefficient in the utility units; - l_i and u_i be explicit lower and upper bounds when y_i=1. The frozen objective and constraints are: ~~~text maximize μᵀx − λ xᵀΣx subject to 1ᵀx = 1 l_i y_i ≤ x_i ≤ u_i y_i Σ_i y_i ≤ K x_i ≥ 0, y_i ∈ {0,1}. ~~~ The package reports y from realized strictly positive holdings (x_i > 0) and reports a separate trade support from |x_i-current_i| > 10⁻⁸. When a positive lower bound is supplied, selection implies a positive holding. With l_i=0, a zero selected coordinate is redundant; the displayed realized support is the enforceable count. A tiny positive holding is not hidden by a presentation threshold. This is a deliberately small exact reference: n ≤ 12, K ≤ 6, finite bounds in [0,1], and normalized current holdings. For λ>0, the exact active-set certificate requires a positive-definite Σ; the λ=0 linear case is handled by vertex enumeration. Larger or singular cases need an explicitly certified MIP/QP workflow rather than a silent fallback. ## Why the source distinction matters MOSEK’s [transaction-cost and cardinality chapter](https://docs.mosek.com/portfolio-cookbook/transaction.html) defines its §6.4 binary vector against x−x0, so that source example counts nonzero trades. It does not directly prove a final-holdings cardinality constraint. This package adapts the binary-link pattern to x_i and proves the adaptation by enumerating every final support. Its §6.5 buy-in thresholds are a nearby trade constraint, not part of this canonical objective. On portfolio data work I have had to reason about changing current weights and repaired inputs before deciding whether a rebalance is needed. That is the practical reason for showing the current vector beside both support counts: a sparse final portfolio can still require many sells and buys. ## Exact small-instance method For each nonempty support S with |S|≤K, the solver sets unselected holdings to zero and solves the bounded continuous quadratic problem. It enumerates each selected coordinate as lower-bound, free, or upper-bound; free coordinates satisfy: ~~~text μ_i − 2 λ (Σx)_i = ν ~~~ for one budget multiplier ν. Feasible candidates are checked against bound and KKT inequalities, and the best objective is retained. Every support is visited before status=optimal is emitted. The independent fixture has ten supports (4 singletons plus 6 pairs). The exact fixture is: ~~~text μ = [0.12, 0.10, 0.09, 0.08] Σ = diag(0.30, 0.02, 0.01, 0.01) λ = 1 current = [0.25, 0.25, 0.25, 0.25] K = 2 ~~~ The certified answer is x=[0,0.5,0.5,0], objective 0.0875, realized final support 2, and trade support 4. A top-two-by-mean allocation [0.5,0.5,0,0] has objective 0.03; it is a heuristic, not the solution to the stated constrained problem. ## Solver and failure contract - optimal: all Σ_{j=1..K} choose(n,j) supports and continuous active sets were checked. - feasible: an incumbent exists but max_supports stopped enumeration; best_bound and relative_gap remain null. - incomplete: max_supports stopped enumeration before any incumbent was found; weights, bounds, and gaps remain null, so this is neither feasibility proof nor infeasibility proof. - infeasible: all declared supports were checked and none met the bounds; no rounded portfolio is returned. - validation errors identify invalid K, bounds, normalization, covariance symmetry/definiteness, or the small-reference scope. No status is inferred from a top-K ranking, rounded weights, a partial support list, or a solver timeout. The output includes the realized support, trade support, enumeration counts, and certificate fields so a caller can make the same decision independently. In a truncated search with no incumbent, the explicit incomplete status prevents a failed first support from being mislabeled infeasible. ## Inputs and units All weights and bounds are fractions of one portfolio; μ and Σ must refer to the same horizon. λ multiplies variance in the same utility units as μᵀx; it is not a universal risk preference. The current vector must be normalized only because it is a diagnostic anchor, not because the optimizer silently repairs it. This package excludes transaction costs, turnover, taxes, short positions, dynamic paths, lot selection, market data ingestion, and a production-scale mixed-integer solver. Related topics handle those separate contracts: Transaction-Cost-Aware Optimization, Turnover-Constrained Optimization, Long-Short Gross/Net Constraints, and Tax-Aware Portfolio Optimization. Their trade variables must not be substituted for final-support binaries here. ## Run it From this topic folder: ~~~bash python -B implementations/python/cardinality_constrained.py python -B -m unittest discover -s tests -p 'test_*.py' -v npx tsx tests/cardinality-constrained.test.ts ~~~ The supplied JSON fixture and both implementations expose the same expected values. The HTML lab adds a bounded support-count toggle and a deliberately truncated enumeration; it is an explanatory interface, not a replacement for the machine-readable result. ## Historical-example decision No historical market, account, or issuer example is claimed for this package. The controlled fixture is sufficient to test the final-versus-trade scope distinction; a future real-data case would require validated prices, corporate-action state, and a dated source record before publication.