| 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: returnfrom 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):
super().__init__(filterFineData, universeSettings)
# 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)