Skip to content

Instantly share code, notes, and snippets.

@mphinance
Created June 17, 2026 01:52
Show Gist options
  • Select an option

  • Save mphinance/c9e8de7cd8173b5db389634f88c912dd to your computer and use it in GitHub Desktop.

Select an option

Save mphinance/c9e8de7cd8173b5db389634f88c912dd to your computer and use it in GitHub Desktop.
strangle-timer: a trend-following options FLIP skill for Claude. Pre-pick an OTM call and put, hold one side at a time off the 5-minute trend, rotate when it flips. Broker-MCP-agnostic, keyless analysis via analyze.py. By mphinance.substack.com
#!/usr/bin/env python3
"""
strangle-timer / analyze.py - trend-following FLIP (rotation) option read.
Broker-agnostic and READ-ONLY. Despite the skill name this is NOT a held strangle.
You pre-pick two vehicles (an OTM call and an OTM put at ~30 DTE) and hold ONE side
at a time, the side the 5-minute trend is paying you on, rotating when it flips.
It pulls the whole option chain for a ticker and prints:
1. Spot + how much price history exists (the realized-vol honesty check).
2. IV term structure (front vs back) + skew (puts vs calls).
3. A vol verdict: is the name going to MOVE enough to flip profitably?
4. Your two vehicles with live strikes/prices (auto-suggested if you don't pass them).
5. The 5-minute TREND read: which side is in play right now (call if up, put if down),
where the recent reversals were, and a plain-English suggestion.
It NEVER places an order. It hands you a plan; you (or your agent's broker MCP) execute.
Data source: yfinance (keyless, works on a fresh machine). If you have a brokerage
MCP (IBKR, Alpaca, Tradier, Tastytrade, ...) wired into your agent, prefer that for
live quotes/positions and feed the same numbers in; this script is the no-account
fallback so the skill runs for anyone.
Usage:
python analyze.py TICKER
python analyze.py SPCX --put 170 --call 230 --contracts 2
python analyze.py AAPL --target-dte 30
"""
from __future__ import annotations
import argparse
import datetime as _dt
import sys
import numpy as np
import pandas as pd
# Make output safe on Windows consoles (cp1252) and everywhere else.
try:
sys.stdout.reconfigure(encoding="utf-8", errors="replace")
except Exception:
pass
try:
import yfinance as yf
except ImportError:
sys.exit("yfinance not installed: pip install yfinance")
def _mid(row) -> float:
b, a = float(row.get("bid", 0) or 0), float(row.get("ask", 0) or 0)
if b > 0 and a > 0:
return round((b + a) / 2, 2)
lp = float(row.get("lastPrice", 0) or 0)
return round(lp, 2)
def _near(df: pd.DataFrame, target: float) -> pd.Series:
d = df.copy()
d["_d"] = (d["strike"] - target).abs()
return d.sort_values("_d").iloc[0]
def main() -> int:
ap = argparse.ArgumentParser()
ap.add_argument("ticker")
ap.add_argument("--put", type=float, help="OTM put strike (downside vehicle) to price")
ap.add_argument("--call", type=float, help="OTM call strike (upside vehicle) to price")
ap.add_argument("--contracts", type=int, default=1, help="contracts per side (1 or 2)")
ap.add_argument("--target-dte", type=int, default=30,
help="DTE to anchor the two vehicles (default 30, so theta isn't brutal)")
args = ap.parse_args()
tk = args.ticker.upper()
t = yf.Ticker(tk)
try:
spot = float(t.fast_info.last_price)
except Exception:
return _fail(f"no spot price for {tk} (bad ticker or no data)")
if not spot or spot <= 0:
return _fail(f"no spot price for {tk}")
print(f"\n{'='*68}\n {tk} - trend-following FLIP read (spot ${spot:,.2f})\n{'='*68}")
# 1. price history / realized-vol honesty check
h = t.history(period="1mo")["Close"].dropna()
nbars = len(h)
rv = None
if nbars >= 3:
rets = np.log(h / h.shift(1)).dropna()
rv = float(rets.std() * np.sqrt(252))
if nbars:
print(f"\n[1] PRICE HISTORY {nbars} daily closes "
f"({h.index[0].date()} to {h.index[-1].date()})")
else:
print("\n[1] PRICE HISTORY none")
if rv is not None:
flag = " ** THIN - barely meaningful, this is a young listing **" if nbars < 15 else ""
print(f" crude annualized realized vol: {rv*100:.0f}%{flag}")
if nbars < 15:
print(" -> No real HV yet. Whether it moves enough to flip is a JUDGMENT, not a screen output.")
# 2. term structure + skew
today = _dt.date.today()
exps = []
for e in (t.options or []):
try:
d = (_dt.date.fromisoformat(e) - today).days
except Exception:
continue
if d >= 1:
exps.append((e, d))
if not exps:
return _fail(f"{tk} has no listed option expiries")
print(f"\n[2] IV TERM STRUCTURE & SKEW ({len(exps)} expiries)")
print(f" {'expiry':<12}{'dte':>5}{'atmIV':>8}{'straddle':>10}{'%spot':>7}"
f"{'putIV':>8}{'callIV':>8} skew")
term = []
for e, d in exps:
if d > 420:
continue
try:
oc = t.option_chain(e)
except Exception:
continue
c, p = oc.calls, oc.puts
if c.empty or p.empty:
continue
ac, ap_ = _near(c, spot), _near(p, spot)
atm_iv = np.nanmean([ac.get("impliedVolatility", np.nan),
ap_.get("impliedVolatility", np.nan)])
strad = _mid(ac) + _mid(ap_)
cu = c[c.strike >= spot * 1.10].sort_values("strike")
pl = p[p.strike <= spot * 0.90].sort_values("strike", ascending=False)
civ = float(cu.iloc[0].impliedVolatility) if len(cu) else np.nan
piv = float(pl.iloc[0].impliedVolatility) if len(pl) else np.nan
sk = ""
if np.isfinite(civ) and np.isfinite(piv):
sk = "PUT-rich (sharp flushes)" if piv - civ > 0.02 else \
"CALL-rich (sharp rips)" if civ - piv > 0.02 else "flat"
term.append((e, d, atm_iv, strad))
print(f" {e:<12}{d:>5}{atm_iv*100:>7.0f}%{strad:>10.2f}"
f"{strad/spot*100:>6.0f}%{piv*100:>7.0f}%{civ*100:>7.0f}% {sk}")
# 3. vol verdict (heuristic, honest) - here vol is a FUEL gauge for flipping
print("\n[3] VOL VERDICT (does this name MOVE enough to flip profitably?)")
if len(term) >= 2:
front_iv = term[0][2]
back_iv = term[-1][2]
slope = "BACKWARDATED (front rich vs back)" if front_iv - back_iv > 0.03 else \
"CONTANGO (front cheap vs back)" if back_iv - front_iv > 0.03 else "flat"
print(f" term shape: {slope} front {front_iv*100:.0f}% vs back {back_iv*100:.0f}%")
if front_iv > 0.80:
print(f" front IV {front_iv*100:.0f}% is HIGH - wide swings, good fuel for the flip.")
elif front_iv < 0.30:
print(f" front IV {front_iv*100:.0f}% is LOW - tight tape, the flip can saw you up; be picky.")
if rv is not None and nbars >= 5:
ratio = front_iv / rv if rv > 0 else float("inf")
print(f" front IV / realized = {ratio:.2f} (theta-vs-movement context for an intraday hold)")
else:
print(" not enough history for a vs-realized read - lean on the intraday range in [5].")
print(" NOTE: vol here is a FUEL gauge, not a structure picker. High IV / wide intraday")
print(" range = clean trends and sharp reversals = what the flip eats. Tight chop = pass.")
print(" Read the NAME (news/chart/float/catalysts), then go to the 5m trend read.")
# 4. your two vehicles at ~target DTE (auto-suggest OTM call + OTM put if not given)
e0, d0 = min(exps, key=lambda x: abs(x[1] - args.target_dte))
oc = t.option_chain(e0)
c, p = oc.calls, oc.puts
print(f"\n[4] YOUR TWO VEHICLES (anchor expiry {e0}, {d0} DTE, ~30 DTE keeps theta sane)")
if args.put and args.call:
put_k, call_k = args.put, args.call
src = "you passed"
else:
# auto-suggest a reasonable OTM put (~10% below) and OTM call (~10% above)
put_k = _near(p, spot * 0.90).strike
call_k = _near(c, spot * 1.10).strike
src = "auto-suggested (pass --put/--call to override)"
legp, legc = _near(p, put_k), _near(c, call_k)
mp, mc = _mid(legp), _mid(legc)
n = max(1, min(2, args.contracts))
print(f" {src}:")
print(f" upside vehicle {n}x {legc.strike:g}C @ {mc:.2f} (debit ${mc*100*n:,.0f} when you're in the call)")
print(f" downside vehicle {n}x {legp.strike:g}P @ {mp:.2f} (debit ${mp*100*n:,.0f} when you're in the put)")
print(" You hold ONE of these at a time, never both. The flip rotates between them.")
# 5. 5-minute TREND read - which vehicle is in play RIGHT NOW
print("\n[5] 5-MINUTE TREND READ (which side is the tape paying you on right now?)")
try:
bars = t.history(period="5d", interval="5m")[["High", "Low", "Close"]].dropna()
except Exception:
bars = pd.DataFrame()
if bars.empty:
print(" no 5-minute intraday data available - can't read the live trend.")
print(" Pull the 5m chart yourself: higher highs+lows = be in the CALL; lower highs+lows = the PUT.")
else:
bars = bars.copy()
close = bars["Close"]
# short vs longer EMA slope = trend direction
ema_fast = close.ewm(span=9, adjust=False).mean()
ema_slow = close.ewm(span=21, adjust=False).mean()
last = float(close.iloc[-1])
ef, es = float(ema_fast.iloc[-1]), float(ema_slow.iloc[-1])
slope_n = min(6, len(ema_fast) - 1)
fast_slope = ef - float(ema_fast.iloc[-1 - slope_n]) if slope_n > 0 else 0.0
# higher-highs / lower-lows structure over the last N bars
n_look = min(12, len(bars))
recent = bars.iloc[-n_look:]
half = max(2, n_look // 2)
prior_hi, prior_lo = recent.High.iloc[:half].max(), recent.Low.iloc[:half].min()
late_hi, late_lo = recent.High.iloc[half:].max(), recent.Low.iloc[half:].min()
hh = late_hi > prior_hi
hl = late_lo > prior_lo
lh = late_hi < prior_hi
ll = late_lo < prior_lo
up = (ef > es and fast_slope > 0) or (hh and hl)
down = (ef < es and fast_slope < 0) or (lh and ll)
if up and not down:
trend, vehicle, why = "UPTREND", "CALL", "fast EMA above slow and rising / higher highs+lows"
elif down and not up:
trend, vehicle, why = "DOWNTREND", "PUT", "fast EMA below slow and falling / lower highs+lows"
else:
trend, vehicle, why = "CHOPPY / unclear", "WAIT", "EMAs flat or structure mixed - no clean trend to ride"
print(f" last 5m close: {last:.2f} fast EMA(9) {ef:.2f} slow EMA(21) {es:.2f}")
print(f" structure (last {n_look} bars): "
f"{'higher' if hh else 'lower'} highs, {'higher' if hl else 'lower'} lows")
print(f" TREND: {trend} ({why})")
if vehicle == "WAIT":
print(" -> IN PLAY: neither. No clean trend to flip on. Wait for a side to assert itself.")
else:
print(f" -> IN PLAY: the {vehicle}. Plain English: the trend is paying the {vehicle} side; "
f"work that one.")
# flag the most recent reversal points (EMA-cross flips)
cross = np.sign((ema_fast - ema_slow).values)
flips = []
for i in range(1, len(cross)):
if cross[i] != 0 and cross[i - 1] != 0 and cross[i] != cross[i - 1]:
ts = bars.index[i]
flips.append((ts, "up->CALL" if cross[i] > 0 else "down->PUT", float(close.iloc[i])))
if flips:
print(" recent 5m reversal points (EMA flips):")
for ts, dirn, px in flips[-4:]:
print(f" {ts.strftime('%m-%d %H:%M')} {dirn:>10} @ {px:.2f}")
# range read = is there enough movement to flip on?
bars["day"] = bars.index.date
ranges = []
for _day, g in bars.groupby("day"):
ranges.append((g.High.max() - g.Low.min()) / g.Low.min() * 100)
avg_rng = float(np.mean(ranges)) if ranges else 0
read = ("WIDE - clean trends and reversals, good fuel for the flip" if avg_rng > 6 else
"modest - flips will be smaller and chop can saw you, be selective")
print(f"\n avg intraday range: {avg_rng:.1f}% -> {read}")
print(" Rule: be in the side the live trend is paying. On a flip, SELL the held side")
print(" and BUY the other in one motion - never hold both. Flatten by the close.")
print()
return 0
def _fail(msg: str) -> int:
print(f"ERROR: {msg}", file=sys.stderr)
return 1
if __name__ == "__main__":
raise SystemExit(main())

strangle-timer - Claude Desktop execution prompt

Copy everything in the box below into a Claude Desktop chat. It assumes you have a brokerage MCP connected in Claude Desktop (IBKR, Alpaca, Tradier, Tastytrade, ...). Replace the TICKER / strikes / contracts up top. It will analyze first, pick the FIRST side off the live 5-minute trend, and then flip you side-to-side as the trend reverses, reading the ticket back before every order. It never trades without your explicit "go". Despite the skill name this is NOT a held strangle: you hold ONE side at a time and rotate, you never sit long both wings.


You are my trend-following options desk. We are running THE FLIP: pre-pick an OTM
call (upside vehicle) and an OTM put (downside vehicle) at the same ~30 DTE expiry,
then hold ONE side at a time off the 5-minute trend and ROTATE when the trend flips.
This is NOT a held strangle. We never sit long both wings. Read-only until I say "go".

TRADE INTENT (edit me):
  ticker:           TICKER
  method:           directional flip (rotate one side at a time, never both)
  upside vehicle:   ~10% OTM call   (suggest a strike if I left it blank)
  downside vehicle: ~10% OTM put    (suggest a strike if I left it blank)
  expiry:           nearest ~30 DTE (so theta isn't brutal on an intraday hold)
  size:             1 contract per side, max 2
  order type:       limit at the mid (improve if it sits)

DO THIS, IN ORDER:

1) BROKER CHECK. List the brokerage tools you have connected (IBKR/Alpaca/Tradier/
   Tastytrade/etc). If none, say so and continue in analysis-only mode.

2) ANALYZE THE NAME (with your own eyes, not just the surface):
   - Pull the FULL option chain for the ticker from the broker MCP (all expiries).
   - Print the IV term structure (front vs back) and the put/call skew.
   - Tell me how much price history exists. If it is a young listing with no real
     realized vol, say plainly that "will it move enough to flip" is a JUDGMENT,
     not a number.
   - Search the news and read the chart: why is it moving, is it trending or
     chopping, how big is the float, what catalysts are on the calendar (earnings,
     lockup, index inclusion).
   - Give me the vol verdict as a FUEL gauge, not a structure picker: high IV and a
     wide intraday range mean clean trends and sharp reversals, which is what the
     flip eats. Tight chop means stand down. Tell me straight: does this name move
     enough to flip profitably, yes or no.

3) STAGE THE TWO VEHICLES:
   - Quote my OTM call and OTM put at ~30 DTE: each one's mid and the per-contract
     debit. Confirm buying power covers a single leg. These are the two vehicles I
     rotate between; I only ever hold one.

4) PICK THE FIRST SIDE OFF THE LIVE 5-MINUTE TREND:
   - Pull the 5m intraday bars. Read the trend: higher highs + higher lows = uptrend
     = the CALL is in play; lower highs + lower lows = downtrend = the PUT is in play.
   - Take whatever the live tape hands you. Do NOT pre-load both sides. Tell me which
     side is in play and why.
   - STAGE that single leg as a limit order, read the full ticket back to me (ticker,
     side, qty, option type, strike, expiry, order type, limit), and wait for my "go"
     before submitting.

5) WATCH FOR THE FLIP AND ROTATE (one side at a time):
   - After the first leg fills, watch the 5m trend. When it reverses (a lower high
     then a breakdown if we're in the call, or a higher low then a breakout if we're
     in the put), that's the flip.
   - The flip is sell-one / buy-other in ONE motion: stage selling the side I hold
     AND buying the other side, one contract at a time. Read each ticket back and
     wait for my "go". Never leave me sitting long both wings, and never leave me
     flat on both if the trend is clearly running.
   - If the first side just keeps trending my way, keep working that one. I may never
     touch the other vehicle all session, and that's fine.

