| Overall Statistics |
|
Total Trades 12 Average Win 0% Average Loss 0% Compounding Annual Return -0.202% Drawdown 2.800% Expectancy 0 Net Profit -0.201% Sharpe Ratio -0.03 Probabilistic Sharpe Ratio 10.864% Loss Rate 0% Win Rate 0% Profit-Loss Ratio 0 Alpha -0.024 Beta 0.155 Annual Standard Deviation 0.031 Annual Variance 0.001 Information Ratio -2.665 Tracking Error 0.056 Treynor Ratio -0.006 Total Fees $13.28 Estimated Strategy Capacity $15000000.00 Lowest Capacity Asset BCM R735QTJ8XC9X |
#region imports
from AlgorithmImports import *
#endregion
class PortfolioRebalanceOnCustomFuncRegressionAlgorithm(QCAlgorithm):
def Initialize(self):
''' Initialise the data and resolution required, as well as the cash and start-end dates for your algorithm. All algorithms must initialized.'''
self.UniverseSettings.Resolution = Resolution.Daily
self.SetStartDate(2017, 1, 1)
self.SetEndDate(2018, 1, 1)
self.Settings.RebalancePortfolioOnInsightChanges = False
self.Settings.RebalancePortfolioOnSecurityChanges = False
self.num_coarse = 500
self.month= 0
self.AddUniverse(self.CoarseSelectionFunction, self.FineSelectionFunction)
self.AddAlpha(FundamentalFactorAlphaModel())
self.SetPortfolioConstruction(MyEqualWeightingPortfolioConstructionModel(self.IsRebalanceDue, ag=self))
self.SetExecution(ImmediateExecutionModel())
def CoarseSelectionFunction(self, coarse):
# If not time to rebalance, keep the same universe
if not self.IsRebalanceDue(self.Time):
return Universe.Unchanged
# 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 [x.Symbol for x in selected[:self.num_coarse]]
def FineSelectionFunction(self, fine):
# Filter the fine data for equities that IPO'd more than 5 years ago in selected sectors
sectors = [
MorningstarSectorCode.FinancialServices,
MorningstarSectorCode.RealEstate,
MorningstarSectorCode.Healthcare,
MorningstarSectorCode.Utilities,
MorningstarSectorCode.Technology]
filtered_fine = [x.Symbol for x in fine if x.SecurityReference.IPODate + timedelta(365*5) < self.Time
and x.AssetClassification.MorningstarSectorCode in sectors
and x.OperationRatios.ROE.Value > 0.15
and x.OperationRatios.NetMargin.Value > 0
and 30 > x.ValuationRatios.PERatio > 0]
# self.Debug(f'{self.Time} len(filtered_fine) {len(filtered_fine)}')
return filtered_fine
def IsRebalanceDue(self, time):
# Rebalance on the first day of the Quarter
if time.month == self.month or time.month not in [1, 4, 7, 10]:
return None
self.month = time.month
self.Debug(f'{self.Time} utc {self.UtcTime}, IsRebalanceDue time {time}')
return time
class MyEqualWeightingPortfolioConstructionModel(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, rebalance = Resolution.Daily, portfolioBias = PortfolioBias.LongShort, ag=None):
'''Initialize a new instance of EqualWeightingPortfolioConstructionModel
Args:
rebalance: Rebalancing parameter. If it is a timedelta, date rules or Resolution, it will be converted into a function.
If None will be ignored.
The function returns the next expected rebalance time for a given algorithm UTC DateTime.
The function returns null if unknown, in which case the function will be called again in the
next loop. Returning current time will trigger rebalance.
portfolioBias: Specifies the bias of the portfolio (Short, Long/Short, Long)'''
super().__init__()
self.portfolioBias = portfolioBias
self.rebalanceTime = datetime.min
# If the argument is an instance of Resolution or Timedelta
# Redefine rebalancingFunc
rebalancingFunc = rebalance
if isinstance(rebalance, int):
rebalance = Extensions.ToTimeSpan(rebalance)
if isinstance(rebalance, timedelta):
rebalancingFunc = lambda dt: dt + rebalance
if rebalancingFunc:
self.SetRebalancingFunc(rebalancingFunc)
ag.Debug(f'pcm init rebalancingFunc')
self.ag = ag
def DetermineTargetPercent(self, activeInsights):
'''Will determine the target percent for each insight
Args:
activeInsights: The active insights to generate a target for'''
# self.ag.Debug(f'{self.ag.Time} pcm-1 triggered at {self.ag.Time}')
# if self.ag.Time < self.rebalanceTime:
# return {}
self.ag.Debug(f'{self.ag.Time} pcm triggered at {self.ag.Time} utc{self.ag.UtcTime}')
# Set the rebalance time to match the insight expiry
self.rebalanceTime = Expiry.EndOfQuarter(self.ag.Time)
result = {}
# give equal weighting to each security
count = sum(x.Direction != InsightDirection.Flat and self.RespectPortfolioBias(x) for x in activeInsights)
percent = 0 if count == 0 else 1.0 / count
for insight in activeInsights:
result[insight] = (insight.Direction if self.RespectPortfolioBias(insight) else InsightDirection.Flat) * percent
return result
def RespectPortfolioBias(self, insight):
'''Method that will determine if a given insight respects the portfolio bias
Args:
insight: The insight to create a target for
'''
return self.portfolioBias == PortfolioBias.LongShort or insight.Direction == self.portfolioBias
class FundamentalFactorAlphaModel(AlphaModel):
def __init__(self):
self.rebalanceTime = datetime.min
# Dictionary containing set of securities in each sector
# e.g. {technology: set(AAPL, TSLA, ...), healthcare: set(XYZ, ABC, ...), ... }
self.sectors = {}
def Update(self, algorithm, data):
'''Updates this alpha model with the latest data from the algorithm.
This is called each time the algorithm receives data for subscribed securities
Args:
algorithm: The algorithm instance
data: The new data available
Returns:
New insights'''
if algorithm.Time < self.rebalanceTime:
return []
# Set the rebalance time to match the insight expiry
self.rebalanceTime = Expiry.EndOfQuarter(algorithm.Time)
algorithm.Debug(f'{algorithm.Time} alpha rebalanceTime {self.rebalanceTime}')
insights = []
for sector in self.sectors:
securities = self.sectors[sector]
sortedByROE = sorted(securities, key=lambda x: x.Fundamentals.OperationRatios.ROE.Value, reverse=True)
sortedByPM = sorted(securities, key=lambda x: x.Fundamentals.OperationRatios.NetMargin.Value, reverse=True)
sortedByPE = sorted(securities, key=lambda x: x.Fundamentals.ValuationRatios.PERatio, reverse=False)
# Dictionary holding a dictionary of scores for each security in the sector
scores = {}
for security in securities:
score = sum([sortedByROE.index(security), sortedByPM.index(security), sortedByPE.index(security)])
scores[security] = score
# Add best 20% of each sector to longs set (minimum 1)
length = max(int(len(scores)/5), 1)
for security in sorted(scores.items(), key=lambda x: x[1], reverse=False)[:length]:
symbol = security[0].Symbol
# Use Expiry.EndOfQuarter in this case to match Universe, Alpha and PCM
insights.append(Insight.Price(symbol, Expiry.EndOfQuarter, InsightDirection.Up))
return insights
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'''
# Remove security from sector set
for security in changes.RemovedSecurities:
for sector in self.sectors:
if security in self.sectors[sector]:
self.sectors[sector].remove(security)
# Add security to corresponding sector set
for security in changes.AddedSecurities:
sector = security.Fundamentals.AssetClassification.MorningstarSectorCode
if sector not in self.sectors:
self.sectors[sector] = set()
self.sectors[sector].add(security)