Overall Statistics
Total Trades
13
Average Win
213.17%
Average Loss
-4.54%
Compounding Annual Return
23.515%
Drawdown
75.800%
Expectancy
6.989
Net Profit
137.172%
Sharpe Ratio
0.564
Probabilistic Sharpe Ratio
5.924%
Loss Rate
83%
Win Rate
17%
Profit-Loss Ratio
46.93
Alpha
0.532
Beta
-1.314
Annual Standard Deviation
0.629
Annual Variance
0.396
Information Ratio
0.311
Tracking Error
0.709
Treynor Ratio
-0.27
Total Fees
$1562.75
Estimated Strategy Capacity
$120000.00
Lowest Capacity Asset
VIX XUJS4Z3KE3GU|VIX 31
from datetime import timedelta, date
from talib import MACD, EMA

from collections import deque

from numpy import sum
import numpy as np
from scipy import stats



class SmoothBlueBat(QCAlgorithm):

    def Initialize(self):
        self.SetStartDate(2017, 11, 15)
        #self.SetEndDate(2020, 5, 1)
        self.SetCash(100000) 
        
        self.last_date = datetime(2000, 1, 1)
        
        self.mode = 1 # sell with MACD
        #self.mode = 2 # sell with preset limit order
        
        self.lookbackIV = 150
        
        self.SetSecurityInitializer(self.security_initializer)
        
        self.underly = self.AddIndex('VIX', Resolution.Daily).Symbol
        self.vix = self.AddData( CBOE, "VIX", Resolution.Daily).Symbol
   
        self.macd = self.MACD(self.vix, 12, 26, 9, MovingAverageType.Exponential, Resolution.Daily)
        
        self.ordered = False
        self.contract = str()
        self.contract_without_limit_order = str()
        self.stop_buy=False
        
        self.count=0
        
        self.options_percentage = 0.05 # percentage of options
        self.DaysBeforeExp = 2
        
        self.SetWarmUp(timedelta(self.lookbackIV)) 
        
        self.Schedule.On(self.DateRules.EveryDay(self.underly), \
                        self.TimeRules.BeforeMarketClose(self.underly, 30), \
                        self.Plotting)                 
        
        
    def security_initializer(self, security):
        if security.Type == SecurityType.Equity:
            security.SetDataNormalizationMode(DataNormalizationMode.Raw)

    def OnData(self, data):
        
        if(self.IsWarmingUp):
            return
        
        # decide when to buy
        if self.Securities[self.vix].Close > 25 and self.Securities[self.vix].Close < 30:
            for kvp in self.Portfolio:
                if self.Securities[kvp.Key].Invested:
                    symbol = kvp.Key
                    id = symbol.ID
                    security_type = id.SecurityType
                    if security_type == 10:#SecurityType.Option:
                        if self.last_date < id.Date:
                            self.last_date = id.Date
                    
            if (self.last_date -  self.Time) < timedelta(10): # x days overlap of options
                if not self.stop_buy:
                    self.Buy_Option( data)   
        
        
        # decide when to sell
        if self.mode == 1:
            if (self.Securities[self.vix].Close > 25) and (self.macd.Current.Value < self.macd.Signal.Current.Value):
                self.stop_buy=True
                
                for kvp in self.Portfolio:
                    if self.Securities[kvp.Key].Invested:   
                        symbol = kvp.Key
                        id = symbol.ID
                        security_type = id.SecurityType
                        if security_type == 10: #SecurityType.Option for VIX Calls
                            self.Liquidate(symbol)
            else:                
                self.stop_buy=False
        
        if self.mode == 2:   
            # set  a limit order
            if self.contract_without_limit_order:
                self.Set_limit_order(data)    
                        
                        
          
        
    def Buy_Option(self, data):   
        if not (data.ContainsKey(self.underly) and data[self.underly] is not None):
            return

        if not self.contract:
            #min_strike = data[self.underly].Price + 10
            #max_strike = data[self.underly].Price + 15
            min_strike = 70
            max_strike = 120
            min_expiry = self.Time + timedelta(days=75) # 60 around 3 month
            max_expiry = self.Time + timedelta(days=95)
            #sorted_function = lambda x: x.BidSize
            sorted_function = lambda x: x.StrikePrice
            direction = True
            self.contract = self.GetOption(data, self.underly, OptionRight.Call, min_strike, max_strike, min_expiry, max_expiry, sorted_function, direction)
        
        if not self.contract:
            return
        
        if data.ContainsKey(self.contract.Symbol):

            contract_value = self.Securities[self.contract.Symbol].AskPrice
            if contract_value > 0:

                quantity = self.CalculateOrderQuantity(self.contract.Symbol, self.options_percentage)
                self.MarketOrder(self.contract.Symbol,  quantity)
                
                self.contract_without_limit_order = self.contract
                self.contract = str()
                self.ordered = True
                
                
    def Set_limit_order(self, data): 
        if not self.contract_without_limit_order:
            return
        
        number_cont = self.Portfolio[self.contract_without_limit_order.Symbol].Quantity
        cost_base   = self.Portfolio[self.contract_without_limit_order.Symbol].AveragePrice
        
        self.LimitOrder(self.contract_without_limit_order.Symbol, - number_cont * 1/3, cost_base*1.5)
        self.LimitOrder(self.contract_without_limit_order.Symbol, - number_cont * 1/3, cost_base*4)
        self.LimitOrder(self.contract_without_limit_order.Symbol, - number_cont * 1/3, cost_base*8)
        
        self.contract_without_limit_order = str()
        
           
    def GetOption(self,data,underlying,optionright,MinStrike,\
                  MaxStrike,MinExpiry,MaxExpiry,sortedfunction,reverse):

        ## Get list of Options Contracts for a specific time
        chainProvider = self.OptionChainProvider 
        contracts = chainProvider.GetOptionContractList(underlying, self.Time)  
        
        if len(contracts) == 0: return
       
        filtered_options = [x for x in contracts if x.ID.OptionRight == optionright and\
                                                     x.ID.StrikePrice > MinStrike and\
                                                     x.ID.StrikePrice < MaxStrike and\
                                                     x.ID.Date > MinExpiry and\
                                                     x.ID.Date < MaxExpiry]
           
        if len(filtered_options) == 0: 
            return
           
        added_contracts = []
           
        for x in filtered_options:
            option = self.AddOptionContract(x, Resolution.Minute)
            optionsymbol = self.Securities[option.Symbol]
            if data.ContainsKey(optionsymbol.Symbol):
                added_contracts.append(optionsymbol)
           
        sorted_options=sorted(added_contracts,key=sortedfunction,reverse=reverse)
        if len(sorted_options) > 0:
            selected = sorted_options[0]
        else:
            selected = None

        return selected
        
        
    def Plotting(self):

        # plot underlying's price
        self.Plot("Data Chart VIX", self.vix, self.Securities[self.underly].Close)

        # plot macd
        self.Plot("Macd Signal", "Value",  self.macd.Current.Value)
        self.Plot("Macd Signal", "Signal", self.macd.Signal.Current.Value)  
       
        for kvp in self.Portfolio:
            symbol = kvp.Key
            id = symbol.ID
            security_type = id.SecurityType
            if security_type == 10:             #SecurityType.Option:
                if self.Securities[symbol].Invested:
                    self.Plot("Options Price", symbol, self.Securities[symbol].Price)


        option_invested = [x.Key for x in self.Portfolio if x.Value.Invested and x.Value.Type== 10] 
        if option_invested:
            self.Plot("Data Chart invested", "strike", option_invested[0].ID.StrikePrice)