learn-finance-with-phoebe / Practitioner session 1 of 10
Learn Finance with Phoebe · Practitioner session 1 of 10

The three statements, wired together

A set of accounts looks like three documents because it is printed as three documents. It is one model. Net income leaves the income statement and lands in equity. The change in cash leaves the cash flow statement and lands in the cash line. Those two wires are the whole connection, and once they are in place the balance sheet has to tie, every month, to the penny. When it does not tie you have recorded one side of something and not the other, and the size of the failure is the size of what you missed. That makes the tie the only error check in finance that finds your mistake for you.

🟢 Hands on Analysts who build the model Anyone who has to trust one 45 min
0-4 · Welcome 4-26 · One model, two wires 26-54 · Build-along: run it, break it 54-60 · Q&A
Part 0

How the practitioner track works

Ten sessions, hands on the model the whole way. Today is the skeleton and the tie. Session 2 is the income statement, revenue down to net income. Session 3 is working capital and how DSO, DIO and DPO each become a cash movement. Session 4 builds the cash flow statement by the indirect method, line by line. Session 5 is the balance sheet and the tie in depth, including breaking it on purpose. Session 6 drives the model from its assumptions and runs the growth ladder live. Session 7 measures the eight levers and finds the four that move no profit at all. Session 8 sizes a funding requirement from the trough. Session 9 is the finding that reversed when the horizon went from 24 months to 48. Session 10 is the bench: build it, break it, watch the tie fail, size the gap. The company throughout is Marchmont Instruments, and the model is assets/fin-live.js, which is loaded on this page.

Live = run together in the session Self-study = the full depth, after class 🔍 Every diagram zooms

★ What you walk out with today. The two links that turn three reports into one model, written the way the engine writes them. The tie as a working error check, with the measured result behind it: across the full canon set of 50 model runs the worst assets minus liabilities and equity error is 2.24e-8. The ability to run the Marchmont model from a console and get the canon figures back. And the reason a model that does not tie gets reported as broken rather than plotted.

LiveOne model, printed as three pages5 min▶

Three statements exist because a business raises three questions. They are computed from one set of transactions, which means the three cannot be independent and the printing is the only thing that separates them. Build them separately and you get three plausible documents that quietly disagree. Build them as one model and the disagreement becomes impossible, because the arithmetic that produces the third number has already used the first two.

The link list is short. Net income, produced at the bottom of the income statement, increases equity on the balance sheet. The change in cash, produced at the bottom of the cash flow statement, moves the cash line on the balance sheet. That is it. Everything else in a three statement model is a consequence of those two.

Real world

In fin-live.js the two wires are two lines of code. equityNew = equity + netIncome and cashNew = cash + freeCF. Nothing else writes to equity, and nothing else writes to cash.

Self-studyThe engine, and how to call it5 min▶

The model exposes six things on a single global. Every figure quoted for Marchmont comes out of them, and there are no stored results anywhere.

APIMARCHMONT.BASE // the default assumptions, as a plain object MARCHMONT.run(overrides, months) // returns { rows: [...], assumptions: {...} } // overrides is an object of assumption keys; months defaults to 24 MARCHMONT.ties(result, tolerance) // returns { ok, worst }; tolerance defaults to 0.01 MARCHMONT.cashOutMonth(result) // the first month where cash is below zero, or 0 if it never is MARCHMONT.profitableThrough(result, m) // true if netIncome was above zero in every month up to m MARCHMONT.cumulative(result, field, upto) // adds one field across the rows, optionally stopping at a month

No credentials of any kind are involved. If an example ever needs one, the string is the word placeholder.

Part 1 · covers the wiring

Two wires, and the rest is consequence

12 min live

Draw the three statements once with the links on them and the structure of every three statement model you will ever meet is already on the page. The income statement and the cash flow statement both cover a period and both end in a single figure. The balance sheet is where both of those figures land.

