A combined quantitative + NLP study of 2020 10-K "Item 1" filings across the US wholesale sector — 27 years of fundamentals, TF-IDF keyword extraction, a Word2Vec model trained on the filing corpus, and a nearest-competitor tool built on top of it.

TF-IDF keyword word cloud — wholesale sector 10-K "Item 1" text

TF-IDF keyword word cloud — wholesale sector 10-K "Item 1" text

Why I picked 10-K filings

10-Ks are one of the few places where US public companies are legally forced to tell you what scares them. Item 1 ("Business") and Item 1A ("Risk Factors") are the parts where management has to describe — on the record, under SEC scrutiny — what they sell, who they compete with, and what could go wrong. Reading one is a little like peering into the operating model a management team carries around in their heads.

When I started this project, the thing that interested me was the signal-hiding-in-plain-sight angle. Every analyst already reads these filings one at a time. I wanted to see whether NLP could surface patterns you would miss reading them individually — is there a dialect that whole industries speak? Do the neighbors of a word like "reseller" make sense? If I pool every firm's Item 1 text into one corpus, does a plain Word2Vec pick up the structure of an industry on its own?

I also liked that the data is free. The SEC's EDGAR system (sec.gov/edgar) hands you the filings for nothing. The only tax is that the HTML is messy to parse, which I'll cover below.

The project is scoped to the US wholesale sector (SIC major groups 50 and 51 — durable and non-durable goods wholesale). I chose it for three reasons. First, it's unglamorous — nobody's writing breathless think-pieces about wholesale distribution, so there's less fashionable narrative to pattern-match against and more chance of surfacing something the market hasn't already priced in. Second, it's high-volume — wholesale has hundreds of public filers, enough to train a model on without resorting to cross-sector pooling. Third, it's macro-sensitive — both the 2008 crisis and the 2020 COVID supply-chain shock show up clearly in the fundamentals panel, which gave me a built-in test: if the models don't see either event, something's wrong.

There's also a selfish reason. Wholesale firms sit between manufacturers and retail, which means their 10-Ks talk about both sides — production, inventory, logistics, distribution, customer concentration, supplier risk. A model trained on this vocabulary picks up the whole middle of the economy, not just one slice.

The pipeline — how I thought about it

The shape of the thing, end to end:

flowchart LR
    A[SEC EDGAR<br/>10-K HTML] --> B[Parse Item 1<br/>Business text]
    B --> C[Clean text<br/>lowercase, strip<br/>punctuation, NLTK<br/>stopwords]
    C --> D[TF-IDF top-10<br/>keywords per firm]
    C --> E[Word2Vec<br/>gensim, trained<br/>on cleaned corpus]
    D --> F[DocumentSimilarity<br/>mean-pooled<br/>firm embeddings]
    E --> F
    F --> G[Nearest-competitor<br/>lookup + visuals]
    H[public_firms.csv<br/>1994-2020 panel<br/>prcc_c, sale, ROA, SIC] --> I[Quantitative Part 1<br/>industry trends,<br/>2008 drawdown]
    I --> G

Three things are worth calling out about this shape:

  1. The quantitative side and the NLP side meet only at the end. Part 1 was 27 years of financial panel data — stock price, sales, ROA — filtered to the 640 wholesale firms. Part 2 was the 2020 filings corpus (5,988 firms' Item 1 text). They don't talk to each other until Part 3, where I use the NLP-derived nearest competitors to benchmark one focal firm's fundamentals.
  2. TF-IDF and Word2Vec are doing different jobs. TF-IDF tells me what vocabulary is distinctive for a firm. Word2Vec tells me what words mean inside this corpus — the neighbors of "reseller" should be plausible wholesale terms, not random stuff from Google News.
  3. The DocumentSimilarity class is the bridge. It takes each firm's top TF-IDF keywords, mean-pools their Word2Vec vectors into a single document embedding, and then cosine-similarity ranks the whole universe. Crude but surprisingly effective.

Data wrangling — the ugly part

This is the part nobody warns you about. 10-K HTML is horrible. Tables drift mid-sentence, footnotes are rendered as paragraphs, page breaks show up as <hr> tags in the middle of a legal sentence, and some filings have images of scanned signatures embedded inside the risk factor section. If you just pull the HTML body and strip tags, you end up with something that reads like a ransom note.

For this project I got lucky — the Item 1 text was provided as a pre-parsed CSV (2020_10K_item1_full.csv, ~5,988 firms) from the course data. That let me focus on the modeling instead of reinventing an EDGAR scraper. But even with that head start, there was still a long tail of weirdness: filings with huge runs of numbers where the parser had eaten a table, filings where Item 1 was somehow empty, filings where the text was a single 400-KB paragraph with no sentence breaks. For any project where you're starting from raw EDGAR, expect the following layered problem: