Skip to content
Cole PageCredit Analyst
ProjectLeveraged LoansEquity

As Filed, Part One

Building a complete financial dataset out of primary sources: point-in-time fundamentals and full statements for every US filer, Treasury and Fed credit history, and a corpus of credit agreements underneath structured covenant terms that cite the exact line they came from.

This is the first of a few posts about a project I have been building since my internship ended in August. It is a financial data platform put together entirely from primary sources: SEC filings, the Treasury curve, the Fed's credit series, and a corpus of credit agreements. The end of it, and the part I actually care about, is structured covenant terms pulled out of those agreements, each one citing the exact line of text it came from, and after that something that can tell me which twenty pages of a 400-page agreement to read first. I am calling it As Filed, because it answers with what a company had actually filed as of a given date rather than with whatever the numbers were restated to later.

It started as a list of things that annoy me. I want ten years of segment revenue for a company and I am copying it out of ten 10-Ks by hand. I want to know what leverage looked like when a memo was written, and the data has been restated since without telling me. I want to compare the leverage covenant in two credit agreements and I am scrolling through several hundred pages twice to find them. None of it is hard. It is just slow, and none of it is the analysis.

There is also a live reason for it. I built and run the portfolio system the student fund at Anderson uses, and it needs real data underneath it: fundamentals, full statements, filings, corporate actions, across every name a member might want to pitch. Commercial APIs that cover that are priced for institutions, which is not a student fund's budget or mine, and most of what they sell is derived from filings that are already public and free. So the options were thin coverage or building the source myself, and the second one is also the more useful thing to know how to do.

Most of the data layer works now. The covenant part is roughly half done. So this is written from the middle rather than the end: what exists, why it is built this way, and what I am still wrong about. Later parts will cover the extraction itself and how well it actually scores.

Why build it

Getting the information was never the hard part. Every filing is on EDGAR. The Treasury publishes the whole par yield curve back to 1990. The Fed publishes its credit-spread series back to 1973. All of it is free and all of it is complete.

The problem is the shape it arrives in. EDGAR hands you documents and bulk zip files, not answers, and the software that turns one into the other has always been sold to institutions at institutional prices. Without it you do the conversion by hand every time, and the numbers you end up with have no history behind them, so you cannot tell a restatement apart from a mistake you made yourself.

Doing that conversion once, properly, is what has gotten cheap. Cheap compute and a model in the loop let one person build the plumbing that used to need a data team. Reading the filing, understanding the business, and deciding what the numbers cannot tell you is still the actual work, and a pipeline makes none of it faster. It only gets rid of the two hours before it. That is the test anything I build has to pass: it exists to make the analysis cheaper, not because building it was interesting.

The timing was also practical. I had a few weeks between the internship ending and school starting, and I wanted them to go toward reading credit agreements. Covenant language is a core skill on the leveraged finance side, and I do not know a way to learn it other than reading a lot of agreements closely. So I read them with a labeling file open next to the document, which forces you to pin down every threshold, defined term, and test date instead of skimming past them. The dataset is what accumulates while you do that.

What exists today

Collect from EDGAR, Treasury, and the Federal Reserve. Normalize into typed, versioned tables. Publish as Parquet files and query them with DuckDB straight over HTTP. No database server, no API keys, near zero running cost. The pipelines run on a schedule, nightly to monthly.

LayerWhat is in itScale
FundamentalsEvery XBRL fact filed by every US filer, with restatements kept as versions72.9M facts, 20,277 filers
SegmentsBusiness, product, and geography segment facts, 2009 to present43.1M facts
Filings indexEvery filing, with acceptance timestamps and 8-K item codes13.4M filings
Derived metricsQuarterly and TTM revenue, EBITDA, margins, growth, balance sheet454K rows
Rates and spreadsDaily Treasury curve to 1990, Fed credit spreads to 1973, daily FX240K observations
Credit documentsEX-10 credit agreements, frozen text, article-level sections552 documents, 16 issuers
Covenant gold setHand-verified covenant labels with quotes and character spans46 documents selected

The fundamentals side is already the whole universe. Every filer, every fact they have tagged, no sampling and no survivorship, which is the standard the rest has to meet. The document corpus is 16 issuers so far, and the plan is for it to end up the same way: complete rather than a selection I found convenient.

Four rules run through every table.

Every fact carries two clocks: when it was true, and when it became public. The first is the fiscal period, the second is the timestamp EDGAR accepted the filing. That pair answers a question most free data cannot. In April 2018, Kraft Heinz's reported FY2016 net income was $3.632B; after the June 2019 restatement, the same fiscal year is $3.596B. Ask this dataset as of April 2018 and you get the first number, along with the filing it was reported in. Ask it today and you get the second. Both are correct for their date. Ask almost anywhere else and you get the restated figure no matter what date you asked about, which means anything built on it is using information nobody had at the time.

Writes are appends, never overwrites, so a restatement adds a version and the old view stays queryable. Every derived number carries a flag saying how it was computed, so an EBITDA figure that came from summing quarters is visibly different from one read off the face of a filing. And when an input is missing, the pipeline returns nothing rather than a guess, because a blank makes you go check the filing and a plausible wrong number never does.

What it is for

