Metadata-Version: 2.5
Name: edgar-traps
Version: 0.1.0
Summary: Eight ways SEC filing data quietly lies to you, each with the bug report that found it.
Project-URL: Homepage, https://github.com/researchaiexe-stack/edgar-traps
Project-URL: Issues, https://github.com/researchaiexe-stack/edgar-traps/issues
Author: Razvan Luca
License: MIT
License-File: LICENSE
Keywords: 13f,edgar,filings,finance,form144,form4,sec,xbrl
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Financial and Insurance Industry
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Topic :: Office/Business :: Financial :: Investment
Requires-Python: >=3.10
Provides-Extra: dev
Requires-Dist: pytest>=7; extra == 'dev'
Description-Content-Type: text/markdown

# edgar-traps

Eight ways SEC filing data lied to me, each with the bug report that found it.

```bash
pip install edgar-traps
```

No dependencies. No network calls. Every function takes data you already fetched,
so it drops into whatever client you have.

---

## Why this exists

I run a research site that values around 900 companies from their own filings.
Everything it publishes comes from EDGAR, and for a year I have been finding out,
one incident at a time, that reading EDGAR correctly is not the same as parsing
it correctly.

None of the bugs below were parsing errors. Not one of them raised. Every single
one produced a plausible number from valid data, which is exactly why they
survived review and were found weeks later by accident — usually while I was
looking at something else entirely.

This package is those eight, with the damage each one did. The number is the
argument. A trap you cannot picture costing anything is a trap you will
reintroduce.

---

## The eight

### 1. A company's own filings feed contains other companies' insiders

A Form 4 names two parties: the issuer whose shares moved, and the reporting
owner who moved them. I queried by issuer CIK and never read `<issuer>`.

But a CIK's submissions feed contains both the filings made *about* it and the
filings it made *as a reporter* of someone else's stock. Own more than 10% of
another public company and you are an insider of that company; every trade you
make in it lands in your own feed.

So a large oil major's page showed **$276.6 million of insider selling that never
happened**. It was real selling — of a completely different company's shares, by
the oil major, in a stake it held. The number looked entirely reasonable for a
company that size. Nothing flagged it, because nothing was malformed.

```python
from edgar_traps import form4_is_about_issuer

form4_is_about_issuer(filing, issuer_cik="0000034088")   # False if it's someone else's
```

### 2. `13F-NT` is a notice that the manager filed *nothing*

It is the institutional "see attached": my holdings are reported on somebody
else's filing. It contains no positions.

Count it as a report and a fund looks current while its last real portfolio is
quarters old. I had a well-known manager showing a fresh filing date over stale
holdings for two quarters. The tracker said they had reported, and they had —
they just had not reported anything.

```python
from edgar_traps import is_position_report

is_position_report("13F-HR")   # True
is_position_report("13F-NT")   # False
```

### 3. One apostrophe hid $11.2 billion

13F filings name holdings in free text, so joining them to tickers means
normalising both sides. I replaced punctuation with a space, which is the
reflex, and it turns `Moody's` into `moody s`. That matches nothing.

**31 companies and $51.2 billion of reported positions went unmatched**,
including Berkshire Hathaway's fifth-largest holding, which showed up on an
otherwise complete page as a blank ticker. I found it because a CSV export
looked one row short.

Apostrophes get deleted. Everything else becomes a separator.

```python
from edgar_traps import normalise_issuer_name

normalise_issuer_name("Moody's Corporation")   # 'moodys'
normalise_issuer_name("MOODYS CORP")           # 'moodys'
```

### 4. A reorganisation moves the ticker to a brand-new CIK

The history stays behind. EDGAR's ticker-to-CIK map follows the reorganisation
immediately, so an ingest keyed on "the CIK for this ticker" starts reading an
entity incorporated last quarter and finds almost nothing — correctly, about a
company that has been filing for decades.

My sweep reported *0 filings in the window* for a company with **301 of them**.
It was right about the CIK it was given. The bridge is in the submissions
document; follow it.

```python
from edgar_traps import candidate_ciks

candidate_ciks(submissions)   # ['0000034088', '0000034089'] — newest first
```

### 5. EDGAR is two hosts, and one of them 404s convincingly

`data.sec.gov` serves the JSON APIs. `www.sec.gov/Archives` serves the documents.
Different origins, different storage.

You meet the JSON APIs first, so you pin `Host: data.sec.gov` once and reuse the
client. Every document fetch then arrives at the archive with the wrong host and
comes back as a storage-layer **404 with a NoSuchKey body**.

