Notebook

Market Regime detetection using Hidden Markov Model or Anomaly Detection (OneClassSVM)

For many algorythmic trading strategies, fixed asset allocation weights and model paramters are static and hence, do not adapt to abrupt market regime changes. I believe many algorithms could improve their overall performance by adjusting certain of their paramters based on the current underlying market regime. For example, an algo could bias its weights toward more short positions during market contractions.

I have searched the web for such techniques and came across one using Hidden Markov State Machine. It is shown here below. The training is done on the SPY as we are trying to determine the broad market regime. It could be applied to specific sectors as well.

The second technique is my own creation. It uses sklearn's OneClassSVM as an unsupervised anomaly detector. The idea is to feed the svm with normalized short time series of the SPY as input features. 20 trading days are used. The OCSVM should hopefully determine which short time series are "abnormal" or "unclustered". We should treat those abnormal time series as a different market regime, one which signals high volatilty and contraction. I think a 1D convnet would work better then a OneClassSVM, but Keras/Tensorflow is not supported. On the otherhand, I would also not be surprised that a convnet overfits.

HMM

In [3]:
from sklearn import hmm
import pandas as pd
from matplotlib import pyplot as plt
import numpy as np
from quantopian.research.experimental import history
from matplotlib import cm
In [4]:
df = history(
    symbols('SPY'), 
    fields=['close_price', 'volume'], 
    frequency='daily', 
    start='2004-11-19', 
    end='2018-11-19'    
)
In [5]:
rets = df['close_price'].pct_change()[1:]
volume = df['volume'][1:]
dates = df.index[1:]
close_v = volume
In [6]:
model = hmm.GaussianHMM(n_components=2, covariance_type="full", n_iter=1000)
/usr/local/lib/python2.7/dist-packages/sklearn/utils/__init__.py:76: DeprecationWarning: Class _BaseHMM is deprecated; WARNING: The HMM module and its function will be removed in 0.17as it no longer falls within the project's scope and API. It has been moved to a separate repository: https://github.com/hmmlearn/hmmlearn
  warnings.warn(msg, category=DeprecationWarning)
In [7]:
X = np.column_stack([rets, np.log(volume)])
In [8]:
model.fit([X])
hidden_states = model.predict(X)
/usr/local/lib/python2.7/dist-packages/sklearn/utils/__init__.py:94: DeprecationWarning: Function normalize is deprecated; WARNING: The HMM module and its functions will be removed in 0.17 as it no longer falls within the project's scope and API. It has been moved to a separate repository: https://github.com/hmmlearn/hmmlearn
  warnings.warn(msg, category=DeprecationWarning)
/usr/local/lib/python2.7/dist-packages/sklearn/utils/__init__.py:94: DeprecationWarning: Function normalize is deprecated; WARNING: The HMM module and its functions will be removed in 0.17 as it no longer falls within the project's scope and API. It has been moved to a separate repository: https://github.com/hmmlearn/hmmlearn
  warnings.warn(msg, category=DeprecationWarning)
/usr/local/lib/python2.7/dist-packages/sklearn/utils/__init__.py:94: DeprecationWarning: Function normalize is deprecated; WARNING: The HMM module and its functions will be removed in 0.17 as it no longer falls within the project's scope and API. It has been moved to a separate repository: https://github.com/hmmlearn/hmmlearn
  warnings.warn(msg, category=DeprecationWarning)
/usr/local/lib/python2.7/dist-packages/sklearn/utils/__init__.py:94: DeprecationWarning: Function normalize is deprecated; WARNING: The HMM module and its functions will be removed in 0.17 as it no longer falls within the project's scope and API. It has been moved to a separate repository: https://github.com/hmmlearn/hmmlearn
  warnings.warn(msg, category=DeprecationWarning)
/usr/local/lib/python2.7/dist-packages/sklearn/utils/__init__.py:94: DeprecationWarning: Function normalize is deprecated; WARNING: The HMM module and its functions will be removed in 0.17 as it no longer falls within the project's scope and API. It has been moved to a separate repository: https://github.com/hmmlearn/hmmlearn
  warnings.warn(msg, category=DeprecationWarning)
/usr/local/lib/python2.7/dist-packages/sklearn/utils/__init__.py:94: DeprecationWarning: Function normalize is deprecated; WARNING: The HMM module and its functions will be removed in 0.17 as it no longer falls within the project's scope and API. It has been moved to a separate repository: https://github.com/hmmlearn/hmmlearn
  warnings.warn(msg, category=DeprecationWarning)
/usr/local/lib/python2.7/dist-packages/sklearn/utils/__init__.py:94: DeprecationWarning: Function normalize is deprecated; WARNING: The HMM module and its functions will be removed in 0.17 as it no longer falls within the project's scope and API. It has been moved to a separate repository: https://github.com/hmmlearn/hmmlearn
  warnings.warn(msg, category=DeprecationWarning)
/usr/local/lib/python2.7/dist-packages/sklearn/utils/__init__.py:94: DeprecationWarning: Function normalize is deprecated; WARNING: The HMM module and its functions will be removed in 0.17 as it no longer falls within the project's scope and API. It has been moved to a separate repository: https://github.com/hmmlearn/hmmlearn
  warnings.warn(msg, category=DeprecationWarning)