Three documents, two wires, one model. Income statement Covers a period Revenue down to the tax line Ends in: netIncome Cash flow statement Covers a period netIncome, then the timing Ends in: freeCF Balance sheet, at a single moment equity rises by netIncome, every month cash moves by freeCF, the change in cash assets = cash + ar + inventory + netFixed liabsEquity = ap + debt + equity tieError = assets - liabsEquity netIncome freeCF Two wires, and that is the whole connection. Change one assumption and all three statements move together, or the model has a mistake in it and the tie will say so.
🔍 Click to zoom - nothing else writes to equity and nothing else writes to cash, which is what makes the tie a real test rather than a formality.
LiveNet income into equity4 min▶

Profit belongs to the owners. If it is not paid out it stays in the business, and the balance sheet records that as a larger equity balance. So each month's net income is added to equity and the income statement is finished with it. That single addition is the reason the income statement is part of the balance sheet rather than a report that sits beside it.

In the engine there is one line doing this, and it runs after tax has been taken off.

fin-live.jsvar netIncome = pretax - tax; // ... later in the same month var equityNew = equity + netIncome;

Nothing else in the file touches equity after month 0, which is worth knowing because it means any equity movement you cannot explain is a bug rather than a subtlety.

LiveThe change in cash into the cash line4 min▶

The cash flow statement starts from net income and then undoes the timing. Depreciation is added back because it reduced profit without moving money. The change in working capital is subtracted because a rising receivables balance is revenue that has been recorded and not collected. Capital spending is subtracted because it moved money without touching profit. What comes out at the bottom is the actual movement in the bank balance.

fin-live.jsvar operatingCF = netIncome + depreciation - dWC; var freeCF = operatingCF - capex; var cashNew = cash + freeCF;

Read those three lines in order and the whole indirect method is there. Session 4 builds each adjustment separately and explains why the sign is what it is.

Self-studyThe assumption keys, and how to override one5 min▶

MARCHMONT.run takes an object of overrides and fills everything else from MARCHMONT.BASE. The keys are the model, so learning them is learning what the model believes can vary.

assumptionsstartRevenue 400000 month 1 revenue, in pounds growth 0.04 month on month revenue growth grossMargin 0.42 opexFixed 95000 salaries, rent, the things that do not flex opexVariable 0.11 as a share of revenue dso 75 days sales outstanding dio 60 days inventory on hand dpo 30 days payable taxRate 0.25 interestRate 0.09 annual, on drawn debt openingCash 250000 openingDebt 300000 capexRate 0.03 capex as a share of revenue depreciationLife 60 months openingNetFixed opening net book value of fixed assets openingEquity present, but not read from: see below

One key changed and the whole model reruns, because every row is computed rather than stored.

consoleMARCHMONT.run({ dso: 45 }, 24); // collection at 45 days, everything else exactly as BASE MARCHMONT.run({ growth: 0.06, dso: 90 }, 48); // two keys changed, run out to 48 months
Self-studyOpening equity is solved, and people argue about that5 min▶

A model has to start somewhere. Marchmont existed before month 1, so it opens with receivables, inventory, payables, cash, debt and fixed assets already on the books. The engine does not ask for an opening equity figure. It works one out, as whatever value makes the opening sheet tie.

fin-live.jsvar equity = (cash + ar + inv + netFixed) - (ap + debt);

One school says this weakens the check. If equity at month 0 is defined as the number that makes month 0 balance, then month 0 cannot fail, and the tie is only testing the forward mechanics rather than the opening data. On that view a model should take every opening balance as an input and let the sheet fail loudly when the inputs are inconsistent, because inconsistent opening balances are one of the commonest real errors.

The other school says equity is not an input in the first place. It is the accumulated history of everything the business has ever earned and everything the owners have ever put in, and in a model of a company you did not found, that history is not independently knowable. Solving for it states plainly what it is, and puts the test where the errors actually get made, which is in the month by month mechanics. Both positions are defensible and the engine has taken the second one. Knowing which it took is the point, because it tells you what the tie does and does not prove.

Part 2 · covers the tie

The tie, and what a failure is telling you

10 min live

Every transaction has two sides. Record both and the sheet stays balanced. Record one and the balance sheet is out by exactly the amount of the side you missed, which is why the failure is not just a signal, it is a measurement.

One entry, two legs. Drop a leg and the sheet tells you how big it was. Both legs recorded An invoice is raised, value V. ar rises by V equity rises by V, through netIncome assets - liabsEquity = 0 Nothing to find, because nothing is missing. One leg recorded An invoice is raised, value V. ar rises by V equity does not move assets - liabsEquity = V The error is exactly the size of the missing leg. Across the full canon set of 50 model runs the worst error is 2.24e-8, against a tolerance of 0.01. That is floating point noise, not a modelling error. Anything worse makes ties() return ok false, and the model is reported as broken rather than plotted.
🔍 Click to zoom - the right hand panel is what a real tie failure looks like. The number it reports is the transaction you half recorded.
LiveRunning the check4 min▶

Every row the engine produces carries its own tieError, and ties walks them and returns the worst one it found along with a verdict. The tolerance is there because a computer adding decimal numbers does not land on exact zero, and it defaults to a penny.

consoleconst m = MARCHMONT.run({}, 48); const t = MARCHMONT.ties(m); t.ok; // true t.worst; // the largest absolute assets - liabsEquity over all 48 months if (!t.ok) { throw new Error("balance sheet does not tie: " + t.worst); }

The throw is the important line. A model that does not tie has a mistake in it, and every chart drawn from it is a picture of that mistake. Reporting it as broken is not pessimism, it is the only response that does not publish a wrong number.

LiveChecking it across a sweep, not one run4 min▶

A single passing run proves very little. Models fail on particular settings: a growth rate that changes a sign, a horizon long enough for a rounding difference to accumulate. The canon set is 50 runs, built from five growth rates, five collection settings and two horizons, and the worst error anywhere in it is 2.24e-8.

The growth ladder is the easiest slice to run yourself.

consoleconst growths = [0, 0.02, 0.04, 0.06, 0.08]; let worst = 0; for (const growth of growths) { for (const months of [24, 48]) { const r = MARCHMONT.run({ growth: growth }, months); const t = MARCHMONT.ties(r); if (!t.ok) throw new Error("does not tie at growth " + growth); if (t.worst > worst) worst = t.worst; } } worst; // ten of the fifty canon runs, all comfortably inside tolerance
Self-studyThe three readers, and what they answer4 min▶

Three small helpers sit on top of the rows. They exist because the same three questions get asked of every run, and answering them by eye off a table is where transcription errors come from.

consoleconst m = MARCHMONT.run({}, 48); MARCHMONT.cashOutMonth(m); // the first month cash is below zero; 0 means it never happens MARCHMONT.profitableThrough(m, 12); // true if netIncome was above zero in every month up to and including 12 MARCHMONT.cumulative(m, "netIncome", 12); MARCHMONT.cumulative(m, "freeCF", 12); // one field added across the rows, stopping at month 12

Those last two are the pair the leader track opens with: twelve month net income of £445,631 against free cash flow of minus £297,261, a gap of £742,892. They are computed here, not typed in, which is the only reason they can be trusted across sixteen pages.

Part 3 · build-along

Run the model, then break it

28 min hands on

Six steps in the browser console on this page. The engine is already loaded, so MARCHMONT is there as soon as the console opens.

Get a run and look at one row

Call MARCHMONT.run({}, 12) and inspect rows[0]. Find revenue, netIncome, deltaWC, freeCF, cash, assets, liabsEquity and tieError. Every later session works on one of those fields, so spend a minute on the names.

Confirm the tie before you look at anything else

