Leaps Python
|

Python Risk Management Series (Part 2): Renting Stocks and Hallucinating Arbitrage

In Part 1, we talked about how to stop paying for insurance. In Part 2, we are going to talk about how to stop paying for stocks.

The trouble with buying stocks is that they are capital inefficient. If you want to buy 100 shares of Dell, you have to find roughly $13,500. If you are a “rational economic actor”—a phrase economists use to describe people who don’t exist—you would prefer to get the exact same exposure to Dell for, say, $4,000, and keep the other $9,000 in T-Bills yielding 4.5%.

The financial industry has a solution for this. It is called the Stock Replacement Strategy, or if you are on Reddit, the Poor Man’s Covered Call (PMCC).

The Strategy: The “Poor Man’s” Hedge Fund

Technically known as a Long Call Diagonal Debit Spread, the PMCC has two moving parts:

  1. The Anchor (Long Leg): You buy a Deep-In-The-Money LEAPS Call (e.g., 0.80 Delta, 1+ Year out). This acts as your “stock.”
  2. The Income (Short Leg): You sell a Near-Term Out-Of-The-Money Call (e.g., 0.30 Delta, 30 Days out) against it.

“Don’t you sell the call to finance the LEAP?” Yes, but partially. Unlike the “Zero-Cost Collar” in Part 1 where the trade was free, a PMCC is a Debit transaction so it cost you money.

  • The LEAPS might cost $10.00.
  • You sell the Near-Term call for $1.00.
  • Net Cost: $9.00.

You are not getting the position for free. You are getting a “discount” on your entry. The goal is to keep selling that $1.00 call every single month until the LEAPS is paid for.

The Python Focus: The code below focuses strictly on Leg 1 (The LEAPS). Why? Because finding a short call is easy—you just look 30 days out. But finding a LEAPS that is liquid, fairly valued, and offers good leverage? That is where the edge is. This script hunts for the “Anchor” of the trade.

The Code: Hunting for Value (and Bugs)

The Python script below scans the market for these “rental agreements.” It looks for:

  1. Deep ITM Calls: Delta between 0.75 and 0.85.
  2. Long Duration: Expiration > 1 year.
  3. “Fair Value”: It runs a Black-Scholes calculation to see if the option is “cheap” relative to its implied volatility.

We ran the code, and the output (see the screenshot) tells a tale of two markets.

LEAPS with Python

1. The Value Play: Crypto Miners (CIFR)

Look at the top rows. The scanner found CIFR (Cipher Mining).

  • Strike: $22.00
  • Expiry: Dec 2027 (years away)
  • Diff%: -15.2%

The Trade: The model thinks the “Fair Value” of this option is $9.85, but the market is selling it for $8.35. The code is telling you that this leverage is on sale. You can control this volatile crypto-mining stock for 15% less than the theoretical model suggests you should pay. The Lev (Leverage) column is roughly 1.72, meaning for every $1 you put in, you get $1.72 of exposure to the stock.

2. The Hallucination: DELL

Now, look at the very last row. This is why we write Python scripts instead of just trusting them.

  • Ticker: DELL
  • Mark: $45.82
  • Diff%: 56,881.1%

The computer has flagged this as the greatest trading opportunity in the history of capitalism. It claims the Fair Value is $0.08, but the price is $45.82.

What happened? The “Mark” is correct—Dell stock is trading around $135, so a $90 strike call should be worth about $45 (Intrinsic Value). But the “Fair Value” calculation hallucinated. Why? Look at the columns Bid and Ask. They are None.

The data feed for this specific option was illiquid or broken at the moment of the snapshot. Because the script couldn’t find a bid/ask, it likely fed garbage into the Black-Scholes model, which spit out a Fair Value of 8 cents.

The Catch: There Is No Free Lunch

It is important to pause here. If “renting” stocks is cheaper than buying them, why buy them at all?

There are specific risks to the PMCC that the Python script cannot code away:

  1. The “Capped Upside” (The FOMO Risk): If you implement the full PMCC and sell a short call against your LEAPS, and the stock “moons” (goes up 50% in a week), you lose. Your LEAPS gains value, but your Short Call loses value just as fast. You are capped. You watch the rocket ship take off while you are tethered to the ground.
  2. The “Gap” (Locked Loss): This is the nightmare scenario. If the stock crashes, your LEAPS loses value. If the stock rips, you are capped. But if the stock stays flat? You still have to beat the “debit” you paid. Unlike owning stock, where “flat” means you just lost time, with a PMCC, “flat” means your LEAPS is slowly rotting away due to time decay (Theta), while your short call might not be decaying fast enough to cover it.
  3. The Dividend Problem: When you own Dell stock, and Michael Dell decides to pay a dividend, a check shows up in your account. When you own a Dell LEAPS, you get nothing. The market prices this in, but you lose that compounding cash flow.
  4. Early Assignment: If your short call goes Deep ITM, the counterparty might exercise it early. You don’t own the shares. You have to scramble to exercise your LEAPS (burning any remaining time value) to cover the position. It is a logistical headache that simple stock owners never face.

The Takeaway

This is the most important lesson in algorithmic trading: Data is hard, and leverage is sharp. The script successfully found a legit deep-value play in CIFR (buying cheap volatility). But it also found a “ghost” in DELL.

If you blindly sorted by “Diff%” and auto-traded, you would be in trouble. But as a tool to filter 5,000 stocks down to 10 candidates? It’s perfect. It highlights the cheap leverage, and it highlights the broken data. It is your job to know the difference.

IMPORTANT: Disclaimer

Nothing in this article constitutes professional and/or financial advice, nor does any information on this site constitute a comprehensive or complete statement of the matters discussed or the law relating thereto. PythonForFinanceHub is not a fiduciary by virtue of any person’s use of or access to the Site or Content. You alone assume the sole responsibility of evaluating the merits and risks associated with the use of any information or other Content on the Site before making any decisions based on such information or other Content.

Put simply: This is code, not a crystal ball. Options trading involves significant risk and is not suitable for every investor. You can lose 100% of your capital. The code provided is for educational purposes only and may contain bugs (as seen above). Do your own research or hire a professional who actually knows what they are doing.


from polygon import RESTClient
from datetime import date, timedelta, datetime
import pandas as pd
import numpy as np
import math
import yfinance as yf
from google.colab import userdata

# --- CONFIGURATION ---
API_KEY = userdata.get("massive")  # your Polygon key in Colab secrets

# Now supports multiple tickers
TICKERS = ["DELL", "CIFR", "EOSE"]  # <-- edit list here

TARGET_DELTA = 0.80
DELTA_BUFFER = 0.05              # keep calls in [0.75, 0.85]
MAX_SPREAD_PCT = 0.10            # max (ask-bid)/mid
MIN_T = 1.0                      # LEAPS = >= 1 year
RISK_FREE_RATE = 0.045           # 4.5%
MIN_THEO = 0.05                  # avoid divide-by-near-zero in Diff%

def black_scholes_call(S, K, T, r, sigma, q=0.0):
    """
    European call price with continuous dividend yield q.
    S spot, K strike, T years, r risk-free, sigma annualized IV, q dividend yield.
    """
    if T <= 0 or sigma <= 0 or S <= 0 or K <= 0:
        return np.nan

    d1 = (np.log(S / K) + (r - q + 0.5 * sigma ** 2) * T) / (sigma * np.sqrt(T))
    d2 = d1 - sigma * np.sqrt(T)

    def N(x):
        return 0.5 * (1 + math.erf(x / np.sqrt(2)))

    return (S * np.exp(-q * T) * N(d1)) - (K * np.exp(-r * T) * N(d2))

def get_underlying_inputs(ticker):
    """
    Get spot and dividend yield from yfinance.
    Dividend yield is optional; defaults to 0 if missing.
    """
    print(f"Fetching underlying data for {ticker} from yfinance...")
    stock = yf.Ticker(ticker)

    hist_1d = stock.history(period="1d")
    if hist_1d.empty:
        raise ValueError("yfinance returned no spot data.")

    spot = float(hist_1d["Close"].iloc[-1])

    try:
        info = stock.info or {}
    except Exception:
        info = {}

    div_yield = info.get("dividendYield", 0.0) or 0.0
    return spot, float(div_yield)

def get_mark_price(contract):
    """
    Robust mark (mid) price.
    Uses bid/ask if available; otherwise last trade; otherwise day close.
    Returns (mark, bid, ask).
    """
    bid = getattr(contract.last_quote, "bid", None)
    ask = getattr(contract.last_quote, "ask", None)

    bid = float(bid) if bid not in (None, 0) else None
    ask = float(ask) if ask not in (None, 0) else None

    if bid is not None and ask is not None and ask >= bid:
        mark = 0.5 * (bid + ask)
        return mark, bid, ask

    last = getattr(contract.last_trade, "price", None)
    last = float(last) if last not in (None, 0) else None
    if last is not None:
        return last, bid, ask

    close = getattr(contract.day, "close", None)
    close = float(close) if close not in (None, 0) else None
    if close is not None:
        return close, bid, ask

    return None, bid, ask

