Strategy·Developer / quant··6 min read

Build a daily call-sweep digest with the options API

A Node script that pulls every unusual call on your watchlist at the close, ranks them by premium and ships a one-screen Markdown digest to your inbox.

Most of the institutional call buying that matters happens before lunch and goes unwatched until the next morning. A 4 p.m. cron job that hits the OptionsBell unusual options endpoint, filters for calls on a watchlist you actually care about, and posts a ranked digest to your inbox turns the API into something you can act on with a coffee in hand.

Below is a complete Node implementation - no framework, no build step, just a single file and one scheduled run per day.

Why the daily digest format works

Real-time alerts are great for risk. Idea generation works better as a list. Once a day, you want to see the ten biggest calls printed against names you already follow, sorted by premium, so you can pick one or two to research before the next session. That is the whole format.

Step 1 - the watchlist and the filters

Pick the universe you actually trade or track. A short watchlist beats a long one because the signal density goes up. The script takes the list as an env var so a separate cron job can repurpose the same code with a different universe.

// digest.mjs - run once a day, e.g. at 16:30 ET
const API     = "https://optionsbell.com/api/v1/options-flow/unusual";
const KEY     = process.env.OPTIONSBELL_API_KEY;
const SYMBOLS = (process.env.WATCHLIST ?? "AAPL,NVDA,META,GOOGL,AMD,TSM,AVGO,PLTR").split(",");
const TO      = process.env.DIGEST_TO ?? "[email protected]";

async function fetchCalls() {
  const params = new URLSearchParams({
    symbols: SYMBOLS.join(","),
    type: "c",
    min_voloi: "5",
    min_premium: "500000",
    max_dte: "60",
    limit: "100",
  });
  const res = await fetch(`${API}?${params}`, {
    headers: { "X-API-Key": KEY },
  });
  if (!res.ok) throw new Error(`API ${res.status}: ${await res.text()}`);
  const json = await res.json();
  return json.data;
}

Step 2 - rank and format the digest

We sort by premium descending and cap at ten lines - the goal is a digest you can read in 20 seconds, not a CSV dump. Each row carries enough metadata (vol/OI, IV, days to expiry) to know whether the print is a real conviction trade or just a quiet hedge.

function buildDigest(rows) {
  const top = rows
    .sort((a, b) => b.premium - a.premium)
    .slice(0, 10);

  const totalPremium = top.reduce((s, r) => s + r.premium, 0);
  const date = new Date().toISOString().slice(0, 10);

  const lines = top.map((r, i) => {
    const prem = (r.premium / 1e6).toFixed(2);
    return `${i + 1}. ${r.symbol.padEnd(5)} ${r.expiry} ${("$" + r.strike).padEnd(7)} C  ` +
           `Vol/OI ${r.voloi.toFixed(1)}x  $${prem}M  IV ${r.iv}%  ${r.dte}d`;
  });

  return [
    `Watchlist call-sweep digest - ${date}`,
    "",
    `Top ${top.length} unusual calls. Combined premium: $${(totalPremium / 1e6).toFixed(1)}M.`,
    "",
    ...lines,
  ].join("\n");
}

Step 3 - ship it somewhere you actually read

Pick whichever channel you live in - Resend, Postmark or SendGrid for email, a Telegram bot, a Discord webhook, or a Notion page. The example below uses Resend because their REST API is one fetch call and the developer plan is free for personal volume.

async function sendEmail(markdown) {
  const res = await fetch("https://api.resend.com/emails", {
    method: "POST",
    headers: {
      "Authorization": `Bearer ${process.env.RESEND_API_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      from: "[email protected]",
      to: TO,
      subject: "Watchlist call-sweep digest",
      text: markdown,
    }),
  });
  if (!res.ok) throw new Error(`Resend ${res.status}`);
}

const rows = await fetchCalls();
if (rows.length === 0) {
  console.log("No qualifying calls today - skipping email.");
} else {
  await sendEmail(buildDigest(rows));
  console.log(`Sent digest with ${Math.min(rows.length, 10)} rows.`);
}

Step 4 - what shows up in the inbox

The Markdown body renders as a clean monospace block in any client. Each row tells you everything you need to triage before opening a chart:

Top 10 unusual calls. Combined premium: $48.2M.

1. NVDA  Jul 19  $145    C  Vol/OI 9.8x  $14.20M  IV 51%  29d
2. AVGO  Aug 16  $1900   C  Vol/OI 6.4x  $7.10M   IV 38%  57d
3. META  Jul 19  $560    C  Vol/OI 5.1x  $5.85M   IV 33%  29d
4. PLTR  Jun 28  $48     C  Vol/OI 12.0x $4.40M   IV 64%   8d
5. AMD   Jul 26  $185    C  Vol/OI 7.2x  $3.95M   IV 47%  36d
...

Step 5 - run it on a schedule

Drop the script on Vercel as a cron-protected route, run it from a GitHub Actions schedule, or just use a server-side cron. The script is idempotent: re-running it inside the same day reproduces the same digest, so a missed schedule is a non-event.

# .github/workflows/digest.yml
name: Options digest
on:
  schedule:
    - cron: "30 20 * * 1-5"  # 16:30 ET, Mon-Fri (UTC)
jobs:
  digest:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with: { node-version: "20" }
      - run: node digest.mjs
        env:
          OPTIONSBELL_API_KEY: ${{ secrets.OPTIONSBELL_API_KEY }}
          RESEND_API_KEY: ${{ secrets.RESEND_API_KEY }}
          WATCHLIST: "AAPL,NVDA,META,GOOGL,AMD,TSM,AVGO,PLTR"
          DIGEST_TO: "[email protected]"

Where the filters bite

The two filters doing the work here are min_voloi=5 and min_premium=$500k. Below those, the noise of normal market-making swamps the institutional signal you actually want. The max_dte=60 cap keeps the digest focused on positioning that has to express itself inside a tradable window - LEAPs and very long-dated calls are noise for a daily routine.

Three small tweaks that level it up

  • Add a second API call with type=p and a smaller premium floor, then print a 'Watch for downside' section underneath the call digest.
  • Persist each day's rows to a Postgres table and add a 'returning prints' badge when a contract from yesterday hits the digest again - those repeats are unusually high signal.
  • Score each row by recent realised volatility relative to implied; a high vol/OI call on a name with collapsing realised vol is a different setup than the same print on a stock already moving.

One endpoint, a 60-line file, and a free cron is enough to turn the OptionsBell API into a research tool that lives where you read your email. From here, anything else you build is just a different filter on the same feed.