<?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: Ahmed Jasarevic</title>
    <description>The latest articles on DEV Community by Ahmed Jasarevic (@ahmed_jasarevic_9fa29bf1d).</description>
    <link>https://dev.to/ahmed_jasarevic_9fa29bf1d</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%2F4006314%2F47e877ab-2397-4744-8093-81ab3bd5c3bb.jpg</url>
      <title>DEV Community: Ahmed Jasarevic</title>
      <link>https://dev.to/ahmed_jasarevic_9fa29bf1d</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ahmed_jasarevic_9fa29bf1d"/>
    <language>en</language>
    <item>
      <title>How to Get Reliable Options Chain Data in Python (When yfinance Keeps Failing)</title>
      <dc:creator>Ahmed Jasarevic</dc:creator>
      <pubDate>Mon, 21 Sep 2026 14:57:30 +0000</pubDate>
      <link>https://dev.to/apify/how-to-get-reliable-options-chain-data-in-python-when-yfinance-keeps-failing-1g5f</link>
      <guid>https://dev.to/apify/how-to-get-reliable-options-chain-data-in-python-when-yfinance-keeps-failing-1g5f</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;TL;DR&lt;/strong&gt; — Getting &lt;em&gt;stock prices&lt;/em&gt; in Python is easy. Getting &lt;strong&gt;options chain data&lt;/strong&gt; (strikes, expirations, implied volatility, open interest, volume) reliably and at scale is a different problem. This guide shows you how to pull clean, structured options data into a pandas DataFrame and build an options screener and IV dashboard on top of it — without fighting rate limits or rewriting your data layer every few weeks.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  The problem nobody warns you about
&lt;/h2&gt;

&lt;p&gt;You've probably built this pipeline before:&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;yfinance&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;yf&lt;/span&gt;

&lt;span class="n"&gt;aapl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;yf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&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;AAPL&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;chain&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;aapl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;option_chain&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-17&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;calls&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;chain&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;calls&lt;/span&gt;
&lt;span class="n"&gt;puts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;chain&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;puts&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It works beautifully in a notebook. Then you ship it.&lt;/p&gt;

&lt;p&gt;And then, three weeks later, it returns empty DataFrames. Or throws a 429. Or works for 40 tickers and silently fails for the other 460. Or your nightly job just… doesn't have data this morning.&lt;/p&gt;

&lt;p&gt;If you're building anything that depends on options data — a screener, a volatility dashboard, an alert bot, a backtest — &lt;strong&gt;the data layer is where projects die.&lt;/strong&gt; Not the strategy. Not the model. The data layer.&lt;/p&gt;

&lt;p&gt;This article is about fixing that layer once, so you can get back to building the interesting part.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why options data is much harder than stock prices
&lt;/h2&gt;

&lt;p&gt;Stock quotes are one number. Options chains are &lt;em&gt;hundreds of contracts per ticker&lt;/em&gt; that each carry their own:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Strike price&lt;/strong&gt; and &lt;strong&gt;expiration date&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Contract type&lt;/strong&gt; (call / put)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Implied volatility (IV)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Open interest&lt;/strong&gt; and &lt;strong&gt;volume&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Bid / ask / last price&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;In-the-money status&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The underlying stock price&lt;/strong&gt; at the moment of the snapshot&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That's a lot of surface area. And unlike a simple price feed, options data is:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Large&lt;/strong&gt; — a single liquid ticker can have thousands of contracts across all expirations.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Time-sensitive&lt;/strong&gt; — IV and open interest shift throughout the trading day.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Painful to fetch at scale&lt;/strong&gt; — you need hundreds of requests for a realistic watchlist.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Most free options data sources were never designed for this. They're fine for a demo with three tickers. They fall over the moment you point them at a real universe.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why the free route breaks in production
&lt;/h2&gt;

