<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Kevin Meneses González</title>
    <description>The latest articles on DEV Community by Kevin Meneses González (@kevin_menesesgonzlez).</description>
    <link>https://dev.to/kevin_menesesgonzlez</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2412421%2F23a50b66-3e3e-4d13-8809-9bc0b03c0f40.png</url>
      <title>DEV Community: Kevin Meneses González</title>
      <link>https://dev.to/kevin_menesesgonzlez</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/kevin_menesesgonzlez"/>
    <language>en</language>
    <item>
      <title>21 Ways to Avoid Getting Blocked While Web Scraping</title>
      <dc:creator>Kevin Meneses González</dc:creator>
      <pubDate>Thu, 22 Oct 2026 17:23:36 +0000</pubDate>
      <link>https://dev.to/kevin_menesesgonzlez/21-ways-to-avoid-getting-blocked-while-web-scraping-4165</link>
      <guid>https://dev.to/kevin_menesesgonzlez/21-ways-to-avoid-getting-blocked-while-web-scraping-4165</guid>
      <description>&lt;p&gt;Your scraper doesn't fail because of your code.&lt;/p&gt;

&lt;p&gt;It fails because it screams "I'm a bot" on every single request.&lt;/p&gt;

&lt;p&gt;If you want to avoid getting blocked while web scraping, you need to understand one thing first: modern websites don't block scrapers, they block &lt;em&gt;signals&lt;/em&gt;. A missing header. An IP from a datacenter. A request every 200 milliseconds, perfectly timed, 24 hours a day.&lt;/p&gt;

&lt;p&gt;If you're:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;scraping prices or product data,&lt;/li&gt;
&lt;li&gt;building datasets for AI or RAG pipelines,&lt;/li&gt;
&lt;li&gt;or monitoring competitors at scale,&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;this guide is for you.&lt;/p&gt;

&lt;p&gt;Below are 21 techniques I use for web scraping without getting blocked, grouped into five blocks, each one with a short Python example you can copy.&lt;/p&gt;




&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Why you get blocked:&lt;/strong&gt; anti-bot systems score four layers of every request: IP, HTTP headers, behavior and browser fingerprint.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Quick wins (free):&lt;/strong&gt; real User-Agent, full browser headers, random delays, backoff on 429, caching, and hidden JSON APIs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Medium effort:&lt;/strong&gt; rotating proxies, residential IPs, geo-targeting and sticky sessions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hard part:&lt;/strong&gt; TLS fingerprints, headless browser detection and CAPTCHAs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Shortcut:&lt;/strong&gt; a web scraping API like &lt;a href="https://www.zenrows.com?fpr=kevin75" rel="noopener noreferrer"&gt;ZenRows&lt;/a&gt; handles proxies, fingerprints and anti-bot challenges in one request. &lt;code&gt;mode=auto&lt;/code&gt; escalates only when needed, and the free plan includes 5,000 credits/month.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Why Websites Block Web Scrapers
&lt;/h2&gt;

&lt;p&gt;Anti-bot systems like &lt;a href="https://developers.cloudflare.com/bots/" rel="noopener noreferrer"&gt;Cloudflare Bot Management&lt;/a&gt;, DataDome, Akamai or PerimeterX score every request. When the score crosses a threshold, you get a 403 Forbidden, a CAPTCHA, or worse: a 200 response with fake or empty data.&lt;/p&gt;

&lt;p&gt;They look at four things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Your IP&lt;/strong&gt;: reputation, type (datacenter vs residential), request volume&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Your HTTP identity&lt;/strong&gt;: User-Agent, headers, header order, cookies&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Your behavior&lt;/strong&gt;: speed, timing patterns, navigation path&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Your browser fingerprint&lt;/strong&gt;: TLS handshake, JavaScript execution, canvas, WebGL&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdiuno2flvj2duomq4xye.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fdiuno2flvj2duomq4xye.png" alt=" " width="720" height="346"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Fix one and ignore the others, and you still get blocked.&lt;/p&gt;

&lt;p&gt;That's the real problem. Blocking is multi-layered, so your strategy to avoid getting blocked has to be too.&lt;/p&gt;




&lt;h2&gt;
  
  
  Block 1: HTTP Headers and Identity (Techniques 1–5)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Use a real, rotating User-Agent
&lt;/h3&gt;

&lt;p&gt;The default &lt;code&gt;python-requests/2.x&lt;/code&gt; User-Agent is the fastest way to get banned. Use real browser strings and rotate them.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;random&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;

&lt;span class="n"&gt;USER_AGENTS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Mozilla/5.0 (Macintosh; Intel Mac OS X 14_6) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.6 Safari/605.1.15&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Mozilla/5.0 (X11; Linux x86_64; rv:131.0) Gecko/20100101 Firefox/131.0&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="n"&gt;headers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;User-Agent&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;choice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;USER_AGENTS&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;
&lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://httpbin.io/headers&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Keep the list updated. A Chrome version from three years ago is almost as suspicious as no User-Agent at all. 📖 &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/User-Agent" rel="noopener noreferrer"&gt;MDN: User-Agent header&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Send the full set of browser headers
&lt;/h3&gt;

&lt;p&gt;A real browser never sends only a User-Agent. It sends &lt;code&gt;Accept&lt;/code&gt;, &lt;code&gt;Accept-Language&lt;/code&gt;, &lt;code&gt;Accept-Encoding&lt;/code&gt;, &lt;code&gt;Sec-Fetch-*&lt;/code&gt; and more.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;headers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;User-Agent&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;USER_AGENTS&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Accept&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Accept-Language&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;en-US,en;q=0.9&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Accept-Encoding&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gzip, deflate, br&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Upgrade-Insecure-Requests&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Sec-Fetch-Dest&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;document&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Sec-Fetch-Mode&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;navigate&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Sec-Fetch-Site&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;none&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Tip: open &lt;a href="https://httpbin.io/headers" rel="noopener noreferrer"&gt;httpbin.io/headers&lt;/a&gt; in your browser, then call it from your script. Compare both outputs. Every difference is a signal.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Keep headers consistent with the User-Agent
&lt;/h3&gt;

&lt;p&gt;If your User-Agent says Firefox but you send Chrome-only headers like &lt;code&gt;sec-ch-ua&lt;/code&gt;, you've just flagged yourself. Every header set should match the browser it claims to be.&lt;/p&gt;

&lt;p&gt;A simple rule: build header &lt;em&gt;profiles&lt;/em&gt; (one per browser) and rotate the full profile, never individual headers. 📖 &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Sec-CH-UA" rel="noopener noreferrer"&gt;MDN: Sec-CH-UA client hints&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Set a realistic Referer
&lt;/h3&gt;

&lt;p&gt;Real users arrive from somewhere. Usually Google, or another page of the same site.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Referer&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://www.google.com/&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For deep pages, set the Referer to the category or listing page that links to them.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Persist cookies with a session
&lt;/h3&gt;

&lt;p&gt;Many sites set cookies on the first visit and expect them back. A scraper that never returns cookies looks like a thousand brand-new visitors.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;session&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Session&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://example.com/&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;             &lt;span class="c1"&gt;# gets cookies
&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://example.com/products&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# sends them back
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;📖 &lt;a href="https://requests.readthedocs.io/en/latest/user/advanced/#session-objects" rel="noopener noreferrer"&gt;Requests docs: Session objects&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Headers alone won't help you avoid getting blocked, though.&lt;/p&gt;

&lt;p&gt;If every request comes from the same IP, nothing else matters.&lt;/p&gt;




&lt;h2&gt;
  
  
  Block 2: Rotating Proxies and IPs (Techniques 6–9)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  6. Use rotating proxies
&lt;/h3&gt;

&lt;p&gt;Sending thousands of requests from one IP is the oldest red flag in web scraping. Use a proxy pool and rotate IPs.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;itertools&lt;/span&gt;

&lt;span class="n"&gt;PROXIES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;http://user:pass@proxy1.example.com:8000&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;http://user:pass@proxy2.example.com:8000&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;http://user:pass@proxy3.example.com:8000&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;proxy_pool&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;itertools&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cycle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PROXIES&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;urls&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;proxy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;proxy_pool&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;proxies&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;http&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;proxy&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;proxy&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;📖 &lt;a href="https://requests.readthedocs.io/en/latest/user/advanced/#proxies" rel="noopener noreferrer"&gt;Requests docs: Proxies&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  7. Use residential proxies for protected sites
&lt;/h3&gt;

&lt;p&gt;Datacenter IPs are cheap and fast, and anti-bot systems know every one of their ranges. Residential proxies use IPs from real ISPs, so they carry much higher trust.&lt;/p&gt;

&lt;p&gt;Use datacenter proxies for easy targets. Switch to residential proxies when you start seeing 403 Forbidden errors.&lt;/p&gt;

&lt;h3&gt;
  
  
  8. Match the IP's geolocation to the target
&lt;/h3&gt;

&lt;p&gt;Scraping a Spanish e-commerce site from a Singapore IP looks odd. It can also return different prices, currencies or content. Pick proxies from the country your target expects.&lt;/p&gt;

&lt;h3&gt;
  
  
  9. Use sticky sessions for multi-step flows
&lt;/h3&gt;

&lt;p&gt;Rotating proxies are great until you need to log in, paginate or keep a cart. Changing IP mid-session is a classic bot signal. Keep the same IP for the whole flow, then rotate.&lt;/p&gt;

&lt;p&gt;Managing a residential proxy pool, geo-targeting and sticky sessions yourself is a real project.&lt;/p&gt;

&lt;p&gt;This is where most people quietly give up.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Skip the proxy plumbing&lt;/strong&gt;&lt;br&gt;
ZenRows gives you rotating residential proxies (&lt;code&gt;premium_proxy&lt;/code&gt;), country targeting (&lt;code&gt;proxy_country&lt;/code&gt;) and sticky sessions (&lt;code&gt;session_id&lt;/code&gt;) as simple API parameters.&lt;br&gt;
&lt;strong&gt;→ &lt;a href="https://www.zenrows.com?fpr=kevin75" rel="noopener noreferrer"&gt;Try ZenRows for free&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Block 3: Scraping Behavior and Rate Limits (Techniques 10–14)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  10. Throttle your request rate
&lt;/h3&gt;

&lt;p&gt;Hammering a server is both rude and obvious. Limit concurrency and requests per second per domain.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;urls&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;1.5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  11. Add random delays
&lt;/h3&gt;

&lt;p&gt;Humans are irregular. Bots are perfectly regular. A fixed &lt;code&gt;sleep(1)&lt;/code&gt; is a fingerprint in itself.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;uniform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;1.2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mf"&gt;4.5&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  12. Back off on 429 and 503
&lt;/h3&gt;

&lt;p&gt;When the server says "slow down", listen. Retrying instantly just confirms you're a bot and gets your IP blocked faster.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;fetch_with_backoff&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;max_retries&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;attempt&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;max_retries&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;429&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;503&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;
        &lt;span class="n"&gt;wait&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Retry-After&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt; &lt;span class="n"&gt;attempt&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;wait&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;random&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;📖 &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/429" rel="noopener noreferrer"&gt;MDN: 429 Too Many Requests&lt;/a&gt; · &lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Retry-After" rel="noopener noreferrer"&gt;MDN: Retry-After&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  13. Respect robots.txt and crawl-delay
&lt;/h3&gt;

&lt;p&gt;Checking &lt;code&gt;robots.txt&lt;/code&gt; keeps you away from the traps (honeypot URLs) and the areas the site watches most closely. It's also the ethical baseline for any web scraping project.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;urllib.robotparser&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;RobotFileParser&lt;/span&gt;

&lt;span class="n"&gt;rp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;RobotFileParser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://example.com/robots.txt&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;rp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;can_fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://example.com/products&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;crawl_delay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;📖 &lt;a href="https://docs.python.org/3/library/urllib.robotparser.html" rel="noopener noreferrer"&gt;Python docs: urllib.robotparser&lt;/a&gt; · &lt;a href="https://developers.google.com/search/docs/crawling-indexing/robots/intro" rel="noopener noreferrer"&gt;Google: How robots.txt works&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  14. Scrape during off-peak hours
&lt;/h3&gt;

&lt;p&gt;Your traffic stands out less when it's spread across the day, and you put less load on the site at night in its local time zone. Big batch jobs belong there.&lt;/p&gt;

&lt;p&gt;Behavior is where most DIY scrapers improve the fastest.&lt;/p&gt;

&lt;p&gt;But the hardest anti-bot layer is still ahead.&lt;/p&gt;




&lt;h2&gt;
  
  
  Block 4: Browser Fingerprint and Anti-Bot Detection (Techniques 15–18)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  15. Use a headless browser with stealth patches
&lt;/h3&gt;

&lt;p&gt;Plain headless Chrome leaks signals like &lt;code&gt;navigator.webdriver = true&lt;/code&gt;. Use stealth patches to hide them.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# pip install playwright playwright-stealth
# playwright install chromium
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;playwright.sync_api&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;sync_playwright&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;playwright_stealth&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Stealth&lt;/span&gt;

&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nc"&gt;Stealth&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;use_sync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;sync_playwright&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;browser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chromium&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;launch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;headless&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;page&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;new_page&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;goto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://example.com&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;title&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="n"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;📖 &lt;a href="https://playwright.dev/python/docs/intro" rel="noopener noreferrer"&gt;Playwright for Python&lt;/a&gt; · &lt;a href="https://pypi.org/project/playwright-stealth/" rel="noopener noreferrer"&gt;playwright-stealth on PyPI&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  16. Fix your TLS fingerprint
&lt;/h3&gt;

&lt;p&gt;This one surprises people. Even with perfect headers, the TLS handshake from Python's &lt;code&gt;requests&lt;/code&gt; doesn't look like Chrome's. Anti-bot systems check it (JA3/JA4 fingerprints).&lt;/p&gt;

&lt;p&gt;&lt;code&gt;curl_cffi&lt;/code&gt; impersonates a real browser at the TLS level:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# pip install curl_cffi
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;curl_cffi&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;cffi_requests&lt;/span&gt;

&lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cffi_requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://example.com&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;impersonate&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;chrome&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;📖 &lt;a href="https://github.com/lexiforest/curl_cffi" rel="noopener noreferrer"&gt;curl_cffi on GitHub&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  17. Render JavaScript only when you need it
&lt;/h3&gt;

&lt;p&gt;Many sites load data with JavaScript, and some anti-bot challenges &lt;em&gt;require&lt;/em&gt; JS execution to pass. But a headless browser is slow and expensive. Use it for the pages that need it, not for everything.&lt;/p&gt;

&lt;h3&gt;
  
  
  18. Avoid CAPTCHAs instead of solving them
&lt;/h3&gt;

&lt;p&gt;If you're seeing CAPTCHAs, the anti-bot system already distrusts you. Solving services work, but they're slow and cost money per solve. Fixing the signals from techniques 1–17 is cheaper than solving the CAPTCHA they trigger.&lt;/p&gt;

&lt;p&gt;Stealth browsers and TLS impersonation work.&lt;/p&gt;

&lt;p&gt;Until the anti-bot vendor ships an update and you're back to debugging on a Friday night.&lt;/p&gt;




&lt;h2&gt;
  
  
  Block 5: Smart Scraping Architecture (Techniques 19–21)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  19. Look for hidden APIs (XHR) first
&lt;/h3&gt;

&lt;p&gt;Open DevTools → Network → Fetch/XHR and reload the page. Many sites load their data from a JSON endpoint. Calling it directly is faster, cleaner and lighter than parsing HTML.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://example.com/api/products?page=1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;📖 &lt;a href="https://developer.chrome.com/docs/devtools/network" rel="noopener noreferrer"&gt;Chrome DevTools: Inspect network activity&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  20. Cache and deduplicate requests
&lt;/h3&gt;

&lt;p&gt;The request you don't send can't get you blocked. Store responses, skip URLs you've already scraped and only refresh what changed.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# pip install requests-cache
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests_cache&lt;/span&gt;

&lt;span class="n"&gt;session&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests_cache&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;CachedSession&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;scrape_cache&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;expire_after&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;3600&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;📖 &lt;a href="https://requests-cache.readthedocs.io/" rel="noopener noreferrer"&gt;requests-cache docs&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  21. Use a web scraping API
&lt;/h3&gt;

&lt;p&gt;Techniques 1 to 18 are a full-time job: rotating proxies, header profiles, TLS impersonation, headless browsers, CAPTCHA handling, retries. And all of it breaks whenever the anti-bot vendors change their rules.&lt;/p&gt;

&lt;p&gt;A web scraping API bundles all of that behind one request.&lt;/p&gt;

&lt;p&gt;After testing several options, ZenRows is the one I reach for when a site fights back. Here's why:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Adaptive Stealth Mode (&lt;code&gt;mode=auto&lt;/code&gt;)&lt;/strong&gt; starts with the cheapest setup and escalates to JS rendering or premium proxies only when needed. You're billed only for the configuration that succeeds. &lt;a href="https://docs.zenrows.com/fetch/features/adaptive-stealth-mode" rel="noopener noreferrer"&gt;Docs&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Residential proxies with geo-targeting&lt;/strong&gt; via &lt;code&gt;premium_proxy=true&lt;/code&gt; and &lt;code&gt;proxy_country&lt;/code&gt; (e.g. &lt;code&gt;es&lt;/code&gt;, &lt;code&gt;us&lt;/code&gt;, &lt;code&gt;de&lt;/code&gt;). &lt;a href="https://docs.zenrows.com/fetch/features/premium-proxy" rel="noopener noreferrer"&gt;Docs&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;JS rendering&lt;/strong&gt; (&lt;code&gt;js_render=true&lt;/code&gt;) plus &lt;code&gt;wait_for&lt;/code&gt; and &lt;code&gt;js_instructions&lt;/code&gt; for dynamic pages.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sticky sessions&lt;/strong&gt; with &lt;code&gt;session_id&lt;/code&gt; (any number from 1 to 99999), which keeps the same IP for 10 minutes. &lt;a href="https://docs.zenrows.com/fetch/features/other#session-id" rel="noopener noreferrer"&gt;Docs&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Managed browser headers&lt;/strong&gt;: ZenRows sets User-Agent, &lt;code&gt;Sec-Ch-Ua&lt;/code&gt;, &lt;code&gt;Sec-Fetch-*&lt;/code&gt; and similar headers for you, so they always match the fingerprint.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Clean output&lt;/strong&gt;: CSS extraction to JSON, or Markdown (&lt;code&gt;response_type=markdown&lt;/code&gt;) for LLM pipelines.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fie4vnu22ilblvr659l8u.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fie4vnu22ilblvr659l8u.png" alt=" " width="720" height="388"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That single technique covers most of the other 20.&lt;/p&gt;




&lt;h2&gt;
  
  
  Full Example: Scrape Without Getting Blocked (DIY vs ZenRows)
&lt;/h2&gt;

&lt;p&gt;Let's use ZenRows' own test page, &lt;a href="https://www.scrapingcourse.com/antibot-challenge" rel="noopener noreferrer"&gt;scrapingcourse.com/antibot-challenge&lt;/a&gt;, which is protected by an anti-bot challenge. First, the DIY version:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;

&lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://www.scrapingcourse.com/antibot-challenge&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;User-Agent&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;USER_AGENTS&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]},&lt;/span&gt; &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Status:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Bypassed:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;You bypassed the Antibot challenge&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You'll get the challenge page instead of the content:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;Bypassed:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;False&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now with ZenRows and Adaptive Stealth Mode:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# pip install requests
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;

&lt;span class="n"&gt;params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;url&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://www.scrapingcourse.com/antibot-challenge&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;apikey&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;YOUR_ZENROWS_API_KEY&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mode&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;auto&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://api.zenrows.com/v1/&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;180&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Bypassed:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;You bypassed the Antibot challenge&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Cost (USD):&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;X-Request-Cost&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Bypassed: True
Cost (USD): 0.025
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No proxy list. No header profiles. No stealth patches.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;X-Request-Cost&lt;/code&gt; header shows what the request cost (0.001 USD = 1 credit), so you can see when the API had to escalate. Two details from the &lt;a href="https://docs.zenrows.com/universal-scraper-api/api-reference" rel="noopener noreferrer"&gt;API reference&lt;/a&gt;: use a client timeout of 180 seconds, because protected pages can take a while, and don't send &lt;code&gt;js_render&lt;/code&gt; or &lt;code&gt;premium_proxy&lt;/code&gt; together with &lt;code&gt;mode=auto&lt;/code&gt;. Auto mode manages those two for you.&lt;/p&gt;

&lt;h3&gt;
  
  
  Manual configuration: geo-targeted scraping with CSS extraction
&lt;/h3&gt;

&lt;p&gt;Need control instead of automation? Set the parameters yourself:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;url&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://www.scrapingcourse.com/ecommerce/&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;apikey&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;YOUR_ZENROWS_API_KEY&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;js_render&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;true&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;premium_proxy&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;true&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;proxy_country&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;es&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;wait_for&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;.product&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;css_extractor&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;names&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;: &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;.product-name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;, &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;prices&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;: &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;.price&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;, &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;links&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;: &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;a.woocommerce-LoopProduct-link @href&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://api.zenrows.com/v1/&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;180&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A few rules worth knowing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;wait_for&lt;/code&gt; (and &lt;code&gt;wait&lt;/code&gt;) only work with &lt;code&gt;js_render=true&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;proxy_country&lt;/code&gt; requires &lt;code&gt;premium_proxy=true&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;css_extractor&lt;/code&gt; returns JSON: one match comes back as a string, several as a list. Add &lt;code&gt;@href&lt;/code&gt; or &lt;code&gt;@src&lt;/code&gt; to grab attributes. &lt;a href="https://docs.zenrows.com/fetch/features/css-extractor" rel="noopener noreferrer"&gt;Docs&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Sending a Referer or cookies
&lt;/h3&gt;

&lt;p&gt;ZenRows manages browser headers, but you can still send your own &lt;code&gt;Referer&lt;/code&gt; or &lt;code&gt;Cookie&lt;/code&gt;. Turn on &lt;code&gt;custom_headers&lt;/code&gt; and pass them with the normal &lt;code&gt;headers=&lt;/code&gt; argument:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;url&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://httpbin.io/anything&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;apikey&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;YOUR_ZENROWS_API_KEY&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;custom_headers&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;true&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="n"&gt;headers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Referer&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://www.google.com/&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://api.zenrows.com/v1/&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;180&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Headers like &lt;code&gt;User-Agent&lt;/code&gt; or &lt;code&gt;Sec-Fetch-*&lt;/code&gt; are ignored here on purpose, so they never contradict the browser fingerprint. &lt;a href="https://docs.zenrows.com/fetch/features/headers" rel="noopener noreferrer"&gt;Docs&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Prefer an SDK?
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# pip install zenrows
&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;zenrows&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ZenRowsClient&lt;/span&gt;

