Skip to content
Cole Page
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've been building since my internship ended in August. It's 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), and the end of it, the part I actually care about, is extracting structured covenant terms from those agreements so that every term cites the exact line of text it came from, and after that building something that can tell me which twenty pages of a 400-page agreement to read first. I'm 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, which turns out to be a distinction most free data doesn't make, and one I came to care about more than I'd expected once I started relying on it.

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

There's also a more immediate 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, and all of that across every name a member might want to pitch). The commercial APIs that cover that are priced for institutions, which isn't a student fund's budget or mine, and most of what they sell is derived from filings that are already public and free anyway. So the options were living with thin coverage or building the source myself, and building the source is also the more useful thing to know how to do.

Most of the data layer works now and the covenant part is roughly half done, so I'm writing this from the middle rather than the end, and it's mostly about what exists and why it's built the way it is, plus the things I'm probably still wrong about. The extraction itself, and how well it actually scores, will come in later parts.

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, and all of it is free and complete. The problem is the shape it arrives in. EDGAR hands you documents and bulk zip files when what you wanted was an answer, 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 can't tell a restatement apart from a mistake you made yourself.

What's gotten cheap is doing that conversion once and doing it properly. Cheap compute and a model in the loop let one person build the plumbing that used to need a data team, though I'd stress that reading the filing, understanding the business and deciding what the numbers can't tell you are still the actual work, and a pipeline doesn't make any of that faster, since all it does is get rid of the two hours before it. That's the test anything I build has to pass, that it exists because it makes the analysis cheaper rather than because building it was interesting (which it also was).

The timing was practical too. I had a few weeks between the internship ending and school starting, and I wanted them to go toward reading credit agreements, since covenant language is a core skill on the leveraged finance side and I don't know a way to learn it other than reading a lot of agreements closely. So I've been reading 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, and the dataset is more or less what accumulates while you do that.

What exists today

The pipeline collects from EDGAR, the Treasury and the Federal Reserve, normalizes everything into typed, versioned tables and publishes those as Parquet files, which get queried with DuckDB straight over HTTP. That means I don't need a database server or any API keys, and it costs close to nothing to run, with the pipelines themselves on a schedule that goes from 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 already covers the whole universe, every filer and every fact they've tagged, with no sampling and no survivorship bias, and that's the standard the rest of it has to meet. The document corpus is only 16 issuers so far, but the plan is for it to end up the same way, complete, instead of being a selection of whatever I happened to find convenient.

There are four rules that run through every table, and the first is the one I'd defend hardest. Every fact carries two dates, one for when it was true and one for when it became public, where the first is the fiscal period and the second is the timestamp at which EDGAR accepted the filing. Having both lets you answer a question most free data can't. In April 2018, Kraft Heinz's reported FY2016 net income was $3.632B, and after the June 2019 restatement the same fiscal year reads $3.596B. If you ask this dataset as of April 2018 you get the first number along with the filing it was reported in, and if you ask it today you get the second, and both are correct for their date. Ask almost anywhere else and you'll 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.

The second rule is that writes are appends and never overwrites, so a restatement adds a version and the old view stays queryable. The third is that every derived number carries a flag saying how it was computed, which means an EBITDA figure that came from summing quarters looks visibly different from one read off the face of a filing. And the fourth is that when an input is missing the pipeline returns nothing instead of a guess, on the theory that 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 an early-career analyst without any enterprise tooling, can actually do with it.

On the equity side, you can screen or chart any of 20,277 filers on as-filed fundamentals and pull segment revenue by product or geography across 17 years, which gets at questions a consolidated income statement won't answer (which products are carrying a company, which regions have stopped growing, when the mix turned), and for the fund work that's most of the initial pass on a name. On the credit side, you can build an issuer's financial profile from filed data, put leverage and coverage next to the Fed's credit-spread reading for the same month, and pull every credit agreement the issuer has filed, broken into sections, with the covenant language quotable down to the exact character offset.