HARD GUARDS:
   - Trade the trend that is live, not the one I want. If it's going down, we're in
     the put, even if my bias was bullish.
   - Rotate, don't stack. One side at a time. Never hold both wings, never sit flat
     on both while the trend runs.
   - FLATTEN BY THE CLOSE. These are day trades. The name can gap 8-11% overnight,
     so never hold a long option over the bell. Whatever side I'm in, get me out
     before the close.
   - Never submit an order without reading the ticket back and getting my explicit "go".
   - If the tape goes to chop with no follow-through (the flip would just saw us up on
     theta and slippage), say so and recommend we flatten and stand down.

Notes for sharing this with others

  • It is broker-agnostic. Anyone with a brokerage MCP in Claude Desktop can run it; the BROKER CHECK step adapts to whatever they have. With no broker connected, it still does the full analysis and hands them a ticket to punch manually.
  • Claude ships no brokerage connector out of the box. Brokerage access is always a third-party MCP the user installs (IBKR via TWS/IB Gateway, Alpaca, Tradier, Tastytrade, ...). The productivity connectors (Drive/Gmail/Calendar/GitHub/Slack) are the first-party ones; brokers are not.
  • The companion scripts/analyze.py does the same analysis keyless (yfinance) from any terminal, including the 5-minute trend read that tells you which side is in play right now, for people not on Claude Desktop.
  • This is not a held strangle. The skill name is Michael's; the method is a trend-following rotation. You hold one side at a time and flip on the 5m reversal. Owning both wings at once is the thing this is built to avoid.
