Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

stockscreen

A Graham/Buffett-style value screen built on SEC EDGAR fundamentals.

Scores a company against thirteen quality criteria and three valuation criteria, reports the two separately, and runs a conservative DCF.

Quick start

Needs uv. SEC rejects anonymous traffic, so the User-Agent must name a real contact — there is no default.

git clone https://github.com/xiaomei-wu/stockscreen.git
cd stockscreen
uv sync
export SEC_USER_AGENT="Your Name you@example.com"

uv run python -m stockscreen AAPL
uv run python -m stockscreen AAPL MSFT KO --years 10
uv run python -m stockscreen AAPL --json > aapl.json
uv run python -m stockscreen AAPL --failures-only
uv run pytest          # 144 tests, fully offline

Output

QUALITY  12/13
  ✓ FCF trend                +7.1%/yr  > 0             positive 10/10 yrs, median margin 25.1%
  ✓ Share count                -31.8%  shrinking       SBC 9.3% of FCF — real
  ✗ Total payout / FCF         104.4%  < 100%
  ⚠ ROE                        110.6%  > 15%           equity shrunk by buybacks — read ROIC (64.2%) instead
...
 Quality 12/13  ·  Valuation FAIL
 → Good business, wait for price

Quality and valuation are never blended into one number. A great company at a bad price is a wait, not a buy, and a single score hides exactly that.

Layers

Each is a pure function of the one before.

Layer Module Touches network
ingest ingest/edgar.py, ingest/prices.py yes — and only here
normalize normalize.py no
derive derive.py, technicals.py no
score score.py, valuation.py no
report report.py no

derive, score and valuation do no I/O, which is why the whole test suite runs offline in under a second.

Three things that will bite you

SEC requires a real User-Agent. A browser-style string gets a 403 that reads like a network fault, so the client refuses to start without one — set SEC_USER_AGENT="Your Name you@example.com" or pass --user-agent. Rate limit is 10 req/s; the client stays under it.

XBRL tags drift between filers and over time. Apple reports revenue as SalesRevenueNet through FY2017 and RevenueFromContractWithCustomerExcludingAssessedTax after — neither tag alone spans ten years. tags.py resolves every field through a per-year coalesce chain, falling back to summed components where no filer-level aggregate exists (Apple has no DebtCurrent; its short-term debt is commercial paper plus the current portion of long-term debt).

Per-share figures mix share bases across a split. EDGAR reports each year on whatever basis was current when it was last filed. Apple's FY2017 share count was last filed in 2018, before the 2020 4-for-1 split, so raw data shows the share count rising 5.5B → 15.0B — turning the largest buyback program in history into apparent dilution. normalize.py corrects this using each fact's filing date against the split history. Verified by an independent identity: eps × shares reconciles to reported net income in every year.

Known limits

Calibrated for mature, profitable, cash-generative businesses. It will:

  • reject high-growth companies — negative FCF during expansion
  • refuse to score banks and insurers — they tag neither EBIT nor capex, and D/E is meaningless when debt is the product

Both are by design, not by accident. The screen says so in its data notes rather than producing a confident wrong answer.

Not investment advice

A screen narrows a list; it does not make a decision. Every threshold here is a judgement call encoded as a number, and the DCF is a way of writing down assumptions rather than a prediction.

About

A Graham/Buffett-style value screen built on SEC EDGAR fundamentals — 13 quality criteria, 3 valuation criteria, scored separately, plus a conservative DCF.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages