Overall Statistics |
Total Trades 4327 Average Win 1.02% Average Loss -0.39% Compounding Annual Return 66.926% Drawdown 54.800% Expectancy 0.322 Net Profit 364.479% Sharpe Ratio 1.413 Probabilistic Sharpe Ratio 53.902% Loss Rate 64% Win Rate 36% Profit-Loss Ratio 2.63 Alpha 0.778 Beta -0.101 Annual Standard Deviation 0.541 Annual Variance 0.292 Information Ratio 1.068 Tracking Error 0.586 Treynor Ratio -7.578 Total Fees $19937.62 |
from System.Collections.Generic import List from QuantConnect.Data.UniverseSelection import * import operator from math import ceil,floor from scipy import stats import numpy as np from datetime import timedelta class Piotroski(QCAlgorithm): '''----------------''' # Backtesting parameters ''' Initialization ''' # Creates EMA indicators for sectors '''----------------''' def Initialize(self): self.SetStartDate(2009, 1, 1) self.SetEndDate(2012, 1, 1) self.SetCash(30000) self.UniverseSettings.Resolution = Resolution.Daily self.UniverseSettings.DataNormalizationMode = DataNormalizationMode.Raw self.AddUniverse(self.CoarseSelectionFunction, self.FineSelectionFunction) self.maxStocks = 30 self.volumeMin = 100000 self.piotroskiMin = 5 self.inMarket = True self.AddEquity("TMF", Resolution.Daily) self.symbols, self.topSectors = [], [] self.sectorFast, self.sectorSlow = {}, {} self.sectorStocks = {"XLV": MorningstarSectorCode.Healthcare, "XLK": MorningstarSectorCode.Technology, "XLI": MorningstarSectorCode.Industrials, "XLU": MorningstarSectorCode.Utilities, "XLF": MorningstarSectorCode.FinancialServices, "XLP": MorningstarSectorCode.ConsumerDefensive, "XLY": MorningstarSectorCode.ConsumerCyclical, "XLB": MorningstarSectorCode.BasicMaterials, "XLE": MorningstarSectorCode.Energy, "PSR": MorningstarSectorCode.RealEstate, "IYZ": MorningstarSectorCode.CommunicationServices} for x in self.sectorStocks: symbol = Symbol.Create(x, SecurityType.Equity, Market.USA) self.AddEquity(symbol, Resolution.Daily) self.sectorFast[x] = self.EMA(symbol, 5, Resolution.Daily) self.sectorSlow[x] = self.EMA(symbol, 13, Resolution.Daily) self.SetWarmUp(timedelta(30)) '''------------------''' ''' Helper Functions ''' '''------------------''' def Length(self): invested = [x.Key for x in self.Portfolio if x.Value.Invested] return len(invested) '''-----------------''' # Is only updated if we do not have all our holdings allocated/stocks have been sold. ''' Coarse Universe ''' # Otherwise the universe is unchanged and not calculated to decrease workload. '''-----------------''' # Also determines which sectors are in an uptrend def CoarseSelectionFunction(self, coarse): if self.Length() < self.maxStocks: sortedCoarse = [x for x in coarse if x.HasFundamentalData and x.Volume > self.volumeMin] self.topSectors = [] for x in self.sectorStocks: if self.sectorFast[x].IsReady and self.sectorSlow[x].IsReady: if self.sectorFast[x].Current.Value > self.sectorSlow[x].Current.Value: self.topSectors.append(self.sectorStocks[x]) return [x.Symbol for x in sortedCoarse] else: return Universe.Unchanged '''---------------''' # Filters companies that are of quality (Piotroski Score) ''' Fine Universe ''' # Filters companies that are within the uptrending sectors '''---------------''' # Sorts by descending PE Ratio to find undervalued stocks def FineSelectionFunction(self, fine): filteredFine = [x for x in fine if x.FinancialStatements.IncomeStatement.NetIncome.TwelveMonths and x.FinancialStatements.CashFlowStatement.CashFlowFromContinuingOperatingActivities.TwelveMonths and x.OperationRatios.ROA.ThreeMonths and x.OperationRatios.ROA.OneYear and x.FinancialStatements.BalanceSheet.ShareIssued.ThreeMonths and x.FinancialStatements.BalanceSheet.ShareIssued.TwelveMonths and x.OperationRatios.GrossMargin.ThreeMonths and x.OperationRatios.GrossMargin.OneYear and x.OperationRatios.LongTermDebtEquityRatio.ThreeMonths and x.OperationRatios.LongTermDebtEquityRatio.OneYear and x.OperationRatios.CurrentRatio.ThreeMonths and x.OperationRatios.CurrentRatio.OneYear and x.OperationRatios.AssetsTurnover.ThreeMonths and x.OperationRatios.AssetsTurnover.OneYear and x.ValuationRatios.NormalizedPERatio] sortedByFScore = [x for x in filteredFine if FScore(x.FinancialStatements.IncomeStatement.NetIncome.TwelveMonths, x.FinancialStatements.CashFlowStatement.CashFlowFromContinuingOperatingActivities.TwelveMonths, x.OperationRatios.ROA.ThreeMonths, x.OperationRatios.ROA.OneYear, x.FinancialStatements.BalanceSheet.ShareIssued.ThreeMonths, x.FinancialStatements.BalanceSheet.ShareIssued.TwelveMonths, x.OperationRatios.GrossMargin.ThreeMonths, x.OperationRatios.GrossMargin.OneYear, x.OperationRatios.LongTermDebtEquityRatio.ThreeMonths, x.OperationRatios.LongTermDebtEquityRatio.OneYear, x.OperationRatios.CurrentRatio.ThreeMonths, x.OperationRatios.CurrentRatio.OneYear, x.OperationRatios.AssetsTurnover.ThreeMonths, x.OperationRatios.AssetsTurnover.OneYear).ObjectiveScore() > self.piotroskiMin ] sortBySector = [x for x in sortedByFScore if x.AssetClassification.MorningstarSectorCode in self.topSectors and x.MarketCap < 2000000000] sortByPERatio = sorted(sortBySector, key=lambda x: x.ValuationRatios.PERatio , reverse = False) uptrend = [] stocksNeeded = self.maxStocks - self.Length() for x in sortByPERatio: symbol = x.Symbol history = self.History(symbol, 13, Resolution.Daily) history['fast'] = history['close'].ewm(span = 5, min_periods = 0, adjust = False, ignore_na = False).mean() history['slow'] = history['close'].ewm(span = 13, min_periods = 0, adjust = False, ignore_na = False).mean() slow = history.iloc[len(history.index)-1]['slow'] fast = history.iloc[len(history.index)-1]['fast'] if fast > slow: uptrend.append(symbol) if len(uptrend) >= stocksNeeded: break self.symbols = uptrend return self.symbols '''--------''' # Sells any held stocks that are no longer in an uptrend ''' OnData ''' # Buys any stocks if we have available stocks slots/unallocated stocks '''--------''' def OnData(self, data): ''' Buying ''' if self.Length() < self.maxStocks and len(self.symbols) > 0: self.inMarket = True buyingPower = (self.Portfolio.MarginRemaining * 2)/len(self.symbols) * .995 for symbol in self.symbols: orderSize = buyingPower / self.ActiveSecurities[symbol].Price self.MarketOrder(symbol, orderSize) if len(self.symbols) <= 0: self.Liquidate ''' Selling ''' for holdings in self.Portfolio.Values: symbol = holdings.Symbol if holdings.Invested: history = self.History(symbol, 13, Resolution.Daily) history['fast'] = history['close'].ewm(span = 5, min_periods = 0, adjust = False, ignore_na = False).mean() history['slow'] = history['close'].ewm(span = 13, min_periods = 0, adjust = False, ignore_na = False).mean() slow = history.iloc[len(history.index)-1]['slow'] fast = history.iloc[len(history.index)-1]['fast'] if fast < slow: self.Liquidate(symbol) '''-----------------------------''' ''' Piotroski Calculating Class ''' '''-----------------------------''' class FScore(object): def __init__(self, netincome, operating_cashflow, roa_current, roa_past, issued_current, issued_past, grossm_current, grossm_past, longterm_current, longterm_past, curratio_current, curratio_past, assetturn_current, assetturn_past): self.netincome = netincome self.operating_cashflow = operating_cashflow self.roa_current = roa_current self.roa_past = roa_past self.issued_current = issued_current self.issued_past = issued_past self.grossm_current = grossm_current self.grossm_past = grossm_past self.longterm_current = longterm_current self.longterm_past = longterm_past self.curratio_current = curratio_current self.curratio_past = curratio_past self.assetturn_current = assetturn_current self.assetturn_past = assetturn_past def ObjectiveScore(self): ''' The Piotroski score is broken down into profitability; leverage, liquidity, and source of funds; and operating efficiency categories, as follows: ''' fscore = 0 ''' Profitability Criteria ''' fscore += np.where(self.netincome > 0, 1, 0) # Positive Net Income (X Months?) fscore += np.where(self.operating_cashflow > 0, 1, 0) # Positive Operating Cash Flow fscore += np.where(self.roa_current > self.roa_past, 1, 0) # Positive Return on Assets fscore += np.where(self.operating_cashflow > self.roa_current, 1, 0) # Cash flow from operations being greater than net income (quality of earnings) ''' Leverage, Liquidity, and Source of Dunds Criteria ''' fscore += np.where(self.longterm_current <= self.longterm_past, 1, 0) # Lower ratio of long term debt in the current period, compared to the previous year (decreased leverage) fscore += np.where(self.curratio_current >= self.curratio_past, 1, 0) # Higher current ratio this year compared to the previous year (more liquidity) fscore += np.where(self.issued_current <= self.issued_past, 1, 0) # No new shares were issued in the last year ''' Operating Efficiency Criteria ''' # A higher gross margin compared to the previous year fscore += np.where(self.grossm_current >= self.grossm_past, 1, 0) # A higher gross margin compared to the previous year fscore += np.where(self.assetturn_current >= self.assetturn_past, 1, 0) # A higher asset turnover ratio compared to the previous year (1 point) return fscore