<?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: Christian Pichichero</title>
    <description>The latest articles on DEV Community by Christian Pichichero (@tradevodata).</description>
    <link>https://dev.to/tradevodata</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%2F4017923%2Fe278d800-4f1b-4a9a-8659-027387e2544f.png</url>
      <title>DEV Community: Christian Pichichero</title>
      <link>https://dev.to/tradevodata</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/tradevodata"/>
    <language>en</language>
    <item>
      <title>Why the Same Fundamental Lives Under Different XBRL Tags in SEC EDGAR</title>
      <dc:creator>Christian Pichichero</dc:creator>
      <pubDate>Wed, 12 Aug 2026 16:31:22 +0000</pubDate>
      <link>https://dev.to/tradevodata/why-the-same-fundamental-lives-under-different-xbrl-tags-in-sec-edgar-fmi</link>
      <guid>https://dev.to/tradevodata/why-the-same-fundamental-lives-under-different-xbrl-tags-in-sec-edgar-fmi</guid>
      <description>&lt;p&gt;If you've tried to pull "revenue" for a US company directly out of SEC EDGAR's XBRL data, you've probably noticed something annoying: the same line item on the income statement doesn't always show up under the same tag. One year it's &lt;code&gt;Revenues&lt;/code&gt;. Another year, for the same company, it's &lt;code&gt;SalesRevenueNet&lt;/code&gt;. After 2018 it might switch again to &lt;code&gt;RevenueFromContractWithCustomerExcludingAssessedTax&lt;/code&gt;. Nothing about the business changed — the taxonomy did.&lt;/p&gt;

&lt;p&gt;This is one of the least-discussed but most consequential problems in building point-in-time fundamentals from EDGAR, and it's the reason a lot of DIY XBRL scrapers quietly produce broken time series without anyone noticing until a backtest looks weird.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the tags change at all
&lt;/h2&gt;

&lt;p&gt;The SEC requires filers to tag financial statement line items using the US GAAP XBRL taxonomy, which the FASB updates annually. A few things drive tag churn:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Taxonomy revisions.&lt;/strong&gt; New tags get added, old ones get deprecated, and companies (or their filing agents) migrate to the current tag in a later filing — sometimes mid-history, sometimes not at all.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Accounting standard changes.&lt;/strong&gt; The rollout of ASC 606 (revenue recognition) around 2018 is the clearest example: many filers moved from &lt;code&gt;Revenues&lt;/code&gt; or &lt;code&gt;SalesRevenueNet&lt;/code&gt; to &lt;code&gt;RevenueFromContractWithCustomerExcludingAssessedTax&lt;/code&gt; or the "IncludingAssessedTax" variant, sometimes in the same fiscal year they adopted the standard.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Filer inconsistency.&lt;/strong&gt; Two companies in the same industry, filing in the same quarter, can choose different (both technically valid) tags for what an analyst would call the same concept. Smaller filers and their outside preparers are especially inconsistent year over year.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Custom/extension tags.&lt;/strong&gt; Filers can create company-specific extension tags instead of using a standard one, which is valid XBRL but invisible to anyone matching on a fixed tag list.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of this is a data error. It's just how the taxonomy and filer behavior evolve. But if your pipeline hardcodes "revenue = tag X," you will silently lose or corrupt history the moment a company switches tags.&lt;/p&gt;

&lt;h2&gt;
  
  
  How this breaks naive datasets
&lt;/h2&gt;

&lt;p&gt;The common failure mode looks like this: a script pulls &lt;code&gt;Revenues&lt;/code&gt; for every 10-K, going back as far as the filer used that tag. The moment the company switches to &lt;code&gt;RevenueFromContractWithCustomerExcludingAssessedTax&lt;/code&gt;, the naive pull sees a gap — the concept looks like it disappeared. Depending on how the downstream code handles missing values, you get one of three quiet failures:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A false gap in the time series (treated as no revenue reported).&lt;/li&gt;
&lt;li&gt;A forward-fill of the last known value, understating growth or flatlining a metric that actually changed.&lt;/li&gt;
&lt;li&gt;A join against the wrong tag entirely, if a fallback rule grabs a &lt;em&gt;different&lt;/em&gt; line item that happens to exist (e.g., falling back to &lt;code&gt;SalesRevenueGoodsNet&lt;/code&gt; and picking up only product revenue, not total revenue).&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;All three are worse than an explicit "no data," because they don't look like errors — they look like real numbers. That's the trap: tag drift doesn't crash your pipeline, it silently biases it.&lt;/p&gt;

&lt;h2&gt;
  
  
  How synonym-tag resolution actually works
&lt;/h2&gt;

&lt;p&gt;The fix is conceptually simple but tedious to do correctly: instead of mapping one concept to one tag, you maintain a &lt;strong&gt;synonym set&lt;/strong&gt; of tags per concept, ordered and scoped by validity period and filer behavior, and you resolve at read time rather than baking in a single tag at ingest time.&lt;/p&gt;

&lt;p&gt;In practice, for Tradevo Data that means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Each of our seven tracked concepts (Revenue, NetIncome, Assets, StockholdersEquity, OperatingCashFlow, EPSDiluted, DilutedShares) maps to a list of known US GAAP tags that filers have used for that concept, not a single tag.&lt;/li&gt;
&lt;li&gt;When a filing is parsed, we check the synonym list in a defined priority order and record which tag actually resolved, so the mapping is auditable rather than a black box.&lt;/li&gt;
&lt;li&gt;We track &lt;code&gt;first_filed&lt;/code&gt; (when the value became public) separately from &lt;code&gt;latest_value&lt;/code&gt; (the current, possibly amended figure), and flag rows as &lt;code&gt;restated&lt;/code&gt; when a same-tag revision moves the value by more than 0.5%, including amendments. That's how the dataset ends up with 18,717 labeled restatements — those are tag-consistent revisions, not tag-switch artifacts.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;qa_status&lt;/code&gt; on each row exists specifically so a switch that looks suspicious (e.g., a jump coinciding with a tag change) is visible to whoever is using the data, rather than silently smoothed over.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The full mapping logic and the reasoning behind it — including which tags we treat as synonyms for each concept and why — is public in the methodology alongside the free sample, not hidden behind the paid API. If you want to see exactly how a specific company's revenue tag changed over time, that's the place to check it yourself: github.com/christianpichichero-max/pit-fundamentals (3,280 rows across 40 companies, full methodology, no signup).&lt;/p&gt;

&lt;p&gt;For the point-in-time angle specifically — why &lt;code&gt;first_filed&lt;/code&gt; matters independently of tag resolution — see &lt;a href="https://dev.to/blog/point-in-time-fundamentals-data"&gt;/blog/point-in-time-fundamentals-data&lt;/a&gt; and &lt;a href="https://dev.to/blog/lookahead-bias-fundamental-backtests"&gt;/blog/lookahead-bias-fundamental-backtests&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  A fair comparison of your options
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Approach&lt;/th&gt;
&lt;th&gt;Handles tag drift?&lt;/th&gt;
&lt;th&gt;Point-in-time (&lt;code&gt;first_filed&lt;/code&gt;)?&lt;/th&gt;
&lt;th&gt;Frequency&lt;/th&gt;
&lt;th&gt;Cost&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Raw SEC EDGAR + your own scraper&lt;/td&gt;
&lt;td&gt;Only if you build synonym mapping yourself&lt;/td&gt;
&lt;td&gt;Only if you build it (EDGAR gives you filing dates, not a PIT API)&lt;/td&gt;
&lt;td&gt;Whatever you implement&lt;/td&gt;
&lt;td&gt;Free (your engineering time)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tradevo Data&lt;/td&gt;
&lt;td&gt;Yes, synonym sets per concept, documented in the free methodology&lt;/td&gt;
&lt;td&gt;Yes, &lt;code&gt;first_filed&lt;/code&gt; + &lt;code&gt;original_value&lt;/code&gt; on every row&lt;/td&gt;
&lt;td&gt;Annual only (10-K / 10-K/A); quarterly is on the roadmap, not available today&lt;/td&gt;
&lt;td&gt;$49/mo&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sharadar / Tiingo / QuantConnect fundamentals&lt;/td&gt;
&lt;td&gt;Not independently verified by us — these are established, mature vendors, so check their docs for specifics&lt;/td&gt;
&lt;td&gt;Varies by product; check each vendor's docs&lt;/td&gt;
&lt;td&gt;Varies by product; check each vendor's docs&lt;/td&gt;
&lt;td&gt;See their pricing pages: &lt;a href="https://data.nasdaq.com/publishers/SHARADAR" rel="noopener noreferrer"&gt;Sharadar via Nasdaq Data Link&lt;/a&gt;, &lt;a href="https://www.tiingo.com/" rel="noopener noreferrer"&gt;Tiingo&lt;/a&gt;, &lt;a href="https://www.quantconnect.com/datasets" rel="noopener noreferrer"&gt;QuantConnect&lt;/a&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This isn't an attempt to declare a winner. We haven't audited Sharadar's, Tiingo's, or QuantConnect's internal tag-resolution logic, so we're not claiming to know how they handle it — only that they're credible, established sources worth comparing against. If price or feature fit matters to your decision, their pricing pages will tell you what's on offer; we're not going to guess a number for you.&lt;/p&gt;

&lt;h2&gt;
  
  
  When the established players are the better choice
&lt;/h2&gt;

&lt;p&gt;Be honest with yourself about what you actually need before defaulting to the cheaper option:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If you need &lt;strong&gt;quarterly fundamentals&lt;/strong&gt;, Tradevo Data doesn't have them yet (roadmap only) — an established vendor that already offers quarterly data is the right call today.&lt;/li&gt;
&lt;li&gt;If you need &lt;strong&gt;non-US markets&lt;/strong&gt;, more historical depth than 12 fiscal years, or a broader concept set beyond our seven, a larger vendor's coverage is likely to fit better.&lt;/li&gt;
&lt;li&gt;If you need a &lt;strong&gt;track record&lt;/strong&gt; — a data provider that's been used in production research for years, with support SLAs and a sales team you can talk to — that's a real advantage of established players over a $49/mo budget tool run by a small team.&lt;/li&gt;
&lt;li&gt;If Parquet or other formats matter to your pipeline today (not roadmap, today), check whether an established vendor already ships it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We'd rather point you to the right tool than pretend we're the right tool for every use case.&lt;/p&gt;

&lt;h2&gt;
  
  
  When you should build it yourself
&lt;/h2&gt;

&lt;p&gt;Building your own EDGAR XBRL parser is a legitimate choice, not just a fallback for people who can't afford data. It's the right call if:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You need concepts or tags outside the seven we track (segment data, non-GAAP reconciliations, footnote disclosures).&lt;/li&gt;
&lt;li&gt;You need international filers or non-EDGAR sources.&lt;/li&gt;
&lt;li&gt;You have engineering time to spend and want full control over the synonym-resolution rules rather than trusting someone else's judgment calls.&lt;/li&gt;
&lt;li&gt;Your research only needs a handful of companies and a few concepts — at that scale, hand-checking tag switches against the actual 10-K filings is faster than integrating a new data source.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Where a vendor (any vendor, not just us) earns its cost is in the tedious part: tracking taxonomy changes across thousands of filers over many years, catching restatements, and doing it consistently so you're not re-solving the same tag-drift problem every time the FASB updates the taxonomy.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it before you pay for it
&lt;/h2&gt;

&lt;p&gt;The free sample has the same tag-resolution logic as the paid dataset, just scoped to 40 companies and 3,280 rows — enough to inspect a real tag switch yourself and decide if the approach holds up: github.com/christianpichichero-max/pit-fundamentals.&lt;/p&gt;

&lt;p&gt;If it does and you need the full 5,193-company, 313,001-row universe with a server-side &lt;code&gt;as_of&lt;/code&gt; query, the API and bulk download (&lt;code&gt;/v1/download&lt;/code&gt;, &lt;code&gt;/v1/snapshot?as_of&lt;/code&gt;) are $49/mo, cancel anytime: &lt;a href="https://tradevodata.com/?ref=blog" rel="noopener noreferrer"&gt;tradevodata.com/?ref=blog&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;Not investment advice. This dataset describes what was publicly filed and when — it makes no claims about future performance. Verify competitor pricing and feature claims yourself on their websites before deciding.&lt;/p&gt;

