<?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: XFINLAB</title>
    <description>The latest articles on DEV Community by XFINLAB (@xfinlab).</description>
    <link>https://dev.to/xfinlab</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%2F4106221%2F8c9a66da-cf48-460e-be8c-c1ffb8c834b7.png</url>
      <title>DEV Community: XFINLAB</title>
      <link>https://dev.to/xfinlab</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/xfinlab"/>
    <language>en</language>
    <item>
      <title>Why I Made My Financial Data API Return `null` Instead of Guessing</title>
      <dc:creator>XFINLAB</dc:creator>
      <pubDate>Wed, 02 Sep 2026 13:07:44 +0000</pubDate>
      <link>https://dev.to/xfinlab/why-i-made-my-financial-data-api-return-null-instead-of-guessing-2nl7</link>
      <guid>https://dev.to/xfinlab/why-i-made-my-financial-data-api-return-null-instead-of-guessing-2nl7</guid>
      <description>&lt;p&gt;I spent the last several months building out a financial data API&lt;br&gt;
(XFINLAB) that pulls from about 20 different government/exchange sources&lt;br&gt;
— SEC EDGAR, FINRA, CFTC, FDIC, USDA, CBOE, EIA, openFDA, CPSC, a couple&lt;br&gt;
of crypto exchanges. Along the way I made one architectural rule that&lt;br&gt;
ended up shaping almost every collector module: &lt;strong&gt;a field is either a&lt;br&gt;
real value with a source, or it's &lt;code&gt;null&lt;/code&gt; with an explicit reason. Never&lt;br&gt;
an estimate presented as if it were real.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This sounds obvious written down. In practice it's a real design tax.&lt;/p&gt;
&lt;h2&gt;
  
  
  The tempting shortcut
&lt;/h2&gt;

&lt;p&gt;Say you're building a collector for USDA agricultural commodity prices.&lt;br&gt;
The USDA Quick Stats API doesn't always have this year's data yet for a&lt;br&gt;
given commodity — sometimes the most recent real observation is from 8&lt;br&gt;
months ago. The tempting move is to interpolate: draw a line between the&lt;br&gt;
last two known points, guess where "now" would sit on it, ship a number.&lt;/p&gt;

&lt;p&gt;It &lt;em&gt;looks&lt;/em&gt; like better data. It demos better. And it's fabricated.&lt;/p&gt;
&lt;h2&gt;
  
  
  What we did instead
&lt;/h2&gt;

&lt;p&gt;Every collector in this codebase follows the same shape:&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="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_context_for_ticker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ticker&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Optional&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;ticker&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;_TICKER_TO_SOURCE_MAPPING&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;  &lt;span class="c1"&gt;# no linkage, not a guess
&lt;/span&gt;    &lt;span class="bp"&gt;...&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;fetch_failed&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;_load_persisted_fallback&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ticker&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# last REAL value we saw, honestly dated
&lt;/span&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;real_fresh_value&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No collector has a code path that produces a plausible-looking number&lt;br&gt;
that isn't traceable to an actual source and an actual fetch timestamp.&lt;br&gt;
When a value truly isn't available, the API returns an explicit&lt;br&gt;
"unavailable" with a reason (not configured / rate-limited / genuinely&lt;br&gt;
no match for this ticker) — not a zero that looks like a real zero, and&lt;br&gt;
not last year's number silently relabeled as current.&lt;/p&gt;
&lt;h2&gt;
  
  
  Making it checkable, not just claimed
&lt;/h2&gt;

&lt;p&gt;Every collector self-registers into a small SQLite-backed registry at&lt;br&gt;
import time:&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="nf"&gt;register_source&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;usda_agriculture&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;USDA Agricultural Commodity Prices&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;agriculture&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;with &lt;code&gt;record_run_start&lt;/code&gt; / &lt;code&gt;record_run_success&lt;/code&gt; / &lt;code&gt;record_run_error&lt;/code&gt;&lt;br&gt;
wrapping the actual fetch calls. That registry backs a public,&lt;br&gt;
unauthenticated status page — &lt;a href="https://www.xfinlab.com/trust.html" rel="noopener noreferrer"&gt;https://www.xfinlab.com/trust.html&lt;/a&gt; — so&lt;br&gt;
"this data source is honestly reporting, not guessing" isn't just a&lt;br&gt;
claim in the docs, it's something anyone can go check live, updated in&lt;br&gt;
real time, including which sources are currently down.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this matters more in 2026 than it used to
&lt;/h2&gt;

&lt;p&gt;Q1 2026 reportedly saw $2.3B in trading losses tied to AI-generated&lt;br&gt;
misstated earnings figures, and FINRA's 2026 Annual Oversight Report&lt;br&gt;
devoted its first-ever dedicated section to AI/hallucination risk for&lt;br&gt;
broker-dealers. 62% of enterprise AI users now cite hallucinations as&lt;br&gt;
their top deployment barrier. None of that is about financial data APIs&lt;br&gt;
specifically, but it's the same underlying failure mode: a plausible&lt;br&gt;
number that isn't real, presented with no way to tell the difference.&lt;/p&gt;

&lt;p&gt;The fix isn't really an AI problem to solve with a better model — it's&lt;br&gt;
an interface design problem. Make the "I don't know" path a first-class,&lt;br&gt;
explicit response, not a bug to route around.&lt;/p&gt;

&lt;h2&gt;
  
  
  If you want to see it live
&lt;/h2&gt;

&lt;p&gt;The API's free tier is instant, no card — 20+ endpoints, Python/JS SDKs,&lt;br&gt;
and an MCP server if you're wiring this into an agent:&lt;br&gt;
&lt;a href="https://www.xfinlab.com/intelligence-api.html" rel="noopener noreferrer"&gt;https://www.xfinlab.com/intelligence-api.html&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Curious how other API builders here handle this same tradeoff —&lt;br&gt;
especially anyone working with genuinely gappy upstream sources. Do you&lt;br&gt;
interpolate, forward-fill, or null-and-explain?&lt;/p&gt;

</description>
      <category>api</category>
      <category>python</category>
      <category>fastapi</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
