Invert a stock price into the bet it implies: the growth, duration, and margin a price assumes, via a textbook expectations-investing reverse DCF over SEC EDGAR filings.
Project description
implied-expectations
Every stock price is a forecast. This tool reads the forecast back to you.
Give it a ticker and today's price. It pulls the company's numbers from SEC EDGAR and runs a discounted cash flow backwards: instead of guessing what the stock is worth, it solves for the revenue growth rate, the years it must be sustained, and the operating margin that today's price already assumes. This is the "expectations investing" method described by Alfred Rappaport and Michael Mauboussin. The output is never a fair value, a price target, or a rating. It is the bet the price implies, stated plainly, so you can judge whether the bet is sane.
pip install git+https://github.com/Keenan-ux/implied-expectations
Sixty seconds to a first read
The SEC requires every EDGAR client to identify itself with contact info. Set it once:
export EDGAR_USER_AGENT="Your Name you@example.com"
Then ask what a price implies. Price is always your input; the tool never fetches quotes.
implied-expectations NVDA --price 180
NVIDIA CORP (NVDA) | price $180.00 | fiscal year ended 2026-01-25
Fundamentals (SEC EDGAR companyfacts, trailing fiscal year)
revenue $215.9B [Revenues]
operating income $130.4B (60.4% margin)
tax rate 15.1% (effective, from the filing)
total debt $12.8B [LongTermDebtNoncurrent + ...]
cash + short-term $62.4B [CashAndCashEquivalentsAtCarryingValue + ...]
diluted shares 24.51B [WeightedAverageNumberOfDilutedSharesOutstanding]
revenue growth, trailing 3 fiscal years: +100.5%/yr
Held assumptions (every one is a flag)
discount rate 9.5%
horizon 10 yrs
incremental ROIC 76% (estimated from the filing: NOPAT / invested capital)
terminal growth 2.5% (terminal return on capital = discount rate)
What the price implies
1. revenue growth of +19.9%/yr for 10 years at a 60.4% margin
2. or: growth of +20.0%/yr sustained for 10.0 years
3. or: at +100.5%/yr growth (its trailing pace), a durable operating margin of 0.6%
Implied growth across horizons and discount rates
7.5% 8.5% 9.5% 10.5% 11.5%
5 yr +28.9% +33.5% +37.9% +42.1% +46.3%
10 yr +15.3% +17.7% +19.9% +22.2% +24.3%
15 yr +11.1% +12.9% +14.7% +16.4% +18.0%
Read it like this: at $180, the market is paying about 33.5 times NVIDIA's trailing operating income. For that to work out at a 9.5% discount rate, the company needs to grow revenue about 20% a year for a decade while holding a 60% operating margin. Whether that is reasonable is your call. The tool's job is to make the bet visible.
The model
A standard two-stage free-cash-flow-to-firm DCF, run in reverse.
For each year of the explicit period, revenue grows at a constant rate g and earns the operating margin. Operating profit is taxed at the filing's effective rate. Growth has to be paid for: each year the company reinvests g / ROIC of its after-tax operating profit, the textbook growth-funding identity. What remains is free cash flow, discounted at a flat rate.
After the explicit period, growth drops to a terminal rate and the return on new capital drops to the discount rate, which makes terminal growth value-neutral. That is deliberate. The terminal value should not smuggle in a second growth story.
Enterprise value is the sum of both parts. Subtract debt, add cash, divide by diluted shares, and you have a price. The solver inverts that function three ways:
| Question | Held fixed | Solved |
|---|---|---|
| What growth does the price imply? | margin, horizon | growth rate |
| How long must growth run? | margin, growth rate | years |
| What margin does the price need? | growth rate, horizon | margin |
The solver never fabricates a number. If no growth rate under the cap reaches the price, it re-expresses the bet as years-at-the-cap. If fifty years at the cap still falls short, it says so. If the price sits below what a company would be worth in a near-total-collapse scenario, it says that too.
Every assumption, and where it comes from
| Parameter | Default | Source |
|---|---|---|
| price | none, required | you |
| revenue, operating income | trailing fiscal year | the 10-K, via EDGAR companyfacts |
| operating margin | current margin, held flat | computed from the filing |
| tax rate | effective rate from the filing | falls back to 21% when the filing rate is unusable |
| debt, cash | latest reported balance | the filing; leases included where tagged |
| shares | diluted weighted average | the filing |
| incremental ROIC | NOPAT / invested capital, clamped to 10-100% | the filing; falls back to 20% |
| discount rate | 9.5% | 4.5% risk-free plus a 5% equity premium; override with --discount-rate |
| horizon | 10 years | convention; override with --years |
| terminal growth | 2.5% | roughly long-run nominal GDP |
| terminal return on capital | equals the discount rate | makes terminal growth value-neutral |
Every row is a CLI flag. Change any of them and the tool re-solves.
What this tool does not do
Honesty about limits beats hedging, so here is the list.
- It does not judge plausibility. It tells you the price implies 20% growth for a decade. It does not tell you how rare that is.
- The discount rate is flat. One rate for every company unless you override it. Computing a per-company rate needs price history, and this tool deliberately has no price feed.
- Margins are held flat over the explicit period. No fade, no S-curve.
- Loss-makers are refused, not mispriced. A negative operating margin
makes the inversion undefined; pass
--marginto model an assumed one. - Banks and insurers are not supported. Operating income is the wrong lens for a balance-sheet business, and the tool says so rather than emitting a number.
- Fundamentals are trailing. The trailing fiscal year from the last annual filing, not a forward estimate.
- US filers only, since the data source is SEC XBRL.
Python API
from implied_expectations import Assumptions, EdgarClient, implied_growth
client = EdgarClient(user_agent="Your Name you@example.com")
f = client.fundamentals("AAPL")
a = Assumptions(tax_rate=f.tax_rate, roic=f.roic)
sol = implied_growth(f.to_company(), price=270.00, years=10, assumptions=a)
print(sol.mode, sol.growth) # SolveMode.GROWTH 0.1859...
Fundamentals carries every extracted number plus the XBRL concept it came
from, so nothing is a black box.
Comparing against boothcheck
boothcheck.com runs the same class of inversion
with more machinery: a discount rate computed per company, segment-level
resolution where filings support it, and mid-cycle normalization for
cyclicals. Its decomposition for ~1,950 US stocks is precomputed and free to
query. Add --compare to any run and the tool prints boothcheck's read next
to your local solve:
implied-expectations NVDA --price 180 --compare
The two will not match exactly. They hold different assumptions, and the gap between them is itself informative. The comparison calls boothcheck's public MCP endpoint; nothing is sent except the ticker, and the flag is off by default.
Data conduct
EDGAR requests carry your User-Agent, run single-threaded at most 4 per second, and cache on disk (a day for filings, a week for the ticker map), so repeat runs make no network calls at all.
Correctness
The test suite is the point of this repo. It contains hand-computed closed-form cases (the expected numbers were worked out on paper, not generated by the code under test), round-trip property tests (value forward at a known growth rate, invert, recover it to 1e-8), a cross-check of the year-by-year loop against an independent geometric-sum implementation, and golden tests over frozen NVIDIA and Apple filings verified against the reported figures. If you find a case where the solver is wrong, an issue with the numbers would be very welcome.
pip install -e ".[dev]"
pytest
Disclaimer
For informational and research purposes only. Nothing here is investment advice, a recommendation, or an offer to buy or sell any security. The output describes what a price implies under stated assumptions; it does not predict returns. Do your own research.
MIT license.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file implied_expectations-0.1.0.tar.gz.
File metadata
- Download URL: implied_expectations-0.1.0.tar.gz
- Upload date:
- Size: 81.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
c9ecdaca94497c41ffad249512bac3eb6335606ad2642a8fec0b7f03e34ee70e
|
|
| MD5 |
5077f72542b0662da02052a6ee4df950
|
|
| BLAKE2b-256 |
f49a7c00560e4568b2d886fb5c80220ad4210402c5984947e87f4b4fd443742f
|
Provenance
The following attestation bundles were made for implied_expectations-0.1.0.tar.gz:
Publisher:
release.yml on Keenan-ux/implied-expectations
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
implied_expectations-0.1.0.tar.gz -
Subject digest:
c9ecdaca94497c41ffad249512bac3eb6335606ad2642a8fec0b7f03e34ee70e - Sigstore transparency entry: 2043674966
- Sigstore integration time:
-
Permalink:
Keenan-ux/implied-expectations@4909a9ecd3435d76577ae175567f6d82c5ffb8fc -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/Keenan-ux
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@4909a9ecd3435d76577ae175567f6d82c5ffb8fc -
Trigger Event:
release
-
Statement type:
File details
Details for the file implied_expectations-0.1.0-py3-none-any.whl.
File metadata
- Download URL: implied_expectations-0.1.0-py3-none-any.whl
- Upload date:
- Size: 21.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
561f020dc0715039ebb8d74cd2c87a74bf81f01204609b8edc4dbe52abdeff7e
|
|
| MD5 |
8adf5bb79aa7aef0fe68609469382d52
|
|
| BLAKE2b-256 |
b1d2eb2ab9b8dfb6eed48705b170891cdec4165e942695ba844e1fa4592ed5a2
|
Provenance
The following attestation bundles were made for implied_expectations-0.1.0-py3-none-any.whl:
Publisher:
release.yml on Keenan-ux/implied-expectations
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
implied_expectations-0.1.0-py3-none-any.whl -
Subject digest:
561f020dc0715039ebb8d74cd2c87a74bf81f01204609b8edc4dbe52abdeff7e - Sigstore transparency entry: 2043674975
- Sigstore integration time:
-
Permalink:
Keenan-ux/implied-expectations@4909a9ecd3435d76577ae175567f6d82c5ffb8fc -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/Keenan-ux
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@4909a9ecd3435d76577ae175567f6d82c5ffb8fc -
Trigger Event:
release
-
Statement type: