The Wheel Screener Methodology
Educational. Scores do not constitute trading recommendations. See Terms §17.
Every public wheel screener ranks cash-secured puts by some form of premium / strike × 365 / DTE. That number is misleading because it ignores:
- How often the put actually breaches — measured against the actual historical price tape, not just the option's delta.
- How deep the breach is when it happens — the conditional drawdown given assignment, in percent of strike.
- The risk-free rate — every dollar tied up in CSP collateral is a dollar not earning T-bill yield. A CSP that doesn't clear the T-bill is a worse T-bill with tail risk.
- Event risk — earnings inside the DTE window inflate IV but also realized moves. Lumping these in with calm names corrupts the ranking.
- Bag-hold risk — a 50%-IV strike on a stock that fell 60% over 5 years isn't paying you premium, it's paying you to catch a falling knife.
The screener that follows replaces naive yield with excess annualized expected return, computed against an empirical breach table built from 5 years of daily closes on the ~2,500-symbol wheel-eligible universe. Every component is exposed so you can re-rank by any signal you trust.
The default screener formula in every Reddit thread:
This treats every premium dollar as pure return. It tells you nothing about the probability the position ends in assignment, nothing about how bad the assignment is when it happens, and nothing about the opportunity cost of locking up collateral.
The fix is not to throw out yield. It's to keep yield and subtract the things you'd subtract if you were honest.
Two independent estimates of P(assignment), shown side-by-side:
- Implied (the market's view). The risk-neutral probability that the short put closes in the money, approximated by
|short_delta|. Pulled directly from the chain. - Historical (the tape's view). Walk five years of daily closes for this ticker. For each historical observation date
t, ask: didclose[t + DTE]drop more thanmoneyness%belowclose[t]? Aggregate to a breach rate per(DTE, moneyness%)cell.
The interesting signal is the gap between the two:
When |delta| > historical_breach_rate by a meaningful margin, the market is pricing assignment risk above what actually happens at that moneyness — the wheel edge. When the inequality flips, the market is right and you're underpaid. The screener exposes this as p_breach_gap and you can sort by it directly.
For ranking, the conservative blend is max(implied, historical) — when the two disagree, take the more pessimistic side. This biases the score toward positions that are cheap by both measures.
P(assignment) is half the story. The other half: when this thing does get assigned, how much worse off are you than you would have been if it had expired worthless?
For a CSP that breaches, the conditional loss is the additional drop below the strike, as a fraction of strike:
For a covered call that gets called away, the equivalent is forgone upside above the strike:
Both come from the same historical walk that produced the breach rate. The screener stores conditional mean, p95, and max per (DTE, moneyness%) cell so you can also see the worst case, not just the average.
The expected cycle return becomes:
A cash-secured put locks up strike × 100 for the life of the trade. With short-tenor Treasuries at 4–5%, that capital has a real opportunity cost. The honest comparison is not "did I make money" — it's "did I make more money than the T-bill I'm displacing."
The screener reads the live risk-free curve from the Federal Reserve cron, picks the tenor closest to your DTE (4-week, 3-month, 6-month, 1-year), and linearly interpolates. Sub-T-bill positions don't get filtered — they just print negative excess_annualized_return so you can see what's actually cheap.
Earnings inside DTE. Earnings releases drive the largest single-day idiosyncratic moves in the dataset. IV is rich, but realized moves are also rich. Some people specifically want this — earnings premium is a legitimate sub-strategy. Lumping it with non-event setups corrupts the ranking, so the screener flags it (has_earnings_in_dte) and the UI lets you include, exclude, or filter to earnings-only.
Bag-hold score (0–3). Composite flag for "this stock will be a problem to own at strike if you get assigned":
| Component | Trigger | Why it matters |
|---|---|---|
| 5-year return < 0 | Persistent downtrend | The wheel doesn't work on stocks where the price floor keeps falling. |
| Realized vol > 60% | Annualized stdev of daily log returns | High vol = wider tail, deeper conditional losses. |
| 1-year max drawdown < −50% | Recent crash inside lookback | If half the value can disappear in 12 months, the assignment math is incomplete. |
Sum of triggers = 0–3. The screener doesn't drop these — they're flagged. Filter by max_bag_hold if you only want clean names; leave it at 3 if you specifically want the spicy yield.
3x and 2x leveraged ETFs (SOXL, TQQQ, UPRO, NUGT, MVLL, TSLL, etc.) rank near the top of any wheel screener that sorts by yield. The premium is real — IV is enormous because these vehicles move 2–3× the underlying every day. The problem is what happens after assignment.
Daily-rebal NAV decay (volatility drag). A 3x ETF achieves 3× the underlying's daily return through nightly rebalancing. The compounded multi-day return is not 3× the underlying's multi-day return:
In choppy markets the leveraged ETF erodes even when the underlying ends flat. The issuer (Direxion, ProShares, GraniteShares, etc.) prints this in the prospectus: "intended for daily exposure, not for periods longer than one day." Wheeling them — where assignment means owning the vehicle for the duration of the next CC cycle, possibly weeks — fights that design directly.
The implication for assignment math. Our historical breach table walks 5 years of daily closes, so the conditional_loss_pct on a leveraged ETF already partially reflects decay-driven drawdowns. But the table can't price the path-dependent rebal effect during your specific holding period. Two assignments at the same strike with the same DTE will have systematically different recovery profiles than the unleveraged equivalent — and worse on average in any non-trending tape.
How the screener handles it.
- ~131 known leveraged / inverse ETFs are tagged in the cache with
is_leveraged=trueand a signedleverage_factor(±2, ±3, ±1.5 for single-stock products). - The default API filter is
leveraged=exclude. The UI flips this via a dropdown. - Leveraged rows that pass the filter get a
LEV3x/INV3xbadge next to the symbol and a +1 bump tobag_hold_score(capped at 3) so the bag-hold filter also catches them.
If you specifically want to wheel leveraged premium for the IV, switch the dropdown to "Include" or "Only" — the data is there. The default just keeps the top of the ranking honest.
A single five-year breach rate is a lossy summary. If the underlying spent 2021–2022 in a volatile bear regime and 2024–2026 in a calm rotation, averaging them produces a number that fits neither. The breach table now computes the same cell three ways and exposes the disagreement:
The drift number is the headline signal:
- Drift > +25%: recent regime is materially riskier than the 5y baseline. Your historical anchor is optimistic; expect breaches more often than the 5y number suggests.
- Drift between −15% and +15%: regime stable. Trust the 5y number.
- Drift < −25%: recent regime is calmer. Premium may be priced off a fearful long-run memory the tape no longer supports — a potential edge for sellers, but watch for regime reversion.
What feeds the score. wheel_score uses breach_rate_5y as the historical anchor (most observations, least noise). Drift is surfaced as a separate column so you can see the disagreement and choose whether to trust the 5y number or down-weight it.
Conditional loss says "if you get assigned, here's the average drop below strike." It does not say what happens next. For a wheeler who rolls into CCs after assignment, the relevant question is: how often did the stock recover back to strike before the next CC cycle ended?
For each historical breach in the table, we walk forward and ask:
A high recovery rate means most assignments self-resolve within one or two CC cycles — the cond_loss is real but rarely permanent. A low recovery rate means assignments tend to stick. Concrete examples from current data:
| Symbol | 5y breach | Cond loss | Rec 30d | Rec 60d | Rec 90d | Read |
|---|---|---|---|---|---|---|
| AAPL 30d 5% OTM | 22% | 3.6% | 73% | 82% | 88% | Clean: even when assigned, ~3 of 4 recover inside one CC cycle. |
| TSLA 30d 5% OTM | 36% | 10.4% | 47% | 67% | 72% | Spicy: slow to recover, but most do within 90 days. |
| MARA 30d 5% OTM | 45% | 20.0% | 41% | 53% | 60% | Trap: 40% of assignments stick past 90 days — these are the bag-holds. |
Recovery is computed for puts only. For called-away CCs the position is closed, so the "recovery" framing doesn't apply — the wheel resets to CSPs.
Plain multiplication. No black-box weights. The headline is the expected economic return above T-bills, discounted for execution friction.
Every component the score is built from is exposed in the screener — sort by any of them. The defaults are: side=put, min_dte=7, max_dte=45, earnings=include, max_bag_hold=3, sort=wheel_score.
Three CSP candidates from real chain data on June 11, 2026, ranked in the order the methodology would order them. Numbers come from the production cache; see the live screener for the latest.
| Symbol | Strike | DTE | Mny% | Mid | Δ | p_impl | p_hist | Cond loss | Raw yield | Excess | Bag | Verdict |
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| SOXL LEV3x | $220 | 7 | 1.97 | $24.10 | −0.41 | 41.4% | 39.9% | 10.15% | 571% | +348% | 2 | Real premium edge (implied ≈ historical, both ~40%), but SOXL is a 3x leveraged semis ETF — daily-rebal NAV decay means the assignment math understates the cost of being stuck holding. Bag-hold bumped to 2 by the leverage tag. Hidden by default in the screener; opt in only if you specifically want to farm 3x-ETF premium. |
| AAPL | $295 | 7 | 0.20 | $3.75 | −0.46 | 46.0% | 24.4% | 2.5% | 66% | +2.3% | 0 | Honest yield: market is pricing 46% breach, history says 24%. Conditional loss tiny (low vol). Clean name. Raw yield 66% is a mirage — once you adjust, it clears T-bills by 2pp. |
| MARA | $13 | 71 | 4.8 | $1.78 | −0.37 | 36.9% | 45.7% | 29.1% | 70% | −1.7% | 3 | The trap. Raw yield 70% looks juicy. Historical breach (45.7%) is well above implied (36.9%) — the market is underpricing assignment. Conditional loss 29% of strike, bag-hold maxed. Negative excess. Avoid. |
Reading the table. Sorting by raw yield puts MARA above AAPL. Sorting by excess_annualized_return — the methodology's headline column — puts SOXL ≫ AAPL ≫ MARA. Same chains, completely different ranking. That's the whole point.
Three of the previous limitations got promoted to features (see Fix 4 — regime drift and Fix 5 — recovery rate above, and the data-sanity bullet below for the illiquid-quote hardening). The remaining honest caveats:
- Implied volatility skew is not separately decomposed. The wheel screener bakes skew into
p_breach_impliedvia|delta|at the actual strike, which is enough for ranking but does not surfaceRR25/BF25as standalone columns the way the Iron Condor screener does. Whenp_breach_implied≫p_breach_historicalon a low-vol name, sticky put skew is the usual explanation — but you have to infer it. v2 will surface the decomposition. - Dividend timing is ignored for CCs. Deep-ITM calls with ex-div inside DTE risk early assignment as the call holder captures the dividend instead of you. v1 does not flag this; v2 will pull from
DividendTracker. For now, prefer CCs that don't span an ex-div. - Illiquid-name stale quotes. The data-sanity layer rejects: bid ≤ 0, mid > 20% of strike (puts) / 20% of underlying (calls),
mid/strikeinconsistent with|delta|, no trades today AND OI < 200, and last-trade vs mid divergence > 50%. These catch the worst stale-snapshot cases but on thin names treatliquidity_scoreas a hard floor, not a tiebreaker — a "great" row backed by 50 OI and a $0.10/$0.30 quote is not actually fillable at mid. - Regime drift is descriptive, not predictive. Fix 4 surfaces how much the 1y breach rate diverges from the 5y, but the score still uses the 5y anchor. If you trust the recent regime more, sort by
p_breach_gapor filter for drift < 0 — but the methodology won't tell you whether to trust the 1y or the 5y. That's a judgment call the data can't make for you.
The full screener is live at /wheel-screener with sortable columns for every component above.