Overall Statistics
Total Orders
852
Average Win
1.23%
Average Loss
-1.34%
Compounding Annual Return
10.989%
Drawdown
24.100%
Expectancy
-0.098
Start Equity
100000
End Equity
381678.66
Net Profit
281.679%
Sharpe Ratio
0.56
Sortino Ratio
0.452
Probabilistic Sharpe Ratio
9.641%
Loss Rate
53%
Win Rate
47%
Profit-Loss Ratio
0.92
Alpha
0.007
Beta
0.622
Annual Standard Deviation
0.113
Annual Variance
0.013
Information Ratio
-0.302
Tracking Error
0.091
Treynor Ratio
0.102
Total Fees
$2082.93
Estimated Strategy Capacity
$290000.00
Lowest Capacity Asset
SPY 32MMF5W48ORTY|SPY R735QTJ8XC9X
Portfolio Turnover
5.97%
# https://quantpedia.com/strategies/volatility-risk-premium-effect/
# 
# Each month, at-the-money straddle, with one month until maturity, is sold at the bid price with a 5% option premium, and an offsetting 15%
# out-of-the-money puts are bought (at the ask price) as insurance against a market crash. The remaining cash and received option premium are
# invested in the index. The strategy is rebalanced monthly.

from AlgorithmImports import *

class VolatilityRiskPremiumEffect(QCAlgorithm):

    def Initialize(self):
        self.SetStartDate(2012, 1, 1)
        self.SetCash(100000)
        
        data = self.AddEquity("SPY", Resolution.Minute)
        data.SetLeverage(5)
        self.symbol = data.Symbol
        
        option = self.AddOption("SPY", Resolution.Minute)
        option.SetFilter(-20, 20, 25, 35)
        
        self.last_day = -1
        
    def OnData(self,slice):
        # Check once a day.
        if self.Time.day == self.last_day:
            return
        self.last_day = self.Time.day
            
        for i in slice.OptionChains:
            chains = i.Value

            if not self.Portfolio.Invested:
                # divide option chains into call and put options 
                calls = list(filter(lambda x: x.Right == OptionRight.Call, chains))
                puts = list(filter(lambda x: x.Right == OptionRight.Put, chains))
                
                # if lists are empty return
                if not calls or not puts: return
            
                underlying_price = self.Securities[self.symbol].Price
                expiries = [i.Expiry for i in puts]
                
                # determine expiration date nearly one month
                expiry = min(expiries, key=lambda x: abs((x.date()-self.Time.date()).days-30))
                strikes = [i.Strike for i in puts]
                
                # determine at-the-money strike
                strike = min(strikes, key=lambda x: abs(x-underlying_price))
                
                # determine 15% out-of-the-money strike
                otm_strike = min(strikes, key = lambda x:abs(x - float(0.85) * underlying_price))
        
                atm_call = [i for i in calls if i.Expiry == expiry and i.Strike == strike]
                atm_put = [i for i in puts if i.Expiry == expiry and i.Strike == strike]
                otm_put = [i for i in puts if i.Expiry == expiry and i.Strike == otm_strike]
        
                if atm_call and atm_put and otm_put:
                    options_q = int(self.Portfolio.MarginRemaining / (underlying_price * 100))

                    # sell at-the-money straddle
                    self.Sell(atm_call[0].Symbol, options_q)
                    self.Sell(atm_put[0].Symbol, options_q)
                    
                    # buy 15% out-of-the-money put
                    self.Buy(otm_put[0].Symbol, options_q)
                    
                    # buy index.
                    self.SetHoldings(self.symbol, 1)
        
            invested = [x.Key for x in self.Portfolio if x.Value.Invested]
            if len(invested) == 1:
                self.Liquidate(self.symbol)