That reads as "this filing does not exist". Meanwhile the same URL opens
perfectly in a browser, so it looks like a network block rather than your own
header. I lost a day to it and very nearly told a colleague our server had been
cut off from the SEC.

```python
from edgar_traps import archive_url, host_header_for

archive_url("0001067983", "0001067983-26-000012", "form4.xml")
# unpadded CIK, stripped accession — either one wrong is a 404 that looks like a
# missing filing
```

### 6. Full-text search matches a string, not a claim

Search EDGAR for `there is substantial doubt about` and you get two completely
different sentences back:

> management has concluded that **there is substantial doubt about** the
> Company's ability to continue as a going concern

> the Company is required to evaluate **whether there is substantial doubt
> about** its ability to continue as a going concern

The second is the accounting standard quoted back at you. Over a 30-day window
across every US filer, I read the first twenty hits by hand: **two were the
standard's own wording.** A 10% false-positive rate on the claim "this company
disclosed going-concern doubt" — which is defamatory when wrong, on a page with
a permanent date on it.

The phrase had been validated. Against large filers, where it returns zero false
positives, because big issuers' counsel does not quote the standard back at you.
Small issuers' counsel does, and small issuers are the entire population you are
searching. The validation set was never the population.

```python
from edgar_traps import states_affirmatively

states_affirmatively(document_html, "there is substantial doubt about")
```

Judge every occurrence. A filing routinely carries both — the standard in the
accounting policies, the conclusion in the notes — so stopping at the first match
clears a company that plainly said it.

### 7. Filers mistype the value, and the XML is valid either way

Form 144 carries `aggregateMarketValue` as free-entry text. Two I found in one
universe: **$25.24 billion for 4,000 shares**, and **$11.7 billion for 860**.
Real filings, typed by real people, perfectly well-formed.

The check is arithmetic rather than a size threshold: divide by the share count
and ask whether the implied price could exist.

```python
from edgar_traps import form144_value_is_plausible

form144_value_is_plausible(25_240_000_000, shares=4_000, reference_price=60.0)
# False — implies $6.3m per share
```

One flag matters. A London-listed issuer files this form in **dollars** while its
shares quote in **pence**, so the implied price sits ~87x from the local quote
for entirely correct reasons. I ran the check across that gap and it rejected
**47 correct values** before I noticed they were all listed in one place.

### 8. A notice is not a sale — but check the direction before you write about it

Form 144 announces an intent to sell. The sale, if it happens, appears later on a
Form 4. They are different documents about different events, and conflating them
is how "insider plans to sell" becomes "insider sold" in a headline.

I was going to write that most announced sales never happen. Then I measured it:
across 11,741 notices whose 90-day window had closed, **85% were followed by a
real sale from the same person.** The notice is a good predictor. The article I
had in mind did not survive contact with the data, which is a cheaper thing to
learn from a docstring than from a correction.

```python
from edgar_traps import notice_was_executed

notice_was_executed(notice_date, seller_name, form4_sales, window_days=90)
```

### 9. The currency a company reports in is not the one its shares trade in

Yes, that is nine. The eighth was a bonus and this one is the worst.

A foreign private issuer can file with the SEC in one currency and trade in
another. A UK spirits group reports in **dollars** while its London line quotes
in **pence**. Divide one by the other and your P/E is roughly 150x from reality,
with no field anywhere marked wrong.

The nastier version is same-currency: pounds against pence is exactly 100x, and
the currency code looks right at a glance.

```python
from edgar_traps import price_matches_fundamentals, minor_unit_factor

price_matches_fundamentals("USD", "GBp")   # False
minor_unit_factor("GBP", "GBp")            # 100.0
minor_unit_factor("USD", "GBp")            # None — that's a real FX rate, not a constant
```

---

## What I would tell you if you are building this

Three things I keep relearning.

**A plausible number is the dangerous kind.** Every bug here produced output that
looked fine. The ones that crash get fixed the same afternoon.

**Validate against the population you will run on.** The going-concern phrase was
checked against mega-caps and it was clean, and that told me nothing about the
micro-caps it would actually be pointed at.

**Repetition is a stronger signal than magnitude.** Twice now I have published a
derived constant as if it were a measurement, and both times the tell was the
same value appearing on companies that had nothing to do with each other — not
the value being wrong-looking.

---

## Running the tests

```bash
pip install -e ".[dev]"
pytest
```

Each test is the bug report. The docstrings say what the mistake cost.

## Licence

MIT. The filings are public record; what is here is the list of ways I misread
them.
