Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

6 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

compsheet

Trading comps from SEC XBRL filings, with one rule enforced by the architecture:

The model does not do the arithmetic.

It picks the peers and writes the commentary. Every number that lands in a cell — enterprise value, EBITDA, the multiples, the medians — is computed in Python from reported XBRL figures, in code you can read and that has tests.

compsheet build ACME --offline --no-commentary     # no API key needed
compsheet build AAPL --xlsx out/aapl.xlsx

Why that rule

Language models are good at valuation reasoning and unreliable at valuation arithmetic. The dangerous part is that the failure is invisible: a plausible EV/EBITDA that happens to be wrong looks exactly like a correct one, and it looks correct all the way into the deck.

So the split is structural, not a matter of prompting discipline:

Job Who does it Why
Which companies are comparable Model Business model and end market, not a SIC code
Do those companies exist Code Every proposal is resolved against the SEC ticker index
EV, EBITDA, multiples, medians Code Auditable, tested, reproducible
What the table means Model Reads the finished table; never recomputes it

The commentary model receives the rendered table as text. It never sees raw XBRL, so any figure it quotes is already in a row above it and a reader can check it.

n/a vs nm vs zero

Most of the work in a comps tool is refusing to make something up. Three states, kept distinct everywhere:

Cell Means Statistics
n/a The filer never tagged the input Excluded, and counted in the coverage column
nm Arithmetically real, analytically useless — negative EBITDA, a 900x from a near-zero denominator Excluded from median and mean
a number Computed from reported figures Included

Never a zero. A defaulted zero flows into a median, drags the whole peer set, and nobody looking at the output can tell it was invented. Every derived field in the model layer is float | None for exactly this reason.

Coverage is printed under the table, so a median drawn from two of nine peers looks like a median drawn from two of nine peers.

The XBRL problem

Filers don't agree on tags. One reports Revenues; another reports RevenueFromContractWithCustomerExcludingAssessedTax; a third reports both with different values because one is a segment total. Total debt is usually split across current and non-current, so LongTermDebt alone understates anyone with a drawn revolver.

So each concept has an ordered list of candidate tags, and which tag actually supplied each figure is recorded and exported to a Sources sheet in the workbook. When two rows disagree, the first question is whether they were even measuring the same thing.

Only 10-K/20-F/40-F observations are used, so a comps table never mixes an annual figure for one company with a trailing quarter for another.

The workbook is live

The statistics rows are =MEDIAN(...) formulas over the peer range, not pasted values.

An analyst's first move on receiving a comp set is to delete a peer they disagree with. A workbook whose median doesn't move when they do is worse than useless — it's confidently wrong in a way that looks fine. The formulas are IFERROR-guarded because MEDIAN over a range of blanks returns #NUM!, and a peer row is blank whenever the filer didn't tag the input.

Install

git clone https://github.com/K611-dot/compsheet
cd compsheet
python -m venv .venv && .venv/bin/activate      # Windows: .venv\Scripts\activate
pip install -e ".[dev]"

Use

# No credentials: fixture companies, explicit peers, no commentary.
compsheet build ACME --offline --no-commentary

# Live, with model-selected peers and a write-up.
export ANTHROPIC_API_KEY=sk-ant-...
export COMPSHEET_SEC_USER_AGENT="compsheet you@example.com"   # SEC requires this
compsheet build AAPL --out out/aapl.md --xlsx out/aapl.xlsx

# Live, with your own peer set — skips model peer selection entirely.
compsheet build AAPL --peers MSFT,GOOGL,AMZN,META --no-commentary
Flag Effect
--peers A,B,C Use these peers; skips model-driven selection
--no-commentary Skip the write-up (the table is fully deterministic without it)
--offline Bundled fixture companies, no network
--xlsx PATH Write the workbook with live formulas
--out PATH Write Markdown to a file

--peers plus --no-commentary means no model calls at all — the whole deterministic half runs on its own.

Exit codes: 2 config, 3 data source, 4 peer selection, 5 commentary.

The fixture is deliberately awkward

--offline runs against a fictional peer set built to break things, because a tidy one would test none of the handling that matters:

  • a peer with negative EBITDAEV/EBITDA must render nm and leave the median
  • a peer that never tagged D&A → EBITDA is None, not EBIT relabelled
  • a peer with no price series → its entire multiples column is n/a
  • a peer with no prior year → growth is n/a while everything else computes
  • a barely-profitable peer producing a real but useless P/E above the bound

Development

pytest              # hermetic: no network, no API calls
ruff check . && ruff format --check .
mypy

tests/test_metrics.py is the file that justifies the project's central claim. If it were thin, "the model doesn't do the maths" would be decoration.

Disclaimer

Figures are computed from SEC XBRL company facts and last closing prices. This is a comps table, which shows where something trades — not what it is worth. Not investment advice, not a valuation, and no substitute for reading the filings.

Licence

MIT

About

Trading comps from SEC XBRL filings. The model picks peers and writes commentary; every number in a cell is computed in Python. n/a and nm are different answers.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages