Lesson 0007

Run a Tiny Portfolio QP

Turn the dual-value diagnostic into a concrete two-asset CVXPY model.

The One-Step Skill

In Lesson 0006, you read a solver table. Now build the table yourself from a tiny quadratic program.

The model is intentionally small: choose weights for assets A and B, reward expected return, penalize variance, require full investment, and cap each asset.

minimize    0.5 * w' Sigma w - mu' w
subject to  sum(w) = 1
            0 <= w
            w <= cap

This is the minimization form used by many solvers. The return term is negative because higher expected return should reduce the objective.

The Code

Run this after installing CVXPY in a Python environment. The official CVXPY examples use the same pattern: create variables, build constraints, solve, then inspect each constraint's dual_value.

import cvxpy as cp
import numpy as np

mu = np.array([0.12, 0.02])
Sigma = np.diag([0.10, 0.20])
cap = np.array([0.70, 0.80])

w = cp.Variable(2)
risk = 0.5 * cp.quad_form(w, Sigma)
ret = mu @ w
objective = cp.Minimize(risk - ret)

constraints = [
    w >= 0,
    w <= cap,
    cp.sum(w) == 1,
]

problem = cp.Problem(objective, constraints)
problem.solve()

print("status:", problem.status)
print("weights:", np.round(w.value, 4))
print("upper slack:", np.round(cap - w.value, 4))
print("upper duals:", np.round(constraints[1].dual_value, 4))
print("budget dual:", round(float(constraints[2].dual_value), 4))

Expected Reading

The numbers are chosen so that the unconstrained optimizer wants more asset A than the cap allows. After solving, you should see this shape:

status:      optimal
weights:     [0.7000 0.3000]
upper slack: [0.0000 0.5000]
upper duals: [0.0900 0.0000]

A is capped

Asset A sits at 0.70, exactly its cap. Its slack is zero, so its upper-bound dual can be positive.

B is loose

Asset B sits at 0.30, below its 0.80 cap. Its slack is positive, so its upper-bound dual should be zero.

The budget is different

The full-investment constraint is an equality. Its dual is not read as slack times pressure and can have either sign.

Verify by Hand

Because w_B = 1 - w_A, the unconstrained one-dimensional objective has its minimum at w_A = 1.00. That is infeasible because w_A <= 0.70.

unconstrained wants: w_A = 1.00
cap allows only:     w_A = 0.70
therefore:           A cap binds

w_B = 1 - w_A = 0.30
B cap slack = 0.80 - 0.30 = 0.50

This hand check is the habit you want before trusting larger portfolio optimizers. Solver output should match the economic story.

Retrieval Practice

Why is the return term written as - mu' w in this model?

If w_A = 0.70 and cap_A = 0.70, what is A's upper slack?

If B has slack 0.50, what should B's upper-cap dual be?

Primary Source

Use CVXPY's official quadratic-program example for the modeling pattern and its dual variables guide for reading dual_value. Keep the CVXPY portfolio template open while modifying the example.