Overall Statistics
Total Trades
0
Average Win
0%
Average Loss
0%
Compounding Annual Return
0%
Drawdown
0%
Expectancy
0
Net Profit
0%
Sharpe 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.859
Tracking Error
0.207
Treynor Ratio
0
Total Fees
$0.00
Estimated Strategy Capacity
$0
Lowest Capacity Asset
Portfolio Turnover
0%
# region imports
from AlgorithmImports import *

from math import log, sqrt, pi, exp
from scipy.stats import norm
import scipy.stats as stats
# endregion

## ------------------------ Assignment 1 AAPL Contracts info
class FatYellowOwl(QCAlgorithm):

    def Initialize(self) -> None:
        self.SetStartDate(2022, 7, 14)
        self.SetEndDate(2022, 12, 1)
        self.SetCash(100000)
        self.SetWarmUp(20)
        
        # Universe settings
        self.UniverseSettings.Resolution = Resolution.Hour
        
        # Requesting data
        self.aapl = self.AddEquity("AAPL",Resolution.Hour).Symbol
        option = self.AddOption("AAPL",Resolution.Hour)
        self.option_symbol = option.Symbol
        
        # Volatility
        self.last_price = RollingWindow[float](2)
        self.std = StandardDeviation(25)

        # Risk Free rate
        self.yieldCurve = self.AddData(USTreasuryYieldCurveRate, "USTYCR", Resolution.Daily).Symbol
        self.last_rfr = RollingWindow[float](4)

        #Set Options Model: Equity Options are American Style
        # As such, need to use CrankNicolsonFD or other models.
        #BlackScholes Model is only for European Options
        option.PriceModel = OptionPriceModels.CrankNicolsonFD()

    ## ----------- Black Scholes ------------- ##
    # Price

    def bsm_price(self, option_type, sigma, s, k, r, T, q):
        # calculate the bsm price of European call and put options
        d1 = (np.log(s / k) + (r - q + sigma ** 2 * 0.5) * T) / (sigma * np.sqrt(T))
        d2 = d1 - sigma * np.sqrt(T)
        if option_type == 'c':
            return np.exp(-r*T) * (s * np.exp((r - q)*T) * stats.norm.cdf(d1) - k *  stats.norm.cdf(d2))
        if option_type == 'p':
            return np.exp(-r*T) * (k * stats.norm.cdf(-d2) - s * np.exp((r - q)*T) *  stats.norm.cdf(-d1))
        raise Exception(f'No such option type: {option_type}')

    def implied_vol(self, option_type, option_price, s, k, r, T, q):
        # apply bisection method to get the implied volatility by solving the BSM function
        precision = 0.00001
        upper_vol = 500.0
        max_vol = 500.0
        min_vol = 0.0001
        lower_vol = 0.0001
        iteration = 50

        while iteration > 0:
            iteration -= 1
            mid_vol = (upper_vol + lower_vol)/2
            price = self.bsm_price(option_type, mid_vol, s, k, r, T, q)

            if option_type == 'c':
                lower_price = self.bsm_price(option_type, lower_vol, s, k, r, T, q)
                if (lower_price - option_price) * (price - option_price) > 0:
                    lower_vol = mid_vol
                else:
                    upper_vol = mid_vol
                if mid_vol > max_vol - 5 :
                    return 0.000001

            if option_type == 'p':
                upper_price = self.bsm_price(option_type, upper_vol, s, k, r, T, q)
                if (upper_price - option_price) * (price - option_price) > 0:
                    upper_vol = mid_vol
                else:
                    lower_vol = mid_vol

            if abs(price - option_price) < precision:
                break

        return mid_vol

    def OnData(self, data: Slice) -> None:
        if data.get(self.yieldCurve):
            self.last_rfr.Add(data[self.yieldCurve].OneYear)
        if self.Time.hour == 10 and data.get(self.aapl): # Once per day
            self.last_price.Add(data[self.aapl].Price)
            if self.last_price.IsReady:
                self.std.Update(self.Time, (self.last_price[0]-self.last_price[1])/self.last_price[1])
        if self.IsWarmingUp: #Wait until warming up is done
            return
        
        if self.Time == datetime(2022,10,14,15):
            rfr = self.last_rfr[0]
            vol = self.std.Current.Value*sqrt(252)
            T = abs(self.Time - datetime(2022,11,18)).days/365
            # Compute the price for the call option
            price = self.bsm_price('c',vol,data[self.aapl].Price, 145, rfr, T, 0)
            # Get the Implied volatility based on computed price 
            implied_volatility = self.implied_vol('c', price, data[self.aapl].Price, 145,rfr, T, 0)
            self.Log(f'At{self.Time} a Call option on AAPL with strike price 145 and expiry of 2022-11-18 has a price {price} and implied volatility {implied_volatility}')
        elif self.Time == datetime(2022,10,17,10):
            rfr = self.last_rfr[0]
            vol = self.std.Current.Value*sqrt(252)
            T = abs(self.Time - datetime(2022,11,11)).days
            # Compute the price for the call option
            price = self.bsm_price('p',vol,data[self.aapl].Price, 145, rfr, T, 0)
            # Get the Implied volatility based on computed price 
            implied_volatility = self.implied_vol('p', price, data[self.aapl].Price, 145,rfr, T, 0)
            self.Log(f'At{self.Time} a Put option on AAPL with strike price 130 and expiry of 2022-11-11 has a price {price} and implied volatility {implied_volatility}')
        elif self.Time == datetime(2022,10,17,13):
            self.interpolate_contract(data)
        
    def interpolate_contract(self,data):
        computed_prices = [None]
        computed_impvol = [None]
        deltas_t = [datetime(2022,12,16)]
        rfr = self.last_rfr[0]
        vol = self.std.Current.Value*sqrt(252)
        # Because we are using +- this will be month approximatly
        for i in range(10):
            delta_t_minus = abs(self.Time - (datetime(2022,12,16)-timedelta(days=i))).days
            delta_t_plus = abs(self.Time - (datetime(2022,12,16)+timedelta(days=i))).days
            deltas_t = [datetime(2022,12,16)+timedelta(days=i)] + deltas_t
            deltas_t.append(datetime(2022,12,16)+timedelta(days=i))
            # Compute the price and Implied volatility for the put option
            price = self.bsm_price('p', vol, data[self.aapl].Price, 145, rfr, delta_t_minus, 0)
            implied_volatility = self.implied_vol('p', price, data[self.aapl].Price, 145,rfr, delta_t_minus, 0)
            computed_prices = [price] + computed_prices
            computed_impvol = [implied_volatility] + computed_impvol

            # Compute the price and Implied volatility for the put option
            price = self.bsm_price('p', vol, data[self.aapl].Price, 145, rfr, delta_t_plus, 0)
            implied_volatility = self.implied_vol('p', price, data[self.aapl].Price, 145,rfr, delta_t_plus, 0)
            computed_prices.append(price)
            computed_impvol.append(implied_volatility)
        my_data = pd.DataFrame({'Price':computed_prices,'Implied_Volatility': computed_impvol}, index=deltas_t)
        intepolated = my_data.interpolate()
        self.Log(f'At {self.Time} the interpolated value for price and implied volatility with expiry 2022-12-16 is {str(intepolated.iloc[10])}')
        self.Log(f'The three previous values are {str(intepolated.iloc[7:10])}')
        self.Log(f'The three following values are {str(intepolated.iloc[11:14])}')
        
