Overall Statistics
Total Trades
319
Average Win
0.02%
Average Loss
-0.02%
Compounding Annual Return
-31.346%
Drawdown
4.100%
Expectancy
-0.874
Net Profit
-3.339%
Sharpe Ratio
-4.982
Probabilistic Sharpe Ratio
2.047%
Loss Rate
94%
Win Rate
6%
Profit-Loss Ratio
1.10
Alpha
-0.219
Beta
-0.368
Annual Standard Deviation
0.055
Annual Variance
0.003
Information Ratio
-2.857
Tracking Error
0.149
Treynor Ratio
0.745
Total Fees
$319.00
Estimated Strategy Capacity
$19000.00
Lowest Capacity Asset
FITB WS49I0BZTKDI|FITB R735QTJ8XC9X
import OptionsUniverse # Our Options Universe
import OptionsAlpha #Our options Alpha 

class Options (QCAlgorithm):
    
    def Initialize(self):
        self.SetStartDate(2018,1,4)  
        self.SetEndDate(2018,2,4)
        self.SetCash(100000)  # Set Strategy Cash
        self.SetTimeZone(TimeZones.Chicago)
       
        self.SetSecurityInitializer(lambda s: s.SetMarketPrice(self.GetLastKnownPrice(s))) 
       
        self.AddUniverseSelection(OptionsUniverse.universe()) #Calls Universe class, returns equities and underlying alphas
    
        self.UniverseSettings.Resolution = Resolution.Minute #Minute resolution for options
        self.UniverseSettings.DataNormalizationMode=DataNormalizationMode.Raw #how data goes into alg
        self.UniverseSettings.FillForward = True #Fill in empty data will next price
        self.UniverseSettings.ExtendedMarketHours = False #Does not takes in account after hours data
        self.UniverseSettings.MinimumTimeInUniverse = 1 # each equity has to spend at least 1 hour in universe
        self.UniverseSettings.Leverage=2 #Set's 2 times leverage
        self.Settings.FreePortfolioValuePercentage = 0.5
      
       
        self.AddAlpha(OptionsAlpha.alpha()) #Emits insights on equities and send automatic market orders on options

         # we do not want to rebalance on insight changes
        self.Settings.RebalancePortfolioOnInsightChanges = False;
        # we want to rebalance only on security changes
        self.Settings.RebalancePortfolioOnSecurityChanges = False;
        #Set's equal weighting for all of our insights (equities) in our algorithm
        self.SetPortfolioConstruction(EqualWeightingPortfolioConstructionModel())


        self.SetRiskManagement(NullRiskManagementModel())
        # Does not set any risk parameters
        
        self.SetExecution(ImmediateExecutionModel())
        
    def OnData(self, slice):
      if self.IsWarmingUp: return
from QuantConnect.Data.UniverseSelection import * 
from Selection.FundamentalUniverseSelectionModel import FundamentalUniverseSelectionModel 
from QuantConnect.Data.Custom.SEC import *
from Selection.OptionUniverseSelectionModel import OptionUniverseSelectionModel

from datetime import timedelta, datetime
from math import ceil
from itertools import chain
import numpy as np
#imports necessary packages