name strangle-timer
description Trend-following directional FLIP on a single name, run off the 5-minute chart. You pre-pick two vehicles on the option chain, an OTM call (upside) and an OTM put (downside) at the same near-term expiry, then hold ONE side at a time, the side the 5-minute trend is paying you on. When the trend flips you rotate: sell the held side and buy the other in one motion. This is explicitly NOT a held strangle (you never own both wings at once). It opens with an honest analysis: pull the whole chain, read IV term structure and skew, judge vol honestly (even when a young listing has no realized vol), read the name with your own eyes (news + chart + float + catalysts), and check the live 5-minute trend. The vol read mostly tells you whether the name will move enough to flip profitably. Broker-agnostic: uses a connected brokerage MCP (IBKR, Alpaca, Tradier, Tastytrade, ...) to read quotes/positions and stage orders when one is present, otherwise falls back to keyless market data and a hand-punch ticket. Use when someone says "run the flip on X", "trend-follow this with options", "which side am I in", "leg in off the 5m", "rotate calls and puts", or "/strangle-timer".
triggers
strangle-timer
flip
the flip
rotate
trend follow
trend-following
which side am i in
leg in
leg into

strangle-timer

A repeatable playbook for a trend-following directional FLIP on a single name, worked off the 5-minute chart. Despite the name, this is not a strangle in the textbook sense. A strangle means you own both wings to expiry and bet on a big move either way. Here you pre-pick both vehicles but you only ever hold ONE at a time, and you rotate between them as the intraday trend flips. The name stuck because Michael calls it that; the method below is what it actually does.

It is read-and-recommend by default; it only sends an order when the user gives a clear instruction, and it reads the ticket back once before submitting.

The core method in one breath

Pre-pick two vehicles on the chain: an OTM call (your upside vehicle) and an OTM put (your downside vehicle), same near-term expiry, ~30 DTE so theta isn't brutal on an intraday hold. Then ride the 5-minute trend on ONE side, 1 or 2 contracts. Higher highs and higher lows means uptrend, so you're in the CALL. A lower high and then a breakdown is the trend flipping, so you SELL the call and BUY the put in the same motion, ride it down, sell into the flush, and when it bottoms and turns you flip right back to the call. Flatten everything by the close.

What this skill does NOT do

  • It is not a held strangle. You never sit long both wings at once. Holding both is the trade this skill is built to avoid; that bleeds theta on both sides and pays you nothing for the rotation. One side at a time, always.
  • It does not pretend a tool can tell you rich/cheap when there's no realized vol. On a young listing that call is a judgment, and the skill says so out loud.
  • It does not place trades on a hint. Execution needs an explicit instruction.
  • It does not promise a winning flip. You are betting the name will trend and reverse cleanly enough to rotate at a profit. A chop-fest with no follow-through will saw you up. The vol read is there to tell you, honestly, whether the name moves enough to make flipping worth it.

