feat(tastytrade): a skill for ranking earnings calendar spreads - #23
Merged
Conversation
A calendar into earnings is one bet: that the stock moves less than the options say it will. The IV crush is real and usually large, but it is only edge if the profit band is wider than the implied move. Usually it isn't. The skill enforces that as a gate. calendars.py fits the vol term structure to base vol plus a one-time event jump, then prices every candidate calendar against three move regimes using real bid/ask, and prints per structure whether its band actually covers the implied expected move. Standard library only, so python3 scripts/calendars.py works with no install. The move distribution is a weighted grid rather than Monte Carlo, which is exact and reproducible and still runs 285 structures in about 4 seconds. Writing the selftest caught a bug in the kernel smoothing: each Gaussian's total weight scaled with its own bandwidth, so wide kernels spanned more grid points and silently outweighed narrow ones, tilting the distribution toward the largest historical moves. Each kernel is now normalised to unit mass. reference/pltr-2026-08-03.json is a real PLTR chain from the afternoon of its Q2 print. It doubles as a regression fixture: fit should return an event jump near 13% against a historical rms of 14.8%, which is the fairly-priced case where no calendar has edge. The skill never places orders.
The version gate caught the miss: scripts/ and skills/ are shipped payload, so without the bump every installed copy keeps serving 0.3.0 from cache and never sees the new skill. Wiring the selftest into `make check` closes a second hole. Skill scripts live outside src/, so pytest never collected calendars.py and its assertions were only running when someone invoked them by hand.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
A calendar into earnings is one bet: that the stock moves less than the options say it will. The IV crush is real and usually large, but it is only edge if the profit band is wider than the implied move. Most of the time it isn't, and the usual way to lose money here is to see a 90th-percentile IV rank and a steeply inverted term structure and read that as an opportunity. It is the market pricing a large event correctly.
This adds an
earnings-calendarsskill to the tastytrade plugin that checks the thing that actually decides the trade.How it works
scripts/calendars.pyfits the vol term structure to base vol plus a one-time event jump, which gives an implied expected move. Every candidate calendar then gets priced against three move regimes: the last four quarters, the chain's own implied jump, and the full history you give it. Quotes are real bid/ask with a quarter-spread per leg per side, because deep-ITM strikes look cheapest at mid and are often quoted 50c wide.The gate is a
wide?column in the ranking: does this structure's profit band cover the implied expected move on both sides. ANOmeans the trade needs a below-consensus move to pay.Three commands:
fitfor the term structure,rankfor the ranking,scenarioto stress one structure across the move range including the direction it doesn't want.Choices worth flagging
Standard library only. No numpy, so
python3 scripts/calendars.pyworks with nothing installed. The move distribution is a weighted grid rather than Monte Carlo, which is exact, reproducible run to run, and still gets through 285 structures in about 4 seconds.The selftest caught a real bug. In the kernel smoothing, each Gaussian's total weight scaled with its own bandwidth, so a wide kernel spanned more grid points and silently outweighed a narrow one. That tilted the whole distribution toward the largest historical moves. Each kernel is now normalised to unit mass. This was ported from a working analysis, so those numbers were slightly pessimistic in the tails.
reference/pltr-2026-08-03.jsonis real. PLTR quotes captured the afternoon of its Q2 print, so it works as both the worked example in the skill and a regression fixture.fiton it returns an event jump near 13% against a historical rms of 14.8%, the fairly-priced case where no calendar has edge.rankfinds 2 of 285 structures positive-EV.What the skill carries forward
Findings from the analysis it came out of, so they don't get rediscovered:
Limits
The base-vol slope and the event jump trade off against each other in the fit, so the split is weakly identified. The expected move is robust to about 0.3pp; the skill says not to quote the jump more precisely than that.
The skill never places orders. It is analysis, and analysis is not authorisation.
Testing
make checkpasses: lint, typecheck, 94 unit tests.python3 scripts/calendars.py --selftestcovers the pricer round-trip, put-call parity, recovery of a known jump from a synthetic chain, the move-grid normalisation, and that a calendar is worth more at its strike than away from it.🤖 Generated with Claude Code