Covered Straddle Wheel Strategy
The 2-lot wheel: an at-the-money put; if assigned, a covered straddle (or strangle); if assigned again, covered calls on both lots. 10% below the cost basis, repair; 30% below, review.
Default settings, 2020 to Nov 4, 2026.
By stock and year
Select a row to chart it below. Shade is the gap to buying and holding that stock.Unhedged: nothing protects against a crash beyond what the strategy itself holds.
| Stock | 2020 | 2021 | 2022 | 2023 | 2024 | 2025 | 2026 YTD | CAGR | B&H CAGR | Max drawdown |
|---|---|---|---|---|---|---|---|---|---|---|
| +5.8%−9.4 | +5.6%−21.3 | +6.7%+26.2 | +8.7%−15.6 | +9.4%−13.9 | +3.8%−12.5 | +9.0%−3.5 | +7.1% | +13.4% | −10.8% | |
| +5.7%−39.4 | +6.6%−20.2 | −4.1%+28.9 | +13.4%−40.4 | +8.2%−16.7 | +4.0%−16.2 | +7.9%−4.8 | +6.0% | +18.5% | −9.1% | |
| +12.4%−6.1 | +17.0%+3.8 | −1.4%+20.2 | +10.8%−4.3 | +10.6%+0.5 | −1.3%−12.7 | +9.4%−5.9 | +8.2% | +8.2% | −16.5% | |
| +2.9%−114.9 | +5.2%−120.0 | −1.5%+48.8 | +13.2%−225.8 | +23.0%−148.2 | +35.4%−3.4 | +5.8%+5.7 | +11.6% | +65.2% | −9.6% | |
| +11.9%−64.8 | +12.2%−21.6 | −5.2%+21.7 | +21.3%−26.9 | +12.9%−17.2 | +3.6%−5.0 | +11.6%−17.0 | +9.7% | +25.2% | −13.5% | |
| +6.6%−8.6 | +4.6%−22.3 | +6.9%+26.3 | +8.2%−16.0 | +9.2%−14.1 | +4.3%−12.0 | +8.1%−4.4 | +7.0% | +13.4% | −8.1% | |
| +6.0%−39.2 | +6.1%−20.7 | −3.8%+29.2 | +13.3%−40.5 | +8.3%−16.5 | +4.1%−16.1 | +7.2%−5.4 | +5.9% | +18.5% | −9.1% | |
| +15.5%−3.0 | +15.5%+2.3 | −0.7%+20.9 | +10.9%−4.3 | +10.5%+0.4 | −0.7%−12.2 | +8.7%−6.7 | +8.5% | +8.2% | −13.5% | |
| +4.5%−113.2 | +4.2%−121.0 | −1.4%+48.9 | +11.3%−227.7 | +32.7%−138.5 | +33.4%−5.4 | +6.1%+6.0 | +12.5% | +65.2% | −9.6% | |
| +16.0%−60.7 | +10.8%−23.0 | −4.7%+22.2 | +20.4%−27.7 | +12.4%−17.7 | +4.0%−4.5 | +10.8%−17.8 | +9.9% | +25.2% | −12.9% |
Growth of $10,000
Weekly closes on a log scale; the red shade is the strategy's drawdown from its last high. Returns are on the capital the strategy needed at its peak, with interest on idle cash. Hypothetical: how the backtests are made.
Rules · the when / do flow, read-only until you make a copyShow flow
The strategy runs as 5 tracks side by side. Each day, the first rule whose conditions hold in each track acts.
1. Stock repair
10%+ below the cost basis: a repair with no locked loss, managed to expiry
- R6Near expiry and not near the short strike: close the repair
When:
leg.rlong.open == 1andleg.rshort.open == 1andmin(leg.rlong.dte, leg.rshort.dte) <= manage_dteandspot < leg.rshort.strikeand(leg.rshort.strike - spot) / leg.rshort.strike > near_bandDo: close rshort; close rlong - R6Repair working: hold (above the short strike the shares are called)
When:
leg.rlong.open == 1andleg.rshort.open == 1Do: nothing (hold) - R2b1 lot, 10%+ below the basis: buy 1 ATM call, sell 2 ITM calls, no locked loss
When:
shares == 100andleg.put.open == 0 and leg.call.open == 0 and leg.rlong.open == 0 and leg.rshort.open == 0andadj_basis > 0 and (adj_basis - spot) / adj_basis > straddle_bandandnot (adj_basis > 0 and (adj_basis - spot) / adj_basis > review_band and repair_deep == 0)Do: buy call (as rlong)qty shares / 100strike spotdte dte (45)dte_min dte_min (30)dte_max dte_max (60)avoid_earnings 1strict 1; sell call (as rshort)qty 2 * shares / 100expiry leg.rlong.expirystrike_max min(leg.rlong.strike, spot) - 0.001scan strike_descscan_max 6require 2 * new.strike - leg.rlong.strike - adj_basis + 2 * new.mark - leg.rlong.mark >= 0 - R4c2 lots, 10%+ below the basis: the same repair per lot
When:
shares >= 200andleg.put.open == 0 and leg.call.open == 0 and leg.rlong.open == 0 and leg.rshort.open == 0andadj_basis > 0 and (adj_basis - spot) / adj_basis > straddle_bandandnot (adj_basis > 0 and (adj_basis - spot) / adj_basis > review_band and repair_deep == 0)Do: buy call (as rlong)qty shares / 100strike spotdte dte (45)dte_min dte_min (30)dte_max dte_max (60)avoid_earnings 1strict 1; sell call (as rshort)qty 2 * shares / 100expiry leg.rlong.expirystrike_max min(leg.rlong.strike, spot) - 0.001scan strike_descscan_max 6require 2 * new.strike - leg.rlong.strike - adj_basis + 2 * new.mark - leg.rlong.mark >= 0
2. Short put
No shares: sell an at-the-money put, take half, roll or take the shares
- R0Sell 1 at-the-money put (IV Rank 30+)
When:
shares < 100andleg.put.open == 0 and leg.call.open == 0 and leg.rlong.open == 0 and leg.rshort.open == 0andiv_rank >= ivr_minDo: sell put (as put)strike spotdte dte (45)dte_min dte_min (30)dte_max dte_max (60)avoid_earnings 1reserve lots_max * 100 * new.strike - R1aHalf the credit kept, but the put is nearly worthless: let it expire
When:
shares < 100andleg.put.open == 1andleg.call.open == 0andleg.put.pnl_pct >= take_pctandleg.put.open == 1 and leg.put.mark < 0.05 and leg.put.dte <= 5Do: nothing (hold) - R1aHalf the credit kept: buy the put back
When:
shares < 100andleg.put.open == 1andleg.call.open == 0andleg.put.pnl_pct >= take_pctDo: close put - R1bJust in the money near expiry: roll out for a credit, or take the shares
When:
shares < 100andleg.put.open == 1andleg.call.open == 0andleg.put.dte <= manage_dteandspot < leg.put.strikeand(leg.put.strike - spot) / leg.put.strike <= near_bandDo: roll putdte dte (45)dte_min dte_min (30)dte_max dte_max (60)avoid_earnings 1strike_max leg.put.strikescan strike_descrequire new.mark > leg.put.mark
3. Covered straddle
1 lot: a call on it and a put for lot 2, at the same strike (or strangle_delta each), call at or above the basis
- R2aSell the covered straddle
When:
shares == 100andleg.put.open == 0 and leg.call.open == 0 and leg.rlong.open == 0 and leg.rshort.open == 0andnot (adj_basis > 0 and (adj_basis - spot) / adj_basis > straddle_band)andiv_rank >= ivr_minandstrangle == 0Do: sell call (as call)strike spotstrike_min adj_basisdte dte (45)dte_min dte_min (30)dte_max dte_max (60)avoid_earnings 1strict 1; sell put (as put)strike leg.call.strikeexpiry leg.call.expirystrict 1reserve (lots_max - 1) * 100 * new.strike - R2aOr a covered strangle: call and put at strangle_delta
When:
shares == 100andleg.put.open == 0 and leg.call.open == 0 and leg.rlong.open == 0 and leg.rshort.open == 0andnot (adj_basis > 0 and (adj_basis - spot) / adj_basis > straddle_band)andiv_rank >= ivr_minandstrangle == 1Do: sell call (as call)delta strangle_delta (0.3)strike_min adj_basisdte dte (45)dte_min dte_min (30)dte_max dte_max (60)avoid_earnings 1strict 1; sell put (as put)delta strangle_delta (0.3)expiry leg.call.expirystrict 1reserve (lots_max - 1) * 100 * new.strike - R3aHalf the credit kept, both legs nearly worthless: let them expire
When:
shares == 100andleg.put.open == 1andleg.rlong.open == 0and(leg.put.cost + if(leg.call.open == 1, leg.call.cost, 0) - leg.put.mark - if(leg.call.open == 1, leg.call.mark, 0)) / (leg.put.cost + if(leg.call.open == 1, leg.call.cost, 0)) * 100 >= take_pctandleg.put.open == 1 and leg.put.mark < 0.05 and leg.put.dte <= 5andleg.call.open == 0 or (leg.call.open == 1 and leg.call.mark < 0.05 and leg.call.dte <= 5)Do: nothing (hold) - R3aHalf the credit kept: buy back the put; the worthless call expires
When:
shares == 100andleg.put.open == 1andleg.rlong.open == 0and(leg.put.cost + if(leg.call.open == 1, leg.call.cost, 0) - leg.put.mark - if(leg.call.open == 1, leg.call.mark, 0)) / (leg.put.cost + if(leg.call.open == 1, leg.call.cost, 0)) * 100 >= take_pctandleg.call.open == 1 and leg.call.mark < 0.05 and leg.call.dte <= 5Do: close put - R3aHalf the credit kept: buy back the call; the worthless put expires
When:
shares == 100andleg.put.open == 1andleg.rlong.open == 0and(leg.put.cost + if(leg.call.open == 1, leg.call.cost, 0) - leg.put.mark - if(leg.call.open == 1, leg.call.mark, 0)) / (leg.put.cost + if(leg.call.open == 1, leg.call.cost, 0)) * 100 >= take_pctandleg.put.open == 1 and leg.put.mark < 0.05 and leg.put.dte <= 5Do: close call - R3aHalf the credit kept: close both legs
When:
shares == 100andleg.put.open == 1andleg.rlong.open == 0and(leg.put.cost + if(leg.call.open == 1, leg.call.cost, 0) - leg.put.mark - if(leg.call.open == 1, leg.call.mark, 0)) / (leg.put.cost + if(leg.call.open == 1, leg.call.cost, 0)) * 100 >= take_pctDo: close put; close call - R3bCall just in the money: roll both legs up and out for a credit
When:
shares == 100andleg.put.open == 1andleg.rlong.open == 0andleg.call.open == 1andspot > leg.call.strikeandleg.call.dte <= manage_dteand(spot - leg.call.strike) / leg.call.strike <= near_bandDo: roll calldte dte (45)dte_min dte_min (30)dte_max dte_max (60)avoid_earnings 1strike_min max(leg.call.strike, adj_basis)scan strike_descrequire mid_put(new.strike, new.expiry) > 0 and new.mark + mid_put(new.strike, new.expiry) > leg.call.mark + leg.put.markstrike_max_near spot; roll putstrike leg.call.strikeexpiry leg.call.expiry - R3bShares will be called; the put is worthless: let it expire
When:
shares == 100andleg.put.open == 1andleg.rlong.open == 0andleg.call.open == 1andspot > leg.call.strikeandleg.call.dte <= manage_dteandleg.put.open == 1 and leg.put.mark < 0.05 and leg.put.dte <= 5Do: nothing (hold) - R3bLet the shares be called; buy back the put
When:
shares == 100andleg.put.open == 1andleg.rlong.open == 0andleg.call.open == 1andspot > leg.call.strikeandleg.call.dte <= manage_dteDo: close put - R3cPut just in the money: roll it out for a credit, or take lot 2
When:
shares == 100andleg.put.open == 1andleg.rlong.open == 0andspot < leg.put.strikeandleg.put.dte <= manage_dteand(leg.put.strike - spot) / leg.put.strike <= near_bandDo: roll putdte dte (45)dte_min dte_min (30)dte_max dte_max (60)avoid_earnings 1strike_max leg.put.strikescan strike_descrequire new.mark > leg.put.mark
4. Covered calls
Calls at or above the basis on the shares held; also when no repair avoids a loss
- R2b1 lot, no repair avoids a loss: covered calls at or above the basis
When:
shares == 100andleg.put.open == 0 and leg.call.open == 0 and leg.rlong.open == 0 and leg.rshort.open == 0andadj_basis > 0 and (adj_basis - spot) / adj_basis > straddle_bandandnot (adj_basis > 0 and (adj_basis - spot) / adj_basis > review_band and repair_deep == 0)andiv_rank >= ivr_minDo: sell call (as call)qty shares / 100strike spotstrike_min adj_basisdte dte (45)dte_min dte_min (30)dte_max dte_max (60)avoid_earnings 1 - R4dMore than 30% below the basis: thesis review, no new options
When:
shares >= 200andleg.put.open == 0 and leg.call.open == 0 and leg.rlong.open == 0 and leg.rshort.open == 0andadj_basis > 0 and (adj_basis - spot) / adj_basis > review_band and repair_deep == 0Do: nothing (hold) - R4c2 lots, no repair avoids a loss: covered calls at or above the basis
When:
shares >= 200andleg.put.open == 0 and leg.call.open == 0 and leg.rlong.open == 0 and leg.rshort.open == 0andadj_basis > 0 and (adj_basis - spot) / adj_basis > straddle_bandandiv_rank >= ivr_minDo: sell call (as call)qty shares / 100strike spotstrike_min adj_basisdte dte (45)dte_min dte_min (30)dte_max dte_max (60)avoid_earnings 1 - R43–10% below the basis: wait
When:
shares >= 200andleg.put.open == 0 and leg.call.open == 0 and leg.rlong.open == 0 and leg.rshort.open == 0andadj_basis > 0 and (adj_basis - spot) / adj_basis > near_bandDo: nothing (hold) - R4bSell covered calls on both lots, at or above the basis
When:
shares >= 200andleg.put.open == 0 and leg.call.open == 0 and leg.rlong.open == 0 and leg.rshort.open == 0andiv_rank >= ivr_minDo: sell call (as call)qty shares / 100strike spotstrike_min adj_basisdte dte (45)dte_min dte_min (30)dte_max dte_max (60)avoid_earnings 1 - R5aHalf the credit kept, but the call is nearly worthless: let it expire
When:
shares >= 100andleg.call.open == 1andleg.put.open == 0andleg.rlong.open == 0andleg.call.pnl_pct >= take_pctandleg.call.open == 1 and leg.call.mark < 0.05 and leg.call.dte <= 5Do: nothing (hold) - R5aHalf the credit kept: buy the calls back
When:
shares >= 100andleg.call.open == 1andleg.put.open == 0andleg.rlong.open == 0andleg.call.pnl_pct >= take_pctDo: close call - R5bJust in the money near expiry: roll up and out for a credit, or let the shares go
When:
shares >= 100andleg.call.open == 1andleg.put.open == 0andleg.rlong.open == 0andleg.call.dte <= manage_dteandspot > leg.call.strikeand(spot - leg.call.strike) / leg.call.strike <= near_bandDo: roll calldte dte (45)dte_min dte_min (30)dte_max dte_max (60)avoid_earnings 1strike_min max(leg.call.strike, adj_basis)scan strike_descrequire new.mark > leg.call.markstrike_max max(leg.call.strike, spot * 1.02)
5. Crash put
A cheap far put under whatever is held; hedge_delta 0 turns it off
- CP1Something is held and no crash put: buy one
When:
hedge_delta > 0andleg.hedge.open == 0andshares >= 100 or book.open > 0Do: buy put (as hedge)qty max(1, round(shares / 100))delta hedge_delta (0)dte hedge_dte (90)dte_min hedge_replace_dte + 7strike_max spot - 0.001strict 1 - CP2Nothing left to protect: sell it
When:
leg.hedge.open == 1andshares < 100andbook.open <= 1Do: close hedge - CP3Down to its last month, or worth hedge_take_x its cost: sell it and buy a fresh one
When:
leg.hedge.open == 1andleg.hedge.dte <= hedge_replace_dte or (hedge_take_x > 0 and leg.hedge.mark >= leg.hedge.cost * hedge_take_x)Do: close hedge; buy put (as hedge)qty max(1, round(shares / 100))delta hedge_delta (0)dte hedge_dte (90)dte_min hedge_replace_dte + 7strike_max spot - 0.001strict 1
Parameters and defaults
Every named number in the rules can be changed in the Rule Builder. These are the defaults the backtest used.
| Parameter | Default |
|---|---|
lots_max | 2 |
strangle | 0 |
strangle_delta | 0.3 |
dte | 45 |
dte_min | 30 |
dte_max | 60 |
ivr_min | 30 |
take_pct | 50 |
manage_dte | 21 |
near_band | 0.03 |
straddle_band | 0.1 |
review_band | 0.3 |
repair_deep | 0 |
hedge_delta | 0 |
hedge_dte | 90 |
hedge_replace_dte | 30 |
hedge_take_x | 3 |
Make a copy to change any of it and run your own backtest.
Trade log · every open, roll, assignment and close for SPYShow trades
Loading…
The 2-lot wheel: an at-the-money put; if assigned, a covered straddle (or strangle); if assigned again, covered calls on both lots. 10% below the cost basis, repair; 30% below, review.
What it is
A two-lot wheel. Sell an at-the-money put when IV Rank is 30 or more; if assigned, sell a covered straddle (or strangle) against the shares; if assigned again, sell covered calls on both lots. If the shares fall 10% below the cost basis, use a stock-repair spread; at 30% below, stop and review.
When to use it
Use it on a stock you are happy to own two lots of, when implied volatility is high enough to pay well for at-the-money options.
Maximum risk
This is a high-delta income strategy: with two lots it can carry up to 200 shares of downside, less premium. The repair spreads reduce the break-even but do not remove the loss in a large fall.
Questions
What is a covered straddle?
Owning shares while selling both a call and a put at the same strike. The call is covered by the shares; the put may add a second lot if assigned.
What does the stock repair do?
Buy one at-the-money call and sell two higher calls for little or no cost. It lowers the price the shares must recover to before the position breaks even.
When does it stop selling options?
At 30% below the cost basis the strategy stops opening new options and waits, since that much damage needs a fresh look at the stock itself.