Overall Statistics |
Total Trades 77 Average Win 3.53% Average Loss -3.60% Compounding Annual Return 3.359% Drawdown 43.000% Expectancy -0.065 Net Profit 2.902% Sharpe Ratio 0.345 Probabilistic Sharpe Ratio 24.205% Loss Rate 53% Win Rate 47% Profit-Loss Ratio 0.98 Alpha 0.177 Beta 0.033 Annual Standard Deviation 0.53 Annual Variance 0.28 Information Ratio 0.022 Tracking Error 0.615 Treynor Ratio 5.572 Total Fees $446.79 |
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 Test(QCAlgorithm): def Initialize(self): ''' Backtesting Parameters ''' self.SetStartDate(2020, 1, 1) # self.SetEndDate(2014, 1, 1) self.SetCash(30000) ''' Universe Settings ''' self.benchmark = Symbol.Create("SPY", SecurityType.Equity, Market.USA) self.UniverseSettings.Resolution = Resolution.Daily self.UniverseSettings.DataNormalizationMode = DataNormalizationMode.Raw self.AddUniverse(self.CoarseSelectionFunction, self.FineSelectionFunction) ''' Minor Parameters ''' self.month = -1 self.leverage = 2 self.leniency = .95 ''' Parameters with meaning ''' self.volumeMin = 250000 # Ensures we are looking at liquid stocks self.piotroskiMin = 5 # The higher the better (6, 7, 8, 9) leaves us with the top 40% best quality companies self.minuteBeforeEOD = 60 # Ensure we have enough time to sell before the end of the day self.priceMin = 5 # Ensures we do not get the stocks with extremely high risk (Penny stocks) self.sectorHistory = 21 # Performance last iteration/month self.stockHistory = 21 # Performance last iteration/month ''' Parameters without meaning ''' self.monthlyStocks = 6 ''' Schedule Settings ''' self.AddEquity("SPY", Resolution.Minute) self.SetBenchmark("SPY") self.Schedule.On(self.DateRules.EveryDay("SPY"), self.TimeRules.AfterMarketOpen("SPY"), Action(self.Daily)) self.Schedule.On(self.DateRules.MonthEnd("SPY"), self.TimeRules.BeforeMarketClose("SPY", self.minuteBeforeEOD), self.Liquidate) ''' Data Structures ''' self.symbols, self.topSectors,self.sectorPerformances = [], [], {} ''' Sectors ''' 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 } def CoarseSelectionFunction(self, coarse): if self.month != self.Time.month: self.month = self.Time.month sortedCoarse = [x for x in coarse if x.HasFundamentalData and x.Volume > self.volumeMin and x.Price > self.priceMin] self.topSectors = [] self.sectorPerformances = {} for x in self.sectorStocks: key = Symbol.Create(x, SecurityType.Equity, Market.USA) history = self.History(key, self.sectorHistory, Resolution.Daily) # Parameter (1) # self.Debug(history) if history.empty: continue first = history.head(1)['close'].iloc[0] last = history.tail(1)['open'].iloc[0] self.sectorPerformances[self.sectorStocks[x]] = (last-first)/last self.topSectors = [x for x in self.sectorPerformances if self.sectorPerformances[x] > 0] return [x.Symbol for x in sortedCoarse] else: return Universe.Unchanged 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 and x.EarningReports.BasicAverageShares.ThreeMonths and x.EarningReports.BasicEPS.TwelveMonths and x.ValuationRatios.PayoutRatio > 0] 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 ] sortedBySector = [x for x in sortedByFScore if x.AssetClassification.MorningstarSectorCode in self.topSectors] self.symbols = [i.Symbol for i in sortedBySector] return self.symbols def Daily(self): if self.month == self.Time.month and not self.Portfolio.Invested: self.Rebalance() def Rebalance(self): filterByPrice = [x for x in self.symbols if self.ActiveSecurities[x].Price > 0] sortByPrice = sorted(filterByPrice, key=lambda x: (self.ActiveSecurities[x].Price), reverse = False) buyingPower = (self.Portfolio.MarginRemaining / (self.monthlyStocks/self.leverage)) * self.leniency if buyingPower > 0: for symbol in sortByPrice: if self.Portfolio.MarginRemaining >= buyingPower: history = self.History(symbol, self.stockHistory, Resolution.Daily) if history.empty: return pOpen = history.head(1)['open'].iloc[0] pClose = history.tail(1)['close'].iloc[0] if pClose > pOpen: 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