&lt;p&gt;If you've used &lt;code&gt;yfinance&lt;/code&gt; or &lt;code&gt;yahoo_fin&lt;/code&gt;, you already know the pattern. These libraries are excellent for exploration and research, and I genuinely recommend them for that. But they share a few characteristics that make production usage painful:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;What you need&lt;/th&gt;
&lt;th&gt;The free/DIY route&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Predictable results at scale&lt;/td&gt;
&lt;td&gt;Rate limiting appears as you scale up&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Consistent schema across tickers&lt;/td&gt;
&lt;td&gt;Fields shift; empty responses happen&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hundreds of tickers per run&lt;/td&gt;
&lt;td&gt;You manage retries, throttling and pacing yourself&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fresh data on a schedule&lt;/td&gt;
&lt;td&gt;Silent failures — you only notice when data is missing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Zero maintenance&lt;/td&gt;
&lt;td&gt;Breaks when upstream changes&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The core issue isn't that these tools are bad. It's that &lt;strong&gt;they're not a data pipeline.&lt;/strong&gt; They're a convenience wrapper. When you build on top of them, you inherit 100% of the reliability burden — retries, backoff, session handling, proxy rotation, schema normalization, and monitoring.&lt;/p&gt;

&lt;p&gt;That's a full-time engineering project. And it has nothing to do with the options strategy you actually want to build.&lt;/p&gt;




&lt;h2&gt;
  
  
  What "reliable" options data actually looks like
&lt;/h2&gt;

