2026-10-17 · updated 2026-10-21 · money · manufacturing

A corrugator engineer values International Paper

I've stood next to the machine that makes this company's product for eleven years. I'd never valued the company. The stock is down twenty percent this year, so I did it properly — Wall Street Prep's mechanics, Damodaran's discipline, and the one thing neither of them has: what the numbers look like from the floor.

Updated October 21. I went back and pulled IP's FY2025 10-K for one reason: pensions and leases are where a paper company keeps the debt that "net debt" leaves out. The pensions and the leases came back clean. A footnote didn't: a $487M tax bill from a 2007 timberland sale, due in 2027. Neither did the reserves for old mill sites and asbestos claims, $373M more. All in, the 10-K takes $1.81 a share off the DCF ($40.25 → $38.44) and moves the down-cycle odds the market is pricing from 48% to 44%. Every number below has been rerun from the model; the line-by-line is here.
price$32Oct 15, 2026 · 52-wk $29–50
YTD−20%−23% over 12 months
yield5.8%$1.85/share · ~$980M/yr
net debt$8.5BEV ≈ $25.4B
debt-like, 10-K$957Mafter tax · −$1.81/share
2026 EBITDA guide$3.2–3.4Bwas $3.5–3.7B in January
2025 free cash flow−$159Mthe dividend wasn't covered
the story first

Damodaran's rule is that every valuation is a story with numbers attached, and the story comes first, because the numbers are only as good as the story they're consistent with. Here's the story as I see it from the plant, before I open a spreadsheet.

the bull case: capacity out, price in
  • IP closed Savannah (1.0 million tons a year) and Riceboro (430k) in September 2025, after Red River and Orange. The industry took out 3.9 million tons in 2025 — nearly 10% of North American containerboard capacity.
  • Three price increases in 2026. The index has recognized about $170 a ton on linerboard. The CFO's own sensitivity: every $10 a ton is $90M of EBITDA a year.
  • 80/20 found ~$510M of run-rate cost in 2025 and expects the same in 2026. Thirty-one EMEA closures, 2,800 positions.
  • They bought the NORPAC mill in Washington for $360M because, in the CEO's words, they were "significantly short paper on the West Coast and shipping it there uneconomically." Anyone who's watched a truck of linerboard cross three states knows what that costs.
the bear case: expensive assets, soft demand
  • Demand is flat. The trade forecast for 2026 containerboard went from +2% to −2% after February. The bank analysts are "mostly worried about demand."
  • EMEA (the DS Smith business) lost $80M in Q2 because paper prices rose faster than box prices. That lag is real; I've lived it from the other side.
  • The 10% capacity cut was cost economics — low performers out — not a demand squeeze. Those mills aren't coming back, which supports price, but the tightness is thinner than the headline.
  • Paper is capital-hungry. D&A is 8% of sales; capex is running $2.1B a year. The dividend costs $980M and 2025 free cash flow was negative. The 5.8% yield is the market telling you it doesn't believe the dividend is safe.

And one thing that isn't in either column because it happened this week: IP announced in January it would spin off EMEA, and on October 15 it said it had unsolicited bids and was running a sale process in parallel. The DS Smith capital markets day is November 3. So the question "what is the sum of the parts worth" is no longer academic.

narrative to numbers

A DCF is a machine that turns operating assumptions into a price. The machine is standard; the assumptions are everything. So here is 2027 built as a bridge from 2026 guidance, with each piece tied to the story, and with my own judgments marked as judgments.

