Overall Statistics |
Total Trades 775 Average Win 9.78% Average Loss -3.42% Compounding Annual Return 353.387% Drawdown 88.100% Expectancy 0.972 Net Profit 593196.953% Sharpe Ratio 3.954 Probabilistic Sharpe Ratio 39.961% Loss Rate 49% Win Rate 51% Profit-Loss Ratio 2.86 Alpha 9.729 Beta 1.894 Annual Standard Deviation 2.509 Annual Variance 6.296 Information Ratio 3.938 Tracking Error 2.494 Treynor Ratio 5.238 Total Fees $6433305.70 |
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): def Initialize(self): ''' Backtesting Parameters ''' self.SetStartDate(2015, 1, 1) # self.SetEndDate(2015, 1, 1) self.SetCash(50000) ''' Universe Settings ''' self.benchmark = Symbol.Create("SPY", SecurityType.Equity, Market.USA) self.UniverseSettings.Resolution = Resolution.Minute self.UniverseSettings.DataNormalizationMode = DataNormalizationMode.Raw self.AddUniverse(self.CoarseSelectionFunction, self.FineSelectionFunction) ''' Schedule Settings ''' self.AddEquity("SPY", Resolution.Minute) self.SetBenchmark("SPY") self.Schedule.On(self.DateRules.MonthEnd("SPY"), self.TimeRules.BeforeMarketClose("SPY", 10), self.Liquidate) self.Schedule.On(self.DateRules.MonthStart("SPY"), self.TimeRules.AfterMarketOpen("SPY"), Action(self.Rebalance)) self.Schedule.On(self.DateRules.EveryDay("SPY"), self.TimeRules.AfterMarketOpen("SPY", 2), Action(self.Daily)) ''' Other Settings ''' self.month = -1 self.symbols = [] self.initiated = -1 def CoarseSelectionFunction(self, coarse): if self.month != self.Time.month: sortedCoarse = [x for x in coarse if x.HasFundamentalData and x.Price > 5] sortedDollarVolume = sorted(sortedCoarse, key=lambda x: x.DollarVolume, reverse=True) topCoarse = sortedDollarVolume return [x.Symbol for x in topCoarse] else: return self.symbols def FineSelectionFunction(self, fine): if self.month != self.Time.month: self.month = self.Time.month ''' Retrieve all stocks that have the valid variation ratios that we want ''' 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 and x.EarningReports.BasicAverageShares.ThreeMonths and x.EarningReports.BasicEPS.TwelveMonths and x.ValuationRatios.PayoutRatio > 0] ''' Using the FScore class, retrieve the stocks that have a score of X or higher ''' 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() > 6 ] ''' The Piotroski score ranks quality stocks, but we still need to determine value by filtering it more ''' sortedByNormalizedPE = sorted(sortedByFScore, key=lambda x: (x.ValuationRatios.NormalizedPERatio), reverse = False) topFine = sortedByNormalizedPE self.symbols = [i.Symbol for i in topFine] return self.symbols else: return self.symbols def Daily(self): if self.initiated < 0: self.Rebalance() self.initiated = 1 def Rebalance(self): filterByPrice = [x for x in self.symbols if self.Securities[x].Price > 0] sortByPrice = sorted(filterByPrice, key=lambda x: (self.Securities[x].Price), reverse = False) ''' Invest in the selected symbols ''' buyingPower = (self.Portfolio.MarginRemaining / 3) * .995 if buyingPower > 0: for symbol in sortByPrice: if self.Securities[symbol].Price > 0: orderSize = buyingPower / self.Securities[symbol].Price self.MarketOrder(symbol, orderSize) else: self.Log("Insufficient Buying Power: " + str(self.Portfolio.MarginRemaining)) 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