The point-in-time layer also helps with reproducibility, because an old query stays answerable. If I wrote a memo in March, I can ask the dataset what I would have seen in March and get back the filings that support it.

The covenant layer

This is the part the rest of it exists to support, and the part I care about most, because as far as I can find there's no open dataset of structured covenant terms, and covenant analysis lives in expensive subscription research or in lawyers' heads. What I'm after in the end is an open version of that, where every threshold that gets extracted cites the exact span of agreement text it came from, so anyone reading it can check it against the document in one click.

The order matters here, and it means the labels come before the extractor. I selected 46 agreements across sectors and vintages, and each one gets labeled with one entry per financial maintenance covenant. An entry records the covenant's 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 field I won't compromise on. A validator finds it in the frozen document text and records its character span, and any quote it can't find uniquely gets rejected. Offsets are never typed by hand, since a hand-typed offset is a number nobody can check.

Most of the labels are drafted by a model, which reads a mechanical survey of the agreement and proposes the fields, and then it's on me to verify each draft against the original on EDGAR. Seven documents are held back from that entirely, to be labeled by me alone without a draft ever being generated, which gives me an untainted subset to report scores on later. I also froze the scoring function before any extraction system existed, because if a scorer can still move after you've seen the results, it isn't measuring anything.

As for where it stands, there are drafts for 38 of the 39 non-holdout documents. Eight of those turned out to be the wrong exhibit or an ancillary fragment and got dropped, and the remaining 30 carry 38 covenants (nine fixed-charge coverage tests, six interest coverage tests, fifteen in the leverage family, and four agreements with no financial maintenance covenant at all). Reviewing those drafts is what I'm working on now, and I haven't touched the holdout.

The reading has already changed the schema. Five covenants don't fit any of the ten types I defined at the start (minimum net worth, minimum liquidity and debt to capitalization are among them), which is a polite way of saying I wrote the schema before I'd read enough agreements. Two agreements define a "Total Leverage Ratio" and mean different things by it, one debt to EBITDA and the other debt to capitalization, and the only way you catch that is by reading the definition every time, which is also, I think, the argument against trusting a covenant summary you didn't derive yourself.

Wrong answers that look right

For a little while a tag mapping I was confident in had Wells Fargo's quarterly revenue at $155M instead of $21B, because 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, but I'd rather not lean on that. Financial data is full of answers that are wrong and look reasonable, which is why every derivation carries a flag, and why I'd take a missing value over a guess.

Alphabet is the opposite case. Its annual report doesn't have an amortization line at all, so a strict EBITDA rule would never compute for it. The fix was a narrow fallback that only fires when the missing piece is provably immaterial, and the rows built that way say so, on the principle that I'd rather write a judgment call into a rule someone can read and argue with than apply it silently.

What it does not do

What it replaces is the manual EDGAR spelunking and the one-off spreadsheets, along with paying for a fundamentals API to do personal research. It doesn't replace a terminal, which is real-time markets and news and the ability to talk to the street, and it doesn't replace sell-side or desk research either. It isn't legal review of any agreement, and a covenant label is a research artifact rather than advice. It also doesn't do the analysis, since making the inputs easier to get isn't the same thing 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 of all this is a system that reads a credit agreement the way I would and tells me where to look first.

Analysts who've read a thousand of these carry that around 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 that I don't have yet, and the only way I know to get it is to read a lot of agreements closely, which is the other reason I'm doing the labeling.

The first pass is a different problem from the reading, though. If you could cut 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, with each one pointing at the exact language, it would save most of a day on every new name. The reading would still have to happen, but it would start in the right place instead of on page one, and I'd find out early whether the document was 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're 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 a different vocabulary, and they're the obvious next document type.

The one thing I can't do in this post 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, and that isn't a promise I'd make about a schema I changed last week. As the series goes on I'd like the public-domain core of it to become public, and to have some way of looking through it that doesn't involve writing SQL, though for now I browse it locally with a small query app, which isn't quite the same thing.

In part two I'll get into the extractor, how it's built and what it scores against the labels, and where it fails.