Dual Momentum Selectors¶
Dual momentum investment strategies are part of the larger family of technical analysis-based investments. It uses a performance criterion, called momentum, to rank the assets and then to select the best of them and to define the fraction of the capital to be invested. The fraction of capital to be invested is also called capital at risk. The rest of the capital, i.e., 1 minus the capital at risk, is kept in cash as a strategic reserve against adverse market conditions.
A dual momentum strategy has 3 essential parameters:
momentum or filter : It is an analytical measure for stock performance expressed as a real number with the following characteristics:
only assets with positive momentum values are considered acceptable investments,
the higher its value the more performant is the underlying asset.
Later we will discuss the f13612w
filter.
selection size, \(N_S\) : It is the maximum number of assets in the final selection . A typical value is \(N_S = 5\). Note that the actual selection size \(n\) is smaller or equal to \(N_S\), and it can be
0
, when the entire capital is kept in cash (during severe adverse market conditions for a long-only type of investment).threshold, \(N_T\) : It is the minimum number of assets with positive momentum considered for a full capital allocation among selected assets (i.e., the capital at risk is equal to the total capital). If the number of assets with positive momentum, \(n\), is smaller than this threshold, then the capital at risk is proportionally smaller than the total capital. \(N_T\) can be viewed as a quantitative expression for “adverse market conditions”, i.e., there are “adverse market conditions” if the actual selection size, \(n\), is smaller than \(N_T\).
The capital at risk, \(\rm CaR\), can be expressed as follows. Let’s consider a set of \(N_U\) assets subject to a dual momentum investment strategy, such that \(N_S <= N_U\) and \(N_T <= N_U\). Let \(N_0\) be the number of assets with positive momentum (as computed by the filter), then the actual selection size \(n\) is given by
The capital at risk as a fraction of the total capital is defined as
Further the Dual Momentum Selector makes no assumptions about how the capital at risk is allocated among the selected assets. An actual capital allocation can be performed by any of the portfolio optimization strategies presented in the Risk-based, Naïve, and Greedy sections.
Regarding the actual momentum criterion, azapy implements
f13612w
filter - It is defined as the weighted average of the most recent annualized 1-, 3-, 6-, and 12-months rates of return. The typical setup is for equal weighted average. However, azapy implementation allows for any set of positive weights (not all zero), e.g.,[1, 2, 1, 1]
.
DualMomentumSelector class¶
- class azapy.Selectors.DualMomentumSelector.DualMomentumSelector(pname='DualMomentum', ftype='f13612w', fw=None, nw=3, threshold=6, col_price='adjusted')¶
Bases:
NullSelector
Dual Momentum Selector.
Given a filter it selects the best candidates among the once with highest moment.
- Attributes
pname : str - portfolio name
mkt : pandas.DataFrame - selection’s market data
rank : pandas.Series - filter rank of all symbols
symb : list - selected symbols
symb_omitted : list - unselected symbols
capital : float - capital at risk as a fraction of the total capital
Methods
getSelection
(mktdata, **params)Computes the selection.
- __init__(pname='DualMomentum', ftype='f13612w', fw=None, nw=3, threshold=6, col_price='adjusted')¶
Constructor
- Parameters:
- pnamestr, optional
Selector name. The default is ‘DualMomentum’.
- ftypestr, optional
The filter name (at this point only ‘f13612w’ filter is supported). The default is ‘f13612w’ are equal weights.
- fwlist, optional
List of filter wights. For ‘f13612w’ it must be a list of 4 positive (not all zero) numbers. A value of None indicates equal weights. Note: the weights are normalized internally. The default is None.
- nwint, optional
Maximum number of selected symbols. The default is 3.
- thresholdint, optional
Minimum number of symbols with positive momentum for a full capital allocation. The default is 6.
- col_pricestr, optional
Name of the price column in the mktdata to be used in the momentum evaluations. The default is ‘adjusted’.
- Returns:
- The object.
- getSelection(mktdata, **params)¶
Computes the selection.
- Parameters:
- mktdatapandas.DataFrame
MkT data in the format produced by the azapy function readMkT.
- **paramsdict, optional
- Other optional parameters:
- verboseBoolean, optional
When it is set to True, the selection symbols are printed. The default is False.
- Returns:
- (capital, mkt)tuple
- capitalfloat
Fraction of capital allocated to the selection (a positive number <= 1, 1 being full allocation). One minus this value is the fraction of reserved capital invested in cash.
- mktpandas.DataFrame
Selection MkT data in the format produced by the azapy function readMkT.
Example DualMomentumSelctor¶
# Examples
import numpy as np
import azapy as az
print(f"azapy version {az.version()}", flush=True)
#==============================================================================
# collect market data
mktdir = '../../MkTdata'
sdate = '2012-01-01'
edate = '2021-07-27'
symb = ['GLD', 'TLT', 'IHI', 'VGT', 'OIH',
'XAR', 'XBI', 'XHE', 'XHS', 'XLB',
'XLE', 'XLF', 'XLI', 'XLK', 'XLU',
'XLV', 'XLY', 'XRT', 'SPY', 'ONEQ',
'QQQ', 'DIA', 'ILF', 'XSW', 'PGF',
'IDV', 'JNK', 'HYG', 'SDIV', 'VIG',
'SLV', 'AAPL', 'MSFT', 'AMZN', 'GOOG',
'IYT', 'VGI', 'IWM', 'BRK-B', 'ITA']
mktdata = az.readMkT(symb, sdate=sdate, edate=edate, file_dir=mktdir,
verbose=False)
#==============================================================================
# DualMomentumSelctor
# maximum number of selected symbol
nw = 3
# minimum number of symbols with positive momentum
# for a full capital allocation -
# in our case roughly 80% of the initial number of symbols
ths = np.floor(len(symb) * 0.8)
selector = az.DualMomentumSelector(nw=nw, threshold=ths)
capital, mkt = selector.getSelection(mktdata)
print(f"As of {edate}\n"
f"capital at risk: {capital}\n"
f"selected symbols: {selector.symb}")