&lt;p&gt;Before choosing a tool, get clear on the contract. You want each row to look like this — one row per option contract:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Field&lt;/th&gt;
&lt;th&gt;Meaning&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ticker&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Underlying symbol (e.g. &lt;code&gt;AAPL&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;symbol&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Full options contract symbol&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;type&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;call&lt;/code&gt; or &lt;code&gt;put&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;strike&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Strike price&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;expiration&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Expiration date&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;price&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Option contract price&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;stockPrice&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Underlying stock price at scrape time&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;iv&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Implied volatility&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;volume&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Contracts traded&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;openInterest&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Open contracts outstanding&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;itm&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Whether the contract is in-the-money&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;scrapedAt&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Timestamp of the snapshot&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;That's a clean, &lt;strong&gt;flattened&lt;/strong&gt; schema — one row per contract, ready for a DataFrame, ready for CSV, ready for a database, ready for a chart. No nested JSON to unpack, no per-ticker field drift.&lt;/p&gt;

&lt;p&gt;The goal is simple: &lt;strong&gt;you ask for tickers, you get back a tidy table.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Getting the data (in about 15 lines)
&lt;/h2&gt;

&lt;p&gt;Instead of maintaining a scraper, you can call a managed data source that returns exactly the schema above. Here's the whole integration — a plain HTTP call, so it works from Python, Node, a cron job, or an agent:&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="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;APIFY_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_APIFY_TOKEN&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;ACTOR&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ahmed_jasarevic~yahoo-finance-options&lt;/span&gt;&lt;span class="sh"&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;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.apify.com/v2/acts/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;ACTOR&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/run-sync-get-dataset-items&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;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;APIFY_TOKEN&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="n"&gt;json&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;tickers&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;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;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;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;limitPerTicker&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&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;300&lt;/span&gt;&lt;span class="p"&gt;,&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;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;resp&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;df&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;head&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; contracts across &lt;/span&gt;&lt;span class="si"&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;ticker&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;nunique&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; tickers&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;That's it. No retry logic, no session management, no proxy setup, no per-ticker error handling.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Input options:&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Field&lt;/th&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Required&lt;/th&gt;
&lt;th&gt;Default&lt;/th&gt;
&lt;th&gt;Notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;tickers&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;array&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;Symbols to fetch, e.g. &lt;code&gt;["AAPL", "TSLA"]&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;limitPerTicker&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;integer&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;&lt;code&gt;20&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Number of call + put contracts per ticker&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;proxyConfiguration&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;object&lt;/td&gt;
&lt;td&gt;❌&lt;/td&gt;
&lt;td&gt;Apify Proxy&lt;/td&gt;
&lt;td&gt;Residential proxy recommended for larger runs&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A couple of things worth knowing before you build on it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;limitPerTicker&lt;/code&gt; controls cost and speed.&lt;/strong&gt; Start at 20–50 while developing, raise it when you go live.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The output is one row per contract&lt;/strong&gt;, so 50 tickers × 50 contracts = 2,500 rows. Size your DataFrame accordingly.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pricing is per result&lt;/strong&gt; (roughly &lt;strong&gt;$1.20 per 1,000 contracts&lt;/strong&gt; at the free tier, and lower as your Apify plan tier goes up). A 10-ticker daily screener is fractions of a cent per run.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can also run it interactively from the Store page if you just want to eyeball the output first:&lt;br&gt;
👉 &lt;strong&gt;&lt;a href="https://apify.com/ahmed_jasarevic/yahoo-finance-options" rel="noopener noreferrer"&gt;Yahoo Options — Chains, IV, Live&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  Use case 1 — Build an options screener
&lt;/h2&gt;

&lt;p&gt;Now the fun part. With a clean DataFrame, a screener is genuinely a few lines.&lt;/p&gt;

&lt;p&gt;Let's find &lt;strong&gt;liquid calls&lt;/strong&gt; — high open interest, real volume, and an IV band that filters out both dead contracts and lottery-ticket noise:&lt;br&gt;
&lt;/p&gt;

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

&lt;span class="c1"&gt;# Normalise types
&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;iv&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_numeric&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;iv&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;astype&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="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&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="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;regex&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="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;coerce&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="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;openInterest&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_numeric&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;openInterest&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;coerce&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;volume&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_numeric&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;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;errors&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;coerce&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;calls&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;df&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;type&lt;/span&gt;&lt;span class="sh"&gt;"&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="nf"&gt;lower&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;call&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;copy&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;screened&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;calls&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;calls&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;openInterest&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="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;      &lt;span class="c1"&gt;# real liquidity
&lt;/span&gt;    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;calls&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="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;            &lt;span class="c1"&gt;# actually trading today
&lt;/span&gt;    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;calls&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;iv&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;between&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="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;      &lt;span class="c1"&gt;# avoid dead + lottery contracts
&lt;/span&gt;    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;calls&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;itm&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="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;              &lt;span class="c1"&gt;# out-of-the-money only
&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;sort_values&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;openInterest&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ascending&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;screened&lt;/span&gt;&lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ticker&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;strike&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;expiration&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;iv&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;openInterest&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="nf"&gt;head&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That table is the beginning of a real tool. From here you can add your own filters: distance from spot, days-to-expiration windows, spread width, IV rank against history — whatever your strategy needs.&lt;/p&gt;

&lt;p&gt;Because the schema is stable, &lt;strong&gt;your screener logic never has to change when the data source does.&lt;/strong&gt; That's the whole point.&lt;/p&gt;




&lt;h2&gt;
  
  
  Use case 2 — Track implied volatility over time
&lt;/h2&gt;

&lt;p&gt;IV is the single most-watched number in options. But IV only becomes &lt;em&gt;useful&lt;/em&gt; when you have history — today's IV means nothing without yesterday's to compare it to.&lt;/p&gt;

&lt;p&gt;The pattern is: &lt;strong&gt;snapshot on a schedule → store → chart.&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="c1"&gt;# Append each run to a CSV (or swap in SQLite / Postgres / DuckDB)
&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;scrapedAt&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;scrapedAt&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;coerce&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;iv_history.csv&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;mode&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="n"&gt;header&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="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;Then compute a simple average IV per ticker over time:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;history&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read_csv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;iv_history.csv&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;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;scrapedAt&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;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;scrapedAt&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

&lt;span class="n"&gt;trend&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;history&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;groupby&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ticker&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;scrapedAt&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;iv&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;mean&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reset_index&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sort_values&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;scrapedAt&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="c1"&gt;# A 30-point IV jump on a single name is worth looking at
&lt;/span&gt;&lt;span class="n"&gt;latest&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;trend&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;groupby&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ticker&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;iv&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;last&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;previous&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;trend&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;groupby&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ticker&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;iv&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;nth&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;spikes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;latest&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;previous&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;sort_values&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ascending&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;spikes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;head&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You've now got the foundation of a &lt;strong&gt;volatility alert system&lt;/strong&gt; — the thing that actually tells you when something interesting is happening in the market, instead of you refreshing a page all day.&lt;/p&gt;




&lt;h2&gt;
  
  
  Use case 3 — Automate it and stop thinking about it
&lt;/h2&gt;

&lt;p&gt;The real value kicks in when this runs without you. Two options:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Scheduling:&lt;/strong&gt; Apify supports cron-style schedules, so you can run the same configuration every market day at a fixed time — pre-market, at the open, or at the close.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Piping the output anywhere:&lt;/strong&gt; Because the output is clean JSON, you can push it straight into Google Sheets, a webhook, Make/Zapier, your own database, or an AI agent that summarises what changed.&lt;/p&gt;

&lt;p&gt;A practical setup for a watchlist monitor:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Schedule&lt;/strong&gt; a run each trading morning with your ticker list.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Append&lt;/strong&gt; results to your storage.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Diff&lt;/strong&gt; against yesterday's snapshot.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Alert&lt;/strong&gt; when IV, open interest or volume moves beyond a threshold you care about.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That's a monitoring product you'd otherwise spend a month building. It's now a config file and a schedule.&lt;/p&gt;




&lt;h2&gt;
  
  
  How this compares to a DIY pipeline
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Capability&lt;/th&gt;
&lt;th&gt;DIY free library&lt;/th&gt;
&lt;th&gt;This managed actor&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Get options chains&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Flattened, stable schema&lt;/td&gt;
&lt;td&gt;⚠️ varies&lt;/td&gt;
&lt;td&gt;✅ one row per contract&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Scale to hundreds of tickers&lt;/td&gt;
&lt;td&gt;⚠️ rate limits&lt;/td&gt;
&lt;td&gt;✅ built for it&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Retries / throttling handled&lt;/td&gt;
&lt;td&gt;❌ you build it&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Runs unattended on a schedule&lt;/td&gt;
&lt;td&gt;⚠️ fragile&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Maintenance when upstream changes&lt;/td&gt;
&lt;td&gt;❌ ongoing&lt;/td&gt;
&lt;td&gt;✅ none&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cost per 1,000 contracts&lt;/td&gt;
&lt;td&gt;"free" + your time&lt;/td&gt;
&lt;td&gt;~$1.20&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The honest framing: free libraries are great for learning and one-off research. A managed data source is what you use when &lt;strong&gt;the data needs to be there every single time&lt;/strong&gt; — because you're shipping a product on top of it.&lt;/p&gt;




&lt;h2&gt;
  
  
  Frequently asked questions
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Is there a free Yahoo Finance options API?
&lt;/h3&gt;

&lt;p&gt;Yahoo shut down its official public API in 2017. Everything you see today — including &lt;code&gt;yfinance&lt;/code&gt; and similar libraries — relies on unofficial endpoints, which is exactly why reliability varies over time. For production use, most developers move to a managed data source rather than maintaining an unofficial one themselves.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why does yfinance return empty options data?
&lt;/h3&gt;

&lt;p&gt;It usually comes down to rate limiting, throttling, or upstream changes. When you request many tickers quickly, you can hit limits that return empty or partial results instead of a clear error — which is the worst kind of failure, because your pipeline looks healthy while quietly producing nothing.&lt;/p&gt;

&lt;h3&gt;
  
  
  How do I get options chain data in Python?
&lt;/h3&gt;

&lt;p&gt;Two routes: use a library that wraps an unofficial source (fast to start, fragile in production), or call a managed API that returns structured JSON you load straight into a DataFrame. The second route removes the maintenance burden entirely — the snippet above is the whole integration.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I get implied volatility and open interest, not just prices?
&lt;/h3&gt;

&lt;p&gt;Yes — and those are usually the fields that matter most. Implied volatility tells you what the market expects, and open interest tells you where the real money is positioned. Any useful options tool is built on those two numbers far more than on raw contract price.&lt;/p&gt;

&lt;h3&gt;
  
  
  How much does it cost to pull options data at scale?
&lt;/h3&gt;

&lt;p&gt;The actor is billed per result, roughly &lt;strong&gt;$1.20 per 1,000 contracts&lt;/strong&gt; at the free tier and cheaper at higher plan tiers. A daily 10-ticker screener at 50 contracts each is 500 contracts — well under a cent per run.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I use this with an AI agent or LLM?
&lt;/h3&gt;

&lt;p&gt;Yes. Because the output is structured JSON and the integration is a single HTTP call, it works cleanly as a tool for an LLM agent — useful for things like "summarise the biggest IV moves in my watchlist today."&lt;/p&gt;

&lt;h3&gt;
  
  
  Does it work for any ticker?
&lt;/h3&gt;

&lt;p&gt;It's built for US-listed tickers with options chains. Set &lt;code&gt;tickers&lt;/code&gt; to your watchlist and run it.&lt;/p&gt;




&lt;h2&gt;
  
  
  The takeaway
&lt;/h2&gt;

&lt;p&gt;Options data doesn't have to be the fragile part of your project.&lt;/p&gt;

&lt;p&gt;The pattern that works:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Get a stable, flattened schema&lt;/strong&gt; — one row per contract.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Build your logic on top of it&lt;/strong&gt; — screeners, IV tracking, alerts.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Schedule it&lt;/strong&gt; so it runs without you.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Never maintain a scraper again.&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Your edge is in what you &lt;em&gt;do&lt;/em&gt; with the data. Everything upstream of that should be boring, predictable and boring again.&lt;/p&gt;

&lt;p&gt;👉 &lt;strong&gt;Get started:&lt;/strong&gt; &lt;a href="https://apify.com/ahmed_jasarevic/yahoo-finance-options" rel="noopener noreferrer"&gt;Yahoo Options — Chains, IV, Live on Apify&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Run it once in the UI to see the output, then drop the 15-line snippet into your project and get back to building.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;If this saved you an afternoon of rate-limit debugging, drop a reaction — and tell me in the comments what you're building with options data. Screener, alert bot, or something weirder?&lt;/em&gt;&lt;/p&gt;




</description>
      <category>python</category>
      <category>finance</category>
      <category>api</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>How I Built a Linktree, Beacons &amp; Bio Email Scraper with Apify</title>
      <dc:creator>Ahmed Jasarevic</dc:creator>
      <pubDate>Sun, 20 Sep 2026 20:42:11 +0000</pubDate>
      <link>https://dev.to/apify/how-i-built-a-linktree-beacons-bio-email-scraper-with-apify-oo0</link>
      <guid>https://dev.to/apify/how-i-built-a-linktree-beacons-bio-email-scraper-with-apify-oo0</guid>
      <description>&lt;h2&gt;
  
  
  1. Introduction
&lt;/h2&gt;

&lt;p&gt;Collecting emails from Linktree, Beacons, and other bio links is often a tedious and time-consuming task. As a developer and freelancer working with lead generation projects, I faced this problem firsthand: manually navigating hundreds of profiles just to extract a few emails is inefficient and error-prone.&lt;br&gt;
To solve this, I built a Linktree, Beacons &amp;amp; Bio Email Scraper Actor using Apify. It automates the process, reliably collects emails from multiple platforms, and provides structured outputs for marketing, data engineering, and business intelligence purposes.&lt;br&gt;
This article details the journey: the challenges I faced, the design of the Actor, lessons learned, and tips for anyone looking to build production-grade scraping solutions.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Problem Context &amp;amp; Motivation
&lt;/h2&gt;

&lt;p&gt;Manual email collection from bio links presents several challenges:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Dynamic pages: Platforms like Linktree often use JavaScript to load content asynchronously. Simple HTML scraping fails here.&lt;/li&gt;
&lt;li&gt;Anti-scraping measures: Repeated requests can trigger rate limits or captchas.&lt;/li&gt;
&lt;li&gt;Scale: For real lead-generation projects, you often need hundreds or thousands of emails daily—manual collection is not an option.
I needed a solution that was fast, scalable, and reliable, and that could handle these technical challenges without breaking.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  3. Actor Overview
&lt;/h3&gt;

&lt;p&gt;Architecture and Workflow&lt;br&gt;
The Actor is built on Apify and Crawlee, combining headless browser automation with structured data extraction. Here’s a high-level workflow:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Input: A list of Linktree, Beacons, or other bio URLs.&lt;/li&gt;
&lt;li&gt;Navigation: Actor launches a headless browser to visit each URL.&lt;/li&gt;
&lt;li&gt;Email Extraction: Uses DOM selectors and regex patterns to identify valid email addresses.&lt;/li&gt;
&lt;li&gt;Anti-blocking: Rotates proxies and applies request throttling to avoid detection.&lt;/li&gt;
&lt;li&gt;Output: Saves results in JSON or CSV with details: URL, email, timestamp.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Input, Output, and Configuration Options&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Input: List of profile URLs (CSV, JSON, or manually typed).&lt;/li&gt;
&lt;li&gt;Output: JSON, CSV, or Google Sheets integration.&lt;/li&gt;
&lt;li&gt;Configurable Options:
Maximum pages per run
Timeout per request
Proxy rotation (on/off)
Output file format
This flexibility allows the Actor to handle both small projects and large-scale campaigns.&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%2F5xwioawnnjr99hm1xvbz.jpg" 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%2F5xwioawnnjr99hm1xvbz.jpg" alt="Image 1: Example of output data" width="800" height="498"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Implementation Details
&lt;/h2&gt;

&lt;p&gt;Handling Dynamic Content&lt;br&gt;
Linktree and Beacons often load content via JavaScript. Initially, I tried simple HTTP requests, but emails were missing in the HTML response. Switching to Crawlee with Playwright solved the issue:&lt;/p&gt;

&lt;p&gt;import { PlaywrightCrawler } from 'crawlee';&lt;/p&gt;

&lt;p&gt;const crawler = new PlaywrightCrawler({&lt;br&gt;
    requestHandler: async ({ page, request, enqueueLinks, log }) =&amp;gt; {&lt;br&gt;
        await page.goto(request.url);&lt;br&gt;
        const emails = await page.$$eval('a[href^="mailto:"]', els =&amp;gt; els.map(e =&amp;gt; e.href));&lt;br&gt;
        console.log(&lt;code&gt;Found emails for ${request.url}: ${emails}&lt;/code&gt;);&lt;br&gt;
    }&lt;br&gt;
});&lt;/p&gt;

&lt;p&gt;await crawler.run(['&lt;a href="https://linktr.ee/example'%5D" rel="noopener noreferrer"&gt;https://linktr.ee/example']&lt;/a&gt;);&lt;/p&gt;

&lt;p&gt;This approach ensures the Actor captures all visible emails, even on dynamic pages.&lt;br&gt;
Anti-Blocking Strategies&lt;br&gt;
During early tests, some accounts triggered rate limits. To fix this, I added:&lt;br&gt;
Proxy rotation&lt;br&gt;
Randomized delays between requests&lt;br&gt;
Error retries for failed pages&lt;br&gt;
These measures increased the success rate to 99.6% over thousands of URLs.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Metrics &amp;amp; Results
&lt;/h2&gt;

&lt;p&gt;To evaluate the Actor in a real-world scenario, I ran it against a dataset of 1,800+ URLs. The results showed strong reliability while keeping the cost of each run predictable:&lt;/p&gt;

&lt;p&gt;99.6% success rate&lt;br&gt;
1,816 emails collected&lt;br&gt;
$14.47 total cost per run&lt;br&gt;
$23.76 profit generated per run from the collected leads&lt;/p&gt;

&lt;p&gt;These results demonstrated that the Actor could process large batches of URLs reliably while remaining cost-effective for lead-generation workflows.&lt;/p&gt;

&lt;p&gt;This shows the Actor is not only technically reliable but also economically valuable for lead-generation workflows.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Lessons Learned
&lt;/h2&gt;

&lt;p&gt;Building this Actor taught me several important lessons:&lt;br&gt;
JavaScript rendering matters: Always test pages in a headless browser when dealing with dynamic content.&lt;br&gt;
Anti-blocking is critical: Even simple rotation and throttling drastically improve success rates.&lt;br&gt;
First-person debugging insights: Logging actual page content during development helped identify hidden issues.&lt;br&gt;
Scalable design: Structuring input/output for batch processing makes the Actor production-ready.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Who Can Benefit
&lt;/h2&gt;

&lt;p&gt;This Actor is useful for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Developers looking to automate repetitive data collection tasks&lt;/li&gt;
&lt;li&gt;Marketers and sales teams needing up-to-date email lists&lt;/li&gt;
&lt;li&gt;Data engineers building pipelines that integrate multiple data sources&lt;/li&gt;
&lt;li&gt;The architecture can also be adapted for other bio link platforms or email collection projects with similar challenges.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  8. Conclusion
&lt;/h3&gt;

&lt;p&gt;Automating email extraction from Linktree, Beacons, and bio links is no longer a manual nightmare. By using Apify and Crawlee, I built a reliable, scalable, and cost-effective Actor that delivers real-world results.&lt;br&gt;
If you plan to build your own Actor, remember: focus on handling dynamic content, preventing blocks, and structuring your data pipeline. Sharing these lessons ensures other developers can build production-grade automation with confidence.&lt;/p&gt;

&lt;h2&gt;
  
  
  9. Next Steps
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Test the Actor on new bio platforms&lt;/li&gt;
&lt;li&gt;Add AI-powered validation to filter incorrect emails&lt;/li&gt;
&lt;li&gt;Explore integrations with CRMs and email marketing tools&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ai</category>
      <category>data</category>
      <category>scraping</category>
    </item>
  </channel>
</rss>