&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ZenRowsClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;YOUR_ZENROWS_API_KEY&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://www.scrapingcourse.com/ecommerce/&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;js_render&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;premium_proxy&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;proxy_country&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;us&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;📖 &lt;a href="https://docs.zenrows.com/fetch/sdk/python/quickstart" rel="noopener noreferrer"&gt;ZenRows Python SDK quickstart&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;From here you can build:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;price monitoring dashboards,&lt;/li&gt;
&lt;li&gt;lead generation pipelines,&lt;/li&gt;
&lt;li&gt;datasets for AI agents and RAG systems.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  The 21 Techniques to Avoid Getting Blocked at a Glance
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;#&lt;/th&gt;
&lt;th&gt;Technique&lt;/th&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;DIY difficulty&lt;/th&gt;
&lt;th&gt;ZenRows&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Real, rotating User-Agent&lt;/td&gt;
&lt;td&gt;HTTP headers&lt;/td&gt;
&lt;td&gt;Easy&lt;/td&gt;
&lt;td&gt;✅ Managed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;Full browser headers&lt;/td&gt;
&lt;td&gt;HTTP headers&lt;/td&gt;
&lt;td&gt;Easy&lt;/td&gt;
&lt;td&gt;✅ Managed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;Headers consistent with UA&lt;/td&gt;
&lt;td&gt;HTTP headers&lt;/td&gt;
&lt;td&gt;Medium&lt;/td&gt;
&lt;td&gt;✅ Managed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;Realistic Referer&lt;/td&gt;
&lt;td&gt;HTTP headers&lt;/td&gt;
&lt;td&gt;Easy&lt;/td&gt;
&lt;td&gt;✅ &lt;code&gt;custom_headers&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;Cookies and sessions&lt;/td&gt;
&lt;td&gt;HTTP headers&lt;/td&gt;
&lt;td&gt;Easy&lt;/td&gt;
&lt;td&gt;Partial (send &lt;code&gt;Cookie&lt;/code&gt; via &lt;code&gt;custom_headers&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;Rotating proxies&lt;/td&gt;
&lt;td&gt;Proxies&lt;/td&gt;
&lt;td&gt;Medium&lt;/td&gt;
&lt;td&gt;✅ &lt;code&gt;premium_proxy&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;Residential proxies&lt;/td&gt;
&lt;td&gt;Proxies&lt;/td&gt;
&lt;td&gt;Hard&lt;/td&gt;
&lt;td&gt;✅ &lt;code&gt;premium_proxy&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;Geo-targeted IPs&lt;/td&gt;
&lt;td&gt;Proxies&lt;/td&gt;
&lt;td&gt;Medium&lt;/td&gt;
&lt;td&gt;✅ &lt;code&gt;proxy_country&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;Sticky sessions&lt;/td&gt;
&lt;td&gt;Proxies&lt;/td&gt;
&lt;td&gt;Medium&lt;/td&gt;
&lt;td&gt;✅ &lt;code&gt;session_id&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;Rate limiting&lt;/td&gt;
&lt;td&gt;Behavior&lt;/td&gt;
&lt;td&gt;Easy&lt;/td&gt;
&lt;td&gt;Your side&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;td&gt;Random delays&lt;/td&gt;
&lt;td&gt;Behavior&lt;/td&gt;
&lt;td&gt;Easy&lt;/td&gt;
&lt;td&gt;Your side&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;12&lt;/td&gt;
&lt;td&gt;Backoff on 429/503&lt;/td&gt;
&lt;td&gt;Behavior&lt;/td&gt;
&lt;td&gt;Easy&lt;/td&gt;
&lt;td&gt;Your side&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;13&lt;/td&gt;
&lt;td&gt;Respect robots.txt&lt;/td&gt;
&lt;td&gt;Behavior&lt;/td&gt;
&lt;td&gt;Easy&lt;/td&gt;
&lt;td&gt;Your side&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;14&lt;/td&gt;
&lt;td&gt;Off-peak scraping&lt;/td&gt;
&lt;td&gt;Behavior&lt;/td&gt;
&lt;td&gt;Easy&lt;/td&gt;
&lt;td&gt;Your side&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;15&lt;/td&gt;
&lt;td&gt;Stealth headless browser&lt;/td&gt;
&lt;td&gt;Fingerprint&lt;/td&gt;
&lt;td&gt;Hard&lt;/td&gt;
&lt;td&gt;✅ &lt;code&gt;js_render&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;td&gt;TLS fingerprint&lt;/td&gt;
&lt;td&gt;Fingerprint&lt;/td&gt;
&lt;td&gt;Hard&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;17&lt;/td&gt;
&lt;td&gt;Selective JS rendering&lt;/td&gt;
&lt;td&gt;Fingerprint&lt;/td&gt;
&lt;td&gt;Medium&lt;/td&gt;
&lt;td&gt;✅ &lt;code&gt;mode=auto&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;18&lt;/td&gt;
&lt;td&gt;Avoid CAPTCHAs&lt;/td&gt;
&lt;td&gt;Fingerprint&lt;/td&gt;
&lt;td&gt;Hard&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;19&lt;/td&gt;
&lt;td&gt;Hidden APIs (XHR)&lt;/td&gt;
&lt;td&gt;Architecture&lt;/td&gt;
&lt;td&gt;Medium&lt;/td&gt;
&lt;td&gt;Partial (&lt;code&gt;json_response&lt;/code&gt;, needs &lt;code&gt;js_render&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;20&lt;/td&gt;
&lt;td&gt;Caching&lt;/td&gt;
&lt;td&gt;Architecture&lt;/td&gt;
&lt;td&gt;Easy&lt;/td&gt;
&lt;td&gt;Your side&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;21&lt;/td&gt;
&lt;td&gt;Web scraping API&lt;/td&gt;
&lt;td&gt;Architecture&lt;/td&gt;
&lt;td&gt;Easy&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Key Takeaways
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Anti-bot systems score signals across four layers (IP, headers, behavior, fingerprint). Fixing only one won't stop you from getting blocked.&lt;/li&gt;
&lt;li&gt;Behavior and architecture (techniques 10–14, 19–20) are cheap wins you should always own.&lt;/li&gt;
&lt;li&gt;For rotating proxies, fingerprints and CAPTCHAs, a web scraping API like ZenRows saves weeks of maintenance.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  FAQs
&lt;/h2&gt;

&lt;p&gt;❓ &lt;strong&gt;How do I avoid getting blocked while web scraping?&lt;/strong&gt;&lt;br&gt;
✅ Combine several techniques: real browser headers, rotating residential proxies, random delays with backoff on 429 errors, and a TLS fingerprint that matches a real browser. If the site uses serious anti-bot protection, a web scraping API like ZenRows handles those layers for you.&lt;/p&gt;

&lt;p&gt;❓ &lt;strong&gt;Why do I get a 403 Forbidden error when web scraping?&lt;/strong&gt;&lt;br&gt;
✅ A 403 usually means the anti-bot system flagged your request. The most common causes are a default Python User-Agent, missing browser headers, a datacenter IP or a non-browser TLS fingerprint. Fix headers first, then move to residential proxies.&lt;/p&gt;

&lt;p&gt;❓ &lt;strong&gt;Is web scraping legal?&lt;/strong&gt;&lt;br&gt;
✅ Scraping publicly available data is generally legal in many jurisdictions, but it depends on the data, the site's terms and local laws (personal data under GDPR, for example). Avoid scraping behind logins without permission, respect robots.txt and check with a lawyer for commercial projects.&lt;/p&gt;

&lt;p&gt;❓ &lt;strong&gt;How many requests per second can I send without getting blocked?&lt;/strong&gt;&lt;br&gt;
✅ There's no universal number. Start around one request every 1–3 seconds per domain with random jitter, watch for 429 responses and adjust. Rotating proxies let you spread volume, but they don't make aggressive scraping invisible.&lt;/p&gt;

&lt;p&gt;❓ &lt;strong&gt;Do I need residential proxies for web scraping?&lt;/strong&gt;&lt;br&gt;
✅ Not always. Datacenter proxies work fine for many sites. You need residential proxies when the target uses serious anti-bot protection (Cloudflare, DataDome, Akamai) or blocks datacenter IP ranges.&lt;/p&gt;

&lt;p&gt;❓ &lt;strong&gt;Is there a free web scraping API?&lt;/strong&gt;&lt;br&gt;
✅ ZenRows has a free plan with 5,000 credits per month and no credit card required. A standard request costs 1 credit, JS rendering 5, premium proxies 10, and both together 25. &lt;a href="https://docs.zenrows.com/first-steps/pricing" rel="noopener noreferrer"&gt;See pricing&lt;/a&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Useful Resources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://docs.zenrows.com/universal-scraper-api/api-reference" rel="noopener noreferrer"&gt;ZenRows API reference&lt;/a&gt;: every parameter, response header and error code&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://docs.zenrows.com/academy/fetch/protected-sites" rel="noopener noreferrer"&gt;ZenRows: scraping protected sites&lt;/a&gt;: official recipe for anti-bot pages&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers" rel="noopener noreferrer"&gt;MDN: HTTP headers&lt;/a&gt;: what each browser header means&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://playwright.dev/python/docs/intro" rel="noopener noreferrer"&gt;Playwright for Python&lt;/a&gt;: headless browser automation&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/lexiforest/curl_cffi" rel="noopener noreferrer"&gt;curl_cffi&lt;/a&gt;: browser TLS impersonation for Python&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://www.scrapingcourse.com/" rel="noopener noreferrer"&gt;scrapingcourse.com&lt;/a&gt;: safe sandbox pages to practice web scraping&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;Avoiding blocks isn't about one clever trick.&lt;/p&gt;

&lt;p&gt;It's about not giving the anti-bot system a single reason to doubt you. You can build that yourself, layer by layer, or hand the hardest layers to a web scraping API and focus on the data.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Stop fighting anti-bot systems&lt;/strong&gt;&lt;br&gt;
Rotating residential proxies, JS rendering and anti-bot bypass in one request. 5,000 free credits per month, no card required.&lt;br&gt;
&lt;strong&gt;→ &lt;a href="https://www.zenrows.com?fpr=kevin75" rel="noopener noreferrer"&gt;Start scraping with ZenRows&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;More practical guides on Python, APIs and automation&lt;/strong&gt;&lt;br&gt;
Tutorials, tools and real workflows for developers.&lt;br&gt;
&lt;strong&gt;→ &lt;a href="https://kevinmeneses.com/en" rel="noopener noreferrer"&gt;Visit kevinmeneses.com&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;&lt;em&gt;Looking for technical content for your company? I can help · &lt;a href="https://www.linkedin.com/in/kevin-meneses-gonzalez/" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt; · &lt;a href="mailto:kevinmenesesgonzalez@gmail.com"&gt;kevinmenesesgonzalez@gmail.com&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;




</description>
      <category>web</category>
      <category>scraping</category>
      <category>api</category>
      <category>data</category>
    </item>
    <item>
      <title>Yahoo Finance Historical Data Download Is Now Paid: 7 Alternatives</title>
      <dc:creator>Kevin Meneses González</dc:creator>
      <pubDate>Tue, 13 Oct 2026 20:30:44 +0000</pubDate>
      <link>https://dev.to/kevin_menesesgonzlez/yahoo-finance-historical-data-download-is-now-paid-7-alternatives-3958</link>
      <guid>https://dev.to/kevin_menesesgonzlez/yahoo-finance-historical-data-download-is-now-paid-7-alternatives-3958</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Quick answer:&lt;/strong&gt; Yahoo Finance historical data is still free to &lt;em&gt;view&lt;/em&gt;, but downloading it as CSV now requires Yahoo Finance Gold ($479.40/year). The cheapest reliable replacement is an end-of-day data API: &lt;strong&gt;EODHD&lt;/strong&gt; ($19.99/month, 30+ years, CSV or JSON) if you need many tickers or a script. &lt;strong&gt;Tiingo&lt;/strong&gt;'s free tier works for personal research, and &lt;strong&gt;Nasdaq.com&lt;/strong&gt; or &lt;strong&gt;Stooq&lt;/strong&gt; cover a quick one-off download for free. If you only need a few prices, &lt;strong&gt;yfinance&lt;/strong&gt; still works, with rate limits and no guarantees.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;You click "Download" on Yahoo Finance.&lt;/p&gt;

&lt;p&gt;Nothing downloads.&lt;/p&gt;

&lt;p&gt;You get an upgrade screen instead.&lt;/p&gt;

&lt;p&gt;For years, Yahoo Finance was the default answer to "where do I get historical stock data?" Analysts used it for Excel models. Students used it for theses. Developers used it as a free backend.&lt;/p&gt;

&lt;p&gt;If you're:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;rebuilding a backtest that used to start with a Yahoo CSV,&lt;/li&gt;
&lt;li&gt;maintaining a spreadsheet that pulled prices every month,&lt;/li&gt;
&lt;li&gt;or running a Python script that suddenly returns empty DataFrames,&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;this matters.&lt;/p&gt;

&lt;h2&gt;
  
  
  What changed with Yahoo Finance historical data
&lt;/h2&gt;

&lt;p&gt;Yahoo's own help page is explicit: &lt;em&gt;"Downloading historical data is available with a Yahoo Finance Gold subscription."&lt;/em&gt; (&lt;a href="https://help.yahoo.com/kb/SLN2311.html" rel="noopener noreferrer"&gt;Yahoo Help&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;You can still open any ticker, go to &lt;strong&gt;Historical Data&lt;/strong&gt;, pick a range and read the table in the browser. The &lt;strong&gt;Download&lt;/strong&gt; button is the part behind the paywall.&lt;/p&gt;

&lt;p&gt;Here's what each Yahoo plan includes, from Yahoo's &lt;a href="https://finance.yahoo.com/about/plans/select-plan" rel="noopener noreferrer"&gt;plan page&lt;/a&gt;:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Plan&lt;/th&gt;
&lt;th&gt;Price (billed yearly)&lt;/th&gt;
&lt;th&gt;Historical data download&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Free&lt;/td&gt;
&lt;td&gt;$0&lt;/td&gt;
&lt;td&gt;No (view only)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bronze&lt;/td&gt;
&lt;td&gt;$95.40/year ($7.95/mo)&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Silver&lt;/td&gt;
&lt;td&gt;$239.40/year ($19.95/mo)&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Gold&lt;/td&gt;
&lt;td&gt;$479.40/year ($39.95/mo)&lt;/td&gt;
&lt;td&gt;Yes, CSV&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Two more limits apply even with Gold. Yahoo says the feature &lt;em&gt;"isn't available for all instruments due to data licensing restrictions,"&lt;/em&gt; and the download is still one ticker at a time, by hand.&lt;/p&gt;

&lt;p&gt;Paying $479 a year for a button is a lot.&lt;/p&gt;

&lt;p&gt;Especially when the button doesn't scale.&lt;/p&gt;

&lt;h2&gt;
  
  
  The real problem isn't the paywall
&lt;/h2&gt;

&lt;p&gt;Most people searching for this want a CSV.&lt;/p&gt;

&lt;p&gt;What they actually need is a &lt;strong&gt;repeatable source of clean daily prices&lt;/strong&gt;: same columns every time, adjusted for splits and dividends, available for hundreds of tickers, and not dependent on a website layout.&lt;/p&gt;

&lt;p&gt;A manual download from any website, free or paid, fails that test the moment you need more than a few tickers.&lt;/p&gt;

&lt;p&gt;So the question isn't "how do I get the Yahoo button back?"&lt;/p&gt;

&lt;p&gt;It's "what replaces it for my volume?"&lt;/p&gt;

&lt;h2&gt;
  
  
  Yahoo Finance alternatives at a glance
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Option&lt;/th&gt;
&lt;th&gt;Free tier&lt;/th&gt;
&lt;th&gt;Paid from&lt;/th&gt;
&lt;th&gt;History depth&lt;/th&gt;
&lt;th&gt;Access&lt;/th&gt;
&lt;th&gt;Main catch&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://eodhd.com/pricing" rel="noopener noreferrer"&gt;EODHD&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;20 calls/day, 1 year&lt;/td&gt;
&lt;td&gt;$19.99/mo ($199/yr)&lt;/td&gt;
&lt;td&gt;30+ years&lt;/td&gt;
&lt;td&gt;API (CSV/JSON), Excel &amp;amp; Sheets add-ons&lt;/td&gt;
&lt;td&gt;Free tier too small for real backtests&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://www.tiingo.com/about/pricing" rel="noopener noreferrer"&gt;Tiingo&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;1,000 req/day, 500 symbols/mo&lt;/td&gt;
&lt;td&gt;$30/mo&lt;/td&gt;
&lt;td&gt;30+ years EOD&lt;/td&gt;
&lt;td&gt;API&lt;/td&gt;
&lt;td&gt;License is internal/personal use only&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://www.alphavantage.co/premium/" rel="noopener noreferrer"&gt;Alpha Vantage&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;25 req/day&lt;/td&gt;
&lt;td&gt;$49.99/mo&lt;/td&gt;
&lt;td&gt;Varies by endpoint&lt;/td&gt;
&lt;td&gt;API&lt;/td&gt;
&lt;td&gt;25 calls/day goes fast; some functions are premium&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://site.financialmodelingprep.com/developer/docs/pricing" rel="noopener noreferrer"&gt;Financial Modeling Prep&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;250 calls/day&lt;/td&gt;
&lt;td&gt;$19/mo (annual)&lt;/td&gt;
&lt;td&gt;5 years on Basic and Starter&lt;/td&gt;
&lt;td&gt;API&lt;/td&gt;
&lt;td&gt;Short history on the cheaper plans&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/ranaroussi/yfinance" rel="noopener noreferrer"&gt;yfinance&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Free&lt;/td&gt;
&lt;td&gt;n/a&lt;/td&gt;
&lt;td&gt;Whatever Yahoo serves&lt;/td&gt;
&lt;td&gt;Python library&lt;/td&gt;
&lt;td&gt;Unofficial, rate-limited, personal use&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://www.nasdaq.com/market-activity/quotes/historical" rel="noopener noreferrer"&gt;Nasdaq.com&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Free&lt;/td&gt;
&lt;td&gt;n/a&lt;/td&gt;
&lt;td&gt;Up to 10 years&lt;/td&gt;
&lt;td&gt;Browser&lt;/td&gt;
&lt;td&gt;Manual, one ticker at a time&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://stooq.com/db/h/" rel="noopener noreferrer"&gt;Stooq&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Free&lt;/td&gt;
&lt;td&gt;n/a&lt;/td&gt;
&lt;td&gt;Long, varies by market&lt;/td&gt;
&lt;td&gt;Browser, bulk files&lt;/td&gt;
&lt;td&gt;Automated downloads unreliable in 2026&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Which type of replacement do you need?
&lt;/h2&gt;

&lt;p&gt;There isn't one best Yahoo Finance alternative. There's a best one for your volume.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;One ticker, once:&lt;/strong&gt; a free website (Nasdaq.com, Stooq) is enough.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A few tickers, regularly, personal project:&lt;/strong&gt; yfinance or a free API tier (Tiingo).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dozens to thousands of tickers, a backtest, or anything client-facing:&lt;/strong&gt; a paid end-of-day API with clear licensing.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Pick your row. Then read only the options that fit it.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. EODHD: best overall Yahoo Finance replacement for historical data
&lt;/h2&gt;

&lt;p&gt;EODHD (EOD Historical Data) is an API built around exactly what the Yahoo download used to give you: daily open, high, low, close, adjusted close and volume. It covers 150,000+ tickers across global exchanges, with 30+ years of history on paid plans.&lt;/p&gt;

&lt;p&gt;I use it in most of my fintech tutorials, so I'll show the exact code I run.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Key features&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;End-of-day prices with an &lt;code&gt;adjusted_close&lt;/code&gt; field (adjusted for splits and dividends)&lt;/li&gt;
&lt;li&gt;CSV or JSON output from the same endpoint (&lt;code&gt;fmt=csv&lt;/code&gt; or &lt;code&gt;fmt=json&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Delisted tickers keep their history, which matters for backtests&lt;/li&gt;
&lt;li&gt;Excel and Google Sheets add-ons for non-coders&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Replicate the Yahoo CSV in Python&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The script below returns the same columns as the old Yahoo download, in the same order. The &lt;code&gt;demo&lt;/code&gt; key works for AAPL.US, TSLA.US, AMZN.US and VTI.US, so you can run it before signing up.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;requests pandas
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;pandas&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;

&lt;span class="n"&gt;API_TOKEN&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;EODHD_API_TOKEN&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;demo&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;yahoo_style_history&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ticker&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;end&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DataFrame&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://eodhd.com/api/eod/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;ticker&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;api_token&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;API_TOKEN&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;from&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;to&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;end&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;period&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;d&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;fmt&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;json&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;raise_for_status&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;DataFrame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rename&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;columns&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;date&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Date&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;open&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Open&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;high&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;High&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;low&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Low&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;close&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Close&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;adjusted_close&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Adj Close&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;volume&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Volume&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Date&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Open&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;High&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Low&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Close&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Adj Close&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Volume&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;

&lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;yahoo_style_history&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;AAPL.US&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2026-10-05&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2026-10-09&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_csv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;AAPL.csv&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Output (real data, pulled October 13, 2026):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csvs"&gt;&lt;code&gt;      &lt;span class="k"&gt;Date&lt;/span&gt;   &lt;span class="k"&gt;Open&lt;/span&gt;   &lt;span class="k"&gt;High&lt;/span&gt;    &lt;span class="k"&gt;Low&lt;/span&gt;  &lt;span class="k"&gt;Close&lt;/span&gt;  &lt;span class="k"&gt;Adj&lt;/span&gt; &lt;span class="k"&gt;Close&lt;/span&gt;   &lt;span class="k"&gt;Volume&lt;/span&gt;
&lt;span class="ld"&gt;2026-10-05&lt;/span&gt; &lt;span class="mf"&gt;332.82&lt;/span&gt; &lt;span class="mf"&gt;336.21&lt;/span&gt; &lt;span class="mf"&gt;331.65&lt;/span&gt; &lt;span class="mf"&gt;332.89&lt;/span&gt;     &lt;span class="mf"&gt;332.89&lt;/span&gt; &lt;span class="mf"&gt;34400900&lt;/span&gt;
&lt;span class="ld"&gt;2026-10-06&lt;/span&gt; &lt;span class="mf"&gt;332.28&lt;/span&gt; &lt;span class="mf"&gt;334.38&lt;/span&gt; &lt;span class="mf"&gt;330.62&lt;/span&gt; &lt;span class="mf"&gt;333.63&lt;/span&gt;     &lt;span class="mf"&gt;333.63&lt;/span&gt; &lt;span class="mf"&gt;30449000&lt;/span&gt;
&lt;span class="ld"&gt;2026-10-07&lt;/span&gt; &lt;span class="mf"&gt;336.96&lt;/span&gt; &lt;span class="mf"&gt;338.67&lt;/span&gt; &lt;span class="mf"&gt;332.78&lt;/span&gt; &lt;span class="mf"&gt;336.67&lt;/span&gt;     &lt;span class="mf"&gt;336.67&lt;/span&gt; &lt;span class="mf"&gt;34147900&lt;/span&gt;
&lt;span class="ld"&gt;2026-10-08&lt;/span&gt; &lt;span class="mf"&gt;336.82&lt;/span&gt; &lt;span class="mf"&gt;341.57&lt;/span&gt; &lt;span class="mf"&gt;335.90&lt;/span&gt; &lt;span class="mf"&gt;340.42&lt;/span&gt;     &lt;span class="mf"&gt;340.42&lt;/span&gt; &lt;span class="mf"&gt;35332400&lt;/span&gt;
&lt;span class="ld"&gt;2026-10-09&lt;/span&gt; &lt;span class="mf"&gt;331.70&lt;/span&gt; &lt;span class="mf"&gt;338.61&lt;/span&gt; &lt;span class="mf"&gt;330.70&lt;/span&gt; &lt;span class="mf"&gt;336.64&lt;/span&gt;     &lt;span class="mf"&gt;336.64&lt;/span&gt; &lt;span class="mf"&gt;37881200&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Same columns. Same format. Your old Excel model or pandas code reads it without changes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;History depth check&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The same call with &lt;code&gt;start="1980-12-01"&lt;/code&gt; returns Apple's first trading day, December 12, 1980 (close 28.74, adjusted close 0.0981). The adjusted column already accounts for every split since then.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Delisted tickers&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Twitter (TWTR.US) was delisted in October 2022. Its history is still there:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csvs"&gt;&lt;code&gt;      &lt;span class="k"&gt;date&lt;/span&gt;  &lt;span class="k"&gt;close&lt;/span&gt;    &lt;span class="k"&gt;volume&lt;/span&gt;
&lt;span class="ld"&gt;2022-10-24&lt;/span&gt;  &lt;span class="mf"&gt;51.52&lt;/span&gt;  &lt;span class="mf"&gt;24296045&lt;/span&gt;
&lt;span class="ld"&gt;2022-10-25&lt;/span&gt;  &lt;span class="mf"&gt;52.78&lt;/span&gt;  &lt;span class="mf"&gt;37489316&lt;/span&gt;
&lt;span class="ld"&gt;2022-10-26&lt;/span&gt;  &lt;span class="mf"&gt;53.35&lt;/span&gt;  &lt;span class="mf"&gt;29698456&lt;/span&gt;
&lt;span class="ld"&gt;2022-10-27&lt;/span&gt;  &lt;span class="mf"&gt;53.70&lt;/span&gt; &lt;span class="mf"&gt;198415408&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If your backtest only uses companies that still exist, it's biased toward the winners. Keeping delisted tickers fixes that.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Download many tickers at once&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;tickers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;AAPL.US&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;TSLA.US&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;AMZN.US&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;VTI.US&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;tickers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;yahoo_style_history&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2016-01-01&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2026-10-09&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;to_csv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;.csv&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Swap the list for your own universe once you're on a paid key.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pricing (verified October 2026)&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Free: 20 API calls/day, 1 year of history&lt;/li&gt;
&lt;li&gt;EOD Historical All-World: $19.99/month or $199/year, 100,000 calls/day, 30+ years&lt;/li&gt;
&lt;li&gt;EOD + Intraday: $29.99/month&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That's $199 a year against $479.40 for Yahoo Gold: about 58% less. And you get an API instead of a button.&lt;/p&gt;

&lt;p&gt;👉 &lt;a href="https://eodhd.com/?via=kmg&amp;amp;ref1=Meneses&amp;amp;utm_source=medium&amp;amp;utm_medium=post&amp;amp;utm_campaign=yahoo-finance-historical-data-download-alternatives&amp;amp;utm_content=Meneses" rel="noopener noreferrer"&gt;Try EODHD here&lt;/a&gt;. Code &lt;strong&gt;KEVIN10&lt;/strong&gt; gives 10% off.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What I liked&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The CSV/JSON switch is a single parameter.&lt;/li&gt;
&lt;li&gt;Delisted tickers and full split/dividend adjustment out of the box.&lt;/li&gt;
&lt;li&gt;Annual plan is cheaper than any Yahoo tier that includes downloads.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Where it falls short&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The free tier (20 calls/day, 1 year) is for testing, not for real research.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;adjusted_close&lt;/code&gt; is recalculated when new dividends arrive, so don't use it as a fixed key in a database.&lt;/li&gt;
&lt;li&gt;Fundamentals are a separate, more expensive plan.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; anyone replacing Yahoo downloads at scale, from Excel analysts to Python backtests.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Skip if:&lt;/strong&gt; you need fewer than five tickers, once.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Tiingo: best free tier for personal research
&lt;/h2&gt;

&lt;p&gt;Tiingo gives you 30+ years of end-of-day prices on its free Starter plan, which is generous by any standard.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pricing (verified October 2026)&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Free: 50 requests/hour, 1,000/day, 500 unique symbols/month&lt;/li&gt;
&lt;li&gt;Power: $30/month or $300/year for individuals&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;What I liked&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Deep history on the free plan.&lt;/li&gt;
&lt;li&gt;500 symbols a month covers most personal portfolios.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Where it falls short&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Both tiers are licensed for &lt;em&gt;"Internal Use Only"&lt;/em&gt;: no displaying or sharing the data. Internal commercial use costs $50/month, and redistribution is priced separately.&lt;/li&gt;
&lt;li&gt;50 requests per hour slows down bulk downloads.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; personal backtests and study projects.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Skip if:&lt;/strong&gt; you're building something other people will see.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Alpha Vantage: best known free API, smallest daily quota
&lt;/h2&gt;

&lt;p&gt;Alpha Vantage is often the first API developers try. It covers time series, technical indicators, fundamentals and FX.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pricing (verified October 2026)&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Free: 25 API requests/day&lt;/li&gt;
&lt;li&gt;Premium: from $49.99/month (75 requests/minute)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;What I liked&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Broad catalog beyond prices.&lt;/li&gt;
&lt;li&gt;Plenty of community examples.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Where it falls short&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;25 requests a day means 25 tickers a day, at best.&lt;/li&gt;
&lt;li&gt;Some functions are premium-only, so check each endpoint before you build on it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; small experiments and learning.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Skip if:&lt;/strong&gt; you need more than a handful of tickers per day.&lt;/p&gt;

&lt;p&gt;I compared Alpha Vantage with six other providers in &lt;a href="https://medium.com/coinmonks/the-7-best-financial-apis-for-investors-and-developers-in-2025-in-depth-analysis-and-comparison-adbc22024f68" rel="noopener noreferrer"&gt;this comparison of financial APIs&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Financial Modeling Prep: generous free calls, shorter history
&lt;/h2&gt;

&lt;p&gt;FMP has a large API catalog, especially for fundamentals and financial statements.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pricing (verified October 2026)&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Basic (free): 250 calls/day, 5 years of EOD history&lt;/li&gt;
&lt;li&gt;Starter: $19/month billed annually, also 5 years&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;What I liked&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;250 free calls a day is ten times Alpha Vantage.&lt;/li&gt;
&lt;li&gt;Strong fundamentals coverage.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Where it falls short&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;5 years of history on the cheaper plans. Long-horizon backtests need a higher tier.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; fundamentals-heavy projects with recent price history.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Skip if:&lt;/strong&gt; you need decades of prices on a small budget.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. yfinance: free, until it isn't
&lt;/h2&gt;

&lt;p&gt;yfinance is a Python library that reads Yahoo's data. Many people still use it, and for a few tickers it often works.&lt;/p&gt;

&lt;p&gt;But know what you're relying on. The project's own README says it is &lt;em&gt;"not affiliated, endorsed, or vetted by Yahoo, Inc."&lt;/em&gt; and that the data is &lt;em&gt;"intended for personal use only."&lt;/em&gt; (&lt;a href="https://github.com/ranaroussi/yfinance" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;)&lt;/p&gt;

&lt;p&gt;In 2025, users reported &lt;code&gt;YFRateLimitError('Too Many Requests. Rate limited. Try after a while.')&lt;/code&gt; (&lt;a href="https://github.com/ranaroussi/yfinance/issues/2422" rel="noopener noreferrer"&gt;issue #2422&lt;/a&gt;) and failed historical requests that they linked to the new paywall (&lt;a href="https://github.com/ranaroussi/yfinance/issues/2340" rel="noopener noreferrer"&gt;issue #2340&lt;/a&gt;). Fixes usually meant upgrading the library or changing how requests are sent.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What I liked&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Free and two lines of code.&lt;/li&gt;
&lt;li&gt;Same data you saw on the Yahoo website.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Where it falls short&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No SLA. A Yahoo change can break your pipeline overnight.&lt;/li&gt;
&lt;li&gt;Rate limits hit hardest when you loop over many tickers.&lt;/li&gt;
&lt;li&gt;Personal use only.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; quick, personal checks.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Skip if:&lt;/strong&gt; anything depends on the data arriving tomorrow.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Nasdaq.com: free and manual
&lt;/h2&gt;

&lt;p&gt;Nasdaq's &lt;a href="https://www.nasdaq.com/market-activity/quotes/historical" rel="noopener noreferrer"&gt;historical quotes page&lt;/a&gt; shows &lt;em&gt;"up to 10 years of daily historical stock prices and volumes for each stock."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What I liked&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Free, official exchange source.&lt;/li&gt;
&lt;li&gt;Enough history for most one-off charts.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Where it falls short&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;10 years maximum.&lt;/li&gt;
&lt;li&gt;Manual, one ticker at a time. No API.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; a single chart or a quick sanity check.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Skip if:&lt;/strong&gt; you need more than 10 years, or more than a few tickers.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Stooq: free bulk files, fragile automation
&lt;/h2&gt;

&lt;p&gt;Stooq offers free historical files for many markets, including bulk daily packages.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What I liked&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Free bulk downloads, no account needed for manual use.&lt;/li&gt;
&lt;li&gt;Long histories for many markets.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Where it falls short&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Automated access got shaky in 2026. Users of portfolio software reported empty responses from Stooq's historical servers starting in April 2026, while manual CSV downloads still worked (&lt;a href="https://fundmanagersoftware.com/forum/viewtopic.php?p=28752" rel="noopener noreferrer"&gt;forum thread&lt;/a&gt;).&lt;/li&gt;
&lt;li&gt;Licensing terms for reuse aren't clear, so treat it as personal research data.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; a free bulk snapshot for personal research.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Skip if:&lt;/strong&gt; your workflow depends on scheduled downloads.&lt;/p&gt;

&lt;h2&gt;
  
  
  Best Yahoo Finance alternative by use case
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Use case&lt;/th&gt;
&lt;th&gt;Pick&lt;/th&gt;
&lt;th&gt;Why&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Excel model with monthly price updates&lt;/td&gt;
&lt;td&gt;EODHD (Excel add-on)&lt;/td&gt;
&lt;td&gt;Same columns as Yahoo, no manual downloads&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Backtest over 10+ years and hundreds of tickers&lt;/td&gt;
&lt;td&gt;EODHD&lt;/td&gt;
&lt;td&gt;30+ years, delisted tickers, 100k calls/day&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Personal research, under 500 symbols&lt;/td&gt;
&lt;td&gt;Tiingo free&lt;/td&gt;
&lt;td&gt;30+ years at $0, personal-use license&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fundamentals plus recent prices&lt;/td&gt;
&lt;td&gt;Financial Modeling Prep&lt;/td&gt;
&lt;td&gt;Large fundamentals catalog, 250 free calls&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;One chart, one time&lt;/td&gt;
&lt;td&gt;Nasdaq.com&lt;/td&gt;
&lt;td&gt;Free, 10 years, no signup&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Quick Python check on a few tickers&lt;/td&gt;
&lt;td&gt;yfinance&lt;/td&gt;
&lt;td&gt;Free, but expect rate limits&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Can you still get Yahoo Finance historical data for free?
&lt;/h2&gt;

&lt;p&gt;Partly. You can view the full historical table on any quote page and copy it into a spreadsheet by hand. For long ranges that means scrolling through thousands of rows, and Yahoo's data is meant for personal use.&lt;/p&gt;

&lt;p&gt;It works for one ticker.&lt;/p&gt;

&lt;p&gt;It doesn't work as a system.&lt;/p&gt;

&lt;p&gt;If you're building anything bigger, I covered the full workflow for pulling and storing price history in &lt;a href="https://medium.com/codex/api-for-historical-stock-data-a-practical-guide-for-developers-140dc3b9f802" rel="noopener noreferrer"&gt;API for Historical Stock Data: A Practical Guide&lt;/a&gt;. For options data, see &lt;a href="https://medium.com/codex/i-pulled-eodhds-full-options-history-with-python-here-s-what-i-learned-8e19fbe8a951" rel="noopener noreferrer"&gt;I Pulled EODHD's Full Options History With Python&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Is Yahoo Finance historical data free?&lt;/strong&gt;&lt;br&gt;
Viewing it is free. Downloading it as CSV requires Yahoo Finance Gold, which costs $479.40 per year billed annually.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why can't I download historical data from Yahoo Finance?&lt;/strong&gt;&lt;br&gt;
Yahoo moved the Download button to the Gold plan. Some instruments can't be downloaded even with Gold because of data licensing restrictions.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;How much is Yahoo Finance Gold?&lt;/strong&gt;&lt;br&gt;
$39.95 per month billed yearly, or $479.40 per year (October 2026). Bronze and Silver don't include downloads.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Does yfinance still work in 2026?&lt;/strong&gt;&lt;br&gt;
Often, yes, for small volumes. It's unofficial, intended for personal use, and has had rate-limit errors (&lt;code&gt;YFRateLimitError&lt;/code&gt;) since 2025.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What is the best free alternative to Yahoo Finance for historical data?&lt;/strong&gt;&lt;br&gt;
Tiingo's free plan offers 30+ years of daily prices for up to 500 symbols a month, for personal use. For a single quick download, Nasdaq.com gives up to 10 years.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What is the cheapest paid alternative to Yahoo Finance Gold for downloads?&lt;/strong&gt;&lt;br&gt;
EODHD's end-of-day plan costs $199 per year (or $19.99/month) for 30+ years of global data via API, CSV, Excel or Google Sheets.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can I use these alternatives in Excel or Google Sheets?&lt;/strong&gt;&lt;br&gt;
Yes. EODHD offers Excel and Google Sheets add-ons, and every API above returns CSV or JSON that Excel can import.&lt;/p&gt;

&lt;h2&gt;
  
  
  Verdict
&lt;/h2&gt;

&lt;p&gt;Yahoo didn't remove historical data. It removed the free button.&lt;/p&gt;

&lt;p&gt;For a one-off, use Nasdaq.com. For personal research, use Tiingo's free tier. For anything you'll run every week, or show to someone else, use an end-of-day API like EODHD at less than half the price of Yahoo Gold.&lt;/p&gt;

&lt;p&gt;👉 &lt;a href="https://eodhd.com/?via=kmg&amp;amp;ref1=Meneses&amp;amp;utm_source=medium&amp;amp;utm_medium=post&amp;amp;utm_campaign=yahoo-finance-historical-data-download-alternatives&amp;amp;utm_content=Meneses" rel="noopener noreferrer"&gt;Get EODHD here&lt;/a&gt; and use &lt;strong&gt;KEVIN10&lt;/strong&gt; for 10% off.&lt;/p&gt;

&lt;p&gt;The data was never the hard part.&lt;/p&gt;

&lt;p&gt;Getting it the same way every time is.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;If you're a software or API company looking to explain your product through high-quality educational content (not marketing fluff), feel free to connect with me on &lt;a href="https://www.linkedin.com/in/kevin-meneses-gonzalez/" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>api</category>
      <category>stock</category>
      <category>data</category>
      <category>python</category>
    </item>
    <item>
      <title>7 Best Email APIs for Developers in 2026 (Compared)</title>
      <dc:creator>Kevin Meneses González</dc:creator>
      <pubDate>Tue, 06 Oct 2026 10:47:01 +0000</pubDate>
      <link>https://dev.to/kevin_menesesgonzlez/7-best-email-apis-for-developers-in-2026-compared-37jj</link>
      <guid>https://dev.to/kevin_menesesgonzlez/7-best-email-apis-for-developers-in-2026-compared-37jj</guid>
      <description>&lt;p&gt;Most developers pick an email API once and never think about it again.&lt;/p&gt;

&lt;p&gt;Until a password reset lands in spam. Or the free tier disappears. Or the dashboard keeps logs for exactly one day, right when you need to debug a customer complaint.&lt;/p&gt;

&lt;p&gt;If you're:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;shipping a SaaS that sends login codes and receipts,&lt;/li&gt;
&lt;li&gt;building an AI agent that needs to send or read email,&lt;/li&gt;
&lt;li&gt;or migrating away from a provider that changed its pricing,&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;this matters.&lt;/p&gt;

&lt;p&gt;Choosing the best email API for developers is not about who has the biggest feature list. It's about how fast you go from &lt;code&gt;pip install&lt;/code&gt; to a delivered email, and how much the tool gets in your way after that.&lt;/p&gt;

&lt;p&gt;I compared seven options: the established names developers already know (&lt;strong&gt;Resend, Postmark, Mailgun, SendGrid&lt;/strong&gt;) and three smaller, newer players worth watching (&lt;strong&gt;Plunk, Lettermint, Sendkit&lt;/strong&gt;). Here's what each one is actually good at.&lt;/p&gt;

&lt;h2&gt;
  
  
  The email API market changed in 2025
&lt;/h2&gt;

&lt;p&gt;For years the default answer was simple: use SendGrid, it's free to start.&lt;/p&gt;

&lt;p&gt;That's no longer true.&lt;/p&gt;

&lt;p&gt;In 2025 SendGrid retired its permanent free plan. New accounts now get a time-limited trial, and after that you pay. That single change pushed a lot of indie developers and startups to look for SendGrid alternatives.&lt;/p&gt;

&lt;p&gt;At the same time, three things happened:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;AI coding tools made "time to first email" a real competitive factor&lt;/li&gt;
&lt;li&gt;Agents started needing email as a tool, not just apps&lt;/li&gt;
&lt;li&gt;A new wave of small, developer-first providers raised the bar on docs, SDKs and pricing&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The old question was "which provider has the best deliverability?"&lt;/p&gt;

&lt;p&gt;The new question is "which one fits the way I build software today?"&lt;/p&gt;

&lt;h2&gt;
  
  
  What actually matters in an email API
&lt;/h2&gt;

&lt;p&gt;Before the list, here's the scoring lens I used. No marketing checklist, just the things that hurt in production:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Time to first email.&lt;/strong&gt; Minutes from signup to an email in your inbox.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SDK quality.&lt;/strong&gt; Is the Python/Node SDK idiomatic, typed and maintained?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Free tier you can actually build on.&lt;/strong&gt; Not a trial that expires mid-project.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Debuggability.&lt;/strong&gt; Log retention, webhooks, clear error messages.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pricing at 50k emails/month.&lt;/strong&gt; Where most growing products land.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Transactional email is infrastructure.&lt;/p&gt;

&lt;p&gt;Infrastructure should be boring.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Resend — The developer-first email API
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://resend.com" rel="noopener noreferrer"&gt;Resend&lt;/a&gt; was built by developers who were tired of fighting legacy email dashboards. It's also the team behind React Email, the open-source library for building email templates with React components.&lt;/p&gt;

&lt;p&gt;The difference shows from minute one. The docs read like a modern dev tool, the SDKs exist for every major language, and the API surface is small and predictable.&lt;/p&gt;

&lt;p&gt;Here's a complete working example in Python:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;resend
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;resend&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;resend.exceptions&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ResendError&lt;/span&gt;

&lt;span class="n"&gt;resend&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;api_key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;RESEND_API_KEY&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;resend&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Emails&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SendParams&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;from&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Acme &amp;lt;onboarding@resend.dev&amp;gt;&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;to&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;delivered@resend.dev&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;subject&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Your login code&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;html&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;lt;p&amp;gt;Your code is &amp;lt;strong&amp;gt;482913&amp;lt;/strong&amp;gt;&amp;lt;/p&amp;gt;&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;email&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;resend&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Emails&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;email&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# {'id': '...'}
&lt;/span&gt;&lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="n"&gt;ResendError&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. No nested JSON objects, no region configuration, no sandbox approval process.&lt;/p&gt;

&lt;p&gt;Resend has also moved fast on AI. In April 2026 it launched an official MCP server that gives agents in Claude Code, Cursor or Codex access to sending, inbound email, contacts, broadcasts, domains and webhooks.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pros&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Fastest setup in this list: a test address (&lt;code&gt;delivered@resend.dev&lt;/code&gt;) lets you send before verifying a domain&lt;/li&gt;
&lt;li&gt;React Email integration for templates written as components, not HTML tables&lt;/li&gt;
&lt;li&gt;Free tier of 3,000 emails/month (100/day) with no expiry date&lt;/li&gt;
&lt;li&gt;Official MCP server for AI agents, plus inbound email on every plan&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Cons&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Younger company than Postmark or SendGrid, so fewer third-party integrations&lt;/li&gt;
&lt;li&gt;The 100/day limit on the free plan can bite during launches&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Pricing:&lt;/strong&gt; Free (3,000/month). Pro at $20/month for 50,000 emails. Scale at $90/month for 100,000.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; Startups, indie hackers and teams building with Next.js, Python or AI agents who want email to just work.&lt;/p&gt;

&lt;p&gt;👉 If you want to try it, start with the &lt;a href="https://resend.com/docs/send-with-python" rel="noopener noreferrer"&gt;Resend Python docs&lt;/a&gt;. You'll send your first email in under five minutes.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Postmark — The deliverability specialist
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://postmarkapp.com" rel="noopener noreferrer"&gt;Postmark&lt;/a&gt; has been doing one thing for over a decade: getting transactional email into inboxes fast. It separates transactional and broadcast traffic into different "message streams" so marketing sends don't damage your password-reset reputation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pros&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Strong reputation for delivery speed on transactional mail&lt;/li&gt;
&lt;li&gt;Message streams keep transactional and bulk traffic isolated&lt;/li&gt;
&lt;li&gt;45-day log retention on Basic, configurable up to 365 days on Pro&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Cons&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Free plan is only 100 emails/month, enough to test and not much more&lt;/li&gt;
&lt;li&gt;Overage pricing adds up quickly past 10,000 emails&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Pricing:&lt;/strong&gt; Free (100/month). Basic from $15/month for 10,000 emails, then $1.80 per 1,000.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; Products where a delayed email is a business problem: fintech, healthcare, B2B SaaS logins.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Mailgun — The flexible workhorse
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.mailgun.com" rel="noopener noreferrer"&gt;Mailgun&lt;/a&gt; is a mature API with powerful routing and inbound parsing. It's a common choice for apps that need to &lt;em&gt;receive&lt;/em&gt; and process email, not just send it.&lt;/p&gt;

&lt;p&gt;You don't even need an SDK. Plain &lt;code&gt;requests&lt;/code&gt; works:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;

&lt;span class="n"&gt;DOMAIN&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mg.yourdomain.com&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://api.mailgun.net/v3/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;DOMAIN&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/messages&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;api&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;MAILGUN_API_KEY&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]),&lt;/span&gt;
    &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;from&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Acme &amp;lt;hello@&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;DOMAIN&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;&amp;gt;&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;to&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;user@example.com&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;subject&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Your login code&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Your code is 482913&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Pros&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Mature inbound routing and email parsing&lt;/li&gt;
&lt;li&gt;Plain REST API, easy to call without an SDK&lt;/li&gt;
&lt;li&gt;EU region available&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Cons&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Only 1 day of log retention on Free and Basic plans&lt;/li&gt;
&lt;li&gt;Dashboard feels dated compared to newer tools&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Pricing:&lt;/strong&gt; Free (100 emails/day). Basic from $15/month for 10,000 emails. Foundation $35/month for 50,000.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; Apps that need inbound email processing and flexible routing rules.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Plunk — The open-source option
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.useplunk.com" rel="noopener noreferrer"&gt;Plunk&lt;/a&gt; is a small, open-source email platform that combines transactional email, marketing broadcasts and automation workflows in one tool. The code is AGPL-3.0 licensed, so you can run it yourself with Docker Compose.&lt;/p&gt;

&lt;p&gt;That's rare in this space.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pros&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Open source and self-hostable: full data ownership, no per-email cost when self-hosted&lt;/li&gt;
&lt;li&gt;Transactional, campaigns and workflows in a single product&lt;/li&gt;
&lt;li&gt;EU-hosted and GDPR compliant, with SDKs for Node.js and Python&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Cons&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Free plan includes Plunk branding in emails&lt;/li&gt;
&lt;li&gt;Smaller team and community than the established players&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Pricing:&lt;/strong&gt; Free (1,000 emails/month). Paid is pure pay-as-you-go at $0.001 per email, with an optional monthly spend cap.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; Developers who want to own their email stack, or startups that need transactional and marketing email without paying for two tools.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Lettermint — The European alternative
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://lettermint.co" rel="noopener noreferrer"&gt;Lettermint&lt;/a&gt; is a young transactional email provider built entirely on EU infrastructure, with ISO 27001 certification. If your customers or your legal team ask where data lives, this answers the question before it's asked.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pros&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;EU-based infrastructure, GDPR compliant and ISO 27001 certified&lt;/li&gt;
&lt;li&gt;REST API, SMTP relay, batch sending, webhooks and a CLI on every tier&lt;/li&gt;
&lt;li&gt;Python SDK available, plus 28-day data retention on all plans&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Cons&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Free tier is small: 300 emails/month and 1 domain&lt;/li&gt;
&lt;li&gt;Inbound email only on paid plans&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Pricing:&lt;/strong&gt; Free (300/month). Paid plans from €10/month for 10,000 emails, with overage from €1.10 per 1,000.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; European SaaS companies and agencies where data residency is a requirement, not a nice-to-have.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Sendkit — The all-in-one for startups
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://sendkit.dev" rel="noopener noreferrer"&gt;Sendkit&lt;/a&gt; is a newer platform that bundles transactional email, campaigns, automations, email validation and SMTP relay. Like Resend, it ships an MCP server (with 39 tools) so AI agents can manage email operations directly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pros&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Free tier of 3,000 emails/month, matching Resend&lt;/li&gt;
&lt;li&gt;Marketing plan charges for emails sent, not contacts stored&lt;/li&gt;
&lt;li&gt;MCP server for AI agent workflows&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Cons&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Young platform with a short track record on deliverability&lt;/li&gt;
&lt;li&gt;Only 1 sending domain and 3 templates on the free plan&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Pricing:&lt;/strong&gt; Free (3,000/month, 100/day). Transactional from $15/month, Marketing from $30/month. Overage at $1 per 1,000 emails.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; Early-stage startups that want transactional and marketing email from one vendor.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. SendGrid — The enterprise default
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://sendgrid.com" rel="noopener noreferrer"&gt;SendGrid&lt;/a&gt;, now fully part of Twilio, is still one of the biggest senders on the planet. If your company already runs on Twilio, keeping email in the same account makes procurement easy.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pros&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Huge scale and long track record&lt;/li&gt;
&lt;li&gt;Email API plus marketing campaigns in one platform&lt;/li&gt;
&lt;li&gt;Fits naturally into the Twilio ecosystem (SMS, WhatsApp, Verify)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Cons&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Permanent free plan retired in 2025, replaced by a time-limited trial&lt;/li&gt;
&lt;li&gt;Developer experience shows its age: complex dashboard, heavier SDKs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Pricing:&lt;/strong&gt; Essentials starts around $19.95/month for 50,000 emails after the trial.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; Enterprises already standardized on Twilio.&lt;/p&gt;

&lt;h2&gt;
  
  
  Email API comparison table
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Provider&lt;/th&gt;
&lt;th&gt;Free tier&lt;/th&gt;
&lt;th&gt;~50k emails/month&lt;/th&gt;
&lt;th&gt;Open source&lt;/th&gt;
&lt;th&gt;Best for&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://resend.com" rel="noopener noreferrer"&gt;&lt;strong&gt;Resend&lt;/strong&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;3,000/month, no expiry&lt;/td&gt;
&lt;td&gt;$20 (Pro)&lt;/td&gt;
&lt;td&gt;React Email (templates)&lt;/td&gt;
&lt;td&gt;Modern dev teams, AI agents&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://postmarkapp.com" rel="noopener noreferrer"&gt;&lt;strong&gt;Postmark&lt;/strong&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;100/month&lt;/td&gt;
&lt;td&gt;~$87 (Basic + overage)&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Critical transactional mail&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://www.mailgun.com" rel="noopener noreferrer"&gt;&lt;strong&gt;Mailgun&lt;/strong&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;100/day&lt;/td&gt;
&lt;td&gt;$35 (Foundation)&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Inbound processing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://www.useplunk.com" rel="noopener noreferrer"&gt;&lt;strong&gt;Plunk&lt;/strong&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;1,000/month&lt;/td&gt;
&lt;td&gt;~$50 (pay-as-you-go)&lt;/td&gt;
&lt;td&gt;Yes (AGPL-3.0)&lt;/td&gt;
&lt;td&gt;Self-hosting, all-in-one&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://lettermint.co" rel="noopener noreferrer"&gt;&lt;strong&gt;Lettermint&lt;/strong&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;300/month&lt;/td&gt;
&lt;td&gt;~€59–70 (base + overage)&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;EU data residency&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://sendkit.dev" rel="noopener noreferrer"&gt;&lt;strong&gt;Sendkit&lt;/strong&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;3,000/month&lt;/td&gt;
&lt;td&gt;From $15 + overage&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Transactional + marketing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://sendgrid.com" rel="noopener noreferrer"&gt;&lt;strong&gt;SendGrid&lt;/strong&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Trial only&lt;/td&gt;
&lt;td&gt;~$19.95 (Essentials)&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Twilio-based enterprises&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;Pricing from each provider's public pricing page, October 2026. Estimates at 50k: Postmark $15 + 40,000 × $1.80/1,000; Plunk 50,000 × $0.001; Lettermint base plan + 40,000 × overage rate. Always check current pricing before committing.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How to choose the right email API
&lt;/h2&gt;

&lt;p&gt;Feature matrices all look the same after a while. Every provider has an API, SMTP, webhooks and DKIM.&lt;/p&gt;

&lt;p&gt;The real differences show up in five decisions.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Start with what you're sending
&lt;/h3&gt;

&lt;p&gt;Transactional email (password resets, receipts, login codes) and marketing email (newsletters, product updates) are different problems.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Transactional only:&lt;/strong&gt; Resend, Postmark or Mailgun.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Both, in one tool:&lt;/strong&gt; Plunk or Sendkit. Resend also covers broadcasts on every tier.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Strict isolation between the two:&lt;/strong&gt; Postmark's message streams were designed for exactly this.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Mixing marketing and transactional traffic on the same domain is how password resets end up in spam.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Decide where your data has to live
&lt;/h3&gt;

&lt;p&gt;If you sell to European companies, someone will eventually ask where email logs are stored.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;EU infrastructure by default:&lt;/strong&gt; Lettermint and Plunk.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;EU region as an option:&lt;/strong&gt; Mailgun.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Full control:&lt;/strong&gt; self-host Plunk.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Answer this before you integrate. Migrating later is always more painful than choosing right the first time.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Be honest about your volume
&lt;/h3&gt;

&lt;p&gt;Free tiers matter at the start. Overage pricing matters after product-market fit.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Under 3,000 emails/month:&lt;/strong&gt; Resend and Sendkit cover you for free, with no expiry.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Around 50,000/month:&lt;/strong&gt; Resend Pro at $20 is hard to beat among managed services.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Unpredictable spikes:&lt;/strong&gt; Plunk's pay-per-email model with a spend cap removes surprises.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Run the math at 10x your current volume, not today's.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Check how well it fits your stack
&lt;/h3&gt;

&lt;p&gt;This is where hours get won or lost.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;React or Next.js:&lt;/strong&gt; Resend, because React Email lets you write templates as components.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AI agents:&lt;/strong&gt; Resend and Sendkit both ship MCP servers, so Claude or Cursor can send and manage email directly.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Already on Twilio:&lt;/strong&gt; SendGrid keeps everything under one contract.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Heavy inbound processing:&lt;/strong&gt; Mailgun's routing rules are the most mature.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  5. Test debuggability before you need it
&lt;/h3&gt;

&lt;p&gt;Send a few emails to a bad address on purpose. Then try to find out what happened.&lt;/p&gt;

&lt;p&gt;Log retention ranges from 1 day (Mailgun Free/Basic) to 45 days (Postmark Basic). When a customer says "I never got the email," that window decides whether you can answer them.&lt;/p&gt;

&lt;h3&gt;
  
  
  The short version
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Want to ship today and forget about email?&lt;/strong&gt; Start with Resend.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Delivery speed is revenue?&lt;/strong&gt; Postmark.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Need inbound parsing and routing?&lt;/strong&gt; Mailgun.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Want open source or self-hosting?&lt;/strong&gt; Plunk.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;EU data residency is mandatory?&lt;/strong&gt; Lettermint.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Transactional and marketing from one small vendor?&lt;/strong&gt; Sendkit.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Locked into Twilio?&lt;/strong&gt; SendGrid.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For most developers reading this, it's the first one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key takeaways
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;The best email API for developers in 2026 is the one with the shortest path from code to inbox. Resend leads there.&lt;/li&gt;
&lt;li&gt;SendGrid's free plan is gone. Resend and Sendkit offer the most generous no-expiry free tiers (3,000/month).&lt;/li&gt;
&lt;li&gt;Smaller players like Plunk and Lettermint win on specific needs: open source and EU data residency.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  FAQs
&lt;/h2&gt;

&lt;p&gt;❓ &lt;strong&gt;What is the best email API for developers?&lt;/strong&gt;&lt;br&gt;
✅ For most developers and startups, Resend offers the best balance of developer experience, free tier and price. Postmark is stronger for strict transactional delivery, and Mailgun for inbound processing.&lt;/p&gt;

&lt;p&gt;❓ &lt;strong&gt;Is there a free email API?&lt;/strong&gt;&lt;br&gt;
✅ Yes. Resend and Sendkit include 3,000 emails/month for free, Plunk 1,000/month, Lettermint 300/month and Mailgun 100/day. SendGrid replaced its free plan with a trial in 2025.&lt;/p&gt;

&lt;p&gt;❓ &lt;strong&gt;What's the best SendGrid alternative?&lt;/strong&gt;&lt;br&gt;
✅ It depends on why you're leaving. For better developer experience and a real free tier, Resend. For deliverability focus, Postmark. For open source, Plunk. For EU hosting, Lettermint.&lt;/p&gt;

&lt;p&gt;❓ &lt;strong&gt;Is there an open-source email API?&lt;/strong&gt;&lt;br&gt;
✅ Plunk is open source (AGPL-3.0) and can be self-hosted with Docker Compose. Resend maintains React Email, an open-source library for building email templates.&lt;/p&gt;

&lt;p&gt;❓ &lt;strong&gt;Can I send email with Python without SMTP?&lt;/strong&gt;&lt;br&gt;
✅ Yes. Email APIs like Resend and Mailgun let you send over HTTPS with a few lines of Python. That avoids SMTP ports, connection handling and most of the boilerplate of &lt;code&gt;smtplib&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final thoughts
&lt;/h2&gt;

&lt;p&gt;Email used to be a deliverability problem.&lt;/p&gt;

&lt;p&gt;Today it's a developer-experience problem.&lt;/p&gt;

&lt;p&gt;The providers that win the next five years won't be the ones with the most features, but the ones that disappear into your workflow, whether that workflow is a Python script, a Next.js app or an AI agent.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Looking for technical content for your company? I can help — &lt;a href="https://www.linkedin.com/in/kevin-meneses-gonzalez/" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt; · &lt;a href="mailto:kevinmenesesgonzalez@gmail.com"&gt;kevinmenesesgonzalez@gmail.com&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;




</description>
      <category>marketing</category>
      <category>api</category>
      <category>webdev</category>
      <category>developer</category>
    </item>
    <item>
      <title>I Asked Claude to Invest $10,000. Then I Changed the Starting Date</title>
      <dc:creator>Kevin Meneses González</dc:creator>
      <pubDate>Sun, 04 Oct 2026 11:42:47 +0000</pubDate>
      <link>https://dev.to/kevin_menesesgonzlez/i-asked-claude-to-invest-10000-then-i-changed-the-starting-date-24b7</link>
      <guid>https://dev.to/kevin_menesesgonzlez/i-asked-claude-to-invest-10000-then-i-changed-the-starting-date-24b7</guid>
      <description>&lt;p&gt;The portfolio was never the biggest risk.&lt;/p&gt;

&lt;p&gt;The date was.&lt;/p&gt;

&lt;p&gt;Three weeks ago I asked Claude to build a $10,000 allocation for a friend who'd had cash sitting in a savings account for eight months. Five positions, each one backed by real numbers pulled from a live data feed. The piece did well. The replies were mostly the same question, worded fifty different ways:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;"OK, but would it have worked?"&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;My friend asked a sharper version. She runs financial models for a living, so she went straight to the weak spot:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;"What if I'd done this in January 2022, right before everything fell?"&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Fair question. So I ran a market timing backtest. Same portfolio, same $10,000, but instead of one start date I tested 105 of them: every month from January 2015 to September 2023, each one held for three years.&lt;/p&gt;

&lt;p&gt;Then I compared every single run against the S&amp;amp;P 500, a classic 60/40 fund, and the portfolio most people actually want to own right now: the Magnificent 7.&lt;/p&gt;

&lt;p&gt;If you're:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;sitting on cash and waiting for "a better moment" to invest,&lt;/li&gt;
&lt;li&gt;building a backtesting tool or an AI agent that reasons over market data,&lt;/li&gt;
&lt;li&gt;or reading performance charts that conveniently start at the bottom,&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;this is worth your ten minutes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Every backtest you've seen has one start date
&lt;/h2&gt;

&lt;p&gt;And the person who wrote it picked that date.&lt;/p&gt;

&lt;p&gt;That's the quiet problem with most "I invested $X in Y" content. One entry point. One exit point. One number at the end that looks like evidence.&lt;/p&gt;

&lt;p&gt;It isn't. It's an anecdote with a chart attached.&lt;/p&gt;

&lt;p&gt;Shift the start by six months and the same strategy can look brilliant or broken. Most authors don't do it on purpose. They pick a round date like January 1st, run the numbers once, and publish whatever comes out.&lt;/p&gt;

&lt;p&gt;Here's the uncomfortable part: when you run the first article's portfolio from January 2019, $10,000 becomes &lt;strong&gt;$19,135&lt;/strong&gt; in three years. Start it in January 2022 and the exact same allocation ends at &lt;strong&gt;$12,382&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Same stocks. Same weights. Same rebalancing rule.&lt;/p&gt;

&lt;p&gt;$6,753 of difference, and none of it came from the portfolio.&lt;/p&gt;

&lt;h2&gt;
  
  
  A single start date is an anecdote. A distribution is evidence.
&lt;/h2&gt;

&lt;p&gt;The fix is called rolling-window backtesting.&lt;/p&gt;

&lt;p&gt;Instead of asking "what did this portfolio return?", you ask "what did this portfolio return &lt;em&gt;for every possible entry date&lt;/em&gt;?" You get a distribution instead of a number. And a distribution tells you three things a single backtest can't:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;how bad your luck can realistically get,&lt;/li&gt;
&lt;li&gt;how often the strategy beats a simple benchmark,&lt;/li&gt;
&lt;li&gt;whether the edge is in the returns or somewhere else.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That third point turned out to be the whole story.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjxr79fqimnlglvbqi3e2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjxr79fqimnlglvbqi3e2.png" alt=" " width="799" height="436"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The setup: rolling 3-year windows on real adjusted prices
&lt;/h2&gt;

&lt;p&gt;Four portfolios, $10,000 each, rebalanced back to target weights every 12 months:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Portfolio&lt;/th&gt;
&lt;th&gt;Holdings&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;Claude's portfolio&lt;/strong&gt; (from the first article)&lt;/td&gt;
&lt;td&gt;35% VOO, 20% MSFT, 20% JNJ, 15% GLD, 10% SHY&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;Magnificent 7&lt;/strong&gt; (popular stocks)&lt;/td&gt;
&lt;td&gt;Equal weight AAPL, MSFT, NVDA, AMZN, GOOGL, META, TSLA&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;S&amp;amp;P 500 benchmark&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;100% VOO&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;60/40 benchmark&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;100% AOR (iShares Core 60/40 allocation ETF)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Start dates: the first trading day of every month from January 2015 to September 2023. That's 105 entry points, and every window closes by September 2026.&lt;/p&gt;

&lt;p&gt;The data comes from &lt;a href="https://eodhd.com/?via=kmg&amp;amp;ref1=Meneses&amp;amp;utm_source=medium&amp;amp;utm_medium=post&amp;amp;utm_campaign=start-date-backtest&amp;amp;utm_content=Meneses" rel="noopener noreferrer"&gt;EODHD's end-of-day API&lt;/a&gt;, using the &lt;code&gt;adjusted_close&lt;/code&gt; field. That matters more than it sounds. Adjusted prices fold in dividends and splits, so JNJ's 2–3% yield and NVDA's and TSLA's stock splits are reflected correctly. Using raw closing prices would quietly understate the dividend payers and wreck the split stocks.&lt;/p&gt;

&lt;p&gt;One API key covered all twelve tickers back to 2014, on the same endpoint and in the same JSON shape.&lt;/p&gt;

&lt;p&gt;→ If you want to reproduce this, &lt;a href="https://eodhd.com/?via=kmg&amp;amp;ref1=Meneses&amp;amp;utm_source=medium&amp;amp;utm_medium=post&amp;amp;utm_campaign=start-date-backtest&amp;amp;utm_content=Meneses" rel="noopener noreferrer"&gt;EODHD's free tier&lt;/a&gt; is enough to start.&lt;/p&gt;

&lt;h2&gt;
  
  
  The code: a rolling-window backtest in Python
&lt;/h2&gt;

&lt;p&gt;About 50 lines. One function downloads prices, one simulates a portfolio from any start date, and a loop runs all 105 windows.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;requests pandas
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;pandas&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;

&lt;span class="n"&gt;API_TOKEN&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;EODHD_API_KEY&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="n"&gt;PORTFOLIOS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Claude&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;VOO&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;35&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;MSFT&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;JNJ&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;GLD&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;SHY&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Mag7&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;   &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;AAPL&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;MSFT&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;NVDA&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;AMZN&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;GOOGL&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;META&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;TSLA&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]},&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;VOO&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;VOO&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;60/40&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;AOR&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="n"&gt;YEARS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_prices&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ticker&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://eodhd.com/api/eod/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;ticker&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;.US&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;api_token&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;API_TOKEN&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;fmt&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;json&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;from&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2014-01-01&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;DataFrame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set_index&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_datetime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;date&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]))[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;adjusted_close&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="n"&gt;tickers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;PORTFOLIOS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;values&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="n"&gt;prices&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;DataFrame&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;get_prices&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;tickers&lt;/span&gt;&lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;dropna&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;simulate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;weights&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;capital&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;10_000&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Buy on `start`, rebalance every 12 months, hold YEARS years.&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;window&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;prices&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;loc&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;DateOffset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;years&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;YEARS&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nf"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;weights&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="n"&gt;cuts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;searchsorted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;start&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;DateOffset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;years&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;YEARS&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
    &lt;span class="n"&gt;cuts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;window&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;capital&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nf"&gt;zip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cuts&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cuts&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:]):&lt;/span&gt;
        &lt;span class="n"&gt;seg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;window&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;iloc&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="n"&gt;shares&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Series&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;weights&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;seg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;iloc&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="n"&gt;curve&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;seg&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;shares&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;axis&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;curve&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="n"&gt;curve&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;iloc&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:])&lt;/span&gt;
        &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;curve&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;iloc&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;concat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;rows&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;date_range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2015-01-01&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2023-09-01&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;freq&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;MS&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;weights&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;PORTFOLIOS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;items&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="n"&gt;curve&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;simulate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;weights&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;start&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;start&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;date&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;portfolio&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;final&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;curve&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;iloc&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]),&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;max_drawdown&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;curve&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;curve&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cummax&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;DataFrame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;final&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pivot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;start&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;columns&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;portfolio&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;values&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;final&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;final&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;percentiles&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[.&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="p"&gt;]).&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Claude beats VOO in&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;final&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Claude&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;final&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;VOO&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]).&lt;/span&gt;&lt;span class="nf"&gt;mean&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;of start dates&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;           count     mean     std      min      10%      50%      90%      max
