Overall Statistics
Total Trades
170
Average Win
0.56%
Average Loss
-0.77%
Compounding Annual Return
32.353%
Drawdown
54.400%
Expectancy
-0.472
Net Profit
121.400%
Sharpe Ratio
0.635
Probabilistic Sharpe Ratio
4.998%
Loss Rate
69%
Win Rate
31%
Profit-Loss Ratio
0.73
Alpha
0.828
Beta
0.026
Annual Standard Deviation
1.309
Annual Variance
1.713
Information Ratio
0.562
Tracking Error
1.314
Treynor Ratio
31.753
Total Fees
$316.14
from Execution.ImmediateExecutionModel import ImmediateExecutionModel
from Portfolio.EqualWeightingPortfolioConstructionModel import EqualWeightingPortfolioConstructionModel
from datetime import datetime

class NetNet(QCAlgorithm):

    def Initialize(self):
        self.SetStartDate(2015, 1, 1)  # Set Start Date
        self.SetEndDate(2017, 10, 31)
        self.SetCash(100000)  # Set Strategy Cash
        
        self.SetUniverseSelection(FineFundamentalUniverseSelectionModel(self.CoarseSelectionFunction, self.FineSelectionFunction, None, None))
        self.UniverseSettings.Resolution = Resolution.Daily
        
        self.SetAlpha(ConstantAlphaModelSellEOY(InsightType.Price, InsightDirection.Up, timedelta(days=365)))
        
        self.SetPortfolioConstruction(EqualWeightingPortfolioConstructionModel(lambda time: None))
        
        self.SetExecution(ImmediateExecutionModel())

        self.SetBenchmark("SPY")
        
        self.year = -1


    def OnData(self, data):
        '''OnData event is the primary entry point for your algorithm. Each new data point will be pumped in here.
            Arguments:
                data: Slice object keyed by symbol containing the stock data
        '''

        # if not self.Portfolio.Invested:
        #    self.SetHoldings("SPY", 1)


    # on 15 Jan, filter for securities with fundamental data
    def CoarseSelectionFunction(self, coarse):
        if self.Time.year == self.year:
            return Universe.Unchanged
    
        self.year = self.Time.year
        return [ x.Symbol for x in coarse if x.HasFundamentalData ][:1000]
    
    
    # on 15 Jan, filter first for securities with shares and then filter a second time for net net stocks
    def FineSelectionFunction(self, fine):
        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) ]
        # filtered = [ x for x in filtered if (x.FinancialStatements.BalanceSheet.OrdinarySharesNumber.ThreeMonths > 0) ]
        # filtered = [ x for x in filtered if (x.FinancialStatements.BalanceSheet.PreferredStock.ThreeMonths > 0) ]
        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 ]
        
        # for x in filtered:
        #     self.Log("Symbol: " + str(x.Symbol))
        #     self.Log("Name: " + x.CompanyReference.LegalName)
        #     self.Log(str(self.Time.month) + str(self.Time.day) + str(self.Time.year))
        #     self.Log("Price: " + str(x.Price))
        #     self.Log("Current Assets: " + str(x.FinancialStatements.BalanceSheet.CurrentAssets.ThreeMonths))
        #     self.Log("Total Liabilities: " + str(x.FinancialStatements.BalanceSheet.TotalLiabilitiesAsReported.ThreeMonths))
        #     self.Log("Preferred Stock: " + str(x.FinancialStatements.BalanceSheet.PreferredStock.ThreeMonths))
        #     self.Log("Shares: " + str(x.FinancialStatements.BalanceSheet.OrdinarySharesNumber.ThreeMonths))
        # filtered = [ x.Symbol for x in filtered ]
        
        return filtered[:100]
        
class ConstantAlphaModelSellEOY(AlphaModel):
    ''' Provides an implementation of IAlphaModel that always returns the same insight for each security'''

    def __init__(self, type, direction, period, magnitude = None, confidence = None):
        '''Initializes a new instance of the ConstantAlphaModel class
        Args:
            type: The type of insight
            direction: The direction of the insight
            period: The period over which the insight with come to fruition
            magnitude: The predicted change in magnitude as a +- percentage
            confidence: The confidence in the insight'''
        self.type = type
        self.direction = direction
        self.period = period
        self.magnitude = magnitude
        self.confidence = confidence
        self.securities = []
        self.nextUpdate = datetime(1995, 1, 1)

        typeString = Extensions.GetEnumString(type, InsightType)
        directionString = Extensions.GetEnumString(direction, InsightDirection)

        self.Name = '{}({},{},{}'.format(self.__class__.__name__, typeString, directionString, strfdelta(period))
        if magnitude is not None:
            self.Name += ',{}'.format(magnitude)
        if confidence is not None:
            self.Name += ',{}'.format(confidence)

        self.Name += ')'


    def Update(self, algorithm, data):
        ''' Creates a constant insight for each security as specified via the constructor
        Args:
            algorithm: The algorithm instance
            data: The new data available
        Returns:
            The new insights generated'''
       
        if self.nextUpdate > algorithm.Time:
            return []
        self.nextUpdate = Expiry.EndOfYear(algorithm.Time)
        insights = []
        for security in self.securities:
            insights.append(Insight(security.Symbol, Expiry.EndOfYear, self.type, self.direction, self.magnitude, self.confidence))
        return insights


    def OnSecuritiesChanged(self, algorithm, changes):
        ''' Event fired each time the we add/remove securities from the data feed
        Args:
            algorithm: The algorithm instance that experienced the change in securities
            changes: The security additions and removals from the algorithm'''
        for added in changes.AddedSecurities:
            self.securities.append(added)

        # this will allow the insight to be re-sent when the security re-joins the universe
        for removed in changes.RemovedSecurities:
            if removed in self.securities:
                self.securities.remove(removed)
    
def strfdelta(tdelta):
    d = tdelta.days
    h, rem = divmod(tdelta.seconds, 3600)
    m, s = divmod(rem, 60)
    return "{}.{:02d}:{:02d}:{:02d}".format(d,h,m,s)