<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: itsRaxzey</title>
    <description>The latest articles on DEV Community by itsRaxzey (@itsraxzey).</description>
    <link>https://dev.to/itsraxzey</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4058335%2F9478eb6e-5b64-4cd4-8b4e-1c653ad79870.jpg</url>
      <title>DEV Community: itsRaxzey</title>
      <link>https://dev.to/itsraxzey</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/itsraxzey"/>
    <language>en</language>
    <item>
      <title>How late is Form 4 'real-time' data? Measuring filing delay on 11,241 insider filings</title>
      <dc:creator>itsRaxzey</dc:creator>
      <pubDate>Mon, 24 Aug 2026 01:13:14 +0000</pubDate>
      <link>https://dev.to/itsraxzey/how-late-is-form-4-real-time-data-measuring-filing-delay-on-11241-insider-filings-2e9j</link>
      <guid>https://dev.to/itsraxzey/how-late-is-form-4-real-time-data-measuring-filing-delay-on-11241-insider-filings-2e9j</guid>
      <description>&lt;p&gt;If you backtest anything involving insider trades, there's a quiet bug waiting for you: &lt;strong&gt;Form 4 data is timestamped twice&lt;/strong&gt;. Every filing carries a &lt;em&gt;transaction date&lt;/em&gt; (when the insider actually traded) and a &lt;em&gt;filing date&lt;/em&gt; (when EDGAR disseminated the form). If your backtest joins on the transaction date, you're trading on information that did not exist yet.&lt;/p&gt;

&lt;p&gt;How big is that gap in practice? I measured it for &lt;strong&gt;every Form 4 filed in July 2026 — 11,241 filings&lt;/strong&gt; — and the answer is: usually 2 days, sometimes 8 years.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;(Disclosure up front: I run &lt;a href="https://filingpulse.io" rel="noopener noreferrer"&gt;FilingPulse&lt;/a&gt;, the SEC filings API this study is built on. This is a methodology piece, not a pitch — the whole thing reproduces on the free tier, and the raw numbers below are what they are regardless of whose API you use.)&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Method
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Window: all Form 4s with a filed date in 2026-07-01 … 2026-07-31 (11,052 originals; 189 Form 4/A amendments excluded — an amendment's delay measures the correction, not the original report).&lt;/li&gt;
&lt;li&gt;For each filing, delay = filed date − earliest transaction date on the form. Holdings-only forms (70) and forms with no dated transaction (31) are excluded; that leaves &lt;strong&gt;10,951 analyzed&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Coverage was verified against EDGAR's own daily indexes for &lt;strong&gt;all 22 trading days in the window (22/22 exact match)&lt;/strong&gt; — so "every Form 4 in July" is a checked claim, not a scrape-and-hope.&lt;/li&gt;
&lt;li&gt;One framing rule I held throughout: a long delay is an &lt;em&gt;observed reporting lag&lt;/em&gt;, &lt;em&gt;not&lt;/em&gt; a compliance violation. Section 16 has deemed-execution-date rules (10b5-1 plans, small acquisitions, etc.) that legitimately extend the clock, and you cannot adjudicate those from the filing alone.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The headline distribution
&lt;/h2&gt;

&lt;p&gt;Calendar days from transaction to dissemination:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Percentile&lt;/th&gt;
&lt;th&gt;Delay&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;p50&lt;/td&gt;
&lt;td&gt;2 days&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;p75&lt;/td&gt;
&lt;td&gt;4 days&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;p90&lt;/td&gt;
&lt;td&gt;5 days&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;p95&lt;/td&gt;
&lt;td&gt;7 days&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;p99&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;91 days&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;max&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;3,140 days (8.6 years)&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;92.9% of filings landed within the SEC's 2-business-day deadline.&lt;/strong&gt; The median is well-behaved. The tail is not: 190 filings arrived 11–30 days after the trade, 131 arrived 31–90 days after, and 110 arrived more than 90 days late. That 8.6-year max is a real filing disseminated in July 2026 for a transaction from 2017.&lt;/p&gt;

&lt;h2&gt;
  
  
  The interesting part: delay depends on what the insider did
&lt;/h2&gt;

&lt;p&gt;Breaking the same numbers out by transaction code:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Code&lt;/th&gt;
&lt;th&gt;Meaning&lt;/th&gt;
&lt;th&gt;Legs&lt;/th&gt;
&lt;th&gt;Within 2 bus. days&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;S&lt;/td&gt;
&lt;td&gt;Open-market sale&lt;/td&gt;
&lt;td&gt;6,694&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;98.2%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;D&lt;/td&gt;
&lt;td&gt;Disposition to issuer&lt;/td&gt;
&lt;td&gt;1,163&lt;/td&gt;
&lt;td&gt;98.8%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;M&lt;/td&gt;
&lt;td&gt;Option exercise&lt;/td&gt;
&lt;td&gt;4,552&lt;/td&gt;
&lt;td&gt;95.4%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A&lt;/td&gt;
&lt;td&gt;Grant/award&lt;/td&gt;
&lt;td&gt;6,897&lt;/td&gt;
&lt;td&gt;92.8%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;F&lt;/td&gt;
&lt;td&gt;Tax withholding&lt;/td&gt;
&lt;td&gt;1,850&lt;/td&gt;
&lt;td&gt;93.8%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;P&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Open-market purchase&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;1,198&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;80.9%&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;J&lt;/td&gt;
&lt;td&gt;"Other"&lt;/td&gt;
&lt;td&gt;822&lt;/td&gt;
&lt;td&gt;79.2%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Sales are filed on time almost universally — they're usually broker-assisted and the paperwork pipeline is well-oiled. &lt;strong&gt;Open-market purchases — the transaction type most insider-trading research cares about most — are the least punctual common code: 1 in 5 arrives after the deadline window.&lt;/strong&gt; If your strategy keys on insider &lt;em&gt;buys&lt;/em&gt; specifically, your look-ahead bias is concentrated exactly where it hurts.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this means for a backtest
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Join on filing date, not transaction date.&lt;/strong&gt; The filing date is when the information became public. Using the transaction date leaks 2+ days of future knowledge on the median filing and months on the tail.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The tail is not ignorable.&lt;/strong&gt; ~4% of filings arrive more than 5 calendar days late. In a monthly-rebalance backtest those stragglers silently teleport information backwards across rebalance boundaries.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Point-in-time data matters more for purchases than sales.&lt;/strong&gt; See the table above.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Reproduce it
&lt;/h2&gt;

&lt;p&gt;The full write-up with charts is here: &lt;strong&gt;&lt;a href="https://filingpulse.io/form4-filing-delay.html" rel="noopener noreferrer"&gt;How late is Form 4 data?&lt;/a&gt;&lt;/strong&gt; — and the study script is a single Python file that runs against a free API key (the &lt;code&gt;since=&lt;/code&gt;/&lt;code&gt;until=&lt;/code&gt; parameters give you a fixed, reproducible population):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;since&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2026-07-01&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;until&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2026-07-31&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;limit&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="c1"&gt;# page through /v1/insider-trades, then for each filing:
# delay = filed_date - min(leg["transaction_date"] for leg in legs)
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Script + methodology notes are linked from the study page. If you rerun it for a different month I'd genuinely like to see whether the purchase-vs-sale punctuality gap holds — it's the one result I didn't expect.&lt;/p&gt;

</description>
      <category>python</category>
      <category>datascience</category>
      <category>finance</category>
    </item>
    <item>
      <title>8-K corporate events hide in SGML headers: how to classify them</title>
      <dc:creator>itsRaxzey</dc:creator>
      <pubDate>Mon, 10 Aug 2026 13:36:04 +0000</pubDate>
      <link>https://dev.to/itsraxzey/8-k-corporate-events-hide-in-sgml-headers-how-to-classify-them-14p2</link>
      <guid>https://dev.to/itsraxzey/8-k-corporate-events-hide-in-sgml-headers-how-to-classify-them-14p2</guid>
      <description>&lt;p&gt;If you want to know &lt;em&gt;the moment&lt;/em&gt; a public company reports earnings, fires a CEO, or signs a merger, the source of truth is a Form 8-K on SEC EDGAR. There's a catch that surprises everyone who tries to consume 8-Ks programmatically: &lt;strong&gt;the thing you actually want — which "items" the 8-K reports — is not in the document. It's in the filing's SGML header.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I run &lt;a href="https://filingpulse.io" rel="noopener noreferrer"&gt;FilingPulse&lt;/a&gt;, an API that normalizes EDGAR filings, so I've had to make this robust across every 8-K the SEC has accepted. Here's what the problem looks like and how the classifier works.&lt;/p&gt;

&lt;h2&gt;
  
  
  What an 8-K "item" is
&lt;/h2&gt;

&lt;p&gt;8-K disclosures are keyed to a fixed list of item numbers. A few you'll recognize:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;2.02&lt;/strong&gt; — Results of Operations (earnings)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;5.02&lt;/strong&gt; — Departure/Election of Directors or Officers&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;1.01&lt;/strong&gt; — Entry into a Material Definitive Agreement&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;7.01&lt;/strong&gt; — Regulation FD Disclosure&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;8.01&lt;/strong&gt; — Other Events&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One filing can carry several items at once. A single 8-K might be &lt;code&gt;[2.02, 7.01, 9.01]&lt;/code&gt; — an earnings release, a Reg-FD statement, and the exhibits index. So the useful signal is &lt;em&gt;which item codes are present&lt;/em&gt;, structured, not prose.&lt;/p&gt;

&lt;h2&gt;
  
  
  The trap: the body doesn't reliably contain the codes
&lt;/h2&gt;

&lt;p&gt;The primary 8-K document is HTML meant for humans. Sometimes it lists "Item 2.02" as a heading; sometimes it just says "Results of Operations and Financial Condition"; sometimes the whole thing is a press release attached as an exhibit and the body barely mentions the item at all. Parsing the body for item codes is a losing game.&lt;/p&gt;

&lt;h2&gt;
  
  
  The fix: the index-headers file
&lt;/h2&gt;

&lt;p&gt;Every filing folder on EDGAR publishes a small header file — the SGML submission header wrapped in HTML — at a predictable URL:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;.../Archives/edgar/data/&amp;lt;CIK&amp;gt;/&amp;lt;ACCESSION_NODASH&amp;gt;/&amp;lt;ACCESSION&amp;gt;-index-headers.html&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Inside, each reported item shows up as a line like:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ITEM INFORMATION: Results of Operations and Financial Condition
ITEM INFORMATION: Financial Statements and Exhibits
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;So you never parse the document at all. You pull one small header file, read the &lt;code&gt;ITEM INFORMATION:&lt;/code&gt; lines, and map each caption back to its code:&lt;/p&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import re, html

&lt;p&gt;def item_codes(header_text):&lt;br&gt;
    caps = re.findall(r"ITEM INFORMATION:\s*([^\r\n&amp;lt;]+)", header_text)&lt;br&gt;
    return [CAPTION_TO_CODE.get(norm(html.unescape(c))) for c in caps]&lt;br&gt;
&lt;/p&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
&lt;br&gt;
  &lt;br&gt;
  &lt;br&gt;
  Two quirks that will bite you&lt;br&gt;
&lt;/h2&gt;

&lt;p&gt;Building the caption-to-code map from the SEC's official item list &lt;em&gt;almost&lt;/em&gt; works. Two things break it against real filings:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. EDGAR's captions don't always match the form instructions.&lt;/strong&gt; The official text for item 3.03 is "Material Modification to Rights of Security Holders" (singular). A large share of real headers say "Material &lt;strong&gt;Modifications&lt;/strong&gt;" (plural). Match captions exactly and you silently drop 3.03 events. You need an alias table built from what EDGAR actually emits, not from the rulebook.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Captions are HTML-escaped.&lt;/strong&gt; Apostrophes arrive as &lt;code&gt;&amp;amp;#39;&lt;/code&gt;, ampersands as &lt;code&gt;&amp;amp;amp;&lt;/code&gt;. "Changes in Registrant's Certifying Accountant" won't match unless you unescape first.&lt;/p&gt;

&lt;p&gt;I found both the only way you ever really find these: by classifying a live feed and watching which items came back as unmapped.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why headers, not full-text search
&lt;/h2&gt;

&lt;p&gt;People reach for EDGAR full-text search or scraping the document because it's the obvious path. The header approach wins on three axes: it's &lt;strong&gt;one small request&lt;/strong&gt; per filing instead of downloading a multi-MB exhibit bundle; it's &lt;strong&gt;structured&lt;/strong&gt; (the SEC already classified it at filing time); and it's &lt;strong&gt;complete&lt;/strong&gt; (every item the filer declared is there, even the ones the body forgets to mention).&lt;/p&gt;

&lt;h2&gt;
  
  
  If you just want the JSON
&lt;/h2&gt;

&lt;p&gt;This is one endpoint in FilingPulse — &lt;code&gt;/v1/events?item=2.02&lt;/code&gt; returns every earnings 8-K, newest first, as normalized JSON, and webhooks push new ones within minutes of filing. Free tier, no card. But honestly the header trick above is the whole secret and you can run it against EDGAR yourself in an afternoon — the fair-access rules just want a declared User-Agent and a sane request rate.&lt;/p&gt;

&lt;p&gt;It's data infrastructure, not investment advice — it reports what was filed, nothing else. Happy to answer EDGAR questions in the comments.&lt;/p&gt;

</description>
      <category>sec</category>
      <category>api</category>
      <category>python</category>
      <category>datascience</category>
    </item>
    <item>
      <title>Parsing SEC EDGAR's Form 4 feed: the edge cases nobody warns you about</title>
      <dc:creator>itsRaxzey</dc:creator>
      <pubDate>Sat, 01 Aug 2026 18:46:14 +0000</pubDate>
      <link>https://dev.to/itsraxzey/parsing-sec-edgars-form-4-feed-the-edge-cases-nobody-warns-you-about-1gm0</link>
      <guid>https://dev.to/itsraxzey/parsing-sec-edgars-form-4-feed-the-edge-cases-nobody-warns-you-about-1gm0</guid>
      <description>&lt;p&gt;Every US public-company insider trade is public data. When an executive buys or sells their own stock, they have two business days to file a Form 4 with the SEC, and it lands on EDGAR — free, no login, no API key. I spent the last month building a pipeline that ingests every one of them in near-real-time, and the gap between "the data is public" and "the data is usable" turned out to be the entire project.&lt;/p&gt;

&lt;p&gt;Here's the field guide I wish I'd had.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. The feed's form-type filter is a lie (it's a prefix match)
&lt;/h2&gt;

