Where you are. Lesson 13 put a day of FX dealing on tape - twenty dollar obligations among four dealer banks, 5,370 gross - and compressed it pair by pair: offsetting flows cancelled, six payments survived, 2,790 moved, and positions() stood guard to prove that no bank’s signed total changed. That was arithmetic on a list; no ledger moved. You also carry two older pieces: module 0’s clearing house, where the clerks totalled each bank against the whole room rather than against any one neighbour, and module 2’s batch rail, which runs exactly that totalling at a cutoff every domestic afternoon. This lesson joins them. Net each bank against everybody at once, settle the result on the miniledger through a hub, and prove, balance for balance, that the compressed day lands every account exactly where gross settlement would have.
The money that only passes through
Look again at the six payments lesson 13 left standing, and follow Cedar’s evening. Cedar owes Alder 10 and Birch 160, so at settlement it must find 170 and send it out the door. The same evening, Damson owes Cedar 1,510. Cedar ends the day up 1,340 - and still had to push 170 out of one door while 1,510 came in the other. Birch is busier still: it pays Alder 380 while collecting 490 from Cedar and Damson. Money is passing through these banks on its way to somewhere else, and pairwise netting cannot stop it, because the 160 Cedar owes Birch and the 1,510 Damson owes Cedar sit on different pair lines, and a pair line only cancels against itself. Add up everything that merely passes through - 380 across Birch, 170 across Cedar - and 550 of the surviving 2,790 is traffic nobody needs to see. The clerks in module 0’s tavern never had this problem, because they never totalled Alder against Birch; they totalled Alder against the day. To take the last offset, you need the whole table at once.
The idea in one paragraph
Multilateral netting nets each participant against the whole system instead of against each counterparty: one pass over the day’s tape, every obligation lowering its payer’s running total and raising its payee’s, and out falls one signed position per bank - who the counterparty was is deliberately forgotten, because against the system only the totals matter. The positions must sum to zero, since every obligation enters the sum twice with opposite signs, and that zero is what lets a hub stand in the middle: net debtors pay the hub their position, the hub pays net creditors theirs, and it starts and ends flat, creating and destroying nothing. Settle those positions on real ledgers - desk entries gross, one net reserves leg per bank - and every balance in the system lands exactly where twenty gross settlements would have put it, which the exercise asserts account by account. The traffic collapses to one payment per bank; the price, and the next lesson’s subject, is that every one of those payments now runs through the same central point.
Forget the counterparty
Lesson 13 already computed the multilateral answer without telling you. positions() - each bank’s signed figure, everything the day owes it minus everything it owes the day - was the invariant pairwise netting had to preserve: compress the traffic however you like, the positions must not move. Multilateral netting promotes the invariant into the settlement itself. Stop compressing the traffic and settle the positions directly: walk the tape once, lower each payer’s running total by the amount, raise each payee’s by the same, and keep no record of who faced whom. On this tape the four numbers come out Alder +790, Birch +110, Cedar +1,340, Damson -2,240, and they sum to zero.
Settling positions instead of pair differences is where the last 550 goes. Cash through the hub is the sum of the positive positions, 2,240, against bilateral’s 2,790 - and the difference is exactly the through-traffic the hook found, 380 across Birch plus 170 across Cedar. A pair line can never cancel a debt to Alder against a claim on Damson; the whole table can, and does.
Somebody stands in the middle
A position is a number against the system, but nobody can wire money to a system: somebody has to stand where the positions meet. That somebody is the hub. Every net debtor makes one payment to the hub; the hub makes one payment to every net creditor. The zero-sum is what makes the seat possible to hold. Today the hub collects 2,240 from Damson and pays out 790, 110 and 1,340 - which is 2,240 - so it starts flat and ends flat, a meeting point rather than a participant. The hub creates no money; it only reroutes the day.
You have met the seat twice without dwelling on it. Module 0’s clearing room was the hub for a day of cheques; module 2’s batch cutoff made the central bank the hub for a country’s afternoon, swinging every reserve line in one posting. Cross-border finance keeps the same chair: lesson 12’s CLS stands where the two legs of every covered FX trade meet, and module 5 will put clearing houses in the chair for trades that settle days after they are struck. The arithmetic in this lesson is what sits underneath all of them.
Wider than the screen; scroll it sideways.
Desks gross, reserves net
Settling the positions on the miniledger reuses a discipline you already trust. Module 2’s batch rail posted customers gross, reserves net; here the customers are the dealers’ own FX desks, and the law is identical. At the cutoff each bank makes one balanced posting: every desk leg of its day at face value - each paying desk down the full amount, each receiving desk up the full amount - plus a single reserves leg carrying the bank’s position. The central bank then posts once, swinging all four reserve lines by their nets, and the zero-sum is exactly what makes that one posting balance. assert_world() runs at the end, and the two tiers agree line by line.
The point of the discipline is what it protects: the payout. Every desk is paid in full, indistinguishable from the gross world - netting is invisible from above and everything from below. That is also what gives the exercise its teeth. It settles the same tape in two separate worlds, gross and hub, then asserts desk for desk and reserve for reserve that they ended identical. The hub changes the plumbing, not the payout, and the assert message says so in as many words.
Three modes, one tape
The bars are stylised - the shape, not this tape’s exact figures - and the shape is what generalises: gross tallest, bilateral roughly half, and the multilateral bar shortest of the three, because the hub squeezes hardest. Your tape’s own numbers land the same way: twenty payments and 5,370 moved gross, six and 2,790 bilateral, four and 2,240 through the hub - 2.4x end to end. If 2.4x sounds modest beside the summary’s order of magnitude, it is: it is what twenty obligations among four banks have room to cancel. Shrinkage grows with two-way traffic. Module 2’s day of 300 payments among six banks shrank 10.6x through its cutoff, and a real dealer day, where each pair trades in both directions hundreds of times, cancels harder still. The mechanism never changes; the tape stays small enough to read every line.
Check yourself
1. Bilateral netting left 2,790 moving; the positions need only 2,240. Point to the missing 550 on the tape.
It is the money that only passes through. Birch pays 380 to Alder while collecting 490; Cedar pays out 170 while collecting 1,510. Those 380 and 170 are flows a pair line cannot cancel, because the debt and the claim face different counterparties - and 380 plus 170 is the 550. Netting against the whole system cancels a debt to anyone against a claim on anyone else; netting one pair at a time cannot, by construction.
2. Why must the four positions sum to zero, and what has your code done if they do not?
Every obligation enters the sum twice, once lowering its payer and once raising its payee, so each amount cancels itself before you know a single number on the tape. Economically, a nonzero sum would be a hub that creates or destroys money by netting. In code it means one side of an obligation was applied with the wrong sign or not at all, and the assert catches it before a single entry posts.
3. The exercise posts desk legs at face value and nets only the reserves leg. What would netting the desk entries too actually change, and which assert would catch it?
It would change who ends the day with what - the one thing netting must never touch. A desk paid its net instead of its gross receipts holds a different balance than in the gross world, so the desk-for-desk comparison fails: the hub changes the plumbing, not the payout. Module 2’s batch rail obeyed the same law as customers gross, reserves net; the compression lives entirely in the reserve tier, invisible from above.
4. On this tape one bank, Damson, funds the hub’s entire 2,240. Compare the other three banks’ position at settlement with the gross world’s.
Under gross settlement, a Damson failure costs each counterparty only what Damson’s own pair lines carried: 400 to Alder, 330 to Birch, 1,510 to Cedar. Through the hub, Damson owes the hub and the hub owes everyone, so whether Alder collects its 790 now depends on how the hub handles a shortfall, not on whether Alder ever traded with Damson. Every exposure in the room points at one place. Pricing that concentration is the next lesson; surviving it is why module 5’s clearing houses carry default funds.
Do this
Twenty minutes, from module-03-across-borders. Open code/multilateral_netting.py. fixture() replays lesson 13’s tape byte for byte, same seed and same twenty obligations, and build_world() puts the four dealer banks and their desks on real ledgers, each desk opening with 20,000: ample, because this lesson compares roads, not queues. settle_gross() is written - the baseline pushes all twenty obligations through submit-and-settle at face value. Your work is multilateral_positions(), one loop in the TODO(you): start every bank at zero, walk the tape once, lower the payer by the amount and raise the payee by the same, and return the four signed numbers - who the counterparty was is deliberately forgotten. settle_nets() then spends your numbers: it asserts they sum to zero, posts each bank’s desk legs gross plus one reserves leg carrying its position, swings the central bank once, and re-asserts the world.
python3 code/multilateral_netting.py
The harness settles the same tape both ways in two separate worlds and compares them desk for desk and reserve for reserve. Green is exactly this scoreboard, ending with the final line verbatim:
bank position
Alder +790
Birch +110
Cedar +1340
Damson -2240
payments, gross 20
payments, via the hub 4
cash moved, gross 5370
cash moved, via the hub 2240
shrinkage 2.4x
the hub nets each bank against everybody: one payment per bank, a fraction of the cash, and the same balances as gross
If the zero-sum assert fires, an obligation entered your loop with one sign only - added to the payer, or applied to the payee alone. If the miniledger raises unbalanced posting instead, your positions sum to zero but are the wrong numbers: swap every sign and they still cancel, but the reserves leg no longer pays for the desk legs it sits beside, and the ledger refuses the posting before any comparison runs. The completed version is solutions/multilateral_netting.py; compare after you are green.
What you can now do. You can run the clearing house’s full trick end to end on real ledgers: one pass over the day, one signed position per bank, a zero-sum proved before anything moves, and the whole day settled as four payments through a hub that starts and ends flat - with balance-for-balance proof that the compression changed the traffic and nothing else. You can also say what the trick costs, because compression and concentration turned out to be the same operation: every claim in the room now points at one seat. Lesson 15 puts a price on that, cash moved against risk concentrated, one point per settlement mode, and neither end simply better. The module project then feeds a seeded day of FX trades through gross, bilateral and multilateral settlement and reports all three - the netting engine you just built is one of the pieces it assembles.