Overall Statistics
Total Trades
39440
Average Win
0.01%
Average Loss
-0.01%
Compounding Annual Return
-3.207%
Drawdown
24.300%
Expectancy
-0.064
Net Profit
-16.645%
Sharpe Ratio
-0.302
Probabilistic Sharpe Ratio
0.007%
Loss Rate
45%
Win Rate
55%
Profit-Loss Ratio
0.69
Alpha
-0.018
Beta
-0.022
Annual Standard Deviation
0.066
Annual Variance
0.004
Information Ratio
-0.624
Tracking Error
0.17
Treynor Ratio
0.897
Total Fees
$125321.13
Estimated Strategy Capacity
$5000.00
Lowest Capacity Asset
HHS R735QTJ8XC9X
#region imports
from AlgorithmImports import *
#endregion
# https://quantpedia.com/Screener/Details/199
class ROAEffectWithinStocks(QCAlgorithm):

    def Initialize(self):
        self.SetStartDate(2015, 1, 1)  
        self.SetEndDate(2020, 8, 1)  
        self.SetCash(10000000) 

        self.UniverseSettings.Resolution = Resolution.Daily
        self.AddUniverse(self.CoarseSelectionFunction, self.FineSelectionFunction)
        self.AddEquity("SPY", Resolution.Daily)
        
        self.Schedule.On(self.DateRules.MonthStart("SPY"), self.TimeRules.AfterMarketOpen("SPY"), self.Rebalance)
       
        self.monthly_rebalance = False 
        self.coarse = False
        
    def CoarseSelectionFunction(self, coarse):
        if self.monthly_rebalance:
            self.coarse = True
            filteredCoarse = [x.Symbol for x in coarse if x.HasFundamentalData]
            return filteredCoarse

        else:
            return Universe.Unchanged
    
    def FineSelectionFunction(self, fine):
        if self.monthly_rebalance:
            fine =[i for i in fine if i.EarningReports.BasicAverageShares.ThreeMonths != 0
                                  and i.EarningReports.BasicEPS.TwelveMonths != 0
                                  and i.ValuationRatios.PERatio != 0
                                  # sales is greater than 10 million
                                  and i.ValuationRatios.SalesPerShare*i.EarningReports.DilutedAverageShares.Value > 10000000
                                  and i.OperationRatios.ROA.Value != 0]
            
            # sort into 2 halfs based on market capitalization
            sorted_market_cap = sorted(fine, key = lambda x:x.MarketCap, reverse=True)
            top = sorted_market_cap[:int(len(sorted_market_cap)*0.5)]
            bottom = sorted_market_cap[-int(len(sorted_market_cap)*0.5):]
            
            # each half is then divided into deciles based on Return on Assets (ROA)
            sortedTopByROA = sorted(top, key = lambda x: x.OperationRatios.ROA.Value, reverse = True)
            sortedBottomByROA = sorted(bottom, key = lambda x: x.OperationRatios.ROA.Value, reverse = True)
            
            # long top decile from each market capitalization group 
            long_ = sortedTopByROA[:int(len(sortedTopByROA)*0.1)] + sortedBottomByROA[:int(len(sortedTopByROA)*0.1)]
            self.longStocks = [i.Symbol for i in long_]
            
            # short bottom decile from each market capitalization group 
            short = sortedTopByROA[-int(len(sortedTopByROA)*0.1):] + sortedBottomByROA[-int(len(sortedTopByROA)*0.1):]
            self.shortStocks = [i.Symbol for i in short]
            
            return self.longStocks + self.shortStocks
        else:
            return Universe.Unchanged
    
    def Rebalance(self):
        self.monthly_rebalance = True
            
    def OnData(self, data):
        if not (self.monthly_rebalance and self.coarse): return

        self.coarse = False
        self.monthly_rebalance = False

        stocks_invested = [x.Key for x in self.Portfolio if x.Value.Invested]
        for i in stocks_invested:
            if i not in self.longStocks+self.shortStocks:
                self.Liquidate(i)

        long_weight = 0.5/len(self.longStocks)
        for i in self.longStocks:
            self.SetHoldings(i, long_weight)

        short_weight = 0.5/len(self.shortStocks)
        for i in self.shortStocks:
            self.SetHoldings(i, -short_weight)