portfolio
60/40      105.0  12326.0  1261.0  10387.0  10779.0  12187.0  14190.0  15312.0
Claude     105.0  15055.0  1662.0  12382.0  12936.0  14879.0  17207.0  19135.0
Mag7       105.0  27870.0  8738.0  16639.0  18374.0  25574.0  39156.0  59977.0
VOO        105.0  14920.0  1764.0  11098.0  13109.0  14473.0  17526.0  20004.0
Claude beats VOO in 54% of start dates
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One detail in &lt;code&gt;simulate()&lt;/code&gt; is easy to get wrong. If the anniversary falls on a weekend, the rebalance has to happen on the next trading day, and the portfolio value has to be measured on that same day. Measure value on Friday and buy at Monday's prices, and you silently drop a day of returns at every rebalance. My first version had exactly that bug. It moved some results by more than $1,000.&lt;/p&gt;

&lt;p&gt;From here you can build:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a "what if I'd started earlier" widget for any watchlist,&lt;/li&gt;
&lt;li&gt;a risk report that shows the worst 10% of entry dates instead of the average,&lt;/li&gt;
&lt;li&gt;an agent that answers "should I wait to invest?" with a distribution instead of an opinion.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Five start dates with names
&lt;/h2&gt;

&lt;p&gt;Before the full distribution, here are the dates people actually ask about. Value of $10,000 after three years, with the worst peak-to-trough drop along the way:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Entry date&lt;/th&gt;
&lt;th&gt;Claude's portfolio&lt;/th&gt;
&lt;th&gt;S&amp;amp;P 500 (VOO)&lt;/th&gt;
&lt;th&gt;60/40 (AOR)&lt;/th&gt;
&lt;th&gt;Magnificent 7&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Jan 2018 (before the Q4 selloff)&lt;/td&gt;
&lt;td&gt;$15,743 / −22.8%&lt;/td&gt;
&lt;td&gt;$14,736 / −34.0%&lt;/td&gt;
&lt;td&gt;$12,422 / −22.9%&lt;/td&gt;
&lt;td&gt;$38,183 / −34.9%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Feb 2020 (pre-COVID peak)&lt;/td&gt;
&lt;td&gt;$12,926 / −22.9%&lt;/td&gt;
&lt;td&gt;$13,308 / −34.0%&lt;/td&gt;
&lt;td&gt;$11,250 / −22.9%&lt;/td&gt;
&lt;td&gt;$19,987 / −50.7%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Apr 2020 (after the COVID crash)&lt;/td&gt;
&lt;td&gt;$15,371 / −18.3%&lt;/td&gt;
&lt;td&gt;$17,448 / −24.5%&lt;/td&gt;
&lt;td&gt;$13,112 / −21.7%&lt;/td&gt;
&lt;td&gt;$29,128 / −49.8%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Jan 2022 (market peak)&lt;/td&gt;
&lt;td&gt;$12,382 / −17.2%&lt;/td&gt;
&lt;td&gt;$12,839 / −24.5%&lt;/td&gt;
&lt;td&gt;$10,789 / −21.5%&lt;/td&gt;
&lt;td&gt;$18,006 / −49.0%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Oct 2022 (near the bear market low)&lt;/td&gt;
&lt;td&gt;$17,839 / −10.3%&lt;/td&gt;
&lt;td&gt;$19,043 / −18.7%&lt;/td&gt;
&lt;td&gt;$15,312 / −9.8%&lt;/td&gt;
&lt;td&gt;$36,297 / −31.2%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Read the Claude column top to bottom. The same portfolio ranges from $12,382 to $17,839 depending only on the month you clicked "buy."&lt;/p&gt;

&lt;p&gt;Now read across any row. Claude vs. the S&amp;amp;P 500 is usually a few hundred dollars apart.&lt;/p&gt;

&lt;p&gt;The month mattered far more than the portfolio did.&lt;/p&gt;

&lt;h2&gt;
  
  
  What 105 start dates actually show
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Timing luck is about 10× bigger than portfolio choice
&lt;/h3&gt;

&lt;p&gt;Across all 105 windows, the median gap between Claude's portfolio and the S&amp;amp;P 500 was &lt;strong&gt;$637&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The gap between Claude's best and worst start date was &lt;strong&gt;$6,753&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;That ratio is the headline. If you spend three weeks optimizing what to buy and zero minutes thinking about how you'll handle a bad entry, you've put your effort in the wrong place.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. On returns alone, Claude's portfolio was a coin flip against the S&amp;amp;P 500
&lt;/h3&gt;

&lt;p&gt;It finished ahead of VOO in &lt;strong&gt;54%&lt;/strong&gt; of start dates. Median ending value: $14,879 vs. $14,473.&lt;/p&gt;

&lt;p&gt;That's not an edge. That's noise.&lt;/p&gt;

&lt;p&gt;If the only goal was "beat the S&amp;amp;P 500," the honest conclusion would be: just buy VOO.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. The edge was in the drawdowns
&lt;/h3&gt;

&lt;p&gt;This is where the two portfolios stopped looking alike.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Across all 105 start dates&lt;/th&gt;
&lt;th&gt;Claude's portfolio&lt;/th&gt;
&lt;th&gt;S&amp;amp;P 500 (VOO)&lt;/th&gt;
&lt;th&gt;60/40 (AOR)&lt;/th&gt;
&lt;th&gt;Magnificent 7&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Median ending value&lt;/td&gt;
&lt;td&gt;$14,879&lt;/td&gt;
&lt;td&gt;$14,473&lt;/td&gt;
&lt;td&gt;$12,187&lt;/td&gt;
&lt;td&gt;$25,574&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Worst ending value&lt;/td&gt;
&lt;td&gt;$12,382&lt;/td&gt;
&lt;td&gt;$11,098&lt;/td&gt;
&lt;td&gt;$10,387&lt;/td&gt;
&lt;td&gt;$16,639&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Median max drawdown&lt;/td&gt;
&lt;td&gt;−17.7%&lt;/td&gt;
&lt;td&gt;−24.5%&lt;/td&gt;
&lt;td&gt;−21.7%&lt;/td&gt;
&lt;td&gt;−36.0%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Worst max drawdown&lt;/td&gt;
&lt;td&gt;−23.5%&lt;/td&gt;
&lt;td&gt;−34.0%&lt;/td&gt;
&lt;td&gt;−22.9%&lt;/td&gt;
&lt;td&gt;−50.7%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Avg. months below $10,000&lt;/td&gt;
&lt;td&gt;2.8&lt;/td&gt;
&lt;td&gt;3.9&lt;/td&gt;
&lt;td&gt;6.3&lt;/td&gt;
&lt;td&gt;3.6&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Beats VOO (share of start dates)&lt;/td&gt;
&lt;td&gt;54%&lt;/td&gt;
&lt;td&gt;n/a&lt;/td&gt;
&lt;td&gt;0%&lt;/td&gt;
&lt;td&gt;100%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Claude's portfolio delivered roughly the same return as the S&amp;amp;P 500 with drawdowns about a third smaller. Its worst entry still ended higher than VOO's worst entry.&lt;/p&gt;

&lt;p&gt;That's what the gold, JNJ and short-term treasuries were doing. They didn't add return. They made the bad start dates less bad.&lt;/p&gt;

&lt;p&gt;And that matters more than it looks on a spreadsheet. A −34% drop is the point where real people sell. A −23% drop is the point where they complain and hold.&lt;/p&gt;

&lt;p&gt;The 60/40 fund made an interesting control. In this specific decade, it never beat the S&amp;amp;P 500 over a 3-year window, and its typical drawdown (−21.7%) was deeper than Claude's (−17.7%). The 2022 bond crash hit it from both sides.&lt;/p&gt;

&lt;h2&gt;
  
  
  The popular-stocks portfolio: huge returns, huge hindsight
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fai5q57sl3b304kpu24am.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fai5q57sl3b304kpu24am.png" alt=" " width="799" height="436"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The Magnificent 7 portfolio beat the S&amp;amp;P 500 in &lt;strong&gt;100% of start dates&lt;/strong&gt;. Median ending value: $25,574. Best case, starting January 2019: &lt;strong&gt;$59,977&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Before you sell everything and buy seven tickers, look at two numbers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The range.&lt;/strong&gt; Same seven stocks, same weights. Starting in January 2019 gave you $59,977. Starting in February 2021 gave you $16,639. That's a $43,339 difference driven only by the entry month. The more concentrated the portfolio, the more the calendar decides your outcome.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The drawdowns.&lt;/strong&gt; The median window included a −36% drop. The worst included −50.7%. Half your money, gone on paper, at least once. Most people who say they'd hold through that haven't had to.&lt;/p&gt;

&lt;p&gt;Then there's the problem no backtest fixes on its own: &lt;strong&gt;look-ahead bias&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Nobody called these seven companies "the Magnificent 7" in 2015. That name dates from 2023. I picked them in 2026 &lt;em&gt;because&lt;/em&gt; they won. Running them back to 2015 is like betting on a race after reading the results.&lt;/p&gt;

&lt;p&gt;The same caveat applies, more mildly, to Claude's portfolio. MSFT and gold were picked with today's information too. A rolling backtest removes start-date bias. It does not remove selection bias.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lump sum vs. dollar-cost averaging: the obvious fix that mostly isn't
&lt;/h2&gt;

&lt;p&gt;If the entry date is the risk, the natural reaction is to spread it out. Instead of investing $10,000 on day one, invest $1,667 a month for six months.&lt;/p&gt;

&lt;p&gt;I ran that too, on Claude's portfolio, with uninvested cash earning zero:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Lump sum&lt;/th&gt;
&lt;th&gt;6-month DCA&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Median ending value&lt;/td&gt;
&lt;td&gt;$14,879&lt;/td&gt;
&lt;td&gt;$14,697&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Worst ending value&lt;/td&gt;
&lt;td&gt;$12,382&lt;/td&gt;
&lt;td&gt;$12,030&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Best ending value&lt;/td&gt;
&lt;td&gt;$19,135&lt;/td&gt;
&lt;td&gt;$17,803&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Wins (share of 105 start dates)&lt;/td&gt;
&lt;td&gt;78%&lt;/td&gt;
&lt;td&gt;22%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Feb 2020 start (pre-COVID)&lt;/td&gt;
&lt;td&gt;$12,926&lt;/td&gt;
&lt;td&gt;$13,338&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Jan 2022 start (market peak)&lt;/td&gt;
&lt;td&gt;$12,382&lt;/td&gt;
&lt;td&gt;$12,763&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;DCA won in only 22% of start dates. It helped when you started right at a peak: about $400 better in February 2020 and in January 2022. Averaged over the 10 worst lump-sum start dates, though, the two came out essentially tied ($12,555 vs. $12,564). And it cost real money in the windows where markets simply went up.&lt;/p&gt;

&lt;p&gt;This matches what Vanguard and others have published for decades: lump sum wins about two-thirds of the time because markets rise more often than they fall. In this decade it won even more often.&lt;/p&gt;

&lt;p&gt;DCA isn't a return strategy. It's a regret strategy. If investing $10,000 the day before a crash would make you sell everything, the six-month version is worth its cost. If it wouldn't, it isn't.&lt;/p&gt;

&lt;h2&gt;
  
  
  The honest caveats
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;This was a good decade.&lt;/strong&gt; Not one of the 420 windows (105 dates × 4 portfolios) lost money over three years. Run the same test starting in 2000 or 2007 and that changes fast. EODHD has 30+ years of history if you want to stress-test it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No taxes, no trading costs.&lt;/strong&gt; Annual rebalancing in a taxable account has a cost this backtest ignores.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Overlapping windows.&lt;/strong&gt; 105 windows over ~11 years aren't 105 independent experiments. Neighboring months share most of their data.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Three years is short.&lt;/strong&gt; It's the horizon where timing hurts most. Over 10–15 years, entry dates matter less. They don't stop mattering.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Key takeaways
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Your entry date moved the result about 10× more than your portfolio choice did.&lt;/strong&gt; $6,753 between best and worst start, vs. a $637 median gap to the S&amp;amp;P 500.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A diversified portfolio's real job is shrinking the worst case, not beating the index.&lt;/strong&gt; Claude's allocation matched VOO on returns and cut the median drawdown from −24.5% to −17.7%.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Concentration amplifies timing.&lt;/strong&gt; The Magnificent 7 turned a $10,000 timing gap into a $43,000 one, and its 100% win rate rests on hindsight.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  FAQs
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Does market timing matter for long-term investing?
&lt;/h3&gt;

&lt;p&gt;Yes, more than most people expect over 3–5 year horizons. In a rolling backtest of 105 start dates between 2015 and 2023 using EODHD price data, the same $10,000 portfolio ended anywhere from $12,382 to $19,135 after three years, depending only on the entry month. The effect shrinks over longer horizons, but it doesn't disappear.&lt;/p&gt;

&lt;h3&gt;
  
  
  How much does the start date change a portfolio's returns?
&lt;/h3&gt;

&lt;p&gt;In this test, entry timing changed 3-year results by up to $6,753 on a $10,000 diversified portfolio and up to $43,339 on a concentrated Magnificent 7 portfolio. The difference between the diversified portfolio and the S&amp;amp;P 500 was much smaller: a median of $637.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is lump sum investing better than dollar-cost averaging?
&lt;/h3&gt;

&lt;p&gt;Usually, yes. In this backtest, investing $10,000 at once beat a 6-month dollar-cost averaging plan in 78% of start dates. DCA only helped when the lump sum landed right before a crash, such as February 2020 or January 2022. It reduces regret more than it improves returns.&lt;/p&gt;

&lt;h3&gt;
  
  
  Did the Magnificent 7 beat the S&amp;amp;P 500?
&lt;/h3&gt;

&lt;p&gt;Yes. From 2015 to 2026, an equal-weight Magnificent 7 portfolio beat the S&amp;amp;P 500 in every 3-year window tested, with a median ending value of $25,574 vs. $14,473 per $10,000. But the result has strong look-ahead bias: the group was selected for having already won, and it suffered drawdowns of up to −50.7%.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is a 60/40 portfolio still worth it?
&lt;/h3&gt;

&lt;p&gt;Between 2015 and 2026, a 60/40 fund (AOR) never beat the S&amp;amp;P 500 over a 3-year window, and the 2022 bond selloff left its median drawdown (−21.7%) deeper than that of a stock-heavy portfolio with gold and short-term treasuries (−17.7%). Its value is in lower volatility in most periods, not higher returns.&lt;/p&gt;

&lt;h3&gt;
  
  
  How do I backtest a portfolio with different start dates in Python?
&lt;/h3&gt;

&lt;p&gt;Download adjusted daily prices (for example from EODHD's &lt;code&gt;/api/eod/&lt;/code&gt; endpoint), write a function that simulates the portfolio from a given start date, then loop it over every monthly start date and collect the ending value and maximum drawdown. Use adjusted prices so dividends and splits are included, and rebalance on the first trading day on or after each anniversary.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can Claude backtest an investment portfolio?
&lt;/h3&gt;

&lt;p&gt;Claude can write and run the backtesting code, and with a market data connection such as EODHD's MCP server or REST API, it can work with real historical prices instead of remembered ones. The output is only as reliable as the data and assumptions behind it, so ask it to show the exact numbers and start dates it used.&lt;/p&gt;

&lt;h2&gt;
  
  
  The real question was never what to buy
&lt;/h2&gt;

&lt;p&gt;My friend read the results and said something I hadn't expected:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;"So I've been waiting eight months for the right moment, and the moment matters more than anything I was researching."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Yes. Which is exactly why waiting doesn't solve it. Nobody knows which month will be the January 2019 and which will be the January 2022.&lt;/p&gt;

&lt;p&gt;What you can choose is a portfolio whose worst start date you could live with.&lt;/p&gt;

&lt;p&gt;Run the backtest yourself with &lt;a href="https://eodhd.com/?via=kmg&amp;amp;ref1=Meneses&amp;amp;utm_source=medium&amp;amp;utm_medium=post&amp;amp;utm_campaign=start-date-backtest&amp;amp;utm_content=Meneses" rel="noopener noreferrer"&gt;EODHD's API&lt;/a&gt;. The free tier covers every endpoint used here, including adjusted end-of-day prices with 30+ years of history.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;This is not financial advice, and I'm not a financial advisor. Past performance, across 105 start dates or one, doesn't guarantee future results.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;This article ran 420 backtests before writing a single claim.&lt;/strong&gt; That's how I write for fintech and API companies: real calls, real data, numbers your readers can reproduce.&lt;/p&gt;

&lt;p&gt;→ &lt;a href="https://kevinmeneses.com/en" rel="noopener noreferrer"&gt;See more of my work at kevinmeneses.com&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Looking for technical content for your company? I can help — &lt;a href="https://www.linkedin.com/in/kevin-meneses-gonzalez/" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt; · &lt;a href="mailto:kevinmenesesgonzalez@gmail.com"&gt;kevinmenesesgonzalez@gmail.com&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>stocks</category>
      <category>api</category>
      <category>claude</category>
    </item>
    <item>
      <title>How to Choose the Best Stock API in 2026</title>
      <dc:creator>Kevin Meneses González</dc:creator>
      <pubDate>Tue, 29 Sep 2026 10:23:25 +0000</pubDate>
      <link>https://dev.to/kevin_menesesgonzlez/how-to-choose-the-best-stock-api-in-2026-1674</link>
      <guid>https://dev.to/kevin_menesesgonzlez/how-to-choose-the-best-stock-api-in-2026-1674</guid>
      <description>&lt;p&gt;Most developers pick a stock API the way they pick a restaurant on a trip.&lt;/p&gt;

&lt;p&gt;The first result that looks decent. A quick glance at the free tier. Done.&lt;/p&gt;

&lt;p&gt;Then, three months into the project, the problems show up:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the backtest needs 20 years of history and the plan gives you five,&lt;/li&gt;
&lt;li&gt;the app needs European stocks and the API only covers the US,&lt;/li&gt;
&lt;li&gt;the "real-time" feed turns out to be 15 minutes delayed.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Switching providers at that point means rewriting ticker mappings, re-validating historical data and re-testing every pipeline that touches prices.&lt;/p&gt;

&lt;p&gt;That's why choosing the best stock API is a decision worth an hour of your time, not five minutes.&lt;/p&gt;

&lt;p&gt;If you're:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;building a backtesting engine or a quant research notebook,&lt;/li&gt;
&lt;li&gt;shipping a portfolio tracker, screener or finance dashboard,&lt;/li&gt;
&lt;li&gt;or wiring market data into an AI agent,&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;this guide walks you through the criteria that actually matter and the trade-offs behind each one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Most Stock API Comparisons Don't Help
&lt;/h2&gt;

&lt;p&gt;Search "best stock market API" and you'll find the same format everywhere. A top 10 list. A feature table. A winner.&lt;/p&gt;

&lt;p&gt;The problem is that feature tables treat every checkbox as equal.&lt;/p&gt;

&lt;p&gt;"Real-time data: ✅" means nothing if real-time starts at $199 a month and you only need end-of-day prices.&lt;br&gt;
"Historical data: ✅" means nothing if the prices aren't adjusted for splits and your backtest shows fake 90% crashes.&lt;/p&gt;

&lt;p&gt;Tables tell you what a provider has. They don't tell you what you're giving up.&lt;/p&gt;

&lt;p&gt;And every choice here gives something up.&lt;/p&gt;
&lt;h2&gt;
  
  
  There Is No Best Stock API
&lt;/h2&gt;

&lt;p&gt;There's the best API for the trade-off you're willing to make.&lt;/p&gt;

&lt;p&gt;A provider optimized for US real-time streaming spends its engineering budget on latency and exchange feeds. A provider optimized for global historical coverage spends it on 60+ exchanges, corporate actions and fundamentals. Both are good. They're good at different things.&lt;/p&gt;

&lt;p&gt;So the real question isn't "which API is best?"&lt;/p&gt;

&lt;p&gt;It's "what does my project need, and what can it live without?"&lt;/p&gt;

&lt;p&gt;Let's break that down into six criteria, plus one that nobody reads until it's too late.&lt;/p&gt;
&lt;h2&gt;
  
  
  Criterion 1: Coverage
&lt;/h2&gt;

&lt;p&gt;Coverage has three dimensions, and most comparisons only look at one.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Markets.&lt;/strong&gt; US-only or global? If there's any chance your product will serve users outside the US, check coverage for London, Frankfurt, Tokyo or whatever markets your users actually trade. Adding a second provider later just for international stocks is painful.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Asset classes.&lt;/strong&gt; Stocks, ETFs, mutual funds, indices, forex, crypto, bonds, options. A portfolio tracker needs most of them. A trading bot might need one.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Datasets.&lt;/strong&gt; Prices are only the start. Fundamentals, ETF holdings, dividends, splits, earnings calendars, insider trades and news are often sold separately or unlocked on higher tiers.&lt;/p&gt;

&lt;p&gt;What to check before paying:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Request your actual watchlist, not AAPL. Every provider covers AAPL.&lt;/li&gt;
&lt;li&gt;Check a small-cap, a foreign listing and an ETF.&lt;/li&gt;
&lt;li&gt;Confirm which datasets your plan includes, not the provider's whole catalog.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Criterion 2: Pricing
&lt;/h2&gt;

&lt;p&gt;Stock API pricing looks simple until you compare two providers side by side. They rarely charge for the same thing.&lt;/p&gt;

&lt;p&gt;Some price by &lt;strong&gt;rate limit&lt;/strong&gt; (more requests per minute, higher tier). Some price by &lt;strong&gt;dataset&lt;/strong&gt; (prices in one plan, fundamentals in another). Some price by &lt;strong&gt;market&lt;/strong&gt; (stocks, options and crypto each have their own ladder). Some price by &lt;strong&gt;license&lt;/strong&gt; (personal vs commercial).&lt;/p&gt;

&lt;p&gt;Three pricing traps to watch:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Annual prices shown as monthly.&lt;/strong&gt; Several providers display the annual-billing price per month. The month-to-month price can be noticeably higher.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The real-time jump.&lt;/strong&gt; Entry tiers are often 15-minute delayed. Real-time US data usually starts two or three tiers up.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dataset add-ons.&lt;/strong&gt; A cheap price plan plus a separate fundamentals plan can cost more than an all-in-one bundle.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Compare total cost for your use case, not entry price.&lt;/p&gt;
&lt;h2&gt;
  
  
  Criterion 3: Rate Limits
&lt;/h2&gt;

&lt;p&gt;Rate limits decide whether your pipeline runs in 5 minutes or 5 hours.&lt;/p&gt;

&lt;p&gt;Look at three things:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Per-minute vs per-day limits.&lt;/strong&gt; A free tier of 25 requests per day is a very different product from one with 60 requests per minute. The first is for learning. The second can run a small live dashboard.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Weighted calls.&lt;/strong&gt; Some providers count heavier endpoints (fundamentals, bulk downloads) as multiple calls. Your "100,000 calls a day" might be fewer in practice.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bulk endpoints.&lt;/strong&gt; This is the most underrated feature in the category. If you refresh 5,000 tickers every evening, an API that returns an entire exchange's end-of-day data in one request changes everything. Without bulk, that's 5,000 requests.&lt;/p&gt;

&lt;p&gt;Quick math before you commit: estimate requests per run, multiply by runs per day, add 30% for retries and new tickers. That number decides your tier.&lt;/p&gt;
&lt;h2&gt;
  
  
  Criterion 4: Historical Depth
&lt;/h2&gt;

&lt;p&gt;How far back you need depends on what you're building.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A price chart for a mobile app: 1 to 5 years is usually enough.&lt;/li&gt;
&lt;li&gt;A backtest that includes 2008: you need 20+ years.&lt;/li&gt;
&lt;li&gt;Factor research or long-horizon studies: 30+ years if you can get it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But depth alone isn't enough. Quality matters more.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Adjusted prices.&lt;/strong&gt; Splits and dividends distort raw prices. NVIDIA's 10-for-1 split in June 2024 looks like a 90% crash in unadjusted data. If your provider doesn't give you an adjusted series, you'll spend days rebuilding one.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Delisted tickers.&lt;/strong&gt; If your historical universe only includes companies that exist today, your backtest suffers from survivorship bias. The companies that went bankrupt disappear from the data, and your strategy looks better than it would have been. Check whether delisted stocks are available and on which plan.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Free tier history.&lt;/strong&gt; Free plans often cap history at one or two years. Fine for testing the API. Useless for testing a strategy.&lt;/p&gt;
&lt;h2&gt;
  
  
  Criterion 5: Real-Time Data
&lt;/h2&gt;

&lt;p&gt;This is where budgets blow up. So start with an honest question.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Do you actually need real-time?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;You need it for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;trading bots that act on intraday price moves,&lt;/li&gt;
&lt;li&gt;live alerting on price thresholds,&lt;/li&gt;
&lt;li&gt;apps where users expect ticking quotes.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You probably don't need it for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;backtesting,&lt;/li&gt;
&lt;li&gt;portfolio analytics and risk reports,&lt;/li&gt;
&lt;li&gt;screeners that run once a day,&lt;/li&gt;
&lt;li&gt;most dashboards and research tools.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you do need it, check the details:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Delayed vs real-time.&lt;/strong&gt; "Live" often means 15-minute delayed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;REST polling vs WebSocket.&lt;/strong&gt; Polling a quote every second burns rate limits. WebSockets push updates instead.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Symbol limits on streams.&lt;/strong&gt; Some plans cap how many symbols you can subscribe to at once.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Market scope.&lt;/strong&gt; Real-time US equities is common. Real-time international equities is rarer and more expensive.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;End-of-day data covers more use cases than most developers assume, and it costs a fraction of real-time.&lt;/p&gt;
&lt;h2&gt;
  
  
  Criterion 6: Developer Experience
&lt;/h2&gt;

&lt;p&gt;Two APIs with identical data can feel completely different to build with.&lt;/p&gt;

&lt;p&gt;What separates good DX from bad:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Consistent ticker format.&lt;/strong&gt; Something like &lt;code&gt;SYMBOL.EXCHANGE&lt;/code&gt; across every market saves hours of mapping logic.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Clear documentation&lt;/strong&gt; with real examples, not just parameter lists.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Predictable errors.&lt;/strong&gt; A 404 for an unknown ticker is fine. An empty 200 response is a debugging nightmare.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Official SDKs&lt;/strong&gt; for your language, or at least a clean REST design that doesn't need one.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AI-ready access.&lt;/strong&gt; In 2026, several providers (including EODHD, FMP and Alpha Vantage) ship MCP servers, so tools like Claude can query market data directly. If you're building agents, this matters.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The best DX test is simple: time how long it takes you to get a clean DataFrame of your watchlist from a fresh account.&lt;/p&gt;
&lt;h2&gt;
  
  
  The Hidden Criterion: Licensing
&lt;/h2&gt;

&lt;p&gt;Nobody reads the terms. Everyone should.&lt;/p&gt;

&lt;p&gt;Most self-serve plans are licensed for &lt;strong&gt;personal use&lt;/strong&gt;. The moment you display that data to paying users, redistribute it, or build a commercial product on top, you usually need a business or enterprise plan.&lt;/p&gt;

&lt;p&gt;This isn't a technicality. Commercial plans often cost several times more than personal ones.&lt;/p&gt;

&lt;p&gt;If you're building a product, not a personal tool, factor the commercial license into your budget from day one.&lt;/p&gt;
&lt;h2&gt;
  
  
  The 5 Trade-Offs You'll Actually Face
&lt;/h2&gt;

&lt;p&gt;Here's where the criteria collide.&lt;/p&gt;
&lt;h3&gt;
  
  
  1. Breadth vs depth
&lt;/h3&gt;

&lt;p&gt;Global providers cover dozens of exchanges and many asset classes. US specialists go deeper on one market: tick data, options chains, sub-second aggregates.&lt;/p&gt;

&lt;p&gt;Pick breadth if your users are international or your product spans asset classes. Pick depth if you're building US trading infrastructure.&lt;/p&gt;
&lt;h3&gt;
  
  
  2. Real-time vs budget
&lt;/h3&gt;

&lt;p&gt;Real-time US equities typically starts around $99 to $199 a month on individual plans. End-of-day data starts around $20.&lt;/p&gt;

&lt;p&gt;If real-time isn't core to your product, don't pay for it yet.&lt;/p&gt;
&lt;h3&gt;
  
  
  3. Generous free tier vs a clear path to scale
&lt;/h3&gt;

&lt;p&gt;Some providers have very generous free tiers. Others keep the free tier tight but have better value on paid plans.&lt;/p&gt;

&lt;p&gt;The free tier helps you build the prototype. The paid tier is what you'll live with. Evaluate both.&lt;/p&gt;
&lt;h3&gt;
  
  
  4. One provider vs a stack
&lt;/h3&gt;

&lt;p&gt;Using one provider for prices, fundamentals and ETF data means one ticker format, one adjustment methodology and one bill. Stacking specialists gives you best-in-class for each dataset, but you'll maintain the glue between them forever.&lt;/p&gt;

&lt;p&gt;For most projects, one strong provider beats three perfect ones.&lt;/p&gt;
&lt;h3&gt;
  
  
  5. Personal vs commercial
&lt;/h3&gt;

&lt;p&gt;Same data, different license, different price. Decide early which one you are.&lt;/p&gt;
&lt;h2&gt;
  
  
  Stock API Comparison: 6 Providers Worth Your Shortlist
&lt;/h2&gt;

&lt;p&gt;List prices, individual plans, checked September 2026. Always confirm on the provider's site before buying, because this category changes often.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Provider&lt;/th&gt;
&lt;th&gt;Free tier&lt;/th&gt;
&lt;th&gt;Paid from&lt;/th&gt;
&lt;th&gt;Strength&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;EODHD&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;20 calls/day, 1 year of history&lt;/td&gt;
&lt;td&gt;$19.99/mo (EOD, all world)&lt;/td&gt;
&lt;td&gt;Global EOD, fundamentals, 30+ years&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;Massive&lt;/strong&gt; (formerly Polygon.io)&lt;/td&gt;
&lt;td&gt;5 calls/min, 2 years, EOD&lt;/td&gt;
&lt;td&gt;$29/mo (15-min delayed)&lt;/td&gt;
&lt;td&gt;US real-time and streaming&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Alpha Vantage&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;25 calls/day&lt;/td&gt;
&lt;td&gt;$49.99/mo (75 calls/min, delayed)&lt;/td&gt;
&lt;td&gt;Technical indicators, simple API&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Financial Modeling Prep&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;250 calls/day&lt;/td&gt;
&lt;td&gt;$22/mo billed annually (US)&lt;/td&gt;
&lt;td&gt;Deep US fundamentals&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Twelve Data&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;800 calls/day&lt;/td&gt;
&lt;td&gt;$29/mo&lt;/td&gt;
&lt;td&gt;Multi-asset, one API for everything&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Finnhub&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;60 calls/min, US real-time&lt;/td&gt;
&lt;td&gt;$49.99/mo&lt;/td&gt;
&lt;td&gt;Free real-time US, alternative data&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Now the trade-offs behind each.&lt;/p&gt;
&lt;h3&gt;
  
  
  EODHD
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Pros&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;30+ years of end-of-day data across 150,000+ tickers on 60+ exchanges, with adjusted prices and delisted tickers on paid plans.&lt;/li&gt;
&lt;li&gt;Prices, fundamentals, ETF holdings, dividends and splits from one API with one ticker format.&lt;/li&gt;
&lt;li&gt;100,000 calls per day on paid plans, plus bulk endpoints for whole-exchange downloads and an MCP server for AI workflows.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Cons&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The free tier (20 calls/day, 1 year) is only good for testing.&lt;/li&gt;
&lt;li&gt;Fundamentals are a separate plan ($59.99/mo) or part of the All-In-One bundle ($99.99/mo).&lt;/li&gt;
&lt;li&gt;Standard plans are personal use; commercial products need a business plan.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; backtesting, portfolio analytics, global dashboards and anyone who wants one provider for prices and fundamentals.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Try EODHD with 10% off&lt;/strong&gt;&lt;br&gt;
30+ years of global market data, fundamentals and ETF holdings in one API. Use code KEVIN10.&lt;br&gt;
&lt;strong&gt;→ &lt;a href="https://eodhd.com/?via=kmg&amp;amp;ref1=Meneses&amp;amp;utm_source=devto&amp;amp;utm_medium=post&amp;amp;utm_campaign=best-stock-api-2026-how-to-choose&amp;amp;utm_content=Meneses" rel="noopener noreferrer"&gt;Get your EODHD API key&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;
  
  
  Massive (formerly Polygon.io)
&lt;/h3&gt;

&lt;p&gt;Polygon.io rebranded to Massive in October 2025. Existing keys and the old endpoint still work.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pros&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Strong US market infrastructure: WebSockets, trades, quotes and options.&lt;/li&gt;
&lt;li&gt;Unlimited calls from the $29 Starter tier.&lt;/li&gt;
&lt;li&gt;20+ years of history on the $199 Advanced plan.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Cons&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Stock data on the $29 and $79 plans is 15-minute delayed. Real-time starts at $199.&lt;/li&gt;
&lt;li&gt;US-focused. International equities aren't the core product.&lt;/li&gt;
&lt;li&gt;Each asset class has its own pricing ladder.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; US trading bots, streaming apps and options-heavy projects.&lt;/p&gt;
&lt;h3&gt;
  
  
  Alpha Vantage
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Pros&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Very simple API, great for learning.&lt;/li&gt;
&lt;li&gt;50+ technical indicators computed server-side.&lt;/li&gt;
&lt;li&gt;Official MCP server.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Cons&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The free tier dropped to 25 requests per day, which disappears fast.&lt;/li&gt;
&lt;li&gt;The $49.99 entry plan is delayed. Real-time US data starts at $99.99.&lt;/li&gt;
&lt;li&gt;No WebSocket streaming.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; students, prototypes and indicator-driven research.&lt;/p&gt;
&lt;h3&gt;
  
  
  Financial Modeling Prep
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Pros&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Deep US fundamentals, ratios and financial statements.&lt;/li&gt;
&lt;li&gt;High rate limits (300 to 3,000 calls/min depending on plan).&lt;/li&gt;
&lt;li&gt;Official MCP server.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Cons&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The Starter plan covers the US with up to 5 years of history. 30 years needs Premium.&lt;/li&gt;
&lt;li&gt;Global coverage, ETF holdings and transcripts sit on the Ultimate tier ($149/mo billed annually).&lt;/li&gt;
&lt;li&gt;Listed prices are annual billing; monthly is higher.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; US fundamental analysis, valuation models and equity research tools.&lt;/p&gt;
&lt;h3&gt;
  
  
  Twelve Data
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Pros&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The most usable free tier for general development (800 requests/day).&lt;/li&gt;
&lt;li&gt;Stocks, forex, crypto, ETFs and fundamentals under one API.&lt;/li&gt;
&lt;li&gt;No daily limits on paid plans.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Cons&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Production WebSocket streaming starts on the Pro plan ($99/mo).&lt;/li&gt;
&lt;li&gt;Credit-based limits take some getting used to.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; multi-asset apps that want one API from prototype to production.&lt;/p&gt;
&lt;h3&gt;
  
  
  Finnhub
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Pros&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;60 calls per minute and real-time US trades over WebSocket on the free tier.&lt;/li&gt;
&lt;li&gt;Interesting alternative datasets (insider trades, lobbying, supply chain).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Cons&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The free tier is non-commercial and US-focused.&lt;/li&gt;
&lt;li&gt;Paid plans are split by dataset, so costs add up if you need several.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; real-time hobby projects and apps that use alternative data.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What about Yahoo Finance?&lt;/strong&gt; Libraries like yfinance are great for a quick notebook. But they rely on unofficial endpoints with no SLA and no license for products. They break without warning. Don't build anything that matters on them.&lt;/p&gt;
&lt;h2&gt;
  
  
  How to Test a Stock API Before You Commit
&lt;/h2&gt;

&lt;p&gt;Marketing pages all look the same. Data doesn't.&lt;/p&gt;

&lt;p&gt;Before paying, run the same three checks against every provider on your shortlist using its free tier or trial:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Coverage:&lt;/strong&gt; does it return your actual watchlist?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Depth:&lt;/strong&gt; how far back does each ticker go?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Quality:&lt;/strong&gt; are prices adjusted correctly around a known split?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Here's the script I use, shown with EODHD. The logic ports to any provider in a few lines.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;requests pandas
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Check 1 and 2: coverage, depth and latency
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;pandas&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;

&lt;span class="n"&gt;API_KEY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;YOUR_EODHD_API_KEY&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;BASE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://eodhd.com/api&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="c1"&gt;# Your real watchlist: US, Europe, Asia, an ETF and crypto
&lt;/span&gt;&lt;span class="n"&gt;WATCHLIST&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;AAPL.US&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;NVDA.US&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;SPY.US&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;SAP.XETRA&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;VOD.LSE&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;7203.TSE&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;BTC-USD.CC&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;check&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;symbol&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;t0&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;perf_counter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;BASE&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/eod/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;symbol&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;api_token&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;API_KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;fmt&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;json&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;from&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;1980-01-01&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="n"&gt;timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;latency&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;perf_counter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;t0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;symbol&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;symbol&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;available&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;latency_s&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;latency&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;DataFrame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;symbol&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;symbol&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;available&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;first_date&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;date&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;iloc&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;rows&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;latency_s&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;latency&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;report&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;DataFrame&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nf"&gt;check&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;WATCHLIST&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;report&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The output gives you one row per ticker: whether it exists, when its history starts, how many daily bars you get and how long the request took. Run it on each shortlisted provider and compare the tables side by side. Missing tickers and short histories jump out immediately.&lt;/p&gt;

&lt;h3&gt;
  
  
  Check 3: adjusted prices around a split
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;BASE&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/eod/NVDA.US&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;api_token&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;API_KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;fmt&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;json&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;from&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2024-06-05&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;to&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2024-06-12&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;DataFrame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;())[[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;date&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;close&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;adjusted_close&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;NVIDIA split 10-for-1 on June 10, 2024. In the &lt;code&gt;close&lt;/code&gt; column, the price drops by roughly 10x overnight. In the &lt;code&gt;adjusted_close&lt;/code&gt; column, the series stays continuous.&lt;/p&gt;

&lt;p&gt;If a provider gives you only the first column, every return, drawdown and correlation you calculate across that date will be wrong.&lt;/p&gt;

&lt;p&gt;From here you can extend the script to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;test fundamentals for the same watchlist,&lt;/li&gt;
&lt;li&gt;measure how your request volume maps to each provider's rate limits,&lt;/li&gt;
&lt;li&gt;log results over a week to check reliability.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;An hour of testing now saves weeks of migration later.&lt;/p&gt;

&lt;h2&gt;
  
  
  Which Stock API Should You Choose? A Decision Framework
&lt;/h2&gt;

&lt;p&gt;Match your project to its non-negotiables.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Building a backtesting engine or doing quant research?&lt;/strong&gt;&lt;br&gt;
Prioritize historical depth, adjusted prices and delisted tickers. Real-time is irrelevant. EODHD fits well here, and Massive's higher tiers do for US-only work.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Building a portfolio tracker or finance dashboard?&lt;/strong&gt;&lt;br&gt;
Prioritize global coverage, ETFs, fundamentals and cost. End-of-day or delayed data is almost always enough. EODHD or Twelve Data for global scope, FMP if the product is US fundamentals-first.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Building a real-time trading bot for US stocks?&lt;/strong&gt;&lt;br&gt;
Prioritize WebSocket streaming and latency. Budget for real-time from the start. Massive, Finnhub or Twelve Data Pro.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Learning, teaching or prototyping?&lt;/strong&gt;&lt;br&gt;
Prioritize the free tier. Twelve Data, Finnhub or Alpha Vantage get you started without a card. Just don't build your production pipeline on assumptions from a free plan.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Building a commercial product?&lt;/strong&gt;&lt;br&gt;
Everything above still applies, plus: talk to sales about the commercial license before you write your first line of production code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key Takeaways
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;There's no best stock API, only the best one for your trade-offs. Define your non-negotiables first.&lt;/li&gt;
&lt;li&gt;Real-time data is the biggest cost driver. Most projects run fine on end-of-day data.&lt;/li&gt;
&lt;li&gt;Test with your own watchlist and a known split before paying. Data quality problems don't show up in feature tables.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  FAQs
&lt;/h2&gt;

&lt;p&gt;❓ &lt;strong&gt;What is the best stock API in 2026?&lt;/strong&gt;&lt;br&gt;
✅ It depends on the project. For global historical data and fundamentals from one provider, EODHD is a strong choice. For US real-time streaming, Massive (formerly Polygon.io) and Finnhub are specialists. For free prototyping, Twelve Data and Finnhub have the most usable free tiers.&lt;/p&gt;

&lt;p&gt;❓ &lt;strong&gt;Is there a free stock API with real-time data?&lt;/strong&gt;&lt;br&gt;
✅ Finnhub's free tier includes real-time US trades over WebSocket for personal, non-commercial use. Most other providers reserve real-time data for paid plans.&lt;/p&gt;

&lt;p&gt;❓ &lt;strong&gt;What's the difference between real-time and end-of-day stock data?&lt;/strong&gt;&lt;br&gt;
✅ Real-time data updates during market hours, trade by trade or second by second. End-of-day data gives you one bar per day (open, high, low, close, volume). End-of-day is enough for backtesting, portfolio analytics and daily screeners, and it costs much less.&lt;/p&gt;

&lt;p&gt;❓ &lt;strong&gt;Can I use a stock market API with Python?&lt;/strong&gt;&lt;br&gt;
✅ Yes. All the providers in this guide offer REST APIs that return JSON, which you can load into pandas with a few lines using &lt;code&gt;requests&lt;/code&gt;. Several also offer official Python SDKs.&lt;/p&gt;

&lt;h2&gt;
  
  
  My Personal Pick: EODHD
&lt;/h2&gt;

&lt;p&gt;Everything above is meant to help you decide for yourself. But people always ask me which one I actually use.&lt;/p&gt;

&lt;p&gt;For most of my projects, it's EODHD.&lt;/p&gt;

&lt;p&gt;Not because it wins every category. It doesn't. If I were building a US options trading bot, I'd look at a specialist.&lt;/p&gt;

&lt;p&gt;I choose it for two reasons.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Ease of use.&lt;/strong&gt; One ticker format for every market. One API key. Prices, fundamentals and ETF holdings that come back in a clean JSON I can drop into pandas in a couple of lines. I rarely open the docs twice for the same endpoint.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Versatility.&lt;/strong&gt; In the last year I've used the same API for a portfolio risk analysis, a dividend screener, backtests going back to the 1990s, European and Asian stocks, crypto, and AI agents that query market data through its MCP server. I never had to add a second provider or rewrite my ticker mapping.&lt;/p&gt;

&lt;p&gt;That's the trade-off I care about most: one provider that covers 90% of what I build, instead of three providers that each cover 100% of one thing.&lt;/p&gt;

&lt;p&gt;If your projects look like mine (analytics, research, dashboards, AI workflows), it's where I'd start.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;The expensive mistake isn't picking the wrong API.&lt;/p&gt;

&lt;p&gt;It's finding out you picked the wrong one after you've built on top of it.&lt;/p&gt;

&lt;p&gt;Decide what you can't live without. Test it. Then commit.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Start with 30+ years of global market data&lt;/strong&gt;&lt;br&gt;
Prices, fundamentals, ETF holdings and bulk downloads from one API, plus an MCP server for AI workflows. 10% off with code KEVIN10.&lt;br&gt;
&lt;strong&gt;→ &lt;a href="https://eodhd.com/?via=kmg&amp;amp;ref1=Meneses&amp;amp;utm_source=devto&amp;amp;utm_medium=post&amp;amp;utm_campaign=best-stock-api-2026-how-to-choose&amp;amp;utm_content=Meneses" rel="noopener noreferrer"&gt;Try EODHD APIs&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;More tutorials on Python, APIs and fintech at &lt;strong&gt;&lt;a href="https://kevinmeneses.com/en" rel="noopener noreferrer"&gt;kevinmeneses.com&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Looking for technical content for your company? I can help — &lt;a href="https://www.linkedin.com/in/kevin-meneses-gonzalez/" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt; · &lt;a href="mailto:kevinmenesesgonzalez@gmail.com"&gt;kevinmenesesgonzalez@gmail.com&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>api</category>
      <category>python</category>
      <category>fintech</category>
      <category>webdev</category>
    </item>
    <item>
      <title>End-of-Day Stock Data API in Python: Full Guide (2026)</title>
      <dc:creator>Kevin Meneses González</dc:creator>
      <pubDate>Wed, 02 Sep 2026 16:05:22 +0000</pubDate>
      <link>https://dev.to/kevin_menesesgonzlez/end-of-day-stock-data-api-in-python-full-guide-2026-5fc4</link>
      <guid>https://dev.to/kevin_menesesgonzlez/end-of-day-stock-data-api-in-python-full-guide-2026-5fc4</guid>
      <description>&lt;p&gt;Good decisions come from good data. Bad decisions come from a CSV you scraped off a website that changed its layout three weeks ago.&lt;/p&gt;

&lt;p&gt;That's the part nobody tells you when you start building anything that touches stock prices: getting the numbers is easy. Getting numbers you can actually trust, at scale, for years of history, without your script breaking every time a website updates its frontend, is a different problem.&lt;/p&gt;

&lt;p&gt;If you're:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;backtesting a trading strategy,&lt;/li&gt;
&lt;li&gt;building a portfolio tracker,&lt;/li&gt;
&lt;li&gt;or feeding price history into an AI agent,&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;this is the part of your stack that decides whether the rest of it means anything.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why web scraping isn't a real solution
&lt;/h2&gt;

&lt;p&gt;The instinct is understandable. Yahoo Finance shows the price right there in the browser. Why not just scrape it?&lt;/p&gt;

&lt;p&gt;Because that price on the page isn't built for you. It's built for a human reading a chart, and the moment you try to automate against it, you inherit every problem that comes with scraping a site that was never designed to be an API.&lt;/p&gt;

&lt;p&gt;The layout changes without notice. Your parser breaks on a Tuesday and you don't find out until your backtest returns garbage numbers. Rate limits and IP blocks show up with no warning and no documentation to check against. And free sources like Yahoo Finance don't guarantee you're getting the same adjusted price twice: request the same historical date on two different days and you can get two different numbers, because the adjustment recalculates as new splits and dividends occur.&lt;/p&gt;

&lt;p&gt;None of this is a minor inconvenience. If your backtest is quietly using unadjusted closes for half your date range, your Sharpe ratio is a work of fiction.&lt;/p&gt;

&lt;p&gt;There are three mistakes almost everyone makes the first time they touch stock data programmatically:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Mixing up &lt;code&gt;close&lt;/code&gt; and &lt;code&gt;adjusted_close&lt;/code&gt;.&lt;/strong&gt; The raw close doesn't account for splits or dividends. If you compute returns from raw close, a stock that did a 4:1 split shows up as an 75% crash on your chart. It didn't crash. Your data did.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ignoring pagination and date ranges on long history pulls.&lt;/strong&gt; Thirty years of daily bars for one ticker is over 7,500 rows. Pull it wrong and you'll silently truncate history without an error telling you so.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hardcoding the API token in the script.&lt;/strong&gt; It ends up in a GitHub commit within a month. Every time.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The real problem is reliability, not access
&lt;/h2&gt;

&lt;p&gt;Getting stock prices was never the hard part. Getting &lt;em&gt;consistent, correctly adjusted, well-documented&lt;/em&gt; stock prices, for enough history to build something real, is what actually matters.&lt;/p&gt;

&lt;p&gt;This is where a proper data API earns its keep.&lt;/p&gt;

&lt;h2&gt;
  
  
  EODHD's End-of-Day API
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://eodhd.com/?via=kmg&amp;amp;ref1=Meneses&amp;amp;utm_source=medium&amp;amp;utm_medium=post&amp;amp;utm_campaign=end-of-day-stock-data-api-python&amp;amp;utm_content=Meneses" rel="noopener noreferrer"&gt;EODHD&lt;/a&gt; covers more than 150,000 tickers across stocks, ETFs, funds, indices, forex, and crypto, with history going back over 30 years for many US symbols (Ford's data starts in June 1972). Bars are available daily, weekly, or monthly, and every request returns the full price series for a symbol, or a sliced date range if you set one.&lt;/p&gt;

&lt;p&gt;You can test it right now without registering. The &lt;code&gt;DEMO&lt;/code&gt; key gives full, unrestricted access to a handful of tickers: &lt;code&gt;AAPL.US&lt;/code&gt;, &lt;code&gt;TSLA.US&lt;/code&gt;, &lt;code&gt;VTI.US&lt;/code&gt;, &lt;code&gt;AMZN.US&lt;/code&gt;, &lt;code&gt;BTC-USD.CC&lt;/code&gt;, and &lt;code&gt;EURUSD.FOREX&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If you're already testing something with a client, drop the affiliate link and this CTA here as a soft nudge:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Try it before you build on it&lt;/strong&gt;&lt;br&gt;
Test the End-of-Day API right now with the free DEMO key, no signup required.&lt;br&gt;
&lt;strong&gt;→ &lt;a href="https://eodhd.com/?via=kmg&amp;amp;ref1=Meneses&amp;amp;utm_source=medium&amp;amp;utm_medium=post&amp;amp;utm_campaign=end-of-day-stock-data-api-python&amp;amp;utm_content=Meneses" rel="noopener noreferrer"&gt;Get your API token&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  EODHD vs web scraping vs Yahoo Finance
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. EODHD — structured API, paid tiers
&lt;/h3&gt;

&lt;p&gt;A REST API with documented parameters, consistent adjusted pricing, and 30 years of depth for major US tickers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pros&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Consistent &lt;code&gt;adjusted_close&lt;/code&gt; calculated the same way every time you query it&lt;/li&gt;
&lt;li&gt;150,000+ tickers across stocks, ETFs, forex, and crypto, one auth method for all of them&lt;/li&gt;
&lt;li&gt;Official libraries for Python, R, Node, PHP, and an MCP server for AI agents&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Cons&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Free plan caps at 20 calls/day and one year of history&lt;/li&gt;
&lt;li&gt;Paid plans start at $19.99/month, which is a real cost for a side project&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; anything that needs to run unattended, backtests, dashboards, agents, and can't tolerate silent data errors.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Web scraping — free, fragile
&lt;/h3&gt;

&lt;p&gt;Parsing HTML off a finance site instead of calling an API it wasn't built to expose.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pros&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No cost, no signup&lt;/li&gt;
&lt;li&gt;Works on literally any page with visible data&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Cons&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Breaks the moment the site changes its markup, with no warning and no changelog to check&lt;/li&gt;
&lt;li&gt;No guarantee the adjusted price is calculated consistently between two requests&lt;/li&gt;
&lt;li&gt;Sits in a legal gray zone against most sites' terms of service&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; a one-off manual check. Nothing you plan to run twice.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Yahoo Finance (unofficial endpoints) — free, unstable
&lt;/h3&gt;

&lt;p&gt;The most common free fallback, usually accessed through unofficial wrappers rather than a supported API.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pros&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Free, and covers a huge range of tickers&lt;/li&gt;
&lt;li&gt;Widely documented in tutorials, easy to get something running fast&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Cons&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No official API. Every wrapper depends on endpoints Yahoo can change or kill without notice&lt;/li&gt;
&lt;li&gt;Historical adjusted prices have been reported to shift retroactively between pulls&lt;/li&gt;
&lt;li&gt;No SLA, no support channel, no changelog when something breaks&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; quick prototyping when correctness doesn't matter yet.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting the data with Python
&lt;/h2&gt;

&lt;p&gt;There are two ways to do this. Raw &lt;code&gt;requests&lt;/code&gt;, or the official &lt;code&gt;eodhd&lt;/code&gt; library.&lt;/p&gt;

&lt;h3&gt;
  
  
  Option 1: Raw requests
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;

&lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://eodhd.com/api/eod/MCD.US&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;api_token&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;YOUR_TOKEN&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;from&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2024-01-02&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;to&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2024-01-05&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;period&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;d&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;fmt&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;json&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"date"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2024-01-02"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"open"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;295.05&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"high"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;297.28&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"low"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;295.05&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"close"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;297.04&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"adjusted_close"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;279.9221&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"volume"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;4458400&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"date"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2024-01-03"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"open"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;297&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"high"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;297.99&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"low"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;294.25&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"close"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;294.39&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"adjusted_close"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;277.4248&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"volume"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3114800&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice the gap between &lt;code&gt;close&lt;/code&gt; and &lt;code&gt;adjusted_close&lt;/code&gt;. That's not a bug, it's McDonald's dividend history baked into the price. &lt;code&gt;close&lt;/code&gt; is the raw traded price. &lt;code&gt;adjusted_close&lt;/code&gt; accounts for both splits and dividends, and &lt;code&gt;volume&lt;/code&gt; is adjusted for splits. If you need OHLC adjusted for splits only (not dividends), that's a separate call to the Technical Indicators endpoint with &lt;code&gt;function=splitadjusted&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Key parameters worth knowing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;period&lt;/code&gt;: &lt;code&gt;d&lt;/code&gt;, &lt;code&gt;w&lt;/code&gt;, or &lt;code&gt;m&lt;/code&gt; for daily, weekly, monthly bars&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;order&lt;/code&gt;: &lt;code&gt;a&lt;/code&gt; for oldest-first, &lt;code&gt;d&lt;/code&gt; for newest-first&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;filter&lt;/code&gt;: skips the whole series and returns one bare value instead, &lt;code&gt;last_close&lt;/code&gt;, &lt;code&gt;last_volume&lt;/code&gt;, and similar options exist. Useful if you just need today's number for a spreadsheet function and don't want to parse an array for it&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Option 2: The official eodhd Python library
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;eodhd &lt;span class="nt"&gt;-U&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;eodhd&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;APIClient&lt;/span&gt;

&lt;span class="n"&gt;api&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;APIClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;YOUR_TOKEN&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;resp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_eod_historical_stock_market_data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;symbol&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;MCD.US&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;period&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;d&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;from_date&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2024-01-02&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;to_date&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2024-01-05&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;a&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Same data, less boilerplate. The library also wraps fundamentals, intraday data, splits, dividends, screener queries, and technical indicators under one client, so if this is the first of several EODHD endpoints you'll be touching, it's worth installing from the start.&lt;/p&gt;

&lt;h2&gt;
  
  
  Turning the response into something usable
&lt;/h2&gt;

&lt;p&gt;A raw JSON array is fine for a script. For anything you're going to analyze, you want it in pandas.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;pandas&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;

&lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://eodhd.com/api/eod/AAPL.US&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;api_token&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;YOUR_TOKEN&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;from&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2025-01-01&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;period&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;d&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;fmt&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;json&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;DataFrame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;date&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_datetime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;date&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set_index&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;date&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;inplace&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;annual_return&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;adjusted_close&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;iloc&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;adjusted_close&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;iloc&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;AAPL return: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;annual_return&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nine lines, and you've got a proper time-indexed DataFrame with a return calculation that actually accounts for corporate actions. Try the same thing with a raw close from a scraped page and watch your number quietly drift every time there's a split.&lt;/p&gt;

&lt;p&gt;From here you can build:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a stock screener that filters by return over a custom window&lt;/li&gt;
&lt;li&gt;a risk model comparing volatility across a basket of tickers&lt;/li&gt;
&lt;li&gt;a scheduled job that refreshes a portfolio dashboard every night after market close&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Where this fits into agentic workflows
&lt;/h2&gt;

&lt;p&gt;The EOD endpoint on its own is a data pull. Wire it into an MCP server or a connector API, and it becomes something an AI agent can query on its own, without you writing a new integration every time you want to ask a different question.&lt;/p&gt;

&lt;p&gt;EODHD ships an official MCP server with two versions: v1 authenticates with an API key in the URL and works with ChatGPT, Claude Code, and custom agents. v2 runs OAuth 2.1 with dynamic client registration, built for Claude Desktop and other MCP clients with native OAuth support. Either version exposes 72 read-only tools across market data, fundamentals, news, and technicals, plus over 100 pages of API documentation embedded as MCP resources, so an agent can look up parameter names and plan coverage without spending an API call to do it.&lt;/p&gt;

&lt;p&gt;In practice, that means you can point an agent at the server and ask a question in plain language instead of writing a new integration for it. Here's what that looks like calling Claude directly with the EODHD MCP server attached:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;anthropic&lt;/span&gt;

&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;anthropic&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Anthropic&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;claude-sonnet-4-6&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;max_tokens&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;mcp_servers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;url&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;url&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://mcp.eodhd.com/v2/mcp&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;eodhd-mcp&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;role&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;user&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Pull McDonald&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;s daily closes for Q1 2024 and flag any day where volume was 3x the 30-day average.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No hardcoded ticker logic, no separate script for volume spikes. The agent calls the EOD tool, pulls the window it needs, computes the rolling average, and flags the outliers, all inside one request. Change the question next week ("do the same for AAPL and TSLA, compare them") and there's no new code to write, just a different prompt.&lt;/p&gt;

&lt;p&gt;That's the shift agentic AI brings to financial data: the API doesn't just answer one hardcoded question, it becomes something an agent can reason over.&lt;/p&gt;

&lt;p&gt;If you're building or advising on this kind of stack, this is worth testing directly:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Building an AI agent that needs financial data?&lt;/strong&gt;&lt;br&gt;
The EODHD MCP server gives your agent structured access to 30+ years of price history, no custom API wrapper required.&lt;br&gt;
&lt;strong&gt;→ &lt;a href="https://eodhd.com/?via=kmg&amp;amp;ref1=Meneses&amp;amp;utm_source=medium&amp;amp;utm_medium=post&amp;amp;utm_campaign=end-of-day-stock-data-api-python&amp;amp;utm_content=Meneses" rel="noopener noreferrer"&gt;Explore the MCP server&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Key takeaways
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;adjusted_close&lt;/code&gt; is the number you want for return calculations, not &lt;code&gt;close&lt;/code&gt;. Confusing the two is the single most common mistake in this space.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;filter&lt;/code&gt; parameter (&lt;code&gt;last_close&lt;/code&gt;, &lt;code&gt;last_volume&lt;/code&gt;, etc.) skips full-series parsing when you only need one number.&lt;/li&gt;
&lt;li&gt;Web scraping and free sources like Yahoo Finance trade reliability for convenience. That trade gets expensive the moment your analysis needs to be reproducible.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  FAQs
&lt;/h2&gt;

&lt;p&gt;❓ Is there a free end-of-day stock data API?&lt;br&gt;
✅ Yes. EODHD's free plan includes the End-of-Day API with 20 calls per day, limited to the past year of history. The DEMO key gives unlimited access to six test tickers with no signup at all.&lt;/p&gt;

&lt;p&gt;❓ What's the difference between close and adjusted_close?&lt;br&gt;
✅ &lt;code&gt;close&lt;/code&gt; is the raw traded price for that day. &lt;code&gt;adjusted_close&lt;/code&gt; factors in every split and dividend since, so historical prices stay comparable across time. Always use &lt;code&gt;adjusted_close&lt;/code&gt; for return calculations and charting.&lt;/p&gt;

&lt;p&gt;❓ Can an AI agent query this data directly?&lt;br&gt;
✅ Yes, through EODHD's official MCP server. It exposes 72 tools over the Model Context Protocol so agents like Claude can request market data, fundamentals, and technicals directly in a conversation, without a custom API integration.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Looking for technical content for your company? I can help — &lt;a href="https://www.linkedin.com/in/kevin-meneses-gonzalez/" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt; · &lt;a href="mailto:kevinmenesesgonzalez@gmail.com"&gt;kevinmenesesgonzalez@gmail.com&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Want to see more breakdowns like this?&lt;/strong&gt;&lt;br&gt;
I write about fintech APIs, Python, and AI agent tooling.&lt;br&gt;
&lt;strong&gt;→ &lt;a href="https://kevinmeneses.com/en" rel="noopener noreferrer"&gt;Visit kevinmeneses.com&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>data</category>
      <category>api</category>
      <category>stocks</category>
      <category>mcp</category>
    </item>
    <item>
      <title>Build an AI Financial Agent with EODHD's MCP Server</title>
      <dc:creator>Kevin Meneses González</dc:creator>
      <pubDate>Fri, 14 Aug 2026 17:48:59 +0000</pubDate>
      <link>https://dev.to/kevin_menesesgonzlez/build-an-ai-financial-agent-with-eodhds-mcp-server-4pjc</link>
      <guid>https://dev.to/kevin_menesesgonzlez/build-an-ai-financial-agent-with-eodhds-mcp-server-4pjc</guid>
      <description>&lt;p&gt;Most people building financial AI agents connect four or five APIs by hand.&lt;/p&gt;

&lt;p&gt;One for prices. One for fundamentals. One for news. One for earnings dates.&lt;/p&gt;

&lt;p&gt;The problem isn't lack of data. It's fragmentation.&lt;/p&gt;

&lt;p&gt;If you're:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;building a research copilot for equities,&lt;/li&gt;
&lt;li&gt;automating pre-earnings screening,&lt;/li&gt;
&lt;li&gt;or designing an agent that reasons across price, fundamentals, and sentiment,&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;this matters.&lt;/p&gt;

&lt;p&gt;Here's how to build an AI financial agent that pulls all four together, using EODHD's financial data API as the backbone.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Traditional Financial Data API Approach Breaks Down Fast
&lt;/h2&gt;

&lt;p&gt;Here's what building a financial agent usually looks like with a plain REST API.&lt;/p&gt;

&lt;p&gt;You write a function to fetch prices. Another to fetch fundamentals. Another for news. Then you write orchestration logic so your agent knows which endpoint to call, in what order, and how to merge the responses into something the model can reason about.&lt;/p&gt;

&lt;p&gt;Each new data type means a new wrapper function.&lt;/p&gt;

&lt;p&gt;Each wrapper function is a new failure point.&lt;/p&gt;

&lt;p&gt;And every time the agent needs to answer a question that spans two data types, like "is this company's fundamentals strong enough to justify holding through earnings," you're the one stitching the context together, not the model.&lt;/p&gt;

&lt;p&gt;Developers usually discover this the hard way. The agent works fine in a demo with one data source. Then a real question comes in that needs prices, fundamentals, and news at once, and the whole orchestration layer has to be rebuilt.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Real Problem Is Context, Not Data
&lt;/h2&gt;

&lt;p&gt;Financial agents don't need more APIs.&lt;/p&gt;

&lt;p&gt;They need a way to reach every data type through one consistent interface, so the model can decide what to call and combine it, instead of you hardcoding that logic in advance.&lt;/p&gt;

&lt;p&gt;That's what MCP (Model Context Protocol) is built for.&lt;/p&gt;

&lt;h2&gt;
  
  
  What an MCP Server Changes for Financial Data APIs
&lt;/h2&gt;

&lt;p&gt;MCP (Model Context Protocol) standardizes how an AI model discovers and calls external tools. Instead of you writing a custom wrapper for every endpoint, the model sees a list of available tools with their schemas, and decides which ones to call based on the question it's answering. It's the same underlying idea as Claude function calling, just extended to a whole catalog of tools instead of one function at a time.&lt;/p&gt;

&lt;p&gt;For financial data, this matters because a single question rarely maps to a single endpoint.&lt;/p&gt;

&lt;p&gt;EODHD ships an official MCP server with over 70 tools covering fundamentals, historical and real-time prices, earnings calendars, news, and sentiment. You point your agent at the server, and it gets access to all of it without you writing a single API wrapper.&lt;/p&gt;

&lt;blockquote&gt;
&lt;strong&gt;Skip the wrapper functions&lt;/strong&gt;&lt;br&gt;
EODHD's MCP server exposes 70+ financial data tools out of the box, ready for Claude and other AI agents.&lt;br&gt;
&lt;strong&gt;→ &lt;a href="https://eodhd.com/?via=kmg&amp;amp;ref1=Meneses&amp;amp;utm_source=medium&amp;amp;utm_medium=post&amp;amp;utm_campaign=build-ai-financial-agent-eodhd-mcp&amp;amp;utm_content=Meneses" rel="noopener noreferrer"&gt;Get your EODHD API key&lt;/a&gt;&lt;/strong&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Building the AI Financial Agent with EODHD's MCP Server
&lt;/h2&gt;

&lt;p&gt;Let's build an agent that can answer a question spanning multiple data types in a single call: fundamentals, upcoming earnings, and recent price action for a given ticker.&lt;/p&gt;

&lt;h3&gt;
  
  
  Setup
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;anthropic
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You'll need an EODHD API token and an Anthropic API key. EODHD's MCP server is available as a hosted endpoint, so there's no server to run yourself.&lt;/p&gt;

&lt;h3&gt;
  
  
  Connecting the agent to EODHD's MCP server in Python
&lt;/h3&gt;

&lt;p&gt;EODHD's MCP server has two versions: v1 takes your API key directly in the URL, v2 uses OAuth 2.1 for clients that support it (like Claude Desktop). For a server-side script like this one, v1 is simpler.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;anthropic&lt;/span&gt;

&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;anthropic&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Anthropic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;YOUR_ANTHROPIC_API_KEY&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;EODHD_API_KEY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;YOUR_EODHD_API_TOKEN&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;claude-sonnet-4-6&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;max_tokens&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1500&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;role&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;user&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Give me a quick pre-earnings health check on AAPL: &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;key fundamentals, the next earnings date, and how the &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;stock has moved over the last month.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;mcp_servers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;url&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;url&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://mcp.eodhd.com/v1/mcp?apikey=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;EODHD_API_KEY&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;eodhd-mcp&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;block&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;type&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;block&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  What's actually happening here
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;mcp_servers&lt;/code&gt; parameter is the whole trick. You're not calling three endpoints and merging the results yourself. You're handing the model a connection to EODHD's tool catalog and letting it decide what it needs.&lt;/p&gt;

&lt;p&gt;For this one prompt, the model will typically:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Call a fundamentals tool to pull margins, revenue growth, and valuation ratios&lt;/li&gt;
&lt;li&gt;Call an earnings calendar tool to find the next reporting date&lt;/li&gt;
&lt;li&gt;Call a historical prices tool to compute the recent price move&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Three tool calls, one conversation, zero orchestration code written by you.&lt;/p&gt;

&lt;h3&gt;
  
  
  Reading the response
&lt;/h3&gt;

&lt;p&gt;The API returns a mix of content blocks: &lt;code&gt;text&lt;/code&gt; for the model's answer, &lt;code&gt;mcp_tool_use&lt;/code&gt; for each tool it called, and &lt;code&gt;mcp_tool_result&lt;/code&gt; for the raw data. If you want to inspect what the agent actually pulled (useful for debugging or logging), filter by block type instead of assuming a fixed order:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;tool_calls&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;input&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;input&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;type&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;mcp_tool_use&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;call&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;tool_calls&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;call&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;call&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;input&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is worth doing early. It shows you exactly which tools the model reached for, which helps you spot when it's calling something unnecessary or missing a data type your prompt implied.&lt;/p&gt;

&lt;h2&gt;
  
  
  Scoring Stock Opportunities with EODHD's Financial Data API
&lt;/h2&gt;

&lt;p&gt;The MCP agent above decides on its own which EODHD tools to call. That's convenient, but it also hides what's happening. If you want to actually understand (and customize) the logic behind "which stocks look like good opportunities," it helps to call the same EODHD endpoints directly and build the scoring yourself.&lt;/p&gt;

&lt;p&gt;This is also useful if you want a scheduled script that scans the market every morning instead of waiting for you to ask a chat agent.&lt;/p&gt;

&lt;p&gt;Let's build that: a short pipeline that pulls candidates from the Screener API, pulls fundamentals and recent price action for each one, checks the news sentiment, and combines all of it into a single opportunity score.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Shortlist candidates with the Screener API
&lt;/h3&gt;

&lt;p&gt;Instead of pulling fundamentals for thousands of tickers, start narrow. The Screener API filters the entire market down to a shortlist in one request.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;

&lt;span class="n"&gt;API_TOKEN&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;YOUR_EODHD_API_TOKEN&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_candidates&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://eodhd.com/api/screener&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;api_token&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;API_TOKEN&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sort&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;market_capitalization.desc&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;filters&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;[[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;market_capitalization&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;,&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;gt;&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;,1000000000],&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
                   &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;exchange&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;,&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;,&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;us&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;],&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
                   &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sector&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;,&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;,&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Technology&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;]]&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;limit&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;fmt&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;json&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;data&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="n"&gt;candidates&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_candidates&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;tickers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;code&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;candidates&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tickers&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This pulls US tech stocks above $1B market cap, sorted by size. Swap the filters for whatever criteria define a "good price" to you: low P/E, high dividend yield, positive EPS growth, or a 52-week low signal.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2: Pull fundamentals for each candidate