</description>
      <category>quant</category>
      <category>python</category>
      <category>finance</category>
      <category>datascience</category>
    </item>
    <item>
      <title>SEC 10-K Filing Deadline Data: The Structural Reason Fundamentals Lag Weeks Behind</title>
      <dc:creator>Christian Pichichero</dc:creator>
      <pubDate>Wed, 12 Aug 2026 16:30:51 +0000</pubDate>
      <link>https://dev.to/tradevodata/sec-10-k-filing-deadline-data-the-structural-reason-fundamentals-lag-weeks-behind-2h36</link>
      <guid>https://dev.to/tradevodata/sec-10-k-filing-deadline-data-the-structural-reason-fundamentals-lag-weeks-behind-2h36</guid>
      <description>&lt;p&gt;If you've ever wondered why a company's "Q4 numbers" aren't publicly known the day the fiscal year ends, the answer isn't vendor laziness or data-pipeline delay. It's the SEC's own filing calendar. The deadlines are public, stable, and have been in place for years — and they're the single biggest structural reason any honest fundamentals dataset has a gap between period-end and public availability.&lt;/p&gt;

&lt;p&gt;This matters for anyone backtesting on fundamentals: if your data model assumes a number was known the moment the quarter closed, you're not modeling reality. You're modeling a fantasy calendar that doesn't exist.&lt;/p&gt;

&lt;h2&gt;
  
  
  The SEC's 10-K Deadlines Are Public and Fixed by Filer Class
&lt;/h2&gt;

&lt;p&gt;Under SEC rules (Exchange Act Rule 12b-2 and related Regulation S-K guidance), the deadline to file an annual report (Form 10-K) after fiscal year-end depends on the company's filer category, which is based on public float:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Filer Category&lt;/th&gt;
&lt;th&gt;Public Float Threshold&lt;/th&gt;
&lt;th&gt;10-K Deadline After Fiscal Year-End&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Large Accelerated Filer&lt;/td&gt;
&lt;td&gt;$700M or more&lt;/td&gt;
&lt;td&gt;60 days&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Accelerated Filer&lt;/td&gt;
&lt;td&gt;$75M to $700M&lt;/td&gt;
&lt;td&gt;75 days&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Non-Accelerated Filer&lt;/td&gt;
&lt;td&gt;Under $75M&lt;/td&gt;
&lt;td&gt;90 days&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;These thresholds and deadlines are public record, not proprietary knowledge. Any company can and does file earlier than its deadline — but a meaningful share file close to it, because compiling audited financials, running the audit committee process, and drafting MD&amp;amp;A takes real time. The deadline is a ceiling companies work toward, not a floor they clear early by default.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the Deadline Creates a Point-in-Time Gap
&lt;/h2&gt;

&lt;p&gt;Here's the mechanical consequence: a fiscal year that ends December 31 might not have its 10-K publicly filed until late February (large accelerated), mid-March (accelerated), or the end of March (non-accelerated). That's a 60-to-90-day window baked into the regulatory structure, before you even account for filers who use extensions (Form 12b-25) or file 10-K/A amendments later.&lt;/p&gt;

&lt;p&gt;So when you see a "FY2023 Revenue" figure, the honest question is: known to the public &lt;em&gt;when&lt;/em&gt;? Not "as of fiscal year-end" — as of the date the 10-K (or amendment) actually hit EDGAR. Any dataset that timestamps fundamentals by fiscal period end instead of filing date is implicitly assuming zero-day disclosure, which the SEC's own deadlines show doesn't happen.&lt;/p&gt;

&lt;h2&gt;
  
  
  From Filing Date to "First Known" Value
&lt;/h2&gt;

&lt;p&gt;This is why point-in-time (PIT) datasets track more than just the number. Tradevo Data's schema, sourced directly from SEC EDGAR (public domain), records for each row:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;first_filed&lt;/strong&gt; — the date the value first became public&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;original_value&lt;/strong&gt; — the first-reported figure (the point-in-time-safe one)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;latest_value&lt;/strong&gt; — the current, possibly-restated figure&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;restated flag&lt;/strong&gt; — set when a later filing changes the same tag by more than 0.5%, including amendments&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;qa_status&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Across the dataset, 18,717 restatements carry the restated flag — a reminder that "the number" for a given fiscal year isn't fixed at first filing; it can move, and a PIT-correct backtest needs to use what was known on a given date, not what we know now with hindsight.&lt;/p&gt;

&lt;h2&gt;
  
  
  What We Actually Measured (Scoped Honestly)
&lt;/h2&gt;

&lt;p&gt;On the free 40-company sample's reliable-filing rows, the measured lookahead — the gap between fiscal period end and first_filed — averaged 43.4 days, with a maximum of 61 days. That number is specific to the 40-company sample and should not be quoted as a property of the full 5,195-company, 313,137-row dataset; the two are measured differently and we're not going to blur that line for a cleaner headline.&lt;/p&gt;

&lt;h2&gt;
  
  
  When a Bigger or Quarterly-Aware Vendor Is the Better Choice
&lt;/h2&gt;

&lt;p&gt;In the interest of not overselling: if your strategy needs quarterly (10-Q) fundamentals, non-US equities, Parquet delivery, or a vendor with a longer operating track record and broader SLA guarantees, providers like Sharadar, Tiingo, or QuantConnect are worth evaluating — see their pricing pages directly, since we won't quote competitor prices here. QuantConnect in particular is worth a look if you want fundamentals data pre-integrated into a backtesting engine rather than delivered as a standalone feed. Tradevo Data is annual-only (10-K plus 10-K/A) for now, US-only, and delivered as JSON/CSV — quarterly and Parquet are roadmap items, not shipped features.&lt;/p&gt;

&lt;h2&gt;
  
  
  When to Build It Yourself from EDGAR
&lt;/h2&gt;

&lt;p&gt;EDGAR's raw filings and XBRL data are free and public. If you only need a handful of tickers, have engineering time to spare, and want full control over parsing logic, building your own extractor is a legitimate option. The tradeoffs to budget for: XBRL taxonomy changes across years, handling 10-K/A amendments correctly so you don't silently overwrite original_value with latest_value, and building the first_filed logic so your backtest can't see a number before it existed. None of this is exotic, but it's also not a weekend project if you want it PIT-correct rather than just "mostly right."&lt;/p&gt;

&lt;h2&gt;
  
  
  Where Tradevo Data Fits
&lt;/h2&gt;

&lt;p&gt;Tradevo Data exists for the middle case: you want SEC-sourced, point-in-time-correct annual fundamentals — Revenue, NetIncome, Assets, StockholdersEquity, OperatingCashFlow, EPSDiluted, DilutedShares, up to 12 fiscal years — across 5,193 US companies (313,001 rows), without building the EDGAR pipeline yourself or paying for a full institutional-grade platform. The $49/mo plan includes the full bulk &lt;code&gt;/v1/download&lt;/code&gt;, the whole-universe &lt;code&gt;/v1/snapshot?as_of&lt;/code&gt; cross-section, and the &lt;code&gt;/v1/fundamentals?ticker&amp;amp;as_of&lt;/code&gt; JSON endpoint (5,000 requests/day), with a Stripe checkout that issues a key instantly. Cancel anytime. Docs are at /docs.&lt;/p&gt;

&lt;p&gt;This is a budget tier of research-grade PIT data — not a claim to be the only cheap option, and not a promise about what it will do for your returns.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try Before You Subscribe
&lt;/h2&gt;

&lt;p&gt;The free sample — 40 companies, 3,280 rows, full methodology, no signup — is on GitHub: github.com/christianpichichero-max/pit-fundamentals. It's the same schema, same first_filed/original_value/restated logic, just smaller. If the filing-deadline mechanics above are new to you, the deeper walkthroughs are at /blog/lookahead-bias-fundamental-backtests and /blog/point-in-time-fundamentals-data.&lt;/p&gt;

&lt;p&gt;If the schema fits your backtests, the full dataset is at tradevodata.com/?ref=blog. If you're still comparing PIT vendors, tradevodata.com/?ref=alt lays out the options side by side.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Not investment advice; verify competitor pricing yourself.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>quant</category>
      <category>python</category>
      <category>finance</category>
      <category>datascience</category>
    </item>
    <item>
      <title>We Shipped a 10x Error on General Mills. Here's the Bug, and the Check That Would Have Caught It.</title>
      <dc:creator>Christian Pichichero</dc:creator>
      <pubDate>Tue, 04 Aug 2026 19:38:46 +0000</pubDate>
      <link>https://dev.to/tradevodata/we-shipped-a-10x-error-on-general-mills-heres-the-bug-and-the-check-that-would-have-caught-it-5gml</link>
      <guid>https://dev.to/tradevodata/we-shipped-a-10x-error-on-general-mills-heres-the-bug-and-the-check-that-would-have-caught-it-5gml</guid>
      <description>&lt;p&gt;On 2026-08-03 our API was serving General Mills' FY2024 revenue as &lt;strong&gt;$2.038 billion&lt;/strong&gt;. The real figure is &lt;strong&gt;$19.857 billion&lt;/strong&gt;. We were wrong by an order of magnitude on an S&amp;amp;P 500 company, for weeks, and every automated check we had said the data was fine.&lt;/p&gt;

&lt;p&gt;This is the post-mortem. It is worth reading if you build anything on SEC XBRL, because the root cause is not exotic — it is the most natural way to write the code, and it is wrong.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the filing actually says
&lt;/h2&gt;

&lt;p&gt;General Mills tags two different numbers in the same 10-K for the same period:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;XBRL element&lt;/th&gt;
&lt;th&gt;FY2024 value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Revenues&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;$2,037,800,000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;RevenueFromContractWithCustomerExcludingAssessedTax&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;$19,857,000,000&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Both come from SEC's own &lt;code&gt;companyconcept&lt;/code&gt; API. Neither is a mistake by the filer. &lt;code&gt;Revenues&lt;/code&gt; here is a component; the consolidated top line lives under the ASC-606 element.&lt;/p&gt;

&lt;p&gt;We picked &lt;code&gt;Revenues&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why we picked the wrong one, and why it was defensible
&lt;/h2&gt;

&lt;p&gt;Our tag resolution used a static priority list, with &lt;code&gt;Revenues&lt;/code&gt; ranked first. That ordering was itself a fix. Earlier, we had ASC-606 contract revenue ranked above &lt;code&gt;Revenues&lt;/code&gt;, and it produced this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;MetLife FY2024&lt;/strong&gt; — served at $2.2B against a true $71.0B&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Capital One&lt;/strong&gt; — 85% low&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AvalonBay&lt;/strong&gt; — 99.8% low, for eight consecutive years&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For insurers, REITs and card issuers, contract revenue is a small slice and &lt;code&gt;Revenues&lt;/code&gt; is the real total. So we promoted &lt;code&gt;Revenues&lt;/code&gt;, verified MetLife came out right, and shipped.&lt;/p&gt;

&lt;p&gt;That fix was correct for MetLife and catastrophically wrong for General Mills — because &lt;strong&gt;there is no single XBRL element that is the consolidated top line for every filer&lt;/strong&gt;. &lt;code&gt;Revenues&lt;/code&gt; is the total for an insurer and a minor component for a consumer-goods company. Any fixed ordering serves one and betrays the other.&lt;/p&gt;

&lt;p&gt;We had traded one class of error for another, and we could not see it, because we validated the fix against the company that motivated it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why nothing caught it
&lt;/h2&gt;

&lt;p&gt;This is the part worth generalising. We had four automated gates:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a claims check comparing published site copy to the database&lt;/li&gt;
&lt;li&gt;a minimum-row-count guard against truncated builds&lt;/li&gt;
&lt;li&gt;endpoint smoke tests&lt;/li&gt;
&lt;li&gt;an invariant scan for internal consistency&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Every one of them asks &lt;em&gt;"is this data consistent with itself?"&lt;/em&gt; None asks &lt;em&gt;"is this number correct?"&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;$2.038 billion is internally consistent. It is correctly dated, plausibly sized for a public company, sourced from a real XBRL element in a real filing, and it passes every range check. It looks exactly like money. There is no internal signal that separates it from the right answer.&lt;/p&gt;

&lt;p&gt;We even had a flag firing. The row carried &lt;code&gt;qa_status = FLAG:ambiguous_tag&lt;/code&gt;, which our engine sets when another element in the same filing reports materially more. &lt;strong&gt;The system detected the ambiguity and published the smaller number anyway.&lt;/strong&gt; Detecting is not deciding.&lt;/p&gt;

&lt;h2&gt;
  
  
  The second bug, which was worse
&lt;/h2&gt;

&lt;p&gt;While fixing the first, we found that our bank-revenue path had been silently broken for months.&lt;/p&gt;

&lt;p&gt;Banks frequently tag no single total-revenue element, so we synthesise one: net interest income plus noninterest income, the standard definition. That code computed Fifth Third's FY2023 revenue correctly at &lt;strong&gt;$8.708B&lt;/strong&gt;. Then it stamped the value with the wrong filing date — &lt;code&gt;2026-02-24&lt;/code&gt; instead of &lt;code&gt;2024-02-27&lt;/code&gt; — because of this:&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;nii&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;end&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;annual_points_for_tag&lt;/span&gt;&lt;span class="p"&gt;(...)}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A dict comprehension keeps the &lt;strong&gt;last&lt;/strong&gt; entry per key. SEC reports the same period across many filings, so the last one is the most recent re-filing. The synthetic bank total therefore always looked like it was filed years late.&lt;/p&gt;

&lt;p&gt;Our selection logic picks the &lt;strong&gt;earliest-filed&lt;/strong&gt; candidate, on purpose — that is the point-in-time discipline. So the correct bank total always arrived "late" and lost to the fee-income tag it existed to replace. &lt;strong&gt;The fix computed the right answer and threw it away, every single time.&lt;/strong&gt; Fifth Third kept publishing $0.577B against a true $8.708B.&lt;/p&gt;

&lt;p&gt;One line, and it silently reversed the outcome of a fix everyone believed had shipped.&lt;/p&gt;

&lt;h2&gt;
  
  
  The check that would have caught both
&lt;/h2&gt;

&lt;p&gt;The missing piece was not more tests. It was one test of a different &lt;em&gt;kind&lt;/em&gt;: compare the data to something outside itself.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;node scripts/verify-against-sec.mjs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For a sample of served values, it asks SEC's &lt;code&gt;companyconcept&lt;/code&gt; API for every candidate revenue element the filer reported for that exact period, and asserts that what we serve is the consolidated total rather than a component of it. It deliberately does &lt;strong&gt;not&lt;/strong&gt; reuse our tag-selection logic — otherwise it would reimplement the bug and agree with itself.&lt;/p&gt;

&lt;p&gt;Run against the broken data, it printed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;WRONG GIS FY2024: serving $2.038B vs SEC $19.857B — 10.3% of the total
WRONG GIS FY2023: serving $1.957B vs SEC $20.094B —  9.7% of the total
WRONG PG  FY2014: serving $29.400B vs SEC $83.062B — 35.4% of the total
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It now runs on every data refresh and fails the build. A gate that cannot catch its own motivating case is decoration, so we proved it fired before shipping it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The actual fix
&lt;/h2&gt;

&lt;p&gt;We stopped ranking elements and started comparing them. Every tag in our candidate list is &lt;em&gt;intended&lt;/em&gt; to be a consolidated total — the gross-overstating ones are explicitly excluded — so when two candidates in the same filing disagree materially, the larger is the total and the smaller is a component of it. That rule gets MetLife and General Mills right simultaneously, which no fixed ordering can.&lt;/p&gt;

&lt;p&gt;The regression test keeps them in the same file, deliberately, because they pull in opposite directions.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this cost, and what changed
&lt;/h2&gt;

&lt;p&gt;Wrong values reached the paid API and the free sample. P&amp;amp;G FY2014 shipped at $29.4B against a true $83.1B in the public CC0 file — the one our own pricing page tells prospects to verify against.&lt;/p&gt;

&lt;p&gt;All of it is corrected now: General Mills at $19.857B, P&amp;amp;G at $83.062B, Fifth Third at $8.708B, M&amp;amp;T at $9.279B, MetLife still right at $70.986B. Roughly 320 values across all seven concepts have since been checked against SEC's API with zero mismatches.&lt;/p&gt;

&lt;p&gt;Three things we would tell anyone building on XBRL:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;There is no universal top-line element.&lt;/strong&gt; Any static tag priority is wrong for some filer. Compare magnitudes within the filing instead of trusting an order.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Detecting an ambiguity is not resolving it.&lt;/strong&gt; We flagged the row and published it anyway. If your system knows something is uncertain, decide what to do about it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Self-consistency is not correctness.&lt;/strong&gt; Every gate we had compared the data to itself. Wrong numbers pass those effortlessly, because wrong numbers are usually well-formed. Something in your pipeline has to compare against the outside world.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;We would rather publish this than have you find it. The free sample is 40 large caps, {{sampleRows}} point-in-time rows, CC0, no signup — &lt;a href="https://github.com/christianpichichero-max/pit-fundamentals" rel="noopener noreferrer"&gt;github.com/christianpichichero-max/pit-fundamentals&lt;/a&gt; — and the methodology is public specifically so it can be attacked.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Not investment advice.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>quant</category>
      <category>python</category>
      <category>finance</category>
      <category>datascience</category>
    </item>
    <item>
      <title>As-Reported vs Restated Financial Data: Why the Difference Matters for Backtesting</title>
      <dc:creator>Christian Pichichero</dc:creator>
      <pubDate>Wed, 29 Jul 2026 16:54:14 +0000</pubDate>
      <link>https://dev.to/tradevodata/as-reported-vs-restated-financial-data-why-the-difference-matters-for-backtesting-1big</link>
      <guid>https://dev.to/tradevodata/as-reported-vs-restated-financial-data-why-the-difference-matters-for-backtesting-1big</guid>
      <description>&lt;p&gt;Every financial statement exists in at least two versions: the number a company first told the market, and whatever number ends up in today's database after amendments, reclassifications, and restatements. If you're building a fundamental backtest, confusing the two is one of the quieter ways to inflate a strategy's historical returns — quieter than survivorship bias, but just as real.&lt;/p&gt;

&lt;p&gt;This article works through what "as-reported" and "restated" actually mean, why a backtest that uses the wrong one is training on information that didn't exist yet, and how a point-in-time (PIT) dataset like Tradevo Data represents both so you can choose deliberately instead of by accident.&lt;/p&gt;

&lt;h2&gt;
  
  
  As-reported: what the market actually saw
&lt;/h2&gt;

&lt;p&gt;As-reported (also called original or first-filed) is the value disclosed in the filing that made it public — for us, a 10-K (or 10-K/A). It's stamped with a date: the moment that number entered the public record via SEC EDGAR. Before that date, no market participant could have known it. That's the entire premise of point-in-time data: every fact carries a timestamp for when it became knowable, not just what the fact eventually became.&lt;/p&gt;

&lt;h2&gt;
  
  
  Restated: what the number is today
&lt;/h2&gt;

&lt;p&gt;Restated (or latest) is the current, revised figure after any subsequent 10-K/A amendments or comparative restatements in later filings. Companies restate for real reasons — accounting errors, standard changes, reclassifications, M&amp;amp;A-driven reallocations. The restated number is often &lt;em&gt;more accurate&lt;/em&gt; as a historical record of what actually happened. It is not, however, what a trader or analyst could have known on the date the original filing hit the tape.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this becomes lookahead bias in a backtest
&lt;/h2&gt;

&lt;p&gt;A backtest that joins today's restated fundamentals to historical prices is implicitly assuming the market had access to information it didn't have yet. If a company later restates revenue upward and your backtest uses the restated figure as of the original filing date, any strategy conditioned on that revenue print will look smarter in hindsight than it could have been in real time. The effect is usually small per company, but restatements aren't rare — our dataset flags 18,772 restatements across 314,436 point-in-time rows, which suggests this is a systematic feature of financial data, not an edge case you can ignore.&lt;/p&gt;

&lt;p&gt;The fix is mechanical, not statistical: for any date you're backtesting against, use only the values that had a &lt;code&gt;first_filed&lt;/code&gt; date on or before that date, and use the value as it was first reported — not as it later became.&lt;/p&gt;

&lt;h2&gt;
  
  
  A concrete example
&lt;/h2&gt;

&lt;p&gt;Suppose a company files its original 10-K reporting revenue for fiscal year N. Some quarters later, it files a 10-K/A that revises that same fiscal year's revenue — the change exceeds the 0.5% same-tag threshold we use to flag a meaningful restatement (not just a rounding or presentation tweak).&lt;/p&gt;

&lt;p&gt;In Tradevo Data's schema, that single revenue fact for fiscal year N produces:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;first_filed&lt;/code&gt;: the date the original 10-K became public&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;original_value&lt;/code&gt;: the revenue as first reported — this is what you join against historical dates for a PIT-safe backtest&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;latest_value&lt;/code&gt;: the revenue as currently stated, after the 10-K/A&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;restated&lt;/code&gt;: &lt;code&gt;true&lt;/code&gt;, because the revision exceeded the 0.5% same-tag threshold&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;qa_status&lt;/code&gt;: our internal check on whether the row parsed and reconciled cleanly&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If your backtest logic is "as of date X, what did we know about this company's revenue," you filter to rows where &lt;code&gt;first_filed &amp;lt;= X&lt;/code&gt; and read &lt;code&gt;original_value&lt;/code&gt;. If your logic is "what actually happened to this company's revenue, full stop," you read &lt;code&gt;latest_value&lt;/code&gt;. Same row, two different questions, two different answers — and picking the wrong one for the wrong question is exactly the bug PIT data exists to prevent.&lt;/p&gt;

&lt;h2&gt;
  
  
  As-reported vs restated at a glance
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;As-reported (&lt;code&gt;original_value&lt;/code&gt;)&lt;/th&gt;
&lt;th&gt;Restated (&lt;code&gt;latest_value&lt;/code&gt;)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Answers the question&lt;/td&gt;
&lt;td&gt;What did the market know on this date?&lt;/td&gt;
&lt;td&gt;What is the corrected historical fact?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Correct use&lt;/td&gt;
&lt;td&gt;Backtesting, PIT research, signal generation&lt;/td&gt;
&lt;td&gt;Fundamental analysis of "true" historical performance, post-mortems&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Risk if misused&lt;/td&gt;
&lt;td&gt;None if used correctly for its purpose&lt;/td&gt;
&lt;td&gt;Lookahead bias if fed into a historical backtest&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Changes over time&lt;/td&gt;
&lt;td&gt;No — frozen at first filing&lt;/td&gt;
&lt;td&gt;Yes — updated as amendments are filed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Where it lives in our schema&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;original_value&lt;/code&gt; + &lt;code&gt;first_filed&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;latest_value&lt;/code&gt; + &lt;code&gt;restated&lt;/code&gt; flag&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  When restated data is genuinely the better choice
&lt;/h2&gt;

&lt;p&gt;This isn't a case for always preferring as-reported. If you're doing retrospective fundamental analysis — "how did this company's margins actually trend over a decade" — the restated figure is usually the more honest answer, since it reflects corrected accounting rather than a since-fixed error. Restated data is also the right call for training long-horizon valuation models where you're not simulating point-in-time decisions, or for auditing how much a company's numbers have moved since first disclosure. The mistake isn't using restated data; it's using it inside a backtest that pretends to be historical.&lt;/p&gt;

&lt;h2&gt;
  
  
  When to build it yourself — or use someone else's dataset
&lt;/h2&gt;

&lt;p&gt;Building a PIT pipeline from raw EDGAR filings is entirely doable, and for some teams it's the right call: you get full control over parsing logic, tag mapping, and restatement detection thresholds, and you're not dependent on anyone's refresh schedule. The tradeoffs are real, though — reconciling XBRL tags across amendments, tracking every 10-K/A, and validating first-filed dates against actual EDGAR timestamps is unglamorous, ongoing work, and it's easy to introduce silent bugs (like accidentally picking up a restated value) that only surface when your backtest results look suspiciously good.&lt;/p&gt;

&lt;p&gt;If you need quarterly data, non-US markets, or Parquet natively today, other providers are worth evaluating — Sharadar, Tiingo, and QuantConnect all offer credible fundamentals products; see their pricing pages directly, since we won't quote competitor prices here and terms change. Tradevo Data is annual-only (10-K/10-K/A), US-only, and CSV-first (Parquet is on the roadmap), so if your use case needs more than that today, one of those may be the better fit right now.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where Tradevo Data fits
&lt;/h2&gt;

&lt;p&gt;What we do offer: 5,216 US companies, 314,436 point-in-time rows across 7 core concepts, up to 12 years of history, with every value carrying &lt;code&gt;first_filed&lt;/code&gt;, &lt;code&gt;original_value&lt;/code&gt;, &lt;code&gt;latest_value&lt;/code&gt;, and a &lt;code&gt;restated&lt;/code&gt; flag so you never have to guess which one you're looking at. On our 40-company free sample, the measured lookahead between a value becoming public and being usable averaged 43.4 days (max 61 days) on reliable-filing rows — a number we report scoped to that sample only, not the full dataset, because we haven't measured it universe-wide yet.&lt;/p&gt;

&lt;p&gt;The full methodology and 3,280 sample rows are free, no signup: &lt;a href="https://github.com/christianpichichero-max/pit-fundamentals" rel="noopener noreferrer"&gt;github.com/christianpichichero-max/pit-fundamentals&lt;/a&gt;. If it fits your workflow, the full dataset and API are $49/mo at &lt;a href="https://tradevodata.com/?ref=blog" rel="noopener noreferrer"&gt;tradevodata.com&lt;/a&gt; — bulk download and whole-universe snapshots included, cancel anytime. For more on how lookahead bias creeps into backtests, see &lt;a href="https://tradevodata.com/blog/lookahead-bias-fundamental-backtests" rel="noopener noreferrer"&gt;/blog/lookahead-bias-fundamental-backtests&lt;/a&gt; and &lt;a href="https://tradevodata.com/blog/point-in-time-fundamentals-data" rel="noopener noreferrer"&gt;/blog/point-in-time-fundamentals-data&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Not investment advice; verify competitor pricing yourself on their respective sites.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>quant</category>
      <category>python</category>
      <category>finance</category>
      <category>datascience</category>
    </item>
    <item>
      <title>How to Detect Lookahead Bias in a Backtest: A Practical Checklist</title>
      <dc:creator>Christian Pichichero</dc:creator>
      <pubDate>Wed, 08 Jul 2026 17:03:29 +0000</pubDate>
      <link>https://dev.to/tradevodata/how-to-detect-lookahead-bias-in-a-backtest-a-practical-checklist-2cb6</link>
      <guid>https://dev.to/tradevodata/how-to-detect-lookahead-bias-in-a-backtest-a-practical-checklist-2cb6</guid>
      <description>&lt;p&gt;Lookahead bias in a fundamentals backtest almost never announces itself. It shows up as a backtest that looks unusually good, and then a live strategy that quietly stops working. The mechanism is boring: your join used a date the data wasn't actually public yet. This article is a checklist for finding that, not a pitch for a particular dataset — though we'll use our own free sample to show working code, because it's the one we can show you column-by-column without asking anything of you.&lt;/p&gt;

&lt;p&gt;This is not investment advice. Nothing here promises a better-performing backtest — only a more honest one, with fewer joins that leak future information into the past.&lt;/p&gt;

&lt;h2&gt;
  
  
  Symptom 1: Your signal times earnings suspiciously well
&lt;/h2&gt;

&lt;p&gt;If a fundamentals-driven signal enters positions right before earnings-driven price moves with better-than-random timing, check what date you used to decide the fundamental was "known." A common bug: joining on fiscal period end date (e.g., Q4 2019 = 2019-12-31) instead of the date the 10-K was actually filed and made public. Every company reports its fiscal year end weeks to months before the filing exists. If your backtest thinks Q4 data is available on the period end date, it's trading on the future.&lt;/p&gt;

&lt;h2&gt;
  
  
  Symptom 2: Live performance decays right after paper trading ends
&lt;/h2&gt;

&lt;p&gt;This is the classic tell. A strategy backtests clean, goes live (or into a genuinely walk-forward paper test), and the edge shrinks or disappears. If the strategy relies on fundamentals and nothing else changed — no regime shift, no capacity issue — lookahead bias in the historical joins is the first thing to rule out, because live trading is the one environment where lookahead is structurally impossible.&lt;/p&gt;

&lt;h2&gt;
  
  
  Symptom 3: Performance is fragile to a few outlier dates
&lt;/h2&gt;

&lt;p&gt;Run your equity curve breakdown by trade. If a disproportionate share of PnL comes from trades placed in a narrow window around known reporting seasons, and those specific trades have unusually strong hit rates, look at whether the fundamental value used in the join was restated later and your backtest is silently using the &lt;em&gt;restated&lt;/em&gt; number instead of what was originally reported.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Join-Date Audit
&lt;/h2&gt;

&lt;p&gt;The fastest way to check: pull your actual join logic and ask, for every fundamental value, "what date did my code think this became true?" Then compare that to the date it was actually filed. Two dates get confused constantly:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Fiscal period end&lt;/strong&gt; — when the reporting period closed (always in the past relative to filing)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;First filed date&lt;/strong&gt; — when the 10-K (or 10-K/A) hit EDGAR and became public&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If your pipeline uses the former where it should use the latter, you have a lookahead bug baked into every single row, sized to the actual filing lag for that company and quarter.&lt;/p&gt;

&lt;h3&gt;
  
  
  A pandas check using first_filed
&lt;/h3&gt;

&lt;p&gt;Our &lt;a href="https://github.com/christianpichichero-max/pit-fundamentals" rel="noopener noreferrer"&gt;free PIT sample&lt;/a&gt; (40 companies, 3,212 rows, no signup) includes a &lt;code&gt;first_filed&lt;/code&gt; column specifically so you can run this check yourself. Example:&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="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;pandas&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;

&lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read_csv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sample.csv&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;parse_dates&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;first_filed&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;fiscal_period_end&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;check_leakage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;as_of_date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;join_col&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;fiscal_period_end&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;as_of&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Timestamp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;as_of_date&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;naive_join&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;join_col&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;as_of&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;        &lt;span class="c1"&gt;# what a naive backtest would include
&lt;/span&gt;    &lt;span class="n"&gt;correct_join&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;first_filed&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;as_of&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;  &lt;span class="c1"&gt;# what was actually public by as_of
&lt;/span&gt;    &lt;span class="n"&gt;leaked&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;naive_join&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;~&lt;/span&gt;&lt;span class="n"&gt;naive_join&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;isin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;correct_join&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;leaked&lt;/span&gt;

&lt;span class="n"&gt;leaked_rows&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;check_leakage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2020-03-31&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;leaked_rows&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; of &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; rows would be lookahead-leaked &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
      &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;if you joined on fiscal_period_end instead of first_filed&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If &lt;code&gt;leaked_rows&lt;/code&gt; is non-empty for a realistic &lt;code&gt;as_of&lt;/code&gt; date, your naive join is including fundamentals before they existed publicly. Swap the join key to &lt;code&gt;first_filed&lt;/code&gt; (or whatever your data vendor's equivalent "became public" field is called) and rerun.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Lag-Shift Test
&lt;/h2&gt;

&lt;p&gt;A second, independent check that doesn't require inspecting your join code line by line: shift your &lt;code&gt;as_of&lt;/code&gt; date backward by a fixed number of days across your entire backtest — say 30, 60, 90 days — and rerun. If performance is fairly stable under small shifts, that's a decent sign your logic isn't sitting right on a knife's edge of leaked information. If performance craters the moment you shift even slightly earlier, your original result was likely dependent on data being available earlier than it should have been. This is a blunt instrument, not proof, but it's cheap to run and catches a lot of accidental lookahead.&lt;/p&gt;

&lt;p&gt;For deeper background on why this matters mechanically, see &lt;a href="https://dev.to/blog/lookahead-bias-fundamental-backtests"&gt;lookahead bias in fundamental backtests&lt;/a&gt; and our overview of &lt;a href="https://dev.to/blog/point-in-time-fundamentals-data"&gt;point-in-time fundamentals&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Comparing Your Options for Fixing It
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Approach&lt;/th&gt;
&lt;th&gt;Effort&lt;/th&gt;
&lt;th&gt;Coverage&lt;/th&gt;
&lt;th&gt;Cost&lt;/th&gt;
&lt;th&gt;Best for&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Scrape EDGAR yourself, track filing dates&lt;/td&gt;
&lt;td&gt;High&lt;/td&gt;
&lt;td&gt;Whatever you build&lt;/td&gt;
&lt;td&gt;Your time&lt;/td&gt;
&lt;td&gt;Teams needing full control, custom concepts, or non-US data&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Research-grade PIT vendors (e.g. Sharadar, Tiingo, QuantConnect)&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;td&gt;Broad, often quarterly + more concepts&lt;/td&gt;
&lt;td&gt;See their pricing pages&lt;/td&gt;
&lt;td&gt;Funds/teams needing quarterly data, longer history, or bulk delivery — this is genuinely where they win over us&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tradevo Data&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;td&gt;5,214 US companies, 7 concepts, annual only, up to 12 fiscal years&lt;/td&gt;
&lt;td&gt;$49/mo&lt;/td&gt;
&lt;td&gt;Individual quants who need PIT-safe &lt;em&gt;annual&lt;/em&gt; US fundamentals cheaply, via API&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Free PIT sample (GitHub)&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;td&gt;40 companies, 3,212 rows&lt;/td&gt;
&lt;td&gt;Free&lt;/td&gt;
&lt;td&gt;Prototyping the join logic and running the checks in this article before paying for anything&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;We're not the only affordable option, and we're not claiming to be — the vendors above are credible and some cover more ground (quarterly data, longer history, bulk delivery). See their pricing pages directly since we won't quote numbers we don't control.&lt;/p&gt;

&lt;h2&gt;
  
  
  When to Build It Yourself (or Use Someone Else)
&lt;/h2&gt;

&lt;p&gt;Being honest about where Tradevo Data isn't the right tool — and where the vendors above genuinely win:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;You need quarterly fundamentals.&lt;/strong&gt; We're annual-only (10-K + 10-K/A). Quarterly PIT logic is a roadmap item, not shipped. A vendor with quarterly coverage wins here.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You need non-US equities.&lt;/strong&gt; We're US-only, sourced from SEC EDGAR. A vendor with international coverage wins here.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You need more than 7 concepts&lt;/strong&gt;, or line items beyond Revenue, NetIncome, Assets, StockholdersEquity, OperatingCashFlow, EPSDiluted, and DilutedShares.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You need bulk Parquet delivery&lt;/strong&gt; for a data lake — we're a single JSON endpoint (&lt;code&gt;/v1/fundamentals&lt;/code&gt;), rate-limited to 5,000 requests/day, which is fine for iterative backtesting but not for a nightly full-universe dump.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You're already comfortable parsing EDGAR filings directly&lt;/strong&gt; and tracking &lt;code&gt;first_filed&lt;/code&gt; yourself — that's a legitimate, free path if you have the engineering time, and it's exactly what our free sample's methodology documents.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If none of those apply and you just want a point-in-time-safe &lt;code&gt;first_filed&lt;/code&gt;/&lt;code&gt;original_value&lt;/code&gt; pair for US annual fundamentals without building the EDGAR pipeline yourself, that's the actual use case we built for.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try It Before You Pay for Anything
&lt;/h2&gt;

&lt;p&gt;Start with the &lt;a href="https://github.com/christianpichichero-max/pit-fundamentals" rel="noopener noreferrer"&gt;free sample&lt;/a&gt; — 40 companies, full methodology, no signup — and run the join-date audit and lag-shift test above on your own logic. If it holds up and you want the full 5,214-company, 313,562-row dataset with &lt;code&gt;first_filed&lt;/code&gt;, &lt;code&gt;original_value&lt;/code&gt;, &lt;code&gt;latest_value&lt;/code&gt;, and restatement flags via API, it's &lt;a href="https://tradevodata.com/?ref=blog" rel="noopener noreferrer"&gt;$49/mo at Tradevo Data&lt;/a&gt;, instant key after checkout, cancel anytime, docs at &lt;code&gt;/docs&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If you're actively comparing PIT data providers, our &lt;a href="https://tradevodata.com/?ref=alt" rel="noopener noreferrer"&gt;alternatives page&lt;/a&gt; lays out where we fit and where we don't.&lt;/p&gt;




&lt;p&gt;Not investment advice; verify competitor pricing yourself on their own pricing pages.&lt;/p&gt;

</description>
      <category>quant</category>
      <category>python</category>
      <category>finance</category>
      <category>datascience</category>
    </item>
    <item>
      <title>Survivorship Bias vs Lookahead Bias: The Two Silent Backtest Killers</title>
      <dc:creator>Christian Pichichero</dc:creator>
      <pubDate>Mon, 06 Jul 2026 14:46:57 +0000</pubDate>
      <link>https://dev.to/tradevodata/survivorship-bias-vs-lookahead-bias-the-two-silent-backtest-killers-pmm</link>
      <guid>https://dev.to/tradevodata/survivorship-bias-vs-lookahead-bias-the-two-silent-backtest-killers-pmm</guid>
      <description>&lt;p&gt;Most blown-up backtests aren't wrecked by a bad strategy idea. They're wrecked by two data problems that quietly inflate returns: &lt;strong&gt;survivorship bias&lt;/strong&gt; and &lt;strong&gt;lookahead bias&lt;/strong&gt;. They're often mentioned in the same breath, which is a mistake — they come from different places, they fail differently, and one of them is much easier to catch than the other.&lt;/p&gt;

&lt;p&gt;This article defines both, explains why lookahead bias is the sneakier of the two, walks through how ordinary fundamentals data causes each one, and lays out what actually fixes them (spoiler: it's not one thing).&lt;/p&gt;

&lt;h2&gt;
  
  
  Survivorship bias, in one sentence
&lt;/h2&gt;

&lt;p&gt;Your backtest only includes companies that still exist today, so every failure, delisting, and bankruptcy is invisible — and your historical universe silently gets better with hindsight than it actually was at the time.&lt;/p&gt;

&lt;p&gt;It's the classic example: you backtest a value strategy on "all S&amp;amp;P 500 companies" using today's constituent list, applied to 2005 data. Every company that got delisted, acquired, or dropped from the index between 2005 and today never shows up in your test. Your strategy looks great because it only ever traded winners-in-hindsight.&lt;/p&gt;

&lt;p&gt;Survivorship bias is loud once you know to look for it. If your universe is built from a current list of tickers and applied to past dates, you have it. The fix is mechanical: use a point-in-time universe (constituents as they were, on each date, including the companies that later failed).&lt;/p&gt;

&lt;h2&gt;
  
  
  Lookahead bias, in one sentence
&lt;/h2&gt;

&lt;p&gt;Your backtest uses a data value on a date before that value was actually knowable, so the strategy is trading on information from the future.&lt;/p&gt;

&lt;p&gt;This is the sneaky one. It rarely announces itself. It hides inside something completely mundane: a merge, a join, a &lt;code&gt;groupby().last()&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why lookahead bias is sneakier
&lt;/h2&gt;

&lt;p&gt;Survivorship bias is a universe problem — you either have delisted names in your data or you don't, and that's checkable with a row count. Lookahead bias is a &lt;strong&gt;timing&lt;/strong&gt; problem buried inside data that otherwise looks perfectly normal.&lt;/p&gt;

&lt;p&gt;Here's the mechanism most people never notice: financial databases typically store one row per fiscal period, keyed by &lt;em&gt;fiscal period end date&lt;/em&gt; (e.g., "FY2022, ended 2022-12-31") — not by the date the number was actually disclosed. A 10-K for fiscal year 2022 isn't filed on December 31, 2022. It's filed weeks or months later, in early-to-mid 2023. If your backtest joins fundamentals to prices using the fiscal period end date as the effective date, you're handing your strategy Q4 2022 revenue on December 31, 2022 — months before any investor could have known it.&lt;/p&gt;

&lt;p&gt;The merge runs without errors. The output looks like a normal dataframe. Nothing crashes. Numbers just quietly get used before they were public, because the strategy is trading on data from the future relative to the date you think it's trading on. That's why lookahead bias survives code review, survives peer review, and often survives into live trading before it's caught — because catching it requires knowing to ask "on what date was this number actually public?", not just "is this number correct?"&lt;/p&gt;

&lt;p&gt;Restatements make it worse. Companies revise prior-period numbers (that's the &lt;code&gt;restated&lt;/code&gt; flag you'll see in decent PIT datasets, and amendments — 10-K/As — are a normal part of that). If your data only stores the latest, current value, you're not just early, you're not even testing the number the market originally saw.&lt;/p&gt;

&lt;h2&gt;
  
  
  How ordinary fundamentals data causes both, side by side
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Survivorship bias&lt;/th&gt;
&lt;th&gt;Lookahead bias&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Root cause&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Universe built from current tickers, not historical ones&lt;/td&gt;
&lt;td&gt;Data keyed by fiscal period end, not filing/disclosure date&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;What's missing&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Delisted, acquired, bankrupt companies&lt;/td&gt;
&lt;td&gt;The gap between period end and public filing date&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Where it hides&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The universe list itself&lt;/td&gt;
&lt;td&gt;A join/merge on &lt;code&gt;date&lt;/code&gt;, invisible in the code&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;How obvious is it&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Fairly obvious once you check for delistings&lt;/td&gt;
&lt;td&gt;Not obvious — no error, no crash, silently optimistic&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Typical inflation&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Overstates returns by excluding losers&lt;/td&gt;
&lt;td&gt;Overstates returns by using future information&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Fix&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Point-in-time universe / constituent history&lt;/td&gt;
&lt;td&gt;Point-in-time fundamentals, filtered by first-known date&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Also affected by restatements?&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Not directly&lt;/td&gt;
&lt;td&gt;Yes — original vs. latest values matter&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  The actual fix: PIT data + a stable universe
&lt;/h2&gt;

&lt;p&gt;There isn't one fix for both, because they're two different bugs:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;For survivorship bias&lt;/strong&gt;: build or buy a universe that includes delisted/failed companies as of each historical date, not a snapshot of today's list applied backward.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;For lookahead bias&lt;/strong&gt;: use fundamentals filtered by the date a value first became public (&lt;code&gt;first_filed &amp;lt;= as_of&lt;/code&gt;), not the fiscal period end date. And decide deliberately whether you're testing against the &lt;code&gt;original_value&lt;/code&gt; (what the market actually saw first) or the &lt;code&gt;latest_value&lt;/code&gt; (post-restatement), because those are two different questions.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Do both, and the numbers your backtest trades on are the numbers an investor could actually have had, on the date the strategy would have acted. We go deeper on the mechanics of the merge bug specifically in &lt;a href="https://tradevodata.com/blog/lookahead-bias-fundamental-backtests" rel="noopener noreferrer"&gt;Lookahead Bias in Fundamental Backtests&lt;/a&gt;, and on what point-in-time data actually looks like row-by-row in &lt;a href="https://tradevodata.com/blog/point-in-time-fundamentals-data" rel="noopener noreferrer"&gt;Point-in-Time Fundamentals, Explained&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;On our own 40-company free sample, the measured gap between fiscal period end and first public filing averaged 43.4 days and reached up to 61 days on the reliable-filing rows — scoped to that sample only, not a claim about the broader dataset. That gap is exactly the window lookahead bias exploits.&lt;/p&gt;

&lt;h2&gt;
  
  
  When the other option is the better choice
&lt;/h2&gt;

&lt;p&gt;Being honest about scope matters more than winning the comparison. A few cases where you should look elsewhere:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;You need quarterly fundamentals.&lt;/strong&gt; Tradevo Data is annual-only (10-K/10-K/A) right now; quarterly is on the roadmap, not shipped. If your strategy needs 10-Q cadence today, that's a real gap.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You need international coverage.&lt;/strong&gt; This is US-only, sourced from SEC EDGAR. Non-US equities need a different provider entirely.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You need bulk Parquet files or very high request volume.&lt;/strong&gt; The API is JSON, rate-limited to 5,000 requests/day. If you're doing large-scale offline research across the full universe repeatedly, a bulk-file provider fits better.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You need decades of history or more concepts.&lt;/strong&gt; This dataset covers up to 12 fiscal years and 7 core concepts. Established research-grade vendors like Sharadar, Tiingo, or QuantConnect may offer more concepts, longer history, or broader asset-class coverage depending on the plan — see their pricing pages directly, since we won't quote competitor prices here.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You'd rather build it yourself.&lt;/strong&gt; SEC EDGAR is public domain. If you have the engineering time, you can pull filings, extract first-filed dates, and track restatements yourself. It's genuinely doable — it's also exactly the plumbing work this dataset already did, which is the tradeoff to weigh against $49/month.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If none of those apply and you mainly need clean, point-in-time US equity fundamentals without the merge-bug risk, the free sample is the lowest-friction way to see the actual row structure: &lt;a href="https://github.com/christianpichichero-max/pit-fundamentals" rel="noopener noreferrer"&gt;github.com/christianpichichero-max/pit-fundamentals&lt;/a&gt; — 40 companies, 3,212 rows, full methodology, no signup. When you're ready for the full 5,214-company dataset, the API is at &lt;a href="https://tradevodata.com/?ref=blog" rel="noopener noreferrer"&gt;tradevodata.com&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;Not investment advice; verify competitor pricing yourself.&lt;/p&gt;

</description>
      <category>quant</category>
      <category>python</category>
      <category>finance</category>
      <category>datascience</category>
    </item>
    <item>
      <title>Point-in-Time Fundamentals Data: What It Is, Why It Matters, and How to Choose</title>
      <dc:creator>Christian Pichichero</dc:creator>
      <pubDate>Mon, 06 Jul 2026 14:46:27 +0000</pubDate>
      <link>https://dev.to/tradevodata/point-in-time-fundamentals-data-what-it-is-why-it-matters-and-how-to-choose-a70</link>
      <guid>https://dev.to/tradevodata/point-in-time-fundamentals-data-what-it-is-why-it-matters-and-how-to-choose-a70</guid>
      <description>&lt;p&gt;If you backtest strategies on company fundamentals — earnings, revenue, book value, cash flow — the single most expensive mistake you can make is testing on numbers that &lt;em&gt;weren't actually knowable&lt;/em&gt; on the date your model traded. That mistake has a name: lookahead bias. And the fix has a name too: &lt;strong&gt;point-in-time (PIT) fundamentals data&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This guide explains what PIT data actually is, why it matters, what separates a real PIT dataset from a marketing label, and an honest survey of your options — from free DIY tooling to five-figure institutional feeds. We build one of those options, and we'll say so plainly at the end, but the goal here is to help you choose the right tool, not to talk you into ours.&lt;/p&gt;

&lt;h2&gt;
  
  
  What "point-in-time" actually means
&lt;/h2&gt;

&lt;p&gt;A normal fundamentals table gives you one number per company per period — say, Apple's FY2019 revenue. The problem is &lt;em&gt;which&lt;/em&gt; version of that number you're looking at. Companies file a 10-K, then sometimes amend it (10-K/A), then restate prior years inside &lt;em&gt;later&lt;/em&gt; filings. A vanilla database silently overwrites the original with the newest revision. So when your 2020 backtest reads "2019 revenue," it may be reading a value that wasn't published until 2022.&lt;/p&gt;

&lt;p&gt;A point-in-time dataset instead answers a different question: &lt;strong&gt;"What was the value of this metric, as it was known on date X?"&lt;/strong&gt; To do that honestly, each data point has to carry two things a plain table throws away:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;A filing date&lt;/strong&gt; — the date the value first became public (the 10-K's filing timestamp, not the fiscal-period-end date).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The original, first-reported value&lt;/strong&gt; — preserved separately from any later revision.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;With those, a query engine can filter to &lt;em&gt;only the rows whose filing date is on or before your as-of date&lt;/em&gt; and hand back the number the market actually had. That's the whole game.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why it matters: lookahead bias and restatements
&lt;/h2&gt;

&lt;p&gt;Two distinct failure modes make non-PIT fundamentals dangerous for backtesting:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Reporting lag.&lt;/strong&gt; A fiscal year ends December 31, but the 10-K might not be filed until late February or March. If your backtest assumes the annual numbers were available on January 1, you've handed your model roughly two months of the future. Multiply that across every rebalance and the phantom edge can be large.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Restatements.&lt;/strong&gt; When a company restates, the revised figure often "improves" the historical picture in ways the market didn't know at the time. A model trained on restated data learns to trade on information that didn't exist yet. Restatements are common enough that ignoring them meaningfully distorts value and quality factors.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We go deep on the mechanics, worked examples, and how to audit your own pipeline in &lt;a href="https://dev.to/blog/lookahead-bias-fundamental-backtests"&gt;our companion article on lookahead bias in fundamental backtests&lt;/a&gt;. If you only read one follow-up, read that one.&lt;/p&gt;

&lt;h2&gt;
  
  
  What to look for in a PIT dataset
&lt;/h2&gt;

&lt;p&gt;Not every dataset that says "point-in-time" earns the label. Here's a practical checklist:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;A real filing-date stamp per row&lt;/strong&gt; — the date the value became public, not just the period-end date. Without this, "as-of" queries are impossible.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Original value preserved separately from the latest value&lt;/strong&gt; — so you can see both what was first reported and what it became, and a &lt;strong&gt;restated flag&lt;/strong&gt; so you can filter or study revisions instead of being silently fed them.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A server-side as-of query&lt;/strong&gt; — ideally you pass an &lt;code&gt;as_of&lt;/code&gt; date and the API does the filing-date filtering for you, rather than you reconstructing it and hoping you got the boundary conditions right.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Survivorship handling&lt;/strong&gt; — for cross-sectional backtests you also need delisted and dead companies present in the universe, or your results are biased toward winners. (Note: fundamentals coverage and a survivorship-free &lt;em&gt;price/universe&lt;/em&gt; file are separate concerns — check both.)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Transparent QA&lt;/strong&gt; — you want to know when a value is flagged or uncertain, not have it quietly dropped.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  An honest survey of your options
&lt;/h2&gt;

&lt;p&gt;There's no single right answer here — the right tool depends on your budget, coverage needs, and how much plumbing you want to own. Pricing below is directional and drifts constantly, so treat every number as "verify on their page," linked.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Option&lt;/th&gt;
&lt;th&gt;Rough cost (as of July 2026)&lt;/th&gt;
&lt;th&gt;PIT capability&lt;/th&gt;
&lt;th&gt;Best for&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;DIY from SEC EDGAR&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Free (your time + infra)&lt;/td&gt;
&lt;td&gt;Full, if you build it&lt;/td&gt;
&lt;td&gt;Teams who want total control and have engineering time&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;Budget PIT APIs&lt;/strong&gt; (incl. us)&lt;/td&gt;
&lt;td&gt;~$0–$50/mo tier&lt;/td&gt;
&lt;td&gt;Varies — check for filing dates + original values&lt;/td&gt;
&lt;td&gt;Individuals and small shops proving an idea&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;a href="https://sharadar.com/" rel="noopener noreferrer"&gt;Sharadar&lt;/a&gt; SF1&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Subscription — see &lt;a href="https://data.nasdaq.com/databases/SF1" rel="noopener noreferrer"&gt;pricing&lt;/a&gt;
&lt;/td&gt;
&lt;td&gt;Point-in-time (datekey-based)&lt;/td&gt;
&lt;td&gt;Retail quants wanting broad coverage + ratios&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;a href="https://www.tiingo.com/about/pricing" rel="noopener noreferrer"&gt;Tiingo&lt;/a&gt; fundamentals&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Add-on — &lt;a href="https://www.tiingo.com/about/pricing" rel="noopener noreferrer"&gt;contact/pricing&lt;/a&gt;
&lt;/td&gt;
&lt;td&gt;As-reported dimension available; confirm filing-date detail&lt;/td&gt;
&lt;td&gt;Users already on Tiingo for prices&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;&lt;a href="https://www.spglobal.com/market-intelligence/en/solutions/products/fundamental-data" rel="noopener noreferrer"&gt;Compustat&lt;/a&gt; / &lt;a href="https://www.lseg.com/en/data-analytics/financial-data/company-data/fundamentals-data/standardized-fundamentals/sp-compustat-database" rel="noopener noreferrer"&gt;LSEG&lt;/a&gt;&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Institutional (commonly $10k–$50k+/yr)&lt;/td&gt;
&lt;td&gt;Deep PIT snapshots (Compustat PIT from 1987)&lt;/td&gt;
&lt;td&gt;Funds, academics via WRDS, anyone needing decades of history&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;DIY from EDGAR&lt;/strong&gt; is genuinely free — all US filings are public domain. You can parse XBRL, stamp filing dates yourself, and own the whole stack. The catch is that "parse XBRL correctly, dedupe amendments, and handle restatements" is weeks-to-months of unglamorous work, and quiet bugs there reintroduce the exact lookahead you were trying to remove.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Sharadar and Tiingo&lt;/strong&gt; are credible, established budget-to-mid options. Sharadar's fundamentals are point-in-time via its datekey structure and cover a broad US universe with many pre-computed ratios; Tiingo offers an as-reported dimension and pairs naturally with its price data. Confirm the specific PIT semantics you need against their current docs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Compustat and LSEG&lt;/strong&gt; are the institutional standard for a reason: decades of carefully curated PIT snapshots, global coverage, and support. They also cost accordingly and are usually accessed through an institution or WRDS.&lt;/p&gt;

&lt;h3&gt;
  
  
  Where we fit
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://tradevodata.com/?ref=blog" rel="noopener noreferrer"&gt;Tradevo Data&lt;/a&gt; is a deliberately narrow, honest &lt;strong&gt;budget tier&lt;/strong&gt; of research-grade PIT — one middle option among the credible ones above, not "the only cheap PIT." Concretely, today it is: point-in-time US &lt;strong&gt;annual&lt;/strong&gt; equity fundamentals sourced from SEC EDGAR (public domain), covering 5,214 companies and 313,562 PIT rows across 7 core concepts (Revenue, NetIncome, Assets, StockholdersEquity, OperatingCashFlow, EPSDiluted, DilutedShares), up to 12 fiscal years.&lt;/p&gt;

&lt;p&gt;Every row carries &lt;code&gt;first_filed&lt;/code&gt; (when it became public), &lt;code&gt;original_value&lt;/code&gt; (first-reported, PIT-safe), &lt;code&gt;latest_value&lt;/code&gt; (current revision), a &lt;code&gt;restated&lt;/code&gt; flag, and a per-row &lt;code&gt;qa_status&lt;/code&gt; — flagged, never silently hidden. There are 18,539 labeled restatements in the set. Delivery is one JSON endpoint — &lt;code&gt;/v1/fundamentals?ticker&amp;amp;as_of[&amp;amp;concept]&lt;/code&gt; — with server-side as-of filtering (&lt;code&gt;first_filed &amp;lt;= as_of&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;Being equally plain about the limits: it's &lt;strong&gt;annual only&lt;/strong&gt; right now (no quarterly yet — on the roadmap), &lt;strong&gt;US only&lt;/strong&gt;, and there's &lt;strong&gt;no Parquet bulk download yet&lt;/strong&gt;. If you need quarterly data, decades of history, or global coverage today, one of the options above is a better fit, and you should use it.&lt;/p&gt;

&lt;h2&gt;
  
  
  When each option is the better choice
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;DIY EDGAR&lt;/strong&gt; — you have engineering time, want zero per-month cost, and value owning the pipeline over speed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sharadar&lt;/strong&gt; — you want broad US coverage plus ready-made ratios and a survivorship-free universe file, at a retail-quant price.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tiingo&lt;/strong&gt; — you're already using Tiingo for prices and want fundamentals in the same stack.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Compustat / LSEG&lt;/strong&gt; — you need decades of PIT history, global coverage, or institutional support, and have the budget.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tradevo Data&lt;/strong&gt; — you want US annual PIT with explicit original-vs-restated fields and a dead-simple as-of API, cheaply, to prove out an idea before committing to a bigger feed.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Try it without a signup
&lt;/h2&gt;

&lt;p&gt;If you just want to see what honest PIT rows look like, our &lt;strong&gt;free sample is on GitHub&lt;/strong&gt; — 40 large caps, 3,212 rows, full methodology, no email required: &lt;a href="https://github.com/christianpichichero-max/pit-fundamentals" rel="noopener noreferrer"&gt;github.com/christianpichichero-max/pit-fundamentals&lt;/a&gt;. Inspect the schema, run an as-of query against your own backtest, and decide for yourself.&lt;/p&gt;

&lt;p&gt;If it fits and you want the full universe, the API is &lt;strong&gt;$49/mo&lt;/strong&gt;, 5,000 requests/day, key issued instantly, cancel anytime — &lt;a href="https://tradevodata.com/?ref=blog" rel="noopener noreferrer"&gt;details and docs at tradevodata.com&lt;/a&gt;. And if it doesn't fit, one of the alternatives above genuinely will.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This is educational content, not investment advice. Pricing and product details for third-party vendors change — verify every number on the vendor's own pricing page before you buy.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>quant</category>
      <category>python</category>
      <category>finance</category>
      <category>datascience</category>
    </item>
    <item>
      <title>Your Backtest Saw the Future: Lookahead Bias in Fundamental Data, Measured Across 313,562 Rows</title>
      <dc:creator>Christian Pichichero</dc:creator>
      <pubDate>Mon, 06 Jul 2026 14:38:33 +0000</pubDate>
      <link>https://dev.to/tradevodata/your-backtest-saw-the-future-lookahead-bias-in-fundamental-data-measured-across-313562-rows-4cba</link>
      <guid>https://dev.to/tradevodata/your-backtest-saw-the-future-lookahead-bias-in-fundamental-data-measured-across-313562-rows-4cba</guid>
      <description>&lt;p&gt;If your backtest joins fundamentals on the fiscal-period-end date, it is trading on numbers that did not exist yet. That's the whole bug, in one sentence. A company's fiscal year ends, and then — weeks later — a 10-K gets filed and the numbers become public. Join on the wrong date and every fundamental data point in your backtest arrives early.&lt;/p&gt;

&lt;p&gt;We measured how early. Across the 313,562 point-in-time rows in our dataset (5,214 US companies, built from raw SEC EDGAR filings), on rows with a reliable filing date, fundamentals became public an average of &lt;strong&gt;43 days after&lt;/strong&gt; the fiscal period ended. The maximum in our sample was 61 days.&lt;/p&gt;

&lt;p&gt;Forty-three days of free clairvoyance on the average row, compounding across every rebalance. That is why fundamental strategies so often look great in testing and evaporate live.&lt;/p&gt;

&lt;p&gt;This post covers what lookahead bias is, the measurement, how to check your own pipeline for it, and what the honest fixes are — including the free ones.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is lookahead bias?
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Lookahead bias is when a backtest uses information that was not yet publicly available at the simulated decision time.&lt;/strong&gt; The simulation "knows" something the market didn't, so the strategy's historical results are inflated in a way that cannot be replicated live.&lt;/p&gt;

&lt;p&gt;It's the quieter sibling of survivorship bias, and it's more dangerous, because it doesn't require a broken universe or a bad vendor — it hides inside a perfectly innocent-looking &lt;code&gt;merge()&lt;/code&gt;. The data is real. The values are correct. Only the &lt;em&gt;timestamps&lt;/em&gt; are lying.&lt;/p&gt;

&lt;p&gt;Price data mostly can't have this problem: a trade prints and is public within milliseconds. Fundamental data is the worst offender, for a structural reason.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why fundamentals are the worst offender
&lt;/h2&gt;

&lt;p&gt;Every fundamentals row has two dates, and most datasets only make one of them easy to use:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Period end&lt;/strong&gt; — the date the fiscal year closed. This is the date the numbers &lt;em&gt;describe&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;First filed&lt;/strong&gt; — the date the 10-K hit EDGAR. This is the date the numbers &lt;em&gt;became knowable&lt;/em&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The gap between them is dead air during which the company knows its results and you don't. SEC deadlines let a 10-K be filed up to 60 days after fiscal year-end for large accelerated filers, 75 for accelerated filers, and 90 for everyone else — and late filers can extend beyond that.&lt;/p&gt;

&lt;p&gt;Naive datasets index everything by period end, because that's the natural key for the &lt;em&gt;accounting&lt;/em&gt;. But your backtest doesn't care what period the number describes; it cares when a trader could have acted on it. Those are different questions with different dates, an average of 43 days apart in our data.&lt;/p&gt;

&lt;h2&gt;
  
  
  A concrete example: Apple's FY2024
&lt;/h2&gt;

&lt;p&gt;These rows come straight from our dataset (you can verify them in the &lt;a href="https://github.com/christianpichichero-max/pit-fundamentals" rel="noopener noreferrer"&gt;free sample&lt;/a&gt; — no signup):&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;FY2024 period end&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;2024-09-28&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;FY2024 10-K filed&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;2024-11-01&lt;/strong&gt; (34 days later)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Revenue as first reported&lt;/td&gt;
&lt;td&gt;$391.0B&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Diluted EPS as first reported&lt;/td&gt;
&lt;td&gt;$6.08&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Now ask the only question a backtest should ever ask: &lt;em&gt;what was the newest annual revenue you could honestly know about Apple on a given date?&lt;/em&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;As-of date&lt;/th&gt;
&lt;th&gt;Newest AAPL annual revenue honestly knowable&lt;/th&gt;
&lt;th&gt;Filed&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;2024-10-15&lt;/td&gt;
&lt;td&gt;FY2023 — $383.3B&lt;/td&gt;
&lt;td&gt;2023-11-03&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2024-11-15&lt;/td&gt;
&lt;td&gt;FY2024 — $391.0B&lt;/td&gt;
&lt;td&gt;2024-11-01&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Same company, one month apart, a different "latest known" number. A naive period-end join hands your October 2024 backtest the FY2024 figures — a month before the market saw them.&lt;/p&gt;

&lt;p&gt;And note: Apple is the &lt;em&gt;best case&lt;/em&gt; on timing. Across 12 fiscal years in our data, Apple filed its 10-K 30–37 days after year end, faster than the 43-day cross-sectional average — the average company gives your backtest a bigger peek than this. Even Apple, though, isn't clean on what comes next: run the sample and you'll find five AAPL rows flagged &lt;code&gt;restated&lt;/code&gt;. FY2018–19 diluted EPS and share counts were retroactively rewritten by exactly 4× when the 2020 stock split was applied to prior years — the diluted EPS the market first saw as $11.91 is carried in current data as $2.98 — and FY2017 operating cash flow was later revised by about 1%. Which is the second problem.&lt;/p&gt;

&lt;h2&gt;
  
  
  The second leak: restatements
&lt;/h2&gt;

&lt;p&gt;Fixing the filing date is necessary but not sufficient, because filed numbers don't stay put. Companies restate and revise: 10-K/A amendments, reclassifications, corrections, retroactive split adjustments. In our dataset we found &lt;strong&gt;18,539 rows where a later filing revised a previously reported value by more than 0.5%&lt;/strong&gt; on the same XBRL tag.&lt;/p&gt;

&lt;p&gt;Here's why that leaks even in a dataset that &lt;em&gt;has&lt;/em&gt; a filing-date field. Most budget fundamentals APIs store &lt;strong&gt;one mutable value per period&lt;/strong&gt;. When a restatement lands, the historical value is silently overwritten. Your backtest then reads the &lt;em&gt;corrected&lt;/em&gt; number stamped with the &lt;em&gt;original&lt;/em&gt; filing date — information that genuinely did not exist on that date. The lookahead is smaller and rarer than the 43-day version, but it's concentrated exactly where it hurts: in the companies whose numbers turned out to be wrong.&lt;/p&gt;

&lt;p&gt;A genuinely point-in-time dataset has to keep both values: what the market first saw, and what we now believe is true.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to detect lookahead bias in your own backtest
&lt;/h2&gt;

&lt;p&gt;You don't have to take any of this on faith. Two checks, both cheap.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Audit your join dates against real filing dates
&lt;/h3&gt;

&lt;p&gt;Our free sample (40 large caps, 3,212 rows, CSV) carries a &lt;code&gt;first_filed&lt;/code&gt; column derived from the actual EDGAR filings. Merge your vendor's data against it and count the leaks:&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="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;pandas&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;

&lt;span class="c1"&gt;# free sample: github.com/christianpichichero-max/pit-fundamentals
&lt;/span&gt;&lt;span class="n"&gt;pit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read_csv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;data/pit_fundamentals_history.csv&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                  &lt;span class="n"&gt;parse_dates&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;period_end&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;first_filed&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="n"&gt;pit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pit&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;pit&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;filed_reliable&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;pit&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;first_filed&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;pit&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;period_end&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]).&lt;/span&gt;&lt;span class="n"&gt;dt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;days&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="c1"&gt;# mean lag ≈ 43 days — on this sample and on the full 313,562-row dataset
&lt;/span&gt;
&lt;span class="c1"&gt;# your data: ticker, fiscal_year, and the date your backtest
# currently *sees* the row (whatever you join on today)
&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;vendor&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;merge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;pit&lt;/span&gt;&lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ticker&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;fiscal_year&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;first_filed&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]].&lt;/span&gt;&lt;span class="nf"&gt;drop_duplicates&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="n"&gt;on&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;ticker&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;fiscal_year&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="n"&gt;leaked&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;visible_date&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;first_filed&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;leaked&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mean&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; of rows were visible to your backtest &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
      &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;before they were public&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If that percentage isn't ~0%, your historical results include information that hadn't been published yet.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. The lag-shift test
