| Overall Statistics |
|
Total Trades 5174 Average Win 0.22% Average Loss -0.19% Compounding Annual Return 3.611% Drawdown 17.600% Expectancy 0.061 Net Profit 32.838% Sharpe Ratio 0.437 Loss Rate 51% Win Rate 49% Profit-Loss Ratio 1.17 Alpha 0.038 Beta -0.046 Annual Standard Deviation 0.077 Annual Variance 0.006 Information Ratio -0.412 Tracking Error 0.16 Treynor Ratio -0.728 Total Fees $31578.95 |
from Execution.ImmediateExecutionModel import ImmediateExecutionModel
from Portfolio.EqualWeightingPortfolioConstructionModel import EqualWeightingPortfolioConstructionModel
class VerticalTachyonRegulators(QCAlgorithm):
def Initialize(self):
self.SetStartDate(2011, 1, 1) # Set Start Date
self.SetEndDate(2019, 1, 1)
#self.SetStartDate(2019, 1, 1)
#self.SetEndDate(2019, 8, 16)
self.SetCash(1000000) # Set Strategy Cash
## Set execution model to mimic market orders
self.SetExecution(ImmediateExecutionModel())
## set equal weighting protfolio construction to mimic intital algorithm weighting scheme
self.SetPortfolioConstruction(EqualWeightingPortfolioConstructionModel())
## Helper variables for universe selection and checking for conditions to update only at the
## beginning of each month
self.num_coarse = 250
self.num_fine = 10
self.lastMonth = -1
## Coarse/Fine universe selection model
self.UniverseSettings.Resolution = Resolution.Daily
self.AddUniverse(self.CoarseSelectionFunction, self.FineSelectionFunction)
## Custom Alpha model where we can perform the trading logic and the filtering done based on Fundamental Data
self.AddAlpha(FundamentalFactorAlphaModel(self.num_fine))
def CoarseSelectionFunction(self, coarse):
## If not time to rebalance, keep the same universe
if self.Time.month == self.lastMonth:
return Universe.Unchanged
## Else reassign the month variable and filter
self.lastMonth = self.Time.month
## Select only those with Fundamental Data and a sufficiently large price
## Sort by top dollar volume -- most liquid to least liquid
selected = sorted([x for x in coarse if x.HasFundamentalData and x.Price > 5],
key = lambda x: x.DollarVolume, reverse=True)
return [i.Symbol for i in selected[:self.num_coarse]]
def FineSelectionFunction(self, fine):
## Filter the fine data for equities with non-zero/non-null Value,
## 1-month Price Change, and Book Value per Share
filtered_fine = [x.Symbol for x in fine if x.OperationRatios.OperationMargin.Value > 0
and x.ValuationRatios.PriceChange1M > 0
and x.ValuationRatios.BookValuePerShare > 0]
return filtered_fine
class FundamentalFactorAlphaModel(AlphaModel):
def __init__(self, num_fine):
## Initialize the various variables/helpers we'll need
self.lastMonth = -1
self.longs = []
self.shorts = []
self.num_fine = num_fine
def Update(self, algorithm, data):
## Return no insights if it's not the month to rebalance
if algorithm.Time.month == self.lastMonth:
return []
self.lastMonth = algorithm.Time.month
## Create empty list of insights
insights = []
## We will liquidate any securities we're still invested in that we don't want to hold a position
## for the next month
for kvp in algorithm.Portfolio:
holding = kvp.Value
symbol = holding.Symbol
if holding.Invested and symbol not in self.longs and symbol not in self.shorts:
insights.append(Insight(symbol, timedelta(30), InsightType.Price, InsightDirection.Flat, None, None))
## Emit Up (buy) Insights for our desired long positions
for symbol in self.longs:
insights.append(Insight(symbol, timedelta(30), InsightType.Price, InsightDirection.Up, 0.01, None))
## Emit Down (sell) Insights for our desired short positions
for symbol in self.shorts:
insights.append(Insight(symbol, timedelta(30), InsightType.Price, InsightDirection.Down, 0.01, None))
return insights
def OnSecuritiesChanged(self, algorithm, changes):
## Get the securities
added = [x for x in changes.AddedSecurities]
## Perform filtering/sorting based on Value, Quality, and Momentum
sortedByfactor1 = sorted(added, key=lambda x: x.Fundamentals.OperationRatios.OperationMargin.Value, reverse=True)
sortedByfactor2 = sorted(added, key=lambda x: x.Fundamentals.ValuationRatios.PriceChange1M, reverse=True)
sortedByfactor3 = sorted(added, key=lambda x: x.Fundamentals.ValuationRatios.BookValuePerShare, reverse=True)
## Create dictionary to store scores
scores = {}
## Assign a score to each stock.
for i,ele in enumerate(sortedByfactor1):
rank1 = i
rank2 = sortedByfactor2.index(ele)
rank3 = sortedByfactor3.index(ele)
scores[ele] = rank1*0.2 + rank2*0.4 + rank3*0.4
## Sort the stocks by their scores
sorted_stock = sorted(scores.items(), key=lambda d:d[1],reverse=False)
sorted_symbol = [x[0] for x in sorted_stock]
## Sort the top stocks into the long_list and the bottom ones into the short_list
self.longs = [x.Symbol for x in sorted_symbol[:self.num_fine]]
self.shorts = [x.Symbol for x in sorted_symbol[-self.num_fine:]]
algorithm.Log('Long: ' + ', '.join(sorted([x.Value for x in self.longs])))
algorithm.Log('Short: ' + ', '.join(sorted([x.Value for x in self.shorts])))
from pytz import utc
from itertools import groupby
from datetime import datetime, timedelta
UTCMIN = datetime.min.replace(tzinfo=utc)
class EqualWeightingPortfolioConstructionModel(PortfolioConstructionModel):
'''Provides an implementation of IPortfolioConstructionModel that gives equal weighting to all securities.
The target percent holdings of each security is 1/N where N is the number of securities.
For insights of direction InsightDirection.Up, long targets are returned and
for insights of direction InsightDirection.Down, short targets are returned.'''
def __init__(self, resolution = Resolution.Daily):
'''Initialize a new instance of EqualWeightingPortfolioConstructionModel
Args:
resolution: Rebalancing frequency'''
self.insightCollection = InsightCollection()
self.removedSymbols = []
self.nextExpiryTime = UTCMIN
self.rebalancingTime = UTCMIN
self.rebalancingPeriod = TimeSpan.FromDays(30)
def ShouldCreateTargetForInsight(self, insight):
'''Method that will determine if the portfolio construction model should create a
target for this insight
Args:
insight: The insight to create a target for'''
return True
def DetermineTargetPercent(self, activeInsights):
'''Will determine the target percent for each insight
Args:
activeInsights: The active insights to generate a target for'''
result = {}
# give equal weighting to each security
count = sum(x.Direction != InsightDirection.Flat for x in activeInsights)
percent = 0 if count == 0 else 1.0 / count
for insight in activeInsights:
result[insight] = insight.Direction * percent
return result
def CreateTargets(self, algorithm, insights):
'''Create portfolio targets from the specified insights
Args:
algorithm: The algorithm instance
insights: The insights to create portfolio targets from
Returns:
An enumerable of portfolio targets to be sent to the execution model'''
targets = []
if (algorithm.UtcTime <= self.nextExpiryTime and
algorithm.UtcTime <= self.rebalancingTime and
len(insights) == 0 and
self.removedSymbols is None):
return targets
for insight in insights:
if self.ShouldCreateTargetForInsight(insight):
self.insightCollection.Add(insight)
# Create flatten target for each security that was removed from the universe
if self.removedSymbols is not None:
universeDeselectionTargets = [ PortfolioTarget(symbol, 0) for symbol in self.removedSymbols ]
targets.extend(universeDeselectionTargets)
self.removedSymbols = None
# Get insight that haven't expired of each symbol that is still in the universe
activeInsights = self.insightCollection.GetActiveInsights(algorithm.UtcTime)
# Get the last generated active insight for each symbol
lastActiveInsights = []
for symbol, g in groupby(activeInsights, lambda x: x.Symbol):
lastActiveInsights.append(sorted(g, key = lambda x: x.GeneratedTimeUtc)[-1])
# Determine target percent for the given insights
percents = self.DetermineTargetPercent(lastActiveInsights)
errorSymbols = {}
for insight in lastActiveInsights:
target = PortfolioTarget.Percent(algorithm, insight.Symbol, percents[insight])
if not target is None:
targets.append(target)
else:
errorSymbols[insight.Symbol] = insight.Symbol
# Get expired insights and create flatten targets for each symbol
expiredInsights = self.insightCollection.RemoveExpiredInsights(algorithm.UtcTime)
expiredTargets = []
for symbol, f in groupby(expiredInsights, lambda x: x.Symbol):
if not self.insightCollection.HasActiveInsights(symbol, algorithm.UtcTime) and not symbol in errorSymbols:
expiredTargets.append(PortfolioTarget(symbol, 0))
continue
targets.extend(expiredTargets)
self.nextExpiryTime = self.insightCollection.GetNextExpiryTime()
if self.nextExpiryTime is None:
self.nextExpiryTime = UTCMIN
self.rebalancingTime = algorithm.UtcTime + self.rebalancingPeriod
return targets
def OnSecuritiesChanged(self, algorithm, changes):
'''Event fired each time the we add/remove securities from the data feed
Args:
algorithm: The algorithm instance that experienced the change in securities
changes: The security additions and removals from the algorithm'''
# Get removed symbol and invalidate them in the insight collection
self.removedSymbols = [x.Symbol for x in changes.RemovedSecurities]
self.insightCollection.Clear(self.removedSymbols)