&lt;p&gt;EDGAR's current-filings feed takes a &lt;code&gt;type&lt;/code&gt; parameter:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://www.sec.gov/cgi-bin/browse-edgar?action=getcurrent&amp;amp;type=4&amp;amp;output=atom
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You'd expect &lt;code&gt;type=4&lt;/code&gt; to return Form 4s. It returns everything that &lt;em&gt;starts with&lt;/em&gt; 4 — including 424B2 prospectuses and 425 merger communications, which have nothing to do with insider trading. You must re-filter on the exact form type parsed from each entry's title. This isn't documented anywhere I could find; you discover it when a bond prospectus shows up in your insider-trades table.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Every filing appears twice
&lt;/h2&gt;

&lt;p&gt;The same filing is listed once under the reporting owner's CIK and once under the issuer's CIK — same accession number, different index URLs. If you dedupe on URL, you store everything twice. The accession number (&lt;code&gt;0001628280-26-049369&lt;/code&gt;) is the real identity of a filing; treat it as your primary key and make inserts idempotent:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;OR&lt;/span&gt; &lt;span class="k"&gt;IGNORE&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;filings&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;accession&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...)&lt;/span&gt; &lt;span class="k"&gt;VALUES&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  3. Not every XML in the filing folder is the filing
&lt;/h2&gt;

&lt;p&gt;A Form 4 filing folder contains several files, and the tempting move — "grab the .xml" — breaks quietly, because filing-fee exhibit XMLs live in the same folder. Validate the root element (&lt;code&gt;&amp;lt;ownershipDocument&amp;gt;&lt;/code&gt;) before parsing, not the file extension.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Some Form 4s contain no transactions at all
&lt;/h2&gt;

&lt;p&gt;Holdings-only filings exist: no transaction table, no holdings table, just remarks and a signature. They're legal, they're rare, and they will null-pointer your parser at 2am. Multi-owner filings also exist — one filing reporting for three retirement-plan trustees simultaneously — so "the reporting owner" is actually a list.&lt;/p&gt;

&lt;p&gt;Amendments (form type &lt;code&gt;4/A&lt;/code&gt;) reference the original filing and carry an amendment date. If you don't handle them, your dataset double-counts corrected transactions.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. The interesting data hides in footnotes
&lt;/h2&gt;

&lt;p&gt;Share counts and prices carry footnote references (&lt;code&gt;&amp;lt;footnoteId id="F1"/&amp;gt;&lt;/code&gt;), and the footnote text often changes the meaning of the number — "price is a weighted average of executions between $18.11 and $18.29" — so a parser that drops footnotes is destroying information. Keep them attached to the fields they annotate.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Decide your philosophy: values as filed, or values as "fixed"
&lt;/h2&gt;

