Overall Statistics |
Total Trades 2836 Average Win 0.07% Average Loss -0.04% Compounding Annual Return -18.561% Drawdown 21.000% Expectancy -0.356 Net Profit -18.559% Sharpe Ratio -2.414 Probabilistic Sharpe Ratio 0.006% Loss Rate 78% Win Rate 22% Profit-Loss Ratio 1.93 Alpha -0.184 Beta 0.128 Annual Standard Deviation 0.063 Annual Variance 0.004 Information Ratio -3.585 Tracking Error 0.111 Treynor Ratio -1.188 Total Fees $2874.30 |
import OptionsUniverse # Our Options Universe import OptionsAlpha #Our options Alpha from Portfolio.EqualWeightingPortfolioConstructionModel import EqualWeightingPortfolioConstructionModel from Execution.VolumeWeightedAveragePriceExecutionModel import VolumeWeightedAveragePriceExecutionModel class Options (QCAlgorithm): def Initialize(self): self.SetStartDate(2019,1,4) self.SetEndDate(2020,1,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, filterFineData = True, universeSettings = None, securityInitializer = None): super().__init__(filterFineData, universeSettings, securityInitializer) # Number of stocks in Coarse Universe self.NumberOfSymbolsCoarse = 2500 # Number of sorted stocks in the fine selection subset using the valuation ratio, EV to EBITDA (EV/EBITDA) self.NumberOfSymbolsFine = 250 # Final number of stocks in security list, after sorted by the valuation ratio, Return on Assets (ROA) self.NumberOfSymbolsInPortfolio =25 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.debttoequityMaxAllowance = 0.8 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)] if key == "M": value2 = [i for i in value1 if i.OperationRatios.QuickRatio.ThreeMonths >= 1.0] if key == "U": value2 = [i for i in value1 if i.OperationRatios.InterestCoverage.ThreeMonths >= 2.0] if key == "T": value2 = [i for i in value1 if i.OperationRatios.ROA.ThreeMonths >= 0.04] if key == "B": value2 = [i for i in value1 if (i.FinancialStatements.IncomeStatement.OtherNonInterestExpense.ThreeMonths / i.FinancialStatements.IncomeStatement.TotalRevenue.ThreeMonths) < 0.60] if key == "I": value2 = [i for i in value1 if i.OperationRatios.LossRatio.ThreeMonths < 1.0] if key != "N" or "M" or "U" or "T" or "B" or "I": value2 = value1 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=[] #creates an empty space in contract list for number of stocks we have for x in self.stocks: self.contract.append(None) for x in self.stocks: #sets the current symbol being looped through (will be used when calling other methods) self.currentSymbol = x.Symbol self.currentstock = x #sets the index specific to each ticker in self.ticker. Indexes between ticker and options should always match #need to write code that checks that option and ticker index is the same self.currentIndex = self.stocks.index(x) if self.contract[self.currentIndex] is None : self.contract[self.currentIndex] = self.GetContract(algorithm) #Generate a contract from the method GetContract and store in contract array which index corresponds to its ticker #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): #set target strike 20% away self.bound=0.2 lowertargetStrike = (self.currentstock.Price * (1-self.bound)) uppertargetStrike=(self.currentstock.Price * (1+self.bound)) #pulls contract data for select equity at current time contracts=algorithm.OptionChainProvider.GetOptionContractList(self.currentSymbol, algorithm.Time) #selects the type of option to be Put contract puts = [x for x in contracts if x.ID.OptionRight == OptionRight.Put] #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) puts = [x for x in puts if x.ID.StrikePrice < lowertargetStrike] #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 puts if 30<(x.ID.Date - algorithm.Time).days <= 60] if not puts: return #selects the type of option to be Put contract call = [x for x in contracts if x.ID.OptionRight ==OptionRight.Call] #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) call = [x for x in call if x.ID.StrikePrice > uppertargetStrike] #then selects all contracts that meet our expiration criteria call = [x for x in call if 30<(x.ID.Date - algorithm.Time).days <= 60] if not call: return #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.optionDataBySymbol ={} self.removed=[] 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 ema=symbolData.EMA putcontract=None callcontract=None for contract ,info in self.optionDataBySymbol.items(): #Set each contract as either a put or call as we pull both if info.Underlying.Value==symbol.Value and info.Right=="put": putcontract=contract if info.Underlying.Value==symbol.Value and info.Right=="call": callcontract=contract #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.Current.Value-std.Current.Value): 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].Invested: algorithm.MarketOrder(putcontract,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].Invested: algorithm.MarketOrder(callcontract,-1,True) elif value> (ema.Current.Value+std.Current.Value): 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].Invested: algorithm.MarketOrder(callcontract,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].Invested: algorithm.MarketOrder(putcontract,-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: self.removed.clear() for contract ,info in self.optionDataBySymbol.items(): if info.Underlying==y.Symbol.Value: self.removed.append(contract) #pull all options with equity underlying it and add to self.removed for x in self.removed : optionData=self.optionDataBySymbol.pop(x,None) #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]: optionData=self.optionDataBySymbol.pop(y.Symbol,None) #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 history = algorithm.History(addedSymbols, self.period, self.resolution) #pulls history for all new symbols for symbol in addedSymbols: ema=ExponentialMovingAverage(self.period) std=StandardDeviation(self.period) consolidator = algorithm.ResolveConsolidator(symbol, self.resolution) algorithm.RegisterIndicator(symbol, std, consolidator) algorithm.RegisterIndicator(symbol, ema, consolidator) #for each new symbol, generate an instance of the indicator std and ema 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(): ema.Update(tuple.Index, tuple.close) std.Update(tuple.Index, tuple.close) self.symbolDataBySymbol[symbol] = SymbolData(symbol, ema,std,consolidator) #Records Symbol Data of each symbol including indicators and consolidator options= [ x.Symbol for x in changes.AddedSecurities if (x.Symbol not in self.optionDataBySymbol and x.Symbol.SecurityType ==SecurityType.Option)] if len(options) == 0: return # Assign the option underlying for option in options: algorithm.Securities[option].Underlying = algorithm.Securities[option.Underlying] newhistory = algorithm.History(options, 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: # for each new option, add to algorithm, save underlying and type of contract underlying=contract.Underlying if contract.ID.OptionRight ==OptionRight.Call: right="call" elif contract.ID.OptionRight ==OptionRight.Put: right="put" self.optionDataBySymbol[contract] = OptionData(contract,underlying,right) #Records Option Data of each contract underlying symbol along with it being a put or call class SymbolData: def __init__(self, symbol, ema ,std,consolidator): self.Symbol = symbol self.EMA = ema self.STD=std self.Consolidator=consolidator class OptionData: def __init__(self,contract, underlying,right): self.Contract=contract self.Underlying=underlying self.Right=right