20 min

Bilateral netting, settle the difference

Two banks with a thousand trades between them do not move money a thousand times; they add up who owes whom and move only the difference, like compacting a log into its final state.

Where you are. Three lessons have made the exchange safe: Herstatt named the loss that comes from paying first, the versus family bound a trade’s two legs into both-or-neither, and CLS manufactures that promise at scale for its member banks and covered currencies. Safety is one axis; this lesson turns to the other, volume. A dealer pair’s day is dozens of obligations pointing both ways, and nothing about both-or-neither says the pile must be paid line by line. The answer has been in your hands since module 0 coined netting in the clearing-house room, and since module 2 ran it as the batch rail at a cutoff - but both of those totalled a whole room, everyone against everyone, over one shared central bank. Across a border there is no room and no shared central bank. So this lesson strips netting down to the smallest machine that still works: two banks, one agreement, one subtraction.

Forty trades, one payment

Two dealer banks, one in London and one in New York, trade dollars against sterling all day. The desks call each other forty times in a session: buy here, sell there, unwind, top up. By the close, the day’s dollar legs form a tape of forty obligations between the same two names, pointing both ways. Nobody pays forty times. A back office adds two columns - what we owe them, what they owe us - subtracts, and one bank sends one payment for the difference. Notice what is missing from the scene. No clearing house convened, no cutoff was decreed by anyone else, no third institution even knows the number. And every payment that never leaves skips the machinery this module’s first half priced: the correspondent hops, the fees, the pre-funded nostro. The whole engine is a contract and a subtraction, and it is the oldest and simplest netting there is.

The idea in one paragraph

Bilateral netting is module 0’s netting with the room deleted: two participants offsetting their mutual obligations directly, pair by pair. For each pair of banks, everything one owes the other cancels against everything owed back, and what survives is a single payment of the difference - or nothing, when the pair’s day cancels outright. Every obligation is discharged in full; each bank’s position, money in minus money out, is untouched; only the traffic shrinks, the way compacting a log preserves the final state while deleting the history. It is the simplest netting engine because it needs no shared infrastructure at all - no hub, no mutualised rulebook, just the two parties and an enforceable agreement - which is exactly why it works across borders, where there is no room to convene. The exercise runs it over a seeded tape of obligations among four dealer banks and watches the traffic collapse to one payment per pair, and a stricter truth waits at the end of the lesson: the offset is a legal construct before it is arithmetic.

One pair, one signed number

Fix one pair and walk the tape. Every obligation between them becomes one signed number from a fixed point of view: the exercise sorts the pair alphabetically and reads the day from the first bank’s seat, counting an amount positive when the first bank is the payer and negative when it is the payee. Sum the column. That signed sum is the pair’s whole day in one number - in plain language, it answers “after everything both ways has cancelled, who still owes whom, and how much”. Positive, and the first bank pays the second exactly that much, once. Negative, and the single payment runs the other way. Zero, and nothing moves at all, even though every obligation in the pile is discharged - met by offset instead of by payment.

The figure draws one stylised pair’s day: 740 owed in one direction, 520 owed back, 1,260 of gross traffic between two names - and the single payment of 220 that discharges every line of it.

image/svg+xml Matplotlib v3.11.1, https://matplotlib.org/ A owes B B owes A net 0 100 200 300 400 500 600 700 value moved between one pair gross, each direction one net payment
a pair's gross against the single net

Now notice the infrastructure bill: zero. Module 0’s room needed every bank in one city under one central bank; module 2’s batch needed a scheduled machine, a rulebook and an RTGS rail to settle over. A pair needs a counterparty, a running total and a signature. That is why pairwise offsetting is the netting that crossed borders first: it asks nothing of the world between the two banks. The nets that do survive still travel the expensive way - hop by correspondent hop, tolled by lesson 9’s cost stack - but every cancelled payment is a cost stack simply not paid.

One requirement hides in the word “sum”. Netting is addition, and addition needs one unit: every line on a netting tape must be denominated in the same currency. The tape below is all dollars for exactly this reason. The yen legs of the same trades form their own tape and net separately; binding a dollar leg to its yen leg so both settle or neither was the last three lessons’ work, not this one’s.

Half the saving, and where the rest hides

Run the arithmetic over four banks instead of two and measure. The exercise’s fixture is the module’s obligation tape: twenty seeded dollar obligations among Alder, Birch, Cedar and Damson, the dollar legs of one day of FX trades, small enough to check by hand and replayed verbatim by the next two lessons so every number stays comparable. Pairwise offsetting takes its twenty payments down to six - on this tape all six pairs survive with a nonzero difference - and its 5,370 of gross cash down to 2,790. Shrinkage: 1.9x.

Why only 1.9x, when module 0’s room shrank its sixteen obligations 10.4x and module 2’s batch shrank its seeded day of 300 payments 10.6x? Because a pair can cancel only what doubles back inside it. Traffic that flows around a cycle - Alder pays Birch, Birch pays Cedar, Cedar pays Alder - is one-way in every pair it touches, so pairwise offsetting cannot see it; summed against the whole room, it cancels to nothing. Module 2’s gridlock lesson met this blindness from the other side, when the deadlocked cycle yielded nothing to pairwise offsetting and everything to the multilateral view. So bilateral netting is unbeatable in a world of two - the summary’s thousand trades genuinely collapse to one payment - and increasingly leaky as the room grows, because more of the flow is cycle-shaped. Lesson 14 puts a hub in the middle of these same four banks, nets each one against its entire day, and finds the saving the pairs left on the table; lesson 15 then asks what that extra compression costs, because it is not free.

Do not let the comparison undersell the pair. Six payments instead of twenty is fourteen trips through the correspondent machine that never happen, fourteen of lesson 9’s cost stacks never paid; and 2,790 of cash now does the settling that 5,370 did before, which at lesson 5’s nostro-float scale is real money parked nowhere.

The agreement under the arithmetic

So far netting has been arithmetic, and arithmetic cannot fail. But look at when the number matters. On an ordinary day the net is a convenience: fewer payments, fewer tolls, less float parked in nostros. The day it was priced for is the day one side fails - the Herstatt day, when “what am I owed?” stops being bookkeeping and becomes a claim against whatever is left of a dead bank. On that day the arithmetic is not in charge. Insolvency law is.

Check yourself

1. The harness asserts positions(netted) == positions(tape). What does that invariant promise, and what does it deliberately not promise?

It promises netting changed only the traffic: every bank ends the day having gained or lost exactly what the gross tape says, whichever payments actually travelled. It does not promise any saving - the untouched gross tape itself passes it, trivially. That is why the harness carries a second assert, net_cash < gross_cash: same destination is one claim, cheaper road is another, and an honest netting engine proves both separately.

2. Module 0’s room found 10.4x and module 2’s batch found 10.6x, yet this tape shrinks only 1.9x. What do the rooms see that a pair cannot?

Cycles. A pair cancels only flow that doubles back inside it; flow around a cycle - Alder to Birch to Cedar to Alder - looks one-way to every pair it touches and survives pairwise netting untouched. Totalled against the whole room, the same cycle cancels to nothing, which is precisely the multilateral view module 2’s gridlock lesson needed when pairwise offsetting found no saving in the deadlocked queue. The gap between 1.9x and 10x is the cycle traffic, and collecting it is lesson 14’s job.

3. Over one day you owe a bank 520 and it owes you 740, so the net says it pays you 220. It fails before settlement. What is your exposure with an enforceable netting agreement, and without one?

With the agreement, the two piles collapse into a single claim: you are owed 220, you owe nothing, and the most the failure can cost you is 220. Without it, the liquidator cherry-picks: your 520 is demanded in full and must be paid now, while your 740 claim joins the unsecured queue for a partial recovery years away - so you are exposed to the full 740 and out of pocket 520 besides. The arithmetic is identical in both worlds. What changed is whether the law honours the subtraction, and that is the entire case for netting law.

4. Every line on the exercise’s tape is dollars. Why must a netting tape be single-currency, and where did the yen legs of those FX trades go?

Because netting is addition and addition needs one unit: dollars minus dollars is a number, dollars minus yen is an exchange rate, and the moment a rate enters you are trading, with lesson 7’s spread attached, not offsetting. The yen legs form their own single-currency tape and net separately on the other side of each trade. Nothing in either tape binds a dollar leg to its yen leg; making the two legs land both-or-neither was PvP and CLS, the previous three lessons. Netting compresses within a currency; the versus family protects across them.

5. A pair’s signed sum comes out exactly zero, so no payment survives - yet every one of that pair’s obligations still counts as discharged. Discharged by what?

By the offset itself. Under a netting agreement, each obligation is extinguished by setting it against obligations running the other way; payment is only what happens to the residue, and a zero residue leaves nothing to pay. That is also why the harness checks len(netted) <= 6 rather than == 6: a pair whose day cancels outright drops off the tape entirely, and its day is still fully settled. Discharge by arithmetic, sanctioned by contract - which is the quiet preview of why the contract has to hold.

Do this

Twenty minutes, from module-03-across-borders. Open code/bilateral_netting.py. The fixture seeds the module’s obligation tape - twenty dollar obligations among four dealer banks, deterministic on every machine - and positions is already written: each bank’s signed day, money in minus money out, the figure your netting must preserve. Your work is bilateral_net, one TODO(you): for each unordered pair, sum the tape’s obligations signed from a fixed perspective - sort the pair alphabetically, count an amount positive when the first bank pays and negative when it is paid - then turn each nonzero sum into one payment: positive means the first bank pays the second, negative means the reverse, zero means the pair vanishes. Return one (payer, payee, amount) per surviving pair, amounts always positive, pairs in sorted order.

python3 code/bilateral_netting.py

Green is exactly this scoreboard, ending with the final line verbatim:

payments before      20
payments after        6
cash moved before  5370
cash moved after   2790
shrinkage           1.9x
bilateral netting settles the difference: the traffic shrank to one payment per pair and the positions did not move

If the a-pair-settles-once assert fires, you kept (Alder, Birch) and (Birch, Alder) as separate keys: sort the two names before you sum. If the positive-amount assert fires, you returned the signed sum itself: a negative net is not a negative payment, it is a payment the other way, so flip payer and payee and negate. And if the positions assert fires, a sign flipped somewhere in your sum: run positions on your output and on the tape, and diff the two dictionaries bank by bank. The completed version is solutions/bilateral_netting.py; compare after you are green.

What you can now do. You can take a day of two-way obligations and collapse it to one net payment per pair - twenty payments to six, 5,370 of cash to 2,790 - and prove the collapse honest, because positions(netted) == positions(tape) says in one line that netting changed the traffic and never the positions. You can say exactly what the simplest engine reaches, flow that doubles back inside a pair, and what it cannot: flow around cycles, invisible pairwise, which is where most of the room’s 10.4x lived. And you can name the ground the subtraction stands on - an enforceable agreement, because a net that insolvency law will not honour is gross exposure wearing a smaller number. Lesson 14 puts a hub among these same four banks and nets each one against its entire day: module 0’s room, rebuilt for a world with borders, running on the tape already sitting on your bench.

What you can now do

You can collapse a day of two-way obligations into one net payment per pair and measure what shrank.