The 2027 bridge for the North American business, $ millions. Grey is company guidance; orange are my judgments (the model's yellow cells); green is what falls out — and the deliberate step down to mid-cycle.

The judgments, stated plainly so you can disagree with them:

where the debt hides

Net debt is what IP owes its lenders less its cash: $8.5B at the end of June. A paper company owes more than that. It owes pensions to the people who ran its mills, health care it promised them, rent on its railcars and warehouses, the cleanup of mills it no longer runs, and taxes it put off. None of it is in net debt, and all of it gets paid before a shareholder sees a dollar. That's why I went back and pulled the FY2025 10-K. Line by line:

itemwhat the 10-K says (Dec 31, 2025)how it's countedper share
Pension plans in surplus$486M. The U.S. qualified plan holds $8,482M against $8,116M owed; the UK plan is $112M over.not counted: a surplus is hard to get back out (counting it adds +$0.70)$0.00
Pension plans in deficit$342M, including $218M of U.S. nonqualified plans with no assets behind themafter tax−$0.49
Retiree medical and postemployment$133Mafter tax−$0.19
Environmental reserves$270M, the "estimated probable liability" for old mill sitesafter tax−$0.39
Asbestos claims$103M, net of insuranceafter tax−$0.15
Brazil tax guarantee to Sylvamo$48M booked, of a potential $274Mthe booked part, in full−$0.09
Operating leases$707Mnot added: the rent is already inside EBITDA$0.00
Finance leases$71Malready inside the debt$0.00
Tax on the Temple-Inland timber notes$487M, "settled with the maturity of the notes in 2027"in full−$0.92
The timber notes, net of their loans$2,349M of notes against $2,127M of nonrecourse loanswhat's left over is IP's+$0.42
What the 10-K takes off the value$957M after tax−$1.81

After tax at the model's 24% rate, per share on 529.5M shares. Pension and lease balances are at December 31, 2025, the last time IP measured them; debt and cash are from June 30, 2026. Every line is an input in the workbook with its quote from the filing, and two of the twenty-one checks tie them back to the 10-K's own totals.

The pensions came back clean. The U.S. plan holds $8,482M against $8,116M owed, and the 10-K says IP's funded plans are "fully funded on a projected benefit obligation basis." I count the plans in deficit and give no credit for the plans in surplus. The leases came back clean in a different way: under U.S. GAAP the cost of an operating lease is an operating expense, so the rent is already inside the EBITDA the whole model runs on. Adding the $707M liability as debt too would count the same rent twice.

The reserves are what a hundred-year-old paper company carries for a long time: $270M for cleaning up mill sites it no longer runs (Cass Lake, the Kalamazoo River, San Jacinto, Versailles Pond) and $103M for asbestos claims after insurance. They're booked, the cash goes out over years, and adjusted EBITDA leaves them out, so they count: −$0.39 and −$0.15 a share.

The footnote is the part I didn't expect. In 2007 Temple-Inland sold its timberland for notes instead of cash and put off the tax; IP inherited both when it bought Temple-Inland in 2012. The 10-K says it plainly: "$487M deferred tax liability related to the Temple-Inland timber monetization, which will be settled with the maturity of the notes in 2027." That year the notes ($2,349M) pay off the loans taken against them ($2,127M) and leave $222M, and the tax takes $487M. Net, −$0.50 a share, due the same year $2.6B of IP's regular debt matures.

Two things I didn't count, so you can: about $344M of unrecognized tax benefits, which IP can't say when or whether it will pay (−$0.65 a share if all of it were), and the rest of a Brazilian tax assessment IP agreed to share with Sylvamo, the printing-papers business it spun off. IP puts its potential share at about $274M, has booked $48M of it (counted above), and a first-round court ruling went its way; the other $226M would be −$0.43 a share.

All in, the 10-K takes $1.81 a share off the DCF, $40.25 to $38.44, and moves the down-cycle odds the market is pricing from 48% to 44%. My edge narrows from 18 points to 14 points. What the bet is on doesn't change: the discount isn't hidden debt. It's the cycle.
what the machine says

Three independent routes, same inputs, and they land in the same place, which is the first thing worth knowing.

DCF, mid-cycle$38WACC 7.4% · TV is 82% of EV
sum of the parts$40NA at 9× · DS Smith at 6.5×
bear / mid / bull$11 / $39 / $69weighted 30/45/25: $38

Two honesty checks the model prints on purpose. The terminal value is 82% of the enterprise value — normal for a mature company, and it means the answer is the terminal assumptions, which is why the next section exists. And the Gordon-growth terminal value implies an exit multiple of 8.2×, right between Smurfit Westrock and Packaging Corp, so the growth-and-discount-rate pair isn't quietly telling a story the comps don't.

The two assumptions that decide the number, with the market price for scale:

NA mid-cycle margin ↓ · exit multiple →6.5×7.5×8.5×9.5×11×12×
13%$22$25$27$29$33$35
15% (Smurfit Westrock)$28$31$34$36$40$43
17% (this model)$34$37$40$43$47$50
19%$40$44$47$50$54$57
21% (Packaging Corp)$47$50$53$57$62$65

Grey cells are at or below the $32 market price. Read it as a map of what you'd have to believe.

At $32, the market is paying a Smurfit Westrock multiple for the North American business and giving DS Smith almost nothing.

That's the thing the model made me see. Back out what the price implies and the stock trades at 7.7× 2026 guidance and about 6× the 2027 bridge. The market isn't disagreeing with the arithmetic; it's disagreeing with the story. It assumes 80/20 doesn't earn IP a multiple above SW's, that most of the $170 doesn't stick, and that the dividend gets cut. Those are three versions of one belief: that the cycle turns down before the transformation shows up.

the bet, priced

A point estimate of $38 is not a bet; it's a sentence. A bet needs a distribution. So I ran the same chain twenty thousand times with the decisive assumptions drawn from ranges instead of set to values — how much price sticks, the mid-cycle margin, the exit multiple, the discount rate, volume, capex intensity — plus a 15% noise term on the terminal value for the possibility that the model's structure is simply wrong.

The first version said 90% of outcomes were above $32. That was a lie, and a common one: I'd drawn the assumptions independently, and in a downturn they move together — price doesn't stick, demand falls, margins compress and the multiple shrinks all at once. So the second version draws the cycle first, with a 30% chance of a down-cycle over the horizon, and draws everything else conditional on it.

Twenty thousand values per share. The dashed line is the $32 price; the tails are what the point estimate hides.
P(worth more than $32)62%given a down-cycle: 3% · given not: 87%
median / mean$37 / $3610th pct $16 · 90th pct $54
P(worth less than $16)11%P(worth more than $64): 2%

Then the question that matters: what does the probability of a down-cycle have to be for the bet to be fair?

The whole bet is one number. P(value > $32) as the probability of a containerboard down-cycle over the next three years goes from 10% to 70%.
At $32 the market is pricing a coin flip on a down-cycle. If you think it's 30%, the shares are worth about $37 and you have an edge. If you think it's 44%, you're being paid nothing.

Everything else — the DCF, the comps, the sum of the parts — collapses into that one judgment. Which is the useful thing about doing this properly: it tells you what you're actually betting on, and it isn't the dividend or the spin-off or the price increase. It's the cycle. And the cycle is the thing you can watch: box shipments, the index in October and November, OCC, and what the DS Smith buyers are willing to pay on November 3.

price your own bet

Everything above is my guess. Here is the same machine with the guesses exposed. Set what you believe — how much of the $170 sticks, the mid-cycle margin, what a buyer pays in 2030, the discount rate, and the odds of a down-cycle — and the page prices the bet the way the spreadsheet and the Monte Carlo do, live.

your value / share—
vs $32—
P(value > $32)—
10th / 90th pct—
2027 EBITDA—

On sizing: Kelly on this distribution says a half-Kelly position of about 20% of a portfolio, which is absurd, and the absurdity is the lesson. Kelly assumes you know the distribution. The distribution is the thing you're least sure of — I assigned that 30% myself. Treat Kelly as an upper bound, then divide by the number of things you could be wrong about. For one stock in one cyclical industry, that's a small position or none.

would I

Here's where I've landed, as a person and not as advice. The honest version of this stock is a bet on the containerboard cycle, with a 5.8% yield that may or may not survive, a separation that may or may not unlock value, and a management that has been doing the hard, expensive, unpopular work of closing the mills everyone knew should close. If I want the quality, I'd own Packaging Corp at 12× and sleep. If I want the same multiple with less drama, Smurfit Westrock is right there at 7×. IP is the one where my information is worth something — because the cycle call is the whole bet, and the cycle is what I read every week in box plants.

That's an argument for a small position and a watch list, not for conviction. The model is the thing I'm confident in. The 30% is the thing I'm not.

What to watch, in order of how fast it would change my mind (kept current in the thesis log): Fibre Box Association shipments for Q4 (demand); the Fastmarkets index in October and November (does the third round hold); the Q3 call's price realization number against the $175M that was in guidance; the November 3 DS Smith day and any sale price (the SOTP); OCC above $120 (the cost side). Each one is a row in the model.
what drives it

Before sizing anything, which assumption actually moves the answer? Rank correlation between each simulated input and the value per share, across the twenty thousand runs. The cycle dominates: the mid-cycle margin and the exit multiple top the list, and both are drawn from the cycle regime, which ranks third on its own. The pricing guess matters, but less than the cycle it sits inside.

Spearman rank correlation of each input with value per share. Negative means a higher input lowers the value.
my bet, as instruments

The model values the business; a position pays on the price, and the two can disagree for a long time. So this adds one more assumption — over about a year the price closes 60% of the gap toward the simulated value, with 12% of market noise on top — and maps that onto the four ways I could actually express the view, per 100 shares. Option premiums are placeholders from a reasonable chain; check the live one before doing anything.

Payoff at the horizon for each instrument (lines) over the distribution of the horizon price (bars). The dashed line is today's $32.
instrument (per 100 shares)capitalE[P&L]P(profit)10th90thworston capital
100 shares at $32$3,200$41465%(932)$1,705(2,109)+13%
1 call, Jan-2028 $35 (~$3.50)$350$2634%(350)$870(350)+7%
1 cash-secured put, $28 6-mo (~$1.75)$2,800$3277%(453)$175(1,627)+1%
1 call spread 35/45 (~$2.25)$225$7139%(225)$775(225)+32%

The table is the "win rate is a proxy" lesson on my own bet. The cash-secured put wins 77% of the time and has the lowest expected return on capital, because the 23% is where it loses $1,600 against $175 of premium. The call spread has the best return on capital and loses money 61% of the time; in 52% of runs it loses the whole $225. Shares are the boring middle: two-thirds odds, +13% expected, and they pay the dividend while I wait, if it survives. None of this is advice; it's the distribution, priced four ways.

show the code (bet.py, as run)
"""My bet: the value distribution mapped onto the instruments I could actually buy.

The Monte Carlo gives a distribution of what the shares are WORTH. A position pays on
what they TRADE at, and the market can disagree with value for a long time. So the one
extra assumption here is convergence: over the horizon the price moves a fraction c of
the way from $32 toward the simulated value (c = 1 is full convergence, 0 is none).

Option premiums are ILLUSTRATIVE placeholders — check the live chain before trading.
Run: python3 bet.py  (writes bet.json for the post's chart)
"""
import io, contextlib, json
import numpy as np

# re-run the simulation to get the value draws (same seed as montecarlo.py)
src = open("montecarlo.py").read().replace('json.dump(out, open("mc.json", "w"), indent=1)', '')
ns = {}
with contextlib.redirect_stdout(io.StringIO()):
    exec(src, ns)
value, down, price = ns["value"], ns["down"], ns["price"]
rng = np.random.default_rng(7)

# ---- horizon price: partial convergence toward value, plus noise the model can't see
c = 0.6                                              # 60% of the gap closes over ~12–15 months
price_T = price + c * (value - price)
MKT_NOISE = 0.12                                     # lognormal market noise the model can't see
price_T = price_T * np.exp(rng.normal(0, MKT_NOISE, len(value)))
price_T = np.maximum(price_T, 3)

# ---- dividend over the horizon: $1.85 held in the base regime, halved in a down-cycle (cut)
div = np.where(down, 0.925, 1.85)

# ---- instruments (per 100 shares / one contract). Premiums are placeholders.
CALL_K, CALL_PREM = 35.0, 3.50          # Jan-2028 $35 call, illustrative
PUT_K, PUT_PREM = 28.0, 1.75            # 6-month $28 cash-secured put, illustrative
SPR_K1, SPR_K2, SPR_NET = 35.0, 45.0, 2.25   # 35/45 call spread, illustrative net debit

instruments = {
    "100 shares at $32": dict(pnl=100 * (price_T - price) + 100 * div, capital=100 * price),
    "1 call, Jan-2028 $35 (~$3.50)": dict(pnl=100 * (np.maximum(0, price_T - CALL_K) - CALL_PREM), capital=100 * CALL_PREM),
    "1 cash-secured put, $28 6-mo (~$1.75)": dict(pnl=100 * (PUT_PREM - np.maximum(0, PUT_K - price_T)), capital=100 * PUT_K),
    "1 call spread 35/45 (~$2.25)": dict(pnl=100 * (np.minimum(SPR_K2 - SPR_K1, np.maximum(0, price_T - SPR_K1)) - SPR_NET), capital=100 * SPR_NET),
}
rows = []
for name, d in instruments.items():
    p = d["pnl"]
    rows.append(dict(name=name, capital=float(d["capital"]), mean=float(p.mean()), p_profit=float((p > 0).mean()),
                     p10=float(np.percentile(p, 10)), p50=float(np.percentile(p, 50)), p90=float(np.percentile(p, 90)),
                     worst=float(p.min()), roc=float(p.mean() / d["capital"]),
                     mean_down=float(p[down].mean()), mean_base=float(p[~down].mean())))
    print(f"{name:40s} capital ${d['capital']:>6,.0f}  E[P&L] ${p.mean():>7,.0f}  P(profit) {(p>0).mean():5.1%}  10th ${np.percentile(p,10):>7,.0f}  90th ${np.percentile(p,90):>7,.0f}  worst ${p.min():>7,.0f}  on capital {p.mean()/d['capital']:+.1%}")

# payoff curves for the chart (per 100 shares, before dividends)
grid = np.arange(10, 70.5, 0.5)
curves = {
    "shares": (100 * (grid - price)).tolist(),
    "call": (100 * (np.maximum(0, grid - CALL_K) - CALL_PREM)).tolist(),
    "put": (100 * (PUT_PREM - np.maximum(0, PUT_K - grid))).tolist(),
    "spread": (100 * (np.minimum(SPR_K2 - SPR_K1, np.maximum(0, grid - SPR_K1)) - SPR_NET)).tolist(),
}
hist, edges = np.histogram(price_T, bins=np.arange(0, 100, 2.5))
json.dump(dict(convergence=c, noise=MKT_NOISE, spread_wipeout=float((price_T <= SPR_K1).mean()), price=price, grid=grid.tolist(), curves=curves, rows=rows,
               priceT=dict(edges=edges.tolist(), counts=hist.tolist(), p10=float(np.percentile(price_T, 10)), p50=float(np.percentile(price_T, 50)), p90=float(np.percentile(price_T, 90))),
               premiums=dict(call=CALL_PREM, put=PUT_PREM, spread=SPR_NET)), open("bet.json", "w"))
print(f"\nhorizon price (c={c}): 10th ${np.percentile(price_T,10):.0f}  median ${np.percentile(price_T,50):.0f}  90th ${np.percentile(price_T,90):.0f}")
does it tie out

A model that evaluates is not a model that's right. So the numbers are checked the way a reviewer would check them — every identity, every cross-check between methods, every claim against what the company itself said — and the checks run as a script that fails loudly. Twenty-one checks; all pass. The ones that matter most: the 2026 cash flow lands inside the company's own guide, the Gordon terminal value implies an exit multiple the comps support, the Python reproduces the spreadsheet to the dollar, the Monte Carlo's breakeven is where the arithmetic says it should be, and the 10-K's pension lines tie to the filing's own totals.

validation output
PASS  market cap = price × shares: $16,944M vs $16,944M
PASS  net debt = gross debt − cash: $8,491M
PASS  10-K pension lines tie out: surplus plans − deficit plans = the filing's net, and the U.S. plan's table = its stated surplus: $486M − $342M = $144M (10-K: $144M); U.S. qualified $8,482M − $8,116M = $366M
PASS  debt-like items rebuild from the 10-K lines and come off the equity, not the WACC: $957M after tax = $957M from the lines; equity = EV − $8,491M − $957M
PASS  2026E EBITDA = guidance midpoint: $3,300M vs $3,300M guide midpoint
PASS  2026E levered FCF inside the company's Jan guide ($300–500M): $303M (guide given before the April cut; the low end is the honest reading)
PASS  dividend cost ≈ $980M: $980M = $1.85 × 529.5M
PASS  2026E levered FCF does NOT cover the dividend: 0.31× coverage
PASS  Gordon TV implies an exit multiple between SW and PKG: 8.2x (SW 7.2x, PKG 12.2x)
PASS  Exit-multiple TV implies a sane perpetual growth (0–4%): 1.89%
PASS  WACC > terminal growth: 7.36% > 2.0%
PASS  terminal value share of EV disclosed and in the mature-company band (60–90%): 82%
PASS  DCF sheet ≈ Scenarios mid-cycle column (same chain, two layouts): $38.44 vs $38.69
PASS  Sensitivity grid (17% × 8.5x) ≈ DCF within the interpolation error: $40.11 vs $38.44
PASS  Bear < Mid < Bull: $11 < $39 < $69
PASS  25% of $170/ton moves 2027E EBITDA by 42.5 × $9M: $382.5M vs $382.5M (CFO: $10/ton ≈ $90M)
PASS  Python chain reproduces Scenarios!C21 (2027E EBITDA): $4,136M vs $4,136M
PASS  P(value > price) falls monotonically as P(down-cycle) rises: 79% → 71% → 62% → 53% → 45% → 37% → 28%
PASS  breakeven P(down-cycle) ≈ 50% (market price = expected value): mean at p=0.5: $30.9
PASS  MC mean sits between the Bear and Bull point estimates: $35.9
PASS  regime split is real: P(>price | down) ≪ P(>price | base): 3% vs 87%

0 failure(s)
show the checks (validate.py)
"""Validation: does the model tie out?  Run: python3 validate.py

Each check is a thing a reviewer would ask. A check prints PASS/FAIL with the numbers,
and the script exits non-zero if anything fails, so it can gate a republish.
"""
import json, sys, subprocess
from openpyxl import load_workbook

wb = load_workbook("IP_valuation_model.xlsx", data_only=True)
inp = {r[0].value: r[1].value for r in wb["Inputs"].iter_rows(min_row=5) if r[0].value and r[1].value is not None}
M, D, S, V = wb["Model"], wb["DCF"], wb["Scenarios"], wb["Sensitivity"]
DCF = {}  # DCF sheet by label, so a new row can't silently shift what a check reads
for row in D.iter_rows(min_row=1):
    if isinstance(row[0].value, str) and row[1].value is not None:
        DCF.setdefault(row[0].value.strip(), row[1].value)
mc = json.load(open("mc.json")); pdown = json.load(open("mc_pdown.json"))
fails = 0
def check(name, ok, detail):
    global fails
    print(f"{'PASS' if ok else 'FAIL'}  {name}: {detail}")
    fails += 0 if ok else 1

# --- identities and tie-outs to what the company said
price, shares = inp["Share price ($)"], inp["Diluted shares (millions)"]
mcap, netdebt, debtlike = DCF["Market capitalization"], DCF["Net debt (gross debt − cash)"], DCF["Debt-like items from the 10-K, after tax (Inputs)"]
check("market cap = price × shares", abs(mcap - price * shares) < 1, f"${mcap:,.0f}M vs ${price*shares:,.0f}M")
check("net debt = gross debt − cash", abs(netdebt - (inp["Gross debt"] - inp["Cash and temporary investments"])) < 1, f"${netdebt:,.0f}M")
L = lambda prefix: next(v for k, v in inp.items() if k.startswith(prefix))
surplus, deficit = L("Pension plans in surplus"), L("Pension plans in deficit")
check("10-K pension lines tie out: surplus plans − deficit plans = the filing's net, and the U.S. plan's table = its stated surplus",
      abs((surplus - deficit) - L("Pensions, all plans")) < 1 and abs(L("Pension, U.S. qualified plan: assets") - L("Pension, U.S. qualified plan: obligation") - 366) < 1,
      f"${surplus:,.0f}M − ${deficit:,.0f}M = ${surplus - deficit:,.0f}M (10-K: ${L('Pensions, all plans'):,.0f}M); U.S. qualified ${L('Pension, U.S. qualified plan: assets'):,.0f}M − ${L('Pension, U.S. qualified plan: obligation'):,.0f}M = $366M")
dl = ((deficit + L("Retiree medical") + L("Environmental remediation") + L("Asbestos claims")) * (1 - inp["Cash tax rate"])
      - surplus * (1 - inp["Cash tax rate"]) * L("Count the pension surplus") + L("Brazil tax guarantee")
      + L("Deferred tax on the Temple-Inland") - (L("Timber notes held") - L("Nonrecourse loans")))
check("debt-like items rebuild from the 10-K lines and come off the equity, not the WACC", abs(dl - debtlike) < 1 and abs(
      DCF["Equity value = EV − net debt − debt-like"] - (DCF["Enterprise value"] - netdebt - debtlike)) < 1,
      f"${debtlike:,.0f}M after tax = ${dl:,.0f}M from the lines; equity = EV − ${netdebt:,.0f}M − ${debtlike:,.0f}M")
e26 = M["C14"].value
check("2026E EBITDA = guidance midpoint", abs(e26 - (inp["2026 adj. EBITDA guidance — low"] + inp["2026 adj. EBITDA guidance — high"]) / 2) < 1, f"${e26:,.0f}M vs $3,300M guide midpoint")
lfcf26 = M["C30"].value
check("2026E levered FCF inside the company's Jan guide ($300–500M)", 250 <= lfcf26 <= 550, f"${lfcf26:,.0f}M (guide given before the April cut; the low end is the honest reading)")
div = -M["C31"].value
check("dividend cost ≈ $980M", abs(div - 980) < 20, f"${div:,.0f}M = $1.85 × 529.5M")
check("2026E levered FCF does NOT cover the dividend", lfcf26 < div, f"{lfcf26/div:.2f}× coverage")

# --- terminal value cross-checks (each method implies the other's parameter)
tv_mult = DCF["implied exit multiple of the Gordon TV"]; tv_g = DCF["implied perpetual growth of the exit-multiple TV"]
check("Gordon TV implies an exit multiple between SW and PKG", inp["Smurfit Westrock EV/EBITDA (LTM)"] <= tv_mult <= inp["PKG EV/EBITDA (LTM)"], f"{tv_mult:.1f}x (SW {inp['Smurfit Westrock EV/EBITDA (LTM)']}x, PKG {inp['PKG EV/EBITDA (LTM)']}x)")
check("Exit-multiple TV implies a sane perpetual growth (0–4%)", 0 <= tv_g <= 0.04, f"{tv_g*100:.2f}%")
check("WACC > terminal growth", DCF["WACC"] > inp["Terminal growth rate"], f"{DCF['WACC']*100:.2f}% > {inp['Terminal growth rate']*100:.1f}%")
tv_share = DCF["terminal value as % of EV"]
check("terminal value share of EV disclosed and in the mature-company band (60–90%)", 0.6 <= tv_share <= 0.9, f"{tv_share*100:.0f}%")

# --- three routes agree
dcf_ps, scen_mid_ps, sens_cell = DCF["Value per share ($)"], S["C30"].value, V["D8"].value  # sensitivity row 17% × col 8.5x
check("DCF sheet ≈ Scenarios mid-cycle column (same chain, two layouts)", abs(dcf_ps - scen_mid_ps) < 1.0, f"${dcf_ps:.2f} vs ${scen_mid_ps:.2f}")
check("Sensitivity grid (17% × 8.5x) ≈ DCF within the interpolation error", abs(sens_cell - dcf_ps) < 3.0, f"${sens_cell:.2f} vs ${dcf_ps:.2f}")
check("Bear < Mid < Bull", S["B30"].value < S["C30"].value < S["D30"].value, f"${S['B30'].value:.0f} < ${S['C30'].value:.0f} < ${S['D30'].value:.0f}")

# --- the CFO's sensitivity is reproduced
e0, e25 = V["C16"].value, V["C17"].value  # 2027E EBITDA at 0% and 25% stick
check("25% of $170/ton moves 2027E EBITDA by 42.5 × $9M", abs((e25 - e0) - 42.5 * 9) < 1, f"${e25-e0:,.1f}M vs ${42.5*9:,.1f}M (CFO: $10/ton ≈ $90M)")

# --- Python reproduces the spreadsheet chain (the Monte Carlo's point estimate equals the mid column)
import importlib.util, io, contextlib
src = open("montecarlo.py").read().replace('json.dump(out, open("mc.json", "w"), indent=1)', '')
ns = {}
with contextlib.redirect_stdout(io.StringIO()):
    exec(src, ns)
import numpy as np
# deterministic run of the same chain at the mid inputs
a = dict(stick=0.5, margin=0.17, mult=8.0, wacc=DCF["WACC"], vol=0.01, emea_m=0.11, emea_d=150.0, capex=0.065)
pd_ = a["stick"] * 170 * 9 - 175
rev_na_27 = ns["rev_na_26"] * (1 + a["vol"]) + pd_; rev_emea_27 = ns["rev_emea_26"] * 1.025
na27 = 2425 + pd_ + ns["rev_na_26"] * a["vol"] * 0.30 + (300 - 250) * ns["rev_na_26"] / ns["rev_26"]
emea27 = 950 + a["emea_d"] + (300 - 250) * ns["rev_emea_26"] / ns["rev_26"]
check("Python chain reproduces Scenarios!C21 (2027E EBITDA)", abs((na27 + emea27 - 75) - S["C21"].value) < 1, f"${na27+emea27-75:,.0f}M vs ${S['C21'].value:,.0f}M")

# --- Monte Carlo sanity
check("P(value > price) falls monotonically as P(down-cycle) rises", all(pdown[i]["p_above"] > pdown[i+1]["p_above"] for i in range(len(pdown)-1)), " → ".join(f"{r['p_above']:.0%}" for r in pdown))
check("breakeven P(down-cycle) ≈ 50% (market price = expected value)", any(abs(r["mean"] - 32) < 1.5 and abs(r["p_down"] - 0.5) < 1e-9 for r in pdown), f"mean at p=0.5: ${[r['mean'] for r in pdown if abs(r['p_down']-0.5)<1e-9][0]:.1f}")
check("MC mean sits between the Bear and Bull point estimates", S["B30"].value < mc["mean"] < S["D30"].value, f"${mc['mean']:.1f}")
check("regime split is real: P(>price | down) ≪ P(>price | base)", mc["p_above_price_given_down"] < 0.2 and mc["p_above_price_given_base"] > 0.8, f"{mc['p_above_price_given_down']:.0%} vs {mc['p_above_price_given_base']:.0%}")

print(f"\n{fails} failure(s)")
sys.exit(1 if fails else 0)

What a green run proves and what it doesn't: the arithmetic is consistent and tied to its sources. It cannot prove the 30%, the 17%, or the 8×. Those are the judgment, and the log is where they get tested against time.

the workbook, rendered

The same spreadsheet, so you don't have to open it. Blue is a hardcoded input, yellow is a judgment of mine, everything else is a formula. The provenance column is the point: every number says where it came from.

Inputs — every assumption with its source
assumptionvalueprovenancesource / note
Market data (as of Oct 15–17, 2026)
ItemValueProvenanceSource / note
Share price ($)$32.00MARKETBenzinga quote page, last updated Oct 15, 2026 (header shows $31.90; FAQ shows $32.12).
Diluted shares (millions)529.5REPORTEDQ2 2026 earnings release: diluted average shares 529.5M.
Dividend per share ($/yr)$1.85MARKETBenzinga: 5.80% yield at $31.90 → $1.85/share; policy maintained through 2026 per Q4 2025 call.
Price dateOct 15, 2026MARKETBenzinga quote page.
52-week low ($)$29.00MARKETBenzinga, Oct 15, 2026 (rounded to the dollar).
52-week high ($)$50.00MARKETBenzinga, Oct 15, 2026 (rounded to the dollar).
Share price change, year to date-20.0%MARKETBenzinga, Oct 15, 2026 (rounded).
Share price change, last 12 months-23.0%MARKETBenzinga, Oct 15, 2026 (rounded).
Gross debt$9,217REPORTEDQ2 2026 balance sheet: notes payable/current maturities $1,002M + long-term debt $8,215M. Excludes $2,131M nonrecourse VIE liabilities.
Cash and temporary investments$726REPORTEDQ2 2026 balance sheet.
What behaves like debt but isn't in net debt (FY2025 10-K, filed Feb 2026; balances at Dec 31, 2025)
Pension, U.S. qualified plan: assets$8,482REPORTED10-K MD&A: 'U.S. qualified pension $ 8,116 $ 8,482' (benefit obligation, fair value of plan assets).
Pension, U.S. qualified plan: obligation (PBO)$8,116REPORTEDSame table. The plan is overfunded: 'a surplus of $366 million'.
Pension, UK qualified plan (DS Smith): surplus$112REPORTED10-K MD&A: 'an overfunded UK qualified pension with a surplus of $112 million.'
Pension, U.S. nonqualified plans: obligation, no assets$218REPORTED10-K MD&A: 'U.S. nonqualified pension 218 —'. Paid as benefits come due ($49M in 2025).
Pensions, all plans: assets above obligations (the filing's net)$144REPORTED10-K MD&A: 'the projected benefit obligation ... was approximately $144 million lower than the fair value of plan assets.' Note 19: U.S. plans +$148M, non-U.S. −$4M.
Pension plans in surplus, total (not counted by default)$486REPORTED10-K Note 19, noncurrent assets: U.S. $367M + non-U.S. $119M = $486M (balance sheet: Overfunded Pension Plan Assets $486M).
Pension plans in deficit, total (counted, after tax)$342REPORTED10-K Note 19, liabilities: U.S. $21M current + $198M noncurrent, non-U.S. $5M current + $118M noncurrent = $342M (balance sheet: Underfunded Pension Benefit Obligations $316M long-term).
Retiree medical and postemployment benefits (counted, after tax)$133REPORTED10-K balance sheet: Postretirement and Postemployment Benefit Obligation $133M. Note 19 calls the other postretirement plans 'not considered material' and gives no table.
Operating lease liabilities (not counted: the lease cost is already inside EBITDA)$707REPORTED10-K Note 10: operating lease liabilities $221M current (other current liabilities) + $486M noncurrent (long-term lease obligations); 2025 operating lease cost $258M. Under U.S. GAAP operating lease cost is an operating expense, so EBITDA is after rent; adding the liability too would count it twice.
Finance lease liabilities (not counted again: already inside gross debt)$71REPORTED10-K Note 10: finance lease liabilities $17M in notes payable and current maturities of long-term debt + $54M in long-term debt.
Environmental remediation reserves, old mill sites (counted, after tax)$270REPORTED10-K Note 14: 'International Paper's estimated probable liability for these environmental matters, totaled approximately $270 million' at Dec 31, 2025 (Cass Lake, Kalamazoo River, San Jacinto, Versailles Pond among them). Remediation is a special item, outside adjusted EBITDA.
Asbestos claims, net of insurance (counted, after tax)$103REPORTED10-K Note 14: 'total recorded liability with respect to pending and future asbestos-related claims was $103 million ... net of insurance recoveries'.
Brazil tax guarantee to Sylvamo, already booked (counted in full)$48REPORTED10-K Note 14: 'The initial fair value estimate and recorded liability as of December 31, 2021 was $48 million and remains this amount at December 31, 2025.'
Deferred tax on the Temple-Inland timber notes, due 2027 (counted)$487REPORTED10-K, contractual obligations: '$487 million deferred tax liability related to the Temple-Inland timber monetization, which will be settled with the maturity of the notes in 2027.'
Timber notes held by the special purpose entities (counted, net of the loans)$2,349REPORTED10-K balance sheet: Long-Term Financial Assets of Variable Interest Entities $2,349M. Note 15: $2.4B face, due 2027, backed by letters of credit from three banks.
Nonrecourse loans against those notes$2,127REPORTED10-K balance sheet: Long-Term Nonrecourse Financial Liabilities of Variable Interest Entities $2,127M. Not in gross debt above; the notes repay them in 2027.
Regular debt maturing in 2027 (context, not added)$2,584REPORTED10-K contractual obligations: debt maturities 2026 $992M, 2027 $2,584M, 2028 $739M.
Unrecognized tax benefits (not counted: timing and amount uncertain)$344REPORTED10-K contractual obligations footnote: 'approximately $344 million of unrecognized tax benefits'.
Brazil tax matter via the Sylvamo agreement, IP's potential share (counted only the $48M booked)$274REPORTED10-K Risk Factors: 'our potential liability for such assessments would currently be approximately $274 million' (assessments total about $394M, 2007–2015 tax years).
Count the pension surplus too? (1 = yes, 0 = no)0JUDGMENTDefault no: a surplus can't be spent without an excise tax or years of contribution holidays. Set 1 to net it against the deficits.
Debt-like items, after tax (from the 10-K lines above)$957DERIVEDPension deficits, retiree medical, environmental and asbestos reserves, after tax; the booked Brazil guarantee; the 2027 timber tax less the equity in the timber structure (notes less loans); minus the pension surplus only if counted. Operating leases excluded.
Latest reported and guided financials
2025 pro forma net sales — Packaging Solutions NA$15,000REPORTEDQ4 2025 call: 'over $15B' pro forma for the North American business (rounded).
2025 pro forma net sales — Packaging Solutions EMEA$8,500REPORTEDQ4 2025 call: about $8.5B pro forma for EMEA (DS Smith full year).
2025 pro forma adj. EBITDA — NA$2,300REPORTEDQ4 2025 call: about $2.3B NA.
2025 pro forma adj. EBITDA — EMEA$800REPORTEDQ4 2025 call: about $800M EMEA.
2025 free cash flow(159)REPORTEDQ4 2025 call: free cash flow of negative $159M, reflecting transformation investment.
H1 2026 net sales$11,975REPORTEDQ2 2026 release: six-month net sales $11,975M.
H1 2026 adj. EBITDA$1,264REPORTEDQ2 2026 release: six-month adjusted EBITDA $1,264M (Q1 $677M, Q2 $587M).
H1 2026 capital expenditures$1,050REPORTEDQ2 2026 release: six-month capex $1,050M.
2026 adj. EBITDA guidance — low$3,200GUIDANCEQ2 2026 release: $3.2–3.4B. (January: $3.5–3.7B; cut in April on demand, inflation, Middle East freight.)
2026 adj. EBITDA guidance — high$3,400GUIDANCEQ2 2026 release.
2026 adj. EBITDA guidance in January — low$3,500GUIDANCEQ4 2025 call (Jan 29, 2026): $3.5–3.7B, cut to $3.2–3.4B in April.
2026 adj. EBITDA guidance in January — high$3,700GUIDANCEQ4 2025 call (Jan 29, 2026).
2026 adj. EBITDA guidance — NA (midpoint)$2,425GUIDANCEApril 2026: NA $2.35–2.5B (from $2.5–2.6B).
2026 adj. EBITDA guidance — EMEA (midpoint)$950GUIDANCEApril 2026: EMEA $0.9–1.0B (from $1.0–1.1B).
2026 depreciation & amortization$1,965GUIDANCEQ2 2026 release outlook reconciliation: D&A $1,965M.
2026 interest expense$370GUIDANCEQ2 2026 release outlook reconciliation.
2026 free cash flow guidance (Jan, midpoint)$400GUIDANCEQ4 2025 call: $300–500M FCF for 2026 (pre-April cut; not updated in the Q2 release).
Separation / transaction costs expected 2026–27$250JUDGMENTQ2 2026 alone: $43M separation costs + $5M NORPAC costs + $9M 80/20 severance. Annualized run-rate ~$250M through the spin; your call.
The story, as numbers (Damodaran: narrative → numbers)
2026 containerboard index increase recognized YTD ($/ton, linerboard)$170MARKETFastmarkets RISI via Packaging Dive (Sept 18, 2026): −20 Feb, +40 Mar, +30 Apr, +50 Jun, +70 Sep ≈ +$170/ton linerboard; medium +$100 in Sept alone.
EBITDA per $1/ton of price (annualized, NA)$9GUIDANCECFO Lance Loeffler, Q4 2025 call: each $10/ton ≈ $90M annualized adjusted EBITDA.
Price benefit already inside 2026 NA guidance$175GUIDANCEQ1 2026 call: IP expects pricing to add ~$175M to NA in 2026, mostly in H2.
Share of the YTD index gain that STICKS through 2027 (floor judgment)50.0%JUDGMENTQ1 for Jason: of the $170/ton announced, what holds at the box level in 12 months? Green Markets found no evidence the third round reached the open market (Sept). Default 50%.
NA box volume growth 20271.0%JUDGMENTMarket flat in 2026 (TTOBMA cut containerboard to −2%); IP claims +3–4 pts vs market. Default +1%.
Contribution margin on incremental NA volume30.0%JUDGMENTIncremental box volume through existing mills/plants; typical converter contribution 25–35%.
Incremental 80/20 cost-out 2027 (NA + EMEA)$300JUDGMENT~$510M run-rate benefits in 2025, 'similar' expected in 2026 (Q4 2025 call); EMEA >$200M run-rate from 31 closures. Default $300M for 2027, fading.
Cost inflation 2027 (OCC, energy, labor, freight)$250JUDGMENTOCC back to ~$80–100/ton from 2025 lows (5-yr avg $140); freight up on Middle East; EMEA inflation ~$100M in 2026 guide. Default $250M.
EMEA EBITDA change 2027 (box-price catch-up + cost-out, net)$150JUDGMENTQ2 2026: EMEA lost money because paper prices rose faster than box prices. Catch-up + cost-out vs soft demand. Default +$150M.
Mid-cycle NA adj. EBITDA margin (2028+)17.0%JUDGMENTSW 15% LTM, PKG 21% LTM, IP NA 2026E ~15.9%. Bull = toward PKG after 80/20; bear = below SW. Default 17%.
Mid-cycle EMEA adj. EBITDA margin (2028+)11.0%JUDGMENTDS Smith historically ~12–13%; 2026E ~10%. Default 11%.
Revenue growth 2028–2030 (nominal)2.5%JUDGMENTBox demand ≈ GDP-ish plus price; 2.5% nominal.
Capex 2027$1,900JUDGMENT2026 run-rate ~$2.1B incl. Riverdale conversion ($250M project) and $225M Mississippi greenfield (opens Q4 2027). Default $1.9B.
Capex as % of revenue 2028+ (maintenance + modest growth)6.5%JUDGMENTPaper is capex-heavy: D&A ≈ 8% of sales today. 6.5% assumes the transformation capex rolls off. Your 'assets in the ground are expensive to run' point lives here.
D&A as % of revenue 2027+8.0%JUDGMENT2026: $1,965M on ~$24B = 8.2%.
Net working capital investment, % of incremental revenue10.0%JUDGMENTPackaging converters typically carry 8–12% of sales in NWC.
Cash tax rate24.0%JUDGMENTUS 21% federal + state, blended with EMEA. Check against the 10-K effective rate.
Discount rate (WACC build)
Risk-free rate (10-yr US Treasury)4.0%MARKETPlaceholder near the Oct 2026 10-yr yield — refresh before publishing.
Equity beta1.1JUDGMENTIP 5-yr regression beta has run ~1.0–1.2; Damodaran paper/forest products industry beta ~1.0. Default 1.10.
Equity risk premium4.5%JUDGMENTDamodaran implied ERP has run 4–5% in 2025–26.
Pre-tax cost of debt5.5%MARKETIP senior unsecured notes have traded ~5–6% yield (BBB-range credit). Refresh.
Terminal growth rate2.0%JUDGMENTAt or below long-run nominal GDP for a mature, capex-heavy business.
Exit EV/EBITDA multiple (cross-check)8.0×JUDGMENTIP's own 10-yr average ~8–9x; SW 7.2x and PKG 12.2x today.
Peers (Multiples.vc, Oct 15–17, 2026)
PKG EV/EBITDA (LTM)12.2×MARKETMultiples.vc Oct 17, 2026; EV $25B, LTM revenue $9.8B, EBITDA margin 21%.
PKG EBITDA margin (LTM)21.0%MARKETMultiples.vc.
Smurfit Westrock EV/EBITDA (LTM)7.2×MARKETMultiples.vc Oct 15, 2026; EV $35B, LTM revenue $32B, EBITDA margin 15%.
Smurfit Westrock EBITDA margin (LTM)15.0%MARKETMultiples.vc.
Scenario switch
Active scenario (1 = Bear, 2 = Mid-cycle, 3 = Bull) — drives the Scenarios sheet overrides2JUDGMENTThe Model sheet uses the Inputs above directly; the Scenarios sheet runs all three side by side.
Model — 2025A to 2030E, the bridge and the cash flow
Fiscal year2025A2026E2027E2028E2029E2030E
Revenue
Net sales — NA$15,000$15,287$16,030$16,431$16,842$17,263
Net sales — EMEA$8,500$8,663$8,879$9,101$9,329$9,562
Net sales — enterprise$23,500$23,950$24,909$25,532$26,170$26,825
growth1.9%4.0%2.5%2.5%2.5%
Adjusted EBITDA — the bridge
Adj. EBITDA — NA$2,300$2,425$3,093$2,793$2,863$2,935
Adj. EBITDA — EMEA$800$950$1,118$1,001$1,026$1,052
Corporate / other(100)(75)(75)(75)(75)(75)
Adj. EBITDA — enterprise$3,000$3,300$4,136$3,719$3,814$3,911
margin12.8%13.8%16.6%14.6%14.6%14.6%
NA margin15.3%15.9%19.3%17.0%17.0%17.0%
EMEA margin9.4%11.0%12.6%11.0%11.0%11.0%
From EBITDA to unlevered free cash flow
Depreciation & amortization(1,867)(1,965)(1,993)(2,043)(2,094)(2,146)
EBIT (adjusted)$1,133$1,335$2,143$1,677$1,721$1,766
Cash taxes on EBIT(272)(320)(514)(402)(413)(424)
NOPAT$861$1,015$1,629$1,274$1,308$1,342
Add back D&A$1,867$1,965$1,993$2,043$2,094$2,146
Capital expenditures(2,000)(2,100)(1,900)(1,660)(1,701)(1,744)
Investment in net working capital(45)(96)(62)(64)(65)
Separation / transaction costs (cash)(250)(125)$0$0$0
Unlevered free cash flow$728$585$1,501$1,595$1,636$1,679
UFCF / revenue3.1%2.4%6.0%6.2%6.3%6.3%
Sanity checks against what the company says
Levered FCF proxy = UFCF − after-tax interest$303$1,219
Dividends paid(980)(980)(980)(980)(980)(980)
2025A reported free cash flow (actual)(159)
dividend coverage by levered FCF0.3×1.2×
DCF — WACC, terminal value two ways, value per share
WACC
Market capitalization$16,944
Net debt (gross debt − cash)$8,491
Debt-like items from the 10-K, after tax (Inputs)$957
Weight of equity (market)66.6%
Weight of debt (market)33.4%
Cost of equity = rf + β × ERP8.9%
After-tax cost of debt4.2%
WACC7.4%
Cash flows
Fiscal year2027E2028E2029E2030E
Unlevered free cash flow$1,501$1,595$1,636$1,679
Years from valuation date (mid-year)0.71.72.73.7
Discount factor1.00.90.80.8
Present value of UFCF$1,428$1,414$1,351$1,291
Terminal value — two ways
Gordon growth: UFCF₂₀₃₀ × (1+g) / (WACC − g)$31,960
Exit multiple: EBITDA₂₀₃₀ × multiple$31,292
implied exit multiple of the Gordon TV8.2×
implied perpetual growth of the exit-multiple TV1.9%
Terminal value used (average of the two)$31,626
PV of terminal value$24,320
Enterprise value → equity value → per share
Sum of PV of 2027–2030 UFCF$5,483
Enterprise value$29,803
terminal value as % of EV81.6%
Equity value = EV − net debt − debt-like$20,355
Value per share ($)$38.44
vs. market price20.1%
implied EV / 2027E EBITDA7.2×
market EV / 2027E EBITDA (today)6.1×
market EV / 2026E EBITDA (today)7.7×
Comps and sum of the parts
Trading comps (LTM, Multiples.vc)
CompanyEV/EBITDAEBITDA margin
Packaging Corp of America (PKG)12.2×21.0%
Smurfit Westrock (SW)7.2×15.0%
International Paper (IP) — 2026E, at $327.7×13.8%
IP NA segment margin 2026E15.9%
Sum of the parts — 2028E mid-cycle EBITDA × segment multiple (not the 2027 price-spike year)
Segment2028E EBITDAMultipleValueWhy
Packaging Solutions NA$2,7939.0×$25,139Between SW (7.2x) and PKG (12.2x): a pure-NA box business with 80/20 margins not yet proven.
Packaging Solutions EMEA (DS Smith)$1,0016.5×$6,507European packaging peers trade 6–7x; a sale to a strategic could clear higher — that's the Oct 15 news.
Corporate (capitalized)(75)9.0×(675)
Enterprise value (SOTP)$30,972
Less: net debt + debt-like(9,448)
Less: separation costs and dis-synergies (one-time)(375)1.5 years of the Inputs run-rate; two public companies cost more to run than one.
Equity value$21,148
Per share ($)$39.94
vs. $32 market24.8%
Breakeven: EMEA multiple at which SOTP = market price2.3×If a buyer pays more than this for DS Smith, the market is underpricing the sum.
the model, line by line

This is the Scenarios sheet of the workbook, values as computed, so the whole argument fits on one screen: ten levers on top (the yellow cells), everything below them a formula. The DCF sheet does the same chain year by year; the Monte Carlo draws the levers from distributions and runs it twenty thousand times.

the chainbearmid-cyclebull
Price that sticks through 2027 (% of $170/ton)25.0%50.0%80.0%
NA volume growth 2027-2.0%1.0%3.0%
Cost-out 2027$200$300$450
Inflation 2027$350$250$200
EMEA 2027 change$0$150$300
Mid-cycle NA margin 2028+13.0%17.0%20.0%
Mid-cycle EMEA margin 2028+9.0%11.0%13.0%
Capex % of revenue 2028+7.5%6.5%6.0%
WACC7.9%7.4%6.9%
Exit multiple7.0×8.0×9.5×
derived — formulas only from here down
NA revenue 2027E$15,189$16,030$16,795
EMEA revenue 2027E$8,879$8,879$8,879
NA EBITDA 2027E$2,445$3,093$3,771
EMEA EBITDA 2027E$896$1,118$1,340
Enterprise EBITDA 2027E$3,266$4,136$5,037
Enterprise revenue 2030E$25,919$26,825$27,648
Enterprise EBITDA 2030E (mid-cycle)$2,912$3,911$4,785
UFCF 2030E$704$1,679$2,441
UFCF 2027E (approx.)$919$1,597$2,296
PV of 2027–2030 UFCF (linear ramp 2027→2030)$2,781$5,613$8,196
Terminal value (avg of Gordon and exit multiple)$16,318$31,626$48,362
Enterprise value$15,115$29,933$46,034
Equity value$5,667$20,484$36,586
Value per share ($)$10.70$38.69$69.09
vs. $32 market-66.6%20.9%115.9%
Terminal value share of EV81.6%81.2%82.2%
Implied EV / 2027E EBITDA4.6×7.2×9.1×

From IP_valuation_model.xlsx → Scenarios. Levers are judgments; derived rows are arithmetic. The Dictionary sheet in the workbook defines every term.

show the Monte Carlo (montecarlo.py, as run)
"""Monte Carlo over the IP valuation chain (same mechanics as the Scenarios sheet).

The deterministic model gives one number per story. This gives the distribution of
value per share when the four decisive assumptions are uncertain, and answers the
question a bet actually needs answered: P(value > price), the tails, and how big a
position the edge justifies (Kelly).

Everything here mirrors IP_valuation_model.xlsx — same inputs, same bridge, same
terminal mechanics. Run: python3 montecarlo.py  (writes mc.json for the post's d3 chart)
"""
import json
import numpy as np

rng = np.random.default_rng(2026)
N = 20000

# ---------------- fixed inputs, read from the workbook's Inputs sheet so the two can't drift ----------------
from openpyxl import load_workbook
I = {r[0].value: r[1].value for r in load_workbook("IP_valuation_model.xlsx", data_only=True)["Inputs"].iter_rows(min_row=5)
     if r[0].value and isinstance(r[1].value, (int, float))}
price, shares = float(I["Share price ($)"]), float(I["Diluted shares (millions)"])
net_debt = I["Gross debt"] - I["Cash and temporary investments"] + next(v for k, v in I.items() if "debt-like" in k.lower())
rev_2025_na, rev_2025_emea = float(I["2025 pro forma net sales — Packaging Solutions NA"]), float(I["2025 pro forma net sales — Packaging Solutions EMEA"])
rev_2026_h1 = float(I["H1 2026 net sales"])
na_26, emea_26 = float(I["2026 adj. EBITDA guidance — NA (midpoint)"]), float(I["2026 adj. EBITDA guidance — EMEA (midpoint)"])
corp = (I["2026 adj. EBITDA guidance — low"] + I["2026 adj. EBITDA guidance — high"]) / 2 - na_26 - emea_26
price_index_ytd = float(I["2026 containerboard index increase recognized YTD ($/ton, linerboard)"])
price_sens, price_in_2026 = float(I["EBITDA per $1/ton of price (annualized, NA)"]), float(I["Price benefit already inside 2026 NA guidance"])
contrib_margin = I["Contribution margin on incremental NA volume"]
costout_27, infl_27 = float(I["Incremental 80/20 cost-out 2027 (NA + EMEA)"]), float(I["Cost inflation 2027 (OCC, energy, labor, freight)"])
emea_27_delta = float(I["EMEA EBITDA change 2027 (box-price catch-up + cost-out, net)"])
rev_growth_lt, da_pct = I["Revenue growth 2028–2030 (nominal)"], I["D&A as % of revenue 2027+"]
nwc_pct, tax = I["Net working capital investment, % of incremental revenue"], I["Cash tax rate"]
capex_2027, sep_costs = float(I["Capex 2027"]), float(I["Separation / transaction costs expected 2026–27"])
g = I["Terminal growth rate"]

rev_na_26 = rev_2026_h1 * 2 * rev_2025_na / (rev_2025_na + rev_2025_emea)
rev_emea_26 = rev_2026_h1 * 2 * rev_2025_emea / (rev_2025_na + rev_2025_emea)
rev_26 = rev_na_26 + rev_emea_26

# ---------------- the uncertain assumptions, with a cycle ----------------
# Independent draws understate the tails: in a downturn price does NOT stick, demand falls,
# margins compress and the multiple shrinks together. So first draw the regime, then draw the
# assumptions conditional on it.  P(down-cycle over the horizon) is itself a judgment: 30%.
p_down = 0.30
down = rng.random(N) < p_down
# 1. Share of the $170/ton that sticks through 2027. Jason: "more like half, lag is months."
stick = np.where(down, rng.beta(2, 5, N), rng.beta(5, 4, N))          # down: mode ~0.2; base: mode ~0.57
# 2. Mid-cycle NA EBITDA margin: SW 13% floor ... PKG 21% ceiling.
na_margin = np.where(down, rng.triangular(0.11, 0.135, 0.16, N), rng.triangular(0.14, 0.175, 0.21, N))
# 3. Exit EV/EBITDA multiple.
mult = np.where(down, rng.triangular(6.0, 7.0, 8.5, N), rng.triangular(7.0, 8.5, 11.0, N))
# 4. WACC: normal around 7.4%, 50 bp sd, floored well above g.
wacc = np.clip(rng.normal(0.074, 0.005, N), 0.055, 0.10)
# 2027 volume: base +1% +/- 1.5%; down-cycle -2.5% +/- 1.5% (TTOBMA's -2% forecast lives here).
vol = np.where(down, rng.normal(-0.025, 0.015, N), rng.normal(0.01, 0.015, N))
# EMEA mid-cycle margin and the 2027 catch-up also depend on the regime.
emea_margin_mid = np.where(down, rng.triangular(0.08, 0.095, 0.11, N), rng.triangular(0.10, 0.115, 0.13, N))
emea_27_delta = np.where(down, rng.normal(0.0, 75.0, N), rng.normal(150.0, 75.0, N))
# Capex intensity 2028+: the 'assets in the ground' lever.
capex_pct = rng.triangular(0.060, 0.065, 0.078, N)
# Model risk: the structure itself is wrong by some amount we cannot see. Lognormal haircut/uplift on
# the terminal value with 15% sd — Damodaran's margin of safety, made explicit instead of applied at the end.
MODEL_NOISE = 0.15
model_noise = np.exp(rng.normal(0.0, MODEL_NOISE, N))

# ---------------- the chain (vectorized, same as Scenarios sheet) ----------------
price_delta = stick * price_index_ytd * price_sens - price_in_2026
rev_na_27 = rev_na_26 * (1 + vol) + price_delta
rev_emea_27 = rev_emea_26 * (1 + rev_growth_lt)
na_27 = na_26 + price_delta + rev_na_26 * vol * contrib_margin + (costout_27 - infl_27) * rev_na_26 / rev_26
emea_27 = emea_26 + emea_27_delta + (costout_27 - infl_27) * rev_emea_26 / rev_26
ebitda_27 = na_27 + emea_27 + corp
rev_27 = rev_na_27 + rev_emea_27
grow3 = (1 + rev_growth_lt) ** 3
rev_30 = rev_27 * grow3
ebitda_30 = rev_na_27 * grow3 * na_margin + rev_emea_27 * grow3 * emea_margin_mid + corp
ufcf_30 = (ebitda_30 - rev_30 * da_pct) * (1 - tax) + rev_30 * da_pct - rev_30 * capex_pct - rev_30 * rev_growth_lt / (1 + rev_growth_lt) * nwc_pct
ufcf_27 = (ebitda_27 - rev_27 * da_pct) * (1 - tax) + rev_27 * da_pct - capex_2027 - sep_costs * 0.5
pv_interim = (ufcf_27 / (1 + wacc) ** 0.7
              + (ufcf_27 * 2 / 3 + ufcf_30 / 3) / (1 + wacc) ** 1.7
              + (ufcf_27 / 3 + ufcf_30 * 2 / 3) / (1 + wacc) ** 2.7
              + ufcf_30 / (1 + wacc) ** 3.7)
tv = 0.5 * (ufcf_30 * (1 + g) / (wacc - g) + ebitda_30 * mult) * model_noise
ev = pv_interim + tv / (1 + wacc) ** 3.7
value = (ev - net_debt) / shares

# ---------------- what a bet needs to know ----------------
p_above = float((value > price).mean())
pct = {k: float(np.percentile(value, k)) for k in (5, 10, 25, 50, 75, 90, 95)}
ev_value = float(value.mean())
# Treat it as a bet that resolves over ~3 years: return r = value/price - 1 (ignoring the 5.8% dividend,
# which is upside if it survives and a cut if it doesn't). Growth-optimal (Kelly) fraction solves
# max_f E[log(1 + f r)] numerically; we report half of it, which is what practitioners actually size to.
r = value / price - 1
fs = np.linspace(0, 1, 201)
growth = np.array([np.mean(np.log1p(np.clip(f * r, -0.999, None))) for f in fs])
kelly = float(fs[growth.argmax()])
half_kelly = kelly / 2

# what drives it: rank correlation of each input with value
def spearman(a, b):
    ra, rb = a.argsort().argsort(), b.argsort().argsort()
    return float(np.corrcoef(ra, rb)[0, 1])
drivers = {"down-cycle regime": spearman(down.astype(float), value), "price sticks": spearman(stick, value), "NA mid-cycle margin": spearman(na_margin, value),
           "exit multiple": spearman(mult, value), "WACC": spearman(wacc, value),
           "2027 volume": spearman(vol, value), "capex intensity": spearman(capex_pct, value), "model noise": spearman(model_noise, value)}

hist, edges = np.histogram(value, bins=np.arange(0, 100, 2.5))
hist_down, _ = np.histogram(value[down], bins=edges)       # the same bins, split by regime: the two humps
hist_base, _ = np.histogram(value[~down], bins=edges)
out = {"n": N, "price": price, "p_down": p_down, "p_above_price_given_down": float((value[down] > price).mean()), "p_above_price_given_base": float((value[~down] > price).mean()),
       "mean_down": float(value[down].mean()), "mean_base": float(value[~down].mean()), "p_above_price": p_above, "mean": ev_value, "percentiles": pct,
       "kelly": kelly, "half_kelly": half_kelly, "drivers": drivers,
       "hist": {"edges": edges.tolist(), "counts": hist.tolist(), "down": hist_down.tolist(), "base": hist_base.tolist()},
       "median_down": float(np.median(value[down])), "median_base": float(np.median(value[~down])), "share_down": float(down.mean()),
       "model_noise_sd": MODEL_NOISE, "p_loss_half": float((value < price * 0.5).mean()), "p_double": float((value > price * 2).mean())}
json.dump(out, open("mc.json", "w"), indent=1)
print(f"P(value > ${price}) = {p_above:.1%}   mean ${ev_value:.1f}   median ${pct[50]:.1f}")
print("percentiles:", {k: round(v, 1) for k, v in pct.items()})
print(f"P(value < half the price) = {out['p_loss_half']:.1%}   P(value > 2x) = {out['p_double']:.1%}")
print(f"Kelly {kelly:.2f}  half-Kelly {half_kelly:.2f}")
print(f"given down-cycle ({p_down:.0%}): P(>price) {out['p_above_price_given_down']:.1%}, mean ${out['mean_down']:.1f};  given base: P(>price) {out['p_above_price_given_base']:.1%}, mean ${out['mean_base']:.1f}")
print("drivers (rank corr):", {k: round(v, 2) for k, v in drivers.items()})

The browser panel above uses the same chain with a 3,000-draw simulation, so its percentages differ from the Python run by a point or two.

the model and the code

The spreadsheet is IP_valuation_model.xlsx: 291 live formulas, a Dictionary sheet that explains every term in plant language, every input tagged with its source (the FY2025 10-K, the Q2 2026 release, the Q1 and Q4 calls, Fastmarkets via Packaging Dive, Multiples.vc), and every judgment of mine in yellow so you can overwrite it. The Monte Carlo is montecarlo.py, which mirrors the sheet's scenario chain line for line; build_model.py generates the workbook, validate.py runs the twenty-one tie-out checks, and bet.py maps the distribution onto the instruments. Public information only. Not investment advice; I'm an engineer who builds solvers, not a financial advisor, and I may hold or trade any of these names.

The mechanics are the ones Wall Street Prep teaches — unlevered free cash flow, WACC, a terminal value two ways, comps and sum-of-the-parts. The discipline is Damodaran's: story first, mid-cycle for a cyclical, show what the terminal value carries, and treat the answer as a distribution because that's what it is. The floor is mine.

Written by Jason Fetzer, edited with Claude. Sources: IP Form 10-K for 2025 (filed February 2026); IP Q2 2026 earnings release; Packaging Dive on the Q1 2026 call (Apr 30), the Savannah/Riceboro closures (Aug 2025) and Fastmarkets RISI pricing (Sept 18, 2026); MarketBeat Q4 2025 call summary (Jan 29, 2026); Investing.com on the EMEA sale process (Oct 15, 2026); Multiples.vc for PKG and SW; Benzinga quote (Oct 15, 2026).