Overall Statistics |
Total Trades 10822 Average Win 0.07% Average Loss -0.03% Compounding Annual Return 36.170% Drawdown 25.700% Expectancy 0.375 Net Profit 90.994% Sharpe Ratio 1.466 Probabilistic Sharpe Ratio 66.566% Loss Rate 55% Win Rate 45% Profit-Loss Ratio 2.06 Alpha 0.358 Beta -0.178 Annual Standard Deviation 0.218 Annual Variance 0.048 Information Ratio 0.31 Tracking Error 0.342 Treynor Ratio -1.803 Total Fees $11900.68 Estimated Strategy Capacity $56000.00 Lowest Capacity Asset LBTYB SZC2UFSQNK9X |
from Execution.ImmediateExecutionModel import ImmediateExecutionModel from Portfolio.EqualWeightingPortfolioConstructionModel import InsightWeightingPortfolioConstructionModel from Portfolio.EqualWeightingPortfolioConstructionModel import EqualWeightingPortfolioConstructionModel from Risk.MaximumDrawdownPercentPerSecurity import MaximumDrawdownPercentPerSecurity from Risk.TrailingStopRiskManagementModel import TrailingStopRiskManagementModel from AlphaModel import FundamentalFactorAlphaModel class VerticalTachyonRegulators(QCAlgorithm): def Initialize(self): self.SetStartDate(2019, 7, 1) #self.SetEndDate(2021, 8, 1) self.startCash = 100000 self.SetCash(self.startCash) self.averages = {} self.slowPeriod = int(self.GetParameter("slowPeriod")) if self.slowPeriod is None: self.slowPeriod = 160 self.fastPeriod = int(self.GetParameter("fastPeriod")) if self.fastPeriod is None: self.fastPeriod = 15 # Benchmark #self.bench = self.AddEquity("SPY", Resolution.Daily).Symbol #self.benchmark = self.AddEquity("SPY", Resolution.Daily) #self.benchStartPrice = self.benchmark.Price #self.SetBenchmark(self.bench) # Execution model self.SetExecution(ImmediateExecutionModel()) # Portfolio construction model self.SetPortfolioConstruction(InsightWeightingPortfolioConstructionModel()) #self.SetPortfolioConstruction(EqualWeightingPortfolioConstructionModel()) # Risk model stopRisk = self.GetParameter("stopRisk") if stopRisk is None: stopRisk = 0.25 self.SetRiskManagement(TrailingStopRiskManagementModel(float(stopRisk))) #self.SetRiskManagement(MaximumDrawdownPercentPerSecurity(float(stopRisk))) # Universe selection self.num_coarse = 4000 self.num_fine = 30 self.UniverseSettings.Leverage = 1 self.UniverseSettings.Resolution = Resolution.Daily #self.UniverseSettings.MinimumTimeInUniverse = timedelta(days=30) self.AddUniverse(self.CoarseSelectionFunction, self.FineSelectionFunction) # rebalancing self.lastMonth = -1 # Set factor weighting quality_weight = self.GetParameter("quality_weight") if quality_weight is None: quality_weight = 15 size_weight = self.GetParameter("size_weight") if size_weight is None: size_weight = 1 value_weight = self.GetParameter("value_weight") if value_weight is None: value_weight = 1 # Alpha Model self.AddAlpha(FundamentalFactorAlphaModel(self.num_fine, quality_weight, value_weight, size_weight)) # schedule weekly plotting self.Schedule.On(self.DateRules.Every(DayOfWeek.Monday), self.TimeRules.At(10, 30), self.Plotting) def Plotting(self): self.Plot("Positions", "Num", len([x.Symbol for x in self.Portfolio.Values if self.Portfolio[x.Symbol].Invested])) #benchPerformance = self.benchmark.Price / self.benchStartPrice #self.Plot("Performance", "Performance", benchPerformance) def CoarseSelectionFunction(self, coarse): selector = [] # If not time to rebalance, keep the same universe if self.Time.month == self.lastMonth: return Universe.Unchanged # Else reassign the month variable 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) # Only add uptrending securities (EMA based) for finer in selected: symbol = finer.Symbol #self.Debug(symbol) if symbol not in self.averages: # Call history to get an array of X days of history data history = self.History(symbol, self.slowPeriod, Resolution.Daily) #Pass in the history result to SelectionData self.averages[symbol] = SelectionData(history, self.slowPeriod, self.fastPeriod) self.averages[symbol].update(self.Time, finer.AdjustedPrice) if self.averages[symbol].is_ready() and self.averages[symbol].fast > self.averages[symbol].slow: selector.append(finer) return [x.Symbol for x in selector[:self.num_coarse]] def FineSelectionFunction(self, fine): # Filter the fine data for equities with non-zero/non-null values filtered_fine = [x.Symbol for x in fine if x.OperationRatios.ROE.SixMonths > 0.05 and x.ValuationRatios.PriceChange1M > 0 and x.ValuationRatios.FCFYield > 0 and x.MarketCap > 0] return filtered_fine class SelectionData(): def __init__(self, history, slowPeriod, fastPeriod): #Save the fast and slow ExponentialMovingAverage self.slow = ExponentialMovingAverage(slowPeriod) self.fast = ExponentialMovingAverage(fastPeriod) #Loop over the history data and update the indicators for bar in history.itertuples(): self.fast.Update(bar.Index[1], bar.close) self.slow.Update(bar.Index[1], bar.close) #Check if our indicators are ready def is_ready(self): return self.slow.IsReady and self.fast.IsReady #Use the "indicator.Update" method to update the time and price of both indicators def update(self, time, price): self.fast.Update(time, price) self.slow.Update(time, price)
from datetime import timedelta class FundamentalFactorAlphaModel(AlphaModel): def __init__(self, num_fine, quality_weight, value_weight, size_weight): # Initialize the various variables/helpers we'll need self.lastMonth = -1 self.longs = [] self.num_fine = num_fine self.period = timedelta(31) # normalize quality, value, size weights weights = [float(quality_weight), float(value_weight), float(size_weight)] weights = [float(i)/sum(weights) for i in weights] self.quality_weight = weights[0] self.value_weight = weights[1] self.size_weight = weights[2] 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 newa available Returns: New insights''' # Return no insights if it's not time to rebalance if algorithm.Time.month == self.lastMonth: return [] self.lastMonth = algorithm.Time.month # List of insights # Insights of the form: Insight(symbol, timedelta, type, direction, magnitude, confidence, sourceModel, weight) insights = [] # Close old positions if they aren't in longs for security in algorithm.Portfolio.Values: if security.Invested and security.Symbol not in self.longs: insights.append(Insight(security.Symbol, self.period, InsightType.Price, InsightDirection.Flat, None, None, None, None)) length = len(self.longs) for i in range(length): insights.append(Insight(self.longs[i], self.period, InsightType.Price, InsightDirection.Up, None, (length - i)**2, None, (length - i)**2 )) 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''' # Get the added securities addedSecurities = [x for x in changes.AddedSecurities] algorithm.Debug(len(addedSecurities)) if len(addedSecurities) == 0: return # Assign quality, value, size score to each stock quality_scores = self.Scores(addedSecurities, [(lambda x: x.Fundamentals.OperationRatios.ROE.SixMonths, True, 1)]) #(lambda x: x.Fundamentals.OperationRatios.QuickRatio.Value, True, 1)]) #(lambda x: x.Fundamentals.OperationRatios.DebttoAssets.Value, False, 1)]) value_scores = self.Scores(addedSecurities, [(lambda x: x.Fundamentals.ValuationRatios.FCFYield, True, 1)]) # larger caps = True, smaller caps = False size_scores = self.Scores(addedSecurities, [(lambda x: x.Fundamentals.MarketCap, False, 1)]) scores = {} # Assign a combined score to each stock for symbol,value in quality_scores.items(): quality_rank = value value_rank = value_scores[symbol] size_rank = size_scores[symbol] scores[symbol] = quality_rank*self.quality_weight + value_rank*self.value_weight + size_rank*self.size_weight # Sort the securities by their scores sorted_stock = sorted(scores.items(), key=lambda tup : tup[1], reverse=False) sorted_symbol = [tup[0] for tup in sorted_stock][:self.num_fine] # Sort the top stocks into the long_list self.longs = [security.Symbol for security in sorted_symbol] # Log longs symbols and their score #algorithm.Log(", ".join([str(x.Symbol.Value) + ": " + str(scores[x]) for x in sorted_symbol])) def Scores(self, addedSecurities, fundamentals): '''Assigns scores to each stock in added Args: added: list of sceurities fundamentals: list of 3-tuples (lambda function, bool, float) Returns: Dictionary with score for each security''' length = len(fundamentals) if length == 0: return {} # Initialize helper variables scores = {} sortedBy = [] rank = [0 for _ in fundamentals] # Normalize weights weights = [tup[2] for tup in fundamentals] weights = [float(i)/sum(weights) for i in weights] # Create sorted list for each fundamental factor passed for tup in fundamentals: sortedBy.append(sorted(addedSecurities, key=tup[0], reverse=tup[1])) # Create and save score for each symbol for index,symbol in enumerate(sortedBy[0]): # Save symbol's rank for each fundamental factor rank[0] = index for j in range(1, length): rank[j] = sortedBy[j].index(symbol) # Save symbol's total score score = 0 for i in range(length): score += rank[i] * weights[i] scores[symbol] = score return scores