/usr/local/lib/python2.7/dist-packages/sklearn/utils/__init__.py:94: DeprecationWarning: Function normalize is deprecated; WARNING: The HMM module and its functions will be removed in 0.17 as it no longer falls within the project's scope and API. It has been moved to a separate repository: https://github.com/hmmlearn/hmmlearn
  warnings.warn(msg, category=DeprecationWarning)
/usr/local/lib/python2.7/dist-packages/sklearn/utils/__init__.py:94: DeprecationWarning: Function normalize is deprecated; WARNING: The HMM module and its functions will be removed in 0.17 as it no longer falls within the project's scope and API. It has been moved to a separate repository: https://github.com/hmmlearn/hmmlearn
  warnings.warn(msg, category=DeprecationWarning)
/usr/local/lib/python2.7/dist-packages/sklearn/utils/__init__.py:94: DeprecationWarning: Function normalize is deprecated; WARNING: The HMM module and its functions will be removed in 0.17 as it no longer falls within the project's scope and API. It has been moved to a separate repository: https://github.com/hmmlearn/hmmlearn
  warnings.warn(msg, category=DeprecationWarning)
/usr/local/lib/python2.7/dist-packages/sklearn/utils/__init__.py:94: DeprecationWarning: Function normalize is deprecated; WARNING: The HMM module and its functions will be removed in 0.17 as it no longer falls within the project's scope and API. It has been moved to a separate repository: https://github.com/hmmlearn/hmmlearn
  warnings.warn(msg, category=DeprecationWarning)
/usr/local/lib/python2.7/dist-packages/sklearn/utils/__init__.py:94: DeprecationWarning: Function normalize is deprecated; WARNING: The HMM module and its functions will be removed in 0.17 as it no longer falls within the project's scope and API. It has been moved to a separate repository: https://github.com/hmmlearn/hmmlearn
  warnings.warn(msg, category=DeprecationWarning)
/usr/local/lib/python2.7/dist-packages/sklearn/utils/__init__.py:94: DeprecationWarning: Function normalize is deprecated; WARNING: The HMM module and its functions will be removed in 0.17 as it no longer falls within the project's scope and API. It has been moved to a separate repository: https://github.com/hmmlearn/hmmlearn
  warnings.warn(msg, category=DeprecationWarning)
/usr/local/lib/python2.7/dist-packages/sklearn/utils/__init__.py:94: DeprecationWarning: Function normalize is deprecated; WARNING: The HMM module and its functions will be removed in 0.17 as it no longer falls within the project's scope and API. It has been moved to a separate repository: https://github.com/hmmlearn/hmmlearn
  warnings.warn(msg, category=DeprecationWarning)
/usr/local/lib/python2.7/dist-packages/sklearn/utils/__init__.py:94: DeprecationWarning: Function normalize is deprecated; WARNING: The HMM module and its functions will be removed in 0.17 as it no longer falls within the project's scope and API. It has been moved to a separate repository: https://github.com/hmmlearn/hmmlearn
  warnings.warn(msg, category=DeprecationWarning)
/usr/local/lib/python2.7/dist-packages/sklearn/utils/__init__.py:94: DeprecationWarning: Function normalize is deprecated; WARNING: The HMM module and its functions will be removed in 0.17 as it no longer falls within the project's scope and API. It has been moved to a separate repository: https://github.com/hmmlearn/hmmlearn
  warnings.warn(msg, category=DeprecationWarning)
/usr/local/lib/python2.7/dist-packages/sklearn/utils/__init__.py:94: DeprecationWarning: Function normalize is deprecated; WARNING: The HMM module and its functions will be removed in 0.17 as it no longer falls within the project's scope and API. It has been moved to a separate repository: https://github.com/hmmlearn/hmmlearn
  warnings.warn(msg, category=DeprecationWarning)
/usr/local/lib/python2.7/dist-packages/sklearn/utils/__init__.py:94: DeprecationWarning: Function normalize is deprecated; WARNING: The HMM module and its functions will be removed in 0.17 as it no longer falls within the project's scope and API. It has been moved to a separate repository: https://github.com/hmmlearn/hmmlearn
  warnings.warn(msg, category=DeprecationWarning)
/usr/local/lib/python2.7/dist-packages/sklearn/utils/__init__.py:94: DeprecationWarning: Function normalize is deprecated; WARNING: The HMM module and its functions will be removed in 0.17 as it no longer falls within the project's scope and API. It has been moved to a separate repository: https://github.com/hmmlearn/hmmlearn
  warnings.warn(msg, category=DeprecationWarning)
/usr/local/lib/python2.7/dist-packages/sklearn/utils/__init__.py:94: DeprecationWarning: Function normalize is deprecated; WARNING: The HMM module and its functions will be removed in 0.17 as it no longer falls within the project's scope and API. It has been moved to a separate repository: https://github.com/hmmlearn/hmmlearn
  warnings.warn(msg, category=DeprecationWarning)
/usr/local/lib/python2.7/dist-packages/sklearn/utils/__init__.py:94: DeprecationWarning: Function normalize is deprecated; WARNING: The HMM module and its functions will be removed in 0.17 as it no longer falls within the project's scope and API. It has been moved to a separate repository: https://github.com/hmmlearn/hmmlearn
  warnings.warn(msg, category=DeprecationWarning)
/usr/local/lib/python2.7/dist-packages/sklearn/utils/__init__.py:94: DeprecationWarning: Function normalize is deprecated; WARNING: The HMM module and its functions will be removed in 0.17 as it no longer falls within the project's scope and API. It has been moved to a separate repository: https://github.com/hmmlearn/hmmlearn
  warnings.warn(msg, category=DeprecationWarning)
/usr/local/lib/python2.7/dist-packages/sklearn/utils/__init__.py:94: DeprecationWarning: Function normalize is deprecated; WARNING: The HMM module and its functions will be removed in 0.17 as it no longer falls within the project's scope and API. It has been moved to a separate repository: https://github.com/hmmlearn/hmmlearn
  warnings.warn(msg, category=DeprecationWarning)
In [9]:
def plot_in_sample_hidden_states(hmm_model, df):
    """
    Plot the adjusted closing prices masked by
    the in-sample hidden states as a mechanism
    to understand the market regimes.
    """
    # Predict the hidden states array
    hidden_states = hmm_model.predict(X)
            
    # Create the correctly formatted plot
    fig, axs = plt.subplots(
        hmm_model.n_components,
        sharex=True, sharey=True
    )
    colours = cm.rainbow(
        np.linspace(0, 1, hmm_model.n_components)
    )
    for i, (ax, colour) in enumerate(zip(axs, colours)):
        mask = hidden_states == i
        ax.plot_date(
            df.index[mask],
            df["close_price"][mask],
            ".", linestyle='none',
            c=colour
        )
        ax.set_title("Hidden State #%s" % i)
        ax.grid(True)
    plt.show()

HMM results

We see that the HMM hidden state #0 seems to capture volatile and downturning market regimes whilst hs #1 represents normal, more profitable regimes.

In [10]:
plot_in_sample_hidden_states(model, df)
/usr/local/lib/python2.7/dist-packages/pandas/tseries/base.py:212: VisibleDeprecationWarning: boolean index did not match indexed array along dimension 0; dimension is 3525 but corresponding boolean dimension is 3524
  result = getitem(key)
/usr/local/lib/python2.7/dist-packages/pandas/core/internals.py:227: VisibleDeprecationWarning: boolean index did not match indexed array along dimension 0; dimension is 3525 but corresponding boolean dimension is 3524
  return self.values[slicer]

One Class SVM anomaly detection

In [16]:
data = df['close_price'].tolist()
In [17]:
# build the mini 20 day long time series normalized.

out = []
for i in range(len(data)):
    chunk = np.array(data[i-20:i])
    try:
        out.append(chunk/chunk[0])
    except:
        out.append(np.ones(20))
In [18]:
X = np.array(out)
In [19]:
from sklearn.svm import OneClassSVM
from sklearn.preprocessing import PolynomialFeatures
In [20]:
# nu represent the expected ratio of outliers. 30% in this case.  Gamma is a fitting param.
model = OneClassSVM(kernel='rbf', nu=0.30, gamma=1.1)
In [21]:
# we use poly feat to increase the number of features and try to suss out meaningful relationships.
# Another approach could be to manually engineer features from the data set. example, diff, rolling slopes, etc.
model.fit(PolynomialFeatures(degree=3).fit_transform(X))
Out[21]:
OneClassSVM(cache_size=200, coef0=0.0, degree=3, gamma=1.1, kernel='rbf',
      max_iter=-1, nu=0.3, random_state=None, shrinking=True, tol=0.001,
      verbose=False)
In [22]:
y = model.predict(PolynomialFeatures(degree=3).fit_transform(X))
In [23]:
def plot_in_sample_clusters(model, df):
    """
    Plot the adjusted closing prices masked by
    the in-sample hidden states as a mechanism
    to understand the market regimes.
    """
    # Predict the hidden states array
    hidden_states = model.predict(PolynomialFeatures(degree=3).fit_transform(X))
    
    hs = pd.Series(hidden_states).rolling(window=5).mean().fillna(1)
    hs[hs >= 0] = 1
    hs[hs < 0] = 0
    
    hidden_states = hs
    hidden_states.index = df.index
        
    # Create the correctly formatted plot
    fig, axs = plt.subplots(
        2,
        sharex=True, sharey=True
    )
    colours = cm.rainbow(
        np.linspace(0, 1, 2)
    )
    for i, (ax, colour) in enumerate(zip(axs, colours)):
        mask = hidden_states == i
        ax.plot_date(
            df.index[mask],
            df["close_price"][mask],
            ".", linestyle='none',
            c=colour
        )
        ax.set_title("Cluster #%s" % i)
        ax.grid(True)
    plt.show()

Results

Similarly to the HMM, it appears that the OCSVM was able to identify market regime changes. Of course, this is all insample.

In [24]:
plot_in_sample_clusters(model, df)
In [ ]: