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

Driving the model

The engine takes two arguments. The first is a set of assumption keys, fifteen of them, and it is the one everybody argues about in the room. The second is how many months to run, and it is the one nobody writes down. Marchmont's base case run to 24 months ends underwater and looks like a company that does not come back. The same base case, not one key changed, run to 48 months troughs at -£115,501 in month 19 and is back above zero in month 28. Both runs are correct. Today is about driving the thing on purpose: sweeping rather than guessing, reading the growth ladder off the engine instead of off a slide, and choosing the horizon deliberately because the horizon is picking the answer.

🟢 Hands on Analysts who run the scenarios Anyone who signs off a forecast 45 min
0-4 · Welcome 4-20 · Fifteen keys and a horizon 20-40 · The ladder and the window 40-60 · Build-along and Q&A
Part 0

What you are actually steering

Sessions 1 to 5 built the machine and proved it ties. From here it is a machine you drive. Driving it badly looks exactly like driving it well: both produce a tidy table of pounds. The difference is whether the number came out of a sweep or out of a guess, and whether anyone recorded the window it was measured through. Everything today runs against assets/fin-live.js, which is loaded on this page, so every figure can be reproduced in the console while you read.

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

★ What you walk out with today. The two arguments to run and why the second one is an assumption rather than a setting. A sweep you can write in six lines that tie checks every run before it reads a single figure. The growth ladder read off the engine across all five rows, including the two where the lowest point lands on the edge of the window and is therefore not a lowest point at all. And the habit of writing the horizon next to the answer, because a figure without its window cannot be checked by anybody else.

LiveA forecast is a statement about assumptions4 min▶

A single figure out of this model is not a prediction about Marchmont. It is a sentence of the form: if revenue grows 4% a month, if customers keep paying on 75 day terms, if the margin holds at 42%, and if you look for 24 months, then cash at month 12 is -£47,261. Strip the conditions off and the sentence becomes a claim about the future that the model never made and cannot support.

That is not a reason to distrust the output. It is a reason to keep the conditions attached to it. The practical form is short: quote the figure, name the assumptions you changed from base, and name the horizon. Three clauses, and the figure survives being questioned.

Real world

The same base case, unchanged, is a company that dies or a company that needs about £115,501 of financing, depending entirely on whether the run stopped at month 24 or month 48. The assumptions did not decide that. The window did.

Self-studyNothing here is advice about buying anything3 min▶

Marchmont is a modelled company and every figure on the page is a model output. Nothing on it is a recommendation to buy, sell or hold any security, to lend to anybody, or to take on debt. What a business is worth, and whether it is a good investment, uses different tools and belongs to a different subject.

The skill being built here is narrower and more durable: given a model, find out what it is sensitive to, and report the answer in a form somebody else can check.

Part 1 · covers the interface

Fifteen keys, and the argument nobody records

14 min live

The whole interface is one function call with two arguments. Learning the keys is learning what the model believes can vary, and noticing the second argument is learning what it believes about time.

Two arguments go in. Only one of them gets minuted. Argument one: the overrides { growth: 0.06, dso: 45 } Fifteen keys exist. Set the ones under discussion, the rest are filled from BASE. Argument two: the horizon MARCHMONT.run(over, 48) Also an assumption. It decides which months the answer is allowed to contain. The engine recomputes every row from scratch. No stored results anywhere. Change one key and all three statements move together, then the tie is checked again. Two arguments, two kinds of assumption, one of them invisible. The overrides end up on a slide. The horizon stays in the conversation, and six weeks later nobody can say which window the figure came from.
🔍 Click to zoom - the second argument has no default worth trusting, because every default is somebody's earlier guess about how far ahead the question reaches.
LiveSweep, do not guess4 min▶

The instinct when somebody asks what happens if collection slips is to reason about it. Reasoning gets the direction right and the magnitude wrong, every time, because the movement runs through receivables, then through the cash line, then through interest, then through tax. Running it costs one line and returns the magnitude.

A sweep is just a loop over one key with everything else held at base, and it has one rule: tie check inside the loop, before anything is read. A run that does not tie is a broken model, and a broken model plotted next to four good ones is worse than no chart at all.

consoleconst out = []; for (const growth of [0, 0.02, 0.04, 0.06, 0.08]) { const r = MARCHMONT.run({ growth: growth }, 48); if (!MARCHMONT.ties(r).ok) throw new Error("does not tie at growth " + growth); out.push({ growth: growth, cashOut: MARCHMONT.cashOutMonth(r), profit12: MARCHMONT.cumulative(r, "netIncome", 12) }); } console.table(out);
LiveWhich keys move the answer, and how you find out5 min▶

Three of the fifteen keys, dso, dio and dpo, only change when money moves. Nothing they do reaches the income statement. The rest change what the business earns as well as when it banks it. That split is structural, it is readable straight off the engine, and session 7 measures exactly what it is worth.

Sensitivity beyond that split is not something to reason about. It is something to run. The measured levers span cash at month 12 from £60,630 to £232,489 against a base of -£47,261, so the weakest measured intervention and the strongest are a long way apart on the same company in the same year.

A ranking of levers is partly a ranking of the step sizes you chose. Thirty days off collection and five points of margin are not the same size of change, so putting them in order by cash moved says as much about the two steps as about the two keys. If the ordering is going to be used to decide anything, sweep each key by a comparable step and say what the step was.

Self-studyWhat the canon set does and does not sweep4 min▶

The canon set behind every figure in the track is 50 runs: five growth rates, 0%, 2%, 4%, 6% and 8%, crossed with five collection settings, 30, 45, 60, 75 and 90 days, each run to both 24 and 48 months. The worst balance sheet error anywhere in those 50 runs is 2.24e-8, which is floating point noise.

So growth and collection are swept. Tax rate, interest rate, capex rate and depreciation life are not, and a claim about how much any of those matters would be a claim nobody has run. That is worth knowing about any model you are handed, not just this one: the swept dimensions are the ones whose behaviour is known, and everything else is untested territory that happens to be inside the same function.

console// the canon set, as a nested loop let worst = 0; for (const growth of [0, 0.02, 0.04, 0.06, 0.08]) { for (const dso of [30, 45, 60, 75, 90]) { for (const months of [24, 48]) { const t = MARCHMONT.ties(MARCHMONT.run({ growth: growth, dso: dso }, months)); if (!t.ok) throw new Error("does not tie: growth " + growth + ", dso " + dso); if (t.worst > worst) worst = t.worst; } } } worst; // 50 runs, and the answer is noise rather than accounting
Part 2 · covers the ladder, run live

Five growth rates, read off the engine

12 min live

Same margin, same customers, same terms, same cost base, five values of one key. Run each to 48 months and read four things: when cash goes below zero, how far it falls, whether it comes back, and what the twelve month profit was. Reading only the last of the four is how a growth plan gets signed.

Each bar is the stretch spent below zero, on a run out to 48 months. growth 12m profit m12 m24 m36 m48 0% £165,627 never below zero, lowest £260,062 in month 1 2% £296,285 never below zero, lowest £186,555 in month 12 4% £445,631 -£115,501 in month 19 under from m10 back above zero in month 28 6% £616,336 -£897,962 in month 31 under from m6 still under at m48 8% £811,427 -£7,885,358 at month 48 under from m5 lowest point is the last month in the window a 24 month run stops here Profit climbs every rung, from £165,627 to £811,427. So does the time spent underwater. Two of the five lowest points sit on the edge of the window, at month 1 and month 48, which means the model never found a bottom for them.
🔍 Click to zoom - the 8% bar is drawn in the darker fill because its lowest point is the final month of the run rather than a turning point the model actually reached.
Monthly growthCash goes negativeLowest cash pointBack above zeroTwelve month profit
0%never£260,062 (m1)never below it£165,627
2%never£186,555 (m12)never below it£296,285
4%month 10-£115,501 (m19)month 28£445,631
6%month 6-£897,962 (m31)not by month 48£616,336
8%month 5-£7,885,358 (m48)not by month 48£811,427
LiveReading all five rows, not the two ends4 min▶

Row by row, the engine says five different things. At 0% there is no cash question at all and £165,627 of profit. At 2% there is still no cash question, profit is £296,285, and the lowest point the cash line reaches is £186,555, which arrives in month 12 and is comfortably positive. Those two rows are the only ones where the answer is a single number.

At 4% the answer becomes an interval rather than a number: eighteen months below zero, a trough of -£115,501 in month 19, recovery in month 28. At 6% the interval outlasts the run, the trough is -£897,962 in month 31 and nothing has recovered by month 48. At 8% the shape stops being an interval at all: cash is still falling on the last month the model computes, where it stands at -£7,885,358, and profit is £811,427.

LiveA trough on the edge of the window is not a trough4 min▶

Two of the five lowest points land on a boundary month. At 0% the lowest cash is month 1, which is the first month the model computes. At 8% the lowest cash is month 48, which is the last. In both cases the minimum is an artefact of where the run was cut, and reporting either as "the trough" would be reporting the edge of the window as a property of the company.

The check is mechanical and worth wiring into any sweep: if the argmin is the first or last month, the model has not found a bottom and the honest output is "still falling at the end of the run", not a figure. Getting this wrong in the other direction is expensive, because -£7,885,358 quoted as a trough invites somebody to size a facility against it.

consolefunction trough(r) { let low = Infinity, at = 0; for (const row of r.rows) { if (row.cash < low) { low = row.cash; at = row.month; } } const last = r.rows[r.rows.length - 1].month; return { low: low, at: at, onEdge: (at === 1 || at === last) }; } trough(MARCHMONT.run({ growth: 0.04 }, 48)).onEdge; // false, month 19 is a real bottom trough(MARCHMONT.run({ growth: 0.08 }, 48)).onEdge; // true, so do not call it a trough
Self-studyRecovery, and the two different zeros4 min▶

cashOutMonth returns 0 when cash never goes below zero, which is unambiguous because the months are numbered from 1. A recovery reader written the same way is not unambiguous, and the difference matters.

consolefunction recovers(r) { const out = MARCHMONT.cashOutMonth(r); if (!out) return 0; // never went under for (const row of r.rows) { if (row.month > out && row.cash >= 0) return row.month; } return 0; // still under when the window ended }

Two very different situations come back as 0: a company that was never in trouble, and a company that was still in trouble when you stopped looking. On the base case at 24 months this function returns 0, and read carelessly that reads as "fine". On the same base case at 48 months it returns 28. A sentinel that means two things is a bug waiting for a deadline, and the fix is to return the reason rather than a number.

Part 3 · covers the horizon

The same run, through two windows

10 min live

Nothing below changes a single assumption key. The only difference between the two panels is the second argument to run, and the two panels support opposite decisions.

MARCHMONT.run({}, 24) and MARCHMONT.run({}, 48). No key changed between them. Window: 24 months Cash goes below zero in month 10. Lowest point in view: -£115,501, month 19. Still below zero when the run ends. Recovery reader returns 0. Reading: it does not come back. Decision this supports: stop growing, or sell. Window: 48 months Cash goes below zero in month 10. Lowest point: -£115,501, month 19. Back above zero in month 28. Eighteen months underwater, then out. Reading: a financing requirement. Decision this supports: borrow about £115,501. A figure with no horizon attached cannot be checked, argued with, or reproduced. The first pass of this bench stopped at 24 months and very nearly published the left hand panel. Session 9 takes that near miss apart in full.
🔍 Click to zoom - both panels are true readings of the same model. Only one of them is a description of the company.
LiveChoosing a horizon on purpose4 min▶

The horizon should be set by the question, not by the default in the function signature or by how far the spreadsheet happened to be dragged. Three tests do most of the work. Does the window contain the turning point, or does the line still have a direction at the end of it? Is it long enough to cover the thing being decided, which for a facility means the term of the facility? And would extending it by half again change the answer?

That last one is the cheapest check in modelling. Run it, run it again at 1.5 times the length, and compare the conclusion rather than the numbers. If the conclusion flips, the original window was the finding.

consoleconst short = MARCHMONT.run({}, 24); const long = MARCHMONT.run({}, 48); MARCHMONT.cashOutMonth(short); // 10 MARCHMONT.cashOutMonth(long); // 10, identical: the months before 24 cannot change recovers(short); // 0, meaning "not inside this window" recovers(long); // 28
LiveWhere the room splits: one number or a range5 min▶

Once you accept that the output is a statement about assumptions, there is a real argument about what to publish, and it does not have a settled answer.

One school says never publish a single point. A model like this has fifteen dials and a window, and a single figure launders all of that into something that looks measured. Publish the sweep: the range across the settings you consider plausible, with the setting that produced each end. On this view a point estimate is a false precision that survives into other people's slides with the conditions stripped off, which is exactly how the 24 month reading nearly got out.

The other school says a range is frequently a way of declining to commit. A board cannot approve a facility of between zero and eight million pounds, and a range wide enough to be honest is usually too wide to act on, so somebody quietly picks a point out of it anyway and now the choice has been made by whoever was last to touch the document. On this view the discipline is to commit to a point, state the assumptions and the horizon beside it in one sentence, and update it when the assumptions move.

Both are defensible and both are widely practised. What is not defensible is either one without the conditions attached, and the disagreement is worth having out loud with whoever is going to act on the figure.

Self-studyWhy the early months never move3 min▶

Extending the horizon cannot change anything that already happened in the model. The engine computes month by month forward from the opening sheet, so months 1 to 24 of a 48 month run are identical, value for value, to months 1 to 24 of a 24 month run. That is worth confirming once by hand, because it is the property that makes the horizon a pure reporting choice rather than a modelling one.

The consequence is slightly uncomfortable. Every figure the short window produced was correct. The failure was not arithmetic and no check would have caught it, because the tie passes on both runs. It was a conclusion drawn from a window that did not contain the event, which is a category of error that only reading and judgement catch.

Part 4 · build-along

Sweep it yourself

18 min hands on

Six steps in the console on this page. MARCHMONT is loaded, so everything below runs as soon as the console opens.

Reproduce the ladder, all five rows

Write the loop over the five growth rates out to 48 months, tie check inside it, and print growth, cash out month and twelve month profit. Check the profit column against £165,627, £296,285, £445,631, £616,336 and £811,427. If any row is off, an assumption other than growth has moved.

Add the trough and the edge flag

Extend the loop with the trough function so each row reports its lowest cash, the month it fell on, and whether that month was the first or last in the window. Confirm that exactly two of the five rows come back with the flag set.

Run the horizon test on one rung

Take the 4% row and run it at 24 months and at 48. Compare month 12, month 19 and month 24 across the two runs and confirm every value is identical. Then compare what you would write in a summary from each, which is the only thing that changed.

Give the recovery reader a mouth

Rewrite recovers so it returns a reason rather than a number: never went under, recovered in month n, or still under at the end of a window of n months. Run it across the ladder and read the five sentences it produces. That output is publishable and the numeric one was not.

Sweep one key by a comparable step

Pick three keys and move each by the same proportion of its base value, holding everything else at base, then compare cash at month 12 across the three. Write down the step you used, because the ordering you get is only meaningful next to it.

Extend by half again and check the conclusion

Take whichever run you would put in front of somebody and run it again at 1.5 times the horizon. Write one sentence from each. If the two sentences disagree, the horizon was doing the work and the finding is that, not the figure.

The habit to keep. Every figure leaves the console with three things attached: what was overridden, how many months, and that the run tie checked. Written down it is one line, and it is the difference between a figure somebody can challenge and a figure somebody has to take on trust.

Check yourself

Three questions

The base case run to 24 months ends below zero. The same base case run to 48 months recovers in month 28. Which assumption changed?

Months 1 to 24 are identical value for value across both runs, because the engine computes forward from the opening sheet. Only the window changed, and it changed the conclusion from "does not come back" to "needs about £115,501".

At 8% growth the lowest cash in a 48 month run is -£7,885,358, in month 48. How should that be reported?

A minimum on a boundary month is a property of where the run was cut, not of the company. The model has not broken, it has simply not been asked a long enough question, and quoting -£7,885,358 as a trough invites somebody to size something against a number that is not a bottom.

Someone ranks the levers by cash moved and concludes that collection terms matter more than margin. What is the first thing to ask?

Tie checking and the horizon both matter and both should be recorded, but neither is what breaks this particular claim. Thirty days off collection and five points of margin are not comparable steps, so the ordering they produce is not a ranking of sensitivity until the steps are made comparable and stated.

Before session 7

Homework

Thirty minutes, in the console on this page.

Predict the four working capital levers, then measure them

Before running anything, write down what you expect cash at month 12 to be with collection at 45 days, and what you expect twelve month profit to be. Then run both and keep your two predictions next to the two results. Session 7 measures all eight levers and the gap between prediction and measurement is the useful part.

Find a figure in your own work with no horizon on it

Locate one forecast figure that circulates in your organisation and try to establish what period it was computed over and what it assumed. Note how long that took. If it took more than a few minutes, that figure is currently uncheckable by anybody who receives it.

Covered today

What this session covers

✓Assumptions as the interfaceFifteen keys, any subset overridden, the rest from BASE.
✓Sweeping instead of reasoningA loop with the tie check inside it, before anything is read.
✓The growth ladder run live, all five rowsCash out month, trough, recovery and twelve month profit for each.
✓A minimum on a boundary monthMonth 1 at 0% and month 48 at 8%; neither is a trough.
✓The horizon as an assumption24 months and 48 months on identical keys, two opposite readings.
✓What a single figure is a statement aboutThe assumptions and the window, not the future.
◐The eight levers, measured one at a timeSession 7, including the four that move no profit.
◐Turning the trough into a funding askSession 8.
◐The 48 month reversal, in fullSession 9.
○Sensitivity of tax rate, interest rate, capex rate and depreciation lifeNot in the swept canon set, so not claimed anywhere.
○Whether any of this is a reason to buy or sell anythingNothing here is advice about any investment.
Take this with you

Cheat sheet

Two arguments. MARCHMONT.run(overrides, months). The second one is an assumption as well.
Tie inside the loop. Check every run before reading any figure from any of them.
The ladder. 0% and 2% never dip. 4% troughs at -£115,501 in m19 and recovers in m28. 6% troughs at -£897,962 in m31. 8% reaches -£7,885,358 by m48.
Profit climbs anyway. £165,627 at 0% up to £811,427 at 8%, on the same company.
Boundary minimum. If the argmin is the first or last month, say "still falling", not "trough".
Two different zeros. Never went under, and still under when you stopped looking. Return a reason instead.
The half again test. Rerun at 1.5 times the horizon and compare conclusions, not numbers.
Three clauses. The figure, what was overridden, how many months. Anything less is uncheckable.