Lessons 0012–0013 wrote |w - w_prev| as if solvers eat absolute values for breakfast. They don't — a QP solver wants linear constraints and a smooth objective, and |·| is neither. Every real optimizer secretly rewrites the kink first. This lesson is that rewrite: the buy/sell split, the single most common modeling trick in portfolio code.
The One-Step Skill
Given any convex problem containing |t|, rewrite it in solver-ready form — split t into buys and sells, or bound it with an epigraph variable — and explain why the solver never cheats the rewrite. When a paper says “we introduce auxiliary variables w = w⁺ - w⁻,” you should hear this lesson.
The Picture First: The V Is the Ceiling of Two Lines
Plot |t| against the trade t: a V with its kink at zero. The kink is why stationarity (Lesson 0005) fails there — no single slope exists. But look at the V sideways:
|t| = max( t, -t )
the V is not a curve with a defect;
it is the CEILING of two perfectly ordinary lines:
the line y = t (charges you for buying)
the line y = -t (charges you for selling)
Lines are the one thing solvers handle natively. So every absolute-value trick is the same move: stop talking about the V, start talking about the two lines under it. There are two standard dialects.
Trick 1: the epigraph variable
Invent u = “my reported turnover” and demand it clears both lines: u >= t and u >= -t. Then use u wherever |t| appeared. Geometrically, u may sit anywhere above the V; the objective's pressure squashes it down onto the V.
Trick 2: the buy/sell split
Write the trade as t = b - s with b, s >= 0: b units bought, s units sold. Replace |t| with b + s. In a portfolio this isn't even a trick — buys and sells are what the trading desk sees.
One case needs no auxiliary variables at all. A constraint |t| <= τ is just two walls: -τ <= t <= τ. You already used this in Lesson 0013 — the trading band [w_prev - τ, w_prev + τ]was this rewrite. Auxiliaries are only needed when |t| sits in the objective or inside a sum.
Memory hook: never differentiate a kink — replace the V with the two lines it is made of. Ceiling of lines → a couple of linear inequalities. That's the whole industry.
The Theory: Why the Solver Never Pads
The split looks dangerous. The pair (b, s) is not unique: a net buy of 10 points can be reported as b = 10, s = 0 — or as b = 25, s = 15. Same trade, but the padded version claims turnover b + s = 40 instead of 10. If the solver may pick any pair, what forces honesty?
The fee does. Take Lesson 0012's penalty problem and rewrite it with the split:
before: minimize f0(w) + κ |w - w_prev|
after: minimize f0(w_prev + b - s) + κ (b + s)
subject to b >= 0, s >= 0
Pad any solution: add p > 0 to both b and s. The net trade b - s is unchanged, so f0 is unchanged — but the fee bill rises by exactly 2κp. With κ > 0, padding is pure loss, so the minimizer sets p = 0, which forces at least one of b, s to zero, which makes b + s = |b - s| = |t|. The rewrite is exact:
no-padding lemma: κ > 0 ⇒ at the optimum, b·s = 0 and b + s = |t|
the solver reports turnover honestly not because we told it to,
but because lying costs 2κp and buys nothing
Now read the fine print, because it is Lesson 0011's convexity license in disguise. The lemma needs the coefficient on b + s to be positive — i.e. it needs |t| to be penalized. Minimizing a convex |t|: fine. But put |t| in with a negative sign (rewarding turnover, or maximizing an absolute deviation) and the lemma dies: every unit of padding now pays, the solver pads forever, and the problem is unbounded. That is not a solver bug — it is the geometry telling you that maximizing a convex function is a non-convex problem. The split doesn't break convexity rules; it exposes them.
The same license, epigraph dialect: u >= |t| stays glued to the V only while pressure pushes udown. Reward u and it floats off to infinity.
The Kink, Finally in Plain Algebra
The split also settles a piece of unfinished business: why the first traded unit costs κ — the fact behind Lesson 0012's no-trade zone. The split objective is smooth in b and s separately, so we can just differentiate. Stand at b = s = 0 (no trade) and price a tiny move in each direction:
first unit bought: d/db [ f0(w_prev + b - s) + κ(b + s) ] = f0'(w_prev) + κ
first unit sold: d/ds [ ... ] = -f0'(w_prev) + κ
with w_prev = 60%: push = -f0'(0.60) = 0.12, fee κ = 0.09
buying: -0.12 + 0.09 = -0.03 < 0 → buying improves; trade to 70%
selling: +0.12 + 0.09 = +0.21 > 0 → selling never pays
no-trade zone = BOTH derivatives >= 0 ⇔ |f0'(w_prev)| <= κ
That last line is a sentence you have seen before wearing a mask: Lesson 0012's subdifferential condition 0 ∈ f0'(w_prev) + κ[-1, 1] is exactly these two inequalities stapled together. The mysterious [-1, 1] is nothing but “the slope of the V is +κ on the buy side and -κ on the sell side, and at the kink you may quote anything in between.” The split turns the try-a-tiny-trade-in-each-direction argument into two ordinary derivatives.
And now the contrast that matters. Replace the V with a smooth quadratic penalty κ(w - w_prev)²: its marginal cost is 2κ(w - w_prev), which is zero at w_prev. The first unit is free, so any nonzero push triggers trading — the no-trade zone collapses to a single point. Kinked costs park you; smooth costs merely slow you down. The L1 kink charges the first unit; the quadratic charges the first unit nothing.
Memory hook: the V charges full fare from the very first unit — that fare is what a push must beat to move you. A parabola lets the first unit ride free, so there is nothing to beat and no zone.
Lab: Pad the Books and Watch the Fee Object
The chart shows the two construction lines (dashed), the V they hold up (solid), and the shaded region above the V where every legal (t, b + s) pair lives. The grey dot is the honest report |t|; the black dot is what the split actually reports. Three experiments: (1) with fee κ = 0.09, drag padding up — the black dot lifts off the V and the fee bill climbs while the net trade sits still: this is the money the no-padding lemma says a minimizer will never spend; (2) set κ = 0 — the bill stays zero at any padding: the optimal w* is still right, but b + s is now meaningless as a turnover report, a real gotcha when you log turnover from a problem with no cost term; (3) push κ negative — every point of padding now reduces the bill, the solver would pad without limit, and the problem is unbounded: the convexity license, revoked before your eyes.
Net trade-
Buys b-
Sells s-
Reported b + s-
Fee bill-
Verdict-
The solver only ever occupies the black dot's position when it is optimal to do so. Your job in this lab is to be a worse optimizer than the solver and see what it costs.
Second Lab: The 120-Day Race — Kinked Book vs Smooth Book
The kink economics, run live. Two portfolios chase the same moving target (the dashed line — think of a signal that drifts every day). The kinked book pays the L1 fee κ|trade|; the smooth book pays the quadratic κq·trade². Press Run and watch when each one trades, not just how much. Three experiments: (1) run with the defaults — the teal path moves in flat shelves (parked in its no-trade zone) and occasional jumps, while the rust path wiggles literally every day: the first-unit-free effect, animated; (2) crank κ up mid-race — the shelves get longer and the teal book starts ignoring whole swings of the signal; (3) drop κ to zero and both books become the target-chaser — the kink is the parking brake. Use Step one day to read the story line day by day: it prints the push-vs-fee comparison that decides each trade.
Day-
Kinked: days traded-
Kinked: turnover-
Smooth: days traded-
Smooth: turnover-
-
This race is also your first taste of the multiperiod arc: each day's w becomes the next day's w_prev, and the cost model you pick shapes the whole trajectory, not just one trade. Papers that report “L1 costs induce sparse, bursty trading; quadratic costs induce continuous partial rebalancing” are describing exactly the two paths on this chart.
Where You'll Meet It in the Wild
Multi-asset turnover
Total turnover is Σ_i |w_i - w_prev,i| — one V per asset. Split each: w_i = w_prev,i + b_i - s_i, turnover = Σ(b_i + s_i). Twice the variables, all constraints linear.
Leverage: the L1 ball
A 130/30 mandate is Σ|w_i| <= 1.6. Split each weight into long and short parts w_i = w⁺_i - w⁻_i — the “long book” and “short book” are the split, institutionalized.
Asymmetric costs
Buys and sells rarely cost the same (borrow fees, taxes, one-sided liquidity). The split prices them separately for free: κ_buy b + κ_sell s. The epigraph form can't do this — one reason desks prefer the split.
In CVXPY: already done for you
cp.abs(w - w_prev) and cp.norm(w - w_prev, 1) perform this rewrite internally — and refuse to appear with a negative sign. The famous DCP error is the no-padding lemma enforced at compile time.
Retrieval Practice
Fee κ = 0.05. The solver returns b = 0.10, s = 0. A colleague proposes b = 0.18, s = 0.08 — same net trade. Its objective value is?
When does the guarantee b + s = |t| at the optimum break?
Why does the L1 fee κ|t| create a no-trade zone while the quadratic κt² does not?
You must encode the constraint |w - w_prev| <= τ for a solver. How many auxiliary variables do you need?
Portfolio Reading
You can now decode a whole genre of paper boilerplate. “Introduce w = w⁺ - w⁻, w⁺, w⁻ >= 0” is the split; “the problem can be cast as an LP/QP” usually means every |·| and max got this treatment; a “slack variable u with u >= ±x” is the epigraph. Boyd et al.'s multi-period paper writes transaction costs as functions of the trade vector and leans on exactly these rewrites to keep everything convex. When you see turnover, leverage, or tracking-error-in-L1 in a paper's constraint list, you now know what the solver actually received.
Primary Source
Boyd & Vandenberghe §4.1.3 introduces the epigraph form and §6.1.1 works the ℓ1-norm case; the MOSEK Modeling Cookbook §2.2 covers the absolute-value rewrites in exactly the two dialects above, with the no-padding caveat. In code, compare CVXPY's DCP rules against this lesson: cp.abs is “convex, so minimize-only” — the lemma as a type system. New cheat sheet: absolute-value recipe card.
Questions?
Ask your agent anything unclear — good prompts: “show the epigraph and split versions of the same 3-asset turnover problem side by side,” “why does CVXPY reject maximize(cp.abs(x)) — connect it to the padding story,” or “how does max(t, 0) get the same treatment?” (hint: it's half a V).