Overall Statistics
Total Orders
160
Average Win
0.16%
Average Loss
-0.01%
Compounding Annual Return
7.199%
Drawdown
19.700%
Expectancy
11.724
Start Equity
1000000
End Equity
1366852.85
Net Profit
36.685%
Sharpe Ratio
0.408
Sortino Ratio
0.45
Probabilistic Sharpe Ratio
10.392%
Loss Rate
3%
Win Rate
97%
Profit-Loss Ratio
12.11
Alpha
-0.02
Beta
0.917
Annual Standard Deviation
0.107
Annual Variance
0.011
Information Ratio
-0.545
Tracking Error
0.048
Treynor Ratio
0.048
Total Fees
$241.59
Estimated Strategy Capacity
$7100000.00
Lowest Capacity Asset
CRHCY R735QTJ8XC9X
Portfolio Turnover
0.07%
#region imports
from AlgorithmImports import *
#endregion
# https://quantpedia.com/Screener/Details/26


class BooktoMarketAnomaly(QCAlgorithm):

    def initialize(self):
        self.set_start_date(2014, 1, 1)   
        self.set_end_date(2018, 7, 1)
        self.set_cash(1000000)
        self._sorted_by_bm = None
        self.set_security_initializer(BrokerageModelSecurityInitializer(
            self.brokerage_model, FuncSecuritySeeder(self.get_last_known_prices)))
        self.add_universe(self.coarse_selection_function, self.fine_selection_function)
        self.add_equity("SPY")
        self.schedule.on(self.date_rules.month_start("SPY"), self.time_rules.after_market_open("SPY"), self._rebalance)
        # Count the number of months that have passed since the algorithm starts
        self._months = -1
        self._yearly_rebalance = True

    def coarse_selection_function(self, coarse):
        if self._yearly_rebalance:
            # drop stocks which have no fundamental data or have low price
            return [x.symbol for x in coarse if (x.has_fundamental_data)]
        else: 
            return Universe.UNCHANGED

    def fine_selection_function(self, fine):
        if self._yearly_rebalance:
            # Filter stocks with positive PB Ratio
            fine = [x for x in fine if (x.valuation_ratios.pb_ratio > 0)] 
            
            top_market_cap = sorted(fine, key=lambda x: x.market_cap, reverse=True)[:int(len(fine)*0.2)]
            # sorted stocks in the top market-cap list by book-to-market ratio
            top_bm = sorted(top_market_cap, key=lambda x: 1 / x.valuation_ratios.pb_ratio, reverse=True)[:int(len(top_market_cap)*0.2)]
            self._sorted_by_bm = [i.symbol for i in top_bm] 
            total_market_cap = np.sum([i.market_cap for i in top_bm])
            # calculate the weight with the market cap
            self._weights = {}
            for i in top_bm:
                self._weights[str(i.symbol)] = i.market_cap/total_market_cap
            return self._sorted_by_bm
        else:
            return Universe.UNCHANGED

    def _rebalance(self):
        # yearly rebalance
        self._months += 1
        if self._months%12 == 0:
            self._yearly_rebalance = True

    def on_data(self, data):
        if not self._yearly_rebalance: 
            return 
        if self._sorted_by_bm:
            stocks_invested = [x.key for x in self.portfolio if x.value.invested]
            # liquidate stocks not in the trading list 
            for i in stocks_invested:
                if i not in self._sorted_by_bm:
                    self.liquidate(i)   
            # goes long on stocks with the highest book-to-market ratio   
            for i in self._sorted_by_bm:
                self.set_holdings(i, self._weights[str(i)])
            
            self._yearly_rebalance = False