Run MARCHMONT.ties(m) and read both fields it returns. Make this the first thing you do with any run, including runs you did not produce. A figure from a model that has not been tie checked is a figure of unknown provenance.

Find the two wires in the rows

Take any two consecutive rows. Check that the later equity minus the earlier equity equals the later netIncome, and that the later cash minus the earlier cash equals the later freeCF. Both should hold to the penny. If either does not, you have found something the tie was built to catch.

Break one wire on purpose

Take the rows into a copy and add a pound to assets on one row without touching anything else. Recompute the difference against liabsEquity and watch the tie error become exactly one pound. That is the whole mechanism: the failure reports the size of what is missing.

Change a collection assumption and re-tie

Run with { dso: 45 } and then with { dso: 90 }, and tie check both. The cash path changes completely and the tie does not move, because collecting sooner or later is a different arrangement of the same money rather than an extra pound from anywhere.

Reproduce two canon figures

Use cumulative to get twelve month net income and twelve month free cash flow, and check them against £445,631 and minus £297,261. If either is off, you have changed an assumption somewhere. Finding out which one is the exercise.

The habit to keep. Tie check first, then look at the answer. In that order it costs one line. In the other order you can spend an afternoon interpreting a shape that came out of a broken model, and the interpretation will be confident.

Check yourself

Three questions

What are the two links that turn three statements into one model?

In the engine the two links are one line each: equity plus netIncome, and cash plus freeCF. Nothing else writes to either balance, which is exactly what makes the tie a genuine test rather than a restatement.

A run comes back with a worst tie error of 2.24e-8. What does that mean?

Adding decimal numbers on a computer does not land on exact zero. Two hundredths of a millionth of a penny is the arithmetic, not the accounting. That is the worst figure across the full canon set of 50 model runs.

A model reports assets minus liabilities and equity of £8,400 in month 7. What has most likely happened?

The tie failure is not a loss and it is not a threshold problem. It is a measurement of an incomplete entry, and the amount is the amount you missed. Widening the tolerance to make it pass hides the one signal in the model that finds mistakes for you.

Before session 2

Homework

Thirty minutes, in the console on this page.

Sweep five collection settings

Run the model at five different dso values out to 48 months, tie check every run, and record the worst error you see. Note whether any setting makes it meaningfully worse than the others, and write down what you would conclude if one did.

Write the income statement out by hand

Take rows[0] and write revenue, cogs, grossProfit, opex, ebitda, depreciation, ebit, interest, tax and netIncome down a page in order, checking each arithmetic step. Session 2 goes through the same ten lines and assumes you have seen them.

Covered today

What this session covers

✓Three statements as one modelWhy building them separately produces three documents that disagree.
✓Net income into equityOne line in the engine, running after tax.
✓The change in cash into the cash lineoperatingCF to freeCF to the bank balance.
✓The tie as an error check2.24e-8 worst across the full canon set of 50 model runs.
✓What a real tie failure meansOne side of a transaction recorded and not the other, sized exactly.
✓Running the enginerun, ties, cashOutMonth, profitableThrough and cumulative.
◐Breaking the tie deliberately, in depthPractitioner session 5, and again on the bench in session 10.
○Valuation and what a business is worthA different subject with different tools.
Take this with you

Cheat sheet

Wire one. equity = equity + netIncome. The income statement lands on the balance sheet.
Wire two. cash = cash + freeCF. The cash flow statement lands on the balance sheet.
The identity. assets = cash + ar + inventory + netFixed. liabsEquity = ap + debt + equity.
The check. MARCHMONT.ties(result) returns ok and worst. Tolerance defaults to 0.01.
The measured result. 2.24e-8 worst across the full canon set of 50 model runs.
A failure is a measurement. The error equals the leg you did not record.
Broken beats plotted. A model that does not tie is reported, never charted.
Overrides. MARCHMONT.run({ dso: 45 }, 48). One key changed, the rest from BASE.