The test I hold it to is what a student or early-career analyst with no enterprise tooling can actually do with it.

On the equity side, screen or chart any of 20,277 filers on as-filed fundamentals, and pull segment revenue by product or geography across 17 years. That answers questions a consolidated income statement will not: which products are carrying a company, which regions have stopped growing, when the mix turned. For the fund work, that is most of the initial pass on a name.

On the credit side, build an issuer's financial profile from filed data, set leverage and coverage next to the Fed's credit-spread reading for the same month, and pull every credit agreement the issuer has filed, sectioned, with the covenant language quotable by exact character offset.

And on research honesty, the point-in-time layer means an old query stays reproducible. If I wrote a memo in March, I can ask the dataset what I would have seen in March, and get the filings that back it up.

The covenant layer

This is the part the rest exists to support, and the part I care most about.

As far as I can find, there is no open dataset of structured covenant terms. Covenant analysis lives in expensive subscription research or in lawyers' heads. The end state is the open version, where every extracted threshold cites the exact span of agreement text it came from, so a reader can check it against the document in one click.

The order matters: the labels come before the extractor. I selected 46 agreements across sectors and vintages, and each one gets labeled by hand: one entry per financial maintenance covenant, with its type, the defined term the agreement uses, the threshold, the test frequency, any step-downs, and a quote copied out of the agreement. The quote is the non-negotiable field. A validator locates it in the frozen document text and records its character span, and a quote it cannot find uniquely gets rejected. Offsets are never typed by hand.

Most labels are model-drafted and human-verified. The model reads a mechanical survey of the agreement and drafts the fields; I check each draft against the original on EDGAR. Seven documents are held back from that entirely, labeled by me alone with no draft ever generated, which gives an untainted subset to report scores on later. The scoring function was frozen before any extraction system existed. If the scorer can move after you see the results, the score measures nothing.

Where it stands: drafts exist for 38 of the 39 non-holdout documents. Eight turned out to be the wrong exhibit or an ancillary fragment and were dropped. The remaining 30 carry 38 covenants, split across nine fixed-charge coverage tests, six interest coverage tests, fifteen in the leverage family, and four agreements with no financial maintenance covenant at all. My review of those drafts is the current work, and the holdout is untouched.

The reading has already changed the schema. Five covenants fit none of the ten types I defined at the start, including minimum net worth, minimum liquidity, and debt to capitalization, which means I wrote the schema before I had read enough agreements. Two of them define a "Total Leverage Ratio" and mean different things by it, one debt to EBITDA and the other debt to capitalization. You only catch that by reading the definition every time, which is also the argument against trusting a covenant summary you did not derive yourself.

Wrong answers that look right

A tag mapping I was confident in briefly made Wells Fargo's quarterly revenue $155M instead of $21B. Banks tag a small equipment-lease line with a name that reads like a revenue concept, and the rule picked it up. Review caught it before anything was published. Financial data is full of answers that are wrong and look reasonable, which is why every derivation carries a flag and why a missing value beats a guess.

The opposite case is Alphabet, whose annual report genuinely contains no amortization line, so a strict EBITDA rule would never compute for it. The fix was a narrow fallback that fires only when the missing piece is provably immaterial, and rows built that way say so. I would rather write a judgment call into a rule someone can read and argue with than apply it silently.

What it does not do

It replaces manual EDGAR spelunking, one-off spreadsheets, and paying for a fundamentals API to do personal research. It does not replace a terminal, which is real-time markets and news and the ability to talk to the street. It does not replace sell-side or desk research. It is not legal review of any agreement, and a covenant label is a research artifact, not advice. And it does not do the analysis. Making the inputs easier to get is not the same as having a view.

None of this is investment advice, and every table carries its own written data-quality caveats.

Where this ends up

The labeled set is a step toward something else. What I want at the end is a system that reads a credit agreement the way I would and tells me where to look first.

Analysts who have read a thousand of these carry that in their heads. On the desk I interned on, I watched people open a 400-page agreement, turn to roughly the right twenty pages, and be right about where the covenant that mattered would sit. That comes from years of reading and I do not have it yet. The only way to get it is to read a lot of agreements closely, which is the other reason for the labeling.

The first pass is a different problem from the reading, though. Cutting 400 pages down to the fifteen or twenty that carry the maintenance covenants, the definitions they depend on, and the carve-outs someone negotiated in, each one pointing at the exact language, would save most of a day on every new name. The reading still has to happen, it just starts in the right place instead of on page one, and I find out early whether the document is worth the day at all.

It also matters that the labels are mine. What counts as important in an agreement depends on which part of the capital structure you are looking at and on how you read, so a system graded against my own labels should surface what I would have gone looking for rather than a generic summary of the document. Indentures are the same problem in different vocabulary, and they are the obvious next document type.

One thing this post cannot do is give you a link. The project is private while the schema is still moving, and every row needs a clean license before any of it goes out. Publishing data that other people might rely on is also a promise to keep it correct, which is not a promise to make about a schema I changed last week. As the series goes on I would like the public-domain core of it to become public, and to have some way of looking through it that does not involve writing SQL. For now I browse it locally with a small query app, which is not the same thing.

Part two will cover the extractor: how it is built, what it scores against the labels, and where it fails.