&lt;/h3&gt;

&lt;p&gt;Re-run your backtest with every fundamental data point delayed to &lt;code&gt;period_end + 90 days&lt;/code&gt; (a conservative bound on public availability). If your Sharpe or CAGR degrades materially, the difference was never alpha — it was the peek. A strategy that only works when it sees filings early is a strategy that doesn't work.&lt;/p&gt;

&lt;p&gt;Softer symptoms worth suspicion: fundamental signals that seem to "time" earnings surprisingly well, value strategies whose turnover clusters near fiscal year-ends, and — the classic — a live track record that decays immediately relative to the backtest.&lt;/p&gt;

&lt;h2&gt;
  
  
  How point-in-time data fixes it
&lt;/h2&gt;

&lt;p&gt;A point-in-time (PIT) dataset makes &lt;em&gt;knowability&lt;/em&gt; the primary index. Every value in ours carries:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;first_filed&lt;/code&gt;&lt;/strong&gt; — the date the number actually became public (the PIT stamp)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;original_value&lt;/code&gt;&lt;/strong&gt; — the number as first reported: what the market saw. Use this for backtests.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;latest_value&lt;/code&gt;&lt;/strong&gt; — the current post-revision number. Use this for present-day screening.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;restated&lt;/code&gt;&lt;/strong&gt; — flagged when a later filing moved the value &amp;gt;0.5% on the same tag (including 10-K/A amendments)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;qa_status&lt;/code&gt;&lt;/strong&gt; — per-row validation result. Rows we can't fully trust are flagged, never hidden.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The query semantics then enforce honesty server-side. Our API is one endpoint:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;GET /v1/fundamentals?ticker=AAPL&amp;amp;as_of=2024-10-15
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This returns only rows where &lt;code&gt;first_filed &amp;lt;= as_of&lt;/code&gt; — so the 2024-10-15 query above returns FY2023 and earlier, never FY2024, no matter how you write your backtest loop. (One honest edge to know: the comparison is inclusive, so a filing dated exactly on your as-of date is returned. If you want to be strict about same-day tradability, subtract a day.)&lt;/p&gt;