Step 1 - Pull the whole chain + the name's story (the setup)

Run the analysis script (keyless, works on any machine):

python scripts/analyze.py TICKER --put <K> --call <K> --contracts <N>

Leave off --put/--call and it auto-suggests a reasonable OTM put and OTM call around spot to use as your two vehicles. It prints: spot, how much price history exists, the IV term structure and skew, a vol verdict, your two vehicle prices, and a 5-minute trend read that tells you which side is in play right now.

Then look with your OWN eyes, the script can't:

  • News: why is it moving? IPO? earnings? M&A? a meme bid? Search it.
  • Chart: is it trending or chopping? A name that chops sideways gives you nothing to flip on. You want clean legs with real follow-through.
  • Float / borrow: a tiny float (e.g. a fresh IPO at ~4% float) makes moves violent both ways and the upside squeezy. That violence is the fuel for the flip.
  • Catalysts on the calendar: earnings date, lockup expiry, index inclusion. These tell you whether big directional swings are plausible (they usually are when catalysts loom; that is good for flipping).

Step 2 - Judge the vol, read the trend

The vol read here is a fuel gauge, not a structure picker. High IV and a wide intraday range mean the name will throw the kind of moves you can flip on. Low IV and a tight range mean there's nothing to rotate around, so stand down.

  • Wide intraday range / high IV means clean trends and sharp reversals, which is exactly what the flip eats. Good to go.
  • Tight range / low IV / sideways chop means no follow-through. The flip will saw you up on theta and slippage. Pass, or wait for a catalyst.
  • Skew tells you which side is more violent. A put-rich skew (downside fear) says the flushes are sharp, so the put side can pay fast. A call-rich skew (squeeze bid) says the rips are sharp.
  • ~30 DTE on both vehicles. You are holding intraday, not to expiry, so you want enough time value that theta isn't murdering you during a hold, while still getting real delta movement. Thirty DTE is the sweet spot.

State, out loud: is this name going to move enough to flip profitably, yes or no, and which side is the tape handing you first.

Step 3 - Run the flip (the 5-minute chart)

This is the execution. One side at a time.

  • Read the 5-minute trend. Higher highs + higher lows = uptrend = be in the CALL. Lower lows + lower highs = downtrend = be in the PUT.
  • Start with whatever the live tape hands you first. Do NOT pre-load both sides. If the name is trending up when you sit down, you start in the call. If it keeps trending your way all session, you keep working just that one side and may never touch the other vehicle at all.
  • The flip is sell-one / buy-other in ONE motion. When a lower high prints and the name breaks down, that's the flip: sell the call and buy the put together. Ride it down, sell into the flush, and when it bottoms and turns back up, flip right back to the call. Never sit market-neutral on both wings between flips.
  • Size: 1 or 2 contracts on the side you're in. This is a rotation, not a position you stack into.

Hard rules that keep it sane:

  1. Trade the trend that's live, not the one you want. If it's going down, you're in the put, even if your bias was bullish.
  2. Rotate, don't stack. One side at a time. The flip is sell-one / buy-other in one motion. You are never holding both wings, and you are never sitting flat on both waiting.
  3. Flatten by the close. These are day trades. The name can gap 8-11% overnight, so never hold a long option over the bell. Whatever side you're in, you're out before the close.

Step 4 - Execute (only on a clear instruction)

Detect a connected brokerage MCP (search your tools for the broker: IBKR, Alpaca, Tradier, Tastytrade, etc.). If present, use it to pull live quotes/positions and to stage the order. If none is connected, output the exact ticket for manual entry.

Before submitting ANY order, read the ticket back once: ticker, side, qty, option type, strike, expiry, order type, limit. That's a fat-finger check, not a gate. Then submit on the user's confirm. For the flip, stage the FIRST side off the live trend, fill it, then watch for the 5-minute trend to reverse. On the reversal, stage the rotation as two legs (sell the held side, buy the other), read each ticket back, one contract at a time.

See PROMPT-claude-desktop.md for a copy-paste prompt that runs the whole flow in Claude Desktop against a connected IBKR (or other) MCP.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment