Reading alerts by hand does not scale past a handful a day. If you want a ranked shortlist waiting for you every morning, wire the two products into a pipeline: poll OptionsBell for unusual flow, enrich each hit with StockMarketScan screener membership and a stock report, score the result, and emit the top names. This post walks the whole thing. The OptionsBell calls are concrete and real; the StockMarketScan enrichment is shown as an illustrative call because the exact endpoint paths are yours to fill in from their docs.
Stage 1: poll OptionsBell for unusual flow
OptionsBell exposes the unusual-flow endpoint at https://optionsbell.com/api/v1/options-flow/unusual, authenticated with an X-API-Key header and accepting a since ISO timestamp so you only pull new prints. On Pro you get 30 requests per minute and 2,000 per day, and every response carries X-RateLimit-Remaining so you can back off cleanly. Poll on an interval that matches the 5-minute scan cadence; there is no value in hammering it faster.
import os, time, urllib.request, json
from datetime import datetime, timezone
API_KEY = os.environ["OPTIONSBELL_API_KEY"]
BASE = "https://optionsbell.com/api/v1"
def fetch_unusual(since_iso):
req = urllib.request.Request(
f"{BASE}/options-flow/unusual?since={since_iso}",
headers={"X-API-Key": API_KEY},
)
with urllib.request.urlopen(req) as r:
remaining = r.headers.get("X-RateLimit-Remaining")
body = json.load(r)
if remaining is not None and int(remaining) < 3:
time.sleep(60) # near the per-minute cap, cool off
return body["results"]
since = datetime.now(timezone.utc).replace(microsecond=0).isoformat().replace("+00:00", "Z")
hits = fetch_unusual(since)
print(f"{len(hits)} unusual prints since {since}")Stage 2: enrich each hit with StockMarketScan
For every ticker that clears your thresholds, ask StockMarketScan two things: which named screeners the ticker belongs to, and what its stock report says about trend and fundamentals. The call below is illustrative. Replace the URL and field names with the real ones from StockMarketScan's API docs, or swap the whole function for MCP calls if you drive that side through Claude instead.
import os, urllib.request, json
# ILLUSTRATIVE ONLY - not a real StockMarketScan endpoint.
# Substitute the actual paths/fields from stockmarketscan.com's API docs.
SMS_BASE = "https://stockmarketscan.com/api" # placeholder base
def enrich(ticker):
req = urllib.request.Request(
f"{SMS_BASE}/enrich?ticker={ticker}", # placeholder path
headers={"Authorization": f"Bearer {os.environ.get('SMS_API_KEY','')}"},
)
try:
with urllib.request.urlopen(req) as r:
data = json.load(r)
except Exception:
return {"screeners": [], "trend": None, "fundamentals": None}
# placeholder field names - align with the real response shape
return {
"screeners": data.get("screeners", []),
"trend": data.get("report", {}).get("trend"),
"fundamentals": data.get("report", {}).get("fundamentals"),
}The point is the shape, not the exact URL: one lookup per ticker that returns a list of screener memberships plus a compact report. If you prefer not to touch their REST API at all, run this stage as StockMarketScan MCP calls inside Claude and feed the structured answer back into your scorer.
Stage 3: score the combined signal
Now fuse the two layers into one number. The options side gives you conviction on the flow; the screener side gives you context on the stock. A simple additive score works well and stays easy to reason about:
- Options weight: scale Vol/OI and premium so a bigger, more anomalous print scores higher.
- Screener weight: add points for each confirming screener, with momentum and trend watch worth more when the flow is directional.
- Alignment bonus: reward the case where flow direction and trend agree, and penalize the case where an aggressive directional bet lands on a defensive or downtrending name.
- Freshness: give a small edge to prints that are minutes old over ones near expiry of your polling window.
def score(hit, ctx):
s = 0.0
s += min(hit["vol_oi"], 10) * 2 # options anomaly
s += min(hit["premium"] / 250_000, 8) # size of the bet
for name in ctx["screeners"]:
s += 3 if name in ("Hot Prospects", "Trend Watch") else 1.5
if hit["direction"] == "bullish" and ctx["trend"] == "up":
s += 4 # alignment bonus
if hit["direction"] == "bullish" and ctx["trend"] == "down":
s -= 3 # fighting the trend
return round(s, 1)
ranked = []
for h in hits:
if h["vol_oi"] < 3 or h["premium"] < 500_000:
continue
ctx = enrich(h["ticker"])
ranked.append((score(h, ctx), h["ticker"], ctx["screeners"]))
ranked.sort(reverse=True)
for sc, tk, screeners in ranked[:10]:
print(f"{sc:>6} {tk:<6} {', '.join(screeners) or 'no screeners'}")Stage 4: schedule, emit, and stay under the limits
Run the pipeline on a scheduler, tracking your last since timestamp so each poll only pulls new prints. Emit the top ten to wherever you actually look: a Slack message, a morning email, or a small dashboard. Respect the Pro caps of 30 per minute and 2,000 per day by watching X-RateLimit-Remaining and batching enrichment so you do not fan out one request per ticker without a ceiling.
The result is a ranked shortlist that already fuses whale flow with screener context, so the names at the top are the ones where an independent system also likes the stock. That is a far better starting point than a raw alert feed.
Ship it
Start from the real endpoint and build outward: the OptionsBell code above runs as-is with your key, and the StockMarketScan enrichment is a single function to point at their real API or MCP server. Read the full API reference at optionsbell.com/docs, wire the enrichment against StockMarketScan, and set up alerts as a live feed alongside the pipeline.