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)