&lt;p&gt;One more thing, because trust in a dataset should be earned, not asserted: before launch we had independent agents adversarially re-derive values from the raw EDGAR filings and compare them against ours. They found one real bug in our revision tracking. We fixed it before shipping. The full methodology — tag resolution, duration filters, restatement thresholds, QA rules — is &lt;a href="https://github.com/christianpichichero-max/pit-fundamentals" rel="noopener noreferrer"&gt;published in the open&lt;/a&gt; alongside the sample.&lt;/p&gt;

&lt;h2&gt;
  
  
  Your options, honestly compared
&lt;/h2&gt;

&lt;p&gt;We sell one of these options, so calibrate accordingly. But the comparison below is real, and for some readers the right answer is not us.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. DIY from SEC EDGAR — free.&lt;/strong&gt; EDGAR's &lt;code&gt;companyfacts&lt;/code&gt; API is public domain, free, and needs no key. This is what we build from, and you can too. The catch is engineering, not access: companies switch XBRL tags over time (Apple's revenue has lived under three different tags since 2014), stub periods and quarters can leak into annual figures, and reconstructing &lt;em&gt;first-reported&lt;/em&gt; values through restatements is genuinely fiddly — that's where our own audit found a bug. If you have the time, our published methodology is a usable blueprint. Budget a few weekends, not a few hours.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Budget fundamentals APIs (~$15–100/mo).&lt;/strong&gt; Many include a filing-date field, which solves the 43-day problem &lt;em&gt;if you remember to join on it&lt;/em&gt;. The structural gap is the mutable single value per period described above: restatements silently overwrite history, so the "point-in-time" join can still leak, and you can't reconstruct what the market originally saw.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Sharadar and Tiingo — the credible budget incumbents.&lt;/strong&gt; We will not pretend to be the only affordable PIT-aware option, because we aren't. Sharadar (via Nasdaq Data Link, roughly $550–830/yr for personal use) offers as-reported dimensions, quarterly data, ~20,000 companies including delisted ones, and history to 1998 — &lt;strong&gt;if you need quarterly or delisted coverage today, buy Sharadar; it is better than us at those things.&lt;/strong&gt; Tiingo's &lt;code&gt;asReported&lt;/code&gt; fields are also genuinely PIT-relevant. The tradeoffs: you assemble the as-of join yourself from dimension tables, pricing sits behind a login, and Sharadar's non-professional license excludes money-management use.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Research-grade institutional PIT ($10k–50k/yr).&lt;/strong&gt; Compustat Snapshot, LSEG, Calcbench, Intrinio (~$9.6k/yr). These are the genuine article — deep history, full revision archives, survivorship-bias-safe universes — and if someone is paying you to manage money, they are probably the right call. They are also priced for institutions and generally won't sell to an individual with a Stripe card.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Tradevo Data — the middle.&lt;/strong&gt; $49/mo, one documented JSON endpoint with server-side &lt;code&gt;as_of&lt;/code&gt;, &lt;code&gt;original_value&lt;/code&gt; vs &lt;code&gt;latest_value&lt;/code&gt; on every row, restatement flags, per-row QA, 5,000 requests/day, key issued instantly after checkout. We think of it as the honest budget tier of a product that has historically cost $10k–50k/yr — not "better data than everyone," just lookahead-safety as the default query semantic, at a price an individual can justify.&lt;/p&gt;

&lt;h2&gt;
  
  
  What our data doesn't do (yet)
&lt;/h2&gt;

&lt;p&gt;Stating this up front so you don't find out after paying:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Annual only&lt;/strong&gt; — 10-K and 10-K/A. Quarterly (10-Q) is on the roadmap, not shipped.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;US companies only&lt;/strong&gt; (5,214 of them).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;7 concepts&lt;/strong&gt;: Revenue, NetIncome, Assets, StockholdersEquity, OperatingCashFlow, EPSDiluted, DilutedShares.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Up to 12 fiscal years&lt;/strong&gt; of history.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No bulk Parquet download yet&lt;/strong&gt; — it's a query API.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;first_filed &amp;lt;= as_of&lt;/code&gt; is same-day-inclusive, as noted above.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If any of those is a dealbreaker, the comparison section above has options that fit better, and we'd rather you use them than be disappointed here.&lt;/p&gt;

&lt;h2&gt;
  
  
  Check the work yourself
&lt;/h2&gt;

&lt;p&gt;The &lt;strong&gt;free sample is on GitHub — 40 large caps, 3,212 point-in-time rows, full methodology, no signup&lt;/strong&gt;: &lt;a href="https://github.com/christianpichichero-max/pit-fundamentals" rel="noopener noreferrer"&gt;github.com/christianpichichero-max/pit-fundamentals&lt;/a&gt;. Run the audit snippet above against your current vendor. If your pipeline comes back clean, excellent — you lost ten minutes and gained a verified negative.&lt;/p&gt;

