Description
Selling a cash-secured put is one of the cleanest income strategies in options trading, but it has its own risks, and it must be done right. In this post, we break down the three pillars that make it work: proper cash backing, quality stock selection, and a neutral-to-bullish market outlook. With a concrete trade example and Python code to scan for candidates.
Introduction
Most options traders discover cash-secured puts the same way: they hear someone say “it’s like getting paid to buy a stock you already like” and immediately start selling puts on whatever has the fattest premium. Then it seems to work: I collect premiums, I can make money… until the first assignment arrives, the stock tanks, and the money evaporates. That’s the wrong way to do it.
The right way starts with discipline and patience. Specifically, three must-have criteria that separate a systematic income strategy from a discretionary choice:
- Full cash backing: You hold enough capital to actually buy the shares if assigned.
- Quality stocks: You’d genuinely want to own the underlying if the option is assigned to you.
- Neutral to bullish outlook: You’re not selling into a collapsing stock.
Get all three right, and the cash-secured put becomes one of the most elegant tools in the options toolkit. Get one wrong, and you’re just selling naked downside risk with extra steps.
Let’s build this from the ground up.
What Is a Cash-Secured Put?
A cash-secured put means you sell a put option and simultaneously set aside enough cash to purchase the underlying shares at the strike price if you get assigned.
The mechanics are simple:
- You sell a put option at strike price K.
- You collect the premium upfront.
- You reserve K Γ 100 dollars in your account (one contract = 100 shares).
- At expiration, one of two things happens:
- Stock stays above K: The put expires worthless. You keep the premium. Done.
- Stock falls below K: You get assigned, and you are obliged to buy 100 shares at K. Your effective cost basis is K minus the premium received.
This is the core beauty of the strategy: even in the “bad” scenario (assignment), you acquire shares at a discount relative to where you already wanted to buy them.
But only if you choose the right stock. More on that in a moment.
Criteria Nr. 1. Cash Backing: The Non-Negotiable Rule
Let’s be clear about what “cash-secured” actually means. If you sell a put with a $150 strike, you must have $15,000 in cash set aside per contract. Not $7,500. Not margin. Cash.
This is not just a risk management suggestion; it’s the philosophical contract you make with yourself when selling this strategy. The entire premise is: “I am willing and able to own 100 shares of this company at $150.” If you don’t have the cash, you’re not selling a cash-secured put. You’re selling a naked put and calling it something else. In the case of naked selling, some other criteria need to be taken into account. We will discuss it in the future.
The simple rule: before you place the trade, confirm you have the cash sitting there, unencumbered, ready to go. This also defines your position sizing for your portfolio. This constraint is actually a gift, since it naturally limits your position size and prevents over-concentration. You can’t accidentally put 80% of your portfolio into one name just because you got excited.
A clean way to think about it: never allocate more than 10β15% of total capital to a single cash-secured put position. If you have $100,000 in your account, your maximum exposure per position is $10,000β$15,000, which means strikes around $100β$150 for 1 contract.
def max_strike_for_position(total_capital, max_pct=0.12, num_contracts=1):
"""
Given your account size, return the max strike price per contract
to stay within position sizing limits.
"""
max_allocation = total_capital * max_pct
max_strike = max_allocation / (100 * num_contracts)
return max_strike
total_capital = 100_000
max_strike = max_strike_for_position(total_capital)
print(f"π Max strike to keep position under 12% allocation: ${max_strike:.2f}")
# Output: Max strike to keep position under 12% allocation: $120.00Criteria Nr. 2. Quality Stocks: You Must Want to Own It
This is where most people get it wrong. They screen for the highest premium and end up selling puts on volatile, speculative names where the premium is high for a reason β because the downside is real and severe.
The right question is not “which put pays the most?” but “which stock would I happily own at a discount?”
What Makes a Quality Candidate?
Here’s my simple, systematic framework:
Fundamental filters:
- Market cap > $10B β Large-cap stability, less risk of catastrophic collapse
- Positive earnings (EPS > 0) β The business is actually profitable
- Reasonable P/E ratio (< 40) β You’re not paying bubble valuations
- Low debt-to-equity (< 2.0) β Balance sheet strength matters if things get rough
Technical filters:
- Price above 50-day and 200-day moving average β Trending stocks make better put-selling candidates than falling knives
- Implied Volatility (IV) elevated relative to historical β You want to sell premium when it’s expensive
import yfinance as yf
import pandas as pd
def screen_csp_candidates(tickers):
"""
Screen a list of tickers for cash-secured put candidates.
Returns a DataFrame with key quality metrics.
"""
results = []
for ticker in tickers:
try:
stock = yf.Ticker(ticker)
info = stock.info
market_cap = info.get("marketCap", 0)
eps = info.get("trailingEps", None)
pe_ratio = info.get("trailingPE", None)
debt_equity = info.get("debtToEquity", None)
current_price = info.get("currentPrice", None)
# Get moving averages from price history
hist = stock.history(period="1y")
if hist.empty or current_price is None:
continue
ma50 = hist["Close"].tail(50).mean()
ma200 = hist["Close"].tail(200).mean()
above_ma50 = current_price > ma50
above_ma200 = current_price > ma200
# Basic quality score (out of 5)
score = 0
if market_cap and market_cap > 10e9: score += 1
if eps and eps > 0: score += 1
if pe_ratio and pe_ratio < 40: score += 1
if debt_equity and debt_equity < 200: score += 1 # yfinance reports as %
if above_ma50 and above_ma200: score += 1
results.append({
"Ticker": ticker,
"Price": round(current_price, 2),
"Market Cap ($B)": round(market_cap / 1e9, 1) if market_cap else None,
"EPS": round(eps, 2) if eps else None,
"P/E": round(pe_ratio, 1) if pe_ratio else None,
"Debt/Equity": round(debt_equity, 1) if debt_equity else None,
"Above MA50": above_ma50,
"Above MA200": above_ma200,
"Quality Score": f"{score}/5",
})
except Exception as e:
print(f"β οΈ Error fetching {ticker}: {e}")
df = pd.DataFrame(results)
df = df.sort_values("Quality Score", ascending=False)
return df
# Example: screen a watchlist of S&P 500 blue-chips
watchlist = ["AAPL", "MSFT", "JNJ", "KO", "V", "NVDA", "META", "HD", "UNH", "PG"]
df_candidates = screen_csp_candidates(watchlist)
print(df_candidates.to_string(index=False))A stock scoring 4/5 or 5/5 is a strong candidate. Anything below 3/5 β move on, no matter how attractive the premium looks.
The Assignment Test
Before pulling the trigger on any trade, ask yourself this one question:
“If I get assigned 100 shares of this company tomorrow at this strike price, would I be okay with that?”
If the honest answer is “I’d be stressed, I’d immediately want to exit, I don’t believe in this business” β then don’t sell the put. The strategy only works when assignment is an acceptable, even welcome, outcome.
Criteria Nr. 3. Neutral to Bullish Outlook
Cash-secured puts are a premium-selling strategy that benefits from:
- Time decay (Theta): Every day that passes, the put loses value
- Stable or rising price: The stock staying above your strike
- Volatility contraction: If IV drops after you sell, the put’s value drops too
What kills the trade:
- A stock in a confirmed downtrend (lower highs, lower lows)
- Bearish macro backdrop with no clear floor
- Upcoming binary events β earnings, FDA decisions β that could gap the stock down 20%
The ideal setup: a stock that has pulled back to a support level in an otherwise healthy uptrend, with elevated IV making the premium especially attractive. You’re not catching a falling knife. You’re collecting income at a level where you’d be happy to own the stock anyway.
Concrete Example
Let’s walk through a real trade setup step by step.
The setup:
- Apple (AAPL) is trading at $220
- It has pulled back ~8% from recent highs
- The trend is still intact (above the 200-day MA)
- Implied volatility is elevated β you see a decent premium available
Your analysis: You’re neutral to bullish on AAPL. You’d love to own it at $210 or below. You have $50,000 in your trading account.
Step 1: Check position sizing
def cash_required(strike, contracts):
return strike * contracts * 100
total_capital = 50_000
strike = 210.0
contracts = 1
premium = 2.50 # per share, so $250 total
cash_reserved = cash_required(strike, contracts)
pct_of_capital = cash_reserved / total_capital * 100
print(f"π΅ Cash reserved: ${cash_reserved:,.0f}")
print(f"π % of capital used: {pct_of_capital:.1f}%")
# Output:
# π΅ Cash reserved: $21,000
# π % of capital used: 42.0%Hmm. Maybe 42% of capital in a single position; that’s too high.
What should I do?
Reduce to 1 contract but accept that this is a large name, or wait for a smaller position size (scale to a $150-$160 strike on a different stock). Alternatively, use a Bull Put Spread if you want to trade AAPL with less capital at risk. Read here for more details.
For the purposes of this example, let’s say you have $100,000; then this is a clean 21% allocation. Still a bit heavy, but defensible for a blue-chip like AAPL if you’re comfortable with the name.
Step 2: Define the trade
# Trade definition
ticker = "AAPL"
current_price = 220.00
strike = 210.00 # OTM put, ~4.5% below current price
expiration = "30 DTE" # ~30 days to expiration
premium_rcvd = 2.50 # per share
# Key metrics
max_profit = premium_rcvd * 100 # $250 per contract
breakeven = strike - premium_rcvd # $207.50
max_loss = (strike - premium_rcvd) * 100 # $20,750 (if stock goes to zero)
annualized_ret = (premium_rcvd / strike) * (365 / 30) * 100 # rough annualization
print(f"π Ticker: {ticker}")
print(f"π² Current Price: ${current_price:.2f}")
print(f"π― Strike: ${strike:.2f}")
print(f"π° Premium Received: ${premium_rcvd:.2f} per share (${max_profit:.0f} total)")
print(f"π Breakeven: ${breakeven:.2f}")
print(f"π
Expiration: {expiration}")
print(f"π Annualized Yield: {annualized_ret:.1f}%")Output:
π Ticker: AAPL
π² Current Price: $220.00
π― Strike: $210.00
π° Premium Received: $2.50 per share ($250 total)
π Breakeven: $207.50
π
Expiration: 30 DTE
π Annualized Yield: 14.5%Step 3: Scenario analysis at expiration
def csp_pnl(stock_price_at_expiry, strike, premium, contracts=1):
"""
Calculate P&L of a cash-secured put at expiration.
"""
if stock_price_at_expiry >= strike:
# Put expires worthless β keep full premium
pnl = premium * 100 * contracts
status = "β
Expired worthless β keep premium"
else:
# Assigned β buy shares at strike, offset by premium
pnl = (stock_price_at_expiry - strike + premium) * 100 * contracts
status = f"π¦ Assigned β effective cost basis: ${strike - premium:.2f}"
return pnl, status
# Run scenarios
scenarios = [230, 220, 210, 207.50, 200, 190]
print(f"{'Price at Expiry':>18} | {'P&L':>10} | Status")
print("-" * 65)
for price in scenarios:
pnl, status = csp_pnl(price, strike=210, premium=2.50)
print(f"${price:>17.2f} | ${pnl:>9.0f} | {status}")Output:
Price at Expiry | P&L | Status
-----------------------------------------------------------------
$230.00 | $250 | β
Expired worthless β keep premium
$220.00 | $250 | β
Expired worthless β keep premium
$210.00 | $250 | β
Expired worthless β keep premium
$207.50 | $ 0| β
Expired worthless β keep premium
$200.00 | -$750 | π¦ Assigned β effective cost basis: $207.50
$190.00 | -$1,750| π¦ Assigned β effective cost basis: $207.50The key insight: even at $200 (a -9% move from entry), you’re buying AAPL at a $207.50 effective cost basis, a level you said you’d be happy owning at. That’s not a disaster. That’s the strategy working exactly as designed.
What to Do After Assignment
If you do get assigned, you now own 100 shares at an effective cost of $207.50. The trade doesn’t stop there β this is where the Wheel Strategy kicks in:
- You own 100 shares of AAPL at $207.50
- You sell a covered call at, say, $215 β collecting more premium
- Either the stock gets called away at $215 (you profit), or the covered call expires, and you collect the premium again
- Rinse and repeat
The cash-secured put is naturally the first leg of the Wheel, and knowing this changes your mindset about assignment entirely. It’s not a failure mode β it’s the next opportunity.
The Internal Debate Before Every Trade
Here’s the conversation that should happen in your head before every cash-secured put:
Greedy Brain: “This biotech has $8 of premium on the $50 strike! That’s a 16% return in 30 days!”
Risk Management Brain: “It’s a biotech. Binary event. Phase 3 trial next week. The stock could go from $60 to $20 overnight. Do you want to own 100 shares at $50 in that scenario?”
Systematic Brain: “Quality score: 1/5. No earnings. Volatile. Not a CSP candidate. Move on.”
The discipline is in the filtering. The boring, systematic filtering that happens before you ever look at premium levels.
Final Thoughts
The cash-secured put is not a get-rich-quick scheme. It’s a systematic income strategy that rewards patience, discipline, and genuinely good stock selection.
The three pillars always apply:
- Full cash backing β if you can’t afford to own it, don’t sell the put
- Quality stocks β premium is meaningless if the underlying collapses
- Neutral to bullish outlook β time decay only helps you if the stock cooperates
When all three align, you have a high-probability income trade with a built-in plan for every scenario. The stock stays flat? You keep the premium. It rallies? You keep the premium. It dips below your strike? You own a quality business at a discount β and you were already prepared for it.
This is the mindset behind The Quantitative Edge β simple ideas, implemented cleanly, that scale into powerful tools for data-driven trading.
Statemi bene!
