Methodology
OptionIncomeTools uses standard Black-Scholes-Merton option pricing with the Abramowitz-Stegun normal CDF approximation. “Probability” on this site is not one number — different tools compute different events with different methods; see the probability-method matrix in section 9a for the exact mapping (Black-Scholes N(d2) for finish-ITM, absolute delta for the quick proxy, 5,000-path Monte Carlo for multi-leg POP where implemented, a heuristic for the Planner). Quote data is refreshed every 5 minutes via Polygon.io (delayed ~15 minutes on the current data plan). The risk-free rate is live-fetched daily from the U.S. Treasury Fiscal Data API and exposed at /api/rfr with its observation date. All formulas — annualized yield, ROC, downside cushion, wheel score, liquidity score — are documented on this page.
The exact formulas, data sources, scoring weights, and limitations behind every calculator, screener, and score on this site. This page is updated whenever methodology changes.
Most options-income websites hide their math behind a brand name. We don't. Below is the complete and current methodology for OptionIncomeTools. If you spot an error or have suggestions, email [email protected].
1. Data sources
| Data point | Primary source | Fallback | Refresh cadence |
|---|---|---|---|
| Option chains (strikes, bids, asks, last, IV, Greeks, OI, volume) | Polygon.io Options Starter | Tradier (BYO key) | 30-60 seconds |
| Equity quote (spot price) | Polygon snapshot | Put-call parity from chain | 30 seconds |
| Day-over-day change / prev close | Stooq CSV (free, reliable from CDN egress) | — | End-of-day |
| Dividend schedule (ex-date, amount, frequency) | Polygon /v3/reference/dividends | — | Daily |
| Earnings calendar | Polygon | — | Daily |
Every external request is cached in Cloudflare Workers KV with TTLs ranging from 30 seconds (quotes) to 24 hours (dividends). On cache miss followed by upstream failure, we serve the last-known-good value from a separate 30-day KV slot.
2. Option pricing — Black-Scholes-Merton
All theoretical option values use the standard closed-form Black-Scholes-Merton model with a continuous-dividend yield. For a European call:
For a European put:
where:
d2 = d1 − σ · √T
- S = spot price · K = strike · T = time to expiration in years (days/365)
- r = risk-free rate. As of 2026-07-16 this is auto-fetched daily from the U.S. Treasury Fiscal Data API (daily_treasury_yield_curve_rates), taking the 3-month Treasury constant-maturity yield published each business day around 4:15 p.m. ET. The rate, its observation date, and the fetch timestamp are exposed at /api/rfr. On upstream failure we serve the last-known-good value from a 30-day KV slot, with the stale-date noted in the response. Example format: “Risk-free rate: 3.84%, 3-month Treasury constant maturity, observation dated 2026-07-14.” A hardcoded 4.5% fallback remains only as a defensive default when both live and LKG fail (e.g., first-ever request before the daily cron has run).
- q = continuous-dividend yield (annualized from upcoming declared dividends; 0 if no dividend)
- σ = implied volatility (sourced from Polygon's chain field; we do NOT recompute it on the client)
- N(·) = standard normal CDF, implemented via Abramowitz-Stegun rational approximation (max error < 1e-7)
Limitation: American-style options can be exercised early. Black-Scholes is a European model and will under-price American puts and over-price American calls near ex-dividend dates. For income-selling strategies (CC, CSP), this approximation is acceptable because we care about expected value, not exercise-time alpha.
3. Greeks
Closed-form Greek derivatives, with the same r and q assumptions:
| Greek | Call | Put |
|---|---|---|
| Delta | exp(-q·T) · N(d1) | exp(-q·T) · (N(d1) − 1) |
| Gamma | exp(-q·T) · φ(d1) / (S · σ · √T) | same as call |
| Theta (per year) | −S · exp(-q·T) · φ(d1) · σ / (2√T) − r · K · exp(-r·T) · N(d2) + q · S · exp(-q·T) · N(d1) | −S · exp(-q·T) · φ(d1) · σ / (2√T) + r · K · exp(-r·T) · N(-d2) − q · S · exp(-q·T) · N(-d1) |
| Vega (per 1% IV change) | 0.01 · S · exp(-q·T) · φ(d1) · √T | same as call |
| Rho (per 1% rate change) | 0.01 · K · T · exp(-r·T) · N(d2) | −0.01 · K · T · exp(-r·T) · N(-d2) |
φ(·) is the standard normal PDF. Theta is reported per calendar day (divided by 365) in the Greeks calculator UI to match trader convention.
4. Annualized yield (covered calls, CSPs)
For a single-cycle annualized yield used everywhere on the site:
The denominator matters. A CC has three legitimate denominators (current market value, user cost basis, contract strike), each answering a different question. The site shows all three when relevant; the denominator label is always visible next to the number. Do not compare a yield printed against “strike” to a yield printed against “cost basis” without adjusting.
| Metric label | Formula (per contract, per cycle) | Question it answers |
|---|---|---|
| Premium yield on current value | premium ÷ (spot × 100) | What income the trade produces relative to the shares' present market value. |
| Premium yield on cost basis | premium ÷ (cost_basis × 100) | What income the trade produces relative to what the user actually paid for the shares (only defined for CC / CSP-after-assignment). |
| Screen yield on strike value | premium ÷ (strike × 100) | Comparison metric for ranking strikes across a chain / across tickers. This is what the screener sorts on. Not a projected return. |
| Total return if called | (premium + (strike − cost_basis) × 100) ÷ (cost_basis × 100) | What the position returns if the CC is assigned at expiration. Combines premium plus share appreciation to strike. |
| Annualized screen yield | (selected cycle yield) × (365 ÷ DTE) | Cycle yield extrapolated to a full year assuming perpetual re-entry at the same terms. Comparison metric, not a projected annual return. |
DTE = days to expiration. premium = mid-of-bid-ask × 100 shares (see §18 for pricing mode).
Realized-return caveat: annualized screen yield assumes the same premium/strike combination repeats every cycle for a full year. It is a comparison metric. Realized returns may differ materially from the annualized screen yield because opportunities may not be continuously available and execution, assignment, underlying-price movement, commissions, and taxes affect results.
5. Downside cushion
For a covered call:
For a cash-secured put:
Cushion represents how far the underlying can drop before the position turns negative — a simple proxy for downside risk before assignment.
6. Wheel Score (0-100 composite)
Used in the Wheel Strategy Screener to rank candidates:
- yield_norm = min(1, combined_yield / 1.5) — caps at 150% APY contribution
- liquidity_norm = liquidity_score / 100 (see #7)
- delta_quality = max(0, 1 − |delta_avg − 0.27| · 2) — peaks at 0.27 average delta
- iv_regime = max(0, 1 − max(0, iv − 0.80) / 0.5) — penalizes IV above 80%
Higher is better. We don't claim this is a backtested edge — it's a heuristic for surfacing candidates that combine income with manageable risk.
7. Risk Score (0-100, higher = riskier)
- cap_penalty = +10 for mid-cap, +20 for small-cap, 0 for large+/mega
- sector_penalty = +5 for Energy, Financial, Crypto-correlated
8. Liquidity Score (0-100)
The site currently uses two distinct liquidity scoring functions because they answer different questions. Both are 0-100 scaled but are not interchangeable — the audit flagged this and it is intentional:
| Function | Formula | Where used | Answers |
|---|---|---|---|
liquidityScore(volume, oi)(Wheel + Ticker rankings) | min(100, round(log10(1 + volume + open interest) / 6 × 100)) | Wheel Screener sub-score (line 899 in worker/src/index.js); feeds the 25% liquidity_norm weight in Wheel Score | “Is this contract deep enough to trade at all?” — heavily rewards mega-liquidity, saturates at ~1M combined |
| Composite spread + OI + volume score (Sweeper, CSP Optimizer) | oiScore × 0.40 + volScore × 0.30 + spreadScore × 0.30, each 0-100 normalized | Iron Condor / Butterfly Sweeper, CSP Optimizer scoring (lines 2714, 2985 in worker/src/index.js) | “How favorable is the execution surface right now?” — punishes wide spreads directly |
Recommended executable-liquidity model (Phase 2 shipped 2026-07-18; see §10b): the shared executionGate() and estimateExecutableCredit() helpers in worker/src/execution.js combine spread% of mid, open interest, volume, quote age, and (when available) displayed size into a single 0-1 fill-probability score, and reject contracts on hard gates (spread% > 30%, OI < 10, quote age > 30 min, executable credit < $0.05/share). New scanners are expected to consume this shared module instead of adding a third liquidity formula. The two legacy scoring functions above are being kept until dependent surfaces are migrated over.
Legacy formula retained for backwards compatibility: liquidity = min(100, round( log10(1 + volume + open_interest) / 6 × 100 ))
This compresses raw volume + OI onto a log scale: 10K combined ≈ 50, 100K ≈ 75, 1M ≈ 100. Used to rank candidates and to surface "thin liquidity" warnings.
9. Income Goal Planner allocation
The planner solves a simple greedy allocation:
- Run the wheel screen at the user-selected risk band's filters (delta, DTE, market-cap, max price).
- For each candidate (in wheel-score order), allocate up to 20% of total capital (diversification cap).
- Stop when either expected monthly income reaches 120% of target, or 95% of capital is deployed.
- Expected monthly premium uses the Sequential Wheel state model (default) — the same capital cycles between (a) cash-state selling a CSP and (b) assigned-state selling a CC, never both simultaneously against the same collateral. Steady-state monthly premium = [ fcash · (csp_premium × 365 / csp_DTE) + fassigned · (cc_premium × 365 / cc_DTE) ] × 100 × contracts / 12, with fcash = fassigned = 0.5 as the baseline occupancy split. The previous formula summed csp_premium + cc_premium in the same cycle, which double-counted premium when the user had a single collateral bucket.
- Users who genuinely fund CSP collateral and long-shares capital as separate buckets can pass
wheelMode=dual-capital. In that mode the additive form is correct and the collateral base doubles.
Probability of hitting target is a rough heuristic: (expected/target) · (1 − avg_delta·0.4) + diversification_bonus. It's not a Monte Carlo simulation.
9a. Probability methods matrix — which tool uses which method
Different tools on the site compute "probability" using different mathematical methods. The methodology page previously said "5,000-sample Monte Carlo" while the calculators separately said "delta" or "Black-Scholes N(d2)." Both were true for their respective tools, but the site never made the mapping explicit. This table fixes that. Assignment probability (early exercise) and finish-ITM probability (at expiration) are different events and are not used interchangeably in the code — only in older prose.
| Metric | Method | Tools that display it | What it does NOT tell you |
|---|---|---|---|
| Finish-ITM probability (put finishes below strike, or call finishes above strike, at expiration) | Black-Scholes closed-form N(d2) under GBM with constant vol | Covered-Call Calculator, Cash-Secured-Put Calculator, per-ticker dashboards | Says nothing about whether the position was profitable en route to expiration, or whether early exercise happened. |
| Quick assignment proxy (rough shortcut for "chance of getting assigned") | Absolute delta of the short leg (|Δ|), which approximates N(d2) for options near the money | Strike Screener, Opportunities dashboard, CSP/CC scan cards | Delta is not the same as N(d2) — it's actually N(d1) for calls / −N(−d1) for puts. It over-states finish-ITM probability by roughly (d1 − d2) which is small for short-dated ATM options and larger for long-dated far-OTM options. We use it because it renders instantly from the chain without needing to recompute. |
| Multi-leg probability of profit (POP) (chance of a spread / condor / butterfly finishing in a net-positive zone at expiration) | 5,000-path Monte Carlo simulation of terminal prices under GBM with the leg's implied vol | Strategy Builder, Sweeper (condor / butterfly), CSP Optimizer’s per-candidate CVaR/EV block, Wheel Monte Carlo page | Uses GBM — assumes constant volatility and no jumps. Actual empirical distributions have fatter tails than GBM, so realized POP tends to be slightly worse than model POP for far-OTM structures. |
| Return distribution (histogram of what your P&L could look like across many market paths) | Not simulated in the Backtester — it uses one historical path per parameter set. Simulated in the Wheel Monte Carlo tool via a separate model. | Backtester (single-path); Wheel Monte Carlo (5,000-path distribution) | The Backtester's single-path result is not a confidence interval. To see distribution shape, use the Wheel Monte Carlo tool. |
| Rough plan-hit heuristic (Income Planner's "probability of hitting monthly target" chip) | Deterministic formula: (expected_monthly ÷ target) · (1 − avg_delta · 0.4) · diversification_bonus, clipped to 0–100% | Income Planner results header only | This is a coarse scoring signal, not a probability in the statistical sense. It exists because the Planner's fan-out cost precludes running a full Monte Carlo on every allocation. |
Vocabulary discipline: The site distinguishes finish-ITM probability (an expiration event, computed from N(d2) or a proxy) from early-assignment probability (a during-the-life event that depends on dividend timing and interest-rate levels). Early assignment for American options is not currently modeled — see the American-option limitation note in section 2.
10. Current Screened Setups ranking
The current screened setups dashboard runs a parallel screen across ~30 popular tickers every 5 minutes, picks the top single CC and top single CSP per ticker within the delta band, and ranks by annualized yield. The KV cache layer is described in the data sources section.
11. Methodology changelog
- 2026-06-10 — Methodology page published. Added Wheel Score and Risk Score weights. Documented stale-while-revalidate caching layer.
- 2026-05-30 — Switched day-over-day change source from Yahoo to Stooq for Cloudflare egress compatibility.
10f. Expected-utility ranking objective (Deep audit Phase 6)
Shipped 2026-07-18. The deep methodology audit’s central recommendation was: “Stop ranking options primarily by annualized yield and delta. Rank them by expected net utility after accounting for company quality, valuation, macro regime, forecast volatility, skew, execution costs, event risk, assignment exposure, opportunity cost, and expected shortfall.” Phase 6 ships the scoring engine and configurable coefficients; the actual ranking pages continue to default to annualized-yield sort during the A/B burn-in.
Formula (per candidate):
Strategy-specific penalty:
- Covered call: add −ω·ExpectedUpsideForfeited — punishes strikes that cap upside on names with real momentum.
- Cash-secured put: add −ρ·E[loss | assignment] — punishes premium that isn’t compensation enough for the tail if the stock breaches.
Default coefficients (v1.0) match the audit’s suggested weights and are intentionally unvalidated — the Phase 5 paper trader will calibrate them over the next 60 days: λ=0.5, μ=0.3, ν=0.2, ξ=0.15, ω=0.4, ρ=0.3. Fundamental-score hard-gate defaults to 40 (candidates below are ineligible regardless of premium). Loss threshold defaults to 10% of collateral.
Live tuning: coefficients live in KV under utility:coefficients:v1 and can be updated without redeploying via POST /api/utility/coefficients?token={secret} with a JSON body of the fields to override. Current values readable at /api/utility/coefficients.
Ad-hoc scoring: POST /api/utility/score with {candidate, simResult, fundamentalScore, eventFlags, portfolio, vrp} returns the utility number, the eligibility flag, the per-component penalty breakdown, and the reasons array. Useful for calculator UI “why this rank?” explainers.
What’s NOT yet flipped: the /opportunities, /screen/wheel, and /csp/scan ranking surfaces still sort by annualized screen yield by default. The audit’s dual-rank A/B rollout means both cohorts are snapshotted to KV each night so the paper trader can grade which set delivered better realized outcomes; the default sort will flip only after that data supports it. This is intentional — the audit warns against promoting a new ranking objective without out-of-sample validation.
10e. Strategy-matched paper trader (Deep audit Phase 5)
Shipped 2026-07-18. The existing TA paper trader validates directional long-option signals — it buys ATM calls/puts on flagged setups and grades the long-option outcome 30 days later. That’s useful for directional signal quality, but it does not validate covered-call or cash-secured-put selection. This module fills that gap.
Daily snapshot cadence: nightly cron (auth-gated at /api/paper/strategy/snapshot?token=X) records the top-10 CC and top-10 CSP candidates from the Opportunities ranking. Each snapshot captures the exact contract, mid quote, executable credit (Phase 2), delta, IV, and underlying price so post-hoc grading is deterministic.
Fixed baselines snapshotted alongside every recommendation: 0.20Δ 30-DTE CSP, 0.30Δ 45-DTE CSP, 0.20Δ 30-DTE CC, broad-index SPY CC (30-DTE 0.20Δ), broad-index SPY CSP (30-DTE 0.20Δ), buy-and-hold matched underlying, cash at RFR. These are the audit’s specified benchmarks — the ranking has to beat them to earn keep.
Grading: as each snapshot reaches its expiration date, the grader (also on nightly cron) pulls the realized underlying close from Stooq (free) and computes the settled payoff. Positions move from paper:strategy:open to paper:strategy:closed:{YYYY-MM}. Turnover, assignment rate, and strike-breach rate are captured per position.
Metrics (from the audit's 17-item spec, computed by computePortfolioMetrics()): net annualized return, return on deployed capital, max drawdown, 95% and 99% expected shortfall, avg win / avg loss, profit factor, win rate, assignment rate, early-assignment rate (roadmap), breach rate, capital utilization, total P&L vs. buy-and-hold baseline, vs. cash baseline, three worst-performing months. Turnover + slippage are proxied by execution-mode stamping. Exposed at /api/paper/strategy/results.
Probability calibration: every graded position contributes its predicted finish-ITM proxy (|delta|) and its realized outcome (0/1) to a decile-bucketed calibration accumulator. Rolling Brier score computed across all buckets. This is the empirical calibration audit item — when the accumulator has enough observations we’ll publish an isotonic-regression calibrated probability alongside the raw model probability.
Backtest overfitting: v1 uses the audit’s baseline-comparison test as a first-order overfitting guard — if the ranking’s realized net-annualized-return is not materially above the fixed-baseline CSPs, the ranking hasn’t earned its complexity. Deflated Sharpe / probability-of-backtest-overfitting are roadmap.
Honest caveats: (1) synthesized fills at executable-credit haircuts, not real broker orders; (2) grades at expiration only, no intra-cycle management (early close / roll / stop); (3) no dividends adjustment yet; (4) survivorship-free (positions are locked in on snapshot day and graded regardless of subsequent delisting); (5) 30-45 days of warm-up before the first graded metrics appear.
10d. Volatility distribution engine (Deep audit Phase 4)
Shipped 2026-07-18. Adds a physical-vol forecast alongside the risk-neutral IV surface, so we can separate premium that’s compensation for real risk from premium that’s an exploitable risk premium.
HAR-RV forecaster — Corsi’s 2009 Heterogeneous AutoRegressive Realized Volatility model. Regresses next-day log-RV on lagged daily, weekly (5-day mean), and monthly (22-day mean) realized-vol components using closed-form ridge OLS. Small (n=4) design so we can fit inside a Workers isolate without external libraries. Falls back to trailing-20-day RV when history is too short. Exposed at /api/vol/forecast; response includes fitted coefficients, R², sample size, and the current daily/weekly/monthly components so users can audit the forecast.
Volatility risk premium (VRP) — /api/vol/vrp returns IV² − forecastRV² alongside a percent-of-IV-variance ratio and a label (rich, neutral, cheap, stale). A positive VRP means the market is paying more for variance than our model expects — the audit’s condition for granting a candidate a volatility-edge benefit. Callers should require VRP > 0 and confirm the excess covers execution costs before treating a strike as attractive.
Filtered historical simulation — filteredHistoricalSimulation() standardizes historical daily returns by their contemporaneous 20-day rolling vol, resamples the standardized residuals with replacement, and rescales to the forecast vol. This preserves the empirical shape (skewness, fat tails) while adjusting for the current regime. Default: 5,000 paths, terminal-price ratios at the requested horizon.
Expected P&L + 95% CVaR helpers — ccExpectedPnl() and cspExpectedPnl() consume a bank of simulated terminal-price ratios plus contract params and return: expected net P&L per contract, probability of profit, probability of underperforming buy-and-hold (CC only), expected upside forfeited if called, 95% CVaR, and expected incremental vs. stock. These are the building blocks for the audit’s expected-utility rank objective — not yet wired into the ranking scans, but callable from calculator surfaces immediately.
What’s NOT in v1: full EGARCH(1,1) MLE (needs scipy-grade optimizer — roadmap); no-arbitrage IV-surface fit (SVI or constrained splines) — roadmap; jump-diffusion earnings variance decomposition — roadmap; probability calibration curve from realized outcomes (needs Phase 5 paper-trader observation stream). VRP alone is a rougher signal than the full stack the audit specifies, but it’s directionally correct and transparent.
10c. Fundamentals + macro layer (Deep audit Phase 3)
Shipped 2026-07-18. Two shared modules feed CSP suitability and covered-call opportunity-cost decisions:
SEC XBRL fundamentals — /api/fundamentals pulls point-in-time financial data from SEC EDGAR’s free Company Facts API (no license required; only a User-Agent header). Extracted metrics: revenue TTM + YoY growth, gross/operating/net/FCF margin, cash conversion (opCF ÷ net income), total debt, net debt, cash, equity, net-debt-to-EBITDA (approx.), interest coverage, debt-to-equity, share count YoY, stock-based-comp-to-revenue. Cached in KV for 24 hours; refreshed nightly for the Core-54 tickers via Workers Cron. Provenance stamped with SEC filing date, entity name, CIK.
Fundamental Downside Score (v1.0) — a 0-100 sector-normalized composite intended to answer the question “is this a company the user should still be willing to own after a 20% decline?” The weighting matches the audit's spec: profitability + cash generation 25%, balance-sheet resilience 20%, growth quality 15%, earnings/accrual quality 15%, dilution/governance 5%. Sector-specific bands are defined for Financial (deprioritize net-debt-to-EBITDA; use book leverage; deposits mean higher acceptable D/E) and Technology (tolerate higher SBC dilution; require higher gross margin); everything else uses default bands. Metrics are interpolated between quality thresholds and combined by weighted average. Guidance/estimate-revisions (10% weight in the audit spec) is omitted from v1.0 because it requires a licensed consensus feed — we run without it until a valid source is wired. Valuation buffer (10%) is present in the module but depends on price data joined at call time, not stored in the fundamental cache.
Macro regime classifier — /api/macro/regime reads six free public series (real GDP growth, nonfarm payrolls, unemployment, core CPI YoY, core PCE YoY, fed-funds effective, 2y/10y Treasury yields, ICE BofA HY OAS, VIX) from the FRED API when FRED_KEY is configured, otherwise falls back to a hardcoded July 2026 snapshot with a stale-date warning. Rule-based classifier assigns probability weights across six regimes: Goldilocks, Inflationary growth, Disinflationary slowdown, Stagflation, Credit stress/recession, Recovery. Softmax over rule-firing scores; no black-box HMM. Transparent reasons[] array explains why each signal fired.
Sector sensitivity table — each of the 9 sectors we track has directional multipliers for rates / credit / oil / dollar exposure. Used to bias regime-conditional CSP eligibility (e.g., long-duration Technology gets penalized in the Inflationary growth regime; Energy gets a bump when oil is rising). Values are directional priors, not calibrated betas.
Not yet wired: the CSP eligibility gate that requires a Fundamental Downside Score above a threshold before a candidate can be surfaced by the ranking scans. The score is computed and available; hooking it into handleCspScan and the CSP Optimizer suggestions panel is the next step. Ranking still uses annualized screen yield as documented in §4 — the Phase 3 modules are strictly additive right now.
10b. Execution controls — executable credit, gates, and event flags
Deep audit Phase 2 (shipped 2026-07-18) added a shared execution layer that every ranking surface can consume:
| Field | Definition | Where |
|---|---|---|
midpoint | (bid + ask) / 2 per share. Optimistic; not what a real order fills at. | Every candidate row, backwards-compat only. |
executable | midpoint − 20% × spread. The default fill target for a patient limit order. This is the credit ranking surfaces should use going forward. | Every candidate row, exposed as exec.executable. |
conservative | midpoint − 50% × spread. Immediate marketable-limit estimate. | Every candidate row, exposed as exec.conservative. |
fillProbability | 0-1 heuristic combining spread%, open interest, volume, quote age, and displayed size. Not yet calibrated against observed fills — Phase 5 work. | Every candidate row, exposed as exec.fillProbability. |
Hard execution gates. A candidate must pass all of these to be surfaced by ranking scans (calculator inputs are always shown regardless):
- spread% of mid ≤ 30%
- open interest ≥ 10
- quote age ≤ 30 minutes (relaxed to 60 min after-hours and weekends)
- executable credit ≥ $0.05 per share ($5 per contract)
- displayed size ≥ 0 (unenforced until Options Advanced data plan is available)
Failing candidates return a gate.reasons array so the UI can explain why a strike was filtered rather than silently dropping it.
Event flags. Each candidate is annotated with eventFlags: earnings, exDiv, and macro[]. Earnings are pulled from an internal calendar mapping the Core-54 tickers to their next report date; ex-dividends come from Polygon reference dividends; macro events (CPI, employment, FOMC) come from a hardcoded rolling calendar populated from the BLS release schedule and Federal Reserve statement dates — refreshed quarterly and cached in KV for 24h. See /api/events for the raw JSON.
Early-exercise flag. For deep-ITM short calls near an ex-dividend, a call holder is economically rational to exercise the night before ex-div if the remaining extrinsic is less than the upcoming dividend. The shared helper earlyExerciseFlag classifies each short call as high / moderate / low / n/a risk with a plain-language reason string.
11a. Pricing mode — commissions, slippage, and collateral interest
Every displayed premium is labeled with one of four pricing modes. The methodology previously said “we don’t adjust for commissions or slippage” while the covered-call calculator claimed “default commission and modeled spread slippage.” Those two statements contradicted each other and confused users about what the printed dollar figures meant. The definitions below now govern site-wide, and each result surface is stamped with the exact mode used.
| Mode | Definition | Where it’s used |
|---|---|---|
| Gross midpoint | Mid-of-bid-ask × 100 shares. Ignores commissions and slippage entirely. Optimistic. | Live opportunities dashboard, Wheel Screener, per-ticker SSR snapshots, custom screener — all ranking surfaces |
| Gross bid-side | Bid × 100 shares. Conservative estimate of an immediate marketable-limit sell. | Available as a comparison toggle on CC + CSP calculators |
| Net after commissions | Mid × 100 − commission (default: $0.65/contract per side). | CC + CSP calculators when the user enters a non-zero commission. Backtester also applies this. |
| Net after commissions and modeled slippage | (Mid × 100) − slippage_haircut × (bid-ask spread × 100) − commission. Default haircut: 20% of spread. | Backtester (strategy simulator). Available as an advanced toggle on the calculators. |
Collateral interest on CSPs. The premium row shows premium alone. When the user enables the “Include collateral interest” toggle, a second row is added: collateral × effective_rate × DTE/365, where effective_rate defaults to the live 3-month Treasury yield fetched at /api/rfr. Combined-return figures are labeled “Premium + collateral interest” so they’re never confused with premium alone. Broker-specific collateral policies (e.g., box-spread margin, SPAXX vs. bank-swept cash, non-interest-bearing money-market equivalents) vary and are not modeled here.
Result stamping. Every scan result and calculator output is tagged with model version (currently v1.0), pricing mode (from the table above), and the quote-as-of timestamp so a stale or overly optimistic figure can be traced back to its exact provenance.
11. Methodology changelog
- 2026-07-18 — Pricing-mode taxonomy (§11a) added; commissions/slippage/collateral behavior reconciled site-wide. Every result now stamped with model version + pricing mode + quote-as-of timestamp.
- 2026-06-10 — Methodology page published. Added Wheel Score and Risk Score weights. Documented stale-while-revalidate caching layer.
- 2026-05-30 — Switched day-over-day change source from Yahoo to Stooq for Cloudflare egress compatibility.
12. Known limitations
Important things this site does NOT do:
- We do not currently model early-exercise risk for American options in the default pricing path (we use European Black-Scholes). Roadmap item: Bjerksund-Stensland approximation for CC + CSP outputs.
- We do not Monte Carlo-simulate return distributions in the covered-call and CSP calculators. Multi-leg tools (Sweeper, Wheel Monte Carlo, CSP Optimizer) do use 5,000-path Monte Carlo — see the probability-method matrix in §9a.
- Commission, slippage, and collateral-interest treatment: see §11a for the exact pricing mode per surface. Ranking surfaces (opportunities, screeners) use gross midpoint by design; calculators surface the net-of-costs modes on demand.
- We do not have access to historical IV — IV-rank computations are approximated from current IV only.
- The Polygon Options Starter plan provides 15-minute delayed quotes during market hours; live trading decisions should always be verified against your broker's order book.
12a. Strategy simulator (formerly “Backtester”) — full assumption disclosure
The tool at /backtest/ was renamed on 2026-07-16 from “Options Backtesting Engine” to “Options strategy simulator (synthetic pricing, single-path)” to reflect what it actually does. Every design choice below is a source of divergence from real trading results.
| Assumption | Current behavior | Impact on results |
|---|---|---|
| Volatility source | Trailing 20-day close-to-close realized volatility, annualized (&sqrt;252), computed from Polygon daily bars | Under-prices premiums during vol-risk-premium windows (Q1 2020, 2022); over-prices premiums when RV spikes then IV mean-reverts |
| Volatility floor / cap | Floor: 5% annualized. Cap: 200% annualized. Values outside are clipped to prevent extreme BSM outputs | Real event-driven IV can exceed 200% for meme names; clipping means the simulator can't reproduce that regime |
| Dividend treatment | Continuous dividend yield q = trailing 12-month cash dividend ÷ current spot. Discrete ex-div drops are not modeled | Slightly under-prices short calls right before ex-div and over-prices otherwise; early assignment on ITM calls before ex-div is not triggered |
| Risk-free rate source | Same live rate as the rest of the site (/api/rfr, U.S. Treasury 3-month CMT, auto-refreshed daily) | None — matches other tools |
| Strike selection | Nearest strike to target delta computed on the closing bar of the trade-entry date, from a synthetic strike grid of $0.50 increments below $50 and $1 increments above | Real strike grids are wider on high-price names and can be asymmetric; simulator may pick a strike that didn’t exist historically |
| Assignment rules | European exercise: assignment happens if and only if the underlying closes ITM at expiration. Early exercise is not modeled | Real short calls with meaningful dividend can be exercised early; simulator misses those exits |
| Roll timing | At contract expiration, or when the short leg breaches a user-specified delta trigger, whichever comes first. Rolls happen at the closing bar | Real rolls happen intraday; using the close biases toward the day’s printed low IV or high IV depending on tape direction |
| Commissions | $0 by default; user-configurable. Recommended setting: $0.65/contract per side (typical broker rate 2026) | Under-states real cost by $1.30/contract-round-trip if left at $0 |
| Bid/ask slippage | Default fill = midpoint. User can apply a haircut fraction (e.g., 20% of spread → 60% of mid). Partial fills are not modeled | Real fills on illiquid contracts sit worse than mid-minus-haircut; simulator overstates income captured on wide spreads |
| Corporate actions | Splits are handled by Polygon’s adjusted-close series. Special dividends, spin-offs, mergers are not handled | Positions in names like MSTR, ABBV pre-spinoff, or takeover targets will produce nonsense results in the impacted window |
| Delisted stocks / survivorship bias | Simulator only runs on tickers that currently have data. Delisted names (BBBY, EXPR, SDC, DIDI etc.) return no data. Historical results therefore have survivorship bias | Over-states long-run returns; catastrophic zero-outcomes are excluded from the sample |
| Daily highs/lows | Uses close-only data. Intraday highs/lows are not consulted | Intraday stops and intra-bar assignment triggers cannot fire; strategies that rely on intraday behavior are un-modelable here |
| Intraday stop triggers | Not modeled. Stops evaluate against the daily close only | A -50% stop on a gap-down day fires on the close (potentially far below the trigger), understating stop effectiveness |
| Result reproducibility | Every run’s parameter set is encoded in the URL query string. Same URL + same Polygon dataset = same result. Cached results include runId and dataAsOf stamp. | Cited results can be verified by re-running the shared URL |
Recommended interpretation: use the simulator to compare relative outcomes across parameter choices (0.20 vs 0.30 delta, 30 vs 45 DTE, roll-at-21 vs hold-to-expiration) on the same historical window. Do not interpret an absolute CAGR figure as what you would have earned trading the strategy live — the systematic biases above (survivorship, IV-vs-RV, no early exercise, no slippage) mean the true realized figure would be lower.
13. Questions or corrections
If you spot a formula error or want to suggest a methodology improvement: [email protected]. Substantive corrections are credited in the changelog.