Perpetual Option Roll on Weekly Put Credit Spreads
Algorithm logic by Alex Joseph
Perpetual Weekly Put Credit Spread (PCS) – Rolling Algorithm
A production‑ready spec for an algorithm that continuously sells and manages weekly put credit spreads, with rule‑based entries, exits, rolls, and risk controls.
1) Strategy Intent & Universe
Intent: harvest short‑dated put premium while keeping tail risk bounded via defined‑risk spreads and an always‑on roll engine.
Underlying universe (examples): SPY, QQQ, IWM, mega‑cap index ETFs, and the top‑N most liquid equity underlyings (weekly options, tight spreads, robust borrow/SSFs if hedging with futures is preferred). Allow a whitelist per account.
2) Core Parameters (tunable)
Entry DTE: 5–9 DTE (default 7).
Entry window: open Mon–Thu for next Fri expiration; avoid opening during last 90 minutes of RTH.
Short‑put target delta: regime‑adaptive 0.10–0.30.
Width: fixed ($1–$10) or ATR‑scaled (e.g.,
width = max($3, 0.6 * ATR20)
), capped by risk.Min credit:
min_credit = max(0.30 * width, $0.20)
; stricter in low IV.Max risk per spread:
width – credit
.Portfolio heat (max net risk at once):
<= 20%
of account (configurable).Max correlated underlyings: e.g., at most 2 concurrent index ETFs.
Event risk filters: block entries 24h before CPI, FOMC, jobs report; block single‑name entries inside earnings window
[t−2, t+1]
trading days.Volatility filters:
IV Rank (IVR): prefer
IVR >= 25
; ifIVR < 15
require min credit>= 40% * width
or skip.VIX filter (for index): if
VIX > 28
, reduce size 50% and shift to 0.10–0.15 delta.
Trend/Regime:
Bullish/Neutral =
MA20 > MA50
and/orRSI14 > 45
→ target short delta 0.20–0.25.Bearish/High vol = otherwise → target short delta 0.10–0.15 and smaller size.
3) Entry Logic (PCS opening)
Pre‑checks (all must pass):
Liquidity: bid/ask width
<= 5%
of mid, open interest on both legs>= 500
(or 10× your size), spread slippage model ok.Risk budget available (portfolio heat OK, correlation budget OK).
No blocked events in window; no hard news flagged.
Strike selection:
Choose expiration
E
with 5–9 DTE.Compute target short delta from regime (Section 2).
Pick short strike
Kshort
closest to target delta; long strikeKlong = Kshort − width
.Enforce distance:
Kshort <= Spot − 0.8 * ATR20
(farther OTM in high vol).Enforce credit: natural credit
>= min_credit
after slippage.
Position sizing:
Contracts
n = floor( min( risk_budget_remaining, heat_cap_remaining ) / (width − credit) )
, bounded by per‑underlying max.If
n == 0
, skip.
Send limit order at mid − slippage (0.5–1.0
ticks). Use smart retry (time‑slice, join the bid when necessary, hard cancel before news).
4) Lifecycle Management
Profit taking
TP1: Close at 50–60% of max profit (e.g., when spread price decays to 40–50% of entry credit) OR when
DTE <= 3
andunrealized_pnl >= 40%
.TP2 (time‑based): If
DTE == 1
and spread is profitable, close at MOC; do not hold through expiry unless (a) wide cushionSpot >= Kshort + 0.8*ATR5
, (b) borrow/assignment handling confirmed, and (c) risk engine green.
Risk exits (untested → tested)
Trigger any of the following and escalate to roll/exit decision:
Spot breaches short strike (
Spot < Kshort
) OR short leg delta>= 0.35
.Spread mark
>= 2.0 * entry_credit
OR stop‑loss at 1.5× credit in low vol.IV spike: underlying IV (or VIX) jumps
>= +30%
versus entry.News shock detected (underlying drops >1.5× ATR in a session).
Emergency stop
If gap risk or structural break (halt, downgrade, macro shock) → flatten all PCS in underlying; suspend entries for N sessions.
5) Roll Engine (Perpetual Behavior)
Objective: keep risk defined and theta allocated while limiting variance drag.
Roll windows:
Early roll (premium harvest): when
DTE <= 3
and position is untested (Spot > Kshort) and price<= 25%
of entry credit, close and re‑open new PCS with 5–9 DTE.Defensive roll (tested): when Spot
<= Kshort
andDTE >= 3
:Out-in-time, same strikes (O→O+7): if credit
>= 30% * width
when rolling calendar‑style, take it.Down‑and‑out: move strikes lower and out a week to keep same width, require net additional credit
>= 10–20%
of width.If unable to collect credit on roll and short delta
> 0.45
, close the spread (cap losses) and re‑enter smaller/wider per regime after cool‑down.
Pin risk roll (DTE 0–1): if Spot is within
±0.25 * width
of Kshort, close intraday (avoid assignment) or roll out a week for small credit only if liquidity conditions are excellent.
Do not roll if doing so increases max loss or provides < $0.05 credit and keeps you tested; prefer close and reset.
6) Optional Hedges
Tail put: buy a 1–2 week 5–10Δ long put for the aggregate book when VIX term structure inverts.
Futures micro‑hedge: if allowed, short /MES or /MNQ proportional to aggregate delta when portfolio PCS delta exceeds a threshold (e.g.,
−0.15 beta‑adj
).Gamma day hedge: on large down open (>1.25× ATR), temporarily pause new entries and consider buying 0DTE units as intraday hedge against early breach.
7) Decision Matrix (Entry/Exit/Roll)
7.1 Quick Matrix (tabular)
Fresh entry signal
5–9
Spot >= Kshort + 0.8*ATR20
IVR ≥ 25 (or min credit met)
n/a
Open PCS sized by risk
Untested, decayed
≤3
Above Kshort
Flat/down
≥50–75% max profit
Close & Re‑open next‑week PCS
Untested, slow decay
2–4
Above Kshort
Flat
<25% of credit
Early Roll Out if next week credit ≥ 30% width, else hold
Tested light
≥3
Spot ~ Kshort
Flat/Down
Loss ≤ 1× credit
Roll Out same strikes for credit; or Down‑and‑Out for ≥10–20% width credit
Tested heavy
≥3
Below Kshort
Up big
Loss > 1.5–2× credit or Δshort ≥ 0.45
Close; optional re‑entry smaller/wider after cooldown
Pin risk
0–1
`
Spot−Kshort
<= 0.25*width`
Any
Event window
Any
Any
Any
Any
No New Entries; manage/flatten risk
7.2 Flowchart (Mermaid)
flowchart TD
A[Daily Scheduler] --> B{Open Positions?}
B -- No --> C{Entry Filters Pass?\n(IVR, Trend, Events, Liquidity)}
C -- Yes --> D[Select 5–9 DTE; choose Δ; set width/credit; size]
D --> E[Send Limit Order]
C -- No --> Z[Skip Entries]
B -- Yes --> F{Position State}
F -->|Untested & DTE<=3 & PnL>=50–60%| G[Close & Reopen Next Week]
F -->|Untested & DTE<=4 & Decay<25%| H{Next‑week credit ≥30% width?}
H -- Yes --> I[Early Roll Out]
H -- No --> J[Hold]
F -->|Tested & DTE>=3 & Δshort<0.45| K{Roll for Net Credit?}
K -- Yes --> L[Roll Out Same/Lower Strikes]
K -- No --> M[Close Spread]
F -->|Tested & Δshort>=0.45 or Price>=1.5–2x credit| M
F -->|Pin Risk DTE<=1| N[Close or Micro‑roll 1w]
F -->|Event Risk/News Shock| O[Flatten & Suspend Entries]
subgraph Risk Controls
P[Portfolio Heat ≤ 20%]
Q[Per‑underlying limits]
R[Correlation budget]
end
E --> P
P --> Q --> R --> S[OK]
S --> T[Live]
8) Order Handling & Microstructure
Use limit orders at mid; cross one tick after N seconds; cancel/replace at thresholds.
Partial fills allowed up to 25% variation; otherwise cancel and resize.
Slippage model:
slip = max(1 tick, 0.15 * quoted spread)
; embed in all checks.
9) Risk & Compliance Guardrails
Hard daily loss stop for the strategy: e.g.,
−2%
of account or−0.75×
monthly vol estimate.Max days held: forced exit on DTE 0 unless wide cushion and assignment permitted by broker rules you’ve coded for.
Circuit breaker: pause new entries for the session after any emergency stop.
10) Pseudocode (Framework‑Agnostic)
for each trading_day:
update_data()
for u in universe:
regime = get_regime(u) # trend, IVR, VIX term, events
risk_ok = check_portfolio_heat() and correlation_ok(u)
if not has_open_pcs(u) and entry_window() and regime.entry_ok and risk_ok:
dte = pick_dte(5, 9)
delta = target_delta(regime) # 0.10–0.30
k_short = strike_by_delta(u, dte, delta)
width = choose_width(u, regime) # fixed or ATR‑scaled
k_long = k_short - width
credit = quote_credit(u, dte, k_short, k_long)
if credit >= min_credit(width, regime) and distance_ok(u, k_short):
size = position_size(credit, width, risk_state)
if size > 0:
send_limit_order(u, dte, k_short, k_long, size)
# Manage open spreads
for pos in open_spreads():
u, dte, k_short, k_long = pos.underlying, pos.dte, pos.k_short, pos.k_long
mark = pos.mark_price()
pnl = pos.unrealized_pnl()
delta_short = leg_delta(u, dte, k_short)
tested = spot(u) <= k_short
if pnl >= 0.5 * pos.max_profit or (dte <= 3 and pnl >= 0.4 * pos.max_profit):
close(pos); continue
if dte <= 1 and near_pin(spot(u), k_short, width):
close(pos); continue
if tested and dte >= 3:
# attempt to roll for net credit
roll = find_roll_candidate(pos, out_weeks=1, allow_down=True)
if roll.net_credit >= 0.1 * width:
execute_roll(pos, roll); continue
if delta_short >= 0.45 or mark >= 1.5 * pos.entry_credit:
close(pos); continue
if news_shock(u) or emergency_flag():
close(pos); suspend_entries(u)
11) Backtest/Live Monitoring KPIs
Win rate; avg win/avg loss; P/L per trade; P/L per calendar week.
Return on risk (ROR): per spread and aggregate.
Tail drawdowns (5% worst days), max DD, recovery time.
Exposure (contracts & risk dollars) vs VIX/IVR.
Percent of rolls vs closes, net credit from rolls, and post‑roll outcomes.
Assignment rate and cost.
12) Suggested Default Settings (SPY example)
Entry: Tue/Thu, 7–8 DTE.
Target Δ: 0.20 in normal regime; 0.12 if VIX>25.
Width: $5; Min credit: $1.50 (30% of width).
TP: 55% of max profit or DTE ≤ 3 with ≥ 40%.
Risk exits: price ≥ 1.8× credit, or Δshort ≥ 0.40.
Rolls: Out 1 week, keep width, seek ≥ $0.50 net credit when tested; otherwise close.
13) Implementation Notes
Schedule two loops: pre‑open (risk/events) and intraday (entries/management).
Log all decisions with state snapshots for auditability.
Parameterize everything; run walk‑forward optimizations on yearly slices.
Appendix A – Test Cases (What‑ifs)
Low IV drift week: few openings due to credit filter; early rolls harvest decay → small, steady gains.
Vol spike mid‑week: tested spreads trigger defensive roll; heat reduced; daily loss stop prevents cascade.
Pin close Friday: algorithm closes at 3pm ET; avoids assignment.
Event lockout (CPI): skips Tuesday entries; resumes after print.
Last updated
Was this helpful?