class universe(FundamentalUniverseSelectionModel):

    def __init__(self, numCoarse = 2500, numFine = 250, numPortfolio = 25, 
                 debttoequityMaxAllowance =  0.8, bound = 0.2, minContractExpiry = 30,
                 maxContractExpiry = 60, filterFineData = True, universeSettings = None, securityInitializer = None):
        super().__init__(filterFineData, universeSettings, securityInitializer)

        # Number of stocks in Coarse Universe
        self.NumberOfSymbolsCoarse = numCoarse
        # Number of sorted stocks in the fine selection subset using the valuation ratio, EV to EBITDA (EV/EBITDA)
        self.NumberOfSymbolsFine =  numFine
        # Final number of stocks in security list, after sorted by the valuation ratio, Return on Assets (ROA)
        self.NumberOfSymbolsInPortfolio = numPortfolio
       
        self.debttoequityMaxAllowance = debttoequityMaxAllowance
        self.bound = bound
        self.minContractExpiry = minContractExpiry
        self.maxContractExpiry = maxContractExpiry
       
        self.lastmonth = -1
        self.dollarVolumeBySymbol = {}
        

    def SelectCoarse(self, algorithm, coarse):
      
        month= algorithm.Time.month
        if month == self.lastmonth:
            return Universe.Unchanged
        self.lastmonth= month
        #We only want to run universe selection once a month

        # sort the stocks by dollar volume and take the top 2000
        top = sorted([x for x in coarse if x.HasFundamentalData],
                    key=lambda x: x.DollarVolume, reverse=True)[:self.NumberOfSymbolsCoarse]
        
        #assigns all the stocks from price to dollarVolumeBySymbol
        self.dollarVolumeBySymbol = { i.Symbol: i.DollarVolume for i in top }

        return list(self.dollarVolumeBySymbol.keys())
    

    def SelectFine(self, algorithm, fine):
        self.priceAllowance = 30

        # QC500:
        ## The company's headquarter must in the U.S. 
        ## The stock must be traded on either the NYSE or NASDAQ 
        ## At least half a year since its initial public offering 
        ## The stock's market cap must be greater than 500 million 
        ## We want the stock's debt to equity ratio to be relatively low to enssure we are investing in stable companies
    
        filteredFine = [x for x in fine if x.CompanyReference.CountryId == "USA"
                                        and x.Price > self.priceAllowance
                                        and (x.CompanyReference.PrimaryExchangeID == "NYS" or x.CompanyReference.PrimaryExchangeID == "NAS")
                                        and (algorithm.Time - x.SecurityReference.IPODate).days > 180
                                        and (x.EarningReports.BasicAverageShares.ThreeMonths * x.EarningReports.BasicEPS.TwelveMonths * x.ValuationRatios.PERatio > 5e8)
                                        and 0 <= (x.OperationRatios.TotalDebtEquityRatioGrowth.OneYear) <= self.debttoequityMaxAllowance #this value will change in accordance to S&P Momentum
                                        and x.FinancialStatements.BalanceSheet.AllowanceForDoubtfulAccountsReceivable.ThreeMonths <= 2.0 * x.FinancialStatements.CashFlowStatement.ProvisionandWriteOffofAssets.ThreeMonths
                                        and (x.FinancialStatements.IncomeStatement.ProvisionForDoubtfulAccounts.TwoMonths <= 1.0*x.FinancialStatements.CashFlowStatement.ProvisionandWriteOffofAssets.ThreeMonths)
                
                                    ]
       
            
      
        count = len(filteredFine)
        if count == 0: return []

        myDict = dict()
        percent = self.NumberOfSymbolsFine / count

        # select stocks with top dollar volume in every single sector based on specific sector ratios
        # N=Normal (Manufacturing), M=Mining, U=Utility, T=Transportation, B=Bank, I=Insurance
        
        
        for key in ["N", "M", "U", "T", "B", "I"]:
            value1 = [x for x in filteredFine if x.CompanyReference.IndustryTemplateCode == key]
            value2 = []
            
            
            if key == "N":
                
                value2 = [i for i in value1 if (1.0 <= i.OperationRatios.InventoryTurnover.ThreeMonths <= 2.0)]
                
            elif key == "M":
                
                value2 = [i for i in value1 if i.OperationRatios.QuickRatio.ThreeMonths >= 1.0]
                
            elif key == "U":
                
                value2 = [i for i in value1 if i.OperationRatios.InterestCoverage.ThreeMonths >= 2.0]
                
            elif key == "T":
                
                value2 = [i for i in value1 if i.OperationRatios.ROA.ThreeMonths >= 0.04]
            
            elif key == "B":
                
                value2 = [i for i in value1 if (i.FinancialStatements.IncomeStatement.OtherNonInterestExpense.ThreeMonths / i.FinancialStatements.IncomeStatement.TotalRevenue.ThreeMonths) < 0.60]
            
            else:
                
                value2 = [i for i in value1 if i.OperationRatios.LossRatio.ThreeMonths < 1.0]
                
            value3 = sorted(value2, key=lambda x: self.dollarVolumeBySymbol[x.Symbol], reverse = True)
            myDict[key] = value3[:ceil(len(value3) * percent)]

        # stocks in QC500 universe
        topFine = chain.from_iterable(myDict.values())

        # sort stocks in the security universe of QC500 based on Enterprise Value to EBITDA valuation ratio
        sortedByEVToEBITDA = sorted(topFine, key=lambda x: x.ValuationRatios.EVToEBITDA , reverse=True)

        # sort subset of stocks that have been sorted by Enterprise Value to EBITDA, based on the valuation ratio Return on Assets (ROA)
        sortedByROA = sorted(sortedByEVToEBITDA[:self.NumberOfSymbolsFine], key=lambda x: x.ValuationRatios.ForwardROA, reverse=False)

        # retrieve list of securites in portfolio
        self.stocks = sortedByROA[:self.NumberOfSymbolsInPortfolio]
        
        self.contract = [self.GetContract(algorithm, stock) for stock in self.stocks]

        #Following block of code combines both the symbols of equities and options
        res = [i for i in self.contract if i] 
        self.result=[]
        for t in res: 
            for x in t: 
                self.result.append(x)
        self.newstocks= [x.Symbol for x in self.stocks]
        
        #Returns our equities and hedged options
        return [x for x in self.newstocks + self.result]

    
    def GetContract(self, algorithm, stock):
        
        #set target strike 20% away
        lowertargetStrike = (stock.Price * (1-self.bound)) 
        uppertargetStrike=(stock.Price * (1+self.bound)) 
   
        #pulls contract data for select equity at current time
        contracts=algorithm.OptionChainProvider.GetOptionContractList(stock.Symbol, algorithm.Time)
   
        #selects the type of option to be Put contract
        #then selects all contracts that meet our expiration criteria
        #We want between 30 and 60 days as we do not want to hold our options close to expiration
        puts = [x for x in contracts if x.ID.OptionRight == OptionRight.Put and \
                                        x.ID.StrikePrice < lowertargetStrike and \
                                        self.minContractExpiry < (x.ID.Date - algorithm.Time).days <= self.maxContractExpiry]
        if not puts:
            return
        
        #sorts contracts by closet expiring date date and closest strike price (sorts in ascending order)
        puts = sorted(sorted(puts, key = lambda x: x.ID.Date), 
            key = lambda x: x.ID.StrikePrice)
        
        #selects the type of option to be Put contract
        #then selects all contracts that meet our expiration criteria
        call = [x for x in contracts if x.ID.OptionRight ==OptionRight.Call and \
                                        x.ID.StrikePrice > uppertargetStrike and \
                                        self.minContractExpiry < (x.ID.Date - algorithm.Time).days <= self.maxContractExpiry]
        if not call:
           return
        
        #sorts contracts by closet expiring date date and closest strike price (sorts in ascending order)
        call = sorted(sorted(call, key = lambda x: x.ID.Date), 
            key = lambda x: x.ID.StrikePrice)
        
        #will eventually return array of optimal puts and calls
        return (puts[0],call[0])
