← All backtests

Covered Straddle Wheel Strategy

IncomeThetaRules built-in

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.

Stock2020202120222023202420252026 YTDCAGRB&H CAGRMax 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%
Gap to buy and hold: behind ahead · option prices before Feb 2024 are modelled, real quotes after

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 copy
Show 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

  1. R6Near expiry and not near the short strike: close the repair When: leg.rlong.open == 1 and leg.rshort.open == 1 and min(leg.rlong.dte, leg.rshort.dte) <= manage_dte and spot < leg.rshort.strike and (leg.rshort.strike - spot) / leg.rshort.strike > near_bandDo: close rshort; close rlong
  2. R6Repair working: hold (above the short strike the shares are called) When: leg.rlong.open == 1 and leg.rshort.open == 1Do: nothing (hold)
  3. R2b1 lot, 10%+ below the basis: buy 1 ATM call, sell 2 ITM calls, no locked loss When: shares == 100 and leg.put.open == 0 and leg.call.open == 0 and leg.rlong.open == 0 and leg.rshort.open == 0 and adj_basis > 0 and (adj_basis - spot) / adj_basis > straddle_band and not (adj_basis > 0 and (adj_basis - spot) / adj_basis > review_band and repair_deep == 0)Do: buy call (as rlong) qty shares / 100 strike spot dte dte (45) dte_min dte_min (30) dte_max dte_max (60) avoid_earnings 1 strict 1; sell call (as rshort) qty 2 * shares / 100 expiry leg.rlong.expiry strike_max min(leg.rlong.strike, spot) - 0.001 scan strike_desc scan_max 6 require 2 * new.strike - leg.rlong.strike - adj_basis + 2 * new.mark - leg.rlong.mark >= 0
  4. R4c2 lots, 10%+ below the basis: the same repair per lot When: shares >= 200 and leg.put.open == 0 and leg.call.open == 0 and leg.rlong.open == 0 and leg.rshort.open == 0 and adj_basis > 0 and (adj_basis - spot) / adj_basis > straddle_band and not (adj_basis > 0 and (adj_basis - spot) / adj_basis > review_band and repair_deep == 0)Do: buy call (as rlong) qty shares / 100 strike spot dte dte (45) dte_min dte_min (30) dte_max dte_max (60) avoid_earnings 1 strict 1; sell call (as rshort) qty 2 * shares / 100 expiry leg.rlong.expiry strike_max min(leg.rlong.strike, spot) - 0.001 scan strike_desc scan_max 6 require 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

  1. R0Sell 1 at-the-money put (IV Rank 30+) When: shares < 100 and leg.put.open == 0 and leg.call.open == 0 and leg.rlong.open == 0 and leg.rshort.open == 0 and iv_rank >= ivr_minDo: sell put (as put) strike spot dte dte (45) dte_min dte_min (30) dte_max dte_max (60) avoid_earnings 1 reserve lots_max * 100 * new.strike
  2. R1aHalf the credit kept, but the put is nearly worthless: let it expire When: shares < 100 and leg.put.open == 1 and leg.call.open == 0 and leg.put.pnl_pct >= take_pct and leg.put.open == 1 and leg.put.mark < 0.05 and leg.put.dte <= 5Do: nothing (hold)
  3. R1aHalf the credit kept: buy the put back When: shares < 100 and leg.put.open == 1 and leg.call.open == 0 and leg.put.pnl_pct >= take_pctDo: close put
  4. R1bJust in the money near expiry: roll out for a credit, or take the shares When: shares < 100 and leg.put.open == 1 and leg.call.open == 0 and leg.put.dte <= manage_dte and spot < leg.put.strike and (leg.put.strike - spot) / leg.put.strike <= near_bandDo: roll put dte dte (45) dte_min dte_min (30) dte_max dte_max (60) avoid_earnings 1 strike_max leg.put.strike scan strike_desc require 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

  1. R2aSell the covered straddle When: shares == 100 and leg.put.open == 0 and leg.call.open == 0 and leg.rlong.open == 0 and leg.rshort.open == 0 and not (adj_basis > 0 and (adj_basis - spot) / adj_basis > straddle_band) and iv_rank >= ivr_min and strangle == 0Do: sell call (as call) strike spot strike_min adj_basis dte dte (45) dte_min dte_min (30) dte_max dte_max (60) avoid_earnings 1 strict 1; sell put (as put) strike leg.call.strike expiry leg.call.expiry strict 1 reserve (lots_max - 1) * 100 * new.strike
  2. R2aOr a covered strangle: call and put at strangle_delta When: shares == 100 and leg.put.open == 0 and leg.call.open == 0 and leg.rlong.open == 0 and leg.rshort.open == 0 and not (adj_basis > 0 and (adj_basis - spot) / adj_basis > straddle_band) and iv_rank >= ivr_min and strangle == 1Do: sell call (as call) delta strangle_delta (0.3) strike_min adj_basis dte dte (45) dte_min dte_min (30) dte_max dte_max (60) avoid_earnings 1 strict 1; sell put (as put) delta strangle_delta (0.3) expiry leg.call.expiry strict 1 reserve (lots_max - 1) * 100 * new.strike
  3. R3aHalf the credit kept, both legs nearly worthless: let them expire When: shares == 100 and leg.put.open == 1 and leg.rlong.open == 0 and (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_pct and leg.put.open == 1 and leg.put.mark < 0.05 and leg.put.dte <= 5 and leg.call.open == 0 or (leg.call.open == 1 and leg.call.mark < 0.05 and leg.call.dte <= 5)Do: nothing (hold)
  4. R3aHalf the credit kept: buy back the put; the worthless call expires When: shares == 100 and leg.put.open == 1 and leg.rlong.open == 0 and (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_pct and leg.call.open == 1 and leg.call.mark < 0.05 and leg.call.dte <= 5Do: close put
  5. R3aHalf the credit kept: buy back the call; the worthless put expires When: shares == 100 and leg.put.open == 1 and leg.rlong.open == 0 and (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_pct and leg.put.open == 1 and leg.put.mark < 0.05 and leg.put.dte <= 5Do: close call
  6. R3aHalf the credit kept: close both legs When: shares == 100 and leg.put.open == 1 and leg.rlong.open == 0 and (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
  7. R3bCall just in the money: roll both legs up and out for a credit When: shares == 100 and leg.put.open == 1 and leg.rlong.open == 0 and leg.call.open == 1 and spot > leg.call.strike and leg.call.dte <= manage_dte and (spot - leg.call.strike) / leg.call.strike <= near_bandDo: roll call dte dte (45) dte_min dte_min (30) dte_max dte_max (60) avoid_earnings 1 strike_min max(leg.call.strike, adj_basis) scan strike_desc require mid_put(new.strike, new.expiry) > 0 and new.mark + mid_put(new.strike, new.expiry) > leg.call.mark + leg.put.mark strike_max_near spot; roll put strike leg.call.strike expiry leg.call.expiry
  8. R3bShares will be called; the put is worthless: let it expire When: shares == 100 and leg.put.open == 1 and leg.rlong.open == 0 and leg.call.open == 1 and spot > leg.call.strike and leg.call.dte <= manage_dte and leg.put.open == 1 and leg.put.mark < 0.05 and leg.put.dte <= 5Do: nothing (hold)
  9. R3bLet the shares be called; buy back the put When: shares == 100 and leg.put.open == 1 and leg.rlong.open == 0 and leg.call.open == 1 and spot > leg.call.strike and leg.call.dte <= manage_dteDo: close put
  10. R3cPut just in the money: roll it out for a credit, or take lot 2 When: shares == 100 and leg.put.open == 1 and leg.rlong.open == 0 and spot < leg.put.strike and leg.put.dte <= manage_dte and (leg.put.strike - spot) / leg.put.strike <= near_bandDo: roll put dte dte (45) dte_min dte_min (30) dte_max dte_max (60) avoid_earnings 1 strike_max leg.put.strike scan strike_desc require 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

  1. R2b1 lot, no repair avoids a loss: covered calls at or above the basis When: shares == 100 and leg.put.open == 0 and leg.call.open == 0 and leg.rlong.open == 0 and leg.rshort.open == 0 and adj_basis > 0 and (adj_basis - spot) / adj_basis > straddle_band and not (adj_basis > 0 and (adj_basis - spot) / adj_basis > review_band and repair_deep == 0) and iv_rank >= ivr_minDo: sell call (as call) qty shares / 100 strike spot strike_min adj_basis dte dte (45) dte_min dte_min (30) dte_max dte_max (60) avoid_earnings 1
  2. R4dMore than 30% below the basis: thesis review, no new options When: shares >= 200 and leg.put.open == 0 and leg.call.open == 0 and leg.rlong.open == 0 and leg.rshort.open == 0 and adj_basis > 0 and (adj_basis - spot) / adj_basis > review_band and repair_deep == 0Do: nothing (hold)
  3. R4c2 lots, no repair avoids a loss: covered calls at or above the basis When: shares >= 200 and leg.put.open == 0 and leg.call.open == 0 and leg.rlong.open == 0 and leg.rshort.open == 0 and adj_basis > 0 and (adj_basis - spot) / adj_basis > straddle_band and iv_rank >= ivr_minDo: sell call (as call) qty shares / 100 strike spot strike_min adj_basis dte dte (45) dte_min dte_min (30) dte_max dte_max (60) avoid_earnings 1
  4. R43–10% below the basis: wait When: shares >= 200 and leg.put.open == 0 and leg.call.open == 0 and leg.rlong.open == 0 and leg.rshort.open == 0 and adj_basis > 0 and (adj_basis - spot) / adj_basis > near_bandDo: nothing (hold)
  5. R4bSell covered calls on both lots, at or above the basis When: shares >= 200 and leg.put.open == 0 and leg.call.open == 0 and leg.rlong.open == 0 and leg.rshort.open == 0 and iv_rank >= ivr_minDo: sell call (as call) qty shares / 100 strike spot strike_min adj_basis dte dte (45) dte_min dte_min (30) dte_max dte_max (60) avoid_earnings 1
  6. R5aHalf the credit kept, but the call is nearly worthless: let it expire When: shares >= 100 and leg.call.open == 1 and leg.put.open == 0 and leg.rlong.open == 0 and leg.call.pnl_pct >= take_pct and leg.call.open == 1 and leg.call.mark < 0.05 and leg.call.dte <= 5Do: nothing (hold)
  7. R5aHalf the credit kept: buy the calls back When: shares >= 100 and leg.call.open == 1 and leg.put.open == 0 and leg.rlong.open == 0 and leg.call.pnl_pct >= take_pctDo: close call
  8. R5bJust in the money near expiry: roll up and out for a credit, or let the shares go When: shares >= 100 and leg.call.open == 1 and leg.put.open == 0 and leg.rlong.open == 0 and leg.call.dte <= manage_dte and spot > leg.call.strike and (spot - leg.call.strike) / leg.call.strike <= near_bandDo: roll call dte dte (45) dte_min dte_min (30) dte_max dte_max (60) avoid_earnings 1 strike_min max(leg.call.strike, adj_basis) scan strike_desc require new.mark > leg.call.mark strike_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

  1. CP1Something is held and no crash put: buy one When: hedge_delta > 0 and leg.hedge.open == 0 and shares >= 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 + 7 strike_max spot - 0.001 strict 1
  2. CP2Nothing left to protect: sell it When: leg.hedge.open == 1 and shares < 100 and book.open <= 1Do: close hedge
  3. CP3Down to its last month, or worth hedge_take_x its cost: sell it and buy a fresh one When: leg.hedge.open == 1 and leg.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 + 7 strike_max spot - 0.001 strict 1

Parameters and defaults

Every named number in the rules can be changed in the Rule Builder. These are the defaults the backtest used.

ParameterDefault
lots_max2
strangle0
strangle_delta0.3
dte45
dte_min30
dte_max60
ivr_min30
take_pct50
manage_dte21
near_band0.03
straddle_band0.1
review_band0.3
repair_deep0
hedge_delta0
hedge_dte90
hedge_replace_dte30
hedge_take_x3

Make a copy to change any of it and run your own backtest.

Trade log · every open, roll, assignment and close for SPY
Show trades

Loading…

income

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.

Related strategies