<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd" xmlns:googleplay="http://www.google.com/schemas/play-podcasts/1.0"><channel><title><![CDATA[Non-Brand Data]]></title><description><![CDATA[Non-Brand Data provides expert tips on Machine Learning, Technology News, and Python Packages to help you excel and stand out in your data career.]]></description><link>https://www.nb-data.com</link><image><url>https://substackcdn.com/image/fetch/$s_!06DP!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F6c0e1cde-d120-4029-8ffd-2a8c7c6e4504_1280x1280.png</url><title>Non-Brand Data</title><link>https://www.nb-data.com</link></image><generator>Substack</generator><lastBuildDate>Sun, 05 Apr 2026 08:27:33 GMT</lastBuildDate><atom:link href="https://www.nb-data.com/feed" rel="self" type="application/rss+xml"/><copyright><![CDATA[Cornellius Yudha Wijaya]]></copyright><language><![CDATA[en]]></language><webMaster><![CDATA[cornellius@substack.com]]></webMaster><itunes:owner><itunes:email><![CDATA[cornellius@substack.com]]></itunes:email><itunes:name><![CDATA[Cornellius Yudha Wijaya]]></itunes:name></itunes:owner><itunes:author><![CDATA[Cornellius Yudha Wijaya]]></itunes:author><googleplay:owner><![CDATA[cornellius@substack.com]]></googleplay:owner><googleplay:email><![CDATA[cornellius@substack.com]]></googleplay:email><googleplay:author><![CDATA[Cornellius Yudha Wijaya]]></googleplay:author><itunes:block><![CDATA[Yes]]></itunes:block><item><title><![CDATA[What Real SQL Work Taught Me About Being a Data Scientist]]></title><description><![CDATA[Why I stopped seeing SQL as a secondary skill and started seeing it as the backbone of real data projects]]></description><link>https://www.nb-data.com/p/what-real-sql-work-taught-me-about</link><guid isPermaLink="false">https://www.nb-data.com/p/what-real-sql-work-taught-me-about</guid><dc:creator><![CDATA[Cornellius Yudha Wijaya]]></dc:creator><pubDate>Sat, 28 Mar 2026 15:07:48 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!7CmX!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4abfb659-bac8-4b53-b22e-24fcad7b32ea_1312x736.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!7CmX!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4abfb659-bac8-4b53-b22e-24fcad7b32ea_1312x736.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!7CmX!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4abfb659-bac8-4b53-b22e-24fcad7b32ea_1312x736.jpeg 424w, https://substackcdn.com/image/fetch/$s_!7CmX!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4abfb659-bac8-4b53-b22e-24fcad7b32ea_1312x736.jpeg 848w, https://substackcdn.com/image/fetch/$s_!7CmX!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4abfb659-bac8-4b53-b22e-24fcad7b32ea_1312x736.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!7CmX!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4abfb659-bac8-4b53-b22e-24fcad7b32ea_1312x736.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!7CmX!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4abfb659-bac8-4b53-b22e-24fcad7b32ea_1312x736.jpeg" width="1312" height="736" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/4abfb659-bac8-4b53-b22e-24fcad7b32ea_1312x736.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:736,&quot;width&quot;:1312,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:90217,&quot;alt&quot;:&quot;What Real SQL Work Taught Me About Being a Data Scientist&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://www.nb-data.com/i/192418111?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4abfb659-bac8-4b53-b22e-24fcad7b32ea_1312x736.jpeg&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="What Real SQL Work Taught Me About Being a Data Scientist" title="What Real SQL Work Taught Me About Being a Data Scientist" srcset="https://substackcdn.com/image/fetch/$s_!7CmX!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4abfb659-bac8-4b53-b22e-24fcad7b32ea_1312x736.jpeg 424w, https://substackcdn.com/image/fetch/$s_!7CmX!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4abfb659-bac8-4b53-b22e-24fcad7b32ea_1312x736.jpeg 848w, https://substackcdn.com/image/fetch/$s_!7CmX!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4abfb659-bac8-4b53-b22e-24fcad7b32ea_1312x736.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!7CmX!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4abfb659-bac8-4b53-b22e-24fcad7b32ea_1312x736.jpeg 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Image by Ideogram.ai</figcaption></figure></div><div class="pullquote"><p>"Real SQL work taught me that trustworthy definitions matter more than flashy queries."</p></div><h1>I did not start by taking SQL seriously</h1><p style="text-align: justify;">Early in my career, I did not see SQL as central to being a data scientist. Most of my learning was built around Python, and the classes and bootcamps I joined reinforced that view. Python felt like the real language of data science. SQL felt useful, but distant.</p><p>So I did not reject it. I simply did not get enough exposure to it.</p><p>That distinction matters. When your early learning path is dominated by notebooks, models, and Python libraries, it is easy to assume that the real work starts once the data is already in front of you. In that worldview, SQL looks like preparation work. Helpful, yes. Foundational, no.</p><p>Real work changed that view gradually.</p><p>The more I worked in corporate settings, the clearer it became that many projects do not begin with modeling, dashboards, or machine learning. They begin with a more basic set of questions: Is the data available? Is the definition correct? Can the result be trusted enough for someone to act on it?</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.nb-data.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://www.nb-data.com/subscribe?"><span>Subscribe now</span></a></p><div><hr></div><h1>Work forced the lesson</h1><p>What changed my view of SQL was not one dramatic moment. It was the accumulation of projects. Again and again, the work pulled me toward the same reality: before anything becomes an analysis, a model, or a recommendation, someone has to make sure the data is available, correctly defined, and usable.</p><p>That is where SQL kept appearing.</p><p>Sometimes the request looked simple. A business team needed a report. Sometimes the request sounded more strategic. A project needed insight to inform a decision. Sometimes the work moved beyond a single analysis into the project's production life. In each case, SQL mattered not only for retrieving data, but for deciding whether the project itself rested on a solid foundation.</p><p>The difficult conversations were often not about syntax at all. They were about meaning. What exactly should count as a sale? Which time window should be used? Which source should be treated as the source of truth? If two tables produce different answers, which one reflects the real business process?</p><p>That was the point where SQL stopped feeling like a supporting skill and became infrastructure.</p><h1>What real SQL work actually looked like</h1><p>The lesson became clearer through a few recurring types of work. These were not glamorous, as they were simply the places where SQL kept proving its value.</p><ul><li><p>Ad-hoc reporting and insight requests that looked simple but hid messy logic and scattered data.</p></li><li><p>Metric definition work, where the challenge was deciding what should count before writing the query.</p></li><li><p>Combining multiple data sources without destroying the business meaning of the result.</p></li><li><p>Preparing the right data for downstream analysis and modeling in Python.</p></li></ul><div><hr></div><h2>1. Ad-hoc reporting taught me that simple requests are rarely simple</h2><p>A lot of real SQL work starts with a seemingly harmless request. The business needs a report. Someone wants a quick performance update. A team asks for insight before a meeting. On paper, it sounds like a straightforward query.</p><p>In practice, it rarely is.</p><p>Sometimes the data is not available in one place. Sometimes it lives across several sources that were never designed to fit together neatly. Sometimes the logic needed to answer the question is more complicated than the request suggests. And often the timeline is short, so you do not have the luxury of slowly wading through the data.</p><p>That changed how I think about SQL skills. In real reporting work, the challenge is not just writing something that runs. The challenge is moving from a vague business question to a reliable answer under real constraints. That takes judgment, prioritization, and a clear sense of what the output needs to mean.</p><p>Useful SQL work is often less glamorous than people expect. It is not always about elegant tricks. Very often, it is about getting the right answer quickly enough to matter, without breaking the logic behind it.</p><div><hr></div><h2>2. Metric definition matters more than query complexity</h2><p>If there is one area where real SQL work changed me the most, it is the definition of metrics.</p><p>In theory, a metric looks clean. In practice, even something as familiar as a sales number can go wrong depending on the time scope, exclusions, business rules, and source tables. A number can look precise and still be misleading if two teams are working from different assumptions or if one table captures the event differently from another.</p><p>That is why some SQL problems cannot be solved by clever syntax alone. You can write a technically correct query and still produce the wrong business answer.</p><p>The real work is often more basic and more demanding at the same time:</p><ul><li><p>deciding what should count</p></li><li><p>deciding what should be excluded</p></li><li><p>choosing which table reflects the operational truth</p></li><li><p>making sure the result matches the way the business actually works</p></li></ul><p>This is where collaboration becomes essential. There are many situations where the data exists, but understanding it requires discussion with business users who know the process behind the records. Without that alignment, a query may return rows but not the truth.</p><p>Over time, I started to see that some of the most dangerous problems in data work are not computational. They are definitional. A wrong definition can quietly damage a project, mislead stakeholders, or erode trust in the team long before anyone notices the issue.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.nb-data.com/p/what-real-sql-work-taught-me-about?utm_source=substack&utm_medium=email&utm_content=share&action=share&quot;,&quot;text&quot;:&quot;Share&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://www.nb-data.com/p/what-real-sql-work-taught-me-about?utm_source=substack&utm_medium=email&utm_content=share&action=share"><span>Share</span></a></p><div><hr></div><h2>3. Combining data sources is harder than it looks</h2><p>Another lesson real SQL work taught me is that combining information from multiple sources without losing meaning is much harder than it first appears.</p><p>From the outside, joins can look like a purely technical step. In practice, they can become one of the most delicate parts of a project. Sometimes a clean primary key does not exist. Sometimes the relationship is not direct. Sometimes aggregation is needed before two datasets can even be compared. And sometimes each source reflects a slightly different view of the same business concept.</p><p>That creates several risks at once: duplicate rows, dropped records, timing mismatches, and numbers that appear structurally valid but are conceptually incorrect.</p><p>This is why SQL work often requires more collaboration than people expect. To combine sources responsibly, you frequently need validation from multiple stakeholders. The challenge is not merely to make the query run. The challenge is to preserve validity.</p><p>For me, this was one of the clearest moments where SQL became inseparable from business understanding. Good SQL was not just about retrieval. It was about preserving meaning as it moved across systems.</p><div><hr></div><h2>4. Even Python-heavy data science often begins with SQL</h2><p>Because my early learning path emphasized Python, I initially imagined that most serious data-science work would begin there. In reality, SQL was often necessary before I could even start proper work in Python.</p><p>If the data lived in a SQL database, then SQL was the gatekeeper. It was how I extracted the relevant population, selected the appropriate time window, assembled the required columns, and checked whether the data were suitable for the task ahead. Whether the next step was exploratory analysis, feature preparation, modeling, or evaluation, SQL was often the first step.</p><p>That changed how I think about the relationship between SQL and data science. SQL is not simply what happens before the interesting work. Very often, it is part of the interesting work.</p><p>If the population is wrong, the feature set is incomplete, or the definition is unstable, the downstream Python work inherits that weakness. In that sense, SQL does not sit beneath data science. It sits inside it.</p><div><hr></div><h1>What I value in SQL work now</h1><p>Real work also changed how I evaluate SQL skills in others and in myself.</p><p>I still care about writing cleaner, more efficient queries, especially as data grows larger and execution speed matters. But that is no longer the first thing I look for.</p><p>What I value first is this:</p><blockquote><p>1. Correctness. The wrong data can quietly damage an entire project.</p><p>2. Stakeholder trust. Data work only becomes valuable when other people believe the result is dependable.</p><p>3. Maintainability. Many projects do not end after a single request, so someone has to live with the logic later.</p></blockquote><p>A strong SQL practitioner, in my view, is not simply someone who knows a large amount of syntax. It is someone who understands the data definition, knows how to acquire the data in the most reliable way, and can produce logic that remains useful beyond the moment it was written.</p><div><hr></div><h1>What I would tell aspiring data scientists now</h1><p>If your learning path has focused mostly on Python, I would say this clearly: do not treat SQL as optional.</p><p>You do not need to memorize every feature of the language before doing meaningful work. Documentation exists, and syntax can be learned as needed. But you do need to understand why SQL matters. It matters because data projects depend on access to the right data, under the right definitions, with logic that can withstand real business use.</p><p>That is the part I wish I had understood earlier. SQL is not important because it looks technical. It is important because it sits close to the truth conditions of data work. It is where data availability is tested. It is where definitions get challenged. It is where numbers either become trustworthy or fall apart.</p><p>For me, that has become one of the clearest professional lessons of real data work. SQL is not the opposite of data science, nor is it a lower-level skill beneath it. In many organizations, SQL is one of the foundations that allows data science to be useful at all.</p><p>And if there is one line I would leave readers with, it is this: real SQL work taught me that trustworthy definitions matter more than flashy queries.</p><p>If you are learning SQL now, learn it through real use cases. Learn it through reporting, metric definition, source validation, and the kind of business questions that force you to care about correctness. </p><div><hr></div><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.nb-data.com/p/what-real-sql-work-taught-me-about/comments&quot;,&quot;text&quot;:&quot;Leave a comment&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://www.nb-data.com/p/what-real-sql-work-taught-me-about/comments"><span>Leave a comment</span></a></p>]]></content:encoded></item><item><title><![CDATA[Best Stock Market data API in the AI Agent era]]></title><description><![CDATA[How modern financial APIs are powering the next generation of AI-driven market research and trading tools]]></description><link>https://www.nb-data.com/p/best-stock-market-data-api-in-the</link><guid isPermaLink="false">https://www.nb-data.com/p/best-stock-market-data-api-in-the</guid><dc:creator><![CDATA[Cornellius Yudha Wijaya]]></dc:creator><pubDate>Sat, 14 Mar 2026 07:49:27 GMT</pubDate><enclosure url="https://images.unsplash.com/photo-1560221328-12fe60f83ab8?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwyfHxzdG9ja3xlbnwwfHx8fDE3NzMxNDkwODZ8MA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://images.unsplash.com/photo-1560221328-12fe60f83ab8?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwyfHxzdG9ja3xlbnwwfHx8fDE3NzMxNDkwODZ8MA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://images.unsplash.com/photo-1560221328-12fe60f83ab8?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwyfHxzdG9ja3xlbnwwfHx8fDE3NzMxNDkwODZ8MA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 424w, https://images.unsplash.com/photo-1560221328-12fe60f83ab8?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwyfHxzdG9ja3xlbnwwfHx8fDE3NzMxNDkwODZ8MA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 848w, https://images.unsplash.com/photo-1560221328-12fe60f83ab8?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwyfHxzdG9ja3xlbnwwfHx8fDE3NzMxNDkwODZ8MA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 1272w, https://images.unsplash.com/photo-1560221328-12fe60f83ab8?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwyfHxzdG9ja3xlbnwwfHx8fDE3NzMxNDkwODZ8MA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 1456w" sizes="100vw"><img src="https://images.unsplash.com/photo-1560221328-12fe60f83ab8?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwyfHxzdG9ja3xlbnwwfHx8fDE3NzMxNDkwODZ8MA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080" width="4800" height="3188" data-attrs="{&quot;src&quot;:&quot;https://images.unsplash.com/photo-1560221328-12fe60f83ab8?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwyfHxzdG9ja3xlbnwwfHx8fDE3NzMxNDkwODZ8MA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:3188,&quot;width&quot;:4800,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;close-up photo of monitor displaying graph&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="close-up photo of monitor displaying graph" title="close-up photo of monitor displaying graph" srcset="https://images.unsplash.com/photo-1560221328-12fe60f83ab8?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwyfHxzdG9ja3xlbnwwfHx8fDE3NzMxNDkwODZ8MA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 424w, https://images.unsplash.com/photo-1560221328-12fe60f83ab8?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwyfHxzdG9ja3xlbnwwfHx8fDE3NzMxNDkwODZ8MA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 848w, https://images.unsplash.com/photo-1560221328-12fe60f83ab8?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwyfHxzdG9ja3xlbnwwfHx8fDE3NzMxNDkwODZ8MA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 1272w, https://images.unsplash.com/photo-1560221328-12fe60f83ab8?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wzMDAzMzh8MHwxfHNlYXJjaHwyfHxzdG9ja3xlbnwwfHx8fDE3NzMxNDkwODZ8MA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=1080 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Photo by <a href="https://unsplash.com/@bash__profile">Nicholas Cappello</a> on <a href="https://unsplash.com">Unsplash</a></figcaption></figure></div><p>The stock market data API landscape is changing. In the past, developers mostly evaluated providers on familiar dimensions: coverage, latency, pricing, documentation, and reliability. Those criteria still matter, but the rise of LLM-powered copilots, autonomous research workflows, and multi-agent financial systems has introduced a new requirement: how easily can a data provider plug into agentic software?</p><p>In that environment, the strongest providers are not just the ones with broad datasets. They are the ones that expose clean, structured interfaces that AI agents can query, reason over, and combine with downstream tools for analysis, monitoring, and decision support. Some vendors are already leaning into this shift with MCP servers and LLM-oriented resources. Others remain stronger as enterprise data backbones than as explicitly AI-native platforms.</p><p>In this article, we will explore the Best Stock Market data API in the AI Agent era. Curious about it? </p><p>Let&#8217;s get into it.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.nb-data.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://www.nb-data.com/subscribe?"><span>Subscribe now</span></a></p><div><hr></div><h1><strong>Alpha Vantage</strong></h1><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!xh-Y!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8d8db652-ca9d-40e1-be34-c06229ff5dc2_2474x1278.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!xh-Y!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8d8db652-ca9d-40e1-be34-c06229ff5dc2_2474x1278.png 424w, https://substackcdn.com/image/fetch/$s_!xh-Y!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8d8db652-ca9d-40e1-be34-c06229ff5dc2_2474x1278.png 848w, https://substackcdn.com/image/fetch/$s_!xh-Y!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8d8db652-ca9d-40e1-be34-c06229ff5dc2_2474x1278.png 1272w, https://substackcdn.com/image/fetch/$s_!xh-Y!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8d8db652-ca9d-40e1-be34-c06229ff5dc2_2474x1278.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!xh-Y!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8d8db652-ca9d-40e1-be34-c06229ff5dc2_2474x1278.png" width="1456" height="752" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/8d8db652-ca9d-40e1-be34-c06229ff5dc2_2474x1278.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:752,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:182857,&quot;alt&quot;:&quot;Best Stock Market data API in the AI Agent era&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.nb-data.com/i/190589167?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8d8db652-ca9d-40e1-be34-c06229ff5dc2_2474x1278.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Best Stock Market data API in the AI Agent era" title="Best Stock Market data API in the AI Agent era" srcset="https://substackcdn.com/image/fetch/$s_!xh-Y!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8d8db652-ca9d-40e1-be34-c06229ff5dc2_2474x1278.png 424w, https://substackcdn.com/image/fetch/$s_!xh-Y!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8d8db652-ca9d-40e1-be34-c06229ff5dc2_2474x1278.png 848w, https://substackcdn.com/image/fetch/$s_!xh-Y!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8d8db652-ca9d-40e1-be34-c06229ff5dc2_2474x1278.png 1272w, https://substackcdn.com/image/fetch/$s_!xh-Y!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8d8db652-ca9d-40e1-be34-c06229ff5dc2_2474x1278.png 1456w" sizes="100vw"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h3><strong>Overview</strong></h3><p><a href="https://www.alphavantage.co/">Alpha Vantage</a> is a widely used financial data platform that provides real-time and historical market data APIs for equities, options, forex, cryptocurrencies, and macroeconomic indicators. The platform is designed to support both individual developers and professional trading systems through a simple, developer-friendly interface and a large catalog of market datasets.<br><br>A distinguishing feature of Alpha Vantage is the breadth of its data coverage. The platform delivers real-time and historical financial market data through programmatic APIs and spreadsheet integrations, enabling developers to build trading dashboards, quantitative research pipelines, and automated trading tools on top of a unified data interface.<br><br>The API also provides a rich library of built-in analytics&#8212;including technical indicators and fundamental datasets&#8212;allowing users to retrieve both raw market data and higher-level financial signals without implementing complex calculations themselves. In practice, this makes Alpha Vantage a flexible backbone for applications ranging from educational projects and fintech prototypes to production trading systems and investment research platforms.</p><h3><strong>What makes it valuable in the AI Agent era?</strong></h3><p>Alpha Vantage has become particularly relevant in the emerging ecosystem of LLM-powered financial tools and autonomous AI agents, largely because it provides structured market data in formats that are easy for agents and models to access, reason over, and integrate into automated workflows.<br><br><strong>1. Native integration with AI agent ecosystems via MCP</strong><br>Alpha Vantage provides an official Model Context Protocol (MCP) server, enabling large language models and agent-based applications to directly access financial data through standardized tools. The MCP server allows AI assistants and development environments to query real-time and historical stock market data programmatically, turning the API into a plug-and-play data source for agentic systems. <br><br><strong>2. Compatibility with multi-agent financial research systems</strong><br>Modern agentic trading frameworks increasingly rely on structured financial APIs like Alpha Vantage as data sources. For example, the open-source <a href="https://trading-agents.ai/">TradingAgents framework</a> simulates a professional trading firm using multiple LLM-powered agents&#8212;such as fundamental analysts, technical analysts, sentiment analysts, traders, and risk managers&#8212;that collaborate to analyze equities and make decisions. This system is powered by Alpha Vantage API as the core data backbone. <br><br><strong>3. Documentation and developer assets optimized for machine consumption</strong><br>Another advantage in the LLM era is the structure and accessibility of Alpha Vantage&#8217;s developer resources. The platform provides comprehensive API documentation, examples, and community libraries across many programming languages, making it straightforward for both humans and AI coding agents to integrate financial data pipelines. Because LLM-powered development tools rely heavily on structured documentation, well-defined API endpoints, and example code, this ecosystem of docs, SDKs, and README files makes Alpha Vantage particularly easy for AI systems to learn and use.</p><h3><strong>In short</strong></h3><p>Alpha Vantage&#8217;s combination of structured financial APIs, an MCP interface for AI agents, and extensive developer documentation positions it as a data infrastructure layer for the emerging generation of AI-powered trading tools, research agents, and autonomous financial analysis systems.</p><div><hr></div><h2>Tradier</h2><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!5L68!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5f43f50f-00df-4007-8f4e-b28403ddf3a0_2399x1459.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!5L68!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5f43f50f-00df-4007-8f4e-b28403ddf3a0_2399x1459.png 424w, https://substackcdn.com/image/fetch/$s_!5L68!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5f43f50f-00df-4007-8f4e-b28403ddf3a0_2399x1459.png 848w, https://substackcdn.com/image/fetch/$s_!5L68!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5f43f50f-00df-4007-8f4e-b28403ddf3a0_2399x1459.png 1272w, https://substackcdn.com/image/fetch/$s_!5L68!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5f43f50f-00df-4007-8f4e-b28403ddf3a0_2399x1459.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!5L68!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5f43f50f-00df-4007-8f4e-b28403ddf3a0_2399x1459.png" width="1456" height="885" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/5f43f50f-00df-4007-8f4e-b28403ddf3a0_2399x1459.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:885,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:848183,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.nb-data.com/i/190589167?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5f43f50f-00df-4007-8f4e-b28403ddf3a0_2399x1459.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!5L68!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5f43f50f-00df-4007-8f4e-b28403ddf3a0_2399x1459.png 424w, https://substackcdn.com/image/fetch/$s_!5L68!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5f43f50f-00df-4007-8f4e-b28403ddf3a0_2399x1459.png 848w, https://substackcdn.com/image/fetch/$s_!5L68!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5f43f50f-00df-4007-8f4e-b28403ddf3a0_2399x1459.png 1272w, https://substackcdn.com/image/fetch/$s_!5L68!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5f43f50f-00df-4007-8f4e-b28403ddf3a0_2399x1459.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h3>Overview</h3><p><a href="https://tradier.com/">Tradier</a> is a brokerage-focused API platform that combines market data, account access, and trading functionality. Its public API supports real-time, delayed, and historical market data through both request/response endpoints and streaming interfaces, while also exposing brokerage capabilities such as account information, positions, orders, watchlists, and trade execution.</p><p>A key differentiator is that Tradier is not just a data API. It is part of a brokerage stack. That means developers can use it not only to retrieve quotes, options chains, time-and-sales data, and historical pricing, but also to connect agentic workflows directly to trading and portfolio actions. Tradier also supports HTTP and WebSocket streaming, which is useful when building systems that need fast updates rather than purely batch-style analysis.</p><p>Tradier&#8217;s market-data positioning is more U.S.-brokerage-centric than broad all-asset-class research platforms. Real-time data is available to Tradier Brokerage account holders for U.S. stocks and options, and delayed data follows the standard 15-minute model for non-real-time access. That makes Tradier particularly compelling for execution-oriented applications rather than for the widest possible global dataset footprint.</p><h3>What makes it valuable in the AI Agent era?</h3><p><strong>1. MCP and LLM-oriented documentation</strong><br>Tradier is unusually forward-leaning in how it presents its docs to the LLM era. Its documentation includes <code>llms.txt</code>, dedicated LLM resources, and a Tradier MCP section. Tradier&#8217;s own MCP documentation says users can access market data, account details, documentation, and even place trades from within connected AI tools. That makes Tradier one of the few providers publicly bridging financial APIs and conversational interfaces in a first-class way.</p><p><strong>2. Strong fit for execution-capable agents</strong><br>Many financial APIs stop at data retrieval. Tradier goes further by combining data access with brokerage actions such as order placement, account history, positions, and balances. In the AI agent era, that matters because the most interesting systems are often not just research agents but action-taking agents. Tradier is therefore especially relevant for developers building guarded execution workflows, trading copilots, or semi-autonomous assistants that need both read and act capabilities.</p><p><strong>3. Streaming interfaces for real-time agent loops</strong><br>Tradier supports both HTTP and WebSocket streaming for market and account data. That is important for agent architectures that continuously monitor events, react to intraday changes, or trigger downstream workflows when market conditions shift. In practical terms, Tradier is better suited than batch-only APIs for event-driven agents that need live context rather than periodic polling alone.</p><h3>In short</h3><p>Tradier is one of the strongest options for AI agents that need to move beyond analysis into brokerage-connected workflows. It may not be the broadest general-purpose research API, but for U.S.-market, execution-aware agents, Tradier&#8217;s mix of market data, account endpoints, streaming support, and MCP/LLM resources makes it highly relevant.</p><div><hr></div><h2>Xignite</h2><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!mBY8!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbcbb256b-df66-44d4-909a-a9faf81debb0_2777x1331.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!mBY8!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbcbb256b-df66-44d4-909a-a9faf81debb0_2777x1331.png 424w, https://substackcdn.com/image/fetch/$s_!mBY8!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbcbb256b-df66-44d4-909a-a9faf81debb0_2777x1331.png 848w, https://substackcdn.com/image/fetch/$s_!mBY8!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbcbb256b-df66-44d4-909a-a9faf81debb0_2777x1331.png 1272w, https://substackcdn.com/image/fetch/$s_!mBY8!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbcbb256b-df66-44d4-909a-a9faf81debb0_2777x1331.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!mBY8!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbcbb256b-df66-44d4-909a-a9faf81debb0_2777x1331.png" width="1456" height="698" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/bcbb256b-df66-44d4-909a-a9faf81debb0_2777x1331.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:698,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:2318388,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.nb-data.com/i/190589167?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbcbb256b-df66-44d4-909a-a9faf81debb0_2777x1331.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!mBY8!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbcbb256b-df66-44d4-909a-a9faf81debb0_2777x1331.png 424w, https://substackcdn.com/image/fetch/$s_!mBY8!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbcbb256b-df66-44d4-909a-a9faf81debb0_2777x1331.png 848w, https://substackcdn.com/image/fetch/$s_!mBY8!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbcbb256b-df66-44d4-909a-a9faf81debb0_2777x1331.png 1272w, https://substackcdn.com/image/fetch/$s_!mBY8!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fbcbb256b-df66-44d4-909a-a9faf81debb0_2777x1331.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h3>Overview</h3><p><a href="https://www.xignite.com/">Xignite</a> is an enterprise financial data platform centered on cloud-delivered APIs and market-data management. Its catalog covers stock quotes, ETFs and mutual funds, foreign exchange, futures and options, indices and benchmarks, fixed income and rates, company fundamentals, reference data, earnings, and news. The company also emphasizes broad upstream sourcing, stating that its data comes from more than 250 providers, alongside curated in-house datasets.</p><p>Xignite&#8217;s public positioning is less &#8220;developer hobbyist API&#8221; and more enterprise-grade market data infrastructure. It highlights unlimited-usage pricing, flexible commercial packaging by asset class, call frequency, and region, and delivery models that include real-time, historical, and reference data. Its developer materials also show a broad set of products for delayed quotes, real-time quotes, historical data, streaming, alerts, IPOs, and company information.</p><p>That means Xignite is best understood as a data platform for institutions and mature fintech products rather than as a lightweight API-first experimentation layer. For many teams, that is a feature, not a drawback. In an AI stack, the most valuable data provider is often the one that can reliably serve as the normalized source behind internal models, orchestration layers, and production analytics systems. This last point is an inference from Xignite&#8217;s product positioning toward scalable enterprise delivery and market-data management.</p><h3>What makes it valuable in the AI Agent era?</h3><p><strong>1. Enterprise-grade breadth for multi-source agent pipelines</strong><br>AI agents become more useful when they can combine quotes, fundamentals, benchmarks, reference data, and news into a single reasoning loop. Xignite&#8217;s catalog is strong on this dimension. Because it covers a wide range of asset classes and reference datasets, it can act as the structured data layer beneath enterprise financial copilots and internal analyst tools.</p><p><strong>2. Strong fit for organizations building their own orchestration layer</strong><br>Unlike Alpha Vantage or EODHD, Xignite&#8217;s public materials emphasize APIs, coverage, and market-data management rather than agent-specific packaging. In practice, that makes it attractive for organizations that want to build their own AI architecture on top of a robust enterprise data backbone instead of depending on vendor-supplied MCP experiences. That is an inference from Xignite&#8217;s public positioning around cloud APIs, data management, and unlimited-usage commercial structure.</p><p><strong>3. Flexible delivery for production-scale systems</strong><br>Xignite supports multiple delivery modes across real-time, delayed, historical, and streaming-style services, and it explicitly markets itself for demanding display applications, backtesting, alerts, and application integration. That flexibility matters in AI systems because not all components need the same data path: one model might need historical fundamentals, another might need event-driven market updates, and a third might need reference data normalization.</p><h3>In short</h3><p>Xignite is not the most visibly AI-marketed provider in this group, but it is a serious contender for enterprise AI finance stacks. If your goal is to build a proprietary agent platform on top of large-scale, normalized market-data services, Xignite&#8217;s breadth and infrastructure orientation make it more compelling than its relative lack of public AI branding might suggest.</p><div><hr></div><h2>EOD Historical Data</h2><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!W-VS!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F488ecb5a-a062-4fc1-8602-441173a0ea4a_2381x1229.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!W-VS!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F488ecb5a-a062-4fc1-8602-441173a0ea4a_2381x1229.png 424w, https://substackcdn.com/image/fetch/$s_!W-VS!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F488ecb5a-a062-4fc1-8602-441173a0ea4a_2381x1229.png 848w, https://substackcdn.com/image/fetch/$s_!W-VS!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F488ecb5a-a062-4fc1-8602-441173a0ea4a_2381x1229.png 1272w, https://substackcdn.com/image/fetch/$s_!W-VS!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F488ecb5a-a062-4fc1-8602-441173a0ea4a_2381x1229.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!W-VS!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F488ecb5a-a062-4fc1-8602-441173a0ea4a_2381x1229.png" width="1456" height="752" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/488ecb5a-a062-4fc1-8602-441173a0ea4a_2381x1229.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:752,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:1281074,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.nb-data.com/i/190589167?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F488ecb5a-a062-4fc1-8602-441173a0ea4a_2381x1229.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!W-VS!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F488ecb5a-a062-4fc1-8602-441173a0ea4a_2381x1229.png 424w, https://substackcdn.com/image/fetch/$s_!W-VS!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F488ecb5a-a062-4fc1-8602-441173a0ea4a_2381x1229.png 848w, https://substackcdn.com/image/fetch/$s_!W-VS!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F488ecb5a-a062-4fc1-8602-441173a0ea4a_2381x1229.png 1272w, https://substackcdn.com/image/fetch/$s_!W-VS!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F488ecb5a-a062-4fc1-8602-441173a0ea4a_2381x1229.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h3>Overview</h3><p><a href="https://eodhd.com/">EOD Historical Data</a>, now commonly presented as EODHD, offers a broad financial data platform spanning fundamentals, historical end-of-day prices, live and real-time feeds, intraday data, U.S. options, financial news, stock screeners, technical indicators, and exchange/reference datasets. On its homepage, the company positions itself as a &#8220;one-stop shop&#8221; for 30+ years of historical, fundamental, and real-time data across global markets, with coverage figures including 60 stock exchanges and 150,000 tickers.</p><p>One of EODHD&#8217;s strengths is that it sits between lightweight developer tools and more professional research infrastructure. It offers structured JSON and CSV responses, coding libraries, spreadsheet add-ons, and a broad menu of market datasets without being limited to only one narrow workflow. It also exposes precomputed technical indicators through API endpoints rather than requiring users to calculate everything from raw time series.</p><p>This combination makes EODHD particularly attractive for builders who want reasonably broad market-data coverage and analytics features in a format that remains accessible to smaller teams, solo developers, and applied AI prototypes.</p><h3>What makes it valuable in the AI Agent era?</h3><p><strong>1. Official MCP support for agent integration</strong><br>EODHD provides an official MCP server for financial data and explicitly documents how to connect it to ChatGPT, Claude, and custom AI agents. The company describes this as a way for AI agents and LLMs to access real-time and historical financial data directly through MCP, making EODHD one of the clearest AI-era data providers alongside Alpha Vantage and Tradier.</p><p><strong>2. An official ChatGPT-oriented financial assistant</strong><br>Beyond MCP, EODHD also offers an official Financial Assistant for ChatGPT, which it describes as an AI that can generate code for EODHD APIs and provide finance insights grounded in real data and news. That does not just signal marketing interest in AI; it suggests the company is actively shaping its product and developer experience around LLM-driven usage patterns.</p><p><strong>3. Strong structured outputs plus higher-level analytics</strong><br>EODHD&#8217;s AI relevance is also practical. It provides structured JSON/CSV outputs, extensive API documentation, libraries, and technical-indicator endpoints that already package financial signals into machine-usable form. For agentic systems, that reduces the burden of transforming raw market data before it can be used in screening, summarization, ranking, or recommendation workflows.</p><h3>In short</h3><p>EODHD is one of the strongest all-around options for the AI agent era. It combines broad market coverage with precomputed indicators, developer-friendly structured data, an official MCP server, and a ChatGPT-oriented assistant. For teams that want something more AI-forward than classic enterprise vendors but broader than a narrow single-purpose API, EODHD is a very strong choice.</p><div><hr></div><h2>QuoteMedia</h2><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!T3Qn!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4a158f85-6722-4e0c-8fb4-5eb4a4b119c6_2613x1409.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!T3Qn!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4a158f85-6722-4e0c-8fb4-5eb4a4b119c6_2613x1409.png 424w, https://substackcdn.com/image/fetch/$s_!T3Qn!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4a158f85-6722-4e0c-8fb4-5eb4a4b119c6_2613x1409.png 848w, https://substackcdn.com/image/fetch/$s_!T3Qn!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4a158f85-6722-4e0c-8fb4-5eb4a4b119c6_2613x1409.png 1272w, https://substackcdn.com/image/fetch/$s_!T3Qn!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4a158f85-6722-4e0c-8fb4-5eb4a4b119c6_2613x1409.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!T3Qn!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4a158f85-6722-4e0c-8fb4-5eb4a4b119c6_2613x1409.png" width="1456" height="785" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/4a158f85-6722-4e0c-8fb4-5eb4a4b119c6_2613x1409.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:785,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:1613479,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.nb-data.com/i/190589167?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4a158f85-6722-4e0c-8fb4-5eb4a4b119c6_2613x1409.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!T3Qn!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4a158f85-6722-4e0c-8fb4-5eb4a4b119c6_2613x1409.png 424w, https://substackcdn.com/image/fetch/$s_!T3Qn!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4a158f85-6722-4e0c-8fb4-5eb4a4b119c6_2613x1409.png 848w, https://substackcdn.com/image/fetch/$s_!T3Qn!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4a158f85-6722-4e0c-8fb4-5eb4a4b119c6_2613x1409.png 1272w, https://substackcdn.com/image/fetch/$s_!T3Qn!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4a158f85-6722-4e0c-8fb4-5eb4a4b119c6_2613x1409.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h3>Overview</h3><p><a href="https://quotemedia.com/">QuoteMedia</a> is a long-standing market-data provider focused on real-time and historical data, news, analytics, and financial information for brokerages, websites, trading systems, and investor-facing products. Its Request APIs and OnDemand services are built around cloud-based access to market data, while its streaming products emphasize tick-by-tick delivery, low latency, and enterprise-grade reliability. QuoteMedia also highlights broad operational scale, including 110+ global exchanges, 200+ data APIs, 99.99% uptime, and 100+ news providers.</p><p>A notable strength of QuoteMedia is delivery flexibility. Its platform spans REST-style OnDemand APIs, WebSocket and other streaming interfaces, and SFTP-based file services for bulk delivery. It also supports JSON, XML, CSV, option-chain data, company profiles, historical time series, filings, and custom calculations. That makes QuoteMedia less of a single API product and more of a market-data delivery platform.</p><p>QuoteMedia&#8217;s public positioning is similar to Xignite in one important way: it is more infrastructure-oriented than explicitly LLM-oriented. In other words, its clearest strengths are reliability, breadth, delivery options, and integration into financial products, not public MCP or agent-marketing. That is an inference from the official materials reviewed.</p><h3>What makes it valuable in the AI Agent era?</h3><p><strong>1. Low-latency data for real-time agent monitoring</strong><br>QuoteMedia&#8217;s streaming stack is designed for real-time or delayed tick-by-tick data, normalized for ease of use and optimized for single-digit millisecond performance. For AI systems that monitor live markets, score signals, or trigger alerts and workflows off intraday movement, that kind of delivery profile is highly relevant.</p><p><strong>2. Multiple delivery modes for different agent architectures</strong><br>Modern AI finance stacks are not monolithic. Some components work best with REST requests, others with streams, and others with bulk files for offline training or evaluation. QuoteMedia supports cloud REST APIs, streaming APIs, and SFTP/file services, which makes it well suited to organizations building layered pipelines that combine real-time agent behavior with batch analytics and historical model development.</p><p><strong>3. Strong fit as a production data layer</strong><br>QuoteMedia offers market data, news, analytics, company profiles, option chains, filings, and historical data in structured formats such as JSON, XML, and CSV. That breadth makes it a useful foundation for internal copilots, research dashboards, summarization systems, and client-facing financial applications where the &#8220;AI&#8221; layer is built on top of the data platform rather than bundled by the vendor itself.</p><h3>In short</h3><p>QuoteMedia is a strong candidate for teams that care more about production-grade delivery and integration flexibility than about whether the vendor has already branded itself around AI agents. In the AI agent era, that still matters a lot: a reliable, low-latency, multi-format market-data backbone can be more valuable than flashy AI positioning if you are building your own orchestration layer.</p><div><hr></div><h2>Conclusion</h2><p>If the goal is to find the most AI-ready providers, Alpha Vantage, Tradier, and EODHD stand out because they already offer MCP or LLM-oriented support. Alpha Vantage is particularly strong for AI-native research tools, Tradier is strong for brokerage-connected agents, and EODHD is a strong general-purpose choice.</p><p>If the goal is enterprise-grade infrastructure for proprietary AI systems, Xignite and QuoteMedia remain highly relevant. They may be less visibly AI-marketed, but they are strong as scalable market data backbones.</p><p>So in the AI agent era, the best stock market data API depends on what you are building. For AI-native financial research, Alpha Vantage has a strong edge. For execution-oriented agents, Tradier stands out. For broad AI-enabled workflows, EODHD is highly competitive. For enterprise infrastructure, Xignite and QuoteMedia are still important players.</p>]]></content:encoded></item><item><title><![CDATA[7 SQL Use Cases Every Data Professional Should Know]]></title><description><![CDATA[Most people learn SQL as syntax. The real thing is knowing what kinds of problems it helps you solve.]]></description><link>https://www.nb-data.com/p/7-sql-use-cases-every-data-professional</link><guid isPermaLink="false">https://www.nb-data.com/p/7-sql-use-cases-every-data-professional</guid><dc:creator><![CDATA[Cornellius Yudha Wijaya]]></dc:creator><pubDate>Sat, 07 Mar 2026 12:19:16 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!9orW!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9bbb6184-6b0e-45de-9ab4-0f7ec96007de_1376x768.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!9orW!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9bbb6184-6b0e-45de-9ab4-0f7ec96007de_1376x768.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!9orW!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9bbb6184-6b0e-45de-9ab4-0f7ec96007de_1376x768.png 424w, https://substackcdn.com/image/fetch/$s_!9orW!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9bbb6184-6b0e-45de-9ab4-0f7ec96007de_1376x768.png 848w, https://substackcdn.com/image/fetch/$s_!9orW!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9bbb6184-6b0e-45de-9ab4-0f7ec96007de_1376x768.png 1272w, https://substackcdn.com/image/fetch/$s_!9orW!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9bbb6184-6b0e-45de-9ab4-0f7ec96007de_1376x768.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!9orW!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9bbb6184-6b0e-45de-9ab4-0f7ec96007de_1376x768.png" width="1376" height="768" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/9bbb6184-6b0e-45de-9ab4-0f7ec96007de_1376x768.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:768,&quot;width&quot;:1376,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:2007256,&quot;alt&quot;:&quot;7 SQL Use Cases Every Data Professional Should Know&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://www.nb-data.com/i/190182290?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9bbb6184-6b0e-45de-9ab4-0f7ec96007de_1376x768.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="7 SQL Use Cases Every Data Professional Should Know" title="7 SQL Use Cases Every Data Professional Should Know" srcset="https://substackcdn.com/image/fetch/$s_!9orW!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9bbb6184-6b0e-45de-9ab4-0f7ec96007de_1376x768.png 424w, https://substackcdn.com/image/fetch/$s_!9orW!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9bbb6184-6b0e-45de-9ab4-0f7ec96007de_1376x768.png 848w, https://substackcdn.com/image/fetch/$s_!9orW!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9bbb6184-6b0e-45de-9ab4-0f7ec96007de_1376x768.png 1272w, https://substackcdn.com/image/fetch/$s_!9orW!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F9bbb6184-6b0e-45de-9ab4-0f7ec96007de_1376x768.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>A lot of people learn SQL in a frustrating way.</p><p>They start with <code>SELECT</code>, <code>FROM</code>, <code>WHERE</code>, <code>GROUP BY</code>, maybe a few joins, and if they stay long enough, a window function or two. They can write queries. They can pass the exercises. But when they face a real business question, they still freeze.</p><p>That usually happens because they learned SQL as a list of clauses instead of a way to think.</p><p>In real work, SQL is rarely about showing that you remember syntax. It is about knowing what question arises once it hits the data. Questions such as:</p><ul><li><p>Is this a reporting problem? </p></li><li><p>A funnel problem? </p></li><li><p>A cohort problem? </p></li><li><p>A segmentation problem? </p></li><li><p>A QA problem? </p></li></ul><p>The moment you can recognize that, SQL becomes much less intimidating and much more useful. That is the shift that matters.</p><p>The people who get genuinely strong at SQL are usually not the people who memorize the most functions. They are the people who can look at a business question and quickly understand what kind of data transformation it needs.</p><p>So instead of thinking about SQL as &#8220;a language I should know,&#8221; I think it is more useful to think about it as a toolkit for a handful of recurring jobs.</p><p>Here are seven of the most important ones. Let&#8217;s get into it.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.nb-data.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://www.nb-data.com/subscribe?"><span>Subscribe now</span></a></p><div><hr></div><h2>1. KPI reporting</h2><p>When teams want to know what is happening in the business, they usually start with some version of a KPI question. Revenue by month. Daily active users. Orders by country. Average order value. Churn rate by plan. Refund rate by product. These are not flashy questions, but they are the foundation of most reporting work.</p><p>This is where SQL starts becoming practical. You are not trying to prove how advanced you are. You are trying to turn raw data into something clear enough for another person to act on.</p><p>That means defining the metric carefully, filtering the right time window, grouping at the right level, and returning a result that is readable. The technical tools are simple, but the judgment behind them matters a lot.</p><p>A lot of people underestimate this kind of SQL because it feels too basic. I think that is a mistake. A team with weak KPI logic usually ends up with weak everything else.</p><p>A simple example is monthly revenue by product category:</p><pre><code>SELECT
    DATE_TRUNC(&#8217;month&#8217;, order_date) AS order_month,
    product_category,
    SUM(revenue) AS total_revenue
FROM orders
WHERE order_date &gt;= DATE &#8216;2026-01-01&#8217;
GROUP BY 1, 2
ORDER BY 1, 3 DESC;</code></pre><p>This is a basic grouped summary, but that is exactly why it matters. A lot of useful SQL is just good filtering, clean aggregation, and returning a table that another person can use.</p><h2>2. Funnel analysis</h2><p>The second major use case is figuring out where people drop off.</p><p>This is where SQL starts feeling very close to product and growth work. A funnel question usually sounds like this: how many users started onboarding, how many completed profile setup, how many created their first project, and how many upgraded? In ecommerce, the same question shows up as view product, add to cart, begin checkout, and pay.</p><p>What makes funnel analysis valuable is that it shows where interest turns into friction.</p><p>A lot of the time, the problem is not &#8220;traffic is low.&#8221; The problem is that the path breaks at one specific step. SQL helps you see that step clearly. It lets you move from a vague sense that &#8220;conversion feels weak&#8221; to a more precise question like &#8220;why do so many users disappear between signup and first action?&#8221;</p><p>A simple event-based funnel might look like this:</p><pre><code>SELECT
    step_name,
    COUNT(DISTINCT user_id) AS users_at_step
FROM onboarding_events
WHERE event_date &gt;= DATE &#8216;2026-03-01&#8217;
GROUP BY 1
ORDER BY
    CASE step_name
        WHEN &#8216;signup&#8217; THEN 1
        WHEN &#8216;verify_email&#8217; THEN 2
        WHEN &#8216;create_project&#8217; THEN 3
        WHEN &#8216;first_active_use&#8217; THEN 4
    END;</code></pre><p>This is not the most advanced funnel query in the world, but it already gives you a clearer conversation. Instead of saying &#8220;activation is weak,&#8221; you can ask, &#8220;Why do so many users disappear between verification and first project creation?&#8221;</p><p>Once you can answer that, the conversation gets much more useful.</p><h2>3. Cohort retention analysis</h2><p>This is one of the most important SQL use cases because it forces better thinking.</p><p>A cohort retention analysis groups users by a shared starting point, then checks whether they come back in later periods. That sounds simple, but it is one of those areas where small definition choices change the whole story. What puts a user into a cohort? What counts as a return? What does a week mean? Should a user count once per week or every time they generate an event?</p><p>That is why good retention work is not mainly about writing SQL. It is about locking the logic before the SQL ever begins.</p><p>This is also where SQL becomes more than a reporting language. It becomes a way of expressing lifecycle behavior. Once you can build a trustworthy retention table, you can stop asking &#8220;are users coming back?&#8221; in a vague way and start asking &#8220;which users are sticking, when do they drop, and what changed across cohorts?&#8221;</p><p>That is one of the reasons I like this use case so much. It pushes people past syntax into actual analytical design.</p><p>A very small example of the logic looks like this:</p><pre><code>WITH user_cohort AS (
    SELECT
        user_id,
        DATE_TRUNC(&#8217;week&#8217;, MIN(login_date)) AS cohort_week
    FROM logins
    GROUP BY 1
),
user_activity AS (
    SELECT
        l.user_id,
        DATE_TRUNC(&#8217;week&#8217;, l.login_date) AS activity_week
    FROM logins l
    GROUP BY 1, 2
)
SELECT
    c.cohort_week,
    a.activity_week,
    COUNT(DISTINCT a.user_id) AS active_users
FROM user_cohort c
JOIN user_activity a
  ON c.user_id = a.user_id
GROUP BY 1, 2
ORDER BY 1, 2;</code></pre><p>This is only the skeleton, not the full retention table. But even here, you can already see the shape: assign the cohort, map later activity, then aggregate by period.</p><p>You can check the deep dive of this use case here:</p><div class="digest-post-embed" data-attrs="{&quot;nodeId&quot;:&quot;a0951efb-026e-4d0d-aeaf-8731e89e2fbc&quot;,&quot;caption&quot;:&quot;Most retention tables are not wrong because the SQL is complicated.&quot;,&quot;cta&quot;:&quot;Read full story&quot;,&quot;showBylines&quot;:true,&quot;size&quot;:&quot;lg&quot;,&quot;isEditorNode&quot;:true,&quot;title&quot;:&quot;Cohort Retention in SQL&quot;,&quot;publishedBylines&quot;:[{&quot;id&quot;:6000855,&quot;name&quot;:&quot;Cornellius Yudha Wijaya&quot;,&quot;bio&quot;:&quot;Sharing Data Knowledge to improve your values&quot;,&quot;photo_url&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/583076b2-657b-44bf-8aa9-9263e5bf04f0_544x544.png&quot;,&quot;is_guest&quot;:false,&quot;bestseller_tier&quot;:null}],&quot;post_date&quot;:&quot;2026-03-06T18:39:19.710Z&quot;,&quot;cover_image&quot;:&quot;https://substackcdn.com/image/fetch/$s_!5opy!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7e76dc69-8ad0-449f-8f74-a8b8885dbdd9_1312x736.jpeg&quot;,&quot;cover_image_alt&quot;:null,&quot;canonical_url&quot;:&quot;https://www.nb-data.com/p/cohort-retention-in-sql&quot;,&quot;section_name&quot;:null,&quot;video_upload_id&quot;:null,&quot;id&quot;:190126571,&quot;type&quot;:&quot;newsletter&quot;,&quot;reaction_count&quot;:1,&quot;comment_count&quot;:0,&quot;publication_id&quot;:37262,&quot;publication_name&quot;:&quot;Non-Brand Data&quot;,&quot;publication_logo_url&quot;:&quot;https://substackcdn.com/image/fetch/$s_!06DP!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F6c0e1cde-d120-4029-8ffd-2a8c7c6e4504_1280x1280.png&quot;,&quot;belowTheFold&quot;:true,&quot;youtube_url&quot;:null,&quot;show_links&quot;:null,&quot;feed_url&quot;:null}"></div><h2>4. Segmentation</h2><p>Once you know the overall number, the next question is almost always: who exactly is driving it?</p><p>That is segmentation.</p><p>Averages are useful, but they hide a lot. SQL becomes much more powerful once you stop treating all users as one group and start cutting the data into meaningful slices. That might mean country, plan, acquisition channel, device type, power users versus casual users, or first purchase month.</p><p>And in practice, this is where a lot of strong SQL users separate themselves. They stop producing one big average and start showing where the business behaves differently across groups.</p><p>A simple segmentation example might be conversion rate by acquisition channel:</p><pre><code>SELECT
    acquisition_channel,
    COUNT(DISTINCT user_id) AS users,
    SUM(CASE WHEN converted = 1 THEN 1 ELSE 0 END) AS converted_users,
    ROUND(
        1.0 * SUM(CASE WHEN converted = 1 THEN 1 ELSE 0 END)
        / COUNT(DISTINCT user_id),
        3
    ) AS conversion_rate
FROM user_conversion_summary
GROUP BY 1
ORDER BY conversion_rate DESC;</code></pre><p>This is where SQL starts feeling strategic. You stop asking, &#8220;Is conversion improving?&#8221; and start asking, &#8220;Is conversion improving for the users we actually care about?&#8221;</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.nb-data.com/p/7-sql-use-cases-every-data-professional?utm_source=substack&utm_medium=email&utm_content=share&action=share&quot;,&quot;text&quot;:&quot;Share&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://www.nb-data.com/p/7-sql-use-cases-every-data-professional?utm_source=substack&utm_medium=email&utm_content=share&action=share"><span>Share</span></a></p><h2>5. Experiment analysis</h2><p>If you work near product or growth teams, SQL becomes very important the moment experiments show up.</p><p>Before anyone talks about significance, lift, or confidence intervals, someone still has to build the dataset properly. Who was in the control group? Who was in the treatment group? Who converted? Over what window? Were there logging issues? Did the assignment logic work as expected?</p><p>A lot of that early work is SQL.</p><p>And this matters more than people think, because if the experiment table is wrong, everything that comes after it is already compromised. If the assignment table is joined incorrectly, if the outcome window is inconsistent, or if duplicated rows quietly inflate conversions, the eventual statistical discussion becomes much less meaningful.</p><p>So even though experiment analysis sounds advanced, a lot of it still comes down to careful SQL habits and clean dataset construction.</p><p>A simple experiment summary might look like this:</p><pre><code>SELECT
    variant,
    COUNT(DISTINCT user_id) AS users,
    SUM(CASE WHEN purchased = 1 THEN 1 ELSE 0 END) AS purchasers,
    ROUND(
        1.0 * SUM(CASE WHEN purchased = 1 THEN 1 ELSE 0 END)
        / COUNT(DISTINCT user_id),
        3
    ) AS purchase_rate
FROM experiment_user_summary
WHERE experiment_name = &#8216;checkout_redesign_v1&#8217;
GROUP BY 1
ORDER BY 1;</code></pre><p>That is not the full experiment analysis, but it is the foundation.</p><h2>6. Data quality and QA checks</h2><p>This is one of the least glamorous SQL use cases, and one of the most valuable.</p><p>A huge amount of trust in data work comes from catching bad structure early. Duplicate rows. Missing keys. Broken joins. Sudden changes in counts. Tables that stopped updating. Records that should be impossible but somehow exist anyway.</p><p>SQL is excellent for this kind of work because it is good at isolating patterns, comparing counts, checking coverage, and surfacing anomalies before they become reporting problems.</p><p>This is also one of the places where data professionals become more mature in practice. They stop using SQL only to answer the question they were asked, and they start using SQL to challenge whether the dataset itself deserves trust.</p><p>That is a very different mindset.</p><p>Once you develop it, your work usually becomes much more reliable.</p><p>For example, if you want to check for duplicate order IDs:</p><pre><code>SELECT
    order_id,
    COUNT(*) AS row_count
FROM orders
GROUP BY 1
HAVING COUNT(*) &gt; 1
ORDER BY row_count DESC;</code></pre><p>This is basic, but incredibly useful. </p><h2>7. Operational monitoring</h2><p>The last use case is the one that makes SQL feel closest to the day-to-day operating layer of a business.</p><p>Sometimes the question is not &#8220;what happened this quarter?&#8221; Sometimes the question is &#8220;did the pipeline run?&#8221;, &#8220;are transactions missing?&#8221;, &#8220;did yesterday&#8217;s volume collapse?&#8221;, or &#8220;did a critical table stop refreshing?&#8221;</p><p>At that point, SQL is not just helping with analysis. It is helping keep the system honest.</p><p>This kind of work often lives somewhere between analytics, operations, and data engineering. You are comparing expected versus actual counts, checking daily or weekly movement, and trying to spot problems before somebody else finds them in a broken dashboard or an angry meeting.</p><p>If you only think of SQL as a tool for reports, you miss how often it becomes part of the business&#8217;s operational nervous system.</p><p>A simple monitoring query might compare day-over-day order counts:</p><pre><code>SELECT
    order_date,
    COUNT(*) AS orders_today,
    LAG(COUNT(*)) OVER (ORDER BY order_date) AS orders_yesterday
FROM orders
GROUP BY 1
ORDER BY 1;</code></pre><p>This is where window functions become especially useful. They let you compare each row to related rows while keeping the row-level result visible, which is exactly the kind of thing you want for trend and monitoring work. </p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.nb-data.com/p/7-sql-use-cases-every-data-professional/comments&quot;,&quot;text&quot;:&quot;Leave a comment&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://www.nb-data.com/p/7-sql-use-cases-every-data-professional/comments"><span>Leave a comment</span></a></p><h2>The bigger point</h2><p>If you look across all seven use cases, the pattern is pretty clear.</p><p>SQL is rarely valuable because of its isolated syntax.</p><p>It is valuable because the same small set of ideas keeps getting reused across real work.</p><p>That is why strong SQL users usually do not sound like they are reciting functions. They sound like they understand data shape.</p><p>That is a much better goal than &#8220;learn more SQL syntax.&#8221;</p><h2>Where to go next</h2><p>If you are still early, I would not try to learn every advanced clause in one sitting.</p><p>I would focus on connecting SQL to actual problems.</p><p>That is exactly why I built the SQL track into the NBD Focus Map. The point is not to learn SQL randomly. The point is to see how the pieces fit together and start shipping small, useful work with them.</p><h2>Start here</h2><p>If you want the broader path, start with the <strong>Focus Map</strong>:<br></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.nb-data.com/p/nbd-focus-map-free-pdf&quot;,&quot;text&quot;:&quot;Focus Map&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://www.nb-data.com/p/nbd-focus-map-free-pdf"><span>Focus Map</span></a></p><p><br>If you want the full paid system, use:</p><ul><li><p><strong>Vault:</strong> <a href="https://www.nb-data.com/p/nbd-reading-vault-paid-guided-paths">https://www.nb-data.com/p/nbd-reading-vault-paid-guided-paths</a></p></li><li><p><strong>Template Index:</strong> <a href="https://www.nb-data.com/p/template-pack-index-paid">https://www.nb-data.com/p/template-pack-index-paid</a></p></li><li><p><strong>Subscriber Benefits:</strong> <a href="https://www.nb-data.com/p/subscriber-benefits?utm_source=chatgpt.com">https://www.nb-data.com/p/subscriber-benefits</a></p></li></ul>]]></content:encoded></item><item><title><![CDATA[Cohort Retention in SQL]]></title><description><![CDATA[From Raw Events to a Decision-Ready Table]]></description><link>https://www.nb-data.com/p/cohort-retention-in-sql</link><guid isPermaLink="false">https://www.nb-data.com/p/cohort-retention-in-sql</guid><dc:creator><![CDATA[Cornellius Yudha Wijaya]]></dc:creator><pubDate>Fri, 06 Mar 2026 18:39:19 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!5opy!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7e76dc69-8ad0-449f-8f74-a8b8885dbdd9_1312x736.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!5opy!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7e76dc69-8ad0-449f-8f74-a8b8885dbdd9_1312x736.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!5opy!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7e76dc69-8ad0-449f-8f74-a8b8885dbdd9_1312x736.jpeg 424w, https://substackcdn.com/image/fetch/$s_!5opy!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7e76dc69-8ad0-449f-8f74-a8b8885dbdd9_1312x736.jpeg 848w, https://substackcdn.com/image/fetch/$s_!5opy!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7e76dc69-8ad0-449f-8f74-a8b8885dbdd9_1312x736.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!5opy!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7e76dc69-8ad0-449f-8f74-a8b8885dbdd9_1312x736.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!5opy!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7e76dc69-8ad0-449f-8f74-a8b8885dbdd9_1312x736.jpeg" width="1312" height="736" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/7e76dc69-8ad0-449f-8f74-a8b8885dbdd9_1312x736.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:736,&quot;width&quot;:1312,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:89400,&quot;alt&quot;:&quot;Cohort Retention in SQL&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://www.nb-data.com/i/190126571?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7e76dc69-8ad0-449f-8f74-a8b8885dbdd9_1312x736.jpeg&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Cohort Retention in SQL" title="Cohort Retention in SQL" srcset="https://substackcdn.com/image/fetch/$s_!5opy!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7e76dc69-8ad0-449f-8f74-a8b8885dbdd9_1312x736.jpeg 424w, https://substackcdn.com/image/fetch/$s_!5opy!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7e76dc69-8ad0-449f-8f74-a8b8885dbdd9_1312x736.jpeg 848w, https://substackcdn.com/image/fetch/$s_!5opy!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7e76dc69-8ad0-449f-8f74-a8b8885dbdd9_1312x736.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!5opy!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F7e76dc69-8ad0-449f-8f74-a8b8885dbdd9_1312x736.jpeg 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Most retention tables are not wrong because the SQL is complicated.</p><p>They are wrong because the definitions are loose.</p><p>Someone says, &#8220;Let&#8217;s look at retention,&#8221; a query gets written, a heatmap shows up in a dashboard, and suddenly everyone is talking about Week 1 and Month 1 as if those numbers are objective facts. They usually are not. They are the result of choices. What counts as the start of a user&#8217;s journey? What counts as a return? What exactly is a week? What timezone are we using? Are we measuring one user once per period, or accidentally counting heavy users multiple times?</p><p>That is the real work in cohort retention. Not the division. Not the pivot table. The real work is deciding what story the table is allowed to tell.</p><p>At its core, cohort analysis is simple. You group users by a shared starting point, then measure what those users do in later periods. That is the common backbone behind most cohort SQL tutorials and warehouse implementations.</p><p>What makes it tricky is that small choices can change the story enough to change the decision.</p><p>So in this piece, I want to show you how I think about cohort retention in SQL when I want something that is not just presentable, but actually trustworthy. We will walk through a small sample dataset, turn it into a retention table step by step, and discuss the parts that often go wrong: cohort definition, return-event design, week boundaries, duplicate activity, partial cohorts, and interpretation.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.nb-data.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://www.nb-data.com/subscribe?"><span>Subscribe now</span></a></p><div><hr></div><h2>Start with the question, not the query</h2><p>Before touching SQL, I like to ask one uncomfortable question:</p><p><strong>What exactly do I want this retention table to help me decide?</strong></p><p>That question matters because different cohort definitions answer different business questions.</p><p>If I group users by the week they signed up, I am usually asking something about onboarding, activation, or acquisition quality. I want to know whether new users are sticking around after entering the funnel.</p><p>If I group users by the week they first did something meaningful, I am asking something slightly different. I am saying that signup is not the real beginning of value. Maybe the real beginning is the first login, the first purchase, the first report built, or the first document uploaded. In that case, I am less interested in the funnel entry and more interested in what happens once a user actually starts using the product.</p><p>Both are valid. But they are not interchangeable.</p><p>The same holds for the return event. If I define retention as &#8220;any page view,&#8221; my table might look reassuring while hiding the fact that users are not doing anything meaningful. If I define retention as &#8220;purchase,&#8221; the metric might be more valuable but also much sparser. There is no universally correct event. There is only one event that is more or less aligned with the value loop you care about.</p><p>Then there is the time bucket. This is the part people often treat as neutral, even though it really isn&#8217;t. A daily retention table tells a different story than a weekly one. A weekly table tells a different story than a monthly one. And even the idea of a &#8220;week&#8221; is less fixed than people think. BigQuery, for example, distinguishes between <code>WEEK</code>, <code>WEEK(&lt;WEEKDAY&gt;)</code>, and <code>ISOWEEK</code>, and those choices affect how dates are grouped and how period differences are calculated.</p><p>That is why I think of cohort retention as a design problem before I think of it as a SQL problem.</p><h2>The version we&#8217;re building here</h2><p>To make this concrete, let&#8217;s keep the example small and explicit.</p><p>In this walkthrough:</p><ul><li><p>A user&#8217;s cohort is the <strong>week of their first login</strong></p></li><li><p>Retention means they performed a <strong>login</strong> in a later week</p></li><li><p>The table uses <strong>calendar weeks</strong></p></li><li><p>Each user should count at most <strong>once per week</strong></p></li></ul><p>That last condition matters a lot. If a user logs in ten times in the same week, they are still one retained user for that week. Retention is about whether someone came back in the period, not how noisy their event stream was.</p><h2>Sample data</h2><p>Here is a tiny events table we can use end-to-end.</p>
      <p>
          <a href="https://www.nb-data.com/p/cohort-retention-in-sql">
              Read more
          </a>
      </p>
   ]]></content:encoded></item><item><title><![CDATA[Template Pack Index (Paid)]]></title><description><![CDATA[Last updated: 2 Apr 2026]]></description><link>https://www.nb-data.com/p/template-pack-index-paid</link><guid isPermaLink="false">https://www.nb-data.com/p/template-pack-index-paid</guid><dc:creator><![CDATA[Cornellius Yudha Wijaya]]></dc:creator><pubDate>Sun, 01 Mar 2026 15:43:57 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!06DP!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F6c0e1cde-d120-4029-8ffd-2a8c7c6e4504_1280x1280.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>This is the index of all NBD Template Packs.</p><p>Templates are practical assets (docs/checklists/sheets) you can reuse to ship faster.</p>
      <p>
          <a href="https://www.nb-data.com/p/template-pack-index-paid">
              Read more
          </a>
      </p>
   ]]></content:encoded></item><item><title><![CDATA[NBD Reading Vault (Paid): Guided Paths + Mini-Projects]]></title><description><![CDATA[Last updated: 24 Feb 2026]]></description><link>https://www.nb-data.com/p/nbd-reading-vault-paid-guided-paths</link><guid isPermaLink="false">https://www.nb-data.com/p/nbd-reading-vault-paid-guided-paths</guid><dc:creator><![CDATA[Cornellius Yudha Wijaya]]></dc:creator><pubDate>Tue, 24 Feb 2026 12:45:05 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!06DP!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fbucketeer-e05bbc84-baa3-437e-9518-adb32be77984.s3.amazonaws.com%2Fpublic%2Fimages%2F6c0e1cde-d120-4029-8ffd-2a8c7c6e4504_1280x1280.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>This Vault is the paid navigation layer of Non-Brand Data.</p><p>If you feel overwhelmed by the archive, use this page instead:</p><ol><li><p>Pick one track (SQL / Python+ML / RAG)</p></li><li><p>Follow the reading order</p></li><li><p>Ship one mini-project at the end</p></li></ol>
      <p>
          <a href="https://www.nb-data.com/p/nbd-reading-vault-paid-guided-paths">
              Read more
          </a>
      </p>
   ]]></content:encoded></item><item><title><![CDATA[✨Subscriber Benefits]]></title><description><![CDATA[Everything included in Non-Brand Data. Updated: February 2026]]></description><link>https://www.nb-data.com/p/subscriber-benefits</link><guid isPermaLink="false">https://www.nb-data.com/p/subscriber-benefits</guid><dc:creator><![CDATA[Cornellius Yudha Wijaya]]></dc:creator><pubDate>Sun, 22 Feb 2026 13:26:51 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!_8Co!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd1b33d87-8050-4869-9be7-989f15554517_1533x593.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h1>Non-Brand Data Subscriber Benefits</h1><p>This is the up-to-date summary of what you get as a free reader, paid member, or founding member. I keep this post updated when something changes.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!_8Co!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd1b33d87-8050-4869-9be7-989f15554517_1533x593.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!_8Co!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd1b33d87-8050-4869-9be7-989f15554517_1533x593.png 424w, https://substackcdn.com/image/fetch/$s_!_8Co!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd1b33d87-8050-4869-9be7-989f15554517_1533x593.png 848w, https://substackcdn.com/image/fetch/$s_!_8Co!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd1b33d87-8050-4869-9be7-989f15554517_1533x593.png 1272w, https://substackcdn.com/image/fetch/$s_!_8Co!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd1b33d87-8050-4869-9be7-989f15554517_1533x593.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!_8Co!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd1b33d87-8050-4869-9be7-989f15554517_1533x593.png" width="1456" height="563" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/d1b33d87-8050-4869-9be7-989f15554517_1533x593.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:563,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:67585,&quot;alt&quot;:&quot;Non-Brand Data Subscriber Benefits&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://www.nb-data.com/i/188513081?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd1b33d87-8050-4869-9be7-989f15554517_1533x593.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Non-Brand Data Subscriber Benefits" title="Non-Brand Data Subscriber Benefits" srcset="https://substackcdn.com/image/fetch/$s_!_8Co!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd1b33d87-8050-4869-9be7-989f15554517_1533x593.png 424w, https://substackcdn.com/image/fetch/$s_!_8Co!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd1b33d87-8050-4869-9be7-989f15554517_1533x593.png 848w, https://substackcdn.com/image/fetch/$s_!_8Co!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd1b33d87-8050-4869-9be7-989f15554517_1533x593.png 1272w, https://substackcdn.com/image/fetch/$s_!_8Co!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd1b33d87-8050-4869-9be7-989f15554517_1533x593.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h2>Full version</h2><h3>Free subscribers</h3><p>Start here: <strong><a href="https://www.nb-data.com/p/nbd-focus-map-free-pdf?r=3kmaf">NBD Focus Map (Free PDF)</a></strong></p><p>What you get:</p><ul><li><p>Focus Map plus the best posts in order</p></li><li><p>Public posts and the public archive</p></li><li><p>Subscriber chat and comment threads</p></li></ul><h3>Paid members</h3><p>What you get:</p><ul><li><p>Member-only deep-dive posts plus full archive</p></li><li><p>Monthly template pack</p></li><li><p>Vault reading list page</p></li></ul><div><hr></div><h3>Founding members</h3><p>What you get:</p><ul><li><p>Everything in Paid</p></li><li><p>Priority feedback plus one annual review call</p></li></ul><p>The annual review call is for one project or portfolio entry. We focus on what to change so it is hiring-manager-ready.</p><div><hr></div><h2>One-time purchases (optional)</h2><p>These are separate from subscriptions. Buy once and reuse anytime.</p><ul><li><p><strong><a href="https://cornelliusyudhawijay.gumroad.com/l/otdloq">Portfolio Rubric Toolkit</a></strong></p></li><li><p><strong><a href="https://cornelliusyudhawijay.gumroad.com/">Data Science Resume Template</a> (FREE)</strong></p></li><li><p><strong><a href="https://cornelliusyudhawijay.gumroad.com/l/hdhuw">Python Packages to Learn Data Science (e-book) </a>(FREE)</strong></p></li></ul><div><hr></div><h2>How to redeem your benefits</h2><h3>All subscribers</h3><ul><li><p><strong><a href="https://www.nb-data.com/p/nbd-focus-map-free-pdf?r=3kmaf">Focus Map</a></strong></p></li><li><p><strong><a href="https://www.nb-data.com/p/welcome-to-non-brand-data-by-cornellius">Start Here</a></strong></p></li></ul><h3>Paid members</h3><ul><li><p>Access member-only posts by logging in with the email you used to subscribe</p></li><li><p>Template packs are delivered by email and will also be collected in one place as the vault grows</p></li><li><p>The member vault reading list will be added here once it is published</p></li></ul><h3>Founding members</h3><p>Reply to any email with the subject: <strong>Founding review</strong><br>Include:</p><ul><li><p>a link to your project/repo/write-up</p></li><li><p>What do you want feedback on. I will send the booking link, and we will schedule the 30 minutes.</p></li></ul><div><hr></div><p><strong>Note:</strong> If you ever feel lost in the archive, do not scroll. Start with the Focus Map, or use the Vault reading list.</p><div><hr></div>]]></content:encoded></item><item><title><![CDATA[Creating a Daily Bulk Ingestion Pipeline for Historical Price Data and Fundamentals]]></title><description><![CDATA[Automate your financial information in your database]]></description><link>https://www.nb-data.com/p/creating-a-daily-bulk-ingestion-pipeline</link><guid isPermaLink="false">https://www.nb-data.com/p/creating-a-daily-bulk-ingestion-pipeline</guid><dc:creator><![CDATA[Cornellius Yudha Wijaya]]></dc:creator><pubDate>Thu, 19 Feb 2026 10:25:55 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!kUfK!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faaad14e7-a6b1-4ea7-8eda-de1a540b1f9e_1120x706.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!kUfK!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faaad14e7-a6b1-4ea7-8eda-de1a540b1f9e_1120x706.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!kUfK!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faaad14e7-a6b1-4ea7-8eda-de1a540b1f9e_1120x706.jpeg 424w, https://substackcdn.com/image/fetch/$s_!kUfK!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faaad14e7-a6b1-4ea7-8eda-de1a540b1f9e_1120x706.jpeg 848w, https://substackcdn.com/image/fetch/$s_!kUfK!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faaad14e7-a6b1-4ea7-8eda-de1a540b1f9e_1120x706.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!kUfK!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faaad14e7-a6b1-4ea7-8eda-de1a540b1f9e_1120x706.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!kUfK!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faaad14e7-a6b1-4ea7-8eda-de1a540b1f9e_1120x706.jpeg" width="1120" height="706" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/aaad14e7-a6b1-4ea7-8eda-de1a540b1f9e_1120x706.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:706,&quot;width&quot;:1120,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!kUfK!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faaad14e7-a6b1-4ea7-8eda-de1a540b1f9e_1120x706.jpeg 424w, https://substackcdn.com/image/fetch/$s_!kUfK!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faaad14e7-a6b1-4ea7-8eda-de1a540b1f9e_1120x706.jpeg 848w, https://substackcdn.com/image/fetch/$s_!kUfK!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faaad14e7-a6b1-4ea7-8eda-de1a540b1f9e_1120x706.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!kUfK!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faaad14e7-a6b1-4ea7-8eda-de1a540b1f9e_1120x706.jpeg 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Photo by <a href="https://unsplash.com/@iridial_?utm_source=medium&amp;utm_medium=referral">iridial</a> on <a href="https://unsplash.com/?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure></div><p>In the finance field, we are usually trying to answer two related questions at the same time:</p><ul><li><p>What did the market do?</p></li><li><p>What did the business do?</p></li></ul><p>Prices move every trading day, reflecting new information and expectations. However, fundamentals update more slowly and in batches because public companies report on a cycle (e.g., U.S. issuers file Form 10-Q after the first three fiscal quarters and an annual 10-K). This becomes a pain point when we are doing valuation and screening reviews, as we need to pull the data at a specific time, but that time can become inconsistent.</p><p>This is why a daily ingestion pipeline exists. It gives us a consistent record that we reuse without re-downloading or questioning what we just pulled. Instead of relying on a live fetch each time, we can maintain a small local dataset that updates on schedule and is ready for further processing.</p><p>In this article, we will learn how to develop a daily bulk ingestion pipeline for historical price and fundamental data using source data from <a href="https://site.financialmodelingprep.com/?utm_source=medium&amp;utm_medium=medium&amp;utm_campaign=corn11">Financial Modelling Prep (FMP)</a>.</p><p>Curious about it? Let&#8217;s get into it.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.nb-data.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://www.nb-data.com/subscribe?"><span>Subscribe now</span></a></p><div><hr></div><h2><strong>Foundation</strong></h2><p>Before we move into the implementation details, it helps to treat this project as an ingestion layer built on top of an external data provider. Building this layer on top of the <a href="https://site.financialmodelingprep.com/?utm_source=medium&amp;utm_medium=medium&amp;utm_campaign=corn11">Financial Modeling Prep (FMP)</a> API offers several practical benefits for financial analysis work.</p><p>First, it reduces duplication by reusing steps for requesting data, validating responses, standardising column names, and applying rules (e.g., date handling) for each symbol.</p><p>Second, it creates a single control point for the workflow, centralizing API key handling and daily logic rather than duplicating logic across scripts.</p><p>Third, it provides a stable historical record by maintaining a local dataset rather than recomputing results from live calls, thereby simplifying research and reporting.</p><p>Finally, it supports routine operation with two phases: an initial backfill to build historical coverage and a daily run to keep data current. Once scheduled, the dataset is automatically updated, ensuring a reliable workflow.</p><h2><strong>The Data Source</strong></h2><p>Let&#8217;s start building our daily ingestion pipeline by deciding which datasets we will pull from FMP. In this project, all data comes from FMP&#8217;s Stable API, which uses a single base URL and a consistent URL pattern:</p><pre><code>https://financialmodelingprep.com/stable/</code></pre><p>In practice, FMP provides many endpoints, but this pipeline intentionally uses only a small subset. The goal is to identify the minimum datasets required to build a reliable store of historical prices and core financial statements, without introducing optional datasets that complicate maintenance.</p><p>For this pipeline, we rely on these endpoints:</p><ul><li><p><strong>Company search</strong> (<code>search-symbol</code>): Lets you search by company name or partial ticker and returns candidates with symbols, names, exchanges, and currencies.</p></li><li><p><strong>Company profile</strong> (<code>profile</code>): Returns the baseline company metadata you typically want to store alongside your price and fundamentals tables.</p></li><li><p><strong>Income statement</strong> (<code>income-statement</code>): Provides revenue, net income, and other income statement fields over time.</p></li><li><p><strong>Balance sheet statement </strong>(<code>balance-sheet-statement</code>): Provides assets, liabilities, and equity fields that help you understand the company&#8217;s financial position.</p></li><li><p><strong>Cash flow statement</strong> (<code>cash-flow-statement</code>): Provides operating, investing, and financing cash flow fields, which are essential for evaluating cash generation and sustainability.</p></li><li><p><strong>Historical end-of-day prices</strong> (<code>historical-price-eod/full</code>): Provides daily OHLCV and related fields for historical price storage.</p></li></ul><p>These datasets are sufficient to build a clean ingestion pipeline that stores daily prices by date and financial statements by reporting period, while keeping the system simple and easy to run every day.</p><h2><strong>Project structure</strong></h2><p>This project is intentionally organised to separate the application, data storage, and entry points.</p><p>A simplified view of the project looks like this:</p><pre><code>fmp<em>_daily_</em>ingestion/
&#9500;&#9472; .github/
&#9474;  &#9492;&#9472; workflows/
&#9474;     &#9492;&#9472; daily<em>_ingestion.yml
&#9500;&#9472; app/
&#9474;  &#9500;&#9472; <strong>__init__</strong>.py
&#9474;  &#9500;&#9472; db.py
&#9474;  &#9500;&#9472; fmp_</em>client.py
&#9474;  &#9500;&#9472; pipeline.py
&#9474;  &#9492;&#9472; settings.py
&#9500;&#9472; data/
&#9474;  &#9500;&#9472; fmp.sqlite3
&#9474;  &#9492;&#9472; scheduler.log
&#9500;&#9472; scripts/
&#9474;  &#9500;&#9472; <strong>__init__</strong>.py
&#9474;  &#9500;&#9472; backfill<em>_symbols.py
&#9474;  &#9500;&#9472; backfill_</em>prices.py
&#9474;  &#9500;&#9472; run<em>_daily.py
&#9474;  &#9500;&#9472; scheduler.py
&#9474;  &#9492;&#9472; check_</em>db.py
&#9500;&#9472; .env
&#9492;&#9472; requirements.txt</code></pre><p>Once we establish the project foundations, we will build our daily ingestion pipeline.</p><h2><strong>Step-by-Step Walkthrough</strong></h2><p>In this section, we will go through how our daily ingestion pipeline is built in each step.</p><h3><strong>Step 1: define dependencies and configuration</strong></h3><p>First, we set up the <code>requirements.txt</code>file by keeping the dependencies minimal.</p><pre><code>requests
python-dotenv
pandas
schedule</code></pre><p>We also define our <code>.env</code> file which will supply runtime configuration without hardcoding secrets or machine-specific paths into code.</p><pre><code>FMP_API_KEY=YOUR_KEY
FMP_STABLE_BASE_URL=https://financialmodelingprep.com/stable
DB_PATH=data/fmp.sqlite3

FMP_WATCHLIST=AAPL,MSFT,TSLA
FUNDAMENTALS_PERIODS_TO_REFRESH=4

REQUEST_TIMEOUT=30
REQUEST_SLEEP=0.15</code></pre><p>FMP&#8217;s Stable API uses a single base URL and authentication through an API key passed as a query parameter.</p><h3><strong>Step 2: Establish a single configuration contract</strong></h3><p>Next, we will create a <code>settings.py</code>which would help every script and module read the configuration consistently. These settings will do the following:</p><ul><li><p>load <code>.env</code></p></li><li><p>validate required values (especially <code>FMP_API_KEY</code>)</p></li><li><p>provide defaults for optional settings</p></li></ul><p>Our implementations will be looks like this:</p><pre><code># app/settings.py
import os
from dotenv import load_dotenv

# Load .env file explicitly
load_dotenv()

FMP_API_KEY = os.getenv(&#8221;FMP_API_KEY&#8221;)
if not FMP_API_KEY:
    raise RuntimeError(&#8221;Missing FMP_API_KEY. Set it as an environment variable or in .env file.&#8221;)

# Use Stable for fundamentals, V3 for historical prices (free-friendly).
FMP_STABLE_BASE_URL = os.getenv(&#8221;FMP_STABLE_BASE_URL&#8221;, &#8220;https://financialmodelingprep.com/stable&#8221;).rstrip(&#8221;/&#8221;)
FMP_V3_BASE_URL = os.getenv(&#8221;FMP_V3_BASE_URL&#8221;, &#8220;https://financialmodelingprep.com/api/v3&#8221;).rstrip(&#8221;/&#8221;)

WATCHLIST = [s.strip().upper() for s in os.getenv(&#8221;FMP_WATCHLIST&#8221;, &#8220;AAPL,MSFT,TSLA&#8221;).split(&#8221;,&#8221;) if s.strip()]

DB_PATH = os.getenv(&#8221;DB_PATH&#8221;, &#8220;data/fmp.sqlite3&#8221;)

# Daily fundamentals: fetch last N rows and upsert (simple + idempotent).
FUNDAMENTALS_PERIODS_TO_REFRESH = int(os.getenv(&#8221;FUNDAMENTALS_PERIODS_TO_REFRESH&#8221;, &#8220;4&#8221;))

REQUEST_TIMEOUT = int(os.getenv(&#8221;REQUEST_TIMEOUT&#8221;, &#8220;30&#8221;))
REQUEST_SLEEP = float(os.getenv(&#8221;REQUEST_SLEEP&#8221;, &#8220;0.15&#8221;))</code></pre><p>This becomes the project&#8217;s control plane, as if you later run the project locally, in GitHub Actions, or under a scheduler, you do not change any application code, only environment values.</p><h3><strong>Step 3: Implement a Stable API client</strong></h3><p>In this section, we will build our client script in the <code>fmp_client.py.</code>The client should be the only script that knows how to:</p><ul><li><p>build Stable URLs</p></li><li><p>attach <code>apikey=...</code></p></li><li><p>enforce timeouts and basic pacing</p></li><li><p>raise clear errors when a request fails</p></li></ul><p>The code we used will look like this:</p><pre><code>from __future__ import annotations

import os
import time
from typing import Any, Dict, Optional

import requests
from urllib3.util import Retry
from requests.adapters import HTTPAdapter

from app.settings import FMP_API_KEY, FMP_STABLE_BASE_URL, REQUEST_TIMEOUT, REQUEST_SLEEP


class FMPClient:
    &#8220;&#8221;&#8220;
    Stable-only client (current docs):
      Base URL: https://financialmodelingprep.com/stable/
      Auth: apikey=&lt;YOUR_KEY&gt;

    Stable quickstart confirms base URL + apikey query auth.
    Historical EOD endpoint lives under Stable as well.
    &#8220;&#8221;&#8220;

    def __init__(
        self,
        api_key: Optional[str] = None,
        stable_base_url: Optional[str] = None,
        v3_base_url: Optional[str] = None, 
        timeout_s: Optional[int] = None,
        sleep_s: Optional[float] = None,
        session: Optional[requests.Session] = None,
    ) -&gt; None:
        self.api_key = (api_key or FMP_API_KEY or &#8220;&#8221;).strip()
        if not self.api_key:
            raise RuntimeError(&#8221;Missing FMP_API_KEY. Set it in .env or environment variables.&#8221;)

        self.base_url = (stable_base_url or FMP_STABLE_BASE_URL or &#8220;https://financialmodelingprep.com/stable&#8221;).rstrip(&#8221;/&#8221;)
        self.timeout_s = int(timeout_s if timeout_s is not None else REQUEST_TIMEOUT)
        self.sleep_s = float(sleep_s if sleep_s is not None else REQUEST_SLEEP)
        
        self.session = session or requests.Session()
        if not session:
            # Configure retries
            retry_strategy = Retry(
                total=5,
                backoff_factor=1,
                status_forcelist=[429, 500, 502, 503, 504],
                allowed_methods=[&#8221;GET&#8221;],
                raise_on_status=True
            )
            adapter = HTTPAdapter(max_retries=retry_strategy)
            self.session.mount(&#8221;https://&#8221;, adapter)
            self.session.mount(&#8221;http://&#8221;, adapter)

    def _get_json(self, endpoint: str, params: Optional[Dict[str, Any]] = None) -&gt; Any:
        params = dict(params or {})
        params[&#8221;apikey&#8221;] = self.api_key

        url = f&#8221;{self.base_url}/{endpoint.lstrip(&#8217;/&#8217;)}&#8221;
        resp = self.session.get(url, params=params, timeout=self.timeout_s)

        if resp.status_code == 402:
            raise RuntimeError(f&#8221;FMP 402 (Restricted Endpoint) for {url}: {resp.text[:300]}&#8221;)

        if not resp.ok:
            raise RuntimeError(f&#8221;FMP error {resp.status_code} for {url}: {resp.text[:300]}&#8221;)

        if self.sleep_s &gt; 0:
            time.sleep(self.sleep_s)

        return resp.json()

    # Symbols
    def fetch_financial_statement_symbol_list(self) -&gt; Any:
        &#8220;&#8221;&#8220;/stable/financial-statement-symbol-list&#8221;&#8220;&#8221;
        return self._get_json(&#8221;financial-statement-symbol-list&#8221;)

    def fetch_profile(self, symbol: str) -&gt; Any:
        &#8220;&#8221;&#8220;/stable/profile?symbol=AAPL&#8221;&#8220;&#8221;
        return self._get_json(&#8221;profile&#8221;, {&#8221;symbol&#8221;: symbol.upper()})

    # Prices (Stable)
    def fetch_historical_price_eod_full(
        self,
        symbol: str,
        date_from: Optional[str] = None,
        date_to: Optional[str] = None,
    ) -&gt; Any:
        &#8220;&#8221;&#8220;
        Stable historical EOD (full):
          /historical-price-eod/full?symbol=AAPL
        &#8220;&#8221;&#8220;
        params: Dict[str, Any] = {&#8221;symbol&#8221;: symbol.upper()}
        if date_from:
            params[&#8221;from&#8221;] = date_from
        if date_to:
            params[&#8221;to&#8221;] = date_to
        return self._get_json(&#8221;historical-price-eod/full&#8221;, params)

    # Fundamentals (Stable)
    def fetch_income_statement(self, symbol: str) -&gt; Any:
        return self._get_json(&#8221;income-statement&#8221;, {&#8221;symbol&#8221;: symbol.upper()})

    def fetch_balance_sheet(self, symbol: str) -&gt; Any:
        return self._get_json(&#8221;balance-sheet-statement&#8221;, {&#8221;symbol&#8221;: symbol.upper()})

    def fetch_cash_flow(self, symbol: str) -&gt; Any:
        return self._get_json(&#8221;cash-flow-statement&#8221;, {&#8221;symbol&#8221;: symbol.upper()})</code></pre><p>These endpoints correspond directly to the Stable documentation for company profile, income statement, and historical EOD prices.</p><h3><strong>Step 4: define the schema and write for the data storage</strong></h3><p>In this section, we will define what we store and how we update it safely within the <code>db.py</code>file.</p><p>The code implementation will be as follows:</p><pre><code>import sqlite3
import json
from datetime import datetime
from typing import Optional, Sequence, Tuple


DDL = &#8220;&#8221;&#8220;
CREATE TABLE IF NOT EXISTS symbols (
  symbol TEXT PRIMARY KEY,
  name TEXT,
  exchange TEXT,
  currency TEXT
);

CREATE TABLE IF NOT EXISTS prices_eod (
  symbol TEXT NOT NULL,
  date TEXT NOT NULL,
  open REAL,
  high REAL,
  low REAL,
  close REAL,
  volume REAL,
  PRIMARY KEY (symbol, date)
);

CREATE TABLE IF NOT EXISTS financials (
  symbol TEXT NOT NULL,
  period_end_date TEXT NOT NULL,
  statement_type TEXT NOT NULL,
  year INTEGER,
  period TEXT,
  payload_json TEXT NOT NULL,
  PRIMARY KEY (symbol, period_end_date, statement_type)
);
&#8220;&#8221;&#8220;


def connect(db_path: str) -&gt; sqlite3.Connection:
    import os
    os.makedirs(os.path.dirname(db_path), exist_ok=True)
    conn = sqlite3.connect(db_path)
    conn.execute(&#8221;PRAGMA journal_mode=WAL;&#8221;)
    conn.execute(&#8221;PRAGMA synchronous=NORMAL;&#8221;)
    return conn


def init_db(conn: sqlite3.Connection) -&gt; None:
    conn.executescript(DDL)
    conn.commit()


def upsert_symbols(conn: sqlite3.Connection, rows: Sequence[Tuple]) -&gt; None:
    conn.executemany(
        &#8220;&#8221;&#8220;
        INSERT INTO symbols (symbol, name, exchange, currency)
        VALUES (?, ?, ?, ?)
        ON CONFLICT(symbol) DO UPDATE SET
            name=excluded.name,
            exchange=excluded.exchange,
            currency=excluded.currency
        &#8220;&#8221;&#8220;,
        rows,
    )
    conn.commit()


def upsert_prices(conn: sqlite3.Connection, rows: Sequence[Tuple]) -&gt; None:
    conn.executemany(
        &#8220;&#8221;&#8220;
        INSERT INTO prices_eod (symbol, date, open, high, low, close, volume)
        VALUES (?, ?, ?, ?, ?, ?, ?)
        ON CONFLICT(symbol, date) DO UPDATE SET
            open=excluded.open,
            high=excluded.high,
            low=excluded.low,
            close=excluded.close,
            volume=excluded.volume
        &#8220;&#8221;&#8220;,
        rows,
    )
    conn.commit()


def upsert_financials(conn: sqlite3.Connection, rows: Sequence[Tuple]) -&gt; None:
    conn.executemany(
        &#8220;&#8221;&#8220;
        INSERT INTO financials (symbol, period_end_date, statement_type, year, period, payload_json)
        VALUES (?, ?, ?, ?, ?, ?)
        ON CONFLICT(symbol, period_end_date, statement_type) DO UPDATE SET
            year=excluded.year,
            period=excluded.period,
            payload_json=excluded.payload_json
        &#8220;&#8221;&#8220;,
        rows,
    )
    conn.commit()


def read_symbols(conn: sqlite3.Connection, limit: Optional[int] = None) -&gt; list[str]:
    q = &#8220;SELECT symbol FROM symbols ORDER BY symbol&#8221;
    if limit:
        q += &#8220; LIMIT ?&#8221;
        cur = conn.execute(q, (limit,))
    else:
        cur = conn.execute(q)
    return [r[0] for r in cur.fetchall()]</code></pre><p>The code above is designed as follows:</p><ul><li><p><code>symbols</code> is the reference table</p></li><li><p><code>prices_eod</code> stores daily OHLCV keyed by <code>(symbol, date)</code></p></li><li><p><code>financials</code> stores statement rows keyed by <code>(symbol, period_end_date, statement_type)</code></p></li></ul><p>The purpose of this layer is not only persistence but also operational reliability. With primary keys and upserts in place, we can rerun backfills and daily jobs without creating duplicates.</p><h3><strong>Step 5: Convert API responses into data rows</strong></h3><p>In this section, we will define the <code>pipeline.py</code>where this script defines the ingestion rules. The script should do the following:</p><ol><li><p>normalize FMP response shapes</p></li><li><p>shape raw records into tuples that match table definitions</p></li><li><p>return those tuples so the DB layer can upsert them</p></li></ol><p>The whole code implementation is as follows:</p><pre><code>from __future__ import annotations

import json
import sqlite3
from typing import Any, Dict, Iterable, List, Optional, Tuple

from app.fmp_client import FMPClient
from app.db import upsert_symbols, upsert_prices, upsert_financials, read_symbols


def _as_list(payload: Any) -&gt; List[Dict[str, Any]]:
    &#8220;&#8221;&#8220;
    Stable endpoints typically return a JSON array.
    This helper makes the pipeline robust if the response is wrapped.
    &#8220;&#8221;&#8220;
    if isinstance(payload, list):
        return [x for x in payload if isinstance(x, dict)]
    if isinstance(payload, dict):
        for key in (&#8221;data&#8221;, &#8220;results&#8221;, &#8220;historical&#8221;):
            v = payload.get(key)
            if isinstance(v, list):
                return [x for x in v if isinstance(x, dict)]
    return []


# 1) Symbols

def seed_symbols(conn: sqlite3.Connection, client: FMPClient, symbols: Optional[Iterable[str]] = None) -&gt; int:
    &#8220;&#8221;&#8220;
    Seeds the symbols table. If symbols are provided, it enriches them via /profile.
    If none provided, it could fetch a global list (but free tier usually restricts this).
    Returns count of symbols processed.
    &#8220;&#8221;&#8220;
    if symbols:
        rows: List[Tuple] = []
        for s in symbols:
            sym = s.strip().upper()
            if not sym:
                continue
            prof = client.fetch_profile(sym)
            p = _as_list(prof)
            row = p[0] if p else {}
            
            name = row.get(&#8221;companyName&#8221;) or row.get(&#8221;name&#8221;)
            exchange = row.get(&#8221;exchange&#8221;) or row.get(&#8221;exchangeShortName&#8221;)
            currency = row.get(&#8221;currency&#8221;)
            
            rows.append((sym, name, exchange, currency))
        
        if rows:
            upsert_symbols(conn, rows)
        return len(rows)
    else:
        # Fallback to fetching a list if possible (Stable API allows financial-statement-symbol-list)
        payload = client.fetch_financial_statement_symbol_list()
        items = _as_list(payload)
        rows = []
        for r in items:
            sym = (r.get(&#8221;symbol&#8221;) or r.get(&#8221;ticker&#8221;) or &#8220;&#8221;).strip().upper()
            if not sym:
                continue
            rows.append((
                sym,
                r.get(&#8221;name&#8221;) or r.get(&#8221;companyName&#8221;),
                r.get(&#8221;exchange&#8221;) or r.get(&#8221;exchangeShortName&#8221;),
                r.get(&#8221;currency&#8221;)
            ))
        if rows:
            upsert_symbols(conn, rows)
        return len(rows)

# 2) Prices

def backfill_prices_for_symbol(
    client: FMPClient,
    symbol: str,
    date_from: Optional[str] = None,
    date_to: Optional[str] = None,
    timeseries: Optional[int] = None,  # Legacy, ignored or used as slice
) -&gt; List[Tuple]:
    &#8220;&#8221;&#8220;
    Returns rows for upsert_prices:
      (symbol, date, open, high, low, close, volume)
    &#8220;&#8221;&#8220;
    sym = symbol.strip().upper()
    payload = client.fetch_historical_price_eod_full(sym, date_from=date_from, date_to=date_to)
    bars = _as_list(payload)

    if timeseries:
        bars = bars[-int(timeseries):]

    out: List[Tuple] = []
    for b in bars:
        dt = b.get(&#8221;date&#8221;) or b.get(&#8221;datetime&#8221;) or b.get(&#8221;time&#8221;)
        if not dt:
            continue
        out.append((
            sym,
            str(dt),
            b.get(&#8221;open&#8221;),
            b.get(&#8221;high&#8221;),
            b.get(&#8221;low&#8221;),
            b.get(&#8221;close&#8221;),
            b.get(&#8221;volume&#8221;)
        ))
    return out


def ingest_prices_for_date(
    conn: sqlite3.Connection,
    client: FMPClient,
    symbols: Iterable[str],
    target_date: str
) -&gt; int:
    &#8220;&#8221;&#8220;
    Daily run: Fetch exactly one day per symbol and upsert.
    &#8220;&#8221;&#8220;
    total = 0
    for s in symbols:
        rows = backfill_prices_for_symbol(client, s, date_from=target_date, date_to=target_date)
        if rows:
            upsert_prices(conn, rows)
            total += len(rows)
    return total

# 3) Fundamentals
def refresh_fundamentals(
    conn: sqlite3.Connection,
    client: FMPClient,
    symbols: Iterable[str],
    last_n: int = 4
) -&gt; int:
    &#8220;&#8221;&#8220;
    Refreshes the latest N financial statements for a watchlist.
    &#8220;&#8221;&#8220;
    total = 0
    for s in symbols:
        sym = s.strip().upper()
        bundles = [
            (&#8221;income_statement&#8221;, client.fetch_income_statement(sym)),
            (&#8221;balance_sheet&#8221;, client.fetch_balance_sheet(sym)),
            (&#8221;cash_flow&#8221;, client.fetch_cash_flow(sym)),
        ]

        rows_to_upsert = []
        for statement_type, payload in bundles:
            rows = _as_list(payload)
            for r in rows[: int(last_n)]:
                period_end = r.get(&#8221;date&#8221;)
                if not period_end:
                    continue
                
                year = r.get(&#8221;calendarYear&#8221;) or r.get(&#8221;year&#8221;)
                period = r.get(&#8221;period&#8221;)
                
                rows_to_upsert.append((
                    sym,
                    str(period_end),
                    statement_type,
                    year,
                    period,
                    json.dumps(r, ensure_ascii=False)
                ))
        
        if rows_to_upsert:
            upsert_financials(conn, rows_to_upsert)
            total += len(rows_to_upsert)
            
    return total</code></pre><p>From there, the pipeline functions become our project lifecycle:</p><ul><li><p><strong>Symbols seeding</strong> enriches a watchlist using the profile endpoint and creates rows for <code>symbols</code>. The profile endpoint is documented with <code>symbol</code> as a required query parameter.</p></li><li><p><strong>Price backfill</strong> fetches historical EOD bars, maps each bar to <code>(symbol, date, open, high, low, close, volume)</code>, then returns rows to be upserted into <code>prices_eod</code>.</p></li><li><p><strong>Daily ingestion</strong> uses the same shaping rules but narrows the request to a single target date (typically yesterday), ensuring the daily mode is not a separate system but a constrained version of the same ingestion path.</p></li><li><p><strong>Fundamentals refresh</strong> fetches the latest statement rows and stores them under a composite key.</p></li></ul><p>The central principle is consistency for all the data we acquired from the FMP API.</p><h3><strong>Step 6: Create runnable entry points</strong></h3><p>The scripts folder exists so we can run the pipeline without writing the code each time. Each script should follow the same pattern:</p><ul><li><p>import settings</p></li><li><p>connect and initialise DB</p></li><li><p>instantiate <code>FMPClient</code></p></li><li><p>call pipeline functions</p></li><li><p>upsert results</p></li><li><p>print a concise summary</p></li></ul><p>In this project, the scripts map directly to operational phases:</p><ul><li><p><code>backfill_symbols.py</code> seeds your <code>symbols</code> table from <code>WATCHLIST</code> :</p></li></ul><pre><code>import sys
from pathlib import Path

# Add project root to sys.path
sys.path.append(str(Path(__file__).parent.parent))

from app.settings import (
    DB_PATH, FMP_API_KEY, FMP_STABLE_BASE_URL, FMP_V3_BASE_URL, WATCHLIST
)
from app.db import connect, init_db
from app.fmp_client import FMPClient
from app.pipeline import seed_symbols


def main():
    conn = connect(DB_PATH)
    init_db(conn)

    client = FMPClient(
        api_key=FMP_API_KEY,
        stable_base_url=FMP_STABLE_BASE_URL,
        v3_base_url=FMP_V3_BASE_URL,
    )

    n = seed_symbols(conn, client, WATCHLIST)
    print(f&#8221;Seeded {n} symbols into DB ({DB_PATH}) from WATCHLIST.&#8221;)


if __name__ == &#8220;__main__&#8221;:
    main()</code></pre><ul><li><p><code>backfill_prices.py</code> performs historical loading for <code>prices_eod</code></p></li></ul><pre><code>import sys
from pathlib import Path

# Add project root to sys.path
sys.path.append(str(Path(__file__).parent.parent))

import argparse

from app.settings import (
    DB_PATH, FMP_API_KEY, FMP_STABLE_BASE_URL, FMP_V3_BASE_URL, WATCHLIST
)
from app.db import connect, init_db, read_symbols, upsert_prices
from app.fmp_client import FMPClient
from app.pipeline import backfill_prices_for_symbol


def main() -&gt; None:
    ap = argparse.ArgumentParser()
    ap.add_argument(&#8221;--limit&#8221;, type=int, default=None, help=&#8221;Backfill only first N symbols from DB&#8221;)
    ap.add_argument(&#8221;--symbols&#8221;, type=str, default=None, help=&#8221;Comma-separated tickers (overrides WATCHLIST)&#8221;)

    # Optional: limit how much history you pull
    ap.add_argument(&#8221;--from-date&#8221;, type=str, default=None, help=&#8221;YYYY-MM-DD&#8221;)
    ap.add_argument(&#8221;--to-date&#8221;, type=str, default=None, help=&#8221;YYYY-MM-DD&#8221;)
    ap.add_argument(&#8221;--timeseries&#8221;, type=int, default=None, help=&#8221;Return last N days&#8221;)

    args = ap.parse_args()

    conn = connect(DB_PATH)
    init_db(conn)

    if args.symbols:
        symbols = [s.strip().upper() for s in args.symbols.split(&#8221;,&#8221;) if s.strip()]
    else:
        # Defaults to watchlist if DB is empty or use current symbols
        db_syms = read_symbols(conn, limit=args.limit)
        symbols = db_syms if db_syms else WATCHLIST

    client = FMPClient(
        api_key=FMP_API_KEY,
        stable_base_url=FMP_STABLE_BASE_URL,
        v3_base_url=FMP_V3_BASE_URL,
    )

    total_rows = 0
    for i, sym in enumerate(symbols, 1):
        rows = backfill_prices_for_symbol(
            client,
            sym,
            date_from=args.from_date,
            date_to=args.to_date,
            timeseries=args.timeseries,
        )
        if rows:
            upsert_prices(conn, rows)
            total_rows += len(rows)

        if i % 25 == 0:
            print(f&#8221;Processed {i}/{len(symbols)} symbols...&#8221;)

    print(f&#8221;Done. Upserted {total_rows} price rows.&#8221;)


if __name__ == &#8220;__main__&#8221;:
    main()</code></pre><ul><li><p><code>run_daily.py</code> runs the daily refresh (yesterday&#8217;s prices + latest fundamentals)</p></li></ul><pre><code>import sys
from pathlib import Path

# Add project root to sys.path
sys.path.append(str(Path(__file__).parent.parent))

import datetime as dt

from app.settings import (
    DB_PATH, FMP_API_KEY, FMP_STABLE_BASE_URL, FMP_V3_BASE_URL, WATCHLIST, FUNDAMENTALS_PERIODS_TO_REFRESH
)
from app.db import connect, init_db
from app.fmp_client import FMPClient
from app.pipeline import ingest_prices_for_date, refresh_fundamentals


def main():
    # Defensive check: today - 1 day
    target_date = (dt.date.today() - dt.timedelta(days=1)).isoformat()

    conn = connect(DB_PATH)
    init_db(conn)

    client = FMPClient(
        api_key=FMP_API_KEY,
        stable_base_url=FMP_STABLE_BASE_URL,
        v3_base_url=FMP_V3_BASE_URL,
    )

    n_prices = ingest_prices_for_date(conn, client, WATCHLIST, target_date)
    n_fin = refresh_fundamentals(conn, client, WATCHLIST, last_n=FUNDAMENTALS_PERIODS_TO_REFRESH)

    print(f&#8221;[{target_date}] upserted {n_prices} price rows and {n_fin} fundamentals rows.&#8221;)


if __name__ == &#8220;__main__&#8221;:
    main()</code></pre><ul><li><p><code>scheduler.py</code> runs <code>run_daily.py</code> on a local schedule and logs output</p></li></ul><pre><code>import sys
from pathlib import Path

# Add project root to sys.path
sys.path.append(str(Path(__file__).parent.parent))

import time
import schedule
import subprocess
import logging

# Configure logging
logging.basicConfig(
    level=logging.INFO,
    format=&#8217;%(asctime)s - %(levelname)s - %(message)s&#8217;,
    handlers=[
        logging.FileHandler(&#8221;data/scheduler.log&#8221;),
        logging.StreamHandler()
    ]
)

def run_job():
    logging.info(&#8221;Starting daily ingestion job...&#8221;)
    try:
        # Run run_daily.py as a subprocess
        result = subprocess.run(
            [sys.executable, &#8220;scripts/run_daily.py&#8221;],
            capture_output=True,
            text=True,
            check=True
        )
        logging.info(f&#8221;Job completed successfully:\n{result.stdout}&#8221;)
    except subprocess.CalledProcessError as e:
        logging.error(f&#8221;Job failed with error:\n{e.stderr}&#8221;)
    except Exception as e:
        logging.error(f&#8221;An unexpected error occurred: {e}&#8221;)

def main():
    # Schedule the job for 01:00 AM every day
    # You can change this time as needed
    schedule.every().day.at(&#8221;01:00&#8221;).do(run_job)
    
    logging.info(&#8221;Scheduler started. Ingestion job scheduled for 01:00 AM daily.&#8221;)
    logging.info(&#8221;Press Ctrl+C to exit.&#8221;)

    try:
        while True:
            schedule.run_pending()
            time.sleep(60) # Check every minute
    except KeyboardInterrupt:
        logging.info(&#8221;Scheduler stopped by user.&#8221;)

if __name__ == &#8220;__main__&#8221;:
    main()</code></pre><ul><li><p><code>check_db.py</code> verifies table counts, date ranges, and recent rows</p></li></ul><pre><code>import sys
from pathlib import Path

# Add project root to sys.path
sys.path.append(str(Path(__file__).parent.parent))

import sqlite3
import pandas as pd
from app.settings import DB_PATH

def main():
    print(f&#8221;Checking database at: {DB_PATH}&#8221;)
    
    con = sqlite3.connect(DB_PATH)

    try:
        print(&#8221;\n--- Row Counts ---&#8221;)
        print(pd.read_sql(&#8221;SELECT COUNT(*) AS n FROM symbols&#8221;, con))
        print(pd.read_sql(&#8221;SELECT COUNT(*) AS n FROM prices_eod&#8221;, con))
        print(pd.read_sql(&#8221;SELECT COUNT(*) AS n FROM financials&#8221;, con))

        print(&#8221;\n--- Price Statistics ---&#8221;)
        print(pd.read_sql(&#8221;SELECT MIN(date) AS min_date, MAX(date) AS max_date FROM prices_eod&#8221;, con))
        
        print(&#8221;\n--- Recent Prices (Last 5) ---&#8221;)
        print(pd.read_sql(&#8221;SELECT * FROM prices_eod ORDER BY date DESC LIMIT 5&#8221;, con))
        
        print(&#8221;\n--- Fundamentals Breakdown ---&#8221;)
        print(pd.read_sql(&#8221;SELECT statement_type, COUNT(*) AS n FROM financials GROUP BY statement_type&#8221;, con))
    except Exception as e:
        print(f&#8221;Error checking DB: {e}&#8221;)
    finally:
        con.close()

if __name__ == &#8220;__main__&#8221;:
    main()</code></pre><p>This separation keeps the project maintainable and we are able to improve the pipeline in the future.</p><h3><strong>Step 7: The database generation</strong></h3><p>The <code>data/</code> folder will contain the generated state:</p><ul><li><p><code>fmp.sqlite3</code> (Our SQLite database)</p></li><li><p><code>scheduler.log</code> (Our local scheduler audit trail, if you use it)</p></li></ul><p>Nothing in <code>data/</code> should be required for understanding the code. It is the product of running the pipeline.</p><h3><strong>Step 8: Scheduling (local or GitHub Actions)</strong></h3><p>We have two scheduling modes, which run locally or using GitHub Actions.</p><ul><li><p><strong>Local scheduling</strong> (<code>scripts/scheduler.py</code>) triggers the daily job at a fixed time and writes logs to <code>data/scheduler.log</code>. It is the simplest option when you control the machine.</p></li><li><p><strong>GitHub Actions scheduling</strong> (<code>.github/workflows/daily_ingestion.yml</code>) runs the same daily script on a cron schedule and stores the SQLite database as a workflow artifact. GitHub&#8217;s scheduled workflows are driven by cron syntax and operate in UTC. We can use the YAML file:</p></li></ul><pre><code>name: Daily Data Ingestion

on:
  schedule:
    # Runs at 02:00 UTC every day
    - cron: &#8216;0 2 * * *&#8217;
  workflow_dispatch:
    # Allows manual triggering

jobs:
  ingest:
    runs-on: ubuntu-latest

    steps:
    - name: Checkout code
      uses: actions/checkout@v3

    - name: Set up Python
      uses: actions/setup-python@v4
      with:
        python-version: &#8216;3.10&#8217;

    - name: Install dependencies
      run: |
        python -m pip install --upgrade pip
        pip install -r requirements.txt

    - name: Run daily ingestion
      env:
        FMP_API_KEY: ${{ secrets.FMP_API_KEY }}
        FMP_WATCHLIST: ${{ vars.FMP_WATCHLIST }}
        DB_PATH: data/fmp.sqlite3
      run: |
        python scripts/run_daily.py

    - name: Upload database
      uses: actions/upload-artifact@v3
      with:
        name: fmp-database
        path: data/fmp.sqlite3</code></pre><p>The important part is that both modes execute the same <code>run_daily.py</code> entry point and therefore share the same ingestion behaviour.</p><p>That is all for the project structure that we built for the daily ingestion pipeline. In the next section, we will go through how to run them step-by-step.</p><h2><strong>Running the scripts</strong></h2><p>All operational entry points is exist in the <code>scripts</code>folder. Each script adds the project root to <code>sys.path</code>, so the recommended way to execute them is from the repository root using <code>python scripts/&lt;name&gt;.py</code>.</p><h3><strong>1. Install dependencies</strong></h3><p>From the project root:</p><pre><code>pip install -r requirements.txt</code></pre><p>This installs the minimal runtime stack (<code>requests</code>, <code>python-dotenv</code>, <code>pandas</code>, <code>schedule</code>).</p><h3><strong>2. Configure </strong><code>.env</code></h3><p>Before running anything, ensure <code>.env</code> defines at least:</p><ul><li><p><code>FMP_API_KEY</code> (Acquired the key from the <a href="https://site.financialmodelingprep.com/developer/docs">FMP site</a>)</p></li><li><p><code>FMP_WATCHLIST</code> (comma-separated tickers)</p></li><li><p><code>DB_PATH</code> (for example <code>data/fmp.sqlite3</code>)</p></li></ul><p>Our scripts read these values through <code>app/settings.py</code> and use them consistently across the pipeline.</p><h3><strong>3. Seed symbols into the database</strong></h3><p>Run the following script:</p><pre><code>python scripts/backfill_symbols.py</code></pre><p>This script connects to the SQLite database at <code>DB_PATH</code>, initializes the schema, instantiates <code>FMPClient</code>, and seeds the <code>symbols</code> table using your <code>WATCHLIST</code>.</p><p>When it completes, it prints a confirmation of the number of symbols seeded and the database file used. Something like:</p><pre><code>Done. Upserted 3768 price rows.</code></pre><h3><strong>4. Backfill historical prices</strong></h3><p>Run the following script:</p><pre><code>python scripts/backfill_prices.py</code></pre><p>This script is the one-time historical loader for <code>prices_eod</code>. It also initializes the database schema before writing. The example result is shown below:</p><pre><code>Seeded 3 symbols into DB (data/fmp.sqlite3) from WATCHLIST.</code></pre><p>Symbol selection follows the rule: if you provide <code>--symbols</code>, it uses that list; otherwise, it reads from the database and falls back to <code>WATCHLIST</code> if the database is empty.</p><p>You can keep the backfill controlled during testing or writing by using the optional arguments defined in the script:</p><pre><code># Backfill only specific tickers
python scripts/backfill_prices.py --symbols AAPL,MSFT

# Backfill only first N symbols read from the DB
python scripts/backfill_prices.py --limit 10

# Limit history by date range
python scripts/backfill_prices.py --symbols AAPL --from-date 2024-01-01 --to-date 2024-12-31

# Limit history by &#8220;last N days&#8221; returned
python scripts/backfill_prices.py --symbols AAPL --timeseries 200</code></pre><p>These flags correspond directly to the script&#8217;s argument parser (<code>--limit</code>, <code>--symbols</code>, <code>--from-date</code>, <code>--to-date</code>, <code>--timeseries</code>).</p><p>During execution, it prints progress every 25 symbols and ends with the total number of upserted price rows.</p><h3><strong>5. Run the daily ingestion job</strong></h3><p>Run the following script:</p><pre><code>python scripts/run_daily.py</code></pre><p>This is the daily operational entry point, where it computes the<code>target_date</code> as today minus one day, then performs two actions, which are price ingestion for that date and refreshes fundamentals for the watchlist. The fundamentals refresh window is controlled by <code>FUNDAMENTALS_PERIODS_TO_REFRESH</code>.</p><p>For example, the result is as following:</p><pre><code>[2026-02-13] upserted 3 price rows and 36 fundamentals rows.</code></pre><h3><strong>6. Verify what was stored in SQLite</strong></h3><p>Run the following script:</p><pre><code>python scripts/check_db.py</code></pre><p>This script is your verification tool. It prints row counts for <code>symbols</code>, <code>prices_eod</code>, and <code>financials</code>, shows min/max dates in <code>prices_eod</code>, prints the last five price rows, and summarizes fundamentals by <code>statement_type</code>.</p><p>The example result is as following:</p><pre><code>Checking database at: data/fmp.sqlite3

--- Row Counts ---
   n
0  3
      n
0  3777
    n
0  36

--- Price Statistics ---
     min_date    max_date
0  2021-02-10  2026-02-13

--- Recent Prices (Last 5) ---
  symbol        date    open    high     low   close      volume
0   AAPL  2026-02-13  262.01  262.23  255.45  255.78  54927132.0
1   MSFT  2026-02-13  404.45  405.54  398.05  401.32  33949805.0
2   TSLA  2026-02-13  414.31  424.06  410.88  417.44  50565054.0
3   AAPL  2026-02-12  275.59  275.72  260.18  261.73  81077229.0
4   MSFT  2026-02-12  405.00  406.20  398.01  401.84  40802400.0

--- Fundamentals Breakdown ---
     statement_type   n
0     balance_sheet  12
1         cash_flow  12
2  income_statement  12</code></pre><p>This script is used for a quick check after backfills or the daily job.</p><h3><strong>7. Automate the daily run</strong></h3><p>First, let&#8217;s take a look at the local scheduler, which runs on our machine:</p><p>Run the following script:</p><pre><code>python scripts/scheduler.py</code></pre><p>This schedules the job daily at<strong> </strong>01:00 AM<strong> </strong>and runs <code>scripts/run_daily.py</code> as a subprocess, writing logs to <code>data/scheduler.log</code> and to stdout.</p><h3><strong>GitHub Actions (hosted schedule)</strong></h3><p>The workflow runs at <strong>02:00 UTC </strong>daily, sets <code>FMP_API_KEY</code>, <code>FMP_WATCHLIST</code>, and <code>DB_PATH=data/fmp.sqlite3</code>, then executes <code>python scripts/run_daily.py</code> and uploads the SQLite file as an artifact. This script runs only when we push it to the GitHub repository.</p><p>That&#8217;s all you need to understand how to build the daily ingestion pipeline with FMP.</p><h2><strong>Conclusion</strong></h2><p>In this article, we have learned how to build a small but reliable daily ingestion workflow that keeps two core financial datasets current: end-of-day prices and company fundamentals.</p><p>By relying on <a href="https://site.financialmodelingprep.com/?utm_source=medium&amp;utm_medium=medium&amp;utm_campaign=corn11">Financial Modeling Prep</a>&#8217;s Stable API as the single upstream source, the pipeline remains consistent in how it authenticates, requests data, and standardizes responses, while remaining practical for routine use in research, screening, and internal analytics.</p><p>I hope it has helped!</p>]]></content:encoded></item><item><title><![CDATA[How to Build an Earnings Briefing Engine Using the FMP API]]></title><description><![CDATA[A repeatable pipeline that turns earnings prep into one-page briefs]]></description><link>https://www.nb-data.com/p/how-to-build-an-earnings-briefing</link><guid isPermaLink="false">https://www.nb-data.com/p/how-to-build-an-earnings-briefing</guid><dc:creator><![CDATA[Cornellius Yudha Wijaya]]></dc:creator><pubDate>Fri, 13 Feb 2026 02:21:05 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!O0Vg!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0f2d5638-c795-4b9b-9326-14bd513f3b6c_1120x747.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!O0Vg!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0f2d5638-c795-4b9b-9326-14bd513f3b6c_1120x747.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!O0Vg!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0f2d5638-c795-4b9b-9326-14bd513f3b6c_1120x747.jpeg 424w, https://substackcdn.com/image/fetch/$s_!O0Vg!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0f2d5638-c795-4b9b-9326-14bd513f3b6c_1120x747.jpeg 848w, https://substackcdn.com/image/fetch/$s_!O0Vg!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0f2d5638-c795-4b9b-9326-14bd513f3b6c_1120x747.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!O0Vg!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0f2d5638-c795-4b9b-9326-14bd513f3b6c_1120x747.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!O0Vg!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0f2d5638-c795-4b9b-9326-14bd513f3b6c_1120x747.jpeg" width="1120" height="747" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/0f2d5638-c795-4b9b-9326-14bd513f3b6c_1120x747.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:747,&quot;width&quot;:1120,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!O0Vg!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0f2d5638-c795-4b9b-9326-14bd513f3b6c_1120x747.jpeg 424w, https://substackcdn.com/image/fetch/$s_!O0Vg!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0f2d5638-c795-4b9b-9326-14bd513f3b6c_1120x747.jpeg 848w, https://substackcdn.com/image/fetch/$s_!O0Vg!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0f2d5638-c795-4b9b-9326-14bd513f3b6c_1120x747.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!O0Vg!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0f2d5638-c795-4b9b-9326-14bd513f3b6c_1120x747.jpeg 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Photo by <a href="https://unsplash.com/@jakubzerdzicki?utm_source=medium&amp;utm_medium=referral">Jakub &#379;erdzicki</a> on <a href="https://unsplash.com/?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure></div><p>Earnings weeks are a compression problem. Many companies report within a short window, yet the preparation work is scattered across multiple sources.</p><p>The workflow is repetitive and time-sensitive, especially when you follow more than a few tickers. When you assemble earnings context one company at a time, you repeat the same steps for every symbol. Over time, briefings become inconsistent because each run follows a slightly different process.</p><p>This is where an earnings briefing engine becomes useful. It converts an ad hoc workflow into a repeatable pipeline, producing a consistent one-page brief for each ticker. It also makes the process easier to audit and extend over time.</p><p>In this article, we will build a minimal earnings briefing engine using <a href="https://site.financialmodelingprep.com/?utm_source=medium&amp;utm_medium=medium&amp;utm_campaign=corn10">Financial Modeling Prep&#8217;s</a> stable endpoints.</p><p>Curious about it? Let&#8217;s get into it.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.nb-data.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://www.nb-data.com/subscribe?"><span>Subscribe now</span></a></p><h2><strong>Foundation</strong></h2><p>An earnings briefing engine is a compact workflow that summarizes the key information you need before an earnings event. It does not attempt to forecast returns, and it does not replace deep research. However, the purpose is more operational as it creates a briefing document we can store and reuse.</p><p>The Earnings Briefing Engine that we will build comprises three things:</p><ul><li><p>Fetch upcoming earnings events for a date window using the earnings calendar endpoint.</p></li><li><p>Build a standardized per-ticker bundle that captures event context, expectations, and recent financial performance.</p></li><li><p>Generate a consistent one-page briefing in a simple format.</p></li></ul><h2><strong>The Data Source</strong></h2><p>This project uses <a href="https://site.financialmodelingprep.com/?utm_source=medium&amp;utm_medium=medium&amp;utm_campaign=corn10">Financial Modeling Prep (FMP)</a> as the primary data source. FMP publishes an extensive catalog of financial datasets through its Stable API. The platform provides over 100 documented endpoints. It also offers additional delivery options, including WebSocket streaming and bulk downloads for selected datasets.</p><p>FMP Stable uses a simple base URL, and authentication is handled via an API key passed as a query parameter.</p><pre><code>Base URL: https://financialmodelingprep.com/stable/
Auth: apikey=&lt;YOUR_KEY&gt;</code></pre><p>This briefing engine is built around a small set of Stable endpoints. Each endpoint maps to a section in the final one-page brief:</p><ul><li><p><strong>Earnings Calendar</strong> (<code>earnings-calendar</code>) provides upcoming and past earnings events. It includes the announcement date and EPS fields when available.</p></li><li><p><strong>Analyst Estimates </strong>(<code>analyst-estimates</code>) provides forecasted revenue and EPS. This supports the market expectations section.</p></li><li><p><strong>Company Profile</strong> (<code>profile</code>) provides a company snapshot such as sector, price, and market capitalization.</p></li><li><p><strong>Income Statement </strong>(<code>income-statement</code>) provides historical statement rows for trend context.</p></li><li><p><strong>Key Metrics </strong>(<code>key-metrics</code>) provides common KPIs used for compact metric blocks.</p></li></ul><p>These are the data we will retrieve from the FMP API, and we will build the system based on it.</p><h2><strong>What the Earnings Briefing Engine Does</strong></h2><ol><li><p><strong>Pull upcoming earnings events for a date window</strong><br>The engine queries the Stable Earnings Calendar endpoint with <code>from</code> and <code>to</code>. It returns upcoming announcements and may include EPS fields when available.</p></li><li><p><strong>Extract symbols and de-duplicate</strong><br>From the calendar response, the engine extracts <code>symbol</code> values. It keeps first occurrence order and removes duplicates.</p></li><li><p><strong>Fetch a fixed dataset per symbol</strong><br>For each ticker, the engine calls a small and explicit set of endpoints: &gt;Company Profile for the snapshot context.<br>&gt;Analyst Estimates for revenue and EPS expectations.<br>&gt;Optional fundamentals endpoints, such as Income Statement and Key Metrics, when you want trend and KPI blocks.</p></li><li><p><strong>Normalize responses into a stable ticker bundle</strong><br>Each API response is mapped into a predictable internal schema. Missing datasets become empty objects or empty lists.</p></li><li><p><strong>Render a one-page briefing from the bundle</strong><br>A single renderer transforms the bundle into a consistent Markdown brief.</p></li><li><p><strong>Save outputs to disk for reuse</strong><br>Each ticker corresponds to a Markdown file in the output folder.</p></li><li><p><strong>Repeat the workflow with different inputs</strong><br>We can rerun the engine with a different date window or a watchlist.</p></li></ol><h2><strong>Project Architecture</strong></h2><p>This project stays intentionally small. The goal is a single, clear pipeline with two entry points, rather than spreading behavior across many scripts.</p><pre><code>earnings_briefing_engine/
&#9500;&#9472; app/
&#9474;  &#9500;&#9472; __init__.py
&#9474;  &#9500;&#9472; config.py            # loads API key and stable base URL once
&#9474;  &#9500;&#9472; fmp_client.py        # HTTP wrapper, apikey injection, error handling
&#9474;  &#9500;&#9472; engine.py            # calendar or watchlist &#8594; bundle &#8594; briefing orchestration
&#9474;  &#9492;&#9472; render_markdown.py   # one-page Markdown template renderer
&#9500;&#9472; output/
&#9474;  &#9492;&#9472; briefings/           # generated files, one per ticker
&#9500;&#9472; .env                    # local configuration
&#9500;&#9472; requirements.txt
&#9500;&#9472; run.py                  # upcoming earnings window mode
&#9500;&#9472; run_watchlist.py        # fixed watchlist mode
&#9492;&#9472; output.txt              # optional run log or notes</code></pre><p>Here are explanations for each of the scripts&#8217; purposes:</p><h3><strong>app/config.py</strong></h3><p>Stores the single source of truth for configuration. This includes <code>FMP_BASE_URL=https://financialmodelingprep.com/stable</code> and your API key. FMP authenticates requests by appending <code>apikey=...</code> to each request.</p><h3><strong>app/fmp_client.py</strong></h3><p>A thin client that constructs URLs, attaches <code>apikey</code>, sets timeouts, and normalizes errors. This keeps API details out of the business logic. The calling pattern follows FMP&#8217;s Stable base URL and query authentication.</p><h3><strong>app/engine.py</strong></h3><p>The orchestration layer. It runs the numbered flow defined earlier:</p><ul><li><p>In calendar mode, it calls <code>earnings-calendar</code> with <code>from</code> and <code>to</code>.</p></li><li><p>It extracts and de-duplicates symbols.</p></li><li><p>It fetches a fixed set of per-ticker datasets, then normalizes them into a stable bundle.</p></li><li><p>In watchlist mode, it can populate the event context with the per-company earnings endpoint <code>earnings</code>.</p></li><li><p>It then calls the renderer and writes output files.</p></li></ul><h3><strong>app/render_markdown.py</strong></h3><p>Converts the normalized bundle into a one-page briefing with consistent headings. Markdown is used because it is portable, diffable, and easy to store. You can add HTML or PDF later without changing the data pipeline.</p><h3><strong>output/briefings/</strong></h3><p>Holds the generated artifacts. A practical convention is one file per ticker per event date, for example <code>AAPL_2026-02-06.md</code>. This creates a durable record you can re-run and compare over time.</p><h2><strong>Building the Earnings Briefing Engine</strong></h2><p>Let&#8217;s start to build our engine. We will break it down step-by-step.</p><h3><strong>Step 1: Create the environment</strong></h3><p>Start with a virtual environment and install only what you need by filling the <code>requirements.txt</code>.</p><pre><code>requests&gt;=2.31.0
python-dotenv&gt;=1.0.0</code></pre><p>We will using the <code>requests</code> for API calls and <code>python-dotenv</code> to load secrets from <code>.env</code></p><h3><strong>Step 2: Add a </strong><code>.env</code><strong> file for configuration</strong></h3><p>Create <code>.env</code> at the project root and store:</p><pre><code>FMP_API_KEY=YOUR_API_KEY
FMP_BASE_URL=https://financialmodelingprep.com/stable</code></pre><p>The Stable base URL is the canonical starting point for the endpoints used in this tutorial.</p><h3><strong>Step 3: Load settings once in </strong><code>app/config.py</code></h3><p>Keep configuration in one place. The engine should not read environment variables inside business logic. It should receive a settings object.</p><p>The <code>config.py</code> will have the following code:</p><pre><code>import os
from dataclasses import dataclass

from dotenv import load_dotenv

load_dotenv()

@dataclass(frozen=True)
class Settings:
    api_key: str
    base_url: str
    out_dir: str = &#8220;output/briefings&#8221;


def get_settings() -&gt; Settings:
    &#8220;&#8221;&#8220;
    This project targets FMP Stable endpoints:
      https://financialmodelingprep.com/stable/...
    &#8220;&#8221;&#8220;
    api_key = os.getenv(&#8221;FMP_API_KEY&#8221;, &#8220;&#8221;).strip()
    base_url = os.getenv(&#8221;FMP_BASE_URL&#8221;, &#8220;&#8221;).strip()

    if not api_key:
        raise RuntimeError(&#8221;Missing FMP_API_KEY. Set it in your environment or in a .env file.&#8221;)

    # Default to Stable API.
    if not base_url:
        base_url = &#8220;https://financialmodelingprep.com/stable&#8221;

    # Auto-correct common misconfiguration.
    if &#8220;/api/v3&#8221; in base_url:
        base_url = &#8220;https://financialmodelingprep.com/stable&#8221;

    return Settings(api_key=api_key, base_url=base_url)</code></pre><p>In this step, we define:</p><ul><li><p><code>api_key</code></p></li><li><p><code>base_url</code></p></li><li><p><code>out_dir</code></p></li></ul><p>This aligns with the Stable API pattern and ensures consistent requests.</p><h3><strong>Step 4: Build a small in </strong><code>app/fmp_client.py</code></h3><p>Next, we will build our FMP Client using the following code:</p><pre><code>from __future__ import annotations

from dataclasses import dataclass
from typing import Any, Dict, Optional

import requests


def _redact_apikey(url: str) -&gt; str:
    if &#8220;apikey=&#8221; not in url:
        return url
    return url.split(&#8221;apikey=&#8221;)[0] + &#8220;apikey=REDACTED&#8221;


@dataclass(frozen=True)
class FmpClient:
    &#8220;&#8221;&#8220;
    Minimal HTTP client for FMP Stable endpoints.
    &#8220;&#8221;&#8220;
    api_key: str
    base_url: str = &#8220;https://financialmodelingprep.com/stable&#8221;
    timeout_s: int = 30
    max_retries: int = 2  # only for 429

    def get_json(
        self,
        path: str,
        params: Optional[Dict[str, Any]] = None,
        *,
        allow_plan_errors: bool = True,
    ) -&gt; Any:
        &#8220;&#8221;&#8220;
        If allow_plan_errors is True:
          - 402 (Payment Required) -&gt; None
          - 403 (Forbidden) -&gt; None
        &#8220;&#8221;&#8220;
        base = self.base_url.rstrip(&#8221;/&#8221;)
        url = f&#8221;{base}/{path.lstrip(&#8217;/&#8217;)}&#8221;

        q = dict(params or {})
        q[&#8221;apikey&#8221;] = self.api_key

        attempts = 0
        while True:
            attempts += 1
            resp = requests.get(url, params=q, timeout=self.timeout_s)

            if allow_plan_errors and resp.status_code in (402, 403):
                return None

            if resp.status_code == 429 and attempts &lt;= self.max_retries:
                retry_after = resp.headers.get(&#8221;Retry-After&#8221;)
                wait_s = int(retry_after) if (retry_after and retry_after.isdigit()) else (1 + attempts)
                import time
                time.sleep(wait_s)
                continue

            if resp.status_code == 401:
                raise requests.HTTPError(f&#8221;Unauthorized (401) for {_redact_apikey(resp.url)}&#8221;, response=resp)

            resp.raise_for_status()
            return resp.json()</code></pre><p>Our client should do only four things:</p><ul><li><p>Construct <code>base_url + path</code></p></li><li><p>Attach <code>apikey</code> to query parameters</p></li><li><p>Set timeouts</p></li><li><p>Normalize common errors</p></li></ul><p>FMP documents API key usage via query parameters, and also notes header-based auth as an alternative.</p><h3><strong>5. Implement the full pipeline in </strong><code>app/engine.py</code></h3><p>We will implement the whole Earnings Briefing within the <code>engine.py</code> with the code below:</p><pre><code>from __future__ import annotations

from datetime import date, timedelta
from pathlib import Path
from typing import Any, Dict, List, Optional

from app.fmp_client import FmpClient
from app.render_markdown import render_markdown


def _dedupe_keep_order(items: List[str]) -&gt; List[str]:
    seen = set()
    out: List[str] = []
    for s in items:
        s = (s or &#8220;&#8221;).strip().upper()
        if s and s not in seen:
            seen.add(s)
            out.append(s)
    return out

def _first_dict(x: Any) -&gt; Dict[str, Any]:
    return x[0] if isinstance(x, list) and x and isinstance(x[0], dict) else {}


def fetch_earnings_calendar(client: FmpClient, start: date, end: date) -&gt; List[Dict[str, Any]]:
    &#8220;&#8221;&#8220;
    Earnings Calendar (stable):
      GET /earnings-calendar?from=YYYY-MM-DD&amp;to=YYYY-MM-DD
    Docs: https://financialmodelingprep.com/stable/earnings-calendar
    &#8220;&#8221;&#8220;
    data = client.get_json(
        &#8220;earnings-calendar&#8221;,
        {&#8221;from&#8221;: start.isoformat(), &#8220;to&#8221;: end.isoformat()},
        allow_plan_errors=True,
    )
    return data or []


def fetch_profile(client: FmpClient, symbol: str) -&gt; Dict[str, Any]:
    &#8220;&#8221;&#8220;
    Company Profile (stable):
      GET /profile?symbol=SYMBOL
    Docs: https://financialmodelingprep.com/stable/profile?symbol=AAPL
    &#8220;&#8221;&#8220;
    data = client.get_json(&#8221;profile&#8221;, {&#8221;symbol&#8221;: symbol}, allow_plan_errors=True)
    return _first_dict(data)


# Optional (may be plan-limited depending on account)
def fetch_analyst_estimates(client: FmpClient, symbol: str, *, period: str = &#8220;quarter&#8221;, limit: int = 8, page: int = 0) -&gt; List[Dict[str, Any]]:
    data = client.get_json(
        &#8220;analyst-estimates&#8221;,
        {&#8221;symbol&#8221;: symbol, &#8220;period&#8221;: period, &#8220;page&#8221;: page, &#8220;limit&#8221;: limit},
        allow_plan_errors=True,
    )
    return data or []


def fetch_income_statement(client: FmpClient, symbol: str, *, period: str = &#8220;quarter&#8221;, limit: int = 8) -&gt; List[Dict[str, Any]]:
    data = client.get_json(
        &#8220;income-statement&#8221;,
        {&#8221;symbol&#8221;: symbol, &#8220;period&#8221;: period, &#8220;limit&#8221;: limit},
        allow_plan_errors=True,
    )
    return data or []


def fetch_key_metrics(client: FmpClient, symbol: str, *, period: str = &#8220;quarter&#8221;, limit: int = 8) -&gt; List[Dict[str, Any]]:
    data = client.get_json(
        &#8220;key-metrics&#8221;,
        {&#8221;symbol&#8221;: symbol, &#8220;period&#8221;: period, &#8220;limit&#8221;: limit},
        allow_plan_errors=True,
    )
    return data or []


def fetch_stock_news(client: FmpClient, symbol: str, *, limit: int = 20) -&gt; List[Dict[str, Any]]:
    data = client.get_json(&#8221;news/stock&#8221;, {&#8221;symbols&#8221;: symbol, &#8220;limit&#8221;: limit}, allow_plan_errors=True)
    return data or []


def fetch_press_releases(client: FmpClient, symbol: str, *, limit: int = 20) -&gt; List[Dict[str, Any]]:
    data = client.get_json(&#8221;news/press-releases&#8221;, {&#8221;symbols&#8221;: symbol, &#8220;limit&#8221;: limit}, allow_plan_errors=True)
    return data or []


def build_bundle(
    client: FmpClient,
    symbol: str,
    *,
    event: Optional[Dict[str, Any]] = None,
    include_estimates: bool = False,
    include_financials: bool = False,
    include_news: bool = False,
    statements_period: str = &#8220;quarter&#8221;,
    statements_limit: int = 8,
) -&gt; Dict[str, Any]:
    profile = fetch_profile(client, symbol)

    estimates: List[Dict[str, Any]] = []
    income: List[Dict[str, Any]] = []
    key_metrics: List[Dict[str, Any]] = []
    news: List[Dict[str, Any]] = []
    press: List[Dict[str, Any]] = []

    if include_estimates:
        estimates = fetch_analyst_estimates(client, symbol, period=statements_period, limit=statements_limit)

    if include_financials:
        income = fetch_income_statement(client, symbol, period=statements_period, limit=statements_limit)
        key_metrics = fetch_key_metrics(client, symbol, period=statements_period, limit=statements_limit)

    if include_news:
        news = fetch_stock_news(client, symbol)
        press = fetch_press_releases(client, symbol)

    return {
        &#8220;symbol&#8221;: symbol,
        &#8220;event&#8221;: event or {},
        &#8220;profile&#8221;: profile,
        &#8220;estimates&#8221;: estimates,
        &#8220;income&#8221;: income,
        &#8220;key_metrics&#8221;: key_metrics,
        &#8220;news&#8221;: news,
        &#8220;press&#8221;: press,
    }

def run(
    settings: Any,
    *,
    days_ahead: int = 7,
    limit: int = 10,
    symbols: Optional[List[str]] = None,
    include_estimates: bool = False,
    include_financials: bool = False,
    include_news: bool = False,
) -&gt; None:
    &#8220;&#8221;&#8220;
    Two modes:
      1) Calendar mode (default): pull upcoming earnings, then build briefs.
      2) Watchlist mode: pass symbols=[...].
    &#8220;&#8221;&#8220;
    client = FmpClient(api_key=settings.api_key, base_url=settings.base_url)

    out_dir = Path(getattr(settings, &#8220;out_dir&#8221;, &#8220;output/briefings&#8221;))
    out_dir.mkdir(parents=True, exist_ok=True)

    events_by_symbol: Dict[str, Dict[str, Any]] = {}
    if symbols:
        target_symbols = _dedupe_keep_order(symbols)
    else:
        start = date.today()
        end = start + timedelta(days=days_ahead)
        events = fetch_earnings_calendar(client, start, end)

        for e in events:
            sym = (e.get(&#8221;symbol&#8221;) or &#8220;&#8221;).strip().upper()
            if sym:
                events_by_symbol.setdefault(sym, e)

        target_symbols = list(events_by_symbol.keys())[:limit]

    if not target_symbols:
        print(
            &#8220;No symbols returned.\n&#8221;
            &#8220;Confirm your base URL is https://financialmodelingprep.com/stable and your API key is valid.\n&#8221;
            &#8220;If you are on the free tier, some datasets may be restricted.&#8221;
        )
        return

    for i, sym in enumerate(target_symbols, start=1):
        bundle = build_bundle(
            client,
            sym,
            event=events_by_symbol.get(sym),
            include_estimates=include_estimates,
            include_financials=include_financials,
            include_news=include_news,
        )
        md = render_markdown(bundle)

        out_path = out_dir / f&#8221;{sym}.md&#8221;
        out_path.write_text(md, encoding=&#8221;utf-8&#8221;)
        print(f&#8221;[{i}/{len(target_symbols)}] wrote {out_path}&#8221;)</code></pre><p>This is where the engine becomes a repeatable workflow. The code above basically does the following actions:</p><ol><li><p><strong>Pull a calendar window.</strong> Call <code>earnings-calendar</code> with <code>from</code> and <code>to</code>. This yields upcoming and past earnings events, including EPS fields when available.</p></li><li><p><strong>Extract symbols and de-duplicate. </strong>Read the <code>symbol</code> field from the calendar results. De-duplicate while preserving order. Apply a small <code>limit</code> so runs remain predictable.</p></li><li><p><strong>Fetch a fixed dataset set per ticker.</strong> Use the same calls for every symbol. Start with the essentials, then treat deeper fundamentals as optional. <code>profile?symbol=...</code> for sector and market cap style snapshot fields. <code>analyst-estimates?symbol=...&amp;period=...&amp;page=...&amp;limit=...</code> for revenue and EPS expectations. Optionally <code>income-statement</code> for trend context and<code>key-metrics</code> for compact KPI blocks.</p></li><li><p><strong>Normalize into a stable bundle schema. </strong>Map responses into one predictable shape, then pass that shape downstream. Missing datasets are represented as<code>{}</code> or <code>[]</code>. This keeps rendering stable even when some endpoints return no data on a given plan.</p></li><li><p><strong>Write one artifact per ticker. </strong>For each bundle, call the renderer and save the Markdown into <code>output/briefings/</code>.</p></li></ol><p>If you also support watchlists, you can populate event context using the per-company earnings endpoint, then reuse the same bundle and rendering path.</p><h3><strong>Step 6: Render the one-page brief in </strong><code>app/render_markdown.py</code></h3><p>Next, we set up the <code>render_markdown.py</code> with the following code:</p><pre><code>from __future__ import annotations

from datetime import datetime
from typing import Any, Dict, List, Optional

Json = Dict[str, Any]


def _first_dict(x: Any) -&gt; Json:
    if isinstance(x, list) and x and isinstance(x[0], dict):
        return x[0]
    if isinstance(x, dict):
        return x
    return {}


def _as_list_of_dicts(x: Any) -&gt; List[Json]:
    if isinstance(x, list):
        return [i for i in x if isinstance(i, dict)]
    return []


def _get_first_present(d: Json, keys: List[str], default: Any = &#8220;N/A&#8221;) -&gt; Any:
    for k in keys:
        v = d.get(k)
        if v is not None and v != &#8220;&#8221;:
            return v
    return default


def _fmt_num(x: Any) -&gt; str:
    if x is None:
        return &#8220;N/A&#8221;
    try:
        if isinstance(x, bool):
            return &#8220;N/A&#8221;
        if isinstance(x, (int, float)):
            if abs(x) &gt;= 1_000_000_000:
                return f&#8221;{x/1_000_000_000:.2f}B&#8221;
            if abs(x) &gt;= 1_000_000:
                return f&#8221;{x/1_000_000:.2f}M&#8221;
            if abs(x) &gt;= 1_000:
                return f&#8221;{x:,.0f}&#8221;
            return f&#8221;{x:.4g}&#8221;
        xf = float(str(x).replace(&#8221;,&#8221;, &#8220;&#8221;))
        return _fmt_num(xf)
    except Exception:
        return str(x)

def render_markdown(bundle: Json) -&gt; str:
    sym = bundle.get(&#8221;symbol&#8221;, &#8220;N/A&#8221;)

    event = bundle.get(&#8221;event&#8221;) or {}
    profile = bundle.get(&#8221;profile&#8221;) or {}
    estimates = _as_list_of_dicts(bundle.get(&#8221;estimates&#8221;))
    key_metrics = _as_list_of_dicts(bundle.get(&#8221;key_metrics&#8221;))
    income = _as_list_of_dicts(bundle.get(&#8221;income&#8221;))
    news = _as_list_of_dicts(bundle.get(&#8221;news&#8221;))
    press = _as_list_of_dicts(bundle.get(&#8221;press&#8221;))

    est0 = _first_dict(estimates)
    km0 = _first_dict(key_metrics)

    company = _get_first_present(profile, [&#8221;companyName&#8221;, &#8220;name&#8221;], sym)
    sector = _get_first_present(profile, [&#8221;sector&#8221;], &#8220;N/A&#8221;)

    # FIX: market cap key is commonly &#8220;marketCap&#8221; on profile payloads.
    mcap = _get_first_present(profile, [&#8221;marketCap&#8221;, &#8220;mktCap&#8221;, &#8220;marketCapitalization&#8221;], None)
    price = _get_first_present(profile, [&#8221;price&#8221;], None)

    event_date = _get_first_present(event, [&#8221;date&#8221;, &#8220;earningDate&#8221;], &#8220;N/A&#8221;)
    event_time = _get_first_present(event, [&#8221;time&#8221;, &#8220;timeEstimated&#8221;], &#8220;N/A&#8221;)

    # Prefer analyst estimates if present, otherwise fall back to the calendar row.
    eps_est = _get_first_present(est0, [&#8221;estimatedEps&#8221;, &#8220;epsEstimated&#8221;], None)
    if eps_est in (None, &#8220;N/A&#8221;):
        eps_est = _get_first_present(event, [&#8221;epsEstimated&#8221;, &#8220;estimatedEps&#8221;], None)

    rev_est = _get_first_present(est0, [&#8221;estimatedRevenue&#8221;, &#8220;revenueEstimated&#8221;], None)
    if rev_est in (None, &#8220;N/A&#8221;):
        rev_est = _get_first_present(event, [&#8221;revenueEstimated&#8221;, &#8220;estimatedRevenue&#8221;], None)

    lines: List[str] = []
    lines.append(f&#8221;# Earnings Briefing: {company} ({sym})&#8221;)
    lines.append(&#8221;&#8220;)
    lines.append(&#8221;## Event&#8221;)
    lines.append(f&#8221;- Date: {event_date}&#8221;)
    lines.append(f&#8221;- Time: {event_time}&#8221;)
    lines.append(&#8221;&#8220;)
    lines.append(&#8221;## Snapshot&#8221;)
    lines.append(f&#8221;- Sector: {sector}&#8221;)
    lines.append(f&#8221;- Price: {_fmt_num(price)}&#8221;)
    lines.append(f&#8221;- Market cap: {_fmt_num(mcap)}&#8221;)
    lines.append(&#8221;&#8220;)

    lines.append(&#8221;## Expectations&#8221;)
    lines.append(f&#8221;- Estimated EPS: {_fmt_num(eps_est)}&#8221;)
    lines.append(f&#8221;- Estimated revenue: {_fmt_num(rev_est)}&#8221;)
    lines.append(&#8221;&#8220;)

    # Only show these sections if you enabled them (or if your plan returns data).
    if km0:
        lines.append(&#8221;## Key metrics (latest)&#8221;)
        lines.append(f&#8221;- P/E: {_fmt_num(km0.get(&#8217;peRatio&#8217;))}&#8221;)
        lines.append(f&#8221;- Net margin: {_fmt_num(km0.get(&#8217;netProfitMargin&#8217;))}&#8221;)
        lines.append(&#8221;&#8220;)

    if income:
        lines.append(&#8221;## Trend context&#8221;)
        lines.append(&#8221;- Financial statements were fetched (see JSON bundle for details).&#8221;)
        lines.append(&#8221;&#8220;)

    if news or press:
        lines.append(&#8221;## Recent context&#8221;)
        if news:
            lines.append(&#8221;- Stock news:&#8221;)
            for n in news[:3]:
                title = _get_first_present(n, [&#8221;title&#8221;], None)
                pub = _get_first_present(n, [&#8221;publishedDate&#8221;, &#8220;date&#8221;], None)
                if title:
                    lines.append(f&#8221;  - {title}&#8221; + (f&#8221; ({pub})&#8221; if pub else &#8220;&#8221;))
        if press:
            lines.append(&#8221;- Press releases:&#8221;)
            for p in press[:3]:
                title = _get_first_present(p, [&#8221;title&#8221;], None)
                pub = _get_first_present(p, [&#8221;date&#8221;, &#8220;publishedDate&#8221;], None)
                if title:
                    lines.append(f&#8221;  - {title}&#8221; + (f&#8221; ({pub})&#8221; if pub else &#8220;&#8221;))
        lines.append(&#8221;&#8220;)

    lines.append(&#8221;## Questions to listen for&#8221;)
    lines.append(&#8221;- What changed in demand, pricing, or volume versus last quarter?&#8221;)
    lines.append(&#8221;- What is driving margin movement?&#8221;)
    lines.append(&#8221;- What guidance signals matter most for the next two quarters?&#8221;)
    lines.append(&#8221;&#8220;)
    lines.append(f&#8221;_Generated at {datetime.utcnow().strftime(&#8217;%Y-%m-%d %H:%M UTC&#8217;)}. Not financial advice._&#8221;)
    return &#8220;\n&#8221;.join(lines)</code></pre><p>The renderer code above takes the bundle and produces a consistent Markdown page:</p><ul><li><p>Event section uses the calendar row</p></li><li><p>Snapshot section uses profile fields</p></li><li><p>Expectations use analyst estimates, with optional fallback to calendar fields</p></li><li><p>Optional sections appear only when data exists</p></li></ul><h3><strong>Step 7: Add entry points for two run modes</strong></h3><p>We keep the entry points thin:</p><ul><li><p><code>run.py</code> for &#8220;upcoming earnings&#8221; mode. It runs the <code>earnings-calendar</code> window and generates briefings for symbols in that window. We can tweak the cide as below:</p></li></ul><pre><code>from app.config import get_settings
from app.engine import run

if __name__ == &#8220;__main__&#8221;:
    settings = get_settings()
    run(settings, days_ahead=7, limit=10)</code></pre><ul><li><p><code>run_watchlist.py</code> for &#8220;watchlist&#8221; mode. It runs the same bundle and renderer, but starts from a fixed list of symbols:</p></li></ul><pre><code>from app.config import get_settings
from app.engine import run

WATCHLIST = [&#8221;AAPL&#8221;, &#8220;MSFT&#8221;, &#8220;NVDA&#8221;, &#8220;TSLA&#8221;]

if __name__ == &#8220;__main__&#8221;:
    settings = get_settings()
    run(settings, symbols=WATCHLIST)</code></pre><p>If you want watchlist mode to always show an earnings context, you can enrich it with the per-company earnings endpoint.</p><h3><strong>Step 8: Verify outputs and iterate safely</strong></h3><p>A successful run should produce one Markdown file per ticker under <code>output/briefings/</code>. For example, the result is shown below:</p><pre><code># Earnings Briefing: Shopify Inc. (SHOP)

## Event
- Date: 2026-02-11
- Time: N/A

## Snapshot
- Sector: Technology
- Price: 112.9
- Market cap: 147.38B

## Expectations
- Estimated EPS: 0.5
- Estimated revenue: 3.59B

## Questions to listen for
- What changed in demand, pricing, or volume versus last quarter?
- What is driving margin movement?
- What guidance signals matter most for the next two quarters?

_Generated at 2026-02-05 17:11 UTC. Not financial advice._</code></pre><p>If you see missing event dates, expand the calendar window. If you see missing expectations, confirm that estimates are enabled and available for those symbols. If you hit request limits, reduce the batch size or add caching. The Basic plan call limit is published in FMP&#8217;s plan comparison</p><p>That&#8217;s all you need to know on how to build an Earnings Briefing Engine using the FMP API.</p><h2><strong>Conclusion</strong></h2><p>In this article, we have learn on how to build an earnings briefing engine that reduces manual effort during earnings weeks by enforcing a repeatable workflow.</p><p>Using <a href="https://site.financialmodelingprep.com/?utm_source=medium&amp;utm_medium=medium&amp;utm_campaign=corn10">Financial Modeling Prep (FMP)</a> as the primary data source, the process relies on a stable API to retrieve earnings events and selected supporting context, then we summarize the results into a standardized one-page briefing format that can be stored and reused.</p><p>In practice, this system will beuseful for maintaining a disciplined pre-earnings routine, supporting watchlist management during busy reporting weeks, and creating a written record of what to review before each announcement.</p><p>I hope it has helped!</p>]]></content:encoded></item><item><title><![CDATA[NBD Focus Map (Free PDF)]]></title><description><![CDATA[A simple 3-track plan to stop learning randomly and start shipping real work]]></description><link>https://www.nb-data.com/p/nbd-focus-map-free-pdf</link><guid isPermaLink="false">https://www.nb-data.com/p/nbd-focus-map-free-pdf</guid><dc:creator><![CDATA[Cornellius Yudha Wijaya]]></dc:creator><pubDate>Sun, 01 Feb 2026 12:36:35 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!UAOl!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc40430b9-c032-4f62-9956-2ee3fa2b8b22_1600x900.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!UAOl!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc40430b9-c032-4f62-9956-2ee3fa2b8b22_1600x900.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!UAOl!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc40430b9-c032-4f62-9956-2ee3fa2b8b22_1600x900.png 424w, https://substackcdn.com/image/fetch/$s_!UAOl!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc40430b9-c032-4f62-9956-2ee3fa2b8b22_1600x900.png 848w, https://substackcdn.com/image/fetch/$s_!UAOl!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc40430b9-c032-4f62-9956-2ee3fa2b8b22_1600x900.png 1272w, https://substackcdn.com/image/fetch/$s_!UAOl!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc40430b9-c032-4f62-9956-2ee3fa2b8b22_1600x900.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!UAOl!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc40430b9-c032-4f62-9956-2ee3fa2b8b22_1600x900.png" width="1456" height="819" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/c40430b9-c032-4f62-9956-2ee3fa2b8b22_1600x900.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:819,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:137128,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://www.nb-data.com/i/186488912?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc40430b9-c032-4f62-9956-2ee3fa2b8b22_1600x900.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!UAOl!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc40430b9-c032-4f62-9956-2ee3fa2b8b22_1600x900.png 424w, https://substackcdn.com/image/fetch/$s_!UAOl!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc40430b9-c032-4f62-9956-2ee3fa2b8b22_1600x900.png 848w, https://substackcdn.com/image/fetch/$s_!UAOl!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc40430b9-c032-4f62-9956-2ee3fa2b8b22_1600x900.png 1272w, https://substackcdn.com/image/fetch/$s_!UAOl!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc40430b9-c032-4f62-9956-2ee3fa2b8b22_1600x900.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Most people do not struggle because they lack effort. They struggle because they learn without a plan.</p><p>The Focus Map is my way of turning Non-Brand Data into a simple path you can follow. Pick one track, stick with it for 2&#8211;4 weeks, and ship one mini project at the end.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.nb-data.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://www.nb-data.com/subscribe?"><span>Subscribe now</span></a></p><h1><strong>What you&#8217;ll get</strong></h1><ul><li><p>SQL track for real analysis work</p></li><li><p>Python + ML track for practical modelling</p></li><li><p>RAG track for building question-answering systems on documents</p></li></ul><p>Each track includes:</p><ul><li><p>5 posts to read in order</p></li><li><p>a weekly cadence (3 sessions/week, 60 minutes/session)</p></li><li><p>One mini project with clear deliverables</p></li><li><p>What &#8220;good&#8221; looks like, so you know when to move on</p></li></ul><p>Note: the SQL Crash Course is a collaboration with Josep Ferrer (DataBites), so a few lessons are open on databites.tech.</p><p><strong>Download the PDF below for the NBD Focus Map.</strong></p><div class="file-embed-wrapper" data-component-name="FileToDOM"><div class="file-embed-container-reader"><div class="file-embed-container-top"><image class="file-embed-thumbnail-default" src="https://substackcdn.com/image/fetch/$s_!0Cy0!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack.com%2Fimg%2Fattachment_icon.svg"></image><div class="file-embed-details"><div class="file-embed-details-h1">Nbd Focus Map</div><div class="file-embed-details-h2">124KB &#8729; PDF file</div></div><a class="file-embed-button wide" href="https://www.nb-data.com/api/v1/file/a1f7b069-0e36-445f-8afd-586f7c470855.pdf"><span class="file-embed-button-text">Download</span></a></div><a class="file-embed-button narrow" href="https://www.nb-data.com/api/v1/file/a1f7b069-0e36-445f-8afd-586f7c470855.pdf"><span class="file-embed-button-text">Download</span></a></div></div><p>If you'd like, reply and let me know which track you&#8217;re starting with.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.nb-data.com/p/nbd-focus-map-free-pdf/comments&quot;,&quot;text&quot;:&quot;Leave a comment&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://www.nb-data.com/p/nbd-focus-map-free-pdf/comments"><span>Leave a comment</span></a></p><h2>After you finish a track</h2><p>If you finish one track and you have a notebook, repo, or write-up, you are already ahead of most people.</p><p>The next step is making it sharper and more reusable.</p><h3><strong>1) Turn it into a portfolio-ready artifact</strong></h3><p>I&#8217;m packaging a <strong>Portfolio Rubric Toolkit</strong> to help you score your project, spot what is missing, and decide what to fix first.</p><p><strong>Portfolio Rubric Toolkit (upgrade your project in 30&#8211;60 minutes):</strong></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://cornelliusyudhawijay.gumroad.com/l/otdloq&quot;,&quot;text&quot;:&quot;Portfolio Rubric Kit&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://cornelliusyudhawijay.gumroad.com/l/otdloq"><span>Portfolio Rubric Kit</span></a></p><h3><strong>2) Keep momentum with guided paths and templates</strong></h3><p>If you prefer a more structured approach, the paid tier is built around member-only deep dives, reusable templates, and guided paths through the archive.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.nb-data.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://www.nb-data.com/subscribe?"><span>Subscribe now</span></a></p><p></p>]]></content:encoded></item><item><title><![CDATA[The Portfolio Rubric Data Science Hiring Managers Use]]></title><description><![CDATA[A practical hiring-manager style framework that you can use]]></description><link>https://www.nb-data.com/p/the-portfolio-rubric-data-science</link><guid isPermaLink="false">https://www.nb-data.com/p/the-portfolio-rubric-data-science</guid><dc:creator><![CDATA[Cornellius Yudha Wijaya]]></dc:creator><pubDate>Tue, 20 Jan 2026 10:42:48 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!41BT!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa2456a78-087d-497c-9ed0-d2da93bf2dac_1312x736.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!41BT!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa2456a78-087d-497c-9ed0-d2da93bf2dac_1312x736.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!41BT!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa2456a78-087d-497c-9ed0-d2da93bf2dac_1312x736.jpeg 424w, https://substackcdn.com/image/fetch/$s_!41BT!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa2456a78-087d-497c-9ed0-d2da93bf2dac_1312x736.jpeg 848w, https://substackcdn.com/image/fetch/$s_!41BT!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa2456a78-087d-497c-9ed0-d2da93bf2dac_1312x736.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!41BT!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa2456a78-087d-497c-9ed0-d2da93bf2dac_1312x736.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!41BT!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa2456a78-087d-497c-9ed0-d2da93bf2dac_1312x736.jpeg" width="1312" height="736" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/a2456a78-087d-497c-9ed0-d2da93bf2dac_1312x736.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:736,&quot;width&quot;:1312,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:89473,&quot;alt&quot;:&quot;The Portfolio Rubric Data Science Hiring Managers Use&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://www.nb-data.com/i/185147236?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa2456a78-087d-497c-9ed0-d2da93bf2dac_1312x736.jpeg&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="The Portfolio Rubric Data Science Hiring Managers Use" title="The Portfolio Rubric Data Science Hiring Managers Use" srcset="https://substackcdn.com/image/fetch/$s_!41BT!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa2456a78-087d-497c-9ed0-d2da93bf2dac_1312x736.jpeg 424w, https://substackcdn.com/image/fetch/$s_!41BT!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa2456a78-087d-497c-9ed0-d2da93bf2dac_1312x736.jpeg 848w, https://substackcdn.com/image/fetch/$s_!41BT!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa2456a78-087d-497c-9ed0-d2da93bf2dac_1312x736.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!41BT!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa2456a78-087d-497c-9ed0-d2da93bf2dac_1312x736.jpeg 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Image generated with ideogram.ai</figcaption></figure></div><p>Picture this scenario: You spend weeks polishing a Kaggle competition notebook with immaculate code, fancy plots, and a near-perfect model. You feel confident. Then, in an interview, the hiring manager asks, &#8220;How would this work with messy real data? Where is the business decision here?&#8221; You scramble for an answer. Awkward silence. The truth is that Kaggle taught you how to compete, not how to solve real business problems. In fact, &#8220;most recruiters don&#8217;t care about your Kaggle rank&#8221;. <strong>They care about something else entirely.</strong></p><p>Too many data science portfolios list projects that impress on the surface but fail to deliver real value. As a hiring manager who&#8217;s screened dozens of candidates, they have noticed a persistent gap between what candidates showcase and what teams actually need. The typical portfolio is just a list of projects, but what they are looking for is evidence of impact, realism, and critical thinking behind them.</p><p>The good news is that you don&#8217;t need a dozen fancy projects to stand out. You need the right qualities in whichever projects you present. Below, I&#8217;ll share the rubric hiring managers use to evaluate data science portfolios, which is a simple scoring framework covering the five areas that matter for hiring. <br></p><p>We&#8217;ll also look at common mistakes to avoid and quick fixes to upgrade your existing portfolio. By the end, you&#8217;ll understand exactly what hiring managers are looking for and how to demonstrate it in your portfolio.</p><p>Let&#8217;s get into it.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.nb-data.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://www.nb-data.com/subscribe?"><span>Subscribe now</span></a></p><div><hr></div><h2>Common Portfolio Mistakes (What to Avoid)</h2><p>Even experienced data scientists fall into some classic portfolio traps. Before we discuss what to do right, let&#8217;s highlight what not to do. Here are some common mistakes that cause portfolios to miss the mark:</p><ul><li><p><strong>Using Only Toy or Overused Datasets</strong>: Relying on Titanic survival predictions or Iris classification projects shows a lack of originality. Recruiters have seen these portfolios thousands of times, and a collection full of such washed-out projects will bore them. It also indicates you haven&#8217;t worked with realistic data. An industry insider said, &#8220;I hate seeing people use common Kaggle datasets like Titanic or Iris. Instead, try to scrape your own data or find unique sources.&#8221; Overall, if your data is pre-cleaned and common, it doesn&#8217;t demonstrate your ability to handle real-world data quirks.</p></li><li><p><strong>No Clear Problem or Purpose:</strong>&nbsp;Failing to define a business question or real-world purpose is a common mistake. A portfolio project like &#8220;I built a neural network to classify images&#8221; without context won&#8217;t impress hiring managers. They want to know why you did it, whether it solves a meaningful problem or was just a class assignment. If you can&#8217;t explain the problem and its significance, it shows a lack of business thinking. Many portfolios fail not due to technical skill but because they don&#8217;t communicate value. Avoid projects without a narrative of who benefits or what decisions can be made. For example, don&#8217;t say &#8220;it was a bootcamp group project&#8221; when asked why you chose it, show that you addressed a problem you care about or an issue relevant to a business.</p></li><li><p><strong>Metrics Over Impact (Model-Centric Thinking)</strong>: Many candidates focus on achieving 99% accuracy in a model and present that as the victory, but hiring managers are wary of this. Focusing on metrics instead of business value is a mistake. For example, a churn prediction model with an AUC of 94% sounds good but has little value if it mostly flags customers who no longer use the product. A narrow focus on metrics often means ignoring whether the solution solves the core problem. Employers want you to deliver value, so don&#8217;t just brag about high scores but show you understand the &#8220;so what?&#8221; of your results.</p></li><li><p><strong>Ignoring Deployment and Next Steps:</strong> A common mistake is treating projects as standalone exercises. Creating a model isn&#8217;t enough; its value lies in deployment and usage. If your projects don&#8217;t mention how to implement, use, or the next steps after building the model, hiring managers notice. Most  employers won&#8217;t consider you a serious candidate for senior employment without knowledge of deployment, retraining, or monitoring. You don&#8217;t need to be an MLOps expert, but showing deployment ideas (even hypothetical) is crucial. </p></li><li><p><strong>Poor Presentation and Communication:</strong> Many portfolios are hard to read, lacking README files, commentary, or visualizations, making it tiring for reviewers to understand your project. A hiring manager said, &#8220;I hate seeing a big mess of code with no README or TL;DR.&#8221; Without a clear summary or visual results, your work can be overlooked. Hiring managers glance through dozens of portfolios, so if yours doesn&#8217;t quickly highlight key points, it likely won&#8217;t hold attention. Another manager said, &#8220;I ignore side projects unless they show real impact... I need impact, not just some model.&#8221; Showing impact also means presenting insights simply&#8212;pictures or charts often communicate more effectively than words. Portfolios without an executive summary, well-designed graphs, or an organized story are at a disadvantage.</p></li></ul><p>Avoid these pitfalls:</p><ol><li><p>Steer clear of overly common projects,</p></li><li><p>Always define the problem and the value,</p></li><li><p>Think beyond accuracy alone,</p></li><li><p>Consider real-world deployment,</p></li><li><p>Present your work clearly. </p></li></ol><p>Next, we&#8217;ll discuss exactly what hiring managers are looking for instead and how to ensure your portfolio checks those boxes.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.nb-data.com/p/the-portfolio-rubric-data-science?utm_source=substack&utm_medium=email&utm_content=share&action=share&quot;,&quot;text&quot;:&quot;Share&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://www.nb-data.com/p/the-portfolio-rubric-data-science?utm_source=substack&utm_medium=email&utm_content=share&action=share"><span>Share</span></a></p><div><hr></div><h2>What Hiring Managers Are Actually Looking For</h2><p>So what does impress a hiring manager in a data science portfolio? In a word: impact. </p><p>They want to see proof that you can apply data science to solve real problems, not just toy exercises. From my experience, this boils down to a few key qualities. Specifically, they evaluate portfolios across five dimensions that map to real on-the-job success:</p><ul><li><p><strong>Problem Framing:</strong> Did you clearly define the problem you tackled and why it matters? Great portfolios start with a well-scoped question or business problem, not just a technique. (Is it a meaningful, non-trivial problem, and do you understand the context around it?)</p></li><li><p><strong>Data Realism:</strong> Did you use data that&#8217;s reflective of real-world complexity? This includes working with messy or authentic datasets, not only pristine samples. It shows you can handle real data challenges and demonstrates curiosity in sourcing data beyond the usual examples.</p></li><li><p><strong>Evaluation Rigor:</strong> How do you measure success, and how trustworthy are your results? We look for the use of proper metrics, baseline comparisons, validation techniques, and an honest assessment of model performance. In short, are you skeptical about metrics and careful about conclusions, or are you just accepting whatever accuracy pops out?</p></li><li><p><strong>Deployment Thinking:</strong> Did you consider what happens after the model is built? That means thinking about how the solution could be deployed or used in production. For example, packaging the model, building an API, or simply discussing how a business could implement your insights. This shows a &#8220;product readiness&#8221; mindset, not just academic analysis.</p></li><li><p><strong>Communication:</strong> Could someone who isn&#8217;t you understand and appreciate the project quickly? This covers the clarity of your writing, visualization of results, and overall storytelling. Great portfolios read almost like case studies: they draw the reader in, highlight key findings, and explain technical details in an accessible way. In fact, storytelling and clear communication are becoming increasingly important. Companies want data scientists who can clearly explain insights, not just write code.</p></li></ul><p>These five categories form the Portfolio Rubric that many hiring managers use to score a portfolio. Think of each as a lens through which your project is evaluated. If your portfolio projects excel in these areas, you&#8217;re demonstrating the qualities that truly matter on the job.</p><p>In the next sections, we&#8217;ll break down each rubric category in detail. For each category, I&#8217;ll explain why it matters in real-world terms and what distinguishes an average project from an outstanding one. I&#8217;ll even provide sample scoring criteria so you can gauge where your projects might fall.</p><p>Let&#8217;s dive into the rubric that can make your portfolio a hiring manager&#8217;s dream.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.nb-data.com/p/the-portfolio-rubric-data-science/comments&quot;,&quot;text&quot;:&quot;Leave a comment&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://www.nb-data.com/p/the-portfolio-rubric-data-science/comments"><span>Leave a comment</span></a></p><div><hr></div><h2>The Portfolio Rubric: 5 Key Evaluation Categories</h2><h3>1. Problem Framing</h3><p>Problem framing is about setting the stage. It&#8217;s answering: &#8220;What exact problem are you solving, and why does it matter?&#8221; A strong portfolio project doesn&#8217;t start with &#8220;I used X algorithm&#8221;; it starts with a clear question or objective. For example, instead of &#8220;I built a time series model,&#8221; good framing would be &#8220;I forecasted weekly sales to help a retailer manage inventory,&#8221; which is a specific problem with a business context.</p><p>In industry, choosing the right problem is half the battle. Companies need data scientists who focus on impactful questions, not just cool techniques. If a project lacks context, it &#8220;only shows your lack of business thinking&#8221;. Remember, a brilliant model solving an irrelevant problem is a wasted effort. Hiring managers look for whether you understood the purpose behind the project. Did you identify a stakeholder or decision-maker, and what they care about? Do you connect your results to a business outcome or insight?</p><p>For example, a candidate&#8217;s portfolio included a project &#8220;Predicting Employee Attrition.&#8221; On paper, it was a classification model with decent accuracy. But what impressed me was the framing. They introduced it as &#8220;Employee turnover prediction to inform HR retention strategies&#8221; and discussed how reducing attrition could save money. That context turned a generic model into a compelling story of business value.</p><p>How we score it (Problem Framing):</p><ul><li><p>Level 1 (Needs Improvement): The project lacks a clear question or goal. It feels like a generic exercise (e.g., &#8220;I applied X algorithm to Y data&#8221; with no further context). The reader can&#8217;t tell what problem this solves or why it&#8217;s important.</p></li><li><p>Level 2 (Good): The project defines a problem, but in a somewhat generic way or without emphasizing its importance. There&#8217;s a basic problem statement (e.g., predicting house prices), but little discussion of who benefits or what one would do with this prediction. Some context is given, but it may be shallow or assumed.</p></li><li><p>Level 3 (Excellent): The project is framed around a specific, meaningful problem with real-world context. It&#8217;s immediately clear why the problem matters (e.g., &#8220;predicting equipment failure to reduce downtime costs&#8221;). The candidate explains the background and stakes: who has this problem, what decision the analysis will inform, and how success is defined. The scope is well-defined (not too broad or vague), showing the candidate knows how to translate an ambiguous idea into a concrete data question.</p></li></ul><div><hr></div><h3>2. Data Realism</h3><p>Data realism refers to using data and approaches that mirror real-world conditions. This means datasets that are messy, large, or obtained from authentic sources. Not just tidy CSVs everyone&#8217;s seen before. It also means demonstrating data wrangling and an understanding of data quality, rather than assuming data is perfect.</p><p>In industry, data is often messy or incomplete. Using only clean, toy datasets (like Kaggle or classroom sets) doesn&#8217;t prove you can handle real data challenges. Recruiters know anyone can run a model on Titanic or Iris; that doesn&#8217;t make you stand out. Relying on such projects may cause recruiters to ignore you, as your portfolio shows a lack of creativity. Instead, sourcing interesting datasets or demonstrating how you managed missing values, outliers, or scaling shows initiative and practical skill. A hiring manager suggests scraping your own dataset or seeking rarer datasets, rather than recycling common examples. </p><p>Imagine two candidates. Alice uses the Titanic dataset but writes as if she&#8217;s helping a cruise company improve safety, discussing the dataset's limitations (e.g., a sample of historical passengers) and how she&#8217;d gather more current data. Bob uses the Titanic dataset and just builds a classifier with 99% accuracy (on a cleaned dataset where missing ages were already handled). Alice is demonstrating data realism; Bob is not. We&#8217;re more likely to interview Alice because she&#8217;s thinking like a professional dealing with real data problems.</p><p>How we score it (Data Realism):</p><ul><li><p>Level 1 (Needs Improvement): Uses only small, common datasets with no evidence of data cleaning or exploration. It appears the data was taken &#8220;as is&#8221; from a textbook or Kaggle, with no mention of missing values, anomalies, or domain specifics. No data sourcing effort is shown (the data fell into their lap). This suggests the candidate might struggle when faced with untidy real-world data.</p></li><li><p>Level 2 (Good): Uses a reasonable dataset and shows some data cleaning or feature engineering, but nothing beyond the ordinary. The dataset might still be a common one, but the project at least acknowledges data issues (e.g., &#8220;had to handle class imbalance by ...&#8221; or &#8220;combined two data sources&#8221;). There is evidence that the candidate can do basic wrangling and is aware of data limitations, though they may not have sought out truly novel data.</p></li><li><p>Level 3 (Excellent): The project uses realistic data, possibly self-collected or multi-source. The candidate may have accessed an API, scraped data, or used an open data portal to gather new data. They clearly document the data cleaning steps and challenges (e.g., handling missing data, skewed distributions, or integrating data from different sources). The approach shows creativity in data sourcing and thoroughness in preparation. It&#8217;s evident they didn&#8217;t just accept the data at face value &#8211; they explored its quality and shaped the data to fit the problem, just like one must do on real teams. This level demonstrates that the person can handle the messiness of actual business data.</p></li></ul><div><hr></div><h3>3. Evaluation Rigor</h3><p>Evaluation rigor means critically assessing your model&#8217;s performance and results. It&#8217;s about using the right metrics, establishing baselines, properly validating the model, and interpreting the outcomes with a skeptical eye. Rigorous evaluation answers: &#8220;How do I know my solution actually works, and how well?&#8221;</p><p>In real projects, a model is only as good as the evidence that it works for the intended purpose. Hiring managers want to see that you didn&#8217;t just run to a conclusion, but that you actually tested it. This includes simple things like comparing against a baseline (e.g., how does your model compare to a naive guess or the current solution?) and using appropriate metrics for the problem (e.g., using precision/recall for a class-imbalanced problem instead of just accuracy). It also means checking for overfitting, using cross-validation or a test set, and analyzing errors or uncertainty.</p><p>Portfolios that demonstrate evaluation rigor stand out. For instance, if you built a classifier, did you also provide a confusion matrix and discuss false positives versus false negatives in context? If you did time-series forecasting, did you hold out the last few months as a true future test? If you optimized a metric, did you consider whether that metric truly reflects business success? Showing such thoroughness tells me they can trust your work. </p><p>I recall a portfolio project on image classification where the candidate not only reported accuracy but also deliberately added noise to the images to test robustness and plotted how performance dropped. They also compared their CNN to a simpler logistic regression as a baseline. This thorough evaluation was a green flag, as it demonstrated scientific thinking and honesty about the model&#8217;s capabilities.</p><p>How we score it (Evaluation Rigor):</p><ul><li><p>Level 1 (Needs Improvement): The project shows minimal evaluation. Perhaps only a single metric (like accuracy) is reported without context, or results are presented without validation (e.g., performance only on the training set or a cherry-picked example). There&#8217;s no baseline or benchmark mentioned. You can&#8217;t tell whether 90% accuracy is good or trivial, given the problem. No discussion of errors, assumptions, or limitations is present. This indicates a lack of critical thinking about the results.</p></li><li><p>Level 2 (Good): The project uses standard evaluation practices, e.g., a train/test split or cross-validation, and reports at least one appropriate metric on a held-out set. A baseline may be mentioned (e.g., &#8220;our model beats a random guess, which was 50%&#8221; or &#8220;improves over a simple linear model by 10%&#8221;). The candidate likely includes some error analysis or at least mentions possible improvements. However, the evaluation might still miss deeper issues (for example, reporting overall accuracy without noting that one class was often mispredicted, or not considering how an unbalanced dataset might skew the metric). Solid effort, but not deeply probing.</p></li><li><p>Level 3 (Excellent): The project demonstrates thorough evaluation, considering multiple performance metrics, including precision, recall, ROC, and domain-specific metrics. It establishes a clear baseline, checks for overfitting (train vs. validation curves), uses methods such as cross-validation, performs sensitivity analysis, and tests edge cases. They interpret results in context: Is the performance acceptable? (e.g., &#8220;An F1 of 0.7 means 30% issues missed, and is it acceptable in healthcare?"), and acknowledge limitations like data bias or assumptions. This rigor reflects a mindset of skepticism and decision-making focus, which we value.</p></li></ul><h3>4. Deployment Thinking</h3><p>Deployment thinking evaluates whether you considered how the project&#8217;s solution would be used in a real-world environment. In other words, did you think beyond the notebook? This could include creating a simple web app for your model, following proper coding practices to package your project, or simply writing a paragraph on how you&#8217;d deploy and monitor the model in production.</p><p>In modern data science teams, the work doesn&#8217;t stop at insight or model training. Models often need to be integrated into products or processes. While you might not personally build the entire production pipeline, you will collaborate with engineers or hand off your work for implementation. Hiring managers, therefore, value awareness of deployment considerations. If two candidates both build a decent model, but one also sets up a Flask API or describes a plan for real-time inference, that candidate demonstrates ownership and practicality. It shows they think about reliability, data pipelines, or user impact, not just modeling.</p><p>In fact, not showing any hint of deployment or next steps can be costly. As noted earlier, employers might question how you&#8217;ll add value if &#8220;you can stick your model you-know-where if it&#8217;s not usable in production&#8221;. We test for a mindset of &#8220;production readiness,&#8221; which means you anticipate the steps needed to make your work actually run and keep running in a live setting.</p><p>Consider a portfolio project that predicts stock prices. Deployment considerations might include: &#8220;I scheduled this script to run daily and send an email alert with the latest prediction.&#8221; Or &#8220;I deployed the model as an API using Streamlit so you can try it live.&#8221; Or even, &#8220;In a real company, I&#8217;d retrain this model weekly as new data comes in and monitor the prediction error over time to detect drift.&#8221; These elements turn a good project into a great one by showing you understand the full lifecycle of ML products.</p><p>How we score it (Deployment Thinking):</p><ul><li><p>Level 1 (Needs Improvement): There&#8217;s no mention of deployment or next steps. The project ends at model evaluation. It&#8217;s as if the analysis exists in isolation. There&#8217;s no consideration of how the model could be consumed (e.g., by an application or user) or maintained. The code may be very prototype-like (hard-coded paths, not modular), suggesting it&#8217;s not ready to be used elsewhere. This suggests the candidate hasn&#8217;t considered real-world implementation.</p></li><li><p>Level 2 (Good): The project shows some awareness of deployment, though it&#8217;s minimal. Perhaps the candidate structured their code well or included instructions for running the project. They might mention in passing how the model could be used (e.g., &#8220;this model could be deployed as a REST API&#8221; or &#8220;in production we&#8217;d need to retrain periodically&#8221;). There may not be an actual deployment, but there&#8217;s at least recognition of the need. Alternatively, they might have taken a small step, such as containerizing the project or using a simple dashboard to present results. It&#8217;s a hint that they know deployment is important, even if they haven&#8217;t fully demonstrated it.</p></li><li><p>Level 3 (Excellent): The project actively incorporates deployment considerations or deliverables. The candidate might have a live demo (a web app, an interactive notebook, or a command-line tool) that others can interact with. Or they provide a link to a GitHub repo with a Dockerfile and clear instructions, showing you could actually run their solution easily. They discuss how they would handle tasks such as model monitoring, data updates, scaling, and integration with existing systems. In essence, they treat the project as a product rather than just an analysis. This aligns with what many hiring managers quietly look for, which is a sense of &#8220;ownership &amp; reliability&#8221; in how you approach your work. </p></li></ul><h3>5. Communication</h3><p>Communication in a portfolio context refers to how well you convey the story and results of your project to others. This includes the organization of your content, the explanations you provide (in writing or orally if presented), the visualizations you choose, and the overall storytelling of the project. Essentially, if someone (technical or not) reviews your project, do they quickly grasp the what, why, and how of it?</p><p>Data science is a team sport, and often a business-facing one. It&#8217;s not enough to have a brilliant analysis; you must also communicate insights to colleagues, managers, or clients. Hiring managers, therefore, seek evidence of strong communication skills in your portfolio. A well-documented project with clear Markdown cells, captioned charts, and a logical flow demonstrates that you can explain your work. </p><p>In practical terms, good communication in a portfolio might mean having a README summary for each project, highlighting key results upfront, and guiding the reader through your process step by step. It also means tailoring the depth of technical detail to your audience. For example, explaining technical concepts or decisions in plain language where appropriate, and using visuals to make results intuitive. A common mistake (as we saw) is to dump a lot of code or an overly complex notebook without context. Instead, present a narrative such as what problem you tackled, what the data told you, what model you built, how well it worked, and what it means.</p><p>I once reviewed a candidate&#8217;s portfolio project on customer segmentation. They included a before-and-after chart showing how their clustering grouped customers in a new way, along with a short paragraph: &#8220;Segment 3 (orange in the chart) had the highest lifetime value but low engagement. This insight suggested a targeted re-engagement campaign for this group.&#8221; That single visualization and explanation conveyed the essence of the project&#8217;s impact. Compare that to someone who might simply say, &#8220;I did K-means clustering on customers,&#8221; and dump the cluster centers without context. The former demonstrates excellent communication and understanding of the audience&#8217;s needs.</p><p>How we score it (Communication):</p><ul><li><p>Level 1 (Needs Improvement): The project is difficult to follow. There&#8217;s little to no documentation or explanation. Perhaps the code is there, but the why behind the steps is not explained. Visualizations, if any, are poorly labeled or absent. There&#8217;s no clear introduction or conclusion. Essentially, only someone with the candidate&#8217;s exact knowledge could decipher the project. This raises concerns about how the person would communicate on a team or to stakeholders.</p></li><li><p>Level 2 (Good): The project is understandable with some effort. The candidate provides a decent structure (e.g., sections in a notebook, some comments or markdown explaining each part). They include a couple of key plots or tables and attempt to summarize findings. However, the narrative might not be as tight or engaging as it could be. Perhaps the introduction or conclusions are brief, or the visuals could be clearer. It&#8217;s adequate,  but it might not fully grab a non-expert audience or highlight the most important insights upfront.</p></li><li><p>Level 3 (Excellent): The project is structured like a compelling story or case study, starting with a brief overview of the problem and approach, then explaining the methodology step-by-step in simple terms, and concluding with clear recommendations. Visuals are used effectively to support the findings, each accompanied by a descriptive title or caption. The writing is concise, with minimal jargon or explanations, making it accessible to both technical and business audiences. Attention to design details, such as bullet points or bold highlights, emphasizes key insights. This allows reviewers to quickly grasp the main points or explore detailed reasoning, demonstrating that the candidate can communicate effectively across functions and deliver meaningful insights beyond just modeling. Ideally, the project is engaging, inspires care for the outcome, and showcases strong storytelling skills.</p></li></ul><p>Those are the five rubric categories: </p><ol><li><p>Problem Framing, </p></li><li><p>Data Realism, </p></li><li><p>Evaluation Rigor, </p></li><li><p>Deployment Thinking,</p></li><li><p>Communication. </p></li></ol><p>Great portfolios hit high marks in all five. </p><p>Next, let&#8217;s see how you can apply this rubric to improve your own portfolio, even if you&#8217;re short on time.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.nb-data.com/?utm_source=substack&utm_medium=email&utm_content=share&action=share&quot;,&quot;text&quot;:&quot;Share Non-Brand Data&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://www.nb-data.com/?utm_source=substack&utm_medium=email&utm_content=share&action=share"><span>Share Non-Brand Data</span></a></p><div><hr></div><h2>Quick Fix: How to Upgrade Your Portfolio in 2 Hours</h2><p>You might be thinking, &#8220;This is great for planning new projects, but what about the projects I already have?&#8221; The good news is that you can improve an existing portfolio relatively quickly by addressing the rubric criteria. Here&#8217;s a step-by-step game plan (which you can literally do in an afternoon) to level up your portfolio using the rubric:</p><ol><li><p><strong>Pick Your Best Project (Focus Your Effort):</strong> If you have many projects, identify one or two that are most relevant to the roles you want or that best showcase your skills. It&#8217;s often better to have one polished, rubric-aligned case study than five mediocre ones. Hiring managers spend maybe 2-3 minutes on an initial portfolio glance, so you want your standout work front and center.</p></li><li><p><strong>Add a Clear Problem Statement:</strong> Open your project README or the top section of your notebook. Write a one-paragraph intro that answers: What problem are you solving and why should anyone care? Be specific and use plain language. For example, &#8220;Goal: Reduce customer churn by predicting which users are likely to cancel, so the marketing team can intervene with retention offers.&#8221; This immediately frames the project in terms of business value and hooks the reader.</p></li><li><p><strong>Provide Context on Data:</strong> Next, describe the dataset and why it&#8217;s appropriate (or if it has limitations). If it&#8217;s a well-known dataset, acknowledge that and perhaps note how you treated it: &#8220;We use the Telco Customer Churn dataset (IBM Sample) as a proxy for a subscription business&#8217;s customer data. In a real scenario, we&#8217;d gather recent customer activity and subscription details;  the sample data serves as a stand-in, which I augmented by adding some noise to simulate real-world imperfections.&#8221; If you did any data cleaning or feature engineering, summarize that process. This shows Data Realism. Even a sentence like &#8220;Note: I had to impute missing values for tenure and handle class imbalance (only ~26% churned) by oversampling&#8221; demonstrates that you dealt with data issues (and gets you points on the rubric).</p></li><li><p><strong>Insert a Baseline and Evaluation Highlights:</strong> Scan your results section. Have you indicated what performance you&#8217;d consider good, or what you&#8217;re comparing against? If not, add a baseline. This could be as simple as &#8220;For context, if we predict &#8216;no churn&#8217; for everyone, we&#8217;d get ~74% accuracy (the non-churn rate). Our model achieves 85% accuracy, significantly improving over this baseline.&#8221; Also, ensure you mention the key metric(s) and why they make sense: &#8220;We optimize for recall, to catch as many churning customers as possible, because missing a churning customer is costlier than a false alarm in this context.&#8221; This addition shows Evaluation Rigor and aligns your project with real decision-making. It can be done with just a few lines of text or an extra table comparing metrics.</p></li><li><p><strong>Discuss Deployment (Even Hypothetically):</strong> Add a short section titled &#8220;Deployment &amp; Next Steps&#8221; at the end. Here, write a few sentences about how this model/analysis could be used in production or what you&#8217;d do next if this were a real company project. For example: &#8220;If this model were deployed in a company, I&#8217;d set it up as a daily batch job scoring each active user. Users predicted to churn would be fed into a CRM tool for the marketing team to target. I&#8217;d also monitor the model&#8217;s precision/recall over time &#8211; if performance drifts, I&#8217;d retrain with fresh data. For real deployment, we&#8217;d need to integrate with the data warehouse and ensure predictions happen within a week of a customer&#8217;s last activity.&#8221; You don&#8217;t have to actually deploy it, but showing you understand the path to production is immensely valuable. It shows that you think like someone who wants to drive results, not just build models.</p></li><li><p><strong>Tighten the Narrative and Presentation:</strong> Now polish the communication. Ensure your notebook or report has a logical flow: Introduction &#8594; Data &#8594; Method &#8594; Results &#8594; Conclusion. Add or refine chart titles and axis labels to be more descriptive (e.g., &#8220;Churn Rate by Tenure Group&#8221; instead of &#8220;Figure1.png&#8221;). Consider adding an illustrative plot if you haven&#8217;t (for instance, a bar chart of feature importances or a sample of predictions vs. actual outcomes). Also, write a short conclusion that reiterates the key insight or performance: &#8220;Conclusion: The model can identify ~50% of churners with 80% precision, which could significantly reduce churn if retention offers are effective. The factors of contract length and monthly charges were the strongest churn predictors, aligning with business intuition.&#8221; This helps a skimmer get the point and shows you understand the results in context. Finally, if the project is on GitHub, make sure the README highlights these points and not just the technical setup.</p></li><li><p><strong>Apply the Same Steps to Other Projects (if time permits):</strong> If you have another project that&#8217;s relevant (say one NLP project and one computer vision project to showcase range), repeat the above steps there. But remember, quality over quantity. It&#8217;s better to fully refurbish one project than half-fix three of them. You want at least one example that scores high on all rubric dimensions.</p></li></ol><p>Within about 2 hours, using the steps above, you can transform a bland, academic project into a professional case study. The key is reframing your existing work to speak the language of hiring managers and to highlight business value. </p><div><hr></div><h2><strong>&#128640; Premium Content: Portfolio Rubric Toolkit (Downloadable)</strong></h2><p></p><p><em>The section below is for Premium subscribers and includes downloadable tools &amp; examples to help you implement the ideas above. Upgrade to access the full toolkit.</em> &#128640;</p>
      <p>
          <a href="https://www.nb-data.com/p/the-portfolio-rubric-data-science">
              Read more
          </a>
      </p>
   ]]></content:encoded></item><item><title><![CDATA[Best Financial Data APIs in 2026]]></title><description><![CDATA[A Practical Comparison to Access the Financial Information You Need]]></description><link>https://www.nb-data.com/p/best-financial-data-apis-in-2026</link><guid isPermaLink="false">https://www.nb-data.com/p/best-financial-data-apis-in-2026</guid><dc:creator><![CDATA[Cornellius Yudha Wijaya]]></dc:creator><pubDate>Mon, 12 Jan 2026 06:47:18 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!BJ_q!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F26b84538-cf77-4692-92e1-25faa2148cfa_1400x1013.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!BJ_q!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F26b84538-cf77-4692-92e1-25faa2148cfa_1400x1013.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!BJ_q!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F26b84538-cf77-4692-92e1-25faa2148cfa_1400x1013.jpeg 424w, https://substackcdn.com/image/fetch/$s_!BJ_q!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F26b84538-cf77-4692-92e1-25faa2148cfa_1400x1013.jpeg 848w, https://substackcdn.com/image/fetch/$s_!BJ_q!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F26b84538-cf77-4692-92e1-25faa2148cfa_1400x1013.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!BJ_q!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F26b84538-cf77-4692-92e1-25faa2148cfa_1400x1013.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!BJ_q!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F26b84538-cf77-4692-92e1-25faa2148cfa_1400x1013.jpeg" width="1400" height="1013" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/26b84538-cf77-4692-92e1-25faa2148cfa_1400x1013.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1013,&quot;width&quot;:1400,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!BJ_q!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F26b84538-cf77-4692-92e1-25faa2148cfa_1400x1013.jpeg 424w, https://substackcdn.com/image/fetch/$s_!BJ_q!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F26b84538-cf77-4692-92e1-25faa2148cfa_1400x1013.jpeg 848w, https://substackcdn.com/image/fetch/$s_!BJ_q!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F26b84538-cf77-4692-92e1-25faa2148cfa_1400x1013.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!BJ_q!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F26b84538-cf77-4692-92e1-25faa2148cfa_1400x1013.jpeg 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Photo by <a href="https://unsplash.com/@campaign_creators?utm_source=medium&amp;utm_medium=referral">Campaign Creators</a> on <a href="https://unsplash.com/?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure></div><p>Financial data APIs provide a direct, programmatic pathway to market information. They support a wide range of applications, including financial analytics, research workflows, automated reporting, and data-driven products. In 2026, the ecosystem is mature and competitive. Many providers offer overlapping capabilities on the surface, yet practical differences can affect implementation quality and long-term maintainability.</p><p>In practice, providers vary in their market presence and the continuity of their historical datasets. They also differ in the depth and standardization of basic data, the availability of real-time or streaming access, and the limitations imposed by rate limits. The quality of documentation, integration tools, and licensing terms also influences whether an API remains usable after initial testing. Given these differences, we need to determine which Financial data APIs best fit our needs.</p><p>In this article, we will review the best financial data APIs available in 2026. The objective is to present clear trade-offs rather than a single universal solution. For each provider, I summarize the types of data you can retrieve, the key advantages and disadvantages, and the contexts in which the API is appropriate.</p><p>Curious about it? Let&#8217;s get into it.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.nb-data.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://www.nb-data.com/subscribe?"><span>Subscribe now</span></a></p><div><hr></div><h2><strong>Financial Modeling Prep (FMP)</strong></h2><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!9NRt!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd985b5da-22b2-48e5-8261-a609d2bb05b7_1400x809.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!9NRt!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd985b5da-22b2-48e5-8261-a609d2bb05b7_1400x809.png 424w, https://substackcdn.com/image/fetch/$s_!9NRt!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd985b5da-22b2-48e5-8261-a609d2bb05b7_1400x809.png 848w, https://substackcdn.com/image/fetch/$s_!9NRt!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd985b5da-22b2-48e5-8261-a609d2bb05b7_1400x809.png 1272w, https://substackcdn.com/image/fetch/$s_!9NRt!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd985b5da-22b2-48e5-8261-a609d2bb05b7_1400x809.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!9NRt!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd985b5da-22b2-48e5-8261-a609d2bb05b7_1400x809.png" width="1400" height="809" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/d985b5da-22b2-48e5-8261-a609d2bb05b7_1400x809.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:809,&quot;width&quot;:1400,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Best Financial Data APIs in 2026&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Best Financial Data APIs in 2026" title="Best Financial Data APIs in 2026" srcset="https://substackcdn.com/image/fetch/$s_!9NRt!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd985b5da-22b2-48e5-8261-a609d2bb05b7_1400x809.png 424w, https://substackcdn.com/image/fetch/$s_!9NRt!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd985b5da-22b2-48e5-8261-a609d2bb05b7_1400x809.png 848w, https://substackcdn.com/image/fetch/$s_!9NRt!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd985b5da-22b2-48e5-8261-a609d2bb05b7_1400x809.png 1272w, https://substackcdn.com/image/fetch/$s_!9NRt!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd985b5da-22b2-48e5-8261-a609d2bb05b7_1400x809.png 1456w" sizes="100vw"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h3><strong>Overview</strong></h3><p><a href="https://site.financialmodelingprep.com/">Financial Modeling Prep (FMP)</a> is a financial data API provider that focuses on broad market coverage and practical endpoints for application development. It offers market prices and fundamental datasets through a straightforward REST interface.</p><h3><strong>Advantages</strong></h3><ul><li><p><strong>All-in-one coverage:</strong> Provides pricing data, company fundamentals, macroeconomic indicators, and market news in one place.</p></li><li><p><strong>Rich endpoint selection:</strong> Includes many ready-to-use endpoints, reducing the need for additional data stitching.</p></li><li><p><strong>Strong developer usability:</strong> Clear documentation and a predictable API structure make integration and iteration efficient.</p></li><li><p><strong>Product-oriented fit:</strong> Well-suited for building stock screeners, analytics dashboards, and research pipelines that combine price and fundamental data.</p></li></ul><h3><strong>Disadvantages</strong></h3><ul><li><p><strong>Limited free tier:</strong> The free plan is suitable for testing and light usage, but rate limits and reduced data depth limit its usefulness.</p></li><li><p><strong>Advanced access requires upgrades:</strong> Certain datasets and higher-capacity usage are reserved for higher-paid tiers.</p></li></ul><h3><strong>Best for</strong></h3><p>Teams or individuals who want a single API that can support both market data and fundamentals for analysis and product development.</p><h3><strong>Ideal starting plan</strong></h3><p>Start with the free tier to validate endpoints and data fit, then move to the entry-level paid tier once you need consistent throughput or deeper coverage.</p><div><hr></div><h2><strong>Alpha Vantage</strong></h2><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!FWWm!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6f963651-86f8-4ed7-9ced-4c94a08d1193_1400x720.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!FWWm!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6f963651-86f8-4ed7-9ced-4c94a08d1193_1400x720.png 424w, https://substackcdn.com/image/fetch/$s_!FWWm!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6f963651-86f8-4ed7-9ced-4c94a08d1193_1400x720.png 848w, https://substackcdn.com/image/fetch/$s_!FWWm!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6f963651-86f8-4ed7-9ced-4c94a08d1193_1400x720.png 1272w, https://substackcdn.com/image/fetch/$s_!FWWm!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6f963651-86f8-4ed7-9ced-4c94a08d1193_1400x720.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!FWWm!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6f963651-86f8-4ed7-9ced-4c94a08d1193_1400x720.png" width="1400" height="720" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/6f963651-86f8-4ed7-9ced-4c94a08d1193_1400x720.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:720,&quot;width&quot;:1400,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Best Financial Data APIs in 2026&quot;,&quot;title&quot;:&quot;Best Financial Data APIs in 2026&quot;,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Best Financial Data APIs in 2026" title="Best Financial Data APIs in 2026" srcset="https://substackcdn.com/image/fetch/$s_!FWWm!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6f963651-86f8-4ed7-9ced-4c94a08d1193_1400x720.png 424w, https://substackcdn.com/image/fetch/$s_!FWWm!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6f963651-86f8-4ed7-9ced-4c94a08d1193_1400x720.png 848w, https://substackcdn.com/image/fetch/$s_!FWWm!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6f963651-86f8-4ed7-9ced-4c94a08d1193_1400x720.png 1272w, https://substackcdn.com/image/fetch/$s_!FWWm!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6f963651-86f8-4ed7-9ced-4c94a08d1193_1400x720.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h3><strong>Overview</strong></h3><p><a href="https://www.alphavantage.co/">Alpha Vantage</a> is a comprehensive financial data API platform designed for both retail investors and institutional trading systems. It provides extensive coverage across equities, options, forex, cryptocurrencies, and macroeconomic datasets, combining real-time market feeds with deep historical data and built-in analytics.<br><br>A key differentiator is that Alpha Vantage sources data from licensed exchanges such as NASDAQ and Options Price Reporting Authority (OPRA), enabling access to professional-grade market data infrastructure through a simple API interface. With millisecond-level real-time updates and more than 20 years of historical price and fundamental data, the platform supports everything from educational projects to institutional-scale algorithmic trading systems.</p><h3><strong>Advantages</strong></h3><ul><li><p><strong>Institutional-grade data licensing:</strong> Alpha Vantage is officially licensed by major market data authorities, including NASDAQ and OPRA, ensuring reliable and compliant access to equity and options data streams. This makes it suitable for professional trading environments that require high-quality exchange-sourced data.</p></li><li><p><strong>Real-time and low-latency market data</strong><br>The platform delivers millisecond-level real-time data, enabling use cases such as algorithmic trading, quantitative research, and automated portfolio monitoring where latency and accuracy are critical.</p></li><li><p><strong>Extensive historical coverage</strong><br>Alpha Vantage offers 20+ years of historical price data across global markets, along with long-range fundamental datasets. This depth allows analysts and quantitative researchers to perform robust backtesting and long-horizon market studies.</p></li><li><p><strong>Built-in technical analysis library</strong><br>The API includes a large catalogue of technical indicators that can be retrieved directly through API calls. This significantly reduces engineering overhead for traders and developers who would otherwise need to implement indicator calculations themselves.</p></li><li><p><strong>Accessible architecture for all users</strong><br>Despite its institutional capabilities, Alpha Vantage maintains a clean, developer-friendly API structure that allows beginners, independent traders, and large trading firms to integrate financial data pipelines quickly.</p></li></ul><h3><strong>Disadvantages</strong></h3><ul><li><p><strong>Free tier constraints:</strong> similar to other providers, certain features are not included in the free tier of Alpha Vantage for compliance and anti-bot purposes.</p></li></ul><h3><strong>Best for</strong></h3><p>Alpha Vantage is particularly well-suited for:</p><ul><li><p>Retail investors and independent developers building trading tools or investment dashboards</p></li><li><p>Quantitative researchers requiring long historical datasets for backtesting</p></li><li><p>Algorithmic and institutional trading systems that need real-time exchange-licensed data feeds</p></li><li><p>Fintech platforms seeking a single API for market data, fundamentals, and analytics</p></li></ul><div><hr></div><h2><strong>EOD Historical Data (EODHD)</strong></h2><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!OTn8!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcdf3085f-f1be-4cc8-8c06-d281768b2150_1400x729.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!OTn8!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcdf3085f-f1be-4cc8-8c06-d281768b2150_1400x729.png 424w, https://substackcdn.com/image/fetch/$s_!OTn8!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcdf3085f-f1be-4cc8-8c06-d281768b2150_1400x729.png 848w, https://substackcdn.com/image/fetch/$s_!OTn8!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcdf3085f-f1be-4cc8-8c06-d281768b2150_1400x729.png 1272w, https://substackcdn.com/image/fetch/$s_!OTn8!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcdf3085f-f1be-4cc8-8c06-d281768b2150_1400x729.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!OTn8!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcdf3085f-f1be-4cc8-8c06-d281768b2150_1400x729.png" width="1400" height="729" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/cdf3085f-f1be-4cc8-8c06-d281768b2150_1400x729.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:729,&quot;width&quot;:1400,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Best Financial Data APIs in 2026&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Best Financial Data APIs in 2026" title="Best Financial Data APIs in 2026" srcset="https://substackcdn.com/image/fetch/$s_!OTn8!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcdf3085f-f1be-4cc8-8c06-d281768b2150_1400x729.png 424w, https://substackcdn.com/image/fetch/$s_!OTn8!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcdf3085f-f1be-4cc8-8c06-d281768b2150_1400x729.png 848w, https://substackcdn.com/image/fetch/$s_!OTn8!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcdf3085f-f1be-4cc8-8c06-d281768b2150_1400x729.png 1272w, https://substackcdn.com/image/fetch/$s_!OTn8!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcdf3085f-f1be-4cc8-8c06-d281768b2150_1400x729.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h3><strong>Overview</strong></h3><p><a href="https://eodhd.com/">EOD Historical Data (EODHD)</a> is a market data provider known for broad international exchange coverage and long historical time series. It combines end-of-day and intraday pricing with fundamentals and several optional datasets that support more advanced workflows.</p><h3><strong>Advantages</strong></h3><ul><li><p><strong>Strong global coverage with a long history:</strong> Offers broad exchange support and historical depth suitable for long-horizon analysis and backtesting.</p></li><li><p><strong>High value on paid tiers:</strong> Paid plans are competitively priced for the amount of data provided, especially when you need global markets and deeper history.</p></li><li><p><strong>Solid fundamentals and add-ons:</strong> Includes company fundamentals and supports additional datasets such as options and macroeconomic indicators, depending on the plan.</p></li><li><p><strong>Practical integration options:</strong> Supports bulk-style access for efficient retrieval, provides some streaming capabilities, and offers spreadsheet-friendly integrations for Excel and Google Sheets.</p></li></ul><h3><strong>Disadvantages</strong></h3><ul><li><p><strong>The free tier is primarily for evaluation.</strong> Request limits are restrictive, so it is best treated as a connectivity and fit check rather than a long-term solution.</p></li><li><p><strong>Real-time depth is uneven:</strong> Real-time availability and latency can differ by asset class and region, with stronger coverage typically in U.S. markets than in many international markets.</p></li></ul><h3><strong>Best for</strong></h3><p>Projects that require global market coverage and long historical datasets, especially when you want substantial value from paid plans.</p><div><hr></div><h2><strong>Finnhub</strong></h2><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!eynQ!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2780e149-0099-4fe5-9df6-fd68c7715d2a_1400x548.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!eynQ!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2780e149-0099-4fe5-9df6-fd68c7715d2a_1400x548.png 424w, https://substackcdn.com/image/fetch/$s_!eynQ!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2780e149-0099-4fe5-9df6-fd68c7715d2a_1400x548.png 848w, https://substackcdn.com/image/fetch/$s_!eynQ!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2780e149-0099-4fe5-9df6-fd68c7715d2a_1400x548.png 1272w, https://substackcdn.com/image/fetch/$s_!eynQ!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2780e149-0099-4fe5-9df6-fd68c7715d2a_1400x548.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!eynQ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2780e149-0099-4fe5-9df6-fd68c7715d2a_1400x548.png" width="1400" height="548" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/2780e149-0099-4fe5-9df6-fd68c7715d2a_1400x548.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:548,&quot;width&quot;:1400,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Best Financial Data APIs in 2026&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Best Financial Data APIs in 2026" title="Best Financial Data APIs in 2026" srcset="https://substackcdn.com/image/fetch/$s_!eynQ!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2780e149-0099-4fe5-9df6-fd68c7715d2a_1400x548.png 424w, https://substackcdn.com/image/fetch/$s_!eynQ!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2780e149-0099-4fe5-9df6-fd68c7715d2a_1400x548.png 848w, https://substackcdn.com/image/fetch/$s_!eynQ!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2780e149-0099-4fe5-9df6-fd68c7715d2a_1400x548.png 1272w, https://substackcdn.com/image/fetch/$s_!eynQ!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F2780e149-0099-4fe5-9df6-fd68c7715d2a_1400x548.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h3><strong>Overview</strong></h3><p><a href="https://finnhub.io/">Finnhub</a> is a financial data API that combines market quotes with news and event-oriented datasets. It is widely used for prototyping and product development because it offers accessible pricing and a relatively broad feature set.</p><h3><strong>Advantages</strong></h3><ul><li><p><strong>Generous free-tier limits:</strong> The free plan typically provides sufficient request capacity to support meaningful experimentation and early-stage prototypes.</p></li><li><p><strong>Balanced dataset mix:</strong> Provides a practical combination of quotes, news, sentiment signals, and market calendars, helping build context-aware applications.</p></li><li><p><strong>WebSocket support:</strong> Provides streaming access through WebSockets, enabling lower-latency updates without relying exclusively on polling.</p></li></ul><h3><strong>Disadvantages</strong></h3><ul><li><p><strong>Shallower fundamentals:</strong> Fundamental coverage is generally less comprehensive than that of providers that focus heavily on financial statements and deep company datasets.</p></li><li><p><strong>Paid plans for full access:</strong> Longer historical depth and specific premium endpoints are gated behind paid tiers, particularly for more advanced or higher-volume use cases.</p></li></ul><h3><strong>Best for</strong></h3><p>Rapid prototyping and application development that benefits from combining price data with news, sentiment, and event calendars.</p><div><hr></div><h2><strong>Tiingo</strong></h2><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!ZpQT!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4468ebfd-2c78-49d3-978d-671aed7b5c6b_1400x704.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!ZpQT!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4468ebfd-2c78-49d3-978d-671aed7b5c6b_1400x704.png 424w, https://substackcdn.com/image/fetch/$s_!ZpQT!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4468ebfd-2c78-49d3-978d-671aed7b5c6b_1400x704.png 848w, https://substackcdn.com/image/fetch/$s_!ZpQT!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4468ebfd-2c78-49d3-978d-671aed7b5c6b_1400x704.png 1272w, https://substackcdn.com/image/fetch/$s_!ZpQT!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4468ebfd-2c78-49d3-978d-671aed7b5c6b_1400x704.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!ZpQT!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4468ebfd-2c78-49d3-978d-671aed7b5c6b_1400x704.png" width="1400" height="704" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/4468ebfd-2c78-49d3-978d-671aed7b5c6b_1400x704.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:704,&quot;width&quot;:1400,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Best Financial Data APIs in 2026&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Best Financial Data APIs in 2026" title="Best Financial Data APIs in 2026" srcset="https://substackcdn.com/image/fetch/$s_!ZpQT!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4468ebfd-2c78-49d3-978d-671aed7b5c6b_1400x704.png 424w, https://substackcdn.com/image/fetch/$s_!ZpQT!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4468ebfd-2c78-49d3-978d-671aed7b5c6b_1400x704.png 848w, https://substackcdn.com/image/fetch/$s_!ZpQT!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4468ebfd-2c78-49d3-978d-671aed7b5c6b_1400x704.png 1272w, https://substackcdn.com/image/fetch/$s_!ZpQT!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F4468ebfd-2c78-49d3-978d-671aed7b5c6b_1400x704.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h3><strong>Overview</strong></h3><p><a href="https://www.tiingo.com/">Tiingo</a> is a financial data provider that emphasizes clean historical market data and straightforward API access. It is commonly used in research and backtesting workflows, particularly by individual developers and small teams.</p><h3><strong>Advantages</strong></h3><ul><li><p><strong>Substantial value for individuals:</strong> Paid plans are typically affordable given the included data and request limits, making Tiingo attractive to solo builders.</p></li><li><p><strong>High-quality historical end-of-day data:</strong> Tiingo is well-regarded for stable, consistent EOD datasets that support backtesting and long-horizon analysis.</p></li><li><p><strong>Practical fundamentals for U.S. equities:</strong> On paid tiers, Tiingo provides solid fundamental coverage of U.S. companies, often sufficient for screening and basic factor research.</p></li></ul><h3><strong>Disadvantages</strong></h3><ul><li><p><strong>Less comprehensive as an all-in-one source:</strong> Tiingo is not primarily positioned as a single provider of macroeconomic data and commodities coverage so that you may need supplementary sources depending on your requirements.</p></li><li><p><strong>Real-time and intraday are not the core focus:</strong> While intraday data may be available, it is not as central or as feature-complete as providers optimized for streaming or high-frequency use cases.</p></li></ul><h3><strong>Best for</strong></h3><p>Individuals and small teams who want reliable historical market data for analysis and backtesting, with reasonable U.S. fundamentals on a cost-effective paid plan.</p><div><hr></div><h2><strong>Twelve Data</strong></h2><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!uL_I!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F073362e2-faa8-41fb-b680-3aac81f30c01_1400x811.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!uL_I!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F073362e2-faa8-41fb-b680-3aac81f30c01_1400x811.png 424w, https://substackcdn.com/image/fetch/$s_!uL_I!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F073362e2-faa8-41fb-b680-3aac81f30c01_1400x811.png 848w, https://substackcdn.com/image/fetch/$s_!uL_I!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F073362e2-faa8-41fb-b680-3aac81f30c01_1400x811.png 1272w, https://substackcdn.com/image/fetch/$s_!uL_I!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F073362e2-faa8-41fb-b680-3aac81f30c01_1400x811.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!uL_I!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F073362e2-faa8-41fb-b680-3aac81f30c01_1400x811.png" width="1400" height="811" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/073362e2-faa8-41fb-b680-3aac81f30c01_1400x811.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:811,&quot;width&quot;:1400,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Best Financial Data APIs in 2026&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Best Financial Data APIs in 2026" title="Best Financial Data APIs in 2026" srcset="https://substackcdn.com/image/fetch/$s_!uL_I!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F073362e2-faa8-41fb-b680-3aac81f30c01_1400x811.png 424w, https://substackcdn.com/image/fetch/$s_!uL_I!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F073362e2-faa8-41fb-b680-3aac81f30c01_1400x811.png 848w, https://substackcdn.com/image/fetch/$s_!uL_I!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F073362e2-faa8-41fb-b680-3aac81f30c01_1400x811.png 1272w, https://substackcdn.com/image/fetch/$s_!uL_I!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F073362e2-faa8-41fb-b680-3aac81f30c01_1400x811.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h3><strong>Overview</strong></h3><p><a href="https://twelvedata.com/">Twelve Data</a> is a market data API focused on time-series access across multiple asset classes. It is commonly used for applications that need consistent pricing endpoints for stocks, foreign exchange, and cryptocurrencies.</p><h3><strong>Advantages</strong></h3><ul><li><p><strong>Clean multi-asset time-series API:</strong> Provides a uniform way to retrieve historical and intraday price data across stocks, FX, and crypto, simplifying implementation.</p></li><li><p><strong>Strong developer experience:</strong> Documentation is generally clear, integration is straightforward, and common workflows are well-supported.</p></li><li><p><strong>Built-in indicators:</strong> Includes technical indicators that reduce the effort required to add analytics to a prototype or dashboard.</p></li></ul><h3><strong>Disadvantages</strong></h3><ul><li><p><strong>Paid tiers may feel expensive:</strong> Pricing can be less attractive when compared with alternatives that offer broader datasets at similar cost levels.</p></li><li><p><strong>Limited depth beyond prices:</strong> Fundamental coverage and macroeconomic datasets are typically less extensive than those from all-in-one providers.</p></li></ul><h3><strong>Best for</strong></h3><p>Projects that primarily require reliable multi-asset price time series, a developer-friendly API, and convenient technical indicators.</p><div><hr></div><h2><strong>Marketstack</strong></h2><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!ksB7!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1af7706e-3977-4328-a94f-6e4b2a3c71c1_1400x725.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!ksB7!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1af7706e-3977-4328-a94f-6e4b2a3c71c1_1400x725.png 424w, https://substackcdn.com/image/fetch/$s_!ksB7!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1af7706e-3977-4328-a94f-6e4b2a3c71c1_1400x725.png 848w, https://substackcdn.com/image/fetch/$s_!ksB7!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1af7706e-3977-4328-a94f-6e4b2a3c71c1_1400x725.png 1272w, https://substackcdn.com/image/fetch/$s_!ksB7!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1af7706e-3977-4328-a94f-6e4b2a3c71c1_1400x725.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!ksB7!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1af7706e-3977-4328-a94f-6e4b2a3c71c1_1400x725.png" width="1400" height="725" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/1af7706e-3977-4328-a94f-6e4b2a3c71c1_1400x725.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:725,&quot;width&quot;:1400,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Best Financial Data APIs in 2026&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Best Financial Data APIs in 2026" title="Best Financial Data APIs in 2026" srcset="https://substackcdn.com/image/fetch/$s_!ksB7!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1af7706e-3977-4328-a94f-6e4b2a3c71c1_1400x725.png 424w, https://substackcdn.com/image/fetch/$s_!ksB7!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1af7706e-3977-4328-a94f-6e4b2a3c71c1_1400x725.png 848w, https://substackcdn.com/image/fetch/$s_!ksB7!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1af7706e-3977-4328-a94f-6e4b2a3c71c1_1400x725.png 1272w, https://substackcdn.com/image/fetch/$s_!ksB7!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F1af7706e-3977-4328-a94f-6e4b2a3c71c1_1400x725.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h3><strong>Overview</strong></h3><p><a href="https://marketstack.com/">Marketstack</a> is a market data API focused on global equity pricing, with coverage across many stock exchanges. It is designed for simple, real-time, and historical stock price retrieval via a lightweight REST interface.</p><h3><strong>Advantages</strong></h3><ul><li><p><strong>Simple global stock pricing access:</strong> Works well when your primary need is equity quotes and historical prices across multiple markets, without complex endpoint structures.</p></li><li><p><strong>Affordable entry-level paid tier:</strong> Paid plans are typically priced for basic application use cases, making them practical for small dashboards and lightweight integrations.</p></li></ul><h3><strong>Disadvantages</strong></h3><ul><li><p><strong>Limited fundamentals and extended datasets:</strong> Marketstack is primarily price-oriented and offers fewer fundamentals, corporate datasets, and value-added endpoints than all-in-one providers.</p></li><li><p><strong>No integrated FX or crypto coverage:</strong> Foreign exchange and cryptocurrency data are not included in the core product and often require separate services.</p></li></ul><h3><strong>Best for</strong></h3><p>Basic applications that need straightforward global stock price data at a predictable cost, without strong requirements for fundamentals or multi-asset coverage.</p><div><hr></div><h2><strong>Polygon.io (Massive)</strong></h2><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!ONPz!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5c2c6eab-a483-4c4e-896a-c165fe371878_1400x643.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!ONPz!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5c2c6eab-a483-4c4e-896a-c165fe371878_1400x643.png 424w, https://substackcdn.com/image/fetch/$s_!ONPz!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5c2c6eab-a483-4c4e-896a-c165fe371878_1400x643.png 848w, https://substackcdn.com/image/fetch/$s_!ONPz!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5c2c6eab-a483-4c4e-896a-c165fe371878_1400x643.png 1272w, https://substackcdn.com/image/fetch/$s_!ONPz!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5c2c6eab-a483-4c4e-896a-c165fe371878_1400x643.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!ONPz!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5c2c6eab-a483-4c4e-896a-c165fe371878_1400x643.png" width="1400" height="643" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/5c2c6eab-a483-4c4e-896a-c165fe371878_1400x643.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:643,&quot;width&quot;:1400,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;Best Financial Data APIs in 2026&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Best Financial Data APIs in 2026" title="Best Financial Data APIs in 2026" srcset="https://substackcdn.com/image/fetch/$s_!ONPz!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5c2c6eab-a483-4c4e-896a-c165fe371878_1400x643.png 424w, https://substackcdn.com/image/fetch/$s_!ONPz!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5c2c6eab-a483-4c4e-896a-c165fe371878_1400x643.png 848w, https://substackcdn.com/image/fetch/$s_!ONPz!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5c2c6eab-a483-4c4e-896a-c165fe371878_1400x643.png 1272w, https://substackcdn.com/image/fetch/$s_!ONPz!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F5c2c6eab-a483-4c4e-896a-c165fe371878_1400x643.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><h3><strong>Overview</strong></h3><p><a href="https://massive.com/">Polygon.io</a> (now positioned under the &#8220;Massive&#8221; brand) is a market data provider focused on high-performance access to U.S. market data. It is best known for low-latency delivery, streaming support, and granular datasets suitable for trading-oriented workloads.</p><h3><strong>Advantages</strong></h3><ul><li><p><strong>Strong U.S. real-time and high-frequency coverage:</strong> Well-suited for use cases that require timely quotes and detailed market activity in U.S. equities.</p></li><li><p><strong>High performance and streaming:</strong> Provides WebSocket streaming and fast REST endpoints, which support responsive applications and real-time monitoring.</p></li><li><p><strong>Granular historical depth:</strong> With the appropriate plan, it offers tick-level history and detailed aggregates that are valuable for advanced backtesting and microstructure analysis.</p></li></ul><h3><strong>Disadvantages</strong></h3><ul><li><p><strong>U.S.-first scope:</strong> Coverage is primarily U.S.-focused, making it not the best fit for projects requiring broad global exchange coverage.</p></li><li><p><strong>Cost scales quickly for premium access:</strong> Real-time entitlements and extensive historical depth are typically available only on higher-priced tiers, which can be more expensive than general-purpose APIs.</p></li></ul><h3><strong>Best for</strong></h3><p>Trading-oriented applications that require high-performance, real-time U.S. market data and benefit from streaming and tick-level history.</p><div><hr></div><h2><strong>Conclusion</strong></h2><p>The financial data API landscape in 2026 is strong, but there is no single provider that is universally best for every scenario. The most practical approach is to select an API that matches the breadth and reliability you need, then confirm that its rate limits, historical depth, and licensing terms align with your data use.</p><p>In 2026, here are the financial data APIs you should know:</p><ul><li><p><strong>Financial Modeling Prep (FMP):</strong> A broad, all-in-one API that combines market prices with fundamentals and additional datasets for building complete financial applications.</p></li><li><p><strong>Alpha Vantage:</strong> A simple API that is well-suited for learning and small projects, especially if you want built-in technical indicators.</p></li><li><p><strong>EOD Historical Data (EODHD):</strong> A strong option for global exchange coverage and long historical datasets, with solid paid-plan value and useful add-ons.</p></li><li><p><strong>Finnhub:</strong> A developer-friendly API with generous free-tier limits and a practical mix of quotes, news, sentiment, and market calendars.</p></li><li><p><strong>Tiingo:</strong> A cost-effective choice for clean end-of-day historical data and backtesting, with good U.S. fundamentals on paid tiers.</p></li><li><p><strong>Twelve Data:</strong> A clean multi-asset time series API for stocks, FX, and crypto, designed for straightforward integration and indicator-driven workflows.</p></li><li><p><strong>Marketstack:</strong> A lightweight API for global stock price data with affordable entry pricing, best for basic applications.</p></li><li><p><strong>Polygon.io (Massive):</strong> A high-performance provider focused on real-time and high-frequency U.S. market data, including streaming and granular history.</p></li></ul><p>I hope it has helped!</p>]]></content:encoded></item><item><title><![CDATA[Batch Screening Fundamentals with Financial Modeling Prep and Streamlit]]></title><description><![CDATA[Build a Lightweight Stock Screener For Your Fundamental Analysis]]></description><link>https://www.nb-data.com/p/batch-screening-fundamentals-with</link><guid isPermaLink="false">https://www.nb-data.com/p/batch-screening-fundamentals-with</guid><dc:creator><![CDATA[Cornellius Yudha Wijaya]]></dc:creator><pubDate>Wed, 07 Jan 2026 14:16:46 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!dIlI!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd27bb505-b22a-4f85-a301-0907c2102fcb_1600x1067.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!dIlI!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd27bb505-b22a-4f85-a301-0907c2102fcb_1600x1067.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!dIlI!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd27bb505-b22a-4f85-a301-0907c2102fcb_1600x1067.jpeg 424w, https://substackcdn.com/image/fetch/$s_!dIlI!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd27bb505-b22a-4f85-a301-0907c2102fcb_1600x1067.jpeg 848w, https://substackcdn.com/image/fetch/$s_!dIlI!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd27bb505-b22a-4f85-a301-0907c2102fcb_1600x1067.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!dIlI!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd27bb505-b22a-4f85-a301-0907c2102fcb_1600x1067.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!dIlI!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd27bb505-b22a-4f85-a301-0907c2102fcb_1600x1067.jpeg" width="1456" height="971" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/d27bb505-b22a-4f85-a301-0907c2102fcb_1600x1067.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:971,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!dIlI!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd27bb505-b22a-4f85-a301-0907c2102fcb_1600x1067.jpeg 424w, https://substackcdn.com/image/fetch/$s_!dIlI!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd27bb505-b22a-4f85-a301-0907c2102fcb_1600x1067.jpeg 848w, https://substackcdn.com/image/fetch/$s_!dIlI!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd27bb505-b22a-4f85-a301-0907c2102fcb_1600x1067.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!dIlI!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd27bb505-b22a-4f85-a301-0907c2102fcb_1600x1067.jpeg 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Photo by <a href="https://unsplash.com/@goshua13?utm_source=medium&amp;utm_medium=referral">Joshua Aragon</a> on <a href="https://unsplash.com?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure></div><p>Batch screening matters because most real-world financial workflows are not about understanding one company. They are about narrowing down a universe. In practice, you start with a watchlist, an index, or a sector set, then ask simple questions such as: which companies have strong profitability, manageable leverage, and healthy cash generation? That first pass turns an overwhelming list of tickers into a short list you can actually research.</p><p>The challenge is that screening requires repetition. If you fetch fundamentals one company at a time, you end up rewriting the same code path for every symbol: call the endpoint, parse the JSON, extract a few fields, compute ratios, and handle missing data. Doing this manually in notebooks does not scale, and it is easy to introduce inconsistencies across analyses.</p><p>In this article, we will build a small-batch screening workflow using <a href="https://site.financialmodelingprep.com/developer/docs">Financial Modeling Prep</a>&#8217;s stable fundamentals endpoints, making it work even on the free tier, pulling the data, and wrapping it in a lightweight Streamlit UI so you can screen companies interactively and export the results for deeper analysis.</p><p>Let&#8217;s get into it!</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.nb-data.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://www.nb-data.com/subscribe?"><span>Subscribe now</span></a></p><div><hr></div><h3>Foundation</h3><p>You can access the entire code used in this tutorial in this repository.</p><p>Batch screening is basically the process of &#8216;shortlisting&#8217; in fundamental analysis. Rather than analyzing one company at a time, you begin with a list of tickers and systematically apply the same criteria: retrieve fundamentals, calculate several ratios, filter, and rank. Performing this manually in notebooks can become repetitive and may lead to inconsistencies.</p><p>A small, structured project helps you standardize the workflow, reuse parsing and ratio logic, and produce a clean output table you can export or integrate into a dashboard.</p><p>In this article, we will build a minimal batch fundamentals screener that does three things:</p><ul><li><p><strong>Fetch</strong> the latest annual fundamentals for a list of tickers</p></li><li><p><strong>Compute</strong> simple screening metrics (for example, ROE and debt-to-equity)</p></li><li><p><strong>Filter and display</strong> the shortlist in a lightweight <strong>Streamlit UI</strong>, with an option to export results as CSV.</p></li></ul><p>This is not a complete analytics platform. It is a compact workflow you can reuse whenever you want to screen a set of companies before deeper analysis.</p><div><hr></div><h3>The Data Source</h3><p>All data comes from Financial Modeling Prep&#8217;s stable API, using a single base URL:</p><pre><code>https://financialmodelingprep.com/stable</code></pre><p>Each function is expressed as an endpoint on this base URL, with parameters passed via query strings. For this screener, we only use a small subset of endpoints focused on company fundamentals:</p><ul><li><p><strong>Income statement (</strong><code>income-statement</code><strong>)</strong>: revenue, net income, and other income statement fields</p></li><li><p><strong>Balance sheet (</strong><code>balance-sheet-statement</code><strong>)</strong>: total assets, total liabilities, and equity fields</p></li><li><p><strong>Cash flow statement (</strong><code>cash-flow-statement</code><strong>)</strong>: operating cash flow and other cash flow items</p></li></ul><p>Across these endpoints, we use consistent parameters:</p><ul><li><p><code>symbol</code>: the ticker (e.g., AAPL)</p></li><li><p><code>period</code>: <code>annual</code> (to keep the example simple and consistent)</p></li><li><p><code>limit</code>: usually <code>1</code> for &#8220;latest snapshot&#8221; screening (you can extend later to multi-year stability checks)</p></li></ul><p>These three statements are sufficient to reconstruct a basic snapshot of a company&#8217;s fundamentals and compute simple screening ratios.</p><div><hr></div><h3>What the Batch Screener Does</h3><p>Instead of exposing REST endpoints like the previous microservice, this project produces a screening table.</p><p>Given a list of tickers, it will:</p><ol><li><p>Pull the latest annual income statement, balance sheet, and cash flow statement for each ticker</p></li><li><p>Compute a few simple metrics, such as:</p></li></ol><ul><li><p><strong>ROE</strong> = netIncome / totalEquity</p></li><li><p><strong>Debt-to-Equity</strong> = totalLiabilities / totalEquity</p></li><li><p><strong>Cash flow health</strong> using operatingCashFlow (for example, requiring it to be positive)</p></li></ul><p>3. Apply thresholds to filter the universe into a shortlist</p><p>4. Display results in Streamlit and allow CSV export for follow-up analysis</p><div><hr></div><h3>Project Architecture</h3><p>We keep the project small and modular:</p><pre><code>fmp_batch_screening/
&#9500;&#9472; app/
&#9474;  &#9500;&#9472; __init__.py
&#9474;  &#9500;&#9472; config.py           # loads env vars (API key + base URL)
&#9474;  &#9500;&#9472; bulk_client.py      # fetches statements per ticker (batch via loop)
&#9474;  &#9500;&#9472; screening.py        # computes ratios + applies filters
&#9474;  &#9492;&#9472; streamlit_app.py    # Streamlit UI (inputs, sliders, table, export)
&#9500;&#9472; requirements.txt
&#9492;&#9472; .env.example</code></pre><p>At a high level, the flow is as follows:</p><div class="captioned-image-container"><figure><a class="image-link image2" target="_blank" href="https://substackcdn.com/image/fetch/$s_!BfHv!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc8b9267c-08d5-4647-86ce-7c9f1f6e2e6d_1600x143.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!BfHv!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc8b9267c-08d5-4647-86ce-7c9f1f6e2e6d_1600x143.png 424w, https://substackcdn.com/image/fetch/$s_!BfHv!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc8b9267c-08d5-4647-86ce-7c9f1f6e2e6d_1600x143.png 848w, https://substackcdn.com/image/fetch/$s_!BfHv!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc8b9267c-08d5-4647-86ce-7c9f1f6e2e6d_1600x143.png 1272w, https://substackcdn.com/image/fetch/$s_!BfHv!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc8b9267c-08d5-4647-86ce-7c9f1f6e2e6d_1600x143.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!BfHv!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc8b9267c-08d5-4647-86ce-7c9f1f6e2e6d_1600x143.png" width="1456" height="130" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/c8b9267c-08d5-4647-86ce-7c9f1f6e2e6d_1600x143.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:130,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!BfHv!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc8b9267c-08d5-4647-86ce-7c9f1f6e2e6d_1600x143.png 424w, https://substackcdn.com/image/fetch/$s_!BfHv!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc8b9267c-08d5-4647-86ce-7c9f1f6e2e6d_1600x143.png 848w, https://substackcdn.com/image/fetch/$s_!BfHv!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc8b9267c-08d5-4647-86ce-7c9f1f6e2e6d_1600x143.png 1272w, https://substackcdn.com/image/fetch/$s_!BfHv!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc8b9267c-08d5-4647-86ce-7c9f1f6e2e6d_1600x143.png 1456w" sizes="100vw" loading="lazy"></picture><div></div></div></a></figure></div><div><hr></div><h3>Building the Batch Screening</h3><p>We will start building our batch screening system. We will cover</p><h4>Step 1: Define dependencies (<code>requirements.txt</code>)</h4><p>Before writing any code, we want to lock down the project dependencies. This keeps the environment reproducible and makes it easy for anyone to install and run the screener.</p><p>Create a <code>requirements.txt</code> in the project root:</p><pre><code>requests
python-dotenv
pandas
streamlit</code></pre><p>Install them in your CLI:</p><pre><code>pip install -r requirements.txt</code></pre><p>What happens here is that:</p><ul><li><p><code>requests</code> handles HTTP calls to the FMP API.</p></li><li><p><code>python-dotenv</code> loads your <code>.env</code> file into environment variables at runtime.</p></li><li><p><code>pandas</code> gives you a table structure (DataFrame) that is perfect for screening, sorting, and filtering.</p></li><li><p><code>streamlit</code> lets you turn the batch workflow into a simple UI without building a full web app.</p></li></ul><div><hr></div><h4>Step 2: Configure environment variables (<code>.env</code>)</h4><p>Next, create a <code>.env</code> file in the project root. This is where you store your API key and base URL. The goal is to keep credentials out of source code and make configuration consistent across scripts.</p><p>Create <code>.env</code>:</p><pre><code>FMP_API_KEY=your_fmp_api_key_here
FMP_BASE_URL=https://financialmodelingprep.com/stable</code></pre><p>The purpose of this is that:</p><ul><li><p><code>FMP_API_KEY</code> will be injected into each API request as <code>apikey=...</code>.</p></li><li><p><code>FMP_BASE_URL</code> becomes the single source of truth for endpoint construction.</p></li><li><p>By using a <code>.env</code>, you can switch keys or URLs without touching any code.</p></li></ul><div><hr></div><h4>Step 3: Centralize config in <code>app/config.py</code></h4><p>Instead of reading environment variables in every file, we centralise configuration in one place. This keeps the rest of the codebase clean and avoids duplication.</p><p>Create <code>app/config.py</code>:</p><pre><code>import os
from dotenv import load_dotenv

load_dotenv()
FMP_API_KEY = os.getenv(&#8221;FMP_API_KEY&#8221;)
FMP_BASE_URL = os.getenv(
    &#8220;FMP_BASE_URL&#8221;,
    &#8220;https://financialmodelingprep.com/stable&#8221;,
).rstrip(&#8221;/&#8221;)
if not FMP_API_KEY:
    raise RuntimeError(
        &#8220;FMP_API_KEY is not set. Please configure it in your .env file.&#8221;
    )</code></pre><p>Let&#8217;s break down what the code above does</p><ul><li><p><code>load_dotenv()</code> reads your <code>.env</code> file and loads all variables into the environment.</p></li><li><p><code>os.getenv("FMP_API_KEY")</code> retrieves the API key for use elsewhere.</p></li><li><p><code>FMP_BASE_URL</code> has a default fallback, and <code>.rstrip("/")</code> ensures the URL does not end with <code>/</code>.</p></li><li><p>This avoids issues like <code>.../stable//income-statement</code> when we later join paths.</p></li><li><p>The <code>RuntimeError</code> acts as an early &#8220;fail fast&#8221; check so you don&#8217;t waste time debugging missing configuration later.</p></li></ul><p>This file becomes a shared dependency across the rest of the project.</p><div><hr></div><h4>Step 4: Build the batch fundamentals fetcher (<code>app/bulk_client.py</code>)</h4><p>The &#8220;batch&#8221; problem is not about one API call. It is about applying the same extraction logic consistently across many tickers. Here, we isolate all interactions with FMP into one module that:</p><ol><li><p>fetches the latest annual statements for one ticker, then</p></li><li><p>loops across many tickers and builds a DataFrame.</p></li></ol><p>Create <code>app/bulk_client.py</code>:</p><pre><code>from typing import Any, Dict, List, Optional
import time
import requests
import pandas as pd
from app.config import FMP_API_KEY, FMP_BASE_URL

def fetch_latest_statements(symbol: str) -&gt; Dict[str, Any]:
    &#8220;&#8221;&#8220;
    Fetch the latest annual income statement, balance sheet, and cash flow
    for a single symbol using stable endpoints.
    &#8220;&#8221;&#8220;
    symbol = symbol.upper()
    def _get(endpoint: str, extra_params: Optional[Dict[str, Any]] = None) -&gt; List[Dict[str, Any]]:
        params: Dict[str, Any] = {
            &#8220;symbol&#8221;: symbol,
            &#8220;apikey&#8221;: FMP_API_KEY,
            &#8220;period&#8221;: &#8220;annual&#8221;,
            &#8220;limit&#8221;: 1,
        }
        if extra_params:
            params.update(extra_params)
        url = f&#8221;{FMP_BASE_URL}/{endpoint}&#8221;
        resp = requests.get(url, params=params, timeout=30)
        if not resp.ok:
            raise RuntimeError(
                f&#8221;FMP API error ({endpoint}) for {symbol}: &#8220;
                f&#8221;{resp.status_code} {resp.text[:200]}&#8221;
            )
        data = resp.json()
        if isinstance(data, list):
            return data
        if isinstance(data, dict):
            return [data]
        return []
    income_list = _get(&#8221;income-statement&#8221;)
    balance_list = _get(&#8221;balance-sheet-statement&#8221;)
    cashflow_list = _get(&#8221;cash-flow-statement&#8221;)
    income = income_list[0] if income_list else {}
    balance = balance_list[0] if balance_list else {}
    cashflow = cashflow_list[0] if cashflow_list else {}
    return {
        &#8220;symbol&#8221;: symbol,
        &#8220;date&#8221;: income.get(&#8221;date&#8221;) or balance.get(&#8221;date&#8221;) or cashflow.get(&#8221;date&#8221;),
        &#8220;revenue&#8221;: income.get(&#8221;revenue&#8221;),
        &#8220;netIncome&#8221;: income.get(&#8221;netIncome&#8221;),
        &#8220;totalAssets&#8221;: balance.get(&#8221;totalAssets&#8221;),
        &#8220;totalLiabilities&#8221;: balance.get(&#8221;totalLiabilities&#8221;),
        &#8220;totalEquity&#8221;: balance.get(&#8221;totalStockholdersEquity&#8221;) or balance.get(&#8221;totalEquity&#8221;),
        &#8220;operatingCashFlow&#8221;: cashflow.get(&#8221;operatingCashFlow&#8221;),
    }

def fetch_fundamentals_for_symbols(
    symbols: List[str],
    sleep_seconds: float = 0.25,
) -&gt; pd.DataFrame:
    &#8220;&#8221;&#8220;
    Loop over a list of tickers and fetch the latest annual statements for each.
    Returns one DataFrame row per symbol.
    &#8220;&#8221;&#8220;
    cleaned = [s.strip().upper() for s in symbols if s.strip()]
    cleaned = list(dict.fromkeys(cleaned))  # de-duplicate, preserve order
    rows: List[Dict[str, Any]] = []
    for sym in cleaned:
        try:
            rows.append(fetch_latest_statements(sym))
        except Exception as exc:
            print(f&#8221;[WARN] Failed for {sym}: {exc}&#8221;)
        time.sleep(sleep_seconds)
    return pd.DataFrame(rows) if rows else pd.DataFrame()</code></pre><p>This module has two layers: a single symbol and a batch loop.</p><p>1)<strong> </strong><code>fetch_latest_statements(symbol)</code></p><ul><li><p>Uppercases the ticker so <code>aapl</code> becomes <code>AAPL</code>.</p></li><li><p>Defines <code>_get(endpoint, extra_params)</code> as a local helper: <br>- Builds query parameters (<code>symbol</code>, <code>period=annual</code>, <code>limit=1</code>, plus <code>apikey</code>).<br>- Constructs the URL using the stable base: <code>f"{FMP_BASE_URL}/{endpoint}"</code>.<br>- Sends a GET request with <code>requests.get(...)</code>.<br>- If the API fails, it raises a clear error showing endpoint + status code + partial body.<br>- Normalizes responses so you always get a list of dictionaries.</p></li><li><p>Calls <code>_get(...)</code> three times:<br><code>income-statement</code> <br><code>balance-sheet-statement</code> <br><code>cash-flow-statement</code></p></li><li><p>Picks the first result from each list (because <code>limit=1</code>) and flattens only the fields we care about into a single dictionary.</p></li></ul><p>That flattening step is important: instead of returning three raw JSON blobs, we return one consistent &#8220;row&#8221; suitable for a DataFrame.</p><p>2)<strong> </strong><code>fetch_fundamentals_for_symbols(symbols)</code></p><ul><li><p>Cleans the input list:<br>- removes empty values<br>- uppercases everything<br>- de-duplicates (so you don&#8217;t waste calls)</p></li><li><p>Loops over each symbol and calls <code>fetch_latest_statements</code>.</p></li><li><p>If one symbol fails, it prints a warning but continues the batch. This matters in real screening because a single broken ticker should not halt the entire run.</p></li><li><p>Sleeps briefly between calls to reduce the chance of rate-limit issues.</p></li><li><p>Returns a DataFrame with one row per ticker.</p></li></ul><p>At this point, you&#8217;ve already converted &#8220;many API calls&#8221; into one table you can analyze.</p><div><hr></div><h4>Step 5: Compute ratios and build the screening rules (<code>app/screening.py</code>)</h4><p>Raw statements are helpful, but screening is usually based on ratios. Here, we compute a minimal set of metrics from the fetched fields and apply filters to shortlist companies.</p><p>Create <code>app/screening.py</code>:</p><pre><code>from typing import Dict, Any, Tuple, List
import pandas as pd

from app.bulk_client import fetch_fundamentals_for_symbols

DEFAULT_THRESHOLDS: Dict[str, Any] = {
    &#8220;min_roe&#8221;: 0.15,
    &#8220;max_debt_to_equity&#8221;: 0.5,
    &#8220;min_operating_cf&#8221;: 0.0,
}

def load_universe_with_ratios(symbols: List[str]) -&gt; pd.DataFrame:
    &#8220;&#8221;&#8220;
    Fetch fundamentals and compute:
      - ROE = netIncome / totalEquity
      - Debt-to-Equity = totalLiabilities / totalEquity
    &#8220;&#8221;&#8220;
    df = fetch_fundamentals_for_symbols(symbols)
    if df.empty:
        return df
    def safe_div(num, den):
        try:
            if den is None or den == 0:
                return None
            return float(num) / float(den)
        except (TypeError, ZeroDivisionError):
            return None
    df[&#8221;roe&#8221;] = [
        safe_div(ni, eq) for ni, eq in zip(df.get(&#8221;netIncome&#8221;), df.get(&#8221;totalEquity&#8221;))
    ]
    df[&#8221;debt_to_equity&#8221;] = [
        safe_div(liab, eq)
        for liab, eq in zip(df.get(&#8221;totalLiabilities&#8221;), df.get(&#8221;totalEquity&#8221;))
    ]
    return df

def apply_screen(
    df: pd.DataFrame,
    min_roe: float,
    max_debt_to_equity: float,
    min_operating_cf: float,
) -&gt; Tuple[pd.DataFrame, pd.DataFrame]:
    &#8220;&#8221;&#8220;
    Apply thresholds and return (cleaned_data, shortlist).
    &#8220;&#8221;&#8220;
    required = [&#8221;symbol&#8221;, &#8220;roe&#8221;, &#8220;debt_to_equity&#8221;, &#8220;operatingCashFlow&#8221;]
    missing = [c for c in required if c not in df.columns]
    if missing:
        return df, pd.DataFrame()
    df_clean = df.dropna(subset=required)
    mask = (
        (df_clean[&#8221;roe&#8221;] &gt;= min_roe)
        &amp; (df_clean[&#8221;debt_to_equity&#8221;] &lt;= max_debt_to_equity)
        &amp; (df_clean[&#8221;operatingCashFlow&#8221;] &gt;= min_operating_cf)
    )
    shortlist = df_clean.loc[mask].copy()
    shortlist = shortlist.sort_values(&#8221;roe&#8221;, ascending=False)
    return df_clean, shortlist</code></pre><p>Let&#8217;s break down what happens in the code above.</p><ul><li><p><code>load_universe_with_ratios(symbols)</code>:<br>- Calls the batch client to get a fundamentals DataFrame.<br>- Defines <code>safe_div()</code> so ratio calculations do not crash when equity is missing or zero.<br>Computes: <code>roe</code> from <code>netIncome / totalEquity</code> and<code>debt_to_equity</code> from <code>totalLiabilities / totalEquity</code> <br>- Adds those computed values as new DataFrame columns.</p></li><li><p><code>apply_screen(...)</code>:<br>- Verifies the required fields exist.<br>- Drops rows missing key metrics (because screening with <code>None</code> values is meaningless).<br>- Applies your filter rules (min ROE, max leverage, min operating cash flow).<br>- Sorts results by ROE so the strongest profitability appears at the top.</p></li></ul><p>This is the &#8220;brain&#8221; of the screener: you can keep extending it with more metrics later without touching the UI.</p><div><hr></div><h4>Step 6: Build the Streamlit UI (<code>app/streamlit_app.py</code>)</h4><p>Now we expose the batch screener as an interactive app. The user provides the tickers and screening thresholds, then gets a shortlist table and CSV export.</p><p>Create <code>app/streamlit_app.py</code>:</p><pre><code>
import os
import sys

# Ensure project root (parent of &#8220;app&#8221;) is on sys.path
CURRENT_DIR = os.path.dirname(os.path.abspath(__file__))
PROJECT_ROOT = os.path.dirname(CURRENT_DIR)
if PROJECT_ROOT not in sys.path:
    sys.path.insert(0, PROJECT_ROOT)

import streamlit as st
import pandas as pd

from app.screening import (
    load_universe_with_ratios,
    apply_screen,
    DEFAULT_THRESHOLDS,
)

st.set_page_config(
    page_title=&#8221;FMPFundamentals Screener&#8221;,
    layout=&#8221;wide&#8221;,
)


@st.cache_data(show_spinner=True)
def get_universe_cached(symbols: tuple) -&gt; pd.DataFrame:
    # symbols is a tuple here because cache_data needs hashable args
    return load_universe_with_ratios(list(symbols))


def main():
    st.title(&#8221;Batch Fundamentals Screener&#8221;)
    st.write(
        &#8220;This app uses only Financial Modeling Prep endpoints that are typically &#8220;
        &#8220;available on the **free plan** (annual financial statements). &#8220;
        &#8220;You provide a list of symbols, and the app fetches the latest annual &#8220;
        &#8220;income statement, balance sheet, and cash flow to compute basic ratios &#8220;
        &#8220;such as ROE and Debt-to-Equity.&#8221;
    )

    # Sidebar: symbols + criteria
    st.sidebar.header(&#8221;Universe &amp; Screening Criteria&#8221;)

    default_symbols = &#8220;AAPL, MSFT, GOOGL, AMZN, META, NVDA, TSLA, JPM, BAC, NFLX&#8221;

    symbols_input = st.sidebar.text_area(
        &#8220;Symbols (comma or newline separated)&#8221;,
        value=default_symbols,
        help=&#8221;Provide a list of tickers to screen. &#8220;
             &#8220;Example: AAPL, MSFT, GOOGL&#8221;,
        height=120,
    )

    min_roe = st.sidebar.slider(
        &#8220;Minimum ROE (Net income / Equity, latest annual)&#8221;,
        min_value=0.0,
        max_value=0.5,
        value=float(DEFAULT_THRESHOLDS[&#8221;min_roe&#8221;]),
        step=0.01,
    )

    max_debt_to_equity = st.sidebar.slider(
        &#8220;Maximum Debt-to-Equity (Total liabilities / Equity, latest annual)&#8221;,
        min_value=0.0,
        max_value=3.0,
        value=float(DEFAULT_THRESHOLDS[&#8221;max_debt_to_equity&#8221;]),
        step=0.05,
    )

    min_operating_cf = st.sidebar.number_input(
        &#8220;Minimum Operating Cash Flow (latest annual, absolute)&#8221;,
        value=float(DEFAULT_THRESHOLDS[&#8221;min_operating_cf&#8221;]),
        step=1_000_000.0,
        format=&#8221;%.0f&#8221;,
        help=&#8221;Set to &gt;0 to require positive operating cash flow.&#8221;,
    )

    st.sidebar.markdown(&#8221;---&#8221;)
    st.sidebar.write(&#8221;Edit the symbols and criteria, then click **Run Screening**.&#8221;)

    if st.button(&#8221;Run Screening&#8221;):
        # Parse symbols
        raw = symbols_input.replace(&#8221;\n&#8221;, &#8220;,&#8221;)
        symbols = [s.strip().upper() for s in raw.split(&#8221;,&#8221;) if s.strip()]

        if not symbols:
            st.warning(&#8221;Please provide at least one symbol.&#8221;)
            return

        try:
            df_universe = get_universe_cached(tuple(symbols))
        except Exception as e:
            st.error(f&#8221;Error fetching data from FMP: {e}&#8221;)
            return

        if df_universe.empty:
            st.warning(&#8221;No data returned from the financial statement endpoints.&#8221;)
            return

        st.subheader(&#8221;Universe Preview&#8221;)
        st.write(
            f&#8221;Fetched latest annual statements for **{len(df_universe)}** symbols.&#8221;
        )

        st.write(&#8221;Columns available (first 20):&#8221;)
        st.code(&#8221;, &#8220;.join(df_universe.columns.tolist()[:20]), language=&#8221;text&#8221;)

        df_all, df_screened = apply_screen(
            df_universe,
            min_roe=min_roe,
            max_debt_to_equity=max_debt_to_equity,
            min_operating_cf=min_operating_cf,
        )

        if df_screened.empty:
            st.warning(
                &#8220;No companies passed the current screening rules. &#8220;
                &#8220;Try relaxing the filters or inspect the raw data.&#8221;
            )
            with st.expander(&#8221;Show full dataset&#8221;):
                st.dataframe(df_all)
            return

        st.subheader(&#8221;Screening Results&#8221;)
        st.write(
            f&#8221;Companies passing the screen: **{len(df_screened)}**. &#8220;
            &#8220;Sorted by ROE descending.&#8221;
        )

        display_cols = [c for c in [            &#8220;symbol&#8221;,            &#8220;date&#8221;,            &#8220;roe&#8221;,            &#8220;debt_to_equity&#8221;,            &#8220;operatingCashFlow&#8221;,            &#8220;revenue&#8221;,            &#8220;netIncome&#8221;,            &#8220;totalAssets&#8221;,            &#8220;totalLiabilities&#8221;,            &#8220;totalEquity&#8221;,        ] if c in df_screened.columns]

        st.dataframe(df_screened[display_cols].reset_index(drop=True))

        # Simple bar chart of top N by ROE
        top_n = min(20, len(df_screened))
        chart_df = df_screened.head(top_n)
        if &#8220;symbol&#8221; in chart_df.columns and &#8220;roe&#8221; in chart_df.columns:
            st.subheader(f&#8221;Top {top_n} by ROE (latest annual)&#8221;)
            st.bar_chart(
                chart_df.set_index(&#8221;symbol&#8221;)[&#8221;roe&#8221;]
            )

        with st.expander(&#8221;Download results as CSV&#8221;):
            csv = df_screened.to_csv(index=False)
            st.download_button(
                label=&#8221;Download CSV&#8221;,
                data=csv,
                file_name=&#8221;screened_companies.csv&#8221;,
                mime=&#8221;text/csv&#8221;,
            )

    else:
        st.info(&#8221;Provide symbols in the sidebar and click **Run Screening**.&#8221;)


if __name__ == &#8220;__main__&#8221;:
    main()</code></pre><p>What happen in our Streamlit UI is:</p><p>Let&#8217;s break down what happens in the code above.</p><ul><li><p>The <code>sys.path</code> block ensures imports like <code>from app.screening import ...</code> work correctly in Streamlit (because Streamlit executes the file as a script).</p></li><li><p>The sidebar captures two things:<br>- a user-defined ticker list<br>- screening thresholds (ROE, debt-to-equity, operating cash flow)</p></li><li><p><code>@st.cache_data</code> caches results for the same ticker list:<br>- if you adjust only the thresholds, Streamlit reuses the fetched data instead of calling the API again</p></li><li><p>When you click Run Screening, the app:<br>- parses tickers into a clean list<br>- fetches fundamentals and builds a DataFrame<br>- computes ratios<br>- applies screening rules<br>- renders the shortlist table and provides CSV export (plus a simple ROE chart)</p></li></ul><div><hr></div><h4>Step 7: Run the app</h4><p>From the project root:</p><pre><code>streamlit run app/streamlit_app.py</code></pre><p>Once it runs, you can:</p><ul><li><p>paste your own universe of tickers,</p></li><li><p>adjust thresholds,</p></li><li><p>export a shortlist for deeper analysis.</p></li></ul><p>That is how we run the batch screening UI we just created. Let&#8217;s take a look at the system we just created by accessing it via localhost. If everything runs fine, you will see the screen something like below:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!qEns!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0f253170-a7ec-41c6-8f32-de1d8c60823f_1600x849.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!qEns!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0f253170-a7ec-41c6-8f32-de1d8c60823f_1600x849.png 424w, https://substackcdn.com/image/fetch/$s_!qEns!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0f253170-a7ec-41c6-8f32-de1d8c60823f_1600x849.png 848w, https://substackcdn.com/image/fetch/$s_!qEns!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0f253170-a7ec-41c6-8f32-de1d8c60823f_1600x849.png 1272w, https://substackcdn.com/image/fetch/$s_!qEns!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0f253170-a7ec-41c6-8f32-de1d8c60823f_1600x849.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!qEns!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0f253170-a7ec-41c6-8f32-de1d8c60823f_1600x849.png" width="1456" height="773" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/0f253170-a7ec-41c6-8f32-de1d8c60823f_1600x849.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:773,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!qEns!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0f253170-a7ec-41c6-8f32-de1d8c60823f_1600x849.png 424w, https://substackcdn.com/image/fetch/$s_!qEns!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0f253170-a7ec-41c6-8f32-de1d8c60823f_1600x849.png 848w, https://substackcdn.com/image/fetch/$s_!qEns!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0f253170-a7ec-41c6-8f32-de1d8c60823f_1600x849.png 1272w, https://substackcdn.com/image/fetch/$s_!qEns!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F0f253170-a7ec-41c6-8f32-de1d8c60823f_1600x849.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>On the left side, you can enter all the information for the screening criteria, while on the right side is where all the information appears after we run the screening.</p><p>The result is the preview of the data universe we acquired and the screening results.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!xB7d!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffd263f19-0d77-47a5-ab4c-cbd069bcaadb_1600x947.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!xB7d!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffd263f19-0d77-47a5-ab4c-cbd069bcaadb_1600x947.png 424w, https://substackcdn.com/image/fetch/$s_!xB7d!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffd263f19-0d77-47a5-ab4c-cbd069bcaadb_1600x947.png 848w, https://substackcdn.com/image/fetch/$s_!xB7d!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffd263f19-0d77-47a5-ab4c-cbd069bcaadb_1600x947.png 1272w, https://substackcdn.com/image/fetch/$s_!xB7d!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffd263f19-0d77-47a5-ab4c-cbd069bcaadb_1600x947.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!xB7d!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffd263f19-0d77-47a5-ab4c-cbd069bcaadb_1600x947.png" width="1456" height="862" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/fd263f19-0d77-47a5-ab4c-cbd069bcaadb_1600x947.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:862,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!xB7d!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffd263f19-0d77-47a5-ab4c-cbd069bcaadb_1600x947.png 424w, https://substackcdn.com/image/fetch/$s_!xB7d!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffd263f19-0d77-47a5-ab4c-cbd069bcaadb_1600x947.png 848w, https://substackcdn.com/image/fetch/$s_!xB7d!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffd263f19-0d77-47a5-ab4c-cbd069bcaadb_1600x947.png 1272w, https://substackcdn.com/image/fetch/$s_!xB7d!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Ffd263f19-0d77-47a5-ab4c-cbd069bcaadb_1600x947.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>As additional features, we have a chart showing the company&#8217;s ROE that passes the screening and a button to download the results as CSV files.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!N59-!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F433cc58c-9e6f-4f9e-9477-d02025d7266a_1600x924.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!N59-!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F433cc58c-9e6f-4f9e-9477-d02025d7266a_1600x924.png 424w, https://substackcdn.com/image/fetch/$s_!N59-!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F433cc58c-9e6f-4f9e-9477-d02025d7266a_1600x924.png 848w, https://substackcdn.com/image/fetch/$s_!N59-!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F433cc58c-9e6f-4f9e-9477-d02025d7266a_1600x924.png 1272w, https://substackcdn.com/image/fetch/$s_!N59-!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F433cc58c-9e6f-4f9e-9477-d02025d7266a_1600x924.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!N59-!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F433cc58c-9e6f-4f9e-9477-d02025d7266a_1600x924.png" width="1456" height="841" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/433cc58c-9e6f-4f9e-9477-d02025d7266a_1600x924.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:841,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!N59-!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F433cc58c-9e6f-4f9e-9477-d02025d7266a_1600x924.png 424w, https://substackcdn.com/image/fetch/$s_!N59-!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F433cc58c-9e6f-4f9e-9477-d02025d7266a_1600x924.png 848w, https://substackcdn.com/image/fetch/$s_!N59-!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F433cc58c-9e6f-4f9e-9477-d02025d7266a_1600x924.png 1272w, https://substackcdn.com/image/fetch/$s_!N59-!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F433cc58c-9e6f-4f9e-9477-d02025d7266a_1600x924.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>That&#8217;s all you need to know to build our batch screening in FMP. You can always extend the metrics and add additional information you need.</p><div><hr></div><h3>Conclusion</h3><p>In this article, we built a lightweight batch fundamentals screener on top of Financial Modeling Prep&#8217;s stable API to analyze many companies within a single, consistent workflow.</p><p>By combining a small data-fetching layer, simple ratio calculations (such as ROE and debt-to-equity), and a Streamlit interface, we can quickly turn a list of tickers into a shortlist that is easy to review and export.</p><p>You can use this project as a starting point for larger screening pipelines and extend it over time with multi-year stability checks, additional metrics, caching, or deeper drill-down views for shortlisted companies.</p>]]></content:encoded></item><item><title><![CDATA[Building an Open-Source Microservice for Financial Data Retrieval with Financial Modelling Prep]]></title><description><![CDATA[Company-Fundamental Tracking Microservice that is Suitable For Your Requirements.]]></description><link>https://www.nb-data.com/p/building-an-open-source-microservice</link><guid isPermaLink="false">https://www.nb-data.com/p/building-an-open-source-microservice</guid><dc:creator><![CDATA[Cornellius Yudha Wijaya]]></dc:creator><pubDate>Sat, 06 Dec 2025 05:33:15 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!JmK7!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F09801c0b-a10c-4b60-bbf4-c0ecb167f940_1400x788.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!JmK7!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F09801c0b-a10c-4b60-bbf4-c0ecb167f940_1400x788.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!JmK7!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F09801c0b-a10c-4b60-bbf4-c0ecb167f940_1400x788.jpeg 424w, https://substackcdn.com/image/fetch/$s_!JmK7!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F09801c0b-a10c-4b60-bbf4-c0ecb167f940_1400x788.jpeg 848w, https://substackcdn.com/image/fetch/$s_!JmK7!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F09801c0b-a10c-4b60-bbf4-c0ecb167f940_1400x788.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!JmK7!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F09801c0b-a10c-4b60-bbf4-c0ecb167f940_1400x788.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!JmK7!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F09801c0b-a10c-4b60-bbf4-c0ecb167f940_1400x788.jpeg" width="1400" height="788" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/09801c0b-a10c-4b60-bbf4-c0ecb167f940_1400x788.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:788,&quot;width&quot;:1400,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!JmK7!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F09801c0b-a10c-4b60-bbf4-c0ecb167f940_1400x788.jpeg 424w, https://substackcdn.com/image/fetch/$s_!JmK7!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F09801c0b-a10c-4b60-bbf4-c0ecb167f940_1400x788.jpeg 848w, https://substackcdn.com/image/fetch/$s_!JmK7!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F09801c0b-a10c-4b60-bbf4-c0ecb167f940_1400x788.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!JmK7!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F09801c0b-a10c-4b60-bbf4-c0ecb167f940_1400x788.jpeg 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Photo by <a href="https://unsplash.com/@growtika?utm_source=medium&amp;utm_medium=referral">Growtika</a> on <a href="https://unsplash.com/?utm_source=medium&amp;utm_medium=referral">Unsplash</a></figcaption></figure></div><p>Financial data is one of the datasets that most companies and individuals need. It is sought for because it is helpful in many projects, such as building investment dashboards and portfolio trackers, running valuation and scenario analysis for listed companies, or training machine learning models for financial use cases. In all of these cases, the hard part is rarely &#8220;getting the data once.&#8221; The hard part is accessing the data cleanly and consistently every time you start a new project.</p><p><a href="https://site.financialmodelingprep.com/developer/docs">Financial Modeling Prep&#8217;</a>s stable API provides a rich set of endpoints for financial fundamentals: income statements, balance sheets, cash flow statements, profiles, and more. It solves the problem of data source availability.</p><p>But there is still a hassle for developers: the APIs are relatively low-level. You have to remember the exact endpoint names, pass the proper query parameters, manage API keys in every script, and repeatedly transform the raw JSON into the handful of fields you actually need for your analysis.</p><p>This is where a small microservice comes in handy. Instead of remembering every FMP&#8217;s URLs and parameters, centralize that logic in one place and provide a few task-specific endpoints like &#8220;search companies,&#8221; &#8220;get snapshot,&#8221; and &#8220;get history.&#8221; This approach allows us to easily manage the data flow and even customize the overall data structure output.</p><p>In this article, we will build a minimal financial microservice on top of Financial Modeling Prep&#8217;s stable API. It will not replace a complete analytics platform; instead, it will provide a focused set of endpoints for any follow-up analytical process.</p><p>Let&#8217;s get into it.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.nb-data.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://www.nb-data.com/subscribe?"><span>Subscribe now</span></a></p><div><hr></div><h2><strong>Foundation</strong></h2><blockquote><p><em>You can access the entire code used in this tutorial in this <a href="https://github.com/CornelliusYW/fmp_microservice_financial">repository</a>.</em></p></blockquote><p>Before we move on to the technical part, we need to understand that building a microservice on top of existing APIs offers several practical benefits.</p><ul><li><p>First, you reduce duplication. Transforming and cleaning the responses from FMP is implemented once, tested once, and shared across everything you build.</p></li><li><p>Second, you gain a single point for the overall information. Configuration of API keys, error handling, rate limiting, and caching can all live in the microservice rather than being reimplemented ad hoc.</p></li><li><p>Third, you create a more approachable entry point for others on your team. For example, they can request <code>/companies/AAPL/snapshot</code> without needing to read the FMP documentation first.</p></li></ul><p>These are a few benefits you have, primarily when you work as a developer and data scientist, that need consistency across all companies.</p><h3><strong>The Data Source</strong></h3><p>Let&#8217;s start building our financial microservice. We will begin by deciding which data from FMP we will use. For this project, all the data comes from Financial Modeling Prep&#8217;s stable API, where we will work with a single base URL and a consistent naming pattern using the following:</p><pre><code>https://financialmodelingprep.com/stable</code></pre><p>Every function is expressed as a specific endpoint on this base, with parameters passed as query string parameters.</p><p>In this microservice, we only use a small subset of what FMP offers, focusing on the core fundamentals most people need. To keep things simple, the service relies on five primary endpoints:</p><ul><li><p><strong>Company search </strong>(<code>search-symbol</code>): Let&#8217;s you search by a company name or a partial ticker and returns candidates with symbols, names, exchanges, and currencies.</p></li><li><p><strong>Company profile </strong>(<code>profile</code>): Returns basic information such as company name, exchange, currency, and other metadata.</p></li><li><p><strong>Income statement </strong>(<code>income-statement</code>): Provides revenue, net income, and other income-statement fields over time.</p></li><li><p><strong>Balance sheet statement </strong>(<code>balance-sheet-statement</code>): Provides total assets, total liabilities, and other balance sheet fields.</p></li><li><p><strong>Cash flow statement </strong>(<code>cash-flow-statement</code>): Provides operating cash flow and other cash flow items.</p></li></ul><p>Each of these endpoints will support parameters like:</p><ul><li><p><code>symbol</code> which is the ticker (e.g. <code>AAPL</code>),</p></li><li><p><code>period</code> like <code>annual</code> or <code>quarterly</code>,</p></li><li><p><code>limit</code> which is the number of records you want (e.g., the last 5 years).</p></li></ul><p>These data are enough to reconstruct a basic picture of a company&#8217;s fundamentals.</p><h3><strong>What the Financial Microservice does</strong></h3><p>In this project, we will develop a consistent REST API for the microservice:</p><ul><li><p><code>GET /health</code>: basic health check.</p></li><li><p><code>GET /companies/search?q=...</code>: search companies by name/symbol.</p></li><li><p><code>GET /companies/{symbol}/snapshot</code>: latest fundamentals snapshot (revenue, net income, assets, liabilities, operating cash flow, plus basic profile).</p></li><li><p><code>GET /companies/{symbol}/history?years=N</code>: simple time series of revenue and net income for the last N annual periods.</p></li></ul><p>These endpoints will abstract the FMP URL details, the API key management, and the raw JSON shape. The endpoint itself is the minimum version, so it does not cover any complex authentication, database management, or advanced applications.</p><h3><strong>Project architecture</strong></h3><p>For the project architecture, we will follow the structure below:</p><pre><code>fmp_microservice_financial/
&#9500;&#9472; app/
&#9474;  &#9500;&#9472; __init__.py
&#9474;  &#9500;&#9472; main.py          # FastAPI app + routes
&#9474;  &#9500;&#9472; fmp_client.py    # Wrapper around FMP stable API
&#9474;  &#9492;&#9472; schemas.py       # Pydantic models for responses
&#9500;&#9472; requirements.txt
&#9500;&#9472; .env.example
&#9492;&#9472; Dockerfile</code></pre><p>At the high level, the microservice will have the flow like below:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!ITRy!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faca3a951-be7c-44b9-af4d-fec9002f7b21_1933x752.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!ITRy!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faca3a951-be7c-44b9-af4d-fec9002f7b21_1933x752.png 424w, https://substackcdn.com/image/fetch/$s_!ITRy!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faca3a951-be7c-44b9-af4d-fec9002f7b21_1933x752.png 848w, https://substackcdn.com/image/fetch/$s_!ITRy!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faca3a951-be7c-44b9-af4d-fec9002f7b21_1933x752.png 1272w, https://substackcdn.com/image/fetch/$s_!ITRy!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faca3a951-be7c-44b9-af4d-fec9002f7b21_1933x752.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!ITRy!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faca3a951-be7c-44b9-af4d-fec9002f7b21_1933x752.png" width="1456" height="566" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/aca3a951-be7c-44b9-af4d-fec9002f7b21_1933x752.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:566,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!ITRy!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faca3a951-be7c-44b9-af4d-fec9002f7b21_1933x752.png 424w, https://substackcdn.com/image/fetch/$s_!ITRy!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faca3a951-be7c-44b9-af4d-fec9002f7b21_1933x752.png 848w, https://substackcdn.com/image/fetch/$s_!ITRy!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faca3a951-be7c-44b9-af4d-fec9002f7b21_1933x752.png 1272w, https://substackcdn.com/image/fetch/$s_!ITRy!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Faca3a951-be7c-44b9-af4d-fec9002f7b21_1933x752.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Financial Microservice Financial high-level</figcaption></figure></div><div><hr></div><h2><strong>Building Financial Microservice</strong></h2><p>Let&#8217;s start by filling up the <code>requirements.txt</code> A file that will contain all the necessary Python libraries we will use to build the financial microservice.</p><pre><code>fastapi
uvicorn
requests
python-dotenv
pydantic</code></pre><p>Based on the requirements, we will use FastAPI to build our endpoint and Pydantic to define the JSON output schema.</p><p>Next, we will set up the <code>.env</code> file to accommodate all the environmental variables used in this project. One requirement is the FMP Free API key, which you can obtain in the <a href="https://site.financialmodelingprep.com/developer/docs/dashboard">FMP dashboard</a>. Once you have the API key, we fill the file using the following information:</p><pre><code>FMP_API_KEY=FMP_API_KEY
FMP_BASE_URL=https://financialmodelingprep.com/stable</code></pre><p>With the configuration done, we will set up the microservice application.</p><h3><strong>Building the FMP Client</strong></h3><p>We will start with the client to wrap the FMP API. To keep the rest of the microservice clean, we isolate all interactions with Financial Modeling Prep in a single class called <code>FMPClient</code>. This class knows how to read configuration, build URLs, attach the API key, and handle errors. Everything else in the codebase just calls methods like <code>get_income_statement(&#8221;AAPL&#8221;)</code> without worrying about the complex details.</p><p>Access the <code>fmp_client.py</code> file and fill them with the following code:</p><pre><code>import os
from typing import Any, Dict, List, Optional
import requests
from dotenv import load_dotenv

load_dotenv()

FMP_API_KEY = os.getenv(&#8221;FMP_API_KEY&#8221;)
FMP_BASE_URL = os.getenv(&#8221;FMP_BASE_URL&#8221;, &#8220;https://financialmodelingprep.com/stable&#8221;)

if not FMP_API_KEY:
    raise RuntimeError(
        &#8220;FMP_API_KEY is not set. Please configure it in your environment or .env file.&#8221;
    )

class FMPClient:
    &#8220;&#8221;&#8220;
    Thin wrapper over Financial Modeling Prep stable endpoints.

    Base: https://financialmodelingprep.com/stable
    Examples:
      - /search-symbol?query=AAPL&amp;apikey=...
      - /income-statement?symbol=AAPL&amp;period=annual&amp;limit=5&amp;apikey=...
    &#8220;&#8221;&#8220;

    def __init__(self, api_key: str = FMP_API_KEY, base_url: str = FMP_BASE_URL) -&gt; None:
        self.api_key = api_key
        self.base_url = base_url.rstrip(&#8221;/&#8221;)

    def _get(self, endpoint: str, params: Optional[Dict[str, Any]] = None) -&gt; Any:
        &#8220;&#8221;&#8220;
        endpoint: e.g. &#8216;search-symbol&#8217;, &#8216;income-statement&#8217;, &#8216;profile&#8217;
        &#8220;&#8221;&#8220;
        if params is None:
            params = {}
        params[&#8221;apikey&#8221;] = self.api_key

        url = f&#8221;{self.base_url}/{endpoint.lstrip(&#8217;/&#8217;)}&#8221;
        resp = requests.get(url, params=params, timeout=10)

        if not resp.ok:
            raise RuntimeError(
                f&#8221;FMP API error: {resp.status_code} {resp.text[:200]}&#8221;
            )
        return resp.json()

    def search_symbol(self, query: str, limit: int = 10) -&gt; List[Dict[str, Any]]:
        &#8220;&#8221;&#8220;
        https://financialmodelingprep.com/stable/search-symbol?query=...&amp;limit=...&amp;exchange=...
        &#8220;&#8221;&#8220;
        return self._get(
            &#8220;search-symbol&#8221;,
            {
                &#8220;query&#8221;: query,
                &#8220;limit&#8221;: limit,
                # you can adjust or drop the exchange filter
                &#8220;exchange&#8221;: &#8220;NASDAQ,NYSE,AMEX&#8221;,
            },
        )

    def get_company_profile(self, symbol: str) -&gt; List[Dict[str, Any]]:
        &#8220;&#8221;&#8220;
        https://financialmodelingprep.com/stable/profile?symbol=AAPL
        &#8220;&#8221;&#8220;
        return self._get(
            &#8220;profile&#8221;,
            {&#8221;symbol&#8221;: symbol.upper()},
        )

    def get_income_statement(
        self,
        symbol: str,
        period: str = &#8220;annual&#8221;,
        limit: int = 5,
    ) -&gt; List[Dict[str, Any]]:
        &#8220;&#8221;&#8220;
        https://financialmodelingprep.com/stable/income-statement?symbol=AAPL&amp;period=annual&amp;limit=5
        &#8220;&#8221;&#8220;
        return self._get(
            &#8220;income-statement&#8221;,
            {
                &#8220;symbol&#8221;: symbol.upper(),
                &#8220;period&#8221;: period,
                &#8220;limit&#8221;: limit,
            },
        )

    def get_balance_sheet(
        self,
        symbol: str,
        period: str = &#8220;annual&#8221;,
        limit: int = 5,
    ) -&gt; List[Dict[str, Any]]:
        &#8220;&#8221;&#8220;
        https://financialmodelingprep.com/stable/balance-sheet-statement?symbol=AAPL&amp;period=annual&amp;limit=5
        &#8220;&#8221;&#8220;
        return self._get(
            &#8220;balance-sheet-statement&#8221;,
            {
                &#8220;symbol&#8221;: symbol.upper(),
                &#8220;period&#8221;: period,
                &#8220;limit&#8221;: limit,
            },
        )

    def get_cash_flow(
        self,
        symbol: str,
        period: str = &#8220;annual&#8221;,
        limit: int = 5,
    ) -&gt; List[Dict[str, Any]]:
        &#8220;&#8221;&#8220;
        https://financialmodelingprep.com/stable/cash-flow-statement?symbol=AAPL&amp;period=annual&amp;limit=5
        &#8220;&#8221;&#8220;
        return self._get(
            &#8220;cash-flow-statement&#8221;,
            {
                &#8220;symbol&#8221;: symbol.upper(),
                &#8220;period&#8221;: period,
                &#8220;limit&#8221;: limit,
            },
        )</code></pre><p>Let&#8217;s break down what happens in the code above. The first few lines are just to set up imports and load the configuration, where we specify the base URL to use for all API calls and the API key to attach.</p><p>Next, we define the <code>FMPClient</code> class as a thin wrapper that encapsulates how to call FMP. The <code>api_key</code> and <code>base_url</code> are initialized from the module-level variables, but can be overridden when instantiating the class. Also, <code>base_url.rstrip(&#8221;/&#8221;)</code> ensures there is no trailing slash on the base URL. This makes it easier to concatenate safely <code>base_url</code> and endpoint names without accidentally creating double slashes.</p><p>Then, we define the shared helper utility <code>_get</code> function, which will be used by the other functions within the <code>FMPClient</code> class.</p><pre><code>def _get(self, endpoint: str, params: Optional[Dict[str, Any]] = None) -&gt; Any:</code></pre><p>The function will accept the endpoint name we set, such as <code>&#8220;search-symbol&#8221;</code> or <code>&#8220;income-statement&#8221;</code>. It will also take an optional <code>params</code> dictionary and ensure one crucial parameter is always present, which is the<code>apikey</code>. The main activity of the function will construct the valid URL and send a GET request using<code>requests.get</code>that returns<code>resp.json()</code>the parsed JSON body from FMP.</p><p>The rest of the class defines small, descriptive methods for specific FMP endpoints. For example the <code>&#8220;search-symbol&#8221;</code>:</p><pre><code>def search_symbol(self, query: str, limit: int = 10) -&gt; List[Dict[str, Any]]:</code></pre><p>For the function, we could pass parameters such as the free-text <code>query</code> and an optional <code>limit</code>. The function will call <code>_get</code> with the endpoint name <code>&#8220;search-symbol&#8221;</code> and a parameters dictionary.</p><p>From the rest of your code, you can write:</p><pre><code>client.search_symbol(&#8221;AAPL&#8221;)</code></pre><p>And get back a list of candidate companies without worrying about URLs or query string details.</p><p>This client will centralize our configuration and error handling and provide the high-level vocabulary for our microservice.</p><h3><strong>Building the Microservice Schema</strong></h3><p>To keep the output consistent, the microservice does not expose raw JSON from FMP directly. Instead, we define a small set of Pydantic models that precisely describe the fields clients can expect from each endpoint, independent of how FMP structures its responses. This is where we will define them at the<code>schemas.py</code> with the following code:</p><pre><code>from typing import List, Optional
from pydantic import BaseModel, Field

class CompanySearchItem(BaseModel):
    symbol: str
    name: str
    exchange: Optional[str] = None
    currency: Optional[str] = None

class CompanySearchResponse(BaseModel):
    results: List[CompanySearchItem]

class IncomeSnapshot(BaseModel):
    revenue: Optional[float] = Field(
        None, description=&#8221;Total revenue for the period&#8221;
    )
    netIncome: Optional[float] = Field(
        None, description=&#8221;Net income for the period&#8221;
    )

class BalanceSheetSnapshot(BaseModel):
    totalAssets: Optional[float] = None
    totalLiabilities: Optional[float] = None

class CashFlowSnapshot(BaseModel):
    operatingCashFlow: Optional[float] = None

class CompanySnapshot(BaseModel):
    symbol: str
    name: Optional[str] = None
    currency: Optional[str] = None
    exchange: Optional[str] = None
    asOf: Optional[str] = Field(
        None, description=&#8221;Financial statement date&#8221;
    )

    income: IncomeSnapshot
    balanceSheet: BalanceSheetSnapshot
    cashFlow: CashFlowSnapshot

class HistoryPoint(BaseModel):
    date: str
    revenue: Optional[float] = None
    netIncome: Optional[float] = None

class CompanyHistoryResponse(BaseModel):
    symbol: str
    points: List[HistoryPoint]</code></pre><p>These Pydantic schema models help define our microservice public interface, even when FMP&#8217;s response changes, create API self-documentation (with Swagger UI), and keep our microservices focused as we decide the output structure.</p><p>You can also change the schema above as needed. What is important is that you understand the FMP outputs and understand the result you want. These schema models will be used together with the client we set up previously in the application, which we set up at the <code>main.py</code>.</p><h3><strong>Building the Microservice Application</strong></h3><p>The <code>main.py</code> file is where the microservice becomes a real API that we can call elsewhere. We can define them as follows:</p><pre><code>from typing import List
from fastapi import Depends, FastAPI, HTTPException, Query
from fastapi.responses import JSONResponse
from app.fmp_client import FMPClient
from app.schemas import (
    CompanySearchItem,
    CompanySearchResponse,
    CompanySnapshot,
    IncomeSnapshot,
    BalanceSheetSnapshot,
    CashFlowSnapshot,
    HistoryPoint,
    CompanyHistoryResponse,
)

app = FastAPI(
    title=&#8221;Company Fundamentals Microservice&#8221;,
    version=&#8221;0.1.0&#8221;,
    description=(
        &#8220;Minimal open-source service that wraps Financial Modeling Prep &#8220;
        &#8220;stable fundamentals endpoints.&#8221;
    ),
)

def get_client() -&gt; FMPClient:
    return FMPClient()

@app.get(&#8221;/health&#8221;)
def health_check() -&gt; dict:
    return {&#8221;status&#8221;: &#8220;ok&#8221;}

@app.get(
    &#8220;/companies/search&#8221;,
    response_model=CompanySearchResponse,
    summary=&#8221;Search for companies by name or symbol&#8221;,
)
def search_companies(
    q: str = Query(..., min_length=1, description=&#8221;Search query&#8221;),
    limit: int = Query(10, ge=1, le=50),
    client: FMPClient = Depends(get_client),
):
    raw = client.search_symbol(q, limit=limit)
    results: List[CompanySearchItem] = []

    for item in raw:
        results.append(
            CompanySearchItem(
                symbol=item.get(&#8221;symbol&#8221;),
                name=item.get(&#8221;name&#8221;) or item.get(&#8221;companyName&#8221;),
                exchange=item.get(&#8221;stockExchange&#8221;),
                currency=item.get(&#8221;currency&#8221;),
            )
        )

    return CompanySearchResponse(results=results)

@app.get(
    &#8220;/companies/{symbol}/snapshot&#8221;,
    response_model=CompanySnapshot,
    summary=&#8221;Latest fundamentals snapshot for a given company&#8221;,
)
def company_snapshot(
    symbol: str,
    client: FMPClient = Depends(get_client),
):
    profiles = client.get_company_profile(symbol)
    if not profiles:
        raise HTTPException(status_code=404, detail=&#8221;Company profile not found&#8221;)

    profile = profiles[0]
    name = profile.get(&#8221;companyName&#8221;) or profile.get(&#8221;name&#8221;)
    currency = profile.get(&#8221;currency&#8221;)
    exchange = profile.get(&#8221;exchangeShortName&#8221;) or profile.get(&#8221;exchange&#8221;)

    income_list = client.get_income_statement(symbol, period=&#8221;annual&#8221;, limit=1)
    balance_list = client.get_balance_sheet(symbol, period=&#8221;annual&#8221;, limit=1)
    cashflow_list = client.get_cash_flow(symbol, period=&#8221;annual&#8221;, limit=1)

    income_raw = income_list[0] if income_list else {}
    balance_raw = balance_list[0] if balance_list else {}
    cashflow_raw = cashflow_list[0] if cashflow_list else {}

    as_of = (
        income_raw.get(&#8221;date&#8221;)
        or balance_raw.get(&#8221;date&#8221;)
        or cashflow_raw.get(&#8221;date&#8221;)
    )

    income = IncomeSnapshot(
        revenue=income_raw.get(&#8221;revenue&#8221;) or income_raw.get(&#8221;revenueTTM&#8221;),
        netIncome=income_raw.get(&#8221;netIncome&#8221;) or income_raw.get(&#8221;netIncomeTTM&#8221;),
    )

    balance = BalanceSheetSnapshot(
        totalAssets=balance_raw.get(&#8221;totalAssets&#8221;),
        totalLiabilities=balance_raw.get(&#8221;totalLiabilities&#8221;),
    )

    cashflow = CashFlowSnapshot(
        operatingCashFlow=cashflow_raw.get(&#8221;operatingCashFlow&#8221;)
        or cashflow_raw.get(&#8221;operatingCashFlowTTM&#8221;)
    )

    snapshot = CompanySnapshot(
        symbol=str(symbol).upper(),
        name=name,
        currency=currency,
        exchange=exchange,
        asOf=as_of,
        income=income,
        balanceSheet=balance,
        cashFlow=cashflow,
    )

    return snapshot

@app.get(
    &#8220;/companies/{symbol}/history&#8221;,
    response_model=CompanyHistoryResponse,
    summary=&#8221;Simple revenue/net income history for charting&#8221;,
)
def company_history(
    symbol: str,
    years: int = Query(5, ge=1, le=20),
    client: FMPClient = Depends(get_client),
):
    income_list = client.get_income_statement(
        symbol, period=&#8221;annual&#8221;, limit=years
    )

    if not income_list:
        raise HTTPException(status_code=404, detail=&#8221;No income statement data found&#8221;)

    points: List[HistoryPoint] = []
    for row in income_list:
        points.append(
            HistoryPoint(
                date=row.get(&#8221;date&#8221;),
                revenue=row.get(&#8221;revenue&#8221;),
                netIncome=row.get(&#8221;netIncome&#8221;),
            )
        )

    return CompanyHistoryResponse(symbol=str(symbol).upper(), points=points)

@app.exception_handler(RuntimeError)
def runtime_error_handler(request, exc: RuntimeError):
    return JSONResponse(
        status_code=502,
        content={&#8221;detail&#8221;: str(exc)},
    )</code></pre><p>Let&#8217;s break down what happens in the code above.</p><p>First, we initiate the FastAPI application with metadata, including <code>title</code>, <code>version</code>, and <code>description</code> which will be used in the auto-generated Swagger UI at <code>/docs</code>.</p><p>Next, we inject the FMP client into the <code>get_client</code> function that tells FastAPI how to create an <code>FMPClient</code> when an endpoint needs one.</p><pre><code>def get_client() -&gt; FMPClient:
    return FMPClient()</code></pre><p>Later, in each route, you will see:</p><pre><code>client: FMPClient = Depends(get_client)</code></pre><p>This makes it easier to construct the client, and it becomes easier to swap in a mock client for testing.</p><p>With the application created, we will set up the endpoint route. Each endpoint will have different information we could acquire. For example, the <code>/companies/{symbol}/snapshot</code> route will return the company&#8217;s fundamental information:</p><pre><code>@app.get(
    &#8220;/companies/{symbol}/snapshot&#8221;,
    response_model=CompanySnapshot,
    summary=&#8221;Latest fundamentals snapshot for a given company&#8221;,
)
def company_snapshot(
    symbol: str,
    client: FMPClient = Depends(get_client),
):</code></pre><p>The endpoint will basically perform five steps, including:</p><ol><li><p><strong>Fetch basic profile</strong></p></li><li><p><strong>Fetch the latest financial statements</strong></p></li><li><p><strong>Determine the &#8220;as of&#8221; date</strong></p></li><li><p><strong>Build the snapshot components</strong></p></li><li><p><strong>Assemble the </strong><code>CompanySnapshot</code></p></li></ol><p>The endpoint returns this <code>CompanySnapshot</code>. FastAPI serializes it to JSON and automatically documents it.</p><h3><strong>Running the Microservice</strong></h3><p>With the application in place, let&#8217;s test the microservice. We can do that by running the following command in the CLI:</p><pre><code>uvicorn app.main:app --reload</code></pre><p>If it&#8217;s run correctly, you should see the information like below in your CLI:</p><pre><code>INFO:     Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
INFO:     Started reloader process [9084] using WatchFiles
INFO:     Started server process [27492]
INFO:     Waiting for application startup.
INFO:     Application startup complete.</code></pre><p>Let&#8217;s check the microservice we just created. As we have been setting up the documentation along the way, we could access them using the following URI in your browser:</p><pre><code>http://localhost:8000/docs</code></pre><p>Access the URI above, and you will see our microservice documentation below:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!8lQY!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F16fdb594-d7c3-41e8-bd81-19af90df3e51_1400x945.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!8lQY!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F16fdb594-d7c3-41e8-bd81-19af90df3e51_1400x945.png 424w, https://substackcdn.com/image/fetch/$s_!8lQY!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F16fdb594-d7c3-41e8-bd81-19af90df3e51_1400x945.png 848w, https://substackcdn.com/image/fetch/$s_!8lQY!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F16fdb594-d7c3-41e8-bd81-19af90df3e51_1400x945.png 1272w, https://substackcdn.com/image/fetch/$s_!8lQY!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F16fdb594-d7c3-41e8-bd81-19af90df3e51_1400x945.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!8lQY!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F16fdb594-d7c3-41e8-bd81-19af90df3e51_1400x945.png" width="1400" height="945" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/16fdb594-d7c3-41e8-bd81-19af90df3e51_1400x945.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:945,&quot;width&quot;:1400,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!8lQY!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F16fdb594-d7c3-41e8-bd81-19af90df3e51_1400x945.png 424w, https://substackcdn.com/image/fetch/$s_!8lQY!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F16fdb594-d7c3-41e8-bd81-19af90df3e51_1400x945.png 848w, https://substackcdn.com/image/fetch/$s_!8lQY!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F16fdb594-d7c3-41e8-bd81-19af90df3e51_1400x945.png 1272w, https://substackcdn.com/image/fetch/$s_!8lQY!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F16fdb594-d7c3-41e8-bd81-19af90df3e51_1400x945.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Try to check out one of the endpoints, for example, the <code>/health</code> endpoint:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!LN36!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8876c7d4-95ee-4741-9891-02ba7f7822c7_1400x725.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!LN36!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8876c7d4-95ee-4741-9891-02ba7f7822c7_1400x725.png 424w, https://substackcdn.com/image/fetch/$s_!LN36!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8876c7d4-95ee-4741-9891-02ba7f7822c7_1400x725.png 848w, https://substackcdn.com/image/fetch/$s_!LN36!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8876c7d4-95ee-4741-9891-02ba7f7822c7_1400x725.png 1272w, https://substackcdn.com/image/fetch/$s_!LN36!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8876c7d4-95ee-4741-9891-02ba7f7822c7_1400x725.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!LN36!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8876c7d4-95ee-4741-9891-02ba7f7822c7_1400x725.png" width="1400" height="725" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/8876c7d4-95ee-4741-9891-02ba7f7822c7_1400x725.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:725,&quot;width&quot;:1400,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!LN36!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8876c7d4-95ee-4741-9891-02ba7f7822c7_1400x725.png 424w, https://substackcdn.com/image/fetch/$s_!LN36!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8876c7d4-95ee-4741-9891-02ba7f7822c7_1400x725.png 848w, https://substackcdn.com/image/fetch/$s_!LN36!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8876c7d4-95ee-4741-9891-02ba7f7822c7_1400x725.png 1272w, https://substackcdn.com/image/fetch/$s_!LN36!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F8876c7d4-95ee-4741-9891-02ba7f7822c7_1400x725.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>We can see that the endpoint executes correctly and returns the expected response.</p><p>Let&#8217;s try out the other endpoint, such as <code>/companies/{symbol}/snapshot</code> to acquire the company&#8217;s financial fundamentals:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!c3nr!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb6ab4a3c-9f18-4afb-ae76-338a0a44a0b1_1400x934.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!c3nr!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb6ab4a3c-9f18-4afb-ae76-338a0a44a0b1_1400x934.png 424w, https://substackcdn.com/image/fetch/$s_!c3nr!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb6ab4a3c-9f18-4afb-ae76-338a0a44a0b1_1400x934.png 848w, https://substackcdn.com/image/fetch/$s_!c3nr!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb6ab4a3c-9f18-4afb-ae76-338a0a44a0b1_1400x934.png 1272w, https://substackcdn.com/image/fetch/$s_!c3nr!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb6ab4a3c-9f18-4afb-ae76-338a0a44a0b1_1400x934.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!c3nr!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb6ab4a3c-9f18-4afb-ae76-338a0a44a0b1_1400x934.png" width="1400" height="934" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/b6ab4a3c-9f18-4afb-ae76-338a0a44a0b1_1400x934.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:934,&quot;width&quot;:1400,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:null,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:null,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:null,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!c3nr!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb6ab4a3c-9f18-4afb-ae76-338a0a44a0b1_1400x934.png 424w, https://substackcdn.com/image/fetch/$s_!c3nr!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb6ab4a3c-9f18-4afb-ae76-338a0a44a0b1_1400x934.png 848w, https://substackcdn.com/image/fetch/$s_!c3nr!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb6ab4a3c-9f18-4afb-ae76-338a0a44a0b1_1400x934.png 1272w, https://substackcdn.com/image/fetch/$s_!c3nr!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb6ab4a3c-9f18-4afb-ae76-338a0a44a0b1_1400x934.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>From the image above, we can see that the microservice successfully accesses multiple FMP endpoints and provides the concise output necessary for our work.</p><h3><strong>Microservice Containerization</strong></h3><p>Lastly, we will containerize our microservice. So far, we have a working microservice that runs locally. That&#8217;s fine for development, but as soon as you want to share the service with someone else or deploy it somewhere other than your laptop, we will run into dependency issues.</p><p>Containerizing the service with Docker provides a self-contained, reproducible environment that anyone with Docker can run, regardless of their local setup.</p><p>To perform Docker containerization, you need to install <a href="https://www.docker.com/products/docker-desktop/">Docker Desktop</a> initially. Then, fill the <code>Dockerfile</code> file with the following code:</p><pre><code>ROM python:3.11-slim

ENV PYTHONDONTWRITEBYTECODE=1 \
    PYTHONUNBUFFERED=1

WORKDIR /code

# Install dependencies
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# Copy application code
COPY app ./app

EXPOSE 8000

# Run the FastAPI app with uvicorn
CMD [&#8221;uvicorn&#8221;, &#8220;app.main:app&#8221;, &#8220;--host&#8221;, &#8220;0.0.0.0&#8221;, &#8220;--port&#8221;, &#8220;8000&#8221;]</code></pre><p>Next, we will build the Docker image with the following command:</p><pre><code>docker build -t microservice-financial-service .</code></pre><p>The build above will result in the reusable image we can use and share with others. Assuming your <code>.env</code> have appropriately filled, we can run the container with the following command:</p><pre><code>docker run --env-file .env -p 8000:8000 microservice-financial-service</code></pre><p>Then, visit the <code>http://localhost:8000/docs</code> once more to access the Microservice documentation.</p><p>With the microservice running in the container, we can test it out in the Jupyter Notebook with the following code:</p><pre><code>import requests

BASE_URL = &#8220;http://127.0.0.1:8000&#8221;
symbol = &#8220;AAPL&#8221;

response = requests.get(f&#8221;{BASE_URL}/companies/{symbol}/snapshot&#8221;)
print(&#8221;Status:&#8221;, response.status_code)
snapshot = response.json()
snapshot</code></pre><p>The output result looks like this:</p><pre><code>Status: 200
{&#8217;symbol&#8217;: &#8216;AAPL&#8217;,
 &#8216;name&#8217;: &#8216;Apple Inc.&#8217;,
 &#8216;currency&#8217;: &#8216;USD&#8217;,
 &#8216;exchange&#8217;: &#8216;NASDAQ&#8217;,
 &#8216;asOf&#8217;: &#8216;2025-09-27&#8217;,
 &#8216;income&#8217;: {&#8217;revenue&#8217;: 416161000000.0, &#8216;netIncome&#8217;: 112010000000.0},
 &#8216;balanceSheet&#8217;: {&#8217;totalAssets&#8217;: 359241000000.0,
  &#8216;totalLiabilities&#8217;: 285508000000.0},
 &#8216;cashFlow&#8217;: {&#8217;operatingCashFlow&#8217;: 111482000000.0}}</code></pre><p>Overall, our microservice financial with FMP works well and is ready to use for any follow-up actions.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.nb-data.com/p/building-an-open-source-microservice?utm_source=substack&utm_medium=email&utm_content=share&action=share&quot;,&quot;text&quot;:&quot;Share&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://www.nb-data.com/p/building-an-open-source-microservice?utm_source=substack&utm_medium=email&utm_content=share&action=share"><span>Share</span></a></p><div><hr></div><h2><strong>Conclusion</strong></h2><p>In this article, we have turned Financial Modelling Prep&#8217;s stable API into a small and reusable microservice that better meets our company&#8217;s needs than the raw endpoints.</p><p>By wrapping core functions such as search, snapshot, and history in FastAPI, Pydantic schemas, and a lightweight Docker image, we now have a straightforward, well-defined interface for our data acquisition.</p><p>You can use this as a drop-in data layer for notebooks, dashboards, or internal tools, and expand it over time with new endpoints, caching, or authentication as your use cases develop.</p>]]></content:encoded></item><item><title><![CDATA[Introduction to Open‑Source Image Generation Models: A Beginner’s Guide]]></title><description><![CDATA[Gentle introduction to understand the image generation AI]]></description><link>https://www.nb-data.com/p/introduction-to-opensource-image</link><guid isPermaLink="false">https://www.nb-data.com/p/introduction-to-opensource-image</guid><dc:creator><![CDATA[Cornellius Yudha Wijaya]]></dc:creator><pubDate>Sun, 09 Nov 2025 12:47:29 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!UgXB!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd41827a0-51fb-4dd2-8143-6415a37c4ce3_1312x736.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!UgXB!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd41827a0-51fb-4dd2-8143-6415a37c4ce3_1312x736.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!UgXB!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd41827a0-51fb-4dd2-8143-6415a37c4ce3_1312x736.jpeg 424w, https://substackcdn.com/image/fetch/$s_!UgXB!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd41827a0-51fb-4dd2-8143-6415a37c4ce3_1312x736.jpeg 848w, https://substackcdn.com/image/fetch/$s_!UgXB!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd41827a0-51fb-4dd2-8143-6415a37c4ce3_1312x736.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!UgXB!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd41827a0-51fb-4dd2-8143-6415a37c4ce3_1312x736.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!UgXB!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd41827a0-51fb-4dd2-8143-6415a37c4ce3_1312x736.jpeg" width="1312" height="736" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/d41827a0-51fb-4dd2-8143-6415a37c4ce3_1312x736.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:736,&quot;width&quot;:1312,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:98353,&quot;alt&quot;:&quot;Introduction to Open&#8209;Source Image Generation Models: A Beginner&#8217;s Guide&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://www.nb-data.com/i/178345084?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd41827a0-51fb-4dd2-8143-6415a37c4ce3_1312x736.jpeg&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Introduction to Open&#8209;Source Image Generation Models: A Beginner&#8217;s Guide" title="Introduction to Open&#8209;Source Image Generation Models: A Beginner&#8217;s Guide" srcset="https://substackcdn.com/image/fetch/$s_!UgXB!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd41827a0-51fb-4dd2-8143-6415a37c4ce3_1312x736.jpeg 424w, https://substackcdn.com/image/fetch/$s_!UgXB!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd41827a0-51fb-4dd2-8143-6415a37c4ce3_1312x736.jpeg 848w, https://substackcdn.com/image/fetch/$s_!UgXB!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd41827a0-51fb-4dd2-8143-6415a37c4ce3_1312x736.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!UgXB!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd41827a0-51fb-4dd2-8143-6415a37c4ce3_1312x736.jpeg 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Image by Author | Ideogram.ai</figcaption></figure></div><h1>Introduction</h1><p>Open&#8209;source image generation models are AI tools that create pictures based on text descriptions, and they are freely available for anyone to use or modify. In simple terms, you can type in a prompt (for example, &#8220;a medieval knight on a horse at sunset&#8221;), and the model will generate an image matching that description. </p><p>These models rose to prominence around 2022 when AI image generators went mainstream.  First with OpenAI&#8217;s proprietary DALL&#8209;E 2, and soon after with the open-source <a href="https://stability.ai/stable-image">Stable Diffusion model</a> released by Stability AI. </p><p>Unlike closed systems (such as Midjourney or DALL&#8209;E, which you can only access via paid services or APIs), open-source models have no paywalls or strict usage rules, allowing anyone to run them locally or in the cloud without the typical costs or restrictions of proprietary software. </p><p>In this article, we will explore Open&#8209;Source Image Generation Models further and how you can navigate them. </p><p>Let&#8217;s get into it.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.nb-data.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://www.nb-data.com/subscribe?"><span>Subscribe now</span></a></p><div><hr></div><h1>Key Advantages</h1><p>Open-source image generation models are powerful AI art tools that put creative control directly in the users&#8217; hands, free of charge and open for customization by the community. </p><p>There are many advantages to using the open-source image generation models, including:</p><ul><li><p><strong>Cost Efficiency:</strong> These models are available without licensing fees or subscription costs. You can run them on your own hardware or affordable cloud instances, avoiding the pay-per-image charges of some commercial services. In short, aside from hardware or electricity, generating images with an open model is practically free.</p></li><li><p><strong>Flexibility &amp; Customization:</strong> Since the code and weights are open, you have the freedom to customize the model to suit your needs. You can adjust parameters, change the model&#8217;s code, or even fine-tune it on your own images to create a specific style. This allows developers or artists to build the tool according to their vision rather than being limited to a generic service. For example, developers have made custom versions of Stable Diffusion for medical imaging, anime art, interior design, and more &#8211; all made possible by the flexible open license.</p></li><li><p><strong>Transparency (Trust &amp; Understanding):</strong> Open-source models enable anyone to see how they work internally. The model&#8217;s architecture and training data can be scrutinized for biases or problems, which helps build trust. There&#8217;s no hidden "secret sauce" behind closed doors, as researchers and users can review the model&#8217;s behavior and make sure it isn&#8217;t doing anything harmful. This openness also encourages learning; students and engineers can study actual, cutting-edge model code to improve their understanding of AI.</p></li><li><p><strong>Community-Driven Innovation:</strong> A vibrant community surrounds these models, leading to rapid updates and contributions worldwide. Developers share features, improvements, and fixes, allowing open models to advance faster than proprietary ones. For example, the Stable Diffusion community has developed a broad ecosystem of plugins, enhancements, and fine-tuned checkpoints. Many community-trained versions are available online for various aesthetics or tasks. This collaborative environment means that if you face a problem or seek a new feature, a solution is likely already available or in progress.</p></li><li><p><strong>No Hard Usage Limits:</strong> Unlike some proprietary tools that may limit the number of images you can generate or impose content restrictions, open-source tools allow you to generate as many as your hardware can support. There&#8217;s no rate limiting or mandatory censorship built into the model itself.</p></li><li><p><strong>Educational Value:</strong> Open models are a great resource for education and research. Students, researchers, or anyone interested can experiment with them to learn about AI image creation. Since everything is accessible, you can observe how modifying the code or training data influences the results, which is very helpful for understanding machine learning. This open access speeds up progress in both academia and industry in generative AI.</p></li></ul><p>These are the benefits you can expect from using the open-source image generation model. However, there are still challenges that come with using these models.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.nb-data.com/p/introduction-to-opensource-image?utm_source=substack&utm_medium=email&utm_content=share&action=share&quot;,&quot;text&quot;:&quot;Share&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://www.nb-data.com/p/introduction-to-opensource-image?utm_source=substack&utm_medium=email&utm_content=share&action=share"><span>Share</span></a></p><div><hr></div><h2>Disadvantages and Challenges</h2><p>Despite their many benefits, open-source image generation models also present some challenges and drawbacks that users should consider:</p><ul><li><p><strong>High Hardware Requirements:</strong> Running advanced image models requires a powerful computer, ideally a modern GPU with ample VRAM. Generating high-resolution or multiple images can be resource-intensive, making it difficult for basic laptops or phones to run models like Stable Diffusion locally. Users may need hardware upgrades or cloud services for good performance. (For example, generating a 512&#215;512 image typically needs a GPU with 4&#8211;8 GB VRAM and can take several seconds.)</p></li><li><p><strong>Technical Complexity:</strong> The open-source community aims to make these tools user-friendly, but they aren&#8217;t always plug-and-play. Setting up and running a model might involve working with Python environments, drivers, and command-line interfaces, which can intimidate beginners. <a href="https://github.com/AUTOMATIC1111/stable-diffusion-webui">The popular UI</a> has&nbsp;many&nbsp;features, which can overwhelm new users. Using open models fully often requires technical knowledge, and troubleshooting issues like installation errors or GPU incompatibilities is part of learning. Advanced features like training custom models or chaining multiple models need even more expertise.</p></li><li><p><strong>Quality Limitations and Trade-offs:</strong> Open models can produce impressive results but aren't perfect, sometimes generating artifacts or errors like distorted hands or text. Outputs vary, as you may need to adjust prompts or settings. While proprietary models like MidJourney are optimized for specific styles, open models may require extra tuning. Sometimes the images look great but lack logical consistency, as models mimic patterns without understanding scenes. Expect trial and error for the desired quality.</p></li><li><p><strong>Ethical Concerns (Bias and Misuse):</strong> Open models learn from large datasets that can contain biases, leading to skewed representations, especially if certain demographics are overrepresented. They lack filters to prevent harmful content, raising ethical concerns about misuse, such as generating violent or misleading images. While open-source freedom enables innovation, it also allows malicious use, creating a double-edged sword.</p></li><li><p><strong>Legal and Copyright Questions:</strong> There are debates about the legality of images from these models, as their training data often includes copyrighted images scraped from the web without permission. This raises lawsuits and uncertainty over infringement when outputs mimic styles or images closely. Commercial use of AI art might&nbsp;face legal issues&nbsp;until laws are updated. Unlike proprietary services that ban generating images of real people or copyrighted characters, open models can do whatever is asked, risking legal trouble if used improperly. It&#8217;s important to stay informed about legal changes and use the technology ethically.</p></li></ul><p>These are the challenges and disadvantages we can encounter if we are using the open-source image generation model.</p><div><hr></div><h1>How Does an Open-Source Image Generation Model Work?</h1><p>Under the hood, most modern open-source image generators use a process called diffusion to create images. In simple terms, the model starts with a field of random noise and gradually refines it into a coherent picture that matches your prompt.</p><p>Diffusion models are a type of AI algorithm within the category of generative models, created to generate new data from existing data. Specifically, in diffusion models, this allows the creation of new images based on the input given.</p><p>For diffusion models, the process differs from traditional methods, as it involves adding and then removing noise from the data. Essentially, the model modifies the images and refines them to generate the final output. Think of it as a denoising process where the model learns to remove noise from images.</p><p>The diffusion model was originally introduced in the paper&nbsp;<em><a href="https://arxiv.org/abs/1503.03585">'Deep Unsupervised</a></em><a href="https://arxiv.org/abs/1503.03585">&nbsp;</a><em><a href="https://arxiv.org/abs/1503.03585">Learning using</a></em><a href="https://arxiv.org/abs/1503.03585">&nbsp;</a><em><a href="https://arxiv.org/abs/1503.03585">Nonequilibrium Thermodynamics'&nbsp;</a></em><a href="https://arxiv.org/abs/1503.03585">by Sohl-Dickstein et al. (2015)</a>. It describes converting data into noise via a controlled forward diffusion process and training a model to reverse this process, reconstructing the data through denoising.</p><p>Building on this foundation, Ho et al. (2020) in their paper&nbsp;<em><a href="https://arxiv.org/abs/2006.11239?">"Denoising Diffusion Probabilistic Models"</a></em>&nbsp;introduce the modern diffusion framework, capable of generating high-quality images and surpassing earlier popular models such as Generative Adversarial Networks (GANs). Typically, the diffusion model involves two essential stages:</p><ol><li><p><strong>Forward (diffusion) process</strong>: Data is progressively corrupted by noise addition until it appears as random static.</p></li><li><p><strong>Reverse (denoising) process</strong>: Involves training a neural network to gradually eliminate noise and learn to reconstruct image data starting from pure randomness.</p></li></ol><p>In practice, these steps are performed <strong>in latent space</strong> using a variational autoencoder (VAE): the model denoises compact latent representations and then decodes them back to pixels. Let&#8217;s now examine the components of the diffusion model more closely to make this concrete.</p><div><hr></div><h3><strong>Forward Process</strong></h3><p>The forward process is the first phase, where the images are systematically degraded by noise until they become random static.</p><p>The forward process is controlled and iterative, which we can summarize in the following steps:</p><ol><li><p><strong>Begin with an image dataset</strong></p></li><li><p><strong>Add a small amount of noise</strong> to the image.</p></li><li><p><strong>Repeat</strong> this process many times, possibly hundreds or thousands of times, each time further corrupting the image.</p></li><li><p>After enough steps, the original image will become just <strong>pure noise</strong>.</p></li></ol><p>The process described above is often represented mathematically as a Markov chain because each noisy version depends only on the one right before it, not on the full sequence of steps.</p><p>Why do we gradually turn the image into noise instead of doing it all at once? Our goal in the forward process is to help the model learn to reverse the corruption step by step. Using gradual steps allows the model to learn how to go from noisy data to clearer data. This method helps the model rebuild the image by learning little by little through the process of adding noise.</p><p>To determine how much noise is added to the step, the concept of the schedule is used. For example, linear schedules gradually introduce noise over time, while cosine schedules add noise more slowly and maintain useful image features for a longer duration.</p><p>That&#8217;s a quick summary of the Forward Process. Let&#8217;s explore the Reverse Process further.</p><div><hr></div><h3><strong>Reverse Process</strong></h3><p>The subsequent step after the forward process involves transforming the model into a generator that learns to convert noise into image data. Through small, iterative adjustments, the model can generate new, previously nonexistent images.</p><p>In general, the <strong>reverse process</strong> is the inverse of the forward process, where:</p><ol><li><p><strong>Begin with pure noise,</strong> which is an entirely random image made up of Gaussian noise.</p></li><li><p><strong>Iteratively remove noise</strong>&nbsp;with a trained model that simulates reversing each forward step. In every iteration, the model receives the current noisy image and its timestep, then predicts how to lower the noise level based on what it learned during training.</p></li><li><p><strong>Gradually,</strong>&nbsp;the image becomes clearer, resulting in usable image data.</p></li></ol><p>This reverse process depends on a well-trained model that can effectively denoise noisy images. Diffusion models typically employ a neural network architecture like a&nbsp;<strong>U-Net</strong>, which functions as an autoencoder with convolutional layers in an encoder&#8211;decoder setup. During training, the model learns to predict the noise added in the forward process. At each step, it also takes the timestep into account, enabling it to modify its predictions according to the noise level.</p><p>The model is usually trained with a loss function like&nbsp;<strong>mean squared error (MSE)</strong>, which measures the difference between predicted and actual noise. By reducing this loss across many examples, the model gradually becomes skilled at reversing the diffusion process.</p><p>Compared to options like Generative Adversarial Networks (GANs), diffusion models provide greater stability and a simpler generative process. The step-by-step denoising method results in more expressive learning, making training more reliable and easier to understand.</p><p>Once the model is fully trained, creating a new image follows the reverse process summarized above.</p><div class="directMessage button" data-attrs="{&quot;userId&quot;:6000855,&quot;userName&quot;:&quot;Cornellius Yudha Wijaya&quot;,&quot;canDm&quot;:null,&quot;dmUpgradeOptions&quot;:null,&quot;isEditorNode&quot;:true}" data-component-name="DirectMessageToDOM"></div><div><hr></div><h3><strong>Text Conditioning</strong></h3><p>In many open-source image generation models, these systems can guide the reverse process using text prompts, which we call text conditioning. By incorporating natural language, we get a matching scene instead of random visuals.</p><p>The system uses a pre-trained text encoder (such as CLIP Text; SDXL variants also utilize OpenCLIP or T5) to convert the prompt into a vector or sequence of embeddings. These embeddings are then fed into the diffusion U-Net through cross-attention, enabling the network to concentrate on relevant words and phrases as it denoises. During each step of the reverse process, the model references both the current noisy sample and the text embeddings, employing cross-attention to align emerging visual features with the prompt&#8217;s semantics.</p><p>Many implementations also use classifier-free guidance (CFG): the network blends unconditional and conditional predictions, with a guidance scale determining how closely the image follows the prompt. In latent-diffusion setups, all conditioning occurs in latent space, and a VAE decoder then converts the final latent back into pixels.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.nb-data.com/?utm_source=substack&utm_medium=email&utm_content=share&action=share&quot;,&quot;text&quot;:&quot;Share Non-Brand Data&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://www.nb-data.com/?utm_source=substack&utm_medium=email&utm_content=share&action=share"><span>Share Non-Brand Data</span></a></p><div><hr></div><h1>Notable Open-Source Text-to-Image Models (2025)</h1><ul><li><p><strong><a href="https://huggingface.co/stable-diffusion-v1-5/stable-diffusion-v1-5">Stable Diffusion v1.5</a></strong> &#8211; The original Stable Diffusion (by CompVis/StabilityAI) is a latent diffusion text-to-image model capable of generating photorealistic images from text prompts.</p></li><li><p><strong><a href="https://huggingface.co/stabilityai/stable-diffusion-2-1">Stable Diffusion v2.1</a></strong> &#8211; A newer StabilityAI release, SD v2.1, is a refined latent diffusion model (768&#215;768) that also creates and edits images from text. </p></li><li><p><strong><a href="https://huggingface.co/stabilityai/stable-diffusion-3-medium">Stable Diffusion 3 Medium (MMDiT)</a></strong> &#8211; A mid-sized &#8220;Stable Diffusion 3&#8221; model utilizing the new Multimodal Diffusion Transformer (MMDiT) architecture. </p></li><li><p><strong><a href="https://huggingface.co/stabilityai/stable-diffusion-3.5-large">Stable Diffusion 3.5 Large (MMDiT)</a></strong> &#8211; A larger MMDiT version of Stable Diffusion 3, optimized for top quality. SD3.5 Large 'offers improved performance in image quality, typography, complex prompt understanding, and resource efficiency.&#8221; </p></li><li><p><strong><a href="https://huggingface.co/stabilityai/stable-diffusion-xl-base-1.0">Stable Diffusion XL 1.0 (base)</a></strong> &#8211; The flagship high-capacity SDXL model. The SDXL 1.0 base model is a latent diffusion model using two large CLIP text encoders (ViT-G and ViT-L) to handle nuanced prompts. </p></li><li><p><strong><a href="https://huggingface.co/ByteDance/SDXL-Lightning">SDXL-Lightning (ByteDance)</a></strong> &#8211; A research model by ByteDance that distills Stable Diffusion XL for speed. SDXL-Lightning &#8220;is a lightning-fast text-to-image generation model&#8221; that can produce 1024px images in only a few diffusion steps. </p></li><li><p><strong><a href="https://huggingface.co/black-forest-labs/FLUX.1-dev">FLUX.1 (Black Forest Labs)</a></strong> &#8211; A modern open-weights rectified-flow transformer (&#8776;12B params) for high-fidelity text-to-image. Strong prompt following and DiT-style efficiency. </p></li><li><p><strong><a href="https://huggingface.co/playgroundai/playground-v2.5-1024px-aesthetic">Playground v2.5 (Playground AI)</a></strong> &#8211; An SDXL-style latent-diffusion base tuned for aesthetic 1024&#215;1024 results and robust aspect ratios.</p></li><li><p><strong><a href="https://github.com/Tencent-Hunyuan/HunyuanImage-3.0">HunyuanImage-3.0 (Tencent)</a></strong> &#8211; A native multimodal open-weights system whose text-to-image module targets parity with leading closed models; active, fast-moving repo with inference code and weights.</p></li><li><p><strong><a href="https://huggingface.co/PixArt-alpha/PixArt-Sigma-XL-2-1024-MS">PixArt-&#931; (PixArt-alpha)</a></strong><a href="https://huggingface.co/PixArt-alpha/PixArt-Sigma-XL-2-1024-MS"> </a>&#8211; A Diffusion-Transformer (DiT) base that can generate up to 4K directly in a single sampling pass; an influential open alternative to UNet-based LDMs.</p></li></ul><p>Each of the above models is open-source and still widely used, and is able to improve your work.</p><div><hr></div><p>That&#8217;s all for the simple introduction to the Open&#8209;Source Image Generation Models. If you like the article, don&#8217;t forget to share and comment.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.nb-data.com/p/introduction-to-opensource-image?utm_source=substack&utm_medium=email&utm_content=share&action=share&quot;,&quot;text&quot;:&quot;Share&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://www.nb-data.com/p/introduction-to-opensource-image?utm_source=substack&utm_medium=email&utm_content=share&action=share"><span>Share</span></a></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.nb-data.com/p/introduction-to-opensource-image/comments&quot;,&quot;text&quot;:&quot;Leave a comment&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://www.nb-data.com/p/introduction-to-opensource-image/comments"><span>Leave a comment</span></a></p><p></p><p></p>]]></content:encoded></item><item><title><![CDATA[14 Portfolio Projects That Demonstrate Real Business Value]]></title><description><![CDATA[Learn from these projects to improve your data career]]></description><link>https://www.nb-data.com/p/14-portfolio-projects-that-demonstrate</link><guid isPermaLink="false">https://www.nb-data.com/p/14-portfolio-projects-that-demonstrate</guid><dc:creator><![CDATA[Cornellius Yudha Wijaya]]></dc:creator><pubDate>Tue, 28 Oct 2025 14:30:20 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!qMFC!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F477891ae-e9c7-40fe-b307-95539df62bde_1312x736.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!qMFC!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F477891ae-e9c7-40fe-b307-95539df62bde_1312x736.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!qMFC!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F477891ae-e9c7-40fe-b307-95539df62bde_1312x736.jpeg 424w, https://substackcdn.com/image/fetch/$s_!qMFC!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F477891ae-e9c7-40fe-b307-95539df62bde_1312x736.jpeg 848w, https://substackcdn.com/image/fetch/$s_!qMFC!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F477891ae-e9c7-40fe-b307-95539df62bde_1312x736.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!qMFC!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F477891ae-e9c7-40fe-b307-95539df62bde_1312x736.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!qMFC!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F477891ae-e9c7-40fe-b307-95539df62bde_1312x736.jpeg" width="1312" height="736" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/477891ae-e9c7-40fe-b307-95539df62bde_1312x736.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:736,&quot;width&quot;:1312,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:95897,&quot;alt&quot;:&quot;14 Portfolio Projects That Demonstrate Real Business Value&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://www.nb-data.com/i/177370160?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F477891ae-e9c7-40fe-b307-95539df62bde_1312x736.jpeg&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="14 Portfolio Projects That Demonstrate Real Business Value" title="14 Portfolio Projects That Demonstrate Real Business Value" srcset="https://substackcdn.com/image/fetch/$s_!qMFC!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F477891ae-e9c7-40fe-b307-95539df62bde_1312x736.jpeg 424w, https://substackcdn.com/image/fetch/$s_!qMFC!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F477891ae-e9c7-40fe-b307-95539df62bde_1312x736.jpeg 848w, https://substackcdn.com/image/fetch/$s_!qMFC!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F477891ae-e9c7-40fe-b307-95539df62bde_1312x736.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!qMFC!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F477891ae-e9c7-40fe-b307-95539df62bde_1312x736.jpeg 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Image by Author | Ideogram.ai</figcaption></figure></div><p>We live in an era when data has become a commodity that every business wants to use. That&#8217;s why there are many companies willing to pay a lot of money to have the best data scientist.</p><p>With numerous competitions happening, the best way to stand out is by having data science portfolios that address real business problems with measurable results. </p><p>Below are 14 real&#8211;world&#8211;inspired projects you can take inspiration from. Each project shows the strategic problem, the approach, measurable impact, and deployment in production.</p><p>Curious about it? Let&#8217;s get into it.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.nb-data.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://www.nb-data.com/subscribe?"><span>Subscribe now</span></a></p><div><hr></div><h1>Sponsored Section</h1><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!ETPr!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb63bfb71-fab5-476c-8e49-1ad9eb45fb0e_1182x588.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!ETPr!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb63bfb71-fab5-476c-8e49-1ad9eb45fb0e_1182x588.png 424w, https://substackcdn.com/image/fetch/$s_!ETPr!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb63bfb71-fab5-476c-8e49-1ad9eb45fb0e_1182x588.png 848w, https://substackcdn.com/image/fetch/$s_!ETPr!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb63bfb71-fab5-476c-8e49-1ad9eb45fb0e_1182x588.png 1272w, https://substackcdn.com/image/fetch/$s_!ETPr!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb63bfb71-fab5-476c-8e49-1ad9eb45fb0e_1182x588.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!ETPr!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb63bfb71-fab5-476c-8e49-1ad9eb45fb0e_1182x588.png" width="1182" height="588" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/b63bfb71-fab5-476c-8e49-1ad9eb45fb0e_1182x588.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:588,&quot;width&quot;:1182,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:75091,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.nb-data.com/i/177370160?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb63bfb71-fab5-476c-8e49-1ad9eb45fb0e_1182x588.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!ETPr!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb63bfb71-fab5-476c-8e49-1ad9eb45fb0e_1182x588.png 424w, https://substackcdn.com/image/fetch/$s_!ETPr!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb63bfb71-fab5-476c-8e49-1ad9eb45fb0e_1182x588.png 848w, https://substackcdn.com/image/fetch/$s_!ETPr!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb63bfb71-fab5-476c-8e49-1ad9eb45fb0e_1182x588.png 1272w, https://substackcdn.com/image/fetch/$s_!ETPr!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fb63bfb71-fab5-476c-8e49-1ad9eb45fb0e_1182x588.png 1456w" sizes="100vw"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Subscribe to <a href="http://recommendations.page/non-brand-data?ref_code=3006913751&amp;email={{subscriber.email_address}}">asiabits</a></figcaption></figure></div><p>Your fast-track to Asia&#8217;s hottest trends. asiabits delivers sharp insights on tech, business &amp; culture. What the world talks about tomorrow, you read today.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;http://recommendations.page/non-brand-data?ref_code=3006913751&amp;email={{subscriber.email_address}}&quot;,&quot;text&quot;:&quot;Subscribe to asiabits&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="http://recommendations.page/non-brand-data?ref_code=3006913751&amp;email={{subscriber.email_address}}"><span>Subscribe to asiabits</span></a></p><div><hr></div><h2><strong>1. Netflix Content Recommendation Engine</strong> </h2><ul><li><p><strong>Business context:</strong> Netflix needed to keep subscribers engaged by surfacing relevant shows. Its personalization system tailors each user&#8217;s homepage.</p></li><li><p><strong>Tech/method:</strong> A hybrid recommendation pipeline (collaborative filtering, deep learning, extensive content tagging, and ranking). Netflix tags content into ~76,000 &#8220;micro-genres&#8221; and uses multiple models to match users to content.</p></li><li><p><strong>Metrics/results:</strong> The recommendation engine drives about 75&#8211;80% of all viewing hours. This personalization substantially boosts user engagement and retention.</p></li><li><p><strong>Deployment:</strong> Fully embedded in Netflix&#8217;s streaming platform; served via real-time APIs to power each user&#8217;s homepage.</p></li></ul><div><hr></div><h2><strong>2. Walmart E-commerce Search Optimization</strong> </h2><ul><li><p><strong>Business context:</strong> Walmart&#8217;s online store needed improved search results to boost conversions. Previously, basic keyword matches frequently showed irrelevant items.</p></li><li><p><strong>Tech/method:</strong> Machine learning&#8211;based search ranking: deep learning and NLP models trained on billions of past search queries and user click logs. Contextual embeddings and click-through data refine the search results.</p></li><li><p><strong>Metrics/results:</strong> After revamping with ML, Walmart saw a 20% increase in conversion rate from search traffic. In other words, far more users bought products after a search.</p></li><li><p><strong>Deployment:</strong> Integrated into Walmart&#8217;s e-commerce platform (Walmart Labs), updating in real time as new products and queries are added.</p></li></ul><div><hr></div><h2><strong>3. Demand Forecasting &amp; Inventory Optimization (Sam&#8217;s Club)</strong> </h2><ul><li><p><strong>Business context:</strong> Sam&#8217;s Club (Walmart) needs to forecast product demand across stores and distribution centers to improve inventory, pricing, and promotions. Different teams used to create isolated forecasts.</p></li><li><p><strong>Tech/method:</strong> A cloud-based Centralized Forecasting Service utilizing statistical and ML models (e.g., gradient boosting and recurrent neural networks) on historical sales, promotions, seasonality, and weather data. All departments share a unified forecasting pipeline.</p></li><li><p><strong>Metrics/results:</strong> The unified system enhances forecast accuracy and consistency. More precise forecasts decrease excess stock and stockouts. For example, Walmart reported a 10% reduction in excess inventory, a 15% increase in on-shelf availability, and approximately $1&#8239;billion in holding cost savings over 12 months.</p></li><li><p><strong>Deployment:</strong> Deployed on Google Cloud Platform, it offers automated, real-time forecasts on demand. Teams can trigger forecasts through APIs and dashboards, ensuring all decisions rely on a single source of truth.</p></li></ul><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.nb-data.com/p/14-portfolio-projects-that-demonstrate?utm_source=substack&utm_medium=email&utm_content=share&action=share&quot;,&quot;text&quot;:&quot;Share&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://www.nb-data.com/p/14-portfolio-projects-that-demonstrate?utm_source=substack&utm_medium=email&utm_content=share&action=share"><span>Share</span></a></p><div><hr></div><h2><strong>4. Personalized Marketing &amp; Offers (Target Guest ID System)</strong></h2><ul><li><p><strong>Business context:</strong> Target gathers both online and in-store customer data. It needed to transform this data into personalized promotions and emails to boost sales.</p></li><li><p><strong>Tech/method:</strong> Real-time ML models that leverage a customer&#8217;s purchase history, demographics, app usage, and social sentiment. Techniques include ensemble learning for propensity scoring and multi-armed bandits for selecting email content.</p></li><li><p><strong>Metrics/results:</strong> In 2023, Target reported that 50% of its digital sales were driven by ML-powered personalization. Personalized emails and in-app suggestions (e.g., dynamic homepage feeds) significantly boosted conversion rates and basket size.</p></li><li><p><strong>Deployment:</strong> Models operate on Google Cloud and Kubernetes, integrated with the e-commerce front end and email marketing systems. A feature store and retraining pipelines ensure models stay updated with live loyalty and browsing data.</p></li></ul><div><hr></div><h2><strong>5. Supply Chain &amp; Workforce Optimization (Target SCOL Project)</strong> </h2><ul><li><p><strong>Business context:</strong> Beyond marketing, Target used ML in its supply chain, such as predicting local demand spikes (from events or weather) and optimizing restocking and staff schedules.</p></li><li><p><strong>Tech/method:</strong> The Supply Chain Optimization Lab (SCOL) developed regression and time-series models using POS data, store traffic, and external data. It also employs classification to generate demand surge alerts.</p></li><li><p><strong>Metrics/results:</strong> These ML initiatives led to notable efficiency improvements: a 12% decrease in out-of-stock items, 20% fewer overstocks, and an 18% boost in labor cost efficiency (better matching staff levels to customer demand).</p></li><li><p><strong>Deployment:</strong> Models are deployed through Airflow and Kubeflow pipelines on Google Cloud. In production, they provide alerts to store management dashboards and automate ordering systems.</p></li></ul><div><hr></div><h2><strong>6. Gaming Hardware Recommender (Razer)</strong></h2><ul><li><p><strong>Business context:</strong> Razer&#8217;s online store serves 175 million users with a variety of gaming devices. Razer aimed to increase cross-sells and up-sells by recommending compatible products, such as suggesting a gaming mouse based on a user&#8217;s PC setup.</p></li><li><p><strong>Tech/method:</strong> They used AWS&#8217;s Amazon Personalize (an ML recommendation service) for user segmentation and filtering. The solution was trained on user-device configurations and purchase history.</p></li><li><p><strong>Metrics/results:</strong> This system achieved a click-through rate 10&#215; higher than industry benchmarks, generating significant additional revenue through customized accessory recommendations.</p></li><li><p><strong>Deployment:</strong> The model operates on Razer Synapse (their configuration utility). Recommendations are provided both in batch (via email campaigns) and in real-time (on the website), and are continuously retrained as user inventories evolve.</p></li></ul><div class="directMessage button" data-attrs="{&quot;userId&quot;:6000855,&quot;userName&quot;:&quot;Cornellius Yudha Wijaya&quot;,&quot;canDm&quot;:null,&quot;dmUpgradeOptions&quot;:null,&quot;isEditorNode&quot;:true}" data-component-name="DirectMessageToDOM"></div><div><hr></div><h2><strong>7. Event Recommendation Newsletter (Ticketek)</strong></h2><ul><li><p><strong>Business context:</strong> Ticketek, a live-event ticketing platform, had 4 million subscribers but only sent out state-based generic newsletters. They aimed to boost sales of smaller events like concerts and sports by matching customers with relevant shows.</p></li><li><p><strong>Tech/method:</strong> Using Amazon Personalize, Ticketek developed a recommendation engine that factors in a user&#8217;s past purchases, browsing history, and event metadata. Every week, it produces personalized event recommendations.</p></li><li><p><strong>Metrics/results:</strong> After the launch, the purchase rate from their newsletter tripled (up 250%), and tickets sold per newsletter opening increased by 49%. These improvements demonstrate highly targeted recommendations, increased engagement, and more sales.</p></li><li><p><strong>Deployment:</strong> Hosted on AWS, the recommender outputs are integrated into Ticketek&#8217;s email system. Personalized newsletters are automatically generated and sent, and real-time REST APIs provide suggestions on the website as well.</p></li></ul><div><hr></div><h2><strong>8. Sports Media Personalization (Pulselive)</strong> </h2><ul><li><p><strong>Business context:</strong> Pulselive, a digital partner for sports clients, needed to customize video highlights for fans of major football clubs and events. Generic video pages were not performing well.</p></li><li><p><strong>Tech/method:</strong> Again, using Amazon Personalize, they input user clickstream and team preferences into an ML model that ranks live match clips and news items.</p></li><li><p><strong>Metrics/results:</strong> For a leading European football client, personalized recommendations boosted video consumption by 20% across web and mobile platforms. Fans interacted more with content when it was customized to their favorite teams and topics.</p></li><li><p><strong>Deployment:</strong> Deployed on AWS, outputs plug into the Pulselive platform. Content is delivered through a personalized video carousel on the club&#8217;s website and app, with a feedback loop for ongoing learning.</p></li></ul><div><hr></div><h2><strong>9. Fraud Detection at Scale (Mastercard)</strong> </h2><ul><li><p><strong>Business context:</strong> Mastercard handles millions of transactions each minute. They needed to enhance fraud detection and cut down on false alerts to better protect merchants and cardholders.</p></li><li><p><strong>Tech/method:</strong> Using a combination of AWS AI/ML services and graph analysis, Mastercard trains models on transaction patterns. Graph algorithms identify rings of suspicious accounts, while real-time scoring flags anomalous payments.</p></li><li><p><strong>Metrics/results:</strong> The new system increased the detection of fraudulent transactions threefold while reducing false positives by ten times. This accuracy saves merchants billions in chargeback costs and enhances customer trust.</p></li><li><p><strong>Deployment:</strong> The ML models operate in the cloud, processing streams of transactions. When a transaction is flagged, it&#8217;s either declined or sent for additional verification. The AI functions as part of Mastercard&#8217;s global authorization pipeline.</p></li></ul><div class="community-chat" data-attrs="{&quot;url&quot;:&quot;https://open.substack.com/pub/cornellius/chat?utm_source=chat_embed&quot;,&quot;subdomain&quot;:&quot;cornellius&quot;,&quot;pub&quot;:{&quot;id&quot;:37262,&quot;name&quot;:&quot;Non-Brand Data&quot;,&quot;author_name&quot;:&quot;Cornellius Yudha Wijaya&quot;,&quot;author_photo_url&quot;:&quot;https://substackcdn.com/image/fetch/$s_!eEx-!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F583076b2-657b-44bf-8aa9-9263e5bf04f0_544x544.png&quot;}}" data-component-name="CommunityChatRenderPlaceholder"></div><div><hr></div><h2><strong>10. Conversational AI Chatbot (International Financial Services)</strong></h2><ul><li><p><strong>Business context:</strong> Customers need 24/7 support for routine questions like account info and policy details, but call centers were costly. The chatbot project aimed to reduce expenses and improve service speed.</p></li><li><p><strong>Tech/method:</strong> A conversational AI built with modern NLP platforms such as Rasa, Dialogflow, or custom LLMs trained on historical support tickets. Key components include intent classification, entity extraction, and dialogue management.</p></li><li><p><strong>Metrics/results:</strong> The bot saved &#8364;2 million annually by handling support tasks automatically. In reality, only about 6% of chats needed live agent handoff, indicating the bot&#8217;s high accuracy. Customer satisfaction also increased due to immediate responses.</p></li><li><p><strong>Deployment:</strong> Integrated into the company&#8217;s website and mobile app, the chatbot system operates on cloud infrastructure with an orchestration layer that logs performance. Analytics dashboards monitor resolution rates and update models iteratively.</p></li></ul><div><hr></div><h2><strong>11. Route Optimization (UPS ORION)</strong></h2><ul><li><p><strong>Business context:</strong> UPS delivers 16.9 million packages daily using about 100,000 vehicles. Even small routing improvements can lead to significant savings.</p></li></ul><ul><li><p><strong>Tech/method:</strong> The ORION system employs advanced combinatorial optimization and heuristics (a customized &#8220;traveling salesman&#8221; solver) for vehicle telematics and delivery data. It combines historical driver knowledge with real-time constraints.</p></li><li><p><strong>Metrics/results:</strong> By 2016, ORION had eliminated approximately 10 million miles of driving per year, saving over 10 million gallons of fuel and roughly $300&#8211;400 million annually. UPS notes that even reducing one driver&#8217;s route by one mile a day can save about $50 million a year overall.</p></li><li><p><strong>Deployment:</strong> ORION is integrated into UPS&#8217;s fleet management software. It creates daily optimized routes for drivers at over 1,000 facilities. Drivers get the updated routes on in-cab devices, and the system keeps learning from feedback.</p></li></ul><div><hr></div><h2><strong>12. Data Center Cooling Optimization (Google DeepMind)</strong> </h2><ul><li><p><strong>Business context:</strong> Data centers use a lot of power for cooling. Even Google&#8217;s very efficient facilities gain small improvements in PUE (power usage efficiency). Cutting down energy consumption lowers operational costs and reduces carbon footprint.</p></li><li><p><strong>Tech/method:</strong> DeepMind developed an ensemble of deep neural networks to forecast future PUE and data center temperatures. A controller model then suggests setpoint adjustments. The system was trained using historical operating data.</p></li><li><p><strong>Metrics/results:</strong> In live A/B tests, the AI controller reduced cooling costs by 40% while maintaining all systems' safety. For Google, this resulted in millions of dollars in savings and a notable decrease in emissions.</p></li><li><p><strong>Deployment:</strong> The model operates within Google&#8217;s data center management software. It continually processes real-time sensor data (via Dataflow/Flink), predicts results, and independently fine-tunes equipment like chillers and fans.</p></li></ul><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.nb-data.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://www.nb-data.com/subscribe?"><span>Subscribe now</span></a></p><div><hr></div><h2><strong>13. Centralized Retail Forecast Platform (Walmart Sam&#8217;s Club)</strong> </h2><ul><li><p><strong>Business context:</strong> Previously, different teams at Walmart (pricing, marketing, supply chain) each ran separate demand forecasts, leading to inconsistent planning.</p></li><li><p><strong>Tech/method:</strong> Sam&#8217;s Club developed a centralized forecasting service on Google Cloud where any team can request forecasts. It uses standardized ML pipelines and trusted feature sets, ensuring all forecasts share the same data and models.</p></li><li><p><strong>Metrics/results:</strong> Centralization significantly enhanced the consistency and speed of forecasting. By utilizing shared, audited datasets and models, teams coordinated strategies and minimized redundant efforts. The system reduces overhead and accelerates decision-making; indirectly, it also lowers inventory risk and manual workload.</p></li><li><p><strong>Deployment:</strong> A cloud-hosted API where users submit parameters (region, SKU, time window). The backend runs time-series ML models and returns predictions. This service spans merchandising, finance, and operations.</p></li></ul><div><hr></div><h2><strong>14. AI Visual Inspection in Manufacturing</strong></h2><ul><li><p><strong>Business context:</strong> Manufacturers require near-perfect defect detection. Manual inspection is prone to errors and slow, especially for critical products like steel slabs or components.</p></li><li><p><strong>Tech/method:</strong> Deep learning computer vision models (CNNs) analyze images from high-resolution cameras on the production line. Trained on labeled defect/no-defect samples, the system detects cracks, dents, misalignments, and more.</p></li><li><p><strong>Metrics/results</strong>: In a steel mill case, accuracy improved from about 70% (manual) to over 98% defect detection. Precision reached 99.8%. The AI saved over $2 million annually, with a 1900% ROI in the first year. Across various examples, factories report approximately 28% less downtime and a 15&#8211;20% reduction in costs from deploying AI inspection.</p></li><li><p><strong>Deployment:</strong> Cameras and edge processors are installed along the production line. The vision models operate in real time, displaying defects on an operator dashboard. Integration with MES/ERP systems automatically triggers hold or rework workflows. Continuous retraining addresses new defect types.</p></li></ul><div><hr></div><p>These are 14 different projects grounded in real-world applications with clear strategic value. I hope it becomes an inspiration for your personal data science project.</p><p>Like this article? Don&#8217;t forget to share and comment.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.nb-data.com/p/14-portfolio-projects-that-demonstrate?utm_source=substack&utm_medium=email&utm_content=share&action=share&quot;,&quot;text&quot;:&quot;Share&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://www.nb-data.com/p/14-portfolio-projects-that-demonstrate?utm_source=substack&utm_medium=email&utm_content=share&action=share"><span>Share</span></a></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.nb-data.com/p/14-portfolio-projects-that-demonstrate/comments&quot;,&quot;text&quot;:&quot;Leave a comment&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://www.nb-data.com/p/14-portfolio-projects-that-demonstrate/comments"><span>Leave a comment</span></a></p>]]></content:encoded></item><item><title><![CDATA[Python 3.14: 12 Features You Can Use Today]]></title><description><![CDATA[The addition features you should not miss]]></description><link>https://www.nb-data.com/p/python-314-12-features-you-can-use</link><guid isPermaLink="false">https://www.nb-data.com/p/python-314-12-features-you-can-use</guid><dc:creator><![CDATA[Cornellius Yudha Wijaya]]></dc:creator><pubDate>Wed, 22 Oct 2025 14:07:37 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!0nht!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F82a2a7ff-b2ff-4901-bada-6cb3c40a2414_1312x736.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!0nht!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F82a2a7ff-b2ff-4901-bada-6cb3c40a2414_1312x736.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!0nht!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F82a2a7ff-b2ff-4901-bada-6cb3c40a2414_1312x736.jpeg 424w, https://substackcdn.com/image/fetch/$s_!0nht!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F82a2a7ff-b2ff-4901-bada-6cb3c40a2414_1312x736.jpeg 848w, https://substackcdn.com/image/fetch/$s_!0nht!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F82a2a7ff-b2ff-4901-bada-6cb3c40a2414_1312x736.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!0nht!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F82a2a7ff-b2ff-4901-bada-6cb3c40a2414_1312x736.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!0nht!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F82a2a7ff-b2ff-4901-bada-6cb3c40a2414_1312x736.jpeg" width="1312" height="736" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/82a2a7ff-b2ff-4901-bada-6cb3c40a2414_1312x736.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:736,&quot;width&quot;:1312,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:91329,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://www.nb-data.com/i/176830352?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F82a2a7ff-b2ff-4901-bada-6cb3c40a2414_1312x736.jpeg&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!0nht!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F82a2a7ff-b2ff-4901-bada-6cb3c40a2414_1312x736.jpeg 424w, https://substackcdn.com/image/fetch/$s_!0nht!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F82a2a7ff-b2ff-4901-bada-6cb3c40a2414_1312x736.jpeg 848w, https://substackcdn.com/image/fetch/$s_!0nht!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F82a2a7ff-b2ff-4901-bada-6cb3c40a2414_1312x736.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!0nht!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F82a2a7ff-b2ff-4901-bada-6cb3c40a2414_1312x736.jpeg 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Image by Author | Ideogram.ai</figcaption></figure></div><p><a href="https://www.python.org/downloads/release/python-3140/">Python 3.14</a> has now been released, bringing a mix of improvements to the language, its implementation, and the standard library. Many of the biggest changes sharpen the language&#8217;s tools, boost developer ergonomics, and open doors to new capabilities without forcing you to rewrite your code. </p><p>In this article, we highlight 12 new features and enhancements in Python 3.14 that are particularly useful for data scientists and Python developers, focusing on practical benefits in data manipulation, performance, and everyday development.</p><p>Each feature below is presented with a brief explanation of what it is, why it matters, and an example (where applicable) showing how you can start using it today. </p><p>Let&#8217;s get into it!</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.nb-data.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://www.nb-data.com/subscribe?"><span>Subscribe now</span></a></p><div><hr></div><h2>1. Colorful Interactive REPL</h2><p>One of the first things you&#8217;ll notice in Python 3.14 is a friendlier interactive shell (REPL). The default Python REPL now highlights Python syntax in color, making code easier to read as you type. Keywords, built-ins, and other syntax elements are colored by default, improving the interactive coding experience. This enhancement helps you spot syntax errors or typos faster and provides a more intuitive, IDE-like feel when working in the terminal.</p><p>In addition to the REPL, several command-line interfaces in the standard library (such as unittest, argparse, json, and others) now support colored output. This means that running your tests or parsing arguments can produce color-coded text (for example, highlighting errors or important information) without any extra configuration. All these improvements contribute to a more pleasant and productive development workflow right out of the box.</p><div><hr></div><h2>2. More Helpful Error Messages</h2><p>Python 3.14 continues the recent trend of improving error messages to be more descriptive and helpful. The interpreter can now often guess your mistakes and suggest fixes. For example, if you accidentally mistype a Python keyword, the error will include a suggestion:</p><pre><code>whille True:
   pass
SyntaxError: invalid syntax. Did you mean &#8216;while&#8217;?</code></pre><p>In this case, Python noticed the misspelling and helpfully suggested the correct keyword. This saves developers time in tracking down simple typos. Similar improvements have been made for other common mistakes. For instance, using an elif after an else block now yields a clear error (&#8220;&#8217;elif&#8217; block follows an &#8216;else&#8217; block&#8221;), and using the wrong prefix on string literals (like ub&#8217;...&#8217;) will tell you that certain prefixes are incompatible.</p><p>Error messages for runtime issues have also been polished. If you try to add an unhashable type to a set or use a list as a dict key, the TypeError will explicitly state which type is unhashable (e.g., &#8220;cannot use &#8216;dict&#8217; as a set element (unhashable type: &#8216;dict&#8217;)&#8221;). Overall, these clearer error messages guide you toward fixes faster, making debugging and development more efficient.</p><div><hr></div><h2>3. Safe Live Debugging (Attach to Running Processes)</h2><p>Debugging long-running processes just got easier and safer. Python 3.14 introduces a zero-overhead debugging interface (PEP 768) that allows debuggers and profilers to attach to a running Python process without pausing or altering its execution. In practical terms, this means you can inspect and debug a live Python program (even in production) without needing to start it under a debugger from the beginning.</p><p>One direct benefit of this feature is that the built-in Python debugger pdb can now attach to an existing process. For example, you can attach to a process with ID 12345 by running:</p><pre><code>python -m pdb -p 12345</code></pre><p>This will connect a pdb session to the running program identified by that PID (process ID). Previously, such capability wasn&#8217;t available as we had to anticipate debugging needs by starting the program with pdb or use external tools. Now, Python 3.14 provides a safe hook for live debugging, so you can investigate issues on the fly. Under the hood, this is enabled by a new sys.remote_exec() function and a carefully designed attach protocol, but you don&#8217;t need to know those details to use it. </p><p>The key takeaway is that debugging and profiling in production or long-running jobs is much more feasible, which is a big win for reliability and developer ergonomics.</p><div><hr></div><h2>4. Template Strings (T-Strings) for Custom String Processing</h2><p>Python 3.14 introduces template string literals, also known as t-strings, providing a safer and more flexible way to perform string interpolation. Syntactically, t-strings look just like f-strings except they use a t prefix instead of f. For example:</p><pre><code>&gt;&gt;&gt; name = &#8220;Alice&#8221;
&gt;&gt;&gt; template = t&#8221;Hello, {name}!&#8221;
&gt;&gt;&gt; type(template)
&lt;class &#8216;string.templatelib.Template&#8217;&gt;
&gt;&gt;&gt; list(template)
[&#8217;Hello, &#8216;, Interpolation(&#8217;Alice&#8217;, &#8216;name&#8217;, None, &#8216;&#8217;), &#8216;!&#8217;]</code></pre><p>Unlike an f-string, which immediately produces a plain string, a t-string evaluates to a Template object (defined in the new string .templatelib module) that contains the static parts and the interpolated parts separately. In the example above, the template holds the literal text and an Interpolation object for the {name} placeholder, including both the value and the original expression. This separation allows you to manipulate or validate the interpolated parts before combining them into a final string.</p><p>Why is this useful? Template strings enable safer string processing patterns. You can build functions to escape or validate interpolated values (e.g. to prevent HTML or SQL injection) before rendering the final string. They open the door to custom domain-specific languages: for instance, you could implement an html() function that takes a Template and produces an HTML-safe string by escaping any dangerous characters in the interpolations. In short, t-strings give you the convenience of f-strings with an extra layer of control over how placeholders are handled. This is particularly useful in data science or web applications where you often need to dynamically generate strings but must be careful about sanitizing inputs.</p><div><hr></div><h2>5. Cleaner Exception Handling Syntax</h2><p>Dealing with exceptions becomes a bit cleaner in Python 3.14. You no longer need to put multiple exception types in parentheses in an except clause when you&#8217;re <em>not</em> using an as alias. In previous versions, to catch multiple exception types, you would write:</p><pre><code>try:
    do_something()
except (ValueError, TypeError):
    handle_error()</code></pre><p>Now you can simply separate them with commas without parentheses:</p><pre><code>try:
    do_something()
except ValueError, TypeError:
    handle_error()</code></pre><p>This change (defined in PEP 758) makes the syntax for catching multiple exceptions more concise. It also applies to except* clauses (used for exception groups in asynchronous tasks) where you can omit the brackets there as well when not binding the exception object. While this is a small tweak, it improves code readability and is one less thing to remember when writing try/except blocks. It&#8217;s a straightforward quality-of-life improvement for developers.</p><p><em>(Note: If you do use as to name the exception, you still need parentheses around multiple exception types to avoid ambiguity.)</em></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.nb-data.com/p/python-314-12-features-you-can-use?utm_source=substack&utm_medium=email&utm_content=share&action=share&quot;,&quot;text&quot;:&quot;Share&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://www.nb-data.com/p/python-314-12-features-you-can-use?utm_source=substack&utm_medium=email&utm_content=share&action=share"><span>Share</span></a></p><div><hr></div><h2>6. AsyncIO Task Introspection with asyncio ps and pstree</h2><p>If you write or maintain asynchronous code, Python 3.14 brings a new tool to help debug and understand your async tasks. The asyncio module now has a command-line introspection interface that lets you inspect running asynchronous tasks in a live process. By running <code>python -m asyncio ps &lt;PID&gt;</code> (where &lt;PID&gt; is the process ID of a Python program using asyncio), you get a snapshot of all running tasks in that event loop. It will list each task, its name, its coroutine call stack, and which tasks (if any) are awaiting it. This is akin to a process listing (ps) but for asyncio tasks, helping you see what coroutines are active or stuck.</p><p>There&#8217;s also <code>python -m asyncio pstree &lt;PID&gt;</code> which displays the tasks in a tree structure, showing parent-child relationships between tasks (e.g., which task spawned or is awaiting which). This is especially useful for visualizing complex async workflows or diagnosing deadlocks in async code. For example, if tasks are awaiting each other and form a cycle, the tool will detect it and report the cycle.</p><p>Why this matters: debugging async applications (like web servers, crawlers, or any I/O-heavy concurrent program) has historically been challenging. This new introspection capability lets you peek inside a running async event loop to troubleshoot performance issues or logical bugs without stopping the program. It&#8217;s a built-in way to monitor and debug asyncio, which will be valuable in real-world scenarios such as identifying which coroutine is blocking your application.</p><div><hr></div><h2>7. Deferred Evaluation of Annotations (Lazy Type Hints)</h2><p>Type annotations in Python 3.14 are now evaluated lazily by default, as specified in PEP 649 and PEP 749. In practice, this means that annotations on functions, classes, and modules are no longer executed at definition time, but stored for later evaluation only when needed. The immediate benefit is performance: defining functions with annotations is faster and has no side effects (previously, if an annotation referred to a name that wasn&#8217;t defined yet, you had to quote it or import it early). Now, you can freely use forward references in annotations without using string literals.</p><p>For example, you can define a self-referential type or mutually referential classes like this:</p><pre><code># Before Python 3.14: forward references had to be in quotes
class Tree:
    def __init__(self, parent: &#8216;Tree&#8217; = None):
        self.parent = parent

# In Python 3.14: no quotes needed for forward references
class Tree:
    def __init__(self, parent: Tree = None):
        self.parent = parent</code></pre><p>In the Python 3.14 version, the annotation parent: Tree won&#8217;t cause a NameError even though the class Tree isn&#8217;t fully defined at that point. The annotation is stored in a deferred form and can be resolved later (for instance, by tools like typing.get_type_hints() or the new annotationlib.get_annotations() module). This deferred evaluation improves runtime performance by avoiding work at import time, and simplifies development because you no longer need to add import hacks or quotes for forward-declared types.</p><p>For data scientists and developers, this &#8220;lazy&#8221; annotation behavior means you can add type hints more freely, even in complex module setups or circular dependencies. It reduces the friction of using type hints in large projects and lays the  groundwork for more powerful type introspection utilities.</p><div><hr></div><h2>8. Parallel Subinterpreters for True Concurrency</h2><p>Python 3.14 adds standard library support for subinterpreters (PEP 734), enabling a new model of parallelism. Subinterpreters are isolated Python interpreters within the same process, which you can think of as lightweight processes that can run in parallel on multiple CPU cores, but without the overhead of launching separate OS processes. The new concurrent.interpreters module and a high-level API InterpreterPoolExecutor in concurrent.futures let you easily run tasks in parallel interpreters.</p><p>Why is this exciting? Subinterpreters offer <em>true multi-core parallelism</em> while keeping a shared memory space (with explicit data passing). They are like threads in terms of efficiency, but unlike threads, they don&#8217;t share all state by default, which avoids the Global Interpreter Lock (GIL) contention and many concurrency headaches. In fact, you can think of multiple interpreters as having &#8220;the isolation of processes with the efficiency of threads.&#8221; For CPU-bound tasks, this can drastically improve performance by utilizing all cores without needing to spin up full separate processes for each task.</p><p>Using subinterpreters is straightforward for developers familiar with concurrent.futures. For example, you can use the new InterpreterPoolExecutor similarly to a ThreadPool or ProcessPool:</p><pre><code>from concurrent.futures import InterpreterPoolExecutor

def compute_square(x):
    return x * x

with InterpreterPoolExecutor() as executor:
    results = list(executor.map(compute_square, range(5)))
    print(results)  # Output: [0, 1, 4, 9, 16]</code></pre><p>Each task submitted to an InterpreterPoolExecutor runs in its own separate interpreter, so CPU-bound computations truly run in parallel across cores. The arguments and results are pickled under the hood (since subinterpreters don&#8217;t share objects), but subinterpreters start much faster and use less memory than spawning new processes. This feature will enable more scalable data processing and parallel algorithms in pure Python, without needing external libraries or leaving the comfort of the Python standard library.</p><p><em>(Keep in mind that some C extension modules may need updates to work in multiple interpreters, but all built-in modules have been made compatible. The community is actively improving support now that this feature is available.)</em></p><div><hr></div><h2>9. Free-Threaded Python (No GIL Mode)</h2><p>Perhaps one of the most impactful changes in Python 3.14 is that a free-threaded (no-GIL) build of Python is now officially supported (PEP 703/779). This variant of the interpreter removes the Global Interpreter Lock, allowing truly parallel threads in the same process. In other words, CPU-bound Python code can potentially use multiple threads at the same time, accelerating workloads like numerical computations, data transformations, or any heavy processing that was limited by the GIL before.</p><p>In Python 3.13, an experimental no-GIL build was introduced, but it required opting in and was not officially supported. In 3.14, the no-GIL build continues to be an opt-in feature, but it is maintained as a fully supported part of CPython going forward. This means you can compile or install a no-GIL edition of Python 3.14 knowing that it will receive updates and won&#8217;t be dropped without warning. If you&#8217;re interested in trying it, you can enable the free-threaded mode and run your multi-threaded code to see significant speed-ups on multi-core machines.</p><p>It&#8217;s worth noting that the free-threaded build, in its current state, may run single-threaded code about 5-10% slower than the regular GIL build due to the overheads introduced by removing the GIL. However, for programs that can utilize multiple threads, the ability to run in parallel often more than makes up for this overhead. This is a huge step for Python in domains like scientific computing and data engineering, where multi-core utilization is key. With Python 3.14, we&#8217;re seeing the beginning of a no-GIL future: you can start experimenting with it today to speed up threaded workloads, without changing your Python code at all (just use the no-GIL build).</p><div><hr></div><h2>10. Experimental JIT Compiler in CPython</h2><p>Python 3.14 takes a step towards boosting performance by including an experimental Just-In-Time (JIT) compiler in the official CPython distribution. In the Windows and macOS Python 3.14 installers, an optional JIT is now bundled (disabled by default). This JIT works by dynamically compiling portions of Python bytecode into machine code at runtime, aiming to accelerate execution of hot code paths. It complements the adaptive interpreter introduced in earlier versions by optimizing at a larger granularity &#8211; not just one bytecode at a time, but sequences of instructions.</p><p>To try out the JIT, you can enable it with an environment variable or command-line switch. For example, running your program with <code>PYTHON_JIT=1</code> in the environment will turn on the JIT compiler. You can also use a -X flag (e.g. -X jit) when launching Python. When enabled, the JIT will monitor your code as it runs and compile parts of it to native code for speed. This can lead to significant speed-ups for long-running or compute-intensive applications &#8211; though because it&#8217;s experimental, the results may vary and not all workloads will see a benefit yet.</p><p>For developers, the message is that Python is getting faster, and you can opt into these improvements right away. If you have a performance-critical script, it may be worth benchmarking with the JIT enabled to see if it helps. As the JIT stabilizes in future releases, we can expect Python to require fewer hand-written C extensions or workarounds for speed. Python 3.14&#8217;s JIT is an early glimpse at these forthcoming gains in execution speed.</p><div><hr></div><h2>11. Tail-Call Optimized Bytecode Interpreter</h2><p>Another under-the-hood improvement in Python 3.14 is a new tail-calling interpreter implementation for CPython. This isn&#8217;t a new feature you use in your code, but rather a change in how the Python interpreter executes bytecode. Instead of using one giant C switch statement for the main loop, the new interpreter uses tail calls between tiny functions that implement each opcode. For certain compilers and platforms, this approach has yielded a 3-5% overall speedup on the Python benchmark suite.</p><p>While a few percent may not sound like much, it&#8217;s a free performance boost that applies to all Python code. Especially in data science or server applications, even single-digit percentage improvements can translate to meaningful time savings over large workloads. The tail-call interpreter is currently an opt-in build (it requires a newish compiler like Clang 19+ and enabling a compile-time flag), so average users won&#8217;t see it unless they build Python from source with those options. However, its inclusion signals ongoing efforts to speed up CPython. It also lays groundwork for future compatibility as compilers evolve (GCC is expected to support this technique soon.</p><p>In summary, Python 3.14&#8217;s tail-call interpreter is purely an internal optimization. It doesn&#8217;t change Python&#8217;s semantics or require any code changes, but it shows that the Python core devs are squeezing out performance wherever possible. Over time, such improvements accumulate, making Python a bit faster with each release.</p><div><hr></div><h2>12. Incremental Garbage Collection</h2><p>Python has an automatic garbage collector for cleaning up unused objects, especially those involved in reference cycles. In Python 3.14, the cyclic garbage collector has been improved to run incrementally, rather than in one big stop-the-world sweep. The result is that garbage collection pause times are dramatically reduced by an order of magnitude or more for large heaps. In practical terms, if your program allocates and releases a lot of objects (common in data processing, simulations, or servers that handle many requests), you should experience shorter delays when the GC runs, leading to smoother performance.</p><p>Previously, the GC might occasionally introduce noticeable pauses if there were a huge number of objects to examine, because it would try to process a lot of them in one go. With incremental GC, the work is broken into smaller chunks interleaved with normal execution, so your program doesn&#8217;t have to stop for as long at once. This is especially beneficial for applications that require responsiveness or have real-time constraints &#8211; for example, a data pipeline that ingests data continuously will see more consistent throughput, and an interactive application will remain more responsive even under heavy memory load.</p><p>As a developer or data scientist, you don&#8217;t need to do anything to reap this benefit &#8211; it&#8217;s an automatic improvement. Your Python 3.14 programs will likely &#8220;feel&#8221; snappier under memory pressure. This change is another example of Python 3.14 refining existing machinery (in this case, memory management) to be more efficient and robust in real-world use.</p><h2>Conclusion</h2><p>Python 3.14 bring new polish with features that are useful for our everyday work. In this article, we have covered the new features released:</p><ol><li><p>Colorized interactive REPL and colored stdlib CLIs</p></li><li><p>Clearer, suggestion-rich error messages</p></li><li><p>Safe live debugging: attach to running processes</p></li><li><p>Template strings (&#8220;t-strings&#8221;) for controlled interpolation</p></li><li><p>Cleaner <code>except</code> syntax for multiple exceptions (no parens)</p></li><li><p>AsyncIO introspection: <code>python -m asyncio ps</code> / <code>pstree</code></p></li><li><p>Deferred evaluation of annotations (lazy type hints)</p></li><li><p>Subinterpreters + <code>InterpreterPoolExecutor</code> for true parallelism</p></li><li><p>Free-threaded (no-GIL) CPython build (opt-in)</p></li><li><p>Experimental JIT compiler in CPython (opt-in)</p></li><li><p>Tail-call-based bytecode interpreter (internal speedup)</p></li><li><p>Incremental garbage collection (shorter GC pauses)</p></li></ol><p>I hope it has helped!</p><div><hr></div><p>Like this article? Don&#8217;t forget to comment and share.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.nb-data.com/p/python-314-12-features-you-can-use/comments&quot;,&quot;text&quot;:&quot;Leave a comment&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://www.nb-data.com/p/python-314-12-features-you-can-use/comments"><span>Leave a comment</span></a></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.nb-data.com/p/python-314-12-features-you-can-use?utm_source=substack&utm_medium=email&utm_content=share&action=share&quot;,&quot;text&quot;:&quot;Share&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://www.nb-data.com/p/python-314-12-features-you-can-use?utm_source=substack&utm_medium=email&utm_content=share&action=share"><span>Share</span></a></p>]]></content:encoded></item><item><title><![CDATA[Building AI-Ready Data]]></title><description><![CDATA[The Preparation and Real Work Before the Modelling]]></description><link>https://www.nb-data.com/p/building-ai-ready-data</link><guid isPermaLink="false">https://www.nb-data.com/p/building-ai-ready-data</guid><dc:creator><![CDATA[Cornellius Yudha Wijaya]]></dc:creator><pubDate>Fri, 17 Oct 2025 05:45:33 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!9J6S!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcd10e6f1-40b0-47df-832a-6bf848b541ae_1312x736.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!9J6S!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcd10e6f1-40b0-47df-832a-6bf848b541ae_1312x736.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!9J6S!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcd10e6f1-40b0-47df-832a-6bf848b541ae_1312x736.jpeg 424w, https://substackcdn.com/image/fetch/$s_!9J6S!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcd10e6f1-40b0-47df-832a-6bf848b541ae_1312x736.jpeg 848w, https://substackcdn.com/image/fetch/$s_!9J6S!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcd10e6f1-40b0-47df-832a-6bf848b541ae_1312x736.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!9J6S!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcd10e6f1-40b0-47df-832a-6bf848b541ae_1312x736.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!9J6S!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcd10e6f1-40b0-47df-832a-6bf848b541ae_1312x736.jpeg" width="1312" height="736" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/cd10e6f1-40b0-47df-832a-6bf848b541ae_1312x736.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:736,&quot;width&quot;:1312,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:71681,&quot;alt&quot;:&quot;Building AI-Ready Data&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://www.nb-data.com/i/175492312?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcd10e6f1-40b0-47df-832a-6bf848b541ae_1312x736.jpeg&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Building AI-Ready Data" title="Building AI-Ready Data" srcset="https://substackcdn.com/image/fetch/$s_!9J6S!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcd10e6f1-40b0-47df-832a-6bf848b541ae_1312x736.jpeg 424w, https://substackcdn.com/image/fetch/$s_!9J6S!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcd10e6f1-40b0-47df-832a-6bf848b541ae_1312x736.jpeg 848w, https://substackcdn.com/image/fetch/$s_!9J6S!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcd10e6f1-40b0-47df-832a-6bf848b541ae_1312x736.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!9J6S!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcd10e6f1-40b0-47df-832a-6bf848b541ae_1312x736.jpeg 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Image by Author | Ideogram.ai</figcaption></figure></div><p>We often think that AI breakthroughs mean advanced algorithms and complex model architectures. However, the real secret to AI success isn&#8217;t just the model; <strong>it&#8217;s the data behind it</strong>. In fact, advanced AI models are just the &#8220;tip of the iceberg,&#8221; and 90% of success lies in data foundations. </p><p>In other words, AI initiatives mainly depend on the quality and readiness of data. As one expert put it, <em>&#8220;AI models are only as good as the data they&#8217;re trained on,&#8221;</em> because bad data leads to unreliable predictions. </p><div class="pullquote"><p>Without AI-ready data, even the best algorithms will stumble.</p></div><p>We can think of them as a pyramid of needs for AI, where the base layers are the foundation, including data collection, cleaning, and organization, which must be solid before the pinnacle (the AI model) can work best.</p><p>So, how could we build AI-ready data? We will explore them in the next section.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.nb-data.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://www.nb-data.com/subscribe?"><span>Subscribe now</span></a></p><div><hr></div><h1>Sponsored Section</h1><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!o2KV!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd6d3a8d2-e253-47dd-94f7-e24a9d87b470_1080x1080.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!o2KV!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd6d3a8d2-e253-47dd-94f7-e24a9d87b470_1080x1080.png 424w, https://substackcdn.com/image/fetch/$s_!o2KV!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd6d3a8d2-e253-47dd-94f7-e24a9d87b470_1080x1080.png 848w, https://substackcdn.com/image/fetch/$s_!o2KV!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd6d3a8d2-e253-47dd-94f7-e24a9d87b470_1080x1080.png 1272w, https://substackcdn.com/image/fetch/$s_!o2KV!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd6d3a8d2-e253-47dd-94f7-e24a9d87b470_1080x1080.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!o2KV!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd6d3a8d2-e253-47dd-94f7-e24a9d87b470_1080x1080.png" width="583" height="583" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/d6d3a8d2-e253-47dd-94f7-e24a9d87b470_1080x1080.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1080,&quot;width&quot;:1080,&quot;resizeWidth&quot;:583,&quot;bytes&quot;:541625,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.nb-data.com/i/175492312?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd6d3a8d2-e253-47dd-94f7-e24a9d87b470_1080x1080.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!o2KV!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd6d3a8d2-e253-47dd-94f7-e24a9d87b470_1080x1080.png 424w, https://substackcdn.com/image/fetch/$s_!o2KV!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd6d3a8d2-e253-47dd-94f7-e24a9d87b470_1080x1080.png 848w, https://substackcdn.com/image/fetch/$s_!o2KV!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd6d3a8d2-e253-47dd-94f7-e24a9d87b470_1080x1080.png 1272w, https://substackcdn.com/image/fetch/$s_!o2KV!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fd6d3a8d2-e253-47dd-94f7-e24a9d87b470_1080x1080.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption"><a href="https://www.atscale.com/resource/semantic-layer-best-practices-enterprise-ai-gigaom/?utm_medium=social&amp;utm_source=pdligroups&amp;utm_campaign=202510wbr&amp;utm_content=webinar&amp;utm_term=ai-grp-d">Register for FREE Here!</a></figcaption></figure></div><p>The 2025 data stack is incomplete without a semantic layer.</p><p>It&#8217;s what makes data trustworthy, analytics consistent, and GenAI enterprise-ready.</p><p>Join <strong>AtScale</strong> and <strong>GigaOm</strong> for an exclusive look at the <strong>2025 Semantic Layer Radar Report</strong>, and learn how to evaluate vendors, align architecture with business impact, and prepare your AI strategy.</p><p><strong>&#128197; Wednesday, October 29, 2025 | 2:00 PM ET | 60 minutes</strong><br>&#127897;&#65039;<strong>Dave Mariani, Co-founder &amp; CTO, AtScale</strong><br>&#127897;&#65039;<strong>Andrew Brust, Analyst, GigaOm</strong></p><p><strong>You&#8217;ll discover:</strong></p><ul><li><p>Which semantic layer vendors lead the 2025 landscape</p></li><li><p>How to integrate GenAI + LLMs with governed data</p></li><li><p>Proven frameworks for scaling and governance</p></li><li><p>How to measure ROI and business impact</p></li></ul><p><strong>Reserve your FREE spot here&#128071;&#128071;&#128071;</strong><br></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.atscale.com/resource/semantic-layer-best-practices-enterprise-ai-gigaom/?utm_medium=social&amp;utm_source=pdligroups&amp;utm_campaign=202510wbr&amp;utm_content=webinar&amp;utm_term=ai-grp-d&quot;,&quot;text&quot;:&quot;FREE Registration&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://www.atscale.com/resource/semantic-layer-best-practices-enterprise-ai-gigaom/?utm_medium=social&amp;utm_source=pdligroups&amp;utm_campaign=202510wbr&amp;utm_content=webinar&amp;utm_term=ai-grp-d"><span>FREE Registration</span></a></p><p>Don&#8217;t miss it!</p><div><hr></div><p></p><h2>What <strong>&#8220;AI-Ready&#8221; Data</strong> Really Means</h2><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!b_PC!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F63d0841a-7d5c-42cd-bf64-81165389ac9a_1612x818.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!b_PC!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F63d0841a-7d5c-42cd-bf64-81165389ac9a_1612x818.png 424w, https://substackcdn.com/image/fetch/$s_!b_PC!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F63d0841a-7d5c-42cd-bf64-81165389ac9a_1612x818.png 848w, https://substackcdn.com/image/fetch/$s_!b_PC!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F63d0841a-7d5c-42cd-bf64-81165389ac9a_1612x818.png 1272w, https://substackcdn.com/image/fetch/$s_!b_PC!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F63d0841a-7d5c-42cd-bf64-81165389ac9a_1612x818.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!b_PC!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F63d0841a-7d5c-42cd-bf64-81165389ac9a_1612x818.png" width="1456" height="739" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/63d0841a-7d5c-42cd-bf64-81165389ac9a_1612x818.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:739,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:158190,&quot;alt&quot;:&quot;&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.nb-data.com/i/175492312?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F63d0841a-7d5c-42cd-bf64-81165389ac9a_1612x818.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" title="" srcset="https://substackcdn.com/image/fetch/$s_!b_PC!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F63d0841a-7d5c-42cd-bf64-81165389ac9a_1612x818.png 424w, https://substackcdn.com/image/fetch/$s_!b_PC!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F63d0841a-7d5c-42cd-bf64-81165389ac9a_1612x818.png 848w, https://substackcdn.com/image/fetch/$s_!b_PC!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F63d0841a-7d5c-42cd-bf64-81165389ac9a_1612x818.png 1272w, https://substackcdn.com/image/fetch/$s_!b_PC!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F63d0841a-7d5c-42cd-bf64-81165389ac9a_1612x818.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>We hear the term &#8220;AI-ready data&#8221; frequently, but what does it mean? </p><p>In simple terms, AI-ready data is information set up so that AI systems can easily use it and generate reliable results. While nearly every company today has plenty of data, not all data is suitable for AI applications. </p><p>AI-ready data has several defining characteristics:</p><ul><li><p><strong>Clean and High-Quality:</strong> It&#8217;s accurate, consistent, and free of major errors or gaps. You won&#8217;t find the AI stumbling over duplicates, missing data, or typos. Garbage in, garbage out is the principle.</p></li><li><p><strong>Well-Governed:</strong> There are transparent processes and rules about it: who owns it, where it came from, and how it&#8217;s allowed to be used. Governance builds trust in the data by ensuring issues like lineage, privacy, and compliance are handled.</p></li><li><p><strong>Context-Rich (Semantically Enriched):</strong> AI-ready data isn&#8217;t just raw figures with confusing column names. It&#8217;s&nbsp;enhanced with context and meaning through metadata, consistent definitions, or a semantic layer, enabling AI to understand what the data represents in real business terms. For example, an AI can recognize that &#8220;Apple&#8221; refers to the tech company rather than the fruit if the data is enriched with that context.</p></li><li><p><strong>Structured and Reusable:</strong>&nbsp;The data is organized in a clear format (tables, categories, labeled text/images, etc.) and standardized so it can be easily combined or reused across different AI applications. It&#8217;s not stored in isolated silos or random spreadsheets; it&#8217;s accessible in a way that many tools or models can work with.</p></li></ul><p>Everything needed for AI technologies must be built on a foundation of clean, well-governed, and context-rich data.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.nb-data.com/p/building-ai-ready-data?utm_source=substack&utm_medium=email&utm_content=share&action=share&quot;,&quot;text&quot;:&quot;Share&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://www.nb-data.com/p/building-ai-ready-data?utm_source=substack&utm_medium=email&utm_content=share&action=share"><span>Share</span></a></p><p>One useful way to grasp the concept is to compare raw data (the starting point) with <strong>AI-ready data</strong> (the goal).  The table below highlights some key differences:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!rUdt!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F01656f2d-aec2-4c0f-9da1-37574d0fa5c0_1311x603.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!rUdt!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F01656f2d-aec2-4c0f-9da1-37574d0fa5c0_1311x603.png 424w, https://substackcdn.com/image/fetch/$s_!rUdt!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F01656f2d-aec2-4c0f-9da1-37574d0fa5c0_1311x603.png 848w, https://substackcdn.com/image/fetch/$s_!rUdt!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F01656f2d-aec2-4c0f-9da1-37574d0fa5c0_1311x603.png 1272w, https://substackcdn.com/image/fetch/$s_!rUdt!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F01656f2d-aec2-4c0f-9da1-37574d0fa5c0_1311x603.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!rUdt!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F01656f2d-aec2-4c0f-9da1-37574d0fa5c0_1311x603.png" width="1311" height="603" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/01656f2d-aec2-4c0f-9da1-37574d0fa5c0_1311x603.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:603,&quot;width&quot;:1311,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:75415,&quot;alt&quot;:&quot;Building AI-Ready Data&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.nb-data.com/i/175492312?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F01656f2d-aec2-4c0f-9da1-37574d0fa5c0_1311x603.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Building AI-Ready Data" title="Building AI-Ready Data" srcset="https://substackcdn.com/image/fetch/$s_!rUdt!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F01656f2d-aec2-4c0f-9da1-37574d0fa5c0_1311x603.png 424w, https://substackcdn.com/image/fetch/$s_!rUdt!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F01656f2d-aec2-4c0f-9da1-37574d0fa5c0_1311x603.png 848w, https://substackcdn.com/image/fetch/$s_!rUdt!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F01656f2d-aec2-4c0f-9da1-37574d0fa5c0_1311x603.png 1272w, https://substackcdn.com/image/fetch/$s_!rUdt!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F01656f2d-aec2-4c0f-9da1-37574d0fa5c0_1311x603.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><div><hr></div><h2>Key Steps to Get Data <strong>AI-Ready</strong></h2><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!lomq!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6c327085-489c-430c-8b99-00bba930bba8_1681x1284.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!lomq!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6c327085-489c-430c-8b99-00bba930bba8_1681x1284.png 424w, https://substackcdn.com/image/fetch/$s_!lomq!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6c327085-489c-430c-8b99-00bba930bba8_1681x1284.png 848w, https://substackcdn.com/image/fetch/$s_!lomq!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6c327085-489c-430c-8b99-00bba930bba8_1681x1284.png 1272w, https://substackcdn.com/image/fetch/$s_!lomq!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6c327085-489c-430c-8b99-00bba930bba8_1681x1284.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!lomq!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6c327085-489c-430c-8b99-00bba930bba8_1681x1284.png" width="1456" height="1112" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/6c327085-489c-430c-8b99-00bba930bba8_1681x1284.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1112,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:195642,&quot;alt&quot;:&quot;Building AI-Ready Data&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.nb-data.com/i/175492312?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6c327085-489c-430c-8b99-00bba930bba8_1681x1284.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Building AI-Ready Data" title="Building AI-Ready Data" srcset="https://substackcdn.com/image/fetch/$s_!lomq!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6c327085-489c-430c-8b99-00bba930bba8_1681x1284.png 424w, https://substackcdn.com/image/fetch/$s_!lomq!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6c327085-489c-430c-8b99-00bba930bba8_1681x1284.png 848w, https://substackcdn.com/image/fetch/$s_!lomq!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6c327085-489c-430c-8b99-00bba930bba8_1681x1284.png 1272w, https://substackcdn.com/image/fetch/$s_!lomq!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6c327085-489c-430c-8b99-00bba930bba8_1681x1284.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Transforming raw data into AI-ready data requires multiple layers of work. It&#8217;s helpful to think of this as a data preparation lifecycle or stack, where each layer adds more readiness. </p><p>Below are the key steps (or layers) involved in making data AI-ready:</p><ol><li><p><strong>Data Collection and Cleaning:</strong>&nbsp;First, gather data from reliable sources and clean it by fixing errors, filling or flagging missing values, removing duplicates, and standardizing formats like dates and units for consistency. This step is crucial as surveys indicate data teams spend up to 80% of their time on cleaning since models can&#8217;t learn from flawed data.</p></li><li><p><strong>Data Structuring and Annotation:</strong> Next, organize data into a structured format with labels or annotations for machine understanding. For example, tag text documents with topics, annotate images with objects, and organize numerical data into tables. Structuring also involves defining schemas to avoid messiness. Labeling features helps AI focus.</p></li><li><p><strong>Integration and Transformation:</strong> Here, we&nbsp;combine&nbsp;and transform data from various sources into a unified dataset by breaking down the data and merging records from multiple sources. Transformation also includes activities such as standardizing standard units, encoding, or creating new features. The goal is to give AI a&nbsp;complete dataset.</p></li><li><p><strong>Quality Assurance and Assessment:</strong> Just like software needs testing, data requires QA. This step involves&nbsp;assessing data quality&nbsp;and fixing issues. Teams use profiling tools or AI assessments to verify values, outliers, biases, or missing key fields. It&#8217;s a feedback loop: identify gaps, then improve the data. Proper QA ensures the data used by the model is accurate and unbiased.</p></li><li><p><strong>Governance, Security, and Compliance:</strong> Implement strong&nbsp;data governance practice<strong>s</strong>&nbsp;throughout, especially at the final stage. Establish policies for data access, usage, and compliance with regulations like GDPR or HIPAA. Track data lineage to understand its history and manage metadata. Governance ensures high-quality, well-documented data used ethically and legally by people or AI. </p></li><li><p><strong>(Optional) Semantic Alignment:</strong> Many organizations add a&nbsp;semantic layer or intelligent data model&nbsp;on top of cleaned and integrated data, mapping business concepts and relationships consistently. This layer ensures uniform definitions of terms like &#8220;churn rate&#8221; or &#8220;active user&#8221; across teams and datasets. While not essential for being &#8220;AI-ready,&#8221; it improves data reuse by making data&nbsp;self-descriptive, helping AI models understand data fields. </p></li></ol><p>Each of these steps adds a layer of preparation to the data. By the end of this process, we&#8217;ve transformed raw data into a refined dataset that an AI model can easily learn from. It&#8217;s important to remember that this is not a one-time task but an ongoing, iterative process.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.nb-data.com/?utm_source=substack&amp;utm_medium=email&amp;utm_content=share&amp;action=share&quot;,&quot;text&quot;:&quot;Share Non-Brand Data&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://www.nb-data.com/?utm_source=substack&amp;utm_medium=email&amp;utm_content=share&amp;action=share"><span>Share Non-Brand Data</span></a></p><div><hr></div><h1>The AI-Ready Data Tooling Example</h1><p>Great models need significant inputs. Think in layers, pick one tool per layer to solve a real bottleneck, then expand as needs grow. </p><p>Here are a few tool examples you can reference:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!wB_D!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa24d876d-ddb4-4c1c-a0e9-0a6b102dfaa4_1525x1205.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!wB_D!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa24d876d-ddb4-4c1c-a0e9-0a6b102dfaa4_1525x1205.png 424w, https://substackcdn.com/image/fetch/$s_!wB_D!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa24d876d-ddb4-4c1c-a0e9-0a6b102dfaa4_1525x1205.png 848w, https://substackcdn.com/image/fetch/$s_!wB_D!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa24d876d-ddb4-4c1c-a0e9-0a6b102dfaa4_1525x1205.png 1272w, https://substackcdn.com/image/fetch/$s_!wB_D!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa24d876d-ddb4-4c1c-a0e9-0a6b102dfaa4_1525x1205.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!wB_D!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa24d876d-ddb4-4c1c-a0e9-0a6b102dfaa4_1525x1205.png" width="1456" height="1150" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/a24d876d-ddb4-4c1c-a0e9-0a6b102dfaa4_1525x1205.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:1150,&quot;width&quot;:1456,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:230184,&quot;alt&quot;:&quot;Building AI-Ready Data&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://www.nb-data.com/i/175492312?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa24d876d-ddb4-4c1c-a0e9-0a6b102dfaa4_1525x1205.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Building AI-Ready Data" title="Building AI-Ready Data" srcset="https://substackcdn.com/image/fetch/$s_!wB_D!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa24d876d-ddb4-4c1c-a0e9-0a6b102dfaa4_1525x1205.png 424w, https://substackcdn.com/image/fetch/$s_!wB_D!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa24d876d-ddb4-4c1c-a0e9-0a6b102dfaa4_1525x1205.png 848w, https://substackcdn.com/image/fetch/$s_!wB_D!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa24d876d-ddb4-4c1c-a0e9-0a6b102dfaa4_1525x1205.png 1272w, https://substackcdn.com/image/fetch/$s_!wB_D!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa24d876d-ddb4-4c1c-a0e9-0a6b102dfaa4_1525x1205.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><div><hr></div><h2>Conclusion</h2><p>AI success relies more on solid data foundations than on flashy models: without AI-ready data that is high-quality, well-governed, enriched with metadata, and structured for reuse, the algorithms cannot succeed. </p><p>Achieving this requires an iterative lifecycle: collect, clean, structure, annotate, and integrate data; run quality checks; and enforce governance and security, with optional semantic alignment. </p><p>Use one fit-for-purpose tool per layer to address bottlenecks, expanding as necessary. Investing in this foundation enhances model reliability, explanation, and scalability across the business.</p><p>I hope it has helped!</p><div><hr></div><p>Like this article? Don&#8217;t forget to comment and share!</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.nb-data.com/p/building-ai-ready-data/comments&quot;,&quot;text&quot;:&quot;Leave a comment&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://www.nb-data.com/p/building-ai-ready-data/comments"><span>Leave a comment</span></a></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.nb-data.com/p/building-ai-ready-data?utm_source=substack&utm_medium=email&utm_content=share&action=share&quot;,&quot;text&quot;:&quot;Share&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://www.nb-data.com/p/building-ai-ready-data?utm_source=substack&utm_medium=email&utm_content=share&action=share"><span>Share</span></a></p>]]></content:encoded></item><item><title><![CDATA[10 Lessons Learned from Building Predictive Models]]></title><description><![CDATA[Subtle yet applicable lessons you should applied to be a better data scientist]]></description><link>https://www.nb-data.com/p/10-lessons-learned-from-building</link><guid isPermaLink="false">https://www.nb-data.com/p/10-lessons-learned-from-building</guid><dc:creator><![CDATA[Cornellius Yudha Wijaya]]></dc:creator><pubDate>Mon, 13 Oct 2025 13:29:47 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!uINW!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe7b382c8-c00d-4631-be88-4fb0d8716a93_1312x736.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!uINW!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe7b382c8-c00d-4631-be88-4fb0d8716a93_1312x736.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!uINW!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe7b382c8-c00d-4631-be88-4fb0d8716a93_1312x736.jpeg 424w, https://substackcdn.com/image/fetch/$s_!uINW!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe7b382c8-c00d-4631-be88-4fb0d8716a93_1312x736.jpeg 848w, https://substackcdn.com/image/fetch/$s_!uINW!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe7b382c8-c00d-4631-be88-4fb0d8716a93_1312x736.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!uINW!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe7b382c8-c00d-4631-be88-4fb0d8716a93_1312x736.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!uINW!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe7b382c8-c00d-4631-be88-4fb0d8716a93_1312x736.jpeg" width="1312" height="736" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/e7b382c8-c00d-4631-be88-4fb0d8716a93_1312x736.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:736,&quot;width&quot;:1312,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:142068,&quot;alt&quot;:&quot;10 Lessons Learned from Building Predictive Models&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://www.nb-data.com/i/175785116?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe7b382c8-c00d-4631-be88-4fb0d8716a93_1312x736.jpeg&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="10 Lessons Learned from Building Predictive Models" title="10 Lessons Learned from Building Predictive Models" srcset="https://substackcdn.com/image/fetch/$s_!uINW!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe7b382c8-c00d-4631-be88-4fb0d8716a93_1312x736.jpeg 424w, https://substackcdn.com/image/fetch/$s_!uINW!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe7b382c8-c00d-4631-be88-4fb0d8716a93_1312x736.jpeg 848w, https://substackcdn.com/image/fetch/$s_!uINW!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe7b382c8-c00d-4631-be88-4fb0d8716a93_1312x736.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!uINW!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fe7b382c8-c00d-4631-be88-4fb0d8716a93_1312x736.jpeg 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Image by Author | Ideogram.ai</figcaption></figure></div><p>Building predictive models is not just a technical or statistical task; it's an ongoing learning process that combines data engineering, business insight, and product thinking. Each project offers lessons that improve how you approach the next one.</p><p>In my experience leading end-to-end predictive modeling projects, I have noted 10 insights that go beyond algorithms and metrics. These lessons reflect both the analytical capability and the practical realities of deploying models that create measurable impact.</p><p>Curious about it? Let&#8217;s get into it!</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.nb-data.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://www.nb-data.com/subscribe?"><span>Subscribe now</span></a></p>
      <p>
          <a href="https://www.nb-data.com/p/10-lessons-learned-from-building">
              Read more
          </a>
      </p>
   ]]></content:encoded></item><item><title><![CDATA[Common Challenges in Operationalizing Models (and How to Overcome Them)]]></title><description><![CDATA[Most data teams hit the same roadblocks when operationalizing models. Knowing what they are changes everything.]]></description><link>https://www.nb-data.com/p/common-challenges-in-operationalizing</link><guid isPermaLink="false">https://www.nb-data.com/p/common-challenges-in-operationalizing</guid><dc:creator><![CDATA[Cornellius Yudha Wijaya]]></dc:creator><pubDate>Sun, 05 Oct 2025 15:00:55 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!12jb!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc2868bb0-037e-40e1-9e26-25b094614656_1312x736.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!12jb!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc2868bb0-037e-40e1-9e26-25b094614656_1312x736.jpeg" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!12jb!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc2868bb0-037e-40e1-9e26-25b094614656_1312x736.jpeg 424w, https://substackcdn.com/image/fetch/$s_!12jb!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc2868bb0-037e-40e1-9e26-25b094614656_1312x736.jpeg 848w, https://substackcdn.com/image/fetch/$s_!12jb!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc2868bb0-037e-40e1-9e26-25b094614656_1312x736.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!12jb!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc2868bb0-037e-40e1-9e26-25b094614656_1312x736.jpeg 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!12jb!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc2868bb0-037e-40e1-9e26-25b094614656_1312x736.jpeg" width="1312" height="736" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/c2868bb0-037e-40e1-9e26-25b094614656_1312x736.jpeg&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:736,&quot;width&quot;:1312,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:135478,&quot;alt&quot;:&quot;Common Challenges in Operationalizing Models (and How to Overcome Them)&quot;,&quot;title&quot;:null,&quot;type&quot;:&quot;image/jpeg&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://www.nb-data.com/i/175250625?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc2868bb0-037e-40e1-9e26-25b094614656_1312x736.jpeg&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="Common Challenges in Operationalizing Models (and How to Overcome Them)" title="Common Challenges in Operationalizing Models (and How to Overcome Them)" srcset="https://substackcdn.com/image/fetch/$s_!12jb!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc2868bb0-037e-40e1-9e26-25b094614656_1312x736.jpeg 424w, https://substackcdn.com/image/fetch/$s_!12jb!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc2868bb0-037e-40e1-9e26-25b094614656_1312x736.jpeg 848w, https://substackcdn.com/image/fetch/$s_!12jb!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc2868bb0-037e-40e1-9e26-25b094614656_1312x736.jpeg 1272w, https://substackcdn.com/image/fetch/$s_!12jb!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc2868bb0-037e-40e1-9e26-25b094614656_1312x736.jpeg 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Image by Author | Ideogram.ai</figcaption></figure></div><p>Bringing a predictive model from the controlled environment of a prototype into the world of production is rarely a smooth journey. </p><p>While building a model in a notebook may take days or weeks, operationalizing it often exposes deeper issues beyond data science. These challenges can stall business progress and hinder opportunities that the company should have.</p><p>Many machine learning projects face common challenges that you should understand when deploying your model into production. That&#8217;s why this article will discuss typical challenges encountered in operationalizing models and the solutions to overcome them.</p><p>Curious about it? Let&#8217;s get into it!</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.nb-data.com/subscribe?&quot;,&quot;text&quot;:&quot;Subscribe now&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://www.nb-data.com/subscribe?"><span>Subscribe now</span></a></p><div><hr></div><h1>Sponsor Section</h1><p><strong>Packt</strong> is currently giving away a FREE E-Book for your learning:<br><br> &#8226; Learn Python Programming<br> &#8226; Mathematics of Machine Learning<br> &#8226; Mastering Power BI<br><br>All bundled with a FREE newsletter. Don&#8217;t miss them here: </p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://forms.office.com/e/RajX7LRHV9&quot;,&quot;text&quot;:&quot;PACKT FREE E-Book&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://forms.office.com/e/RajX7LRHV9"><span>PACKT FREE E-Book</span></a></p><div><hr></div><p></p><h1>1. Data Pipeline and Quality Issues</h1><p>The integrity of data pipelines is essential for the success of any predictive modelling system. In practice, many projects face performance issues after deployment, not because of flawed algorithms, but because the production data feeding those models differs from the data used during training.</p><p>For example, issues such as discrepancies in data structure, missing values, delayed updates, or unrecorded schema changes can lead to silent failures, which distort model outputs and undermine stakeholder trust.</p><p>To reduce these risks, here are a few things you can do:</p><ol><li><p><strong>Implement end-to-end data validation</strong><br>Perform quality checks at each stage of the pipeline, from ingestion to transformation and storage, to verify completeness, consistency, and validity.</p></li><li><p><strong>Use automated validation frameworks</strong><br>Automated data validation frameworks, such as&nbsp;<strong><a href="https://greatexpectations.io/">Great Expectations</a></strong>&nbsp;or&nbsp;<strong><a href="https://www.tensorflow.org/tfx/tutorials/data_validation/tfdv_basic">TensorFlow Data Validation,</a></strong>&nbsp;can help detect anomalies before they affect production components. </p></li><li><p><strong>Maintain data lineage and versioning</strong><br>Conduct regular data flow audits and maintain version histories to trace the origin and evolution of training features.</p></li><li><p><strong>Strengthen communication between teams</strong><br>Foster collaboration between data engineering and data science teams so upstream changes, such as new collection methods or revised definitions, are quickly addressed.</p></li><li><p><strong>Establish clear documentation and schema registries</strong><br>Maintain centralized schema definitions and data documentation to ensure consistency among sources, transformations, and models.</p></li><li><p><strong>Treat data as a managed asset</strong><br>Manage data with the same discipline as software assets. Stable, well-governed data pipelines build the foundation for reliable and scalable predictive systems.</p></li></ol><p>Ultimately, developing a quality data pipeline and resolving quality issues will establish a stable foundation on which predictive models can operate reliably and be scaled confidently.</p><div><hr></div><h1>2. Reproducibility and Version Control</h1><p>Reproducibility is a key principle in implementing predictive models. It guarantees that each step can be repeated with the same results when using the same inputs and environment. </p><p>This principle is often violated in many companies when experiments are conducted without standard tracking datasets, feature changes, hyperparameters, or library versions. These oversights usually hinder model validation, making it harder to identify the causes of performance differences between development and production environments.</p><p>To help mitigate this problem, you can use the follow tips:</p><ul><li><p><strong>Standardize experiment tracking</strong><br>C traceable records, use structured logging of data sources, parameters, and model outcomes. To manage your experiments, you can use&nbsp;<strong><a href="https://mlflow.org/">MLflow</a></strong>&nbsp;and&nbsp;<strong><a href="https://wandb.ai/site/">Weights &amp; Biases</a></strong>.</p></li><li><p><strong>Version both code and data</strong><br>Maintain all scripts and datasets under version control to ensure reproducibility of training conditions.<br><em>Common tools:</em> Git for code, DVC or LakeFS for dataset versioning.</p></li><li><p><strong>Ensure environmental consistency</strong><br>Use containerization to guarantee that models execute within the same software environment across development and production. You can use <a href="https://www.docker.com/">Docker</a> to help the consistency proces.</p></li><li><p><strong>Adopt governance standards</strong><br>Document experiment results, model versions, and approval processes. Assign clear ownership for maintaining reproducibility practices.</p></li></ul><p>In the end, establishing reproducibility and version control is both a technical safeguard and a governance requirement. These practices strengthen transparency and accountability and help ensure that predictive systems remain reliable.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.nb-data.com/p/common-challenges-in-operationalizing?utm_source=substack&utm_medium=email&utm_content=share&action=share&quot;,&quot;text&quot;:&quot;Share&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://www.nb-data.com/p/common-challenges-in-operationalizing?utm_source=substack&utm_medium=email&utm_content=share&action=share"><span>Share</span></a></p><div><hr></div><h1>3. Scalability and Performance Constraints</h1><p>A predictive model that performs well in experimental settings may not sustain the same level of efficiency once deployed in production. </p><p>The shift from offline testing to real-time or large-scale settings often reveals hidden inefficiencies in computation, memory management, and data throughput. For example, models that perform within seconds on small samples during development can become problematic when required to process millions of data points within milliseconds.</p><p>To address these challenges, here are a few tips to follow:</p><ol><li><p><strong>Design for scalability from the outset</strong><br>Anticipate production requirements early to avoid structural limitations that are difficult to resolve later.</p></li><li><p><strong>Profile performance early</strong><br>Use profiling tools to detect bottlenecks in training and inference before deployment.</p></li><li><p><strong>Simplify complex models</strong><br>Reduce computational overhead through pruning, quantization, or other optimization techniques without compromising accuracy.</p></li><li><p><strong>Match infrastructure to the use case</strong><br>Allow a distributed system for real-time tasks and parallelized pipelines for batch processing.</p></li><li><p><strong>Test under realistic conditions</strong><br>Validate responsiveness and stability with production-scale data and workloads.</p></li><li><p><strong>Monitor and optimize continuously</strong><br>Track latency, throughput, and resource utilization to maintain consistent performance as data and traffic increase.</p></li></ol><p>Achieving scalability is a matter of increasing computational power and designing systems that balance all the essential components. It&#8217;s an important issues that need to be consider everytime we talking about production.</p><div><hr></div><h2>4. Model Degradation and Concept Drift</h2><p>Predictive performance can decline after deployment because the data-generating process changes over time. </p><p>Two patterns are most common: </p><ul><li><p>Data drift occurs when the distribution of input features shifts compared with the training data, and </p></li><li><p>Concept drift occurs when the relationship between inputs and the target outcome changes. </p></li></ul><p>Both effects diminish the validity of learned parameters and can result in unstable or biased decisions if not managed.</p><p>To mitigate them, here are a few tips you can follow:</p><ol><li><p><strong>Define reference baselines</strong><br>Preserve training snapshots, feature statistics, and performance metrics for comparison.</p></li><li><p><strong>Monitor continuously</strong><br>Track input and prediction distributions, calibration, and task metrics.<br>You can use tools such as <a href="https://www.evidentlyai.com/">Evidently AI</a> or <a href="https://docs.seldon.io/projects/alibi-detect/en/latest/#">Alibi Detect</a>. You can also use major cloud monitors (e.g., SageMaker Model Monitor, Vertex AI Model Monitoring, Azure ML Data Drift).</p></li><li><p><strong>Alert and diagnose</strong><br>Establish thresholds, then localize issues to specific features, segments, and time windows.</p></li><li><p><strong>Retrain and validate</strong><br>Use recent data for periodic or event-driven retraining, and apply windowed training or incremental learning. Validate with backtesting and fresh holdouts.</p></li><li><p><strong>Control deployment risk</strong><br>Release updates through shadow, canary, or A/B testing; ensure a clear rollback plan.</p></li><li><p><strong>Harden data pipelines</strong><br>Enforce schema validation, maintain unit consistency, control categories, and ensure data freshness SLAs.</p></li><li><p><strong>Document governance</strong><br>Log drift events, criteria, model versions, approvals, and ownership for monitoring and response.</p></li></ol><p>Do not sleep on the model degradation and concept drift for a reliable production system.</p><div><hr></div><p>Like this article? Don&#8217;t forget to share and comment.</p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.nb-data.com/p/common-challenges-in-operationalizing?utm_source=substack&utm_medium=email&utm_content=share&action=share&quot;,&quot;text&quot;:&quot;Share&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://www.nb-data.com/p/common-challenges-in-operationalizing?utm_source=substack&utm_medium=email&utm_content=share&action=share"><span>Share</span></a></p><p class="button-wrapper" data-attrs="{&quot;url&quot;:&quot;https://www.nb-data.com/p/common-challenges-in-operationalizing/comments&quot;,&quot;text&quot;:&quot;Leave a comment&quot;,&quot;action&quot;:null,&quot;class&quot;:null}" data-component-name="ButtonCreateButton"><a class="button primary" href="https://www.nb-data.com/p/common-challenges-in-operationalizing/comments"><span>Leave a comment</span></a></p>]]></content:encoded></item></channel></rss>