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

(1)\[\begin{equation} n = \min(N_0, N_S), \end{equation}\]

The capital at risk as a fraction of the total capital is defined as

(2)\[\begin{equation} {\rm CaR} = \min\left( \frac{n}{N_S}, 1\right) \min\left( \frac{n}{N_T}, 1\right). \end{equation}\]

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].

TOP

DualMomentumSelector class

class azapy.Selectors.DualMomentumSelector.DualMomentumSelector(pname='DualMomentum', ftype='f13612w', fw=None, nw=3, threshold=6, col_price='adjusted', **kwargs)

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', **kwargs)

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’.

**kwargsdict, optional

Holder for other args.

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.

TOP

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', 'VIG', '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}")

TOP