Skip to content
All library documents

Monthly Book-to-Price Long-Short Stock Selection

Article Strategy library · Author: QuantRocket

Summary

This cross-sectional stock strategy ranks securities by price-to-book ratio, using book value per share calculated from total assets, total liabilities, and shares outstanding. It buys the lowest-ratio group and shorts the highest-ratio group, with the example selecting the bottom and top deciles. Signals are refreshed monthly, equal-weighted, and shifted so positions follow the signal; returns are modeled from one open to the next.

The approach is a value factor portfolio that seeks to express the spread between cheaper and more expensive stocks. The code includes a US stock commission class and an exchange-specific variant, but the document provides no backtest results, benchmark, risk analysis, or discussion of fundamental-data publication lags. Those omissions matter: rankings may be affected by stale or unavailable financial data, and the portfolio’s realized performance depends on costs, liquidity, short availability, and implementation timing.

Key ideas

  • Book value per share is estimated from assets less liabilities divided by outstanding shares.
  • The portfolio buys stocks with the lowest price-to-book ratios and shorts those with the highest ratios.
  • The example selects decile groups, rebalances monthly, and uses equal weights.
  • The source models positions after signal formation and calculates returns from open prices.
  • No performance evidence or analysis of data timing, shorting constraints, or risk is supplied.

Tags

Full text
# HighMinusLow


# HighMinusLow









Strategy that buys stocks with high book-to-market ratios and shorts
    stocks with low book-to-market ratios.

    Specifically:

    - calculate book value per share
    - rank securities by price-to-book ratio
    - buy the TOP_N_PCT percent of stocks with the lowest P/B ratios and short the TOP_N_PCT
    percent of stocks with the highest P/B ratios
    - rebalance the portfolio according to REBALANCE_INTERVAL

## Source (Apache-2.0)

```python
# Copyright 2018 QuantRocket LLC - All Rights Reserved
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from moonshot import Moonshot
from moonshot.commission import PerShareCommission
from quantrocket.fundamental import get_reuters_financials_reindexed_like

class HighMinusLow(Moonshot):
    """
    Strategy that buys stocks with high book-to-market ratios and shorts
    stocks with low book-to-market ratios.

    Specifically:

    - calculate book value per share
    - rank securities by price-to-book ratio
    - buy the TOP_N_PCT percent of stocks with the lowest P/B ratios and short the TOP_N_PCT
    percent of stocks with the highest P/B ratios
    - rebalance the portfolio according to REBALANCE_INTERVAL
    """

    CODE = "hml"
    TOP_N_PCT = 10 # Buy/sell the bottom/top decile
    REBALANCE_INTERVAL = "M" # M = monthly; see http://pandas.pydata.org/pandas-docs/stable/timeseries.html#offset-aliases

    def prices_to_signals(self, prices):

        # calculate book value per share, defined as:
        #
        #    (Total Assets - Total Liabilities) / Number of shares outstanding
        #
        # The COA codes for these metrics are 'ATOT' (Total Assets), 'LTLL' (Total
        # Liabilities), and 'QTCO' (Total Common Shares Outstanding).

        closes = prices.loc["Close"]
        financials = get_reuters_financials_reindexed_like(closes, ["ATOT", "LTLL", "QTCO"])
        tot_assets = financials.loc["ATOT"].loc["Amount"]
        tot_liabilities = financials.loc["LTLL"].loc["Amount"]
        shares_out = financials.loc["QTCO"].loc["Amount"]
        book_values_per_share = (tot_assets - tot_liabilities)/shares_out

        # Calculate and rank by price-to-book ratio
        pb_ratios = closes/book_values_per_share
        highest_pb_ratio_ranks = pb_ratios.rank(axis=1, ascending=False, pct=True)
        lowest_pb_ratio_ranks = pb_ratios.rank(axis=1, ascending=True, pct=True)

        top_n_pct = self.TOP_N_PCT / 100

        # Get long and short signals and convert to 1, 0, -1
        longs = (lowest_pb_ratio_ranks <= top_n_pct)
        shorts = (highest_pb_ratio_ranks <= top_n_pct)

        longs = longs.astype(int)
        shorts = -shorts.astype(int)

        # Combine long and short signals
        signals = longs.where(longs == 1, shorts)

        # Resample using the rebalancing interval.
        # Keep only the last signal of the month, then fill it forward
        signals = signals.resample(self.REBALANCE_INTERVAL).last()
        signals = signals.reindex(closes.index, method="ffill")

        return signals

    def signals_to_target_weights(self, signals, prices):
        weights = self.allocate_equal_weights(signals)
        return weights

    def target_weights_to_positions(self, weights, prices):
        # Enter the position in the period/day after the signal
        return weights.shift()

    def positions_to_gross_returns(self, positions, prices):
        # We'll enter on the open, so our return is today's open to
        # tomorrow's open
        opens = prices.loc["Open"]
        gross_returns = opens.pct_change() * positions.shift()
        return gross_returns

class USStockCommission(PerShareCommission):
    IB_COMMISSION_PER_SHARE = 0.005

class HighMinusLowAmex(HighMinusLow):

    CODE = "hml-amex"
    DB = "amex-1d"
    COMMISSION_CLASS = USStockCommission

```

Shown in full with attribution under the source's licence. Licence: Apache-2.0

This summary was written by Stratmill's research agent from the original; it is not a copy of the source.