One note per holding, updated once a quarter after the company reports. Each note exists to answer three questions, in this order:
- Is the thesis intact? Is the business still the quality compounder that was bought, judged by the QARP criteria that justified the purchase?
- Is it still worth owning at today's price? Cheap, fairly priced, or expensive enough to sell?
- Is the investment actually working? Independent of the share price — is cash flow per share growing?
A note is not a research report. It is a scoreboard with pre-committed thresholds, written when judgement was calm so it can be applied when it is not.
docs/portfolio/
README.md this file -- the shared framework
position-note-template.md blank note, copy for each new holding
adbe-us.md one file per position, named after the canonical symbol
Name each file after the canonical symbol, lowercased, with . replaced by -
(ADBE.US → adbe-us.md). That keeps the file discoverable from any screen output
and sorts sensibly as positions accumulate.
| Position | Symbol | listing_id | Opened | Cost basis | Note |
|---|---|---|---|---|---|
| Adobe Inc. | ADBE.US | 53407 | 2026-08-07 | $262.00 | adbe-us.md |
Every cash-flow yield in these notes is computed twice, on two different denominators. The difference between them is the most informative number in the note.
Yield on cost freezes the denominator at the purchase price forever. It therefore moves only when the business moves. FCF per share up 12% → FCF yield on cost up 12%. It is a scoreboard for the business, immune to market mood, and it is what compounding actually feels like from the inside.
Yield on latest price floats with the market. It is what a new buyer would be offered today, so it is the buy / hold / sell signal — including for the marginal dollar of your own money.
Read them together:
| Yield on cost rising (business compounding) | Yield on cost falling (business shrinking) | |
|---|---|---|
| Yield on price ≥ entry level | Compounding and still cheap — add | Cheap for a reason — investigate, lean sell |
| Yield on price well below entry | Compounding but re-rated upward — hold, trim only at an extreme | Expensive and deteriorating — sell |
The bottom-left cell is the value trap; the top-right is the one most investors sell too early. The gap between the two yields is pure multiple re-rating: paper gains that the business has not yet earned. Track it explicitly.
All three per-share numbers fall out of the latest price and one stored ratio, so no share count is needed:
| Figure | Formula | Yield on cost |
|---|---|---|
| FCF per share | latest_price / price_to_fcf |
FCF_per_share / cost_basis |
| Owner earnings per share | latest_price * oey_equity |
OE_per_share / cost_basis |
| EPS (TTM) | eps_ttm (stored directly) |
eps_ttm / cost_basis |
| Dividend per share | latest_price * dividend_yield_ttm |
DPS / cost_basis |
dividend_yield_ttm is a measured 0 for evidenced non-payers, not missing data —
so income yield on cost is a real zero for a company that has never paid, and the row
stays in the table so the day it initiates a dividend is visible.
The business scoreboard. These are the numbers that should grind upward regardless of what the share price does.
| Metric | Why it earns a row |
|---|---|
| FCF per share, and its yield on cost | The single best per-share compounding measure; cash, not accrual |
| Owner earnings per share, yield on cost | Buffett's figure: earnings after maintenance capex only, so growth spending is not penalised |
| EPS (TTM), yield on cost | Accounting anchor; the gap vs FCF/share is the accrual story |
| Dividend per share, income yield on cost | Cash actually received; the only fully unambiguous return |
net_buyback_yield |
For a non-payer this is the capital return channel |
shareholder_yield_ttm |
Dividend + net buyback: total capital returned |
| Share count (absolute) | The claim you own; buybacks only count if shares actually fall |
| Buyback efficiency (derived) | share count reduction % ÷ net_buyback_yield %. Below ~60% means much of the buyback is plugging stock-comp dilution rather than shrinking the share base — a leak that a headline buyback yield hides |
owner_earnings_cagr_10y, fcf_per_share_cagr_10y |
Long-run compounding rate, the thing being bought |
| Metric | Why it earns a row |
|---|---|
fcf_yield_ev |
Primary valuation gauge; EV-based so leverage cannot flatter it |
oey_ev_norm |
Owner-earnings yield on a 5Y-median numerator — guards against buying peak earnings |
ebit_yield_ev / ev_to_ebit |
Greenblatt's lens; comparable across capital structures |
earnings_yield, price_to_fcf, price_to_book |
Conventional cross-checks |
peg_ratio |
Growth-adjusted; the sanity check on paying up for a compounder |
| Yield spread vs entry (derived) | Yield on cost minus yield on price: how much re-rating has happened since purchase |
These are the QARP criteria that justified the buy. Each has a breach level set at purchase. A breach is not an automatic sell; two consecutive quarters of breach is a mandatory re-underwrite.
roic_ttm, roic_10y_min, gross_margin_ttm, gm_10y_std, opm_7y_min,
cfo_to_ni_ttm, cfo_to_ni_10y_median, net_debt_to_ebitda, interest_coverage,
share_count_cagr_5y, owner_earnings_cagr_10y, iroic_5y.
Running the screen itself each quarter is the cheapest version of this check — it prints every criterion with its value.
Not in the screen, but this is where deterioration shows up first.
| Metric | What a bad reading means |
|---|---|
accruals_ratio |
Rising / positive = earnings drifting ahead of cash (Sloan). Negative is good |
sbc_to_fcf, sbc_to_revenue |
Cash generation partly funded by paying staff in stock; large values make "FCF" flattering |
piotroski_f_score |
Nine-point fundamental health; a multi-point fall is a warning even from a high base |
altman_z |
Distress score; irrelevant until it is not |
current_ratio, working_capital |
Structurally negative for subscription businesses (deferred revenue) — read the trend, not the level |
debt_paydown_years |
Years of FCF to clear debt; the honest leverage measure |
mcapex_ttm vs total capex |
Maintenance vs growth split; rising maintenance share means the moat is getting expensive to hold |
A short list, but these often lead the reported numbers by a quarter or more. Fill them in from the earnings release.
- Remaining performance obligations / deferred revenue growth — for a subscription business, the best forward indicator there is
- Net revenue retention — expansion within the existing base, the moat in one number
- Segment revenue growth — where growth is actually coming from
- Guidance change — raised, held, or cut, and management's stated reason
- Insider buying / selling — open-market purchases only; sales are noise
- Debt maturity wall — what refinances in the next 24 months, and at what rate
- One-line narrative — what changed in the competitive position this quarter
Written in advance, applied mechanically. Position-specific numbers live in each note; these are the defaults.
Sell on valuation — thesis intact, price extreme:
fcf_yield_evbelow 4% (EV/FCF above 25x), orev_to_ebitabove 25x,- and the compounding rate no longer justifies the multiple (
owner_earnings_cagr_10ybelow the earnings yield you are giving up).
Sell on thesis — price irrelevant:
- Two or more tripwires breached for two consecutive quarters, or
- FCF per share lower than at entry for four consecutive quarters, or
- Any single tripwire breached by a wide margin (e.g. cash conversion below 0.7, net debt/EBITDA above 3.5x).
Add:
fcf_yield_evat or above the entry level, all tripwires intact, position below its target portfolio weight.
Hold — everything else. The default, and the correct answer most quarters.
Run after the company reports, then update the note's log table.
# 1. Latest price (0-day window forces a refresh even if today's snapshot exists)
pyvalue update-market-data --symbols ADBE.US --max-age-days 0
# 2. New filing -> raw payload -> facts -> metrics
pyvalue ingest-fundamentals --symbols ADBE.US --max-age-days 0
pyvalue normalize-fundamentals --symbols ADBE.US
pyvalue compute-metrics --symbols ADBE.US
# 3. Re-run the screen that justified the purchase
pyvalue run-screen --config screeners/quality_reasonable_price_primary.yml --symbols ADBE.USThen pull the tracked metrics in one read (substitute the position's listing_id):
SELECT metric_id, ROUND(value, 4) AS value, as_of
FROM metrics
WHERE listing_id = 53407
AND metric_id IN (
'fcf_yield_ev', 'oey_ev', 'oey_ev_norm', 'ebit_yield_ev', 'earnings_yield',
'ev_to_ebit', 'ev_to_ebitda', 'price_to_fcf', 'price_to_book', 'peg_ratio',
'eps_ttm', 'market_cap',
'roic_ttm', 'roic_10y_min', 'gross_margin_ttm', 'gm_10y_std', 'opm_7y_min',
'cfo_to_ni_ttm', 'cfo_to_ni_10y_median', 'net_debt_to_ebitda',
'interest_coverage', 'share_count_cagr_5y', 'owner_earnings_cagr_10y', 'iroic_5y',
'dividend_yield_ttm', 'net_buyback_yield', 'shareholder_yield_ttm',
'accruals_ratio', 'sbc_to_fcf', 'sbc_to_revenue', 'piotroski_f_score', 'altman_z',
'current_ratio', 'debt_paydown_years', 'mcapex_ttm', 'fcf_per_share_cagr_10y'
)
ORDER BY metric_id;Share count actually outstanding, for the buyback-efficiency check:
SELECT fiscal_period, end_date, value / 1e6 AS shares_millions
FROM financial_facts
WHERE listing_id = 53407 AND concept = 'CommonStockSharesOutstanding'
ORDER BY end_date DESC LIMIT 8;Metric definitions are in Metrics Catalog; read the
definition before acting on a number, because several have documented caps and
fallbacks (iroic_5y caps at 1.0, dividend_yield_ttm measures a true 0 for
evidenced non-payers).
- Fiscal lag. Metric
as_ofdates trail the price. A note dated today mixes a fresh price with a quarter-old balance sheet; that is correct, not a bug, but the yield on latest price is always slightly stale on the numerator. - Screen pass ≠ buy. The screen is a filter on the past. Nothing in it sees competitive disruption coming.
- Cost basis is not a price target. The market does not know or care what was paid. Yield on cost measures the business; it never justifies holding a broken one.
- One quarter is noise. Every rule here requires two consecutive quarters before it compels an action, deliberately.