Backtest: Weekly Short Straddle¶

Open In Colab

A simplified, cost-free backtest of a weekly ATM short straddle.

Part 14 of 20 in the ServLoci algo/options trading notebook series — full index in notebooks/README.md.

Setup¶

# Get your dedicated static IPv6 + SOCKS5 credentials free:
#   https://comm.servloci.in/register        (or /auth/google?free=1 for an instant trial)
# Your api_key / api_secret pair shows up in the portal after signup:
#   https://comm.servloci.in/user
!pip install -q "requests[socks]"
!curl -sL https://comm.servloci.in/sdk/servloci.py -o servloci.py

import os
from servloci import ServLoci

SERVLOCI_API_KEY = os.environ.get("SERVLOCI_API_KEY", "dhan:1000000001")   # broker:client_id
SERVLOCI_API_SECRET = os.environ.get("SERVLOCI_API_SECRET", "")            # from the portal — leave blank to run this notebook in demo mode

sl = None
if SERVLOCI_API_SECRET:
    sl = ServLoci(api_key=SERVLOCI_API_KEY, api_secret=SERVLOCI_API_SECRET)
    print("ServLoci configured:", sl.host, sl.port)
else:
    print("SERVLOCI_API_SECRET not set — running in demo mode (no live proxy calls).")

Illustrative only — no slippage, costs, or margin. Sells a weekly ATM straddle every Monday, priced with 20-day realized vol via Black-Scholes (notebook 06), held to Friday's close.

from scipy.stats import norm
import math

def bs_price(opt_type, spot, strike, t_years, vol, rate=0.065):
    if t_years <= 0 or vol <= 0:
        return max(spot - strike, 0) if opt_type == "CE" else max(strike - spot, 0)
    d1 = (math.log(spot / strike) + (rate + vol * vol / 2) * t_years) / (vol * math.sqrt(t_years))
    d2 = d1 - vol * math.sqrt(t_years)
    if opt_type == "CE":
        return spot * norm.cdf(d1) - strike * math.exp(-rate * t_years) * norm.cdf(d2)
    return strike * math.exp(-rate * t_years) * norm.cdf(-d2) - spot * norm.cdf(-d1)

import pandas as pd
import math

nifty = pd.read_csv("nifty_2y.csv", index_col=0, parse_dates=True)
nifty["ret"] = nifty["Close"].pct_change()
nifty["realized_vol"] = nifty["ret"].rolling(20).std() * math.sqrt(252)

trades = []
mondays = nifty[nifty.index.weekday == 0].dropna(subset=["realized_vol"])
for entry_date, row in mondays.iterrows():
    exit_idx = nifty.index.searchsorted(entry_date) + 4  # ~Friday, 5 trading days later
    if exit_idx >= len(nifty):
        continue
    spot_in, spot_out = row["Close"], nifty["Close"].iloc[exit_idx]
    vol, t_years = row["realized_vol"], 5 / 252
    strike = round(spot_in / 50) * 50
    call_prem = bs_price("CE", spot_in, strike, t_years, vol)
    put_prem = bs_price("PE", spot_in, strike, t_years, vol)
    payoff = -(max(spot_out - strike, 0) - call_prem) - (max(strike - spot_out, 0) - put_prem)
    trades.append({"entry": entry_date, "spot_in": spot_in, "spot_out": spot_out, "pnl_per_lot": payoff})

results = pd.DataFrame(trades)
print(results.tail())
print("Total simulated P&L (1 lot, no costs):", round(results["pnl_per_lot"].sum(), 2))

« Previous: Historical Data for Backtesting
Next: Position Sizing & Risk Management »

Try the concepts above interactively: Options Strategy Builder · Docs · Get your static IP