Overall Statistics
Total Orders
0
Average Win
0%
Average Loss
0%
Compounding Annual Return
0%
Drawdown
0%
Expectancy
0
Start Equity
100000
End Equity
100000
Net Profit
0%
Sharpe Ratio
0
Sortino Ratio
0
Probabilistic Sharpe Ratio
0%
Loss Rate
0%
Win Rate
0%
Profit-Loss Ratio
0
Alpha
0
Beta
0
Annual Standard Deviation
0
Annual Variance
0
Information Ratio
0
Tracking Error
0
Treynor Ratio
0
Total Fees
$0.00
Estimated Strategy Capacity
$0
Lowest Capacity Asset
Portfolio Turnover
0%
from AlgorithmImports import *
import pandas as pd
import numpy as np

class MSFTOptionAnalysis(QCAlgorithm):
    def Initialize(self):
        self.SetStartDate(2024, 1, 18)  # Start date
        self.SetEndDate(2024, 1, 19)    # End date - data is collected only for the target date, avoiding unnecessary computation
        self.SetCash(100000)            # Starting cash
        
        self.equity = self.AddEquity("MSFT", Resolution.Hour)
        option = self.AddOption("MSFT", Resolution.Hour)
        option.SetFilter(lambda u: u.Strikes(-50, 50).Expiration(0, 90))    # Broad filter only for retrieving purposes
        
        self.target_date = datetime(2024, 1, 18, 15, 0, 0)  # Target date, time, and expiry
        self.expiry_date = datetime(2024, 3, 15)
        
        # Our strikes
        self.call_strike = 360
        self.put_strike = 320
        self.otc_put_strike = 328.5
        
        self.option_data = {}
        self.data_processed = False  #preventing OnData from running multiple times and flooding with logs
    
    def OnData(self, slice):
        #process data at Jan 18, 2024 at 3:00 PM
        if not self.ShouldProcessData():
            return
        
        chain = self.GetOptionChain(slice)
        if not chain:
            return
        
        self.StoreOptionPrices(chain)
        self.InterpolateOTCImpliedVol(chain)
        self.PrintResults()
        
        self.data_processed = True
    
    def ShouldProcessData(self):
        return (
            self.Time.date() == self.target_date.date() and
            self.Time.hour == self.target_date.hour and
            not self.data_processed
        )
    
    def GetOptionChain(self, slice):
        if slice.OptionChains.Count == 0:
            return None
        chains = list(slice.OptionChains.Values)
        return chains[0] if chains else None
    
    def StoreOptionPrices(self, chain):
        underlying_price = chain.Underlying.Price
        self.Debug(f"Underlying Price (MSFT): ${underlying_price}")

        #store all call options expiring on our target date
        put_contracts = [c for c in chain if c.Right == OptionRight.Put and c.Expiry.date() == self.expiry_date.date()]
        call_contracts = [c for c in chain if c.Right == OptionRight.Call and c.Expiry.date() == self.expiry_date.date()]
        
        self.StoreOptionData(call_contracts, self.call_strike, 'call_360')
        self.StoreOptionData(put_contracts, self.put_strike, 'put_320')
    
    def StoreOptionData(self, contracts, strike, key):
        option = next((c for c in contracts if c.Strike == strike), None)
        if option:
            price = (option.BidPrice + option.AskPrice) / 2
            self.option_data[key] = {
                'price': price,
                'strike': option.Strike,
                'expiry': option.Expiry,
                'days_to_expiry': (self.expiry_date - self.Time).days,
                'implied_vol': option.ImpliedVolatility  # Use contract's implied volatility
            }
    
    def InterpolateOTCImpliedVol(self, chain):

        ## Calls are not used because IV behavior can be diff between calls & Puts due to skew/smile effects

        put_contracts = [c for c in chain if c.Right == OptionRight.Put and c.Expiry.date() == self.expiry_date.date()]
        put_data = [{'strike': p.Strike, 'implied_vol': p.ImpliedVolatility} for p in put_contracts]
        
        if put_data:
            df = pd.DataFrame(put_data).sort_values('strike').set_index('strike')
            if len(df) >= 2 and min(df.index) < self.otc_put_strike < max(df.index):
                self.option_data['otc_put'] = {
                    'strike': self.otc_put_strike,
                    'implied_vol': np.interp(self.otc_put_strike, df.index, df['implied_vol'])
                }
    
    def PrintResults(self):
        self.Debug("Analysis Results:")
        for key in ['call_360', 'put_320']:
            if key in self.option_data:
                data = self.option_data[key]
                self.Debug(f"MSFT {key.split('_')[0].capitalize()} Option (Strike: {data['strike']}, Expiry: {data['expiry'].strftime('%Y-%m-%d')}):")
                self.Debug(f"Price: ${data['price']:.2f}")
                self.Debug(f"Implied Volatility: {data['implied_vol']:.4f}")
        
        if 'otc_put' in self.option_data:
            otc_data = self.option_data['otc_put']
            self.Debug(f"Interpolated IV for OTC Put Option (Strike: {otc_data['strike']}, Expiry: {self.expiry_date.strftime('%Y-%m-%d')}):")
            self.Debug(f"{otc_data['implied_vol']:.4f}")