&lt;/h3&gt;

&lt;p&gt;Once you have a shortlist, fetch the fundamentals for each ticker. This is where you check whether the price is actually backed by solid financials, not just cheap for a reason.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_fundamentals&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;symbol&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://eodhd.com/api/fundamentals/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;symbol&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;.US&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;api_token&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;API_TOKEN&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;fmt&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;json&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="n"&gt;highlights&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Highlights&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{})&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pe_ratio&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;highlights&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;PERatio&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;peg_ratio&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;highlights&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;PEGRatio&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;profit_margin&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;highlights&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ProfitMargin&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;eps_growth&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;highlights&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;EPSEstimateNextYear&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 3: Check recent price action
&lt;/h3&gt;

&lt;p&gt;Fundamentals tell you if a company is healthy. Price history tells you if the market has already priced that in, or if there's a gap worth paying attention to.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;pandas&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timedelta&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_price_trend&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;symbol&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://eodhd.com/api/eod/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;symbol&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;.US&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;api_token&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;API_TOKEN&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;period&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;d&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;order&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;d&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;from&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;today&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nf"&gt;timedelta&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;days&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;90&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;isoformat&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;fmt&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;json&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;prices&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;df&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;DataFrame&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prices&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;last_close&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;iloc&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;close&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;month_ago_close&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;iloc&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;21&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;close&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;21&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;iloc&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;close&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;change_pct&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;last_close&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;month_ago_close&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;month_ago_close&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;last_close&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;last_close&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;change_30d_pct&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;change_pct&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A stock with strong fundamentals that just dropped 15% in a month is a very different opportunity than one that already ran up 40%.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 4: Read the news sentiment
&lt;/h3&gt;

&lt;p&gt;Price and fundamentals don't tell you why a stock moved. Sentiment fills that gap, and it's often what separates a real opportunity from a value trap.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_sentiment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;symbol&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://eodhd.com/api/sentiments&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;s&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;symbol&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;.US&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;api_token&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;API_TOKEN&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;from&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;today&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nf"&gt;timedelta&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;days&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;14&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;isoformat&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;fmt&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;json&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;entries&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;symbol&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;.US&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[])&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;avg_sentiment&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;avg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;normalized&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;avg_sentiment&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;avg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 5: Combine everything into a single score
&lt;/h3&gt;

&lt;p&gt;This is the part a raw API can't do for you. Each endpoint gives you one dimension. Deciding what a "good opportunity" means is a judgment call, and it belongs in your code, not buried in someone else's black-box score.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;score_opportunity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;symbol&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;fundamentals&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_fundamentals&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;symbol&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;price&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_price_trend&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;symbol&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;sentiment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_sentiment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;symbol&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;score&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;fundamentals&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pe_ratio&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;fundamentals&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pe_ratio&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;25&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;score&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;fundamentals&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;profit_margin&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;fundamentals&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;profit_margin&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mf"&gt;0.10&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;score&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;price&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;change_30d_pct&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;score&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;sentiment&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;avg_sentiment&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;sentiment&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;avg_sentiment&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mf"&gt;0.15&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;score&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;symbol&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;symbol&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;score&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;score&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;fundamentals&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;price&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;sentiment&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nf"&gt;score_opportunity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;tickers&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="n"&gt;ranked&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;sorted&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;score&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;reverse&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;ranked&lt;/span&gt;&lt;span class="p"&gt;[:&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;symbol&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;score:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;score&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;| P/E:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;pe_ratio&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
          &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;| 30d change:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;change_30d_pct&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;%&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
          &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;| sentiment:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;avg_sentiment&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Four data points, one loop, one ranked list. Reasonable valuation, healthy margins, a recent dip, and improving sentiment together are a much stronger signal than any one of them alone.&lt;/p&gt;

&lt;p&gt;This scoring logic is intentionally simple. You'll want to weight it differently depending on your strategy: a value investor cares more about P/E and margins, a swing trader cares more about the price drop and sentiment shift. The point is that the four EODHD endpoints give you the raw material, and the decision logic is yours to tune.&lt;/p&gt;

&lt;p&gt;None of this is financial advice. It's a framework for turning scattered data into a shortlist worth researching further, not a signal to buy.&lt;/p&gt;

&lt;h3&gt;
  
  
  Wiring this back into the agent
&lt;/h3&gt;

&lt;p&gt;Once this scoring pipeline works as a standalone script, you can expose it to your MCP agent as a custom tool alongside EODHD's built-in ones. Then a prompt like "find me tech stocks that dropped recently but still look fundamentally solid" runs your exact scoring logic instead of the model guessing at criteria on its own.&lt;/p&gt;

&lt;p&gt;That's the real advantage of combining direct API calls with MCP: you get full control over the decision logic, and the model still handles the natural-language layer on top of it.&lt;/p&gt;

&lt;p&gt;This is, in practice, what it means to build an AI financial agent: not a single clever prompt, but a data layer you trust plus a model that knows when to reach for it.&lt;/p&gt;

&lt;h2&gt;
  
  
  From Here You Can Build
&lt;/h2&gt;

&lt;p&gt;Once the agent can combine fundamentals, prices, earnings, and news on its own, the use cases stop being single-question demos.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;pre-earnings screeners that flag weak fundamentals before a reporting date&lt;/li&gt;
&lt;li&gt;sentiment-aware alerts that combine news tone with price moves&lt;/li&gt;
&lt;li&gt;research copilots that answer multi-part questions without a rigid script&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of this requires new wrapper code. It requires better prompts and, occasionally, a narrower system prompt telling the agent which tools to prioritize.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key Takeaways
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Traditional REST integrations force you to write and maintain orchestration logic for every combination of data types&lt;/li&gt;
&lt;li&gt;MCP moves that orchestration into the model itself, so one conversation can span fundamentals, prices, earnings, and news&lt;/li&gt;
&lt;li&gt;Combining the Screener, fundamentals, price, and sentiment endpoints directly gives you full control over what counts as a "good opportunity," instead of relying on a single metric&lt;/li&gt;
&lt;li&gt;EODHD's MCP server gives you 70+ financial tools without writing a single wrapper function, and you can add your own scoring logic as a custom tool on top of it&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  FAQs
&lt;/h2&gt;

&lt;p&gt;❓ &lt;strong&gt;Do I need to run my own MCP server to use EODHD's tools?&lt;/strong&gt;&lt;br&gt;
✅ No. EODHD hosts the MCP server, so you connect to it with a URL and your API token, the same way you'd call a REST endpoint.&lt;/p&gt;

&lt;p&gt;❓ &lt;strong&gt;Does MCP replace the EODHD REST API?&lt;/strong&gt;&lt;br&gt;
✅ No, it sits on top of it. The REST API still powers every tool call, MCP just gives the AI model a structured way to discover and use those endpoints without custom wrapper code.&lt;/p&gt;

&lt;p&gt;❓ &lt;strong&gt;Can I limit which tools the agent has access to?&lt;/strong&gt;&lt;br&gt;
✅ Yes. You can scope the system prompt or restrict the conversation to specific tool categories if you don't want the agent reaching for endpoints outside a given use case, like keeping it to fundamentals and earnings only.&lt;/p&gt;

&lt;p&gt;❓ &lt;strong&gt;Is this approach only useful with Claude?&lt;/strong&gt;&lt;br&gt;
✅ No. MCP is an open protocol, so EODHD's server works with any MCP-compatible client, including ChatGPT, Cursor, and Windsurf, not just Claude.&lt;/p&gt;

&lt;p&gt;❓ &lt;strong&gt;How many API calls does a scan like this use?&lt;/strong&gt;&lt;br&gt;
✅ It depends on your shortlist size. Each screener request counts as 5 calls, and each fundamentals, price, or sentiment call for a ticker counts as 1. Scanning 20 candidates works out to roughly 65 calls, well within EODHD's free tier for occasional runs.&lt;/p&gt;

&lt;p&gt;❓ &lt;strong&gt;Can I use this scoring script to automate actual trades?&lt;/strong&gt;&lt;br&gt;
✅ The script only ranks and prints candidates. Connecting it to a broker's execution API is a separate step, and it's worth adding manual review before any live-money decision, no matter how the shortlist was generated.&lt;/p&gt;




&lt;p&gt;If you're a software or API company looking to explain your product through high-quality educational content (not marketing fluff), feel free to connect with me on LinkedIn.&lt;/p&gt;

&lt;blockquote&gt;
&lt;strong&gt;Want more content like this?&lt;/strong&gt;&lt;br&gt;
Python, APIs, fintech, and AI agent tutorials for developers.&lt;br&gt;
&lt;strong&gt;→ &lt;a href="https://kevinmeneses.com/en" rel="noopener noreferrer"&gt;Visit kevinmeneses.com&lt;/a&gt;&lt;/strong&gt;
&lt;/blockquote&gt;




&lt;p&gt;&lt;em&gt;Looking for technical content for your company? I can help — &lt;a href="https://www.linkedin.com/in/kevin-meneses-gonzalez/" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt; · &lt;a href="mailto:kevinmenesesgonzalez@gmail.com"&gt;kevinmenesesgonzalez@gmail.com&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>stocks</category>
      <category>api</category>
    </item>
    <item>
      <title>Best 5 Browser Automation API for AI Agents in 2026</title>
      <dc:creator>Kevin Meneses González</dc:creator>
      <pubDate>Thu, 06 Aug 2026 08:04:53 +0000</pubDate>
      <link>https://dev.to/kevin_menesesgonzlez/best-5-browser-automation-api-for-ai-agents-in-2026-5hcg</link>
      <guid>https://dev.to/kevin_menesesgonzlez/best-5-browser-automation-api-for-ai-agents-in-2026-5hcg</guid>
      <description>&lt;p&gt;Every team building browser automation hits the same fork in the road. Manage headless Chrome yourself, or hand the whole problem to an API.&lt;/p&gt;

&lt;p&gt;Browserless.io built this category. It turned "run a headless browser at scale" from a DevOps project into a single endpoint.&lt;/p&gt;

&lt;p&gt;The question worth asking in 2026 isn't whether Browserless works. It's what actually competes with it, and when one of those alternatives fits your stack better.&lt;/p&gt;

&lt;p&gt;If you're:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;scraping data at scale,&lt;/li&gt;
&lt;li&gt;running AI agents that need to browse the web,&lt;/li&gt;
&lt;li&gt;or automating screenshots, PDFs, and testing pipelines,&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;this comparison is for you.&lt;/p&gt;

&lt;h2&gt;
  
  
  What a Managed Headless Browser API Actually Needs to Solve
&lt;/h2&gt;

&lt;p&gt;A "headless browser API" sounds simple on paper. Send a URL, get back HTML, a screenshot, or a PDF.&lt;/p&gt;

&lt;p&gt;In production, the real problems show up fast:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Browsers crash and leak memory under load&lt;/li&gt;
&lt;li&gt;Websites detect and block automated traffic&lt;/li&gt;
&lt;li&gt;CAPTCHAs interrupt otherwise working scripts&lt;/li&gt;
&lt;li&gt;Sessions need to be debugged when an agent silently fails&lt;/li&gt;
&lt;li&gt;Scaling from 10 to 10,000 concurrent sessions breaks naive setups&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Every vendor in this space is really selling the same thing: someone else managing that complexity so you don't have to.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Real Differentiator Isn't the Feature List
&lt;/h2&gt;

&lt;p&gt;Most comparison articles line up checkboxes. Stealth mode, yes or no. CAPTCHA solving, yes or no. Proxy rotation, yes or no.&lt;/p&gt;

&lt;p&gt;That's not where these tools actually diverge.&lt;/p&gt;

&lt;p&gt;The real differentiator is production reliability, plus how a vendor responds when something breaks at 2am on a Sunday. A landing page can promise stealth browsing. It can't promise someone will answer your support ticket in twenty minutes.&lt;/p&gt;

&lt;p&gt;That's the lens for this comparison.&lt;/p&gt;

&lt;h2&gt;
  
  
  5 Headless Browser APIs Worth Evaluating in 2026
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Browserless.io
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://www.browserless.io/" rel="noopener noreferrer"&gt;Browserless.io&lt;/a&gt; provides managed headless browser infrastructure built specifically for developers and engineering teams.&lt;/p&gt;

&lt;p&gt;Its core offerings are BrowserQL (a stealth-first query language for bot bypass and structured data extraction), Browsers as a Service (a pooled cloud of Chromium, Firefox, and WebKit instances compatible with Puppeteer and Playwright), and straightforward REST APIs for screenshots, PDFs, and scraping. It also solves CAPTCHAs automatically, including reCAPTCHA, hCaptcha, and Cloudflare Turnstile.&lt;/p&gt;

&lt;p&gt;The platform abstracts away browser lifecycle management: version updates, memory leaks, scaling, and detection avoidance become one API endpoint instead of a maintenance burden.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pros&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;BrowserQL gives you stealth and structured extraction without hand-rolling anti-detection logic&lt;/li&gt;
&lt;li&gt;Supports Puppeteer, Playwright, and Selenium with a one-line connection change&lt;/li&gt;
&lt;li&gt;Self-hosting is available (through providers like Sliplane) if you want fixed infrastructure costs instead of per-unit billing&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Cons&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Paid plans range from $25/month (Prototyping) up to $350/month (Scale), and cost can climb quickly with concurrency&lt;/li&gt;
&lt;li&gt;The free tier caps out at 1,000 units/month, which disappears fast in a real scraping job&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; teams that need full headless browser control (Puppeteer or Playwright scripts, PDF generation, multi-page navigation flows) without building that infrastructure themselves.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Looking for technical content that explains your API like this?&lt;/strong&gt;&lt;br&gt;
I write educational, SEO-optimized articles for developer tools and infrastructure companies, not marketing fluff.&lt;br&gt;
&lt;strong&gt;→ &lt;a href="https://www.linkedin.com/in/kevin-meneses-gonzalez/" rel="noopener noreferrer"&gt;Let's talk on LinkedIn&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  2. Hyperbrowser.ai
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://www.hyperbrowser.ai/" rel="noopener noreferrer"&gt;Hyperbrowser.ai&lt;/a&gt; positions itself as internet infrastructure built specifically for AI agents rather than general-purpose scraping.&lt;/p&gt;