def find_value_leaps_iv_one_ticker(client, ticker):
    """
    Runs your LEAPS cheapness scan for a single ticker.
    Returns a DataFrame (possibly empty).
    """
    # 1) Underlying inputs
    spot, div_yield = get_underlying_inputs(ticker)
    print(f"Spot Price: ${spot:.2f}")
    print(f"Dividend Yield (yfinance): {div_yield*100:.2f}%")
    print(f"Risk Free Rate: {RISK_FREE_RATE*100:.2f}%")

    # 2) Pull LEAPS calls chain snapshot from Polygon
    min_expiration = date.today() + timedelta(days=int(MIN_T * 365))
    print("Fetching Options Chain from Polygon...")

    try:
        chain_iter = client.list_snapshot_options_chain(
            ticker,
            params={
                "expiration_date.gte": min_expiration.isoformat(),
                "contract_type": "call",
            },
        )
    except Exception as e:
        print(f"Polygon Error for {ticker}: {e}")
        return pd.DataFrame()

    candidates = []
    print("Filtering and calculating IV-based Fair Value...")

    for c in chain_iter:
        if not c.greeks or not c.day:
            continue

        delta = getattr(c.greeks, "delta", None)
        if delta is None:
            continue

        if not ((TARGET_DELTA - DELTA_BUFFER) <= delta <= (TARGET_DELTA + DELTA_BUFFER)):
            continue

        mark, bid, ask = get_mark_price(c)
        if mark is None or mark <= 0:
            continue

        # Spread filter if we have bid+ask
        if bid is not None and ask is not None and ask > 0:
            spread_pct = (ask - bid) / mark
            if spread_pct > MAX_SPREAD_PCT:
                continue
        else:
            spread_pct = np.nan

        strike = float(c.details.strike_price)
        expiry_str = c.details.expiration_date
        expiry = datetime.strptime(expiry_str, "%Y-%m-%d").date()
        T = (expiry - date.today()).days / 365.0
        if T <= 0:
            continue

        iv = getattr(c, "implied_volatility", None)
        if iv is None or iv <= 0:
            continue
        iv = float(iv)

        theo = black_scholes_call(spot, strike, T, RISK_FREE_RATE, iv, q=div_yield)
        if theo is None or np.isnan(theo) or theo < MIN_THEO:
            continue

        diff_pct = (mark - theo) / theo
        leverage = (delta * spot) / mark

        candidates.append({
            "Ticker": ticker,
            "Expiry": expiry_str,
            "Strike": strike,
            "Delta": round(delta, 2),
            "Bid": bid,
            "Ask": ask,
            "Mark": round(mark, 2),
            "Spread%": round(spread_pct * 100, 1) if not np.isnan(spread_pct) else None,
            "IV%": round(iv * 100, 1),
            "Fair Val (IV BS)": round(theo, 2),
            "Diff%": round(diff_pct * 100, 1),
            "Lev": round(leverage, 2),
        })

    if not candidates:
        return pd.DataFrame()

    return pd.DataFrame(candidates).sort_values("Diff%")

def find_value_leaps_iv_multi(tickers):
    """
    Multi-ticker wrapper.
    Prints per-ticker tables and also returns a combined DataFrame.
    """
    client = RESTClient(API_KEY)

    all_dfs = []
    for t in tickers:
        print(f"\n==============================")
        print(f" RUNNING LEAPS SCAN FOR {t}")
        print(f"==============================")

        df_t = find_value_leaps_iv_one_ticker(client, t)
        if df_t.empty:
            print(f"No candidates found for {t}.")
            continue

        print(f"\n--- LEAPS IV-BASED ANALYSIS FOR {t} ---")
        print("(Negative Diff% = below IV-based BS fair value; Positive = above)")
        print(df_t.to_string(index=False))

        all_dfs.append(df_t)

    if not all_dfs:
        print("\nNo candidates found for any ticker.")
        return pd.DataFrame()

    combined = pd.concat(all_dfs, ignore_index=True).sort_values(["Ticker", "Diff%"])
    return combined

if __name__ == "__main__":
    combined_df = find_value_leaps_iv_multi(TICKERS)

    # Optional: view combined cheapest across all names
    if not combined_df.empty:
        print("\n==========================================")
        print(" CHEAPEST LEAPS ACROSS ALL TICKERS (TOP 25)")
        print("==========================================")
        print(combined_df.sort_values("Diff%").head(25).to_string(index=False))

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.