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