A simple regime filter
Regime filters¶
A regime filter is a classification to indicate the market condition we are in. They could provide some useful pointers to improve trading results. Though we cannot predict the market movements with certainty, we can atleast get an edge.
We should keep these filters simple so that it is easy to understand and do not overfit too much to available data. Boolean filters are a good choice since they only have a true or false value and not prone to too much overfitting, though they may only have a slight edge.
A simple price filter¶
Let us a simple price filter.
- Create a moving average of the price for the last 60 days.
- If the close price is greater than the average price, give it a value of 1.
- If the close price is less than the average price, give it a value of 0
We shift the average price by 1 day so that we do not include today's close price
import pandas as pd
import seaborn as sns
sns.set()
# parameters
ma = 60
df = pd.read_csv('/home/pi/data/sp500.csv', parse_dates=['Date']).rename(
columns = lambda x:x.lower()).sort_values(by='date').set_index('date')
df['ret'] = df.close.pct_change()
df.tail()
df['ma_price'] = df.close.rolling(ma).mean().shift(1)
df['is_price'] = df.eval('close > ma_price')+0
df['is_price'] = df.is_price.shift(1) # Shifting price since we use the signal only next day
df.groupby(['is_price']).ret.describe()
sns.displot(data=df, x='ret', hue='is_price', kind='kde')
Looks there seem to be an edge when the filter is negative where the returns are a bit more.
In the earlier version, I computed the end of the day returns which is obvious and just a explanation of events already happened. Forward returns should always be considered to find a predictive edge.
Let us see whether the edge persists through all years.
df['year'] = df.index.year
df.groupby(['year', 'is_price']).ret.mean().unstack().plot.bar(stacked=True,
title="Mean returns by year")
The edge fairly persists over the years.
Let us try changing it to median and see if it improves the results
df['ma_price'] = df.close.rolling(ma).median().shift(1)
df['is_price'] = df.eval('close > ma_price')+0
df['is_price'] = df.is_price.shift(1) # Shifting price since we use the signal only next day
df.groupby(['is_price']).ret.describe()
Not so big. This is just a simple way to create a filter to define a market environment and see how it impacts the returns. The following could be done to improve a filter
- Use quantiles instead of mean or median
- Calculate the filter for different moving averages
- Try simulating for periods instead of rolling over averages