&lt;p&gt;It provides instant, scalable browser infrastructure with built-in CAPTCHA solving, proxy management, and anti-bot detection, plus HyperAgent, an AI-native automation framework that layers natural language commands (&lt;code&gt;page.ai()&lt;/code&gt;, &lt;code&gt;page.extract()&lt;/code&gt;) on top of standard Playwright.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pros&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Built around agent workflows from day one, not scraping retrofitted for agents&lt;/li&gt;
&lt;li&gt;Falls back to regular Playwright when you don't need the AI layer, so you're not locked into one paradigm&lt;/li&gt;
&lt;li&gt;Backed by Y Combinator, Accel, and SV Angel, with active engineering hiring&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Cons&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Small team (4 employees as of 2026), which means less dedicated support bandwidth than a larger vendor&lt;/li&gt;
&lt;li&gt;Younger platform with a smaller track record at large scale compared to Browserless&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; teams building AI agents that need to browse and extract data using natural language instructions instead of brittle selector-based scripts.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Steel.dev
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://steel.dev/" rel="noopener noreferrer"&gt;Steel.dev&lt;/a&gt; is an open-source browser API purpose-built for AI agents, with a clear focus on speed and observability.&lt;/p&gt;

&lt;p&gt;Its published lifecycle benchmark shows Steel completing a create-connect-navigate-release cycle in an average of 0.89 seconds, which the team reports as 1.70x to 8.95x faster than other providers tested under the same conditions. It adds live session viewers, MP4 replays, and Agent Logs, a timeline of every click, navigation, and scroll an agent takes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pros&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Open source, so you can self-host and audit exactly what's running&lt;/li&gt;
&lt;li&gt;Agent Logs and session replay make debugging failed automation runs far less painful&lt;/li&gt;
&lt;li&gt;Fast session starts matter directly for agent workloads that spin up and tear down browsers repeatedly&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Cons&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Early-stage company; smaller team and less enterprise track record than Browserless&lt;/li&gt;
&lt;li&gt;Fewer built-in integrations outside the core Puppeteer/Playwright/Selenium compatibility layer&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; teams that want an open-source, self-hostable browser API and care about debugging tools as much as raw automation.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Surfsky.io
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://surfsky.io/" rel="noopener noreferrer"&gt;Surfsky.io&lt;/a&gt; is built around one specific problem: bypassing advanced anti-bot systems that block most standard headless browser setups.&lt;/p&gt;

&lt;p&gt;It targets CAPTCHA, WAF, and bot-detection bypass as its primary differentiator, positioning itself directly against Browserbase and ZenRows on bypass reliability rather than general feature parity. The platform offers an API-first architecture designed to integrate with existing automation frameworks.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pros&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Purpose-built for the hardest scraping targets: sites with aggressive anti-bot protection&lt;/li&gt;
&lt;li&gt;Cloud-based infrastructure designed for scale without manual proxy management&lt;/li&gt;
&lt;li&gt;API-first design means less glue code to plug it into an existing pipeline&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Cons&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Narrower focus than Browserless. Less useful if PDF generation, screenshots, or general testing automation are your primary use case&lt;/li&gt;
&lt;li&gt;Smaller, less established brand compared to the other tools on this list&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; teams whose main blocker is bypassing sophisticated bot detection on specific high-value targets, not general-purpose browser automation.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Gaffa
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://gaffa.dev/" rel="noopener noreferrer"&gt;Gaffa&lt;/a&gt; is a lightweight API for web scraping and browser automation aimed at developers who want one API call instead of managing browser infrastructure.&lt;/p&gt;

&lt;p&gt;It handles residential proxies, scaling, and failure recovery internally, and supports actions like scrolling, clicking, and extracting page data as images, Markdown, HTML, or LLM-structured JSON. Credits are charged for actual browser execution time and bandwidth, with no fixed infrastructure costs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pros&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Simple, single-endpoint design with low setup overhead&lt;/li&gt;
&lt;li&gt;Native output as clean Markdown or JSON, useful for feeding LLM and RAG pipelines directly&lt;/li&gt;
&lt;li&gt;Pay-for-usage pricing with no fixed infrastructure fees&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Cons&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Indie, early-stage project with a very small team, so support and roadmap velocity are unproven at scale&lt;/li&gt;
&lt;li&gt;Fewer public case studies or production references compared to established vendors&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; solo developers or small teams that want a minimal, low-overhead scraping and automation API without evaluating a full enterprise platform.&lt;/p&gt;

&lt;h2&gt;
  
  
  Headless Browser API Comparison Table
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;Team size&lt;/th&gt;
&lt;th&gt;Pricing model&lt;/th&gt;
&lt;th&gt;Stealth / anti-bot&lt;/th&gt;
&lt;th&gt;Self-host option&lt;/th&gt;
&lt;th&gt;Best for&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://www.browserless.io/" rel="noopener noreferrer"&gt;Browserless.io&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Established&lt;/td&gt;
&lt;td&gt;$25 to $350/month tiers, free tier (1,000 units)&lt;/td&gt;
&lt;td&gt;BrowserQL stealth mode, CAPTCHA solving&lt;/td&gt;
&lt;td&gt;Yes (via Sliplane, ~€9/month)&lt;/td&gt;
&lt;td&gt;Full Puppeteer/Playwright control at production scale&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://www.hyperbrowser.ai/" rel="noopener noreferrer"&gt;Hyperbrowser.ai&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;~4 employees&lt;/td&gt;
&lt;td&gt;Usage-based, free to start&lt;/td&gt;
&lt;td&gt;Built-in CAPTCHA + anti-bot&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;AI-agent-native automation with natural language commands&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://steel.dev/" rel="noopener noreferrer"&gt;Steel.dev&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Early-stage&lt;/td&gt;
&lt;td&gt;Open source, self-hosted or managed&lt;/td&gt;
&lt;td&gt;Stealth browsing patches&lt;/td&gt;
&lt;td&gt;Yes (open source)&lt;/td&gt;
&lt;td&gt;Self-hosted browser API with strong debugging tools&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://surfsky.io/" rel="noopener noreferrer"&gt;Surfsky.io&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Small&lt;/td&gt;
&lt;td&gt;Not publicly listed&lt;/td&gt;
&lt;td&gt;Advanced anti-bot / WAF bypass&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Bypassing aggressive bot detection on specific targets&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://gaffa.dev/" rel="noopener noreferrer"&gt;Gaffa&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Indie / micro-team&lt;/td&gt;
&lt;td&gt;Pay-per-execution, no fixed cost&lt;/td&gt;
&lt;td&gt;Stealth mode with residential proxies&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Solo devs needing a simple, low-overhead API&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Key Takeaways
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Browserless.io remains the safest default pick if you need full browser control (Puppeteer, Playwright, PDF generation) at production scale, backed by a mature platform and a self-hosting path.&lt;/li&gt;
&lt;li&gt;If your workload is AI-agent-specific, Hyperbrowser.ai and Steel.dev are worth testing for their agent-native tooling and debugging features.&lt;/li&gt;
&lt;li&gt;If bot detection bypass is your single hardest problem, Surfsky.io is a narrower but more targeted option than a general-purpose platform.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The smaller vendors on this list (Hyperbrowser, Steel.dev, Surfsky.io, Gaffa) can move faster on support and roadmap requests precisely because they're small. That's a real advantage, but it comes with less production history than Browserless.io has built over time.&lt;/p&gt;

&lt;p&gt;For most teams evaluating a headless browser API in 2026, that trade-off, and not the feature list, is the decision that actually matters.&lt;/p&gt;




&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Building a developer tool in this space?&lt;/strong&gt;&lt;br&gt;
I create technical, SEO-optimized content like this comparison for API and infrastructure companies. If that's useful for your product, let's talk.&lt;br&gt;
&lt;strong&gt;→ &lt;a href="https://www.linkedin.com/in/kevin-meneses-gonzalez/" rel="noopener noreferrer"&gt;Connect with me on LinkedIn&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;More technical writing and projects:&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;→ &lt;a href="https://kevinmeneses.com/en" rel="noopener noreferrer"&gt;kevinmeneses.com&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;&lt;em&gt;Looking for technical content for your company? I can help — &lt;a href="https://www.linkedin.com/in/kevin-meneses-gonzalez/" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt; · &lt;a href="mailto:kevinmenesesgonzalez@gmail.com"&gt;kevinmenesesgonzalez@gmail.com&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>api</category>
      <category>automation</category>
      <category>agents</category>
    </item>
    <item>
      <title>How to Build an AI Trading Bot with Claude and the EODHD API</title>
      <dc:creator>Kevin Meneses González</dc:creator>
      <pubDate>Wed, 05 Aug 2026 13:52:53 +0000</pubDate>
      <link>https://dev.to/kevin_menesesgonzlez/how-to-build-an-ai-trading-bot-with-claude-and-the-eodhd-api-1l2a</link>
      <guid>https://dev.to/kevin_menesesgonzlez/how-to-build-an-ai-trading-bot-with-claude-and-the-eodhd-api-1l2a</guid>
      <description>&lt;p&gt;Most trading bots are just if-statements wearing a costume.&lt;/p&gt;

&lt;p&gt;RSI below 30, buy. Moving averages cross, sell. That works fine until the market does something the rules didn't anticipate, and then the bot keeps executing the same logic anyway, because it can't actually think.&lt;/p&gt;

&lt;p&gt;If you're:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;building AI agents that need to reason over structured data,&lt;/li&gt;
&lt;li&gt;exploring what LLMs can actually do in a finance context,&lt;/li&gt;
&lt;li&gt;or evaluating how to combine market data with automated decision-making,&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;this is for you.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem with rule-based bots
&lt;/h2&gt;

&lt;p&gt;A rule-based bot doesn't understand the market. It pattern-matches against a formula someone wrote months ago.&lt;/p&gt;

&lt;p&gt;Earnings surprise. A sudden news event. A sector rotation nobody coded for. The bot has no way to account for any of it, because it was never built to interpret context, only to check conditions.&lt;/p&gt;

&lt;p&gt;Developers usually discover this the hard way. They backtest a strategy, watch it perform well on historical data, deploy it, and then spend the next few months tweaking thresholds every time the market shifts. The bot isn't wrong. It's just blind.&lt;/p&gt;

&lt;p&gt;There's also the data problem underneath all of this. Plenty of hobby projects lean on scraped Yahoo Finance endpoints or free tiers that cap out fast, and both tend to fail exactly when you need them most.&lt;/p&gt;

&lt;h2&gt;
  
  
  The real problem
&lt;/h2&gt;

&lt;p&gt;A trading bot doesn't need more rules. It needs reasoning.&lt;/p&gt;

&lt;p&gt;That's a different kind of system entirely. Instead of "if X then Y," you want something closer to "given this data, what would a reasonable analyst conclude, and why."&lt;/p&gt;

&lt;p&gt;Large language models are good at exactly that kind of contextual judgment, as long as you feed them clean, structured data instead of asking them to guess.&lt;/p&gt;

&lt;h2&gt;
  
  
  The stack
&lt;/h2&gt;

&lt;p&gt;This tutorial combines two pieces:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;EODHD API&lt;/strong&gt; for market data. End-of-day prices, real-time quotes, fundamentals, and historical data through a single REST interface, without maintaining scrapers that break every time a website changes its HTML.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Claude&lt;/strong&gt; for the reasoning layer. Given a snapshot of price action, volume, and basic fundamentals, Claude produces a structured decision (buy, hold, sell) along with the reasoning behind it, in a format your code can actually parse and act on.&lt;/p&gt;

&lt;p&gt;You could swap either piece out. The point isn't "use these exact two tools forever," it's showing how the pattern works so you can adapt it.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Want reliable market data without building your own scraping layer?&lt;/strong&gt;&lt;br&gt;
EODHD covers over 150,000 tickers with a generous free tier and no rate-limit surprises mid-project.&lt;br&gt;
&lt;strong&gt;&lt;a href="https://eodhd.com/?via=kmg&amp;amp;ref1=Meneses&amp;amp;utm_source=medium&amp;amp;utm_medium=post&amp;amp;utm_campaign=ai-trading-bot-claude-eodhd-api&amp;amp;utm_content=Meneses" rel="noopener noreferrer"&gt;Get your free EODHD API key →&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Setting up
&lt;/h2&gt;

&lt;p&gt;You'll need two API keys: one from EODHD, one from Anthropic.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;pip &lt;span class="nb"&gt;install &lt;/span&gt;requests anthropic python-dotenv
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Store your keys in a &lt;code&gt;.env&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;EODHD_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;your_eodhd_key_here
&lt;span class="nv"&gt;ANTHROPIC_API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;your_anthropic_key_here
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Step 1: Pull market data from EODHD
&lt;/h2&gt;

&lt;p&gt;Start with a function that grabs recent price history and a live quote for a given ticker.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;dotenv&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;load_dotenv&lt;/span&gt;

&lt;span class="nf"&gt;load_dotenv&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;EODHD_KEY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;EODHD_API_KEY&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_market_data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ticker&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;exchange&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;US&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;symbol&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;ticker&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;.&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;exchange&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

    &lt;span class="c1"&gt;# Last 30 days of end-of-day prices
&lt;/span&gt;    &lt;span class="n"&gt;eod_url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://eodhd.com/api/eod/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;symbol&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;eod_params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;api_token&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;EODHD_KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;period&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;d&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;fmt&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;json&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;order&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;d&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;eod_resp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;eod_url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;eod_params&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()[:&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="c1"&gt;# Live quote
&lt;/span&gt;    &lt;span class="n"&gt;quote_url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://eodhd.com/api/real-time/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;symbol&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;quote_params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;api_token&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;EODHD_KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;fmt&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;json&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;quote_resp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;quote_url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;quote_params&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ticker&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;ticker&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;current_price&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;quote_resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;close&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;change_pct&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;quote_resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;change_p&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;volume&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;quote_resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;volume&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;recent_history&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;eod_resp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This gives you a clean payload: current price, percentage change, volume, and 30 days of history. No scraping, no broken HTML selectors.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 2: Ask Claude to reason over the data
&lt;/h2&gt;

&lt;p&gt;This is the part that separates it from a rule-based bot. Instead of hardcoding thresholds, you hand Claude the data and ask for a structured judgment call.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;anthropic&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Anthropic&lt;/span&gt;

&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Anthropic&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ANTHROPIC_API_KEY&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_trading_decision&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;market_data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;prompt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;You are a trading analyst reviewing market data for &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;market_data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;ticker&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;.

Current price: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;market_data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;current_price&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;
Change today: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;market_data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;change_pct&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;%
Volume: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;market_data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;volume&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;
Recent 30-day history: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;market_data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;recent_history&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;

Based on this data, provide a trading decision. Respond ONLY with valid JSON in this exact format:
{{
  &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;decision&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;: &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;buy&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt; | &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;hold&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt; | &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sell&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;,
  &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;confidence&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;: 0.0 to 1.0,
  &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;reasoning&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;: &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;2-3 sentence explanation grounded in the data provided&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;
}}&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;

    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;claude-sonnet-4-6&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;max_tokens&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;300&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;role&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;user&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;}],&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;raw_text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;loads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;raw_text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The key detail here is the response format. Asking for structured JSON, not a paragraph, is what makes this usable in an actual pipeline instead of a chat window.&lt;/p&gt;

&lt;h2&gt;
  
  
  Step 3: Wire it together
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;run_analysis&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ticker&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_market_data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ticker&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;decision&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_trading_decision&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;ticker&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; — Decision: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;decision&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;decision&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;upper&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Confidence: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;decision&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;confidence&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Reasoning: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;decision&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;reasoning&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;decision&lt;/span&gt;


&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;run_analysis&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;AAPL&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Sample output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;AAPL — Decision: HOLD
Confidence: 0.62
Reasoning: Price is up 0.8% on below-average volume, suggesting limited
conviction behind the move. Recent history shows consolidation rather than
a clear trend, so waiting for a volume-confirmed breakout makes more sense
than acting now.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That reasoning field is the whole point. You get a decision plus the logic behind it, which you can log, review, and adjust over time. A rule-based bot never gives you that.&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing it without risking real money
&lt;/h2&gt;

&lt;p&gt;Before connecting this to anything resembling a real account, run it against a paper trading environment.&lt;/p&gt;

&lt;p&gt;Alpaca is a solid option here. Its paper trading API mirrors the live trading endpoints exactly, so you can route Claude's decisions through simulated buy and sell orders and see how the strategy would have performed, with fake money and real market conditions.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;alpaca_trade_api&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;tradeapi&lt;/span&gt;

&lt;span class="n"&gt;alpaca&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tradeapi&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;REST&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ALPACA_API_KEY&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ALPACA_SECRET_KEY&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://paper-api.alpaca.markets&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;execute_paper_trade&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ticker&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;decision&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;decision&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;decision&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;hold&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;No action for &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;ticker&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;, holding position.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;

    &lt;span class="n"&gt;side&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;buy&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;decision&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;decision&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;buy&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sell&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

    &lt;span class="n"&gt;alpaca&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;submit_order&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;symbol&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;ticker&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;qty&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;side&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;side&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nb"&gt;type&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;market&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;time_in_force&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;day&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Paper &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;side&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; order submitted for &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;ticker&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;.&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run &lt;code&gt;execute_paper_trade&lt;/code&gt; after &lt;code&gt;get_trading_decision&lt;/code&gt; and you have a full loop: EODHD for data, Claude for reasoning, Alpaca for execution, all without a single dollar at risk.&lt;/p&gt;

&lt;p&gt;This is also where you'd start tracking accuracy. Log every decision, compare it against what actually happened three or five days later, and you'll quickly see whether the reasoning holds up or needs a better prompt.&lt;/p&gt;

&lt;p&gt;From here you can build:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a scheduler that runs this analysis daily across a watchlist&lt;/li&gt;
&lt;li&gt;a logging layer that scores decisions against actual outcomes&lt;/li&gt;
&lt;li&gt;a risk filter that blocks trades below a confidence threshold&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Key takeaways
&lt;/h2&gt;

&lt;p&gt;An LLM adds contextual reasoning that fixed rules can't replicate on their own.&lt;/p&gt;

&lt;p&gt;Clean, reliable market data matters more than people expect. Claude is only as good as what you feed it, and EODHD removes the guesswork of scraping or rate-limited free APIs.&lt;/p&gt;

&lt;p&gt;This is a starting point, not a production system. Position sizing, stop losses, and proper risk management live outside the scope of this tutorial, and skipping them before going live is how paper gains turn into real losses.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQs
&lt;/h2&gt;

&lt;p&gt;❓ Do I need trading experience to build this?&lt;br&gt;
✅ No. You need basic Python and an understanding of what a buy, hold, or sell decision means. The reasoning comes from Claude, not from you having to encode strategy logic manually.&lt;/p&gt;

&lt;p&gt;❓ Is this real algorithmic trading?&lt;br&gt;
✅ It's a form of it, specifically an LLM-assisted decision layer rather than a pure quantitative model. Traditional algo trading uses fixed mathematical rules; this approach adds a reasoning step on top of the data.&lt;/p&gt;

&lt;p&gt;❓ Can I connect this to a real brokerage account?&lt;br&gt;
✅ Technically yes, since Alpaca's live and paper APIs share the same structure. Don't, until you've backtested extensively and added proper risk controls. This tutorial is educational, not a plug-and-play trading system.&lt;/p&gt;

&lt;p&gt;❓ Does EODHD provide real-time data or only end-of-day?&lt;br&gt;
✅ Both. EODHD offers end-of-day historical data going back years, plus real-time and delayed quotes depending on your plan, which is why it works for both the historical context and the live price checks in this tutorial.&lt;/p&gt;




&lt;p&gt;If you're a software or API company looking to explain your product through high-quality educational content, not marketing fluff, feel free to connect with me on LinkedIn: &lt;a href="https://www.linkedin.com/in/kevin-meneses-gonzalez/" rel="noopener noreferrer"&gt;Kevin Meneses González&lt;/a&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Building something with market data?&lt;/strong&gt;&lt;br&gt;
EODHD gives you 30+ years of historical data, real-time quotes, and fundamentals in one API.&lt;br&gt;
&lt;strong&gt;&lt;a href="https://eodhd.com/?via=kmg&amp;amp;ref1=Meneses&amp;amp;utm_source=medium&amp;amp;utm_medium=post&amp;amp;utm_campaign=ai-trading-bot-claude-eodhd-api&amp;amp;utm_content=Meneses" rel="noopener noreferrer"&gt;Start free with EODHD →&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;More tutorials like this&lt;/strong&gt;&lt;br&gt;
I write about fintech APIs, Python, and AI agents every week.&lt;br&gt;
&lt;strong&gt;&lt;a href="https://kevinmeneses.com/en" rel="noopener noreferrer"&gt;Read more on kevinmeneses.com →&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;&lt;em&gt;Looking for technical content for your company? I can help — &lt;a href="https://www.linkedin.com/in/kevin-meneses-gonzalez/" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt; · &lt;a href="mailto:kevinmenesesgonzalez@gmail.com"&gt;kevinmenesesgonzalez@gmail.com&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>api</category>
      <category>stocks</category>
      <category>trading</category>
    </item>
    <item>
      <title>7 Best OCR Tools with AI in 2026 (Compared)</title>
      <dc:creator>Kevin Meneses González</dc:creator>
      <pubDate>Tue, 04 Aug 2026 08:18:47 +0000</pubDate>
      <link>https://dev.to/kevin_menesesgonzlez/7-best-ocr-tools-with-ai-in-2026-compared-1189</link>
      <guid>https://dev.to/kevin_menesesgonzlez/7-best-ocr-tools-with-ai-in-2026-compared-1189</guid>
      <description>&lt;p&gt;Every "best OCR tool" article online is one of two things: a vendor's own product page, or a listicle that copied the same five names from another listicle. Neither tells you which tool actually fits what you're building.&lt;/p&gt;

&lt;p&gt;This one does.&lt;/p&gt;

&lt;p&gt;If you're:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;digitizing scanned paperwork for a small team,&lt;/li&gt;
&lt;li&gt;building a document pipeline that needs to run unattended,&lt;/li&gt;
&lt;li&gt;or deciding whether an OCR API is worth the integration work,&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;this comparison is built around those three decisions, not around which vendor has the biggest marketing budget.&lt;/p&gt;

&lt;h2&gt;
  
  
  OCR still fails in predictable ways
&lt;/h2&gt;

&lt;p&gt;Optical character recognition has gotten good enough that most people assume it just works now. Feed it a document, get clean text back.&lt;/p&gt;

&lt;p&gt;That assumption breaks down fast on three kinds of input: a photo taken at an angle under bad light, a PDF with a table instead of plain paragraphs, and anything with handwriting. Vendor pages rarely mention this. They quote a single accuracy number, usually from a clean, high-resolution test document that looks nothing like what actually lands in most inboxes.&lt;/p&gt;

&lt;p&gt;The other thing nobody advertises up front: uploading a document to a free online OCR tool means sending its content to someone else's server. For a random screenshot, that's irrelevant. For an ID, a payslip, or a signed contract, it's the first question you should be asking, not the last.&lt;/p&gt;

&lt;h2&gt;
  
  
  The real decision isn't "which OCR is best"
&lt;/h2&gt;

&lt;p&gt;It's whether you need a one-off web tool, an API you call from code, or something self-hosted that never leaves your machine.&lt;/p&gt;

&lt;p&gt;That single choice determines cost, speed, and how much control you have over where your data goes, more than any accuracy benchmark does.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Approach&lt;/th&gt;
&lt;th&gt;How it works&lt;/th&gt;
&lt;th&gt;Best for&lt;/th&gt;
&lt;th&gt;Typical cost&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Web tool (Adobe, Google Lens)&lt;/td&gt;
&lt;td&gt;Upload a file, it's processed on the vendor's server, download the result&lt;/td&gt;
&lt;td&gt;One-off use, low volume&lt;/td&gt;
&lt;td&gt;Free with limits, or a paid plan for full features&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;API (Google Vision, Mistral OCR, Mindee, Unstract)&lt;/td&gt;
&lt;td&gt;You call it from your own app or pipeline&lt;/td&gt;
&lt;td&gt;Volume, automation, product integration&lt;/td&gt;
&lt;td&gt;Per page or per call, usually a few cents&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Self-hosted (Tesseract)&lt;/td&gt;
&lt;td&gt;Runs on your own machine or server, nothing gets uploaded&lt;/td&gt;
&lt;td&gt;Full data control, no recurring fee&lt;/td&gt;
&lt;td&gt;Free, but you handle setup and tuning&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Once you know which lane you're in, picking between tools gets a lot faster. Here are the 7 worth knowing.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Google Cloud Vision API: the default cloud OCR
&lt;/h2&gt;

&lt;p&gt;Vision API is Google's general-purpose image analysis service, and text detection is one feature inside it, alongside label and object detection.&lt;/p&gt;

&lt;p&gt;Google Vision is built for general OCR and image analysis, while specialized services like AWS Textract focus on structured document extraction such as tables and invoices. That distinction matters: Vision reads text well, but it returns raw text and bounding boxes, not structured fields. Pricing runs on a free tier of 1,000 images a month, then roughly $1.50 per 1,000 images for standard text detection, with a separate and considerably more expensive Document AI product for forms and tables.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pros&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Backed by Google's infrastructure, so uptime and language coverage are strong&lt;/li&gt;
&lt;li&gt;Cheap at low-to-mid volume for plain text extraction&lt;/li&gt;
&lt;li&gt;Well-documented Python SDK&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Cons&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Billing is split across features and products, and it's easy to underestimate cost once you add Document AI for tables&lt;/li&gt;
&lt;li&gt;No built-in structured output for invoices or forms without the pricier add-on&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; developers who need reliable text extraction from images or scans and are comfortable managing a GCP project.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://cloud.google.com/vision" rel="noopener noreferrer"&gt;cloud.google.com/vision&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Mistral OCR: the AI-native option built for RAG pipelines
&lt;/h2&gt;

&lt;p&gt;Mistral built its OCR model specifically around modern document AI use cases: feeding clean, structured text into retrieval pipelines and AI agents.&lt;/p&gt;

&lt;p&gt;Mistral OCR handles complex document elements including interleaved images, mathematical expressions, tables, and layouts like LaTeX formatting, and supports over 170 languages. It's priced per page rather than per token, which keeps costs predictable at scale, and a self-hosted deployment option exists for organizations that need to keep sensitive documents off shared infrastructure.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pros&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Markdown output with reconstructed tables, ready to drop into a RAG pipeline&lt;/li&gt;
&lt;li&gt;Flat per-page pricing instead of token-based billing&lt;/li&gt;
&lt;li&gt;Self-hosting available for privacy-sensitive workloads&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Cons&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Newer product, so community resources and third-party tutorials are thinner than Google's or AWS's&lt;/li&gt;
&lt;li&gt;Overkill if you just need plain text from a handful of documents&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; teams building AI agents or RAG systems that need documents converted into clean, structured markdown.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://mistral.ai/" rel="noopener noreferrer"&gt;mistral.ai&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  3. ABBYY: the enterprise veteran
&lt;/h2&gt;

&lt;p&gt;ABBYY has been in document recognition longer than most of the other names on this list, and it shows in how many languages and document types it handles out of the box.&lt;/p&gt;

&lt;p&gt;Its FineReader Engine SDK targets developers building OCR into desktop or server applications, while FineReader PDF is the consumer-facing product for individuals converting scans into editable files. Pricing for the SDK is quote-based and depends on volume and deployment, so there's no flat number to point to. The consumer product runs in the low hundreds of dollars per year.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pros&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Recognition quality across a very wide set of languages and scripts&lt;/li&gt;
&lt;li&gt;Long track record in regulated industries like finance and legal&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Cons&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;SDK pricing requires a sales conversation, which slows down evaluation for smaller teams&lt;/li&gt;
&lt;li&gt;Feels dated next to API-first competitors built around REST and JSON from day one&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; enterprises with existing document workflows in legal, finance, or healthcare that need broad language support and are willing to go through a sales process.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.abbyy.com/ai-document-processing/api/" rel="noopener noreferrer"&gt;abbyy.com&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Mindee: developer-first document extraction
&lt;/h2&gt;

&lt;p&gt;Mindee sits closer to Mistral than to Google: it's built for developers who want structured fields back, not just raw text.&lt;/p&gt;