&lt;p&gt;The biggest design decision in the whole pipeline: numbers in filings are typed by humans at law firms. They contain typos, weird formats, and occasionally nonsense. You can either coerce everything to floats (and silently corrupt the record when coercion guesses wrong) or deliver strings exactly as filed and let consumers coerce at their edge.&lt;/p&gt;

&lt;p&gt;I chose strings-as-filed, and it's the choice I'd defend hardest: this is &lt;em&gt;federal filing data&lt;/em&gt;. If an insider fat-fingers a share count, you should see what the SEC saw — plus two invariants that make the schema livable: every documented field is always present, and &lt;code&gt;null&lt;/code&gt; always means "not stated in the filing."&lt;/p&gt;

&lt;h2&gt;
  
  
  7. HTML entities are everywhere
&lt;/h2&gt;

&lt;p&gt;Issuer names arrive HTML-escaped in some feed views and not others: &lt;code&gt;Organon &amp;amp;amp; Co.&lt;/code&gt; in one place, &lt;code&gt;Organon &amp;amp; Co.&lt;/code&gt; in another. Normalize with &lt;code&gt;html.unescape()&lt;/code&gt; on ingest or enjoy duplicate-company bugs forever. (The related 8-K feed also HTML-escapes apostrophes in item captions, which breaks caption-matching — same fix.)&lt;/p&gt;

&lt;h2&gt;
  
  
  8. The 403 that isn't a throttle
&lt;/h2&gt;

&lt;p&gt;The nastiest production incident so far: EDGAR serves &lt;strong&gt;403 Forbidden — not 404 — for daily-index files that were never published&lt;/strong&gt; (weekends, holidays). A 403 is also exactly what EDGAR's rate limiter serves when you're being throttled. My backfill hit a Sunday index file, interpreted the 403 as throttling, and politely retried the same URL on a backoff loop for 17 hours.&lt;/p&gt;

&lt;p&gt;The fix is a canary request: on a 403 for an index file, immediately fetch a URL you know exists. If the canary returns 200, you're not throttled — the index is genuinely absent, and that day can be checkpointed as empty.&lt;/p&gt;

&lt;h2&gt;
  
  
  9. Be polite — it's cheap and it's the rules
&lt;/h2&gt;

&lt;p&gt;EDGAR's fair-access guidance asks for a declared User-Agent with contact info and a cap of 10 req/s. I run a process-wide rate limiter pinned to 2 req/s with exponential backoff on 403/429 — one enforcement point that every request goes through, so adding a new worker can't accidentally exceed the budget. The entire pipeline — feed polling, two years of backfill, ~90,000 Form 4s and ~30,000 8-Ks so far — runs comfortably inside that on a €5 VPS with SQLite in WAL mode.&lt;/p&gt;

&lt;h2&gt;
  
  
  The punchline
&lt;/h2&gt;

&lt;p&gt;None of these problems is hard in isolation. The product is surviving &lt;em&gt;all of them at once&lt;/em&gt;, continuously, unattended — which is why "the data is free" and "the data is usable" are separated by a parser with a regression corpus of real filings: the amendments, the holdings-only ones, the multi-owner ones, the footnote-heavy ones.&lt;/p&gt;

&lt;p&gt;I packaged all of this as &lt;a href="https://filingpulse.io" rel="noopener noreferrer"&gt;FilingPulse&lt;/a&gt; — normalized Form 4 + 8-K JSON over REST and HMAC-signed webhooks, free tier with the full schema (2,500 req/mo, email signup, no card). If you'd rather build your own pipeline, honestly: take the edge-case list above, it's yours. That's the part I couldn't Google a month ago.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Standard disclaimer that matters in this domain: this is data infrastructure. Nothing here is investment advice — the API reports what was filed, nothing more.&lt;/em&gt;&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

</description>
      <category>python</category>
      <category>api</category>
      <category>webdev</category>
      <category>programming</category>
    </item>
  </channel>
</rss>
