# Options Screener — IV Premium, Unusual Volume, Covered Calls (`yzy001/options-screener`) Actor

Screen US equity and ETF option chains on derived metrics, not raw chains: implied volatility vs realized volatility, volume-to-open-interest ratio, IV skew, put/call ratio, and annualized covered-call and cash-secured-put yields. Liquidity-filtered.

- **URL**: https://apify.com/yzy001/options-screener.md
- **Developed by:** [宗諭 楊](https://apify.com/yzy001) (community)
- **Categories:** Business, Developer tools, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.00 / 1,000 option contracts

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Options Screener — IV Premium, Unusual Volume & Covered-Call Yield

Most option scrapers hand you the chain. This one hands you the numbers you were going to
compute from the chain.

Give it tickers and it returns option contracts ranked by a **derived** metric: implied
volatility measured against the stock's own realized volatility, volume against open
interest, or the annualized yield of selling the contract. Everything is liquidity-filtered
first, because a yield you cannot fill is not a yield.

### The five screens

| Screen | Ranks by | Answers |
|---|---|---|
| **IV premium** | `iv ÷ realizedVol` | Are these options expensive relative to how much the stock has actually been moving? |
| **Unusual volume** | `volume ÷ openInterest` | Where is today's trading larger than the entire existing position? |
| **Covered-call yield** | annualized call premium ÷ spot | If I hold the shares and sell this call, what is the annualized return? |
| **Cash-secured-put yield** | annualized put premium ÷ strike | If I set aside the cash and sell this put, what is the annualized return? |
| **Liquid chain only** | symbol / expiry / strike | Just the chain, with the illiquid strikes removed. |

### What you get per contract

| Field | Example |
|---|---|
| `symbol` / `expiry` / `dte` | `AAPL` / `2026-11-21` / `41` |
| `side` / `strike` / `moneyness` | `call` / `340` / `1.01` |
| `mid` / `bid` / `ask` / `spreadPct` | `8.45` / `8.30` / `8.60` / `0.035` |
| `iv` | `0.287` |
| `realizedVol` | `0.241` |
| **`ivPremium`** | **`1.19`** — implied is 19% above realized |
| `volume` / `openInterest` / **`volOiRatio`** | `4,210` / `1,905` / **`2.21`** |
| **`coveredCallYield`** / **`cashSecuredPutYield`** | annualized, from the mid price |
| `atmIv` / `ivSkew` / `putCallVolRatio` / `putCallOiRatio` | per expiration |
| `breakevenMovePct` | the one-sigma move the option is pricing in |
| `inTheMoney` / `lastTradeDate` / `contractSymbol` | `inTheMoney` is computed from the `spot` in the same row, so it always agrees with `moneyness` and `extrinsicPct` |

Two dataset views: **Contracts** (everything) and **Premium selling** (the two yields side
by side with the spread, so you can see what is actually fillable).

### Filters

- **Days to expiry** window — very short-dated options have unstable implied volatility, so
  the default starts at 7 days.
- **Min open interest** — without it the output fills with strikes nobody trades.
- **Max bid-ask spread** as a fraction of mid — the liquidity filter that makes the yields
  meaningful.
- **Realized volatility window** — the denominator of IV premium.
- **Expirations per symbol** — each one is a separate request.

### How it handles bad data

The source encodes "this strike has no market" as a **number**, not as a blank: contracts
with no quotes come back with an implied volatility of about `0.00001`. Averaged or ranked
naively, those values quietly destroy every statistic — and the rows still look fine,
because they have a strike, a bid, an ask and a last price.

| Check | What it catches |
|---|---|
| Placeholder IV | Implied volatility at or below `0.0001` is converted to `null` in the data layer, before it can reach any calculation. |
| Whole chain unpriced | If *every* contract in an expiration carries a placeholder IV, that symbol stops with an explanation instead of returning confident-looking numbers. |
| No expirations | Reported as "this symbol has no option chain", never as "nothing matched your filters". If none of the symbols has options while the control symbol SPY does, the run is refused with that explanation (no rows, no per-row charge). |
| Market closed | Yahoo zeroes option bids, asks and open interest outside US market hours (measured Monday 02:00 ET: 14 of 3,131 contracts still had a quote). With fewer than 5% of contracts quoted outside 9:30–16:00 ET on a trading day, the run is refused with the time and the last trade, instead of returning an empty success. The same picture during market hours is treated as a source failure. `SUMMARY.quotedShare` reports the share for every run. |
| Crossed market | More than 20% of contracts quoting bid above ask means the quote data is unusable. |
| Too little history | Realized volatility is the denominator of IV premium, so a short history stops the symbol rather than returning a ratio built on noise. |
| Everything filtered out | If liquidity filters remove every contract while quotes exist, the run succeeds with no rows and its status message says which filters removed them. |
| Unknown screen name | Rejected, not silently replaced with the default. |

A gate failure on one symbol does not kill the run; it appears in `SUMMARY.warnings` with
the reason, and that symbol is left out rather than half-reported.

### Notes on the data and the metrics

- `ivPremium` is **relative pricing, not a forecast**. Above 1 means the option is priced
  for more movement than the stock has recently delivered. It does not mean selling is
  profitable.
- Yields use the **mid price**, not the last trade — a last trade can be days old. Your
  actual fill lands inside the spread, which is why `spreadPct` is in every row.
- Annualized yields use calendar days (365), because collateral is tied up in calendar
  days. Realized volatility annualizes with trading days (252).
- Quotes are **delayed**, not live. This is a screening tool, not an order-routing tool.
- Nothing here is investment advice.

# Actor input Schema

## `symbols` (type: `array`):

US stock or ETF tickers whose option chains to screen, e.g. AAPL, SPY, NVDA.

## `screen` (type: `string`):

Which derived metric to rank by. 'IV premium' compares option-implied volatility against the stock's own recent realized volatility.

## `maxExpiries` (type: `integer`):

How many of the nearest expirations to fetch per symbol. Each one is a separate request.

## `minDaysToExpiry` (type: `integer`):

Skip expirations closer than this. Very short-dated options have unstable implied volatility.

## `maxDaysToExpiry` (type: `integer`):

Skip expirations further out than this.

## `minOpenInterest` (type: `integer`):

Drop contracts with less open interest than this. Without it the results fill up with strikes nobody trades.

## `maxSpreadPct` (type: `number`):

Drop contracts whose bid-ask spread exceeds this fraction of the mid price. 0.15 means 15%. A yield you cannot actually fill is not a yield.

## `minExtrinsicPct` (type: `number`):

Drop contracts whose extrinsic (time) value is below this fraction of the mid price. Deep in-the-money options are almost all intrinsic value, and the implied volatility solved from such a price is numerical noise - one measured example had 0.03% extrinsic and an implied volatility of 239%. 0.05 means 5%.

## `moneynessMin` (type: `number`):

Only include strikes at or above this fraction of the spot price. Implied volatility is not comparable across moneyness - the volatility smile makes far out-of-the-money strikes structurally higher, so ranking by IV without this window returns the most out-of-the-money strikes rather than the most expensive options.

## `moneynessMax` (type: `number`):

Only include strikes at or below this fraction of the spot price.

## `perSymbolLimit` (type: `integer`):

Cap how many rows each symbol can contribute, so one symbol cannot fill the whole output. Leave empty for no per-symbol cap.

## `realizedVolDays` (type: `integer`):

Window for close-to-close annualized realized volatility, the denominator of IV premium.

## `maxResults` (type: `integer`):

Maximum number of contract rows to return after ranking.

## `delaySeconds` (type: `number`):

Pause between chain requests. Yahoo throttles datacenter IPs and returns stub chains with only a handful of contracts - one measured SPY expiration came back with 4 contracts instead of the usual 60-103. Raise this if you see throttling warnings.

## `requestRetries` (type: `integer`):

How many times to retry a failed request to the data source.

## `proxyConfiguration` (type: `object`):

Proxies spread the request rate so Yahoo does not throttle the run into returning stub option chains. Recommended on the platform.

## `asOfDate` (type: `string`):

ISO date used to compute days-to-expiry, e.g. 2026-10-11. Leave empty to use today.

## Actor input object example

```json
{
  "symbols": [
    "AAPL",
    "SPY",
    "NVDA"
  ],
  "screen": "ivPremium",
  "maxExpiries": 3,
  "minDaysToExpiry": 7,
  "maxDaysToExpiry": 60,
  "minOpenInterest": 100,
  "maxSpreadPct": 0.15,
  "minExtrinsicPct": 0.05,
  "moneynessMin": 0.85,
  "moneynessMax": 1.15,
  "realizedVolDays": 20,
  "maxResults": 500,
  "delaySeconds": 1,
  "requestRetries": 3,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

## `contracts` (type: `string`):

One row per option contract: strike, mid, spread, IV, volume, open interest, derived ratios and annualized yields.

## `summary` (type: `string`):

Per-symbol spot, realized volatility, ATM IV, IV premium and skew; the filters applied; warnings; and what each metric does and does not mean.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "symbols": [
        "AAPL",
        "SPY",
        "NVDA"
    ],
    "screen": "ivPremium",
    "maxExpiries": 3,
    "minDaysToExpiry": 7,
    "maxDaysToExpiry": 60,
    "minOpenInterest": 100,
    "maxSpreadPct": 0.15,
    "minExtrinsicPct": 0.05,
    "moneynessMin": 0.85,
    "moneynessMax": 1.15,
    "realizedVolDays": 20,
    "maxResults": 500,
    "delaySeconds": 1,
    "requestRetries": 3
};

// Run the Actor and wait for it to finish
const run = await client.actor("yzy001/options-screener").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "symbols": [
        "AAPL",
        "SPY",
        "NVDA",
    ],
    "screen": "ivPremium",
    "maxExpiries": 3,
    "minDaysToExpiry": 7,
    "maxDaysToExpiry": 60,
    "minOpenInterest": 100,
    "maxSpreadPct": 0.15,
    "minExtrinsicPct": 0.05,
    "moneynessMin": 0.85,
    "moneynessMax": 1.15,
    "realizedVolDays": 20,
    "maxResults": 500,
    "delaySeconds": 1,
    "requestRetries": 3,
}

# Run the Actor and wait for it to finish
run = client.actor("yzy001/options-screener").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "symbols": [
    "AAPL",
    "SPY",
    "NVDA"
  ],
  "screen": "ivPremium",
  "maxExpiries": 3,
  "minDaysToExpiry": 7,
  "maxDaysToExpiry": 60,
  "minOpenInterest": 100,
  "maxSpreadPct": 0.15,
  "minExtrinsicPct": 0.05,
  "moneynessMin": 0.85,
  "moneynessMax": 1.15,
  "realizedVolDays": 20,
  "maxResults": 500,
  "delaySeconds": 1,
  "requestRetries": 3
}' |
apify call yzy001/options-screener --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,yzy001/options-screener"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/wdhmDGmW77maAbIBC/builds/0HafTsAao7NF6OxGn/openapi.json
