Overall Statistics
Total Orders
63
Average Win
3.77%
Average Loss
-2.21%
Compounding Annual Return
20.242%
Drawdown
47.100%
Expectancy
0.701
Start Equity
100000
End Equity
190674.56
Net Profit
90.675%
Sharpe Ratio
0.54
Sortino Ratio
0.852
Probabilistic Sharpe Ratio
11.517%
Loss Rate
37%
Win Rate
63%
Profit-Loss Ratio
1.70
Alpha
0.146
Beta
0.269
Annual Standard Deviation
0.309
Annual Variance
0.095
Information Ratio
0.278
Tracking Error
0.318
Treynor Ratio
0.621
Total Fees
$430.14
Estimated Strategy Capacity
$6000.00
Lowest Capacity Asset
PBIB TM78KLLUMAG5
Portfolio Turnover
0.51%
#region imports
from AlgorithmImports import *
#endregion


class PriceEarningsAnamoly(QCAlgorithm):

    def initialize(self):
        self.set_start_date(2016, 1, 1)   
        self.set_end_date(2019, 7, 1)         
        self.set_cash(100000)
        self.set_benchmark("SPY")
        
        self.universe_settings.resolution = Resolution.DAILY
        self._symbols = []
        
        # record the year that have passed since the algorithm starts
        self._year = -1
        self._num_coarse_stocks = 200
        self._num_stocks_in_portfolio = 10
        self.add_universe(self._coarse_selection_function, self._fine_selection_function)
        
        
    def _coarse_selection_function(self, coarse):
        if self.time.year == self._year:
            return self._symbols
        
        # drop stocks which have no fundamental data or have low price
        coarse_with_fundamental = [x for x in coarse if x.has_fundamental_data and x.price > 5]
        sorted_by_dollar_volume = sorted(coarse_with_fundamental, key=lambda x: x.dollar_volume, reverse=False) 
        return [i.symbol for i in sorted_by_dollar_volume[:self._num_coarse_stocks]]

    def _fine_selection_function(self, fine):
        if self.time.year == self._year:
            return self._symbols
        self._year = self.time.year
        
        fine = [x for x in fine if x.valuation_ratios.pe_ratio > 0]
        sorted_pe_ratio = sorted(fine, key=lambda x: x.valuation_ratios.pe_ratio)
        self._symbols = [i.symbol for i in sorted_pe_ratio[:self._num_stocks_in_portfolio]]
        return self._symbols
    
    def on_securities_changed(self, change):
        
        # liquidate securities that removed from the universe
        for security in change.removed_securities:
            if self.portfolio[security.symbol].invested:
                self.liquidate(security.symbol)

        count = len(change.added_securities)

        # evenly invest on securities that newly added to the universe
        for security in change.added_securities:
            symbol = security.symbol
            if not self.securities[symbol].price:
                continue
            self.set_holdings(symbol, 1.0/count)