&lt;p&gt;If it doesn't, and you want the fix as an API instead of a weekend project: the full dataset is &lt;strong&gt;$49/mo&lt;/strong&gt; at &lt;a href="https://tradevodata.com" rel="noopener noreferrer"&gt;tradevodata.com&lt;/a&gt;, key issued instantly, docs at &lt;a href="https://tradevodata.com/docs" rel="noopener noreferrer"&gt;tradevodata.com/docs&lt;/a&gt;, cancel anytime.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Tradevo Data is a product of Tradevo Technologies Inc. Source data is U.S. SEC EDGAR (public domain). We sell a dataset, not signals — nothing here is investment advice, and no data product can make a bad strategy good. It can only stop a backtest from lying to you.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>quant</category>
      <category>python</category>
      <category>finance</category>
      <category>datascience</category>
    </item>
    <item>
      <title>How to Build a Point-in-Time Fundamentals Database from SEC EDGAR (and When Not To)</title>
      <dc:creator>Christian Pichichero</dc:creator>
      <pubDate>Mon, 06 Jul 2026 14:38:27 +0000</pubDate>
      <link>https://dev.to/tradevodata/how-to-build-a-point-in-time-fundamentals-database-from-sec-edgar-and-when-not-to-2gn6</link>
      <guid>https://dev.to/tradevodata/how-to-build-a-point-in-time-fundamentals-database-from-sec-edgar-and-when-not-to-2gn6</guid>
      <description>&lt;p&gt;If you backtest with today's fundamentals, you are cheating yourself. The Revenue figure you pull for Q4 2019 was probably restated in 2021, and the restated number was not knowable on the day you would have traded. That gap is &lt;a href="https://dev.to/blog/lookahead-bias-fundamental-backtests"&gt;lookahead bias&lt;/a&gt;, and it is why "point-in-time" (PIT) fundamentals exist: a dataset that answers &lt;em&gt;what did filers report, and when did the market first see it?&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The good news: the raw material is free and public. The SEC's EDGAR system publishes structured XBRL financials for every US filer, and with enough patience you can assemble a real PIT database yourself. The honest news: a few steps are fiddly enough that most DIY builds ship a subtle bug. This guide teaches the whole pipeline — our full &lt;a href="https://github.com/christianpichichero-max/pit-fundamentals" rel="noopener noreferrer"&gt;methodology is public on GitHub&lt;/a&gt; — then weighs the cost fairly so you can decide whether to build or buy.&lt;/p&gt;

&lt;h2&gt;
  
  
  The build: EDGAR companyfacts, step by step
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. Pull companyfacts.&lt;/strong&gt; EDGAR exposes a per-company JSON blob at &lt;code&gt;data.sec.gov/api/xbrl/companyfacts/CIK{10-digit}.json&lt;/code&gt;. It contains every XBRL fact the company has ever tagged, grouped by taxonomy (&lt;code&gt;us-gaap&lt;/code&gt;, &lt;code&gt;dei&lt;/code&gt;) and concept, each with an array of reported values. This is your source of truth. Respect the SEC's fair-access rules (declare a User-Agent, throttle to ~10 requests/sec).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Filter to annual 10-K facts.&lt;/strong&gt; Each fact carries a &lt;code&gt;form&lt;/code&gt; (&lt;code&gt;10-K&lt;/code&gt;, &lt;code&gt;10-K/A&lt;/code&gt;, &lt;code&gt;10-Q&lt;/code&gt;, &lt;code&gt;8-K&lt;/code&gt;…) and, for flow concepts like Revenue, a &lt;code&gt;start&lt;/code&gt;/&lt;code&gt;end&lt;/code&gt; window. You want annual figures, so keep &lt;code&gt;10-K&lt;/code&gt; and &lt;code&gt;10-K/A&lt;/code&gt; (amendments) and filter duration concepts to roughly 365-day windows. Skip this and you will silently mix quarterly and annual Revenue into the same series — a classic corruption.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Resolve XBRL synonym tags across years.&lt;/strong&gt; This is where builds quietly break. Companies change the tag they use for the &lt;em&gt;same&lt;/em&gt; economic concept. Revenue might be &lt;code&gt;Revenues&lt;/code&gt; one year, &lt;code&gt;RevenueFromContractWithCustomerExcludingAssessedTax&lt;/code&gt; the next, and &lt;code&gt;SalesRevenueNet&lt;/code&gt; in an older filing. If you map only one tag, you get holes exactly where a company evolved its reporting. You need an ordered synonym list per concept and a deterministic resolution rule so the &lt;em&gt;same&lt;/em&gt; logical field is populated every year.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Stamp &lt;code&gt;first_filed&lt;/code&gt;.&lt;/strong&gt; For each value, record the date it &lt;em&gt;first&lt;/em&gt; appeared in a filing (the accession's filing date), not the fiscal period end. This &lt;code&gt;first_filed&lt;/code&gt; stamp is the entire point of PIT: a backtest asks "what was known as of date X?" and you answer by returning only rows where &lt;code&gt;first_filed &amp;lt;= X&lt;/code&gt;. Same-day filings should be inclusive.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Detect same-tag restatements.&lt;/strong&gt; When a later 10-K or 10-K/A reports a &lt;em&gt;different&lt;/em&gt; value for a period you already have, that is a restatement. Keep both: the &lt;code&gt;original_value&lt;/code&gt; (first-reported, PIT-safe) and the &lt;code&gt;latest_value&lt;/code&gt; (current best estimate), plus a &lt;code&gt;restated&lt;/code&gt; flag. Use a tolerance — we flag when the same tag's value moves more than 0.5% — so rounding noise does not masquerade as a restatement.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;6. QA every row.&lt;/strong&gt; Sanity-check signs, magnitudes, and duplicate periods; carry a &lt;code&gt;qa_status&lt;/code&gt; so consumers can filter. Fundamentals data is dirty; a QA column is not optional.&lt;/p&gt;

&lt;h3&gt;
  
  
  The part everyone underestimates
&lt;/h3&gt;

&lt;p&gt;Restatement reconstruction is deceptively hard, and we say that from experience: our own audit found a bug in exactly this step. It is easy to write logic that &lt;em&gt;looks&lt;/em&gt; right on Apple and Microsoft — clean filers with stable tags — and quietly mislabels the messier 80% of the market. Tag switches (step 3) and restatement detection (step 5) are where a DIY dataset silently drifts from ground truth, and you often won't notice until a backtest looks suspiciously good. Budget real test coverage across small caps, amenders, and tag-switchers, not just the mega-caps.&lt;/p&gt;

&lt;h2&gt;
  
  
  Build vs. buy: an honest comparison
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Build from EDGAR&lt;/th&gt;
&lt;th&gt;Tradevo Data ($49/mo)&lt;/th&gt;
&lt;th&gt;Research-grade vendors&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Cost&lt;/td&gt;
&lt;td&gt;$0 + your time&lt;/td&gt;
&lt;td&gt;$49/mo&lt;/td&gt;
&lt;td&gt;Higher / quote-based&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Setup&lt;/td&gt;
&lt;td&gt;Days to weeks&lt;/td&gt;
&lt;td&gt;Minutes (key after checkout)&lt;/td&gt;
&lt;td&gt;Varies&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PIT correctness&lt;/td&gt;
&lt;td&gt;Yours to get right&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;first_filed&lt;/code&gt;, original vs. latest, restatement flags&lt;/td&gt;
&lt;td&gt;Yes (their core)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Coverage&lt;/td&gt;
&lt;td&gt;Whatever you build&lt;/td&gt;
&lt;td&gt;5,214 US companies, annual only&lt;/td&gt;
&lt;td&gt;Often broader (quarterly, global)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;History&lt;/td&gt;
&lt;td&gt;As far as you pull&lt;/td&gt;
&lt;td&gt;Up to 12 fiscal years&lt;/td&gt;
&lt;td&gt;Often 20-25+ years&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Restatements&lt;/td&gt;
&lt;td&gt;You reconstruct&lt;/td&gt;
&lt;td&gt;18,539 labeled&lt;/td&gt;
&lt;td&gt;Handled&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bulk / Parquet&lt;/td&gt;
&lt;td&gt;Build it&lt;/td&gt;
&lt;td&gt;JSON API only (no bulk yet)&lt;/td&gt;
&lt;td&gt;Often available&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Methodology&lt;/td&gt;
&lt;td&gt;Yours&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/christianpichichero-max/pit-fundamentals" rel="noopener noreferrer"&gt;Public&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Usually opaque&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Credible paid PIT options exist and we won't pretend otherwise. &lt;strong&gt;Sharadar Core US Fundamentals&lt;/strong&gt; (via Nasdaq Data Link) is a well-regarded point-in-time dataset with deep history; its personal pricing is not publicly listed as of July 2026, so &lt;a href="https://data.nasdaq.com/databases/SF1" rel="noopener noreferrer"&gt;check their page&lt;/a&gt;. &lt;strong&gt;Tiingo&lt;/strong&gt; offers a Power plan at $30/mo individual / $50/mo commercial as of July 2026, with fundamentals sold as a separate API add-on (pricing on request) and an as-reported option of its own — &lt;a href="https://www.tiingo.com/about/pricing" rel="noopener noreferrer"&gt;see their pricing&lt;/a&gt;. Both are legitimate; verify current terms before you buy, because vendor pricing moves.&lt;/p&gt;

&lt;h2&gt;
  
  
  When to build it yourself
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;You want to learn the plumbing.&lt;/strong&gt; Building the pipeline once teaches you what PIT actually means, and you'll trust your data more for having wrangled it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You need concepts or coverage nobody sells.&lt;/strong&gt; Custom tags, obscure line items, non-standard filers — EDGAR has it all; you just have to extract it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Your time is genuinely free&lt;/strong&gt; relative to $49/mo, and you enjoy this kind of work.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You need full control and auditability&lt;/strong&gt; down to the accession level.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  When buying is the better choice
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;You'd rather test a strategy than debug XBRL synonym tables.&lt;/strong&gt; The engineering is real, and every week on the pipeline is a week not spent on research.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You want restatement handling you didn't have to reconstruct&lt;/strong&gt; — the exact step where our own audit caught a bug, and where DIY builds most often ship wrong.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;$49/mo is cheaper than your afternoon.&lt;/strong&gt; For most working quants, it is.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You need broader coverage or quarterly data.&lt;/strong&gt; Then a deeper vendor like Sharadar or Tiingo may fit better than either a weekend build &lt;em&gt;or&lt;/em&gt; our annual-only tier — we're the honest budget tier, not the only option.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Start free, either way
&lt;/h2&gt;

&lt;p&gt;Whichever path you pick, start by looking at real PIT data. Our &lt;a href="https://github.com/christianpichichero-max/pit-fundamentals" rel="noopener noreferrer"&gt;free GitHub sample&lt;/a&gt; has 40 large caps, 3,212 rows, and the complete methodology — tag resolution, &lt;code&gt;first_filed&lt;/code&gt; stamping, same-tag restatement detection, QA — with no signup. Read it, copy the approach, and build your own. If you'd rather skip the fiddly parts, &lt;a href="https://tradevodata.com/?ref=blog" rel="noopener noreferrer"&gt;Tradevo Data&lt;/a&gt; serves the same discipline as one JSON endpoint (&lt;code&gt;/v1/fundamentals?ticker&amp;amp;as_of&lt;/code&gt;) with server-side &lt;code&gt;as_of&lt;/code&gt; filtering, 5,000 requests/day, and an instant key after checkout — cancel anytime.&lt;/p&gt;

&lt;p&gt;Build it or buy it. Just don't backtest on restated numbers and call it edge.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Not investment advice. Tradevo Data provides point-in-time US equity fundamentals sourced from public SEC EDGAR filings; it does not make performance claims. Competitor pricing and features are as of July 2026 — always verify current terms on each vendor's own page before purchasing.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>quant</category>
      <category>python</category>
      <category>finance</category>
      <category>datascience</category>
    </item>
  </channel>
</rss>