class alpha(AlphaModel):   
    def __init__(self,period = 63,resolution = Resolution.Daily):
      
        self.period = period
        self.resolution = resolution
        self.insightPeriod = Time.Multiply(Extensions.ToTimeSpan(resolution), period)
        self.symbolDataBySymbol ={} 
        self.options = []
        
        resolutionString = Extensions.GetEnumString(resolution, Resolution)
        self.Name = '{}({},{})'.format(self.__class__.__name__, period, resolutionString)
        self.day=None
        
        
    def Update(self, algorithm, data):
   
        insights=[]
        
        if algorithm.Time.day == self.day:
            return []
        #Currently we only want to emit insights once a day
         
        
        for symbol, symbolData in self.symbolDataBySymbol.items():
            
            if not data.Bars.ContainsKey(symbol):
                continue
            
            #Pulls our symbol specific indicators
            value = algorithm.Securities[symbol].Price
            std=symbolData.STD.Current.Value
            ema=symbolData.EMA.Current.Value
            putcontract=None
            callcontract=None
            
            for contract in self.options:
                if contract.Underlying.Symbol.Value == symbol.Value:
                    if contract.Right == OptionRight.Put:
                        putcontract = contract
                    if contract.Right == OptionRight.Call:
                        callcontract = contract
                if putcontract is not None and callcontract is not None:
                    break
                  
            #The Alpha strategy longs if equity current price is a standard deviation below EMA and shorts if vice versa
            #Emits flat otherwise
            if value< (ema-std):
                insights.append(Insight.Price(symbol, timedelta(days=1), InsightDirection.Up, 0.0025, 1.00,"Options", .5))
                
                #If we are longing equity and have not already bought a put contract and one exists buy one
                if putcontract is not None and not algorithm.Portfolio[putcontract.Symbol].Invested:
                    algorithm.MarketOrder(putcontract.Symbol,1,True)
                   #If we are trying to buy a put and we already have a call on the equity , we should sell it
                    
                    if callcontract is not None and algorithm.Portfolio[callcontract.Symbol].Invested:
                        algorithm.MarketOrder(callcontract.Symbol,-1,True)
             
                     
            elif value> (ema+std):
                insights.append(Insight.Price(symbol, timedelta(days=1), InsightDirection.Down,0.0025, 1.00,"Options", .5))
                
                #If we are shorting equity and have not already bought a put contract and one exists buy one  
                if callcontract is not None and not algorithm.Portfolio[callcontract.Symbol].Invested:
                    algorithm.MarketOrder(callcontract.Symbol,1,True)
                   
                    #If we are trying to buy a call and we already have a put on the equity , we should sell it
                    if  putcontract is not None and algorithm.Portfolio[putcontract.Symbol].Invested:
                        algorithm.MarketOrder(putcontract.Symbol,-1,True)
            else:
                insights.append(Insight.Price(symbol, timedelta(days=1), InsightDirection.Flat,0.0025, 1.00,"ReversiontotheMean", .5))

        if insights:
            self.day = algorithm.Time.day

        return insights



    def OnSecuritiesChanged(self, algorithm, changes):
        for y in  changes.RemovedSecurities:
            #There are two ways which we can remove options
            
            #1 if an equity is no longer in our universe remove it and corresponding equity
            if y.Symbol.SecurityType ==SecurityType.Equity:
                remove = [contract for contract in self.options if contract.Underlying.Symbol.Value == y.Symbol.Value]
                for x in remove:
                    self.options.remove(x)
                    #As we do not create any indicators with options we do not have any consolidators that we need to remove here
                    
                symbolData = self.symbolDataBySymbol.pop(y.Symbol, None)
                if symbolData:
                    algorithm.SubscriptionManager.RemoveConsolidator(y.Symbol, symbolData.Consolidator)
                    #Remove our consolidators to not slow down our algorithm
            
            #If the option is no longer the desired one and we also arent removing the equity remove it
            elif y.Symbol.SecurityType ==SecurityType.Option:
                if y.Underlying not in [x.Symbol for x in changes.RemovedSecurities]:
                    contractToRemove = [contract for contract in self.options if contract.Symbol == y.Symbol]
                    if len(contractToRemove) > 0:
                        self.options.remove(contractToRemove[0])
                  #As we do not create any indicators with options we do not have any consolidators that we need to remove here

      
        addedSymbols = [ x.Symbol for x in changes.AddedSecurities if (x.Symbol not in self.symbolDataBySymbol and x.Symbol.SecurityType ==SecurityType.Equity)]
     
    
        if len(addedSymbols) == 0: return
        #if no new symbols we do not need to generate any new instances

        for symbol in addedSymbols:
            self.symbolDataBySymbol[symbol] = SymbolData(symbol, algorithm, self.period, self.resolution)
            #Records Symbol Data of each symbol including indicators and consolidator
            
        options = [ x for x in changes.AddedSecurities if (x not in self.options and x.Symbol.SecurityType ==SecurityType.Option)]
        optionSymbols = [x.Symbol for x in options]
        if len(options) == 0: return
    
        # Assign the option underlying
        for option in optionSymbols:
            algorithm.Securities[option].Underlying = algorithm.Securities[option.Underlying] 
       
        newhistory = algorithm.History(optionSymbols, self.period, Resolution.Minute)
    
        if  newhistory.empty: return
        #if no new symbols we do not need to generate any new instances
        
        for contract in options:
            self.options.append(contract)
       #Records Option Data  of each contract underlying symbol along with it being a put or call
  
class SymbolData:
    def __init__(self, symbol, algorithm, period, resolution):
        self.Symbol = symbol
        
        self.EMA = ExponentialMovingAverage(period)
        self.STD = StandardDeviation(period)
        self.Consolidator = algorithm.ResolveConsolidator(symbol, resolution)
        algorithm.RegisterIndicator(symbol, self.STD, self.Consolidator)
        algorithm.RegisterIndicator(symbol, self.EMA, self.Consolidator)
        #for each new symbol, generate an instance of the indicator std and ema
        
        history = algorithm.History(symbol, period, resolution)
        
        if not history.empty:
            ticker = SymbolCache.GetTicker(symbol)
            #if history isnt empty set the ticker as the symbol
            
            for tuple in history.loc[ticker].itertuples():
                self.EMA.Update(tuple.Index, tuple.close)
                self.STD.Update(tuple.Index, tuple.close)