```
# 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
```