Overall Statistics
Total Trades
90
Average Win
0%
Average Loss
0%
Compounding Annual Return
-15.343%
Drawdown
33.600%
Expectancy
0
Net Profit
-15.343%
Sharpe Ratio
-0.781
Probabilistic Sharpe Ratio
2.100%
Loss Rate
0%
Win Rate
0%
Profit-Loss Ratio
0
Alpha
-0.107
Beta
-0.07
Annual Standard Deviation
0.152
Annual Variance
0.023
Information Ratio
-1.088
Tracking Error
0.259
Treynor Ratio
1.701
Total Fees
$0.00
# This backtest is designed to test Benjamin Graham's Net Current Asset Value strategy
# Also commonly known as the Net Net strategy

from Execution.ImmediateExecutionModel import ImmediateExecutionModel
from Portfolio.EqualWeightingPortfolioConstructionModel import EqualWeightingPortfolioConstructionModel

class CustomFeeModel:
    def GetOrderFee(self, parameters):
        return OrderFee(CashAmount(0, 'USD'))

class NetNet(QCAlgorithm):

    def Initialize(self):
        self.SetStartDate(1998, 4, 1)
        self.SetEndDate(1999, 3, 31)
        self.SetCash(100000)
        
        self.SetUniverseSelection(FineFundamentalUniverseSelectionModel(self.CoarseSelectionFunction, self.FineSelectionFunction, None, None))
        self.UniverseSettings.Resolution = Resolution.Daily
        self.SetSecurityInitializer(lambda x: x.SetDataNormalizationMode(DataNormalizationMode.Raw))
        self.SetSecurityInitializer(lambda x: x.SetFeeModel(CustomFeeModel()))
        
        self.SetAlpha(NetNetAlpha())
        
        # By default the EqualWeightingPortfolioConstructionModel rebalances with the specified resolution (which is Resolution.Daily here).
        # To avoid this, just pass "lambda time: None" to your PortfolioConstructionModel and use:
        #   self.Settings.RebalancePortfolioOnInsightChanges = True
        #   self.Settings.RebalancePortfolioOnSecurityChanges = False
        # to make sure the algorithm will rebalance only when there were changes in your universe.
        self.SetPortfolioConstruction(EqualWeightingPortfolioConstructionModel(lambda time: None))
        self.Settings.RebalancePortfolioOnInsightChanges = True
        self.Settings.RebalancePortfolioOnSecurityChanges = False
        
        self.SetExecution(ImmediateExecutionModel())


    # on 1 Apr, filter for securities with fundamental data
    def CoarseSelectionFunction(self, coarse):
        if self.Time.month == 4 and self.Time.day == 1:
            filtered = [ x.Symbol for x in coarse if x.HasFundamentalData ]
            return filtered
        else:
            return Universe.Unchanged
    
    
    # the two filters are for avoiding zero division errors
    def FineSelectionFunction(self, fine):
        # filter first for securities with positive NCAV and share count    
        filtered = [ x for x in fine if ((x.FinancialStatements.BalanceSheet.CurrentAssets.ThreeMonths - x.FinancialStatements.BalanceSheet.TotalLiabilitiesAsReported.ThreeMonths - x.FinancialStatements.BalanceSheet.PreferredStock.ThreeMonths) > 0) and (x.FinancialStatements.BalanceSheet.OrdinarySharesNumber.ThreeMonths > 0) ]
        
        # then filter a second time for net net stocks
        filtered = [ x.Symbol for x in filtered if (x.Price / ((x.FinancialStatements.BalanceSheet.CurrentAssets.ThreeMonths - x.FinancialStatements.BalanceSheet.TotalLiabilitiesAsReported.ThreeMonths - x.FinancialStatements.BalanceSheet.PreferredStock.ThreeMonths) / x.FinancialStatements.BalanceSheet.OrdinarySharesNumber.ThreeMonths)) <= 0.66 ]
    
        return filtered


class NetNetAlpha(AlphaModel):
    def Update(self, algorithm, data):
        insights = []
        
        if not algorithm.Portfolio.Invested:
            for security in algorithm.ActiveSecurities.Values:
                insights.append(Insight.Price(security.Symbol, timedelta(days=1000), InsightDirection.Up))
            return insights
        else:
            return insights