&lt;p&gt;It ships pre-trained models for invoices, receipts, IDs, and bank statements, so instead of parsing raw OCR output yourself, you get named fields like supplier name or total amount directly in the response. Pricing is usage-based, with a free tier for testing and a per-page rate that scales down as volume increases.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pros&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Pre-built models for common document types cut integration time significantly&lt;/li&gt;
&lt;li&gt;Clear, transparent per-page pricing published on the site&lt;/li&gt;
&lt;li&gt;EU-based, which matters for teams with GDPR requirements&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Cons&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Less flexible than a general OCR API if your documents don't fit the pre-trained categories&lt;/li&gt;
&lt;li&gt;Custom document types require more setup than the ready-made models&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; teams processing a specific, repeatable document type (invoices, receipts, IDs) who want structured JSON without building their own parsing logic.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.mindee.com/pricing" rel="noopener noreferrer"&gt;mindee.com&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Unstract: open-source, no-code document pipelines
&lt;/h2&gt;

&lt;p&gt;Unstract takes a different approach entirely: instead of a fixed OCR model, you define what you want extracted using prompts, test them against sample documents, then deploy the result as an API or an automated pipeline.&lt;/p&gt;

&lt;p&gt;It's open source under AGPL-3.0, available self-hosted through Docker, as a managed cloud service, or on-premise for enterprise deployments. Its OCR preprocessor, LLMWhisperer, is built to preserve the layout of tables and columns before handing the text to an LLM, which is exactly the kind of detail that matters when a document isn't just paragraphs of text.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pros&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Prompt-based extraction adapts to new document layouts without retraining&lt;/li&gt;
&lt;li&gt;Open-source option removes vendor lock-in entirely&lt;/li&gt;
&lt;li&gt;Built-in connectors for ingesting from S3, Dropbox, or a data lake&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Cons&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The no-code/prompt-based workflow has a learning curve if you're used to a simple REST call&lt;/li&gt;
&lt;li&gt;Best results depend on which underlying LLM you pair it with, which adds a decision on top of the tool itself&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; teams that need to extract structured data from varied or changing document layouts without hand-coding a parser for each one.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://unstract.com/" rel="noopener noreferrer"&gt;unstract.com&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Adobe Acrobat: the one everyone already has
&lt;/h2&gt;

&lt;p&gt;More people search for Adobe's OCR than for any other tool on this list, mostly because Acrobat is already installed somewhere in every office.&lt;/p&gt;

&lt;p&gt;OCR only ships in the Pro tier, not the free Reader. It converts scanned files into editable, searchable PDFs and can batch-process folders through Action Wizard. Adobe doesn't publish a specific accuracy figure the way ABBYY does, but it covers 50-plus languages with automatic detection, and the Pro plan runs close to $20 a month.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pros&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Already familiar to most office workers, so there's no learning curve&lt;/li&gt;
&lt;li&gt;Batch processing across folders is straightforward&lt;/li&gt;
&lt;li&gt;Output stays inside the PDF workflow most teams already use&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Cons&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No API for automated, high-volume processing, it's a desktop tool&lt;/li&gt;
&lt;li&gt;OCR is locked behind the paid Pro tier, not available in free Reader&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; individuals or small teams doing occasional OCR inside a document workflow they already run through Acrobat.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.adobe.com/acrobat/how-to/ocr-software-convert-pdf-to-text.html" rel="noopener noreferrer"&gt;adobe.com/acrobat&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Tesseract: free, self-hosted, no data leaves your machine
&lt;/h2&gt;

&lt;p&gt;Tesseract is the answer for anyone who read the privacy section above and decided nothing should leave their own infrastructure.&lt;/p&gt;

&lt;p&gt;It's open source under the Apache 2.0 license, was originally developed at HP and later taken over by Google, and has a fully featured API that can be compiled for a wide range of targets, including Android and iPhone. It supports over 100 languages out of the box and runs entirely on your own hardware. Python developers usually reach it through the &lt;code&gt;pytesseract&lt;/code&gt; wrapper.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pros&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Completely free, no per-page or per-call cost ever&lt;/li&gt;
&lt;li&gt;Nothing gets uploaded anywhere, which settles the privacy question by design&lt;/li&gt;
&lt;li&gt;Actively maintained, wide language support&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Cons&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Accuracy on messy scans or handwriting lags noticeably behind AI-native APIs&lt;/li&gt;
&lt;li&gt;You own the image preprocessing and language-model tuning yourself, that work doesn't disappear, it just moves to you&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; developers who need full control over where documents are processed and are willing to handle image preprocessing themselves.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/tesseract-ocr/tesseract" rel="noopener noreferrer"&gt;github.com/tesseract-ocr/tesseract&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What no one else covers: what happens to your document
&lt;/h2&gt;

&lt;p&gt;Every free online OCR tool works the same way behind the scenes: your file gets uploaded to a server, processed, and the result gets sent back. What happens to the file after that depends entirely on the vendor's retention policy, and most people never check.&lt;/p&gt;

&lt;p&gt;Before uploading anything sensitive (an ID, a payslip, a signed contract) to a free web tool, three questions are worth answering:&lt;/p&gt;

&lt;p&gt;Does the provider delete the file immediately after processing, or keep it for some retention window? Where are the servers located, which matters for GDPR if you're in the EU and for general data-handling expectations everywhere else? And does the free tier reuse uploaded documents to train future models?&lt;/p&gt;

&lt;p&gt;If you can't find a clear answer to those three on the vendor's own site, treat that as the answer. For anything sensitive, either pick a tool with a documented deletion policy or process it locally with Tesseract, where the question doesn't even come up.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Need a technical writer who actually tests the tools?&lt;/strong&gt;&lt;br&gt;
I write developer-focused comparisons and tutorials for API and document-processing companies, not marketing copy dressed up as content.&lt;br&gt;
&lt;strong&gt;→ &lt;a href="https://www.linkedin.com/in/kevin-meneses-gonzalez/" rel="noopener noreferrer"&gt;Connect on LinkedIn&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Three things to take away
&lt;/h2&gt;

&lt;p&gt;Volume decides more than accuracy does: below a few hundred documents a month, a free web tool or Tesseract covers it, above that, an API pays for itself in time saved.&lt;/p&gt;

&lt;p&gt;The advertised accuracy number is almost never the number you'll get on a bad scan or a handwritten form, budget for that gap when picking a tool for production use.&lt;/p&gt;

&lt;p&gt;Privacy isn't a footnote, it's a selection criterion. Know where your document goes before you upload it, not after.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQs
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What is OCR and what is it used for?&lt;/strong&gt;&lt;br&gt;
OCR (optical character recognition) extracts text from images or scanned documents so it can be edited, searched, or processed by software. It's used for digitizing paperwork, converting scanned PDFs into searchable files, and automating data entry from invoices, receipts, and forms.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What's the difference between traditional OCR and AI-powered OCR?&lt;/strong&gt;&lt;br&gt;
Traditional OCR matches character shapes against a fixed pattern library, which struggles with handwriting, unusual fonts, and messy scans. AI-powered OCR uses machine learning models trained on huge datasets, which handles those same cases far better and can also understand document structure like tables and forms.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Is it safe to upload documents to an online OCR tool?&lt;/strong&gt;&lt;br&gt;
It depends on the provider's data retention policy. Free tools don't always delete files immediately or disclose where they're processed. For sensitive documents, check the vendor's privacy policy first or use a self-hosted option like Tesseract.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What's the best free OCR tool?&lt;/strong&gt;&lt;br&gt;
Tesseract is the strongest free option if you're comfortable with some setup, since it runs entirely on your own machine. Google Vision and Mistral OCR both offer a limited free tier for testing before you need to pay.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;When should I use an OCR API instead of a web tool?&lt;/strong&gt;&lt;br&gt;
Once you're processing documents regularly, automatically, or as part of a larger workflow, an API replaces manual uploads with a single function call and scales with your volume without extra manual work.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Is Tesseract still a good option in 2026?&lt;/strong&gt;&lt;br&gt;
Yes, for teams that prioritize cost and data privacy over top-tier accuracy on difficult documents. It's less accurate than AI-native APIs on handwriting or low-quality scans, but it's free, self-hosted, and actively maintained.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Looking for technical content like this for your company?&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I write comparisons and tutorials for API and document-processing companies.&lt;br&gt;
&lt;strong&gt;→ &lt;a href="https://kevinmeneses.com/en" rel="noopener noreferrer"&gt;More about my work&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>ai</category>
      <category>pdf</category>
      <category>coding</category>
      <category>data</category>
    </item>
    <item>
      <title>5 Best Free AI Courses in 2026 (With Certificates)</title>
      <dc:creator>Kevin Meneses González</dc:creator>
      <pubDate>Sat, 01 Aug 2026 11:22:26 +0000</pubDate>
      <link>https://dev.to/kevin_menesesgonzlez/5-best-free-ai-courses-in-2026-with-certificates-igp</link>
      <guid>https://dev.to/kevin_menesesgonzlez/5-best-free-ai-courses-in-2026-with-certificates-igp</guid>
      <description>&lt;p&gt;Most people assume learning AI seriously means paying for a bootcamp or a $200 certification program.&lt;/p&gt;

&lt;p&gt;That's not true anymore.&lt;/p&gt;

&lt;p&gt;Open any search for "AI courses" and you'll land on the same problem every time: dozens of tabs, half of them paywalled, half of them outdated, and no clear sense of which one actually matches your level. You start an advanced course with zero foundations. You finish a beginner course you'd already outgrown. You bookmark five links and never open them again.&lt;/p&gt;

&lt;p&gt;That's not a knowledge problem.&lt;/p&gt;

&lt;p&gt;It's a filtering problem.&lt;/p&gt;

&lt;p&gt;Most "best AI courses" roundups make it worse. They repeat the same generic Coursera or edX links, list them in no particular order, and never tell you whether a course assumes you can code or whether it teaches anything you couldn't already find in the docs for free.&lt;/p&gt;

&lt;p&gt;The real problem isn't finding a course. It's finding the right one for where you actually are, in the right order.&lt;/p&gt;

&lt;p&gt;That's what this list solves. Five free courses, all with certificates, ordered from complete beginner to advanced developer.&lt;/p&gt;

&lt;p&gt;If you're:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;starting from zero and just want to understand what AI actually is,&lt;/li&gt;
&lt;li&gt;a developer wanting to build with LLMs and tools like Claude,&lt;/li&gt;
&lt;li&gt;or already technical and looking to go deeper into Transformers and NLP,&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;there's a course below built for exactly that.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Anthropic Academy - Claude 101
&lt;/h2&gt;

&lt;p&gt;Anthropic is the company behind Claude, one of the most capable AI models available today, with a rapidly growing set of tools around it.&lt;/p&gt;

&lt;p&gt;Claude 101 is the foundational course inside Anthropic Academy. It covers what Claude actually is, how to start a conversation, and how to write better prompts to get more reliable results.&lt;/p&gt;

&lt;p&gt;It also goes further than most beginner courses by introducing &lt;strong&gt;Projects&lt;/strong&gt;, &lt;strong&gt;Skills&lt;/strong&gt;, and &lt;strong&gt;MCP (Model Context Protocol)&lt;/strong&gt;, which is how Claude connects to external tools and data sources to extend what it can do.&lt;/p&gt;

&lt;p&gt;The course includes a certificate you can add to LinkedIn or your resume.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;→ &lt;a href="https://anthropic.skilljar.com/claude-101" rel="noopener noreferrer"&gt;Start Claude 101 on Anthropic Academy&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pros&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Covers both fundamentals and practical prompt optimization&lt;/li&gt;
&lt;li&gt;Introduces MCP, a concept most beginner courses skip entirely&lt;/li&gt;
&lt;li&gt;Certificate included at no cost&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Cons&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Focused specifically on the Claude ecosystem, not model-agnostic&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; anyone starting from zero who wants a foundation built around a real, production-grade AI tool instead of abstract theory.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Anthropic Academy also offers a follow-up course on &lt;strong&gt;Claude Code&lt;/strong&gt;, aimed at developers who want to use AI for programming tasks. Worth checking once you finish Claude 101.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  2. AI For Everyone - Andrew Ng
&lt;/h2&gt;

&lt;p&gt;If there's one instructor almost universally recommended for AI fundamentals, it's Andrew Ng.&lt;/p&gt;

&lt;p&gt;AI For Everyone is a beginner-level course, roughly 7 hours long, that covers what AI actually is, the terminology you'll keep running into, and the practical differences between AI models. It also walks through small projects so the concepts don't stay purely theoretical.&lt;/p&gt;

&lt;p&gt;Like the first course on this list, it's free and includes a certificate.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;→ &lt;a href="https://www.coursera.org/learn/ai-for-everyone" rel="noopener noreferrer"&gt;Start AI For Everyone on Coursera&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pros&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Taught by one of the most recognized instructors in the field&lt;/li&gt;
&lt;li&gt;Clear, jargon-light explanations of AI terminology&lt;/li&gt;
&lt;li&gt;Includes practical mini-projects, not just lectures&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Cons&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Intentionally high-level. Not for anyone wanting technical depth&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; complete beginners who want a solid conceptual map before touching any code.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Hugging Face - NLP Course
&lt;/h2&gt;

&lt;p&gt;Hugging Face functions as the GitHub of AI models. If you're building anything involving language models, you'll end up there eventually.&lt;/p&gt;

&lt;p&gt;This course is more technical and assumes you already have some AI foundation. It covers &lt;strong&gt;Transformers&lt;/strong&gt;, &lt;strong&gt;NLP fundamentals&lt;/strong&gt;, &lt;strong&gt;tokenization&lt;/strong&gt;, and how to fine-tune and optimize models so they work well with your own data.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;→ &lt;a href="https://huggingface.co/learn/llm-course/chapter1/1" rel="noopener noreferrer"&gt;Start the NLP Course on Hugging Face&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pros&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Deep, hands-on coverage of Transformers and tokenization&lt;/li&gt;
&lt;li&gt;Directly applicable to real model fine-tuning work&lt;/li&gt;
&lt;li&gt;Free, with strong community support around it&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Cons&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Not beginner-friendly. Requires prior AI/ML context&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; developers ready to move from "using AI tools" to actually building and customizing models.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Google - Generative AI Learning Path
&lt;/h2&gt;

&lt;p&gt;Google's ecosystem includes Gemini, one of the leading models alongside Claude and ChatGPT.&lt;/p&gt;

&lt;p&gt;This learning path covers the fundamentals of generative AI, how Google's infrastructure supports these models, and how to build applications ranging from image generation to voice. It also explains what an LLM actually is and how to make use of the resources Google makes available to developers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;→ &lt;a href="https://www.skills.google/paths/118" rel="noopener noreferrer"&gt;Start the Generative AI Learning Path on Google Skills&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pros&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Covers multiple modalities: text, image, and voice generation&lt;/li&gt;
&lt;li&gt;Backed by Google's own infrastructure and documentation&lt;/li&gt;
&lt;li&gt;Multiple course tracks depending on your interest&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Cons&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Broader scope means less depth per topic compared to Hugging Face's course&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; developers who want to understand the infrastructure side of generative AI, not just the model layer.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. ChatGPT - Building Agents and Workflows
&lt;/h2&gt;

&lt;p&gt;The last course on this list focuses on something increasingly relevant in 2026: &lt;strong&gt;agents and workflow automation&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This free course, built around the ChatGPT ecosystem, takes about 90 minutes and covers how to build agents and automated workflows to handle repetitive tasks and improve your daily productivity.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;→ &lt;a href="https://academy.openai.com/pages/courses" rel="noopener noreferrer"&gt;Start Agents and Workflows on OpenAI Academy&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pros&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Short and focused. Around 90 minutes total&lt;/li&gt;
&lt;li&gt;Directly applicable to automating real tasks&lt;/li&gt;
&lt;li&gt;Free, no certificate paywall&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Cons&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Shorter format means less depth than the other courses on this list&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Best for:&lt;/strong&gt; anyone who already understands AI basics and wants to start automating tasks with agents right away.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Choose Your Path
&lt;/h2&gt;

&lt;p&gt;You don't need all five. Pick based on where you are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Starting from zero?&lt;/strong&gt; Anthropic Academy's Claude 101, then Andrew Ng's course.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Already technical, want to build models?&lt;/strong&gt; Hugging Face's NLP course.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Interested in the infrastructure behind generative AI?&lt;/strong&gt; Google's learning path.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Want to automate tasks right now?&lt;/strong&gt; The ChatGPT agents course.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Want a personalized AI roadmap instead of a generic course list?&lt;/strong&gt;&lt;br&gt;
I offer 1:1 consulting sessions based on your specific goals and current AI setup.&lt;br&gt;
&lt;strong&gt;→ &lt;a href="https://kevinmeneses.com/en" rel="noopener noreferrer"&gt;Book a consultation&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Key Takeaways
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;The best AI courses in 2026 are free and include certificates, no need to pay for a bootcamp to start.&lt;/li&gt;
&lt;li&gt;Level matters more than course popularity. Match the course to where you actually are, not where you want to be.&lt;/li&gt;
&lt;li&gt;MCP, agents, and workflow automation are now core topics, not optional extras, for anyone learning AI in 2026.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  FAQs
&lt;/h2&gt;

&lt;p&gt;❓ &lt;strong&gt;Are there really free AI courses with certificates in 2026?&lt;/strong&gt;&lt;br&gt;
✅ Yes. Anthropic Academy, Andrew Ng's AI For Everyone, and Google's Generative AI Learning Path all offer free certificates you can add to LinkedIn or your resume.&lt;/p&gt;

&lt;p&gt;❓ &lt;strong&gt;What's the best AI course for complete beginners?&lt;/strong&gt;&lt;br&gt;
✅ Start with Anthropic Academy's Claude 101 or Andrew Ng's AI For Everyone. Both are designed for zero prior knowledge and take just a few hours to complete.&lt;/p&gt;

&lt;p&gt;❓ &lt;strong&gt;Do I need to know how to code to take these courses?&lt;/strong&gt;&lt;br&gt;
✅ No. Courses 1, 2, 4, and 5 on this list require no coding background. Only Hugging Face's NLP course assumes prior technical experience.&lt;/p&gt;




&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Are you a software or API company that needs technical content, not marketing fluff?&lt;/strong&gt;&lt;br&gt;
I write SEO-optimized articles, tutorials, and video content that actually explain your product to developers.&lt;br&gt;
&lt;strong&gt;→ &lt;a href="https://www.linkedin.com/in/kevin-meneses-gonzalez/" rel="noopener noreferrer"&gt;Get in touch on LinkedIn&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Want more AI and fintech content like this?&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;→ &lt;a href="https://kevinmeneses.com/en" rel="noopener noreferrer"&gt;Explore more articles&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;&lt;em&gt;Looking for technical content for your company? I can help — &lt;a href="https://www.linkedin.com/in/kevin-meneses-gonzalez/" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt; · &lt;a href="mailto:kevinmenesesgonzalez@gmail.com"&gt;kevinmenesesgonzalez@gmail.com&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>claude</category>
      <category>coding</category>
      <category>llm</category>
    </item>
    <item>
      <title>How I Built a Portfolio Risk &amp; Return Tracker with EODHD</title>
      <dc:creator>Kevin Meneses González</dc:creator>
      <pubDate>Sat, 25 Jul 2026 10:33:40 +0000</pubDate>
      <link>https://dev.to/kevin_menesesgonzlez/how-i-built-a-portfolio-risk-return-tracker-with-eodhd-5gkl</link>
      <guid>https://dev.to/kevin_menesesgonzlez/how-i-built-a-portfolio-risk-return-tracker-with-eodhd-5gkl</guid>
      <description>&lt;p&gt;Most retail investors track their portfolio in a spreadsheet.&lt;/p&gt;

&lt;p&gt;Some upgrade to a free app that shows total value and daily change. That's it.&lt;/p&gt;

&lt;p&gt;Neither tells you the numbers that actually matter: your annualized volatility, your Sharpe ratio, your beta against the market, your maximum drawdown, how correlated your positions really are with each other.&lt;/p&gt;

&lt;p&gt;Institutional investors have had this for decades. Retail investors get a pie chart.&lt;/p&gt;

&lt;p&gt;If you're:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;managing your own portfolio across multiple brokers,&lt;/li&gt;
&lt;li&gt;building a fintech side project,&lt;/li&gt;
&lt;li&gt;or just tired of guessing how risky your holdings actually are,&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;this matters.&lt;/p&gt;

&lt;h2&gt;
  
  
  The spreadsheet problem
&lt;/h2&gt;

&lt;p&gt;Excel is where most portfolios go to die.&lt;/p&gt;

&lt;p&gt;You start with a clean sheet. One column per position, one row per transaction. It works — for about three months.&lt;/p&gt;

&lt;p&gt;Then you add a new broker account. Then you start tracking dividends separately. Then a formula breaks because you inserted a row in the wrong place, and you don't notice until your total is off by 4%.&lt;/p&gt;

&lt;p&gt;Spreadsheets don't calculate volatility, beta, or drawdown by default. You'd have to build those formulas yourself, pull historical prices manually, and keep them updated — forever, by hand.&lt;/p&gt;

&lt;p&gt;Most people don't. So they end up managing a portfolio without knowing its actual risk profile.&lt;/p&gt;

&lt;h2&gt;
  
  
  The paid-tool problem
&lt;/h2&gt;

&lt;p&gt;The other option is a paid portfolio tracker or a Bloomberg-style terminal.&lt;/p&gt;

&lt;p&gt;These solve the calculation problem. They don't solve the trust problem.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Your position sizes, tickers, and entry prices sit on a third-party server.&lt;/li&gt;
&lt;li&gt;You pay a monthly fee for metrics that are, mathematically, not complicated.&lt;/li&gt;
&lt;li&gt;Many of these tools are optimized to upsell you into their brokerage or premium tier, not to give you a neutral view of your risk.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Sharpe ratio, beta, drawdown, correlation — these aren't proprietary black-box models. They're well-defined formulas. Paying $30/month to have someone else run them on your data, on their servers, is a trade-off a lot of people make without questioning it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The real problem isn't a lack of financial data. It's a lack of a tool that computes real risk metrics, locally, without asking you to hand your portfolio to a third party.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Vault: a portfolio tracker that runs in your browser
&lt;/h2&gt;

&lt;p&gt;I built &lt;a href="https://github.com/Kevinelectronics/portafoliotracker" rel="noopener noreferrer"&gt;Vault&lt;/a&gt;, an open source portfolio tracker with React 19 and Vite that uses the &lt;a href="https://eodhd.com/?via=kmg&amp;amp;ref1=Meneses&amp;amp;utm_source=medium&amp;amp;utm_medium=post&amp;amp;utm_campaign=vault-portfolio-tracker&amp;amp;utm_content=Meneses" rel="noopener noreferrer"&gt;EODHD API&lt;/a&gt; to pull real market data and calculate six metrics that any serious investor should be tracking:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Returns&lt;/strong&gt; — portfolio performance vs. S&amp;amp;P 500, indexed and per-position&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Diversification&lt;/strong&gt; — position weights and concentration (Herfindahl-Hirschman Index)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Risk&lt;/strong&gt; — annualized volatility, Sharpe ratio, beta vs. market, 95% daily VaR&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Drawdown&lt;/strong&gt; — underwater curve and maximum historical drawdown&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Correlations&lt;/strong&gt; — Pearson correlation matrix between your holdings&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sector exposure&lt;/strong&gt; — GICS sector breakdown from EODHD fundamentals&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No backend. No account. No server storing your positions.&lt;/p&gt;

&lt;p&gt;Everything runs in the browser, and your portfolio is saved in &lt;code&gt;localStorage&lt;/code&gt; — on your machine, not on mine.&lt;/p&gt;

&lt;h3&gt;
  
  
  A quick note on EODHD
&lt;/h3&gt;

&lt;p&gt;I've used EODHD across several projects before this one, mainly because of three things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;One API key covers historical prices, fundamentals, and search — no juggling three different providers&lt;/li&gt;
&lt;li&gt;The free tier is generous enough to actually build and test a real project on it&lt;/li&gt;
&lt;li&gt;Response format is consistent across endpoints, which cuts down on glue code&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you want to follow along or fork Vault, you'll need a key — &lt;a href="https://eodhd.com/?via=kmg&amp;amp;ref1=Meneses&amp;amp;utm_source=medium&amp;amp;utm_medium=post&amp;amp;utm_campaign=vault-portfolio-tracker&amp;amp;utm_content=Meneses" rel="noopener noreferrer"&gt;get one free here&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  How the calculations actually work
&lt;/h2&gt;

&lt;p&gt;All the math lives in a single file: &lt;a href="https://github.com/Kevinelectronics/portafoliotracker/blob/main/src/utils/finance.js" rel="noopener noreferrer"&gt;&lt;code&gt;src/utils/finance.js&lt;/code&gt;&lt;/a&gt;. No external statistics libraries — every formula is implemented from scratch.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Time-weighted returns&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Each day is weighted by the previous day's closing value. This matters more than it sounds: without it, adding a new position mid-month distorts your daily portfolio return.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Volatility and Sharpe ratio&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Standard deviation of daily returns, annualized with &lt;code&gt;√252&lt;/code&gt;, then converted to a Sharpe ratio against a configurable risk-free rate.&lt;/p&gt;

&lt;p&gt;Here's the core of it, simplified to Python for clarity:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;numpy&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;annualized_volatility&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;daily_returns&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;std&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;daily_returns&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sqrt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;252&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;sharpe_ratio&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;daily_returns&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;risk_free_rate&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.02&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;excess_returns&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;array&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;daily_returns&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;risk_free_rate&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;252&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mean&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;excess_returns&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;std&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;daily_returns&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sqrt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;252&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Beta&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Covariance of portfolio returns against the S&amp;amp;P 500 (&lt;code&gt;GSPC.INDX&lt;/code&gt; on EODHD), divided by the benchmark's variance.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Drawdown&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Percentage drop from the running peak of a growth-indexed curve (base 100). This is the number that tells you what it would have actually felt like to hold this portfolio through its worst stretch.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Correlation matrix&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Pairwise Pearson coefficient between assets, aligned by date. This is the metric most retail portfolios get wrong without knowing it — five stocks can feel diversified and still move together 90% of the time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Concentration (HHI)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Herfindahl-Hirschman Index applied to position weights. One number that tells you how concentrated you actually are, beyond "I have 12 positions so I'm diversified."&lt;/p&gt;

&lt;p&gt;From here, the same building blocks extend into:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;automated risk alerts&lt;/li&gt;
&lt;li&gt;portfolio rebalancing scripts&lt;/li&gt;
&lt;li&gt;backtesting engines&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What it looks like in practice
&lt;/h2&gt;

&lt;p&gt;You add a position, EODHD's Search + EOD endpoints autocomplete the ticker and the closing price on your purchase date.&lt;/p&gt;

&lt;p&gt;From there, Vault renders your returns curve against the S&amp;amp;P 500, your correlation heatmap, and your sector exposure pulled straight from fundamentals data.&lt;/p&gt;

&lt;p&gt;No manual formula-building. No re-entering prices when a position updates.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key takeaways
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Portfolio risk metrics (Sharpe, beta, drawdown, correlation) aren't proprietary — they're standard formulas most tools just gatekeep behind a paywall&lt;/li&gt;
&lt;li&gt;Running them client-side means your positions never leave your browser&lt;/li&gt;
&lt;li&gt;EODHD's Search, EOD, and Fundamentals endpoints cover everything needed to build this without stitching together multiple data providers&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Try it yourself
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/Kevinelectronics/portafoliotracker.git
&lt;span class="nb"&gt;cd &lt;/span&gt;portafoliotracker
npm &lt;span class="nb"&gt;install&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add your EODHD key to &lt;code&gt;.env&lt;/code&gt;, run &lt;code&gt;npm run dev&lt;/code&gt;, and add your first position.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Get your free EODHD API key&lt;/strong&gt;&lt;br&gt;
Vault runs entirely on EODHD's Search, EOD, and Fundamentals endpoints — no backend, no paywall on the metrics.&lt;br&gt;
&lt;strong&gt;→ &lt;a href="https://eodhd.com/?via=kmg&amp;amp;ref1=Meneses&amp;amp;utm_source=medium&amp;amp;utm_medium=post&amp;amp;utm_campaign=vault-portfolio-tracker&amp;amp;utm_content=Meneses" rel="noopener noreferrer"&gt;Get your EODHD API key&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If you're a software or API company looking to explain your product through high-quality educational content — not marketing fluff — feel free to connect with me on LinkedIn.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Need technical content like this for your product?&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;→ &lt;a href="https://kevinmeneses.com/en" rel="noopener noreferrer"&gt;kevinmeneses.com&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;p&gt;&lt;em&gt;Looking for technical content for your company? I can help — &lt;a href="https://www.linkedin.com/in/kevin-meneses-gonzalez/" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt; · &lt;a href="mailto:kevinmenesesgonzalez@gmail.com"&gt;kevinmenesesgonzalez@gmail.com&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>rag</category>
      <category>llm</category>
      <category>pdf</category>
    </item>
  </channel>
</rss>