# class FatYellowOwl(QCAlgorithm):

#     def Initialize(self) -> None:
#         self.SetStartDate(2022, 9, 14)
#         self.SetEndDate(2022, 12, 1)
#         self.SetCash(100000)
#         self.SetWarmUp(20)
        
#         # Universe settings
#         self.UniverseSettings.Resolution = Resolution.Hour
        
#         # Requesting data
#         self.aapl = self.AddEquity("AAPL",Resolution.Hour).Symbol
#         option = self.AddOption("AAPL",Resolution.Hour)
#         self.option_symbol = option.Symbol
        
#         # Volatility
#         self.std = StandardDeviation(22)
#         self.RegisterIndicator(self.aapl, self.std, Resolution.Daily)

#         # Risk Free rate
#         self.yieldCurve = self.AddData(USTreasuryYieldCurveRate, "USTYCR", Resolution.Daily).Symbol
#         self.last_rfr = RollingWindow[float](4)

#         # OTCM contracts
#         self.otc = self.AddEquity("OTCM",Resolution.Hour).Symbol
#         option = self.AddOption("OTCM",Resolution.Hour)
#         self.otc_symbol = option.Symbol


#         #Set Options Model: Equity Options are American Style
#         # As such, need to use CrankNicolsonFD or other models.
#         #BlackScholes Model is only for European Options
#         option.PriceModel = OptionPriceModels.CrankNicolsonFD()

#     ## ----------- Black Scholes ------------- ##
#     # Price
#     def d1(self,S,K,T,r,sigma):
#         return(log(S/K)+(r+sigma**2/2.)*T)/(sigma*sqrt(T))
#     def d2(self,S,K,T,r,sigma):
#         return self.d1(S,K,T,r,sigma)-sigma*sqrt(T)

#     def bs_call(self,S,K,T,r,sigma):
#         return S*norm.cdf(self.d1(S,K,T,r,sigma))-K*exp(-r*T)*norm.cdf(self.d2(S,K,T,r,sigma))
    
#     def bs_put(self,S,K,T,r,sigma):
#         return K*exp(-r*T)-S*self.bs_call(S,K,T,r,sigma)

#     # Implied Volatility
#     def call_implied_volatility(self,Price, S, K, T, r):
#         sigma = 0.001
#         while sigma < 1:
#             Price_implied = S * \
#                 norm.cdf(self.d1(S, K, T, r, sigma))-K*exp(-r*T) * \
#                 norm.cdf(self.d2(S, K, T, r, sigma))
#             if Price-(Price_implied) < 0.001:
#                 return sigma
#             sigma += 0.001
#         return "Not Found"

#     def put_implied_volatility(self,Price, S, K, T, r):
#         sigma = 0.001
#         while sigma < 1:
#             Price_implied = K*exp(-r*T)-S+self.bs_call(S, K, T, r, sigma)
#             if Price-(Price_implied) < 0.001:
#                 return sigma
#             sigma += 0.001
#         return "Not Found"

#     def OnData(self, data: Slice) -> None:
#         if data.get(self.yieldCurve):
#             self.last_rfr.Add(data[self.yieldCurve].OneYear)
#         if self.IsWarmingUp: #Wait until warming up is done
#             return
#         if self.Time == datetime(2022,10,14,15):
#             rfr = self.last_rfr[0]
#             # Compute the price for the call option
#             price = self.bs_call(data[self.aapl].Price, 145, abs(self.Time - datetime(2022,11,18)).days, rfr, self.std.Current.Value*sqrt(252))
#             # Get the Implied volatility based on computed price 
#             implied_volatility = self.call_implied_volatility(price, 145, abs(self.Time - datetime(2022,11,18)).days, rfr, self.std.Current.Value*sqrt(252))
#             self.Log(f'At{self.Time} a Call option on AAPL with strike price 145 and expiry of 2022-11-18 has a price {price} and implied volatility {implied_volatility}')
#         elif self.Time == datetime(2022,10,17,10):
#             rfr = self.last_rfr[0]
#             # Compute the price for the put option
#             price = self.bs_put(data[self.aapl].Price, 130, abs(self.Time - datetime(2022,11,11)).days, rfr, self.std.Current.Value*sqrt(252))
#             # Get the Implied volatility based on computed price 
#             implied_volatility = self.put_implied_volatility(price, 130, abs(self.Time - datetime(2022,11,11)).days, rfr, self.std.Current.Value*sqrt(252))
#             self.Log(f'At{self.Time} a Put option on AAPL with strike price 130 and expiry of 2022-11-11 has a price {price} and implied volatility {implied_volatility}')
#         elif self.Time == datetime(2022,10,17,13):
#             self.otc_contract(data)
        
#     def otc_contract(self,data):
#         computed_prices = [None]
#         computed_impvol = [None]
#         deltas_t = [datetime(2022,12,16)]
#         rfr = self.last_rfr[0]
#         # Because we are using +- this will be month approximatly
#         for i in range(10):
#             delta_t_minus = abs(self.Time - (datetime(2022,12,16)-timedelta(days=i))).days
#             delta_t_plus = abs(self.Time - (datetime(2022,12,16)+timedelta(days=i))).days
#             deltas_t = [datetime(2022,12,16)+timedelta(days=i)] + deltas_t
#             deltas_t.append(datetime(2022,12,16)+timedelta(days=i))
#             # Compute the price and Implied volatility for the put option
#             price = self.bs_put(data[self.aapl].Price, 128.25, delta_t_minus, rfr, self.std.Current.Value*sqrt(252))
#             implied_volatility = self.put_implied_volatility(price, 130, delta_t_minus, rfr, self.std.Current.Value*sqrt(252))
#             computed_prices = [price] + computed_prices
#             computed_impvol = [implied_volatility] + computed_impvol

#             # Compute the price and Implied volatility for the put option
#             price = self.bs_put(data[self.aapl].Price, 128.25, delta_t_plus, rfr, self.std.Current.Value*sqrt(252))
#             implied_volatility = self.put_implied_volatility(price, 130, delta_t_plus, rfr, self.std.Current.Value*sqrt(252))
#             computed_prices.append(price)
#             computed_impvol.append(implied_volatility)
#         my_data = pd.DataFrame({'Price':computed_prices,'Implied_Volatility': computed_impvol}, index=deltas_t)
#         intepolated = my_data.interpolate()
#         self.Log(f'At {self.Time} the interpolated value for price and implied volatility with expiry 2022-12-16 is {str(intepolated.iloc[10])}')
#         self.Log(f'The three previous values are {str(intepolated.iloc[7:10])}')
#         self.Log(f'The three following values are {str(intepolated.iloc[11:14])}')
        
       
## ------------------------ Assignment 2