Correlation Index: Momentum and Volatility Checks
Our attempt at using the correlation index as a trading signal did not generate very good results. After optimizing for historical behavior, the results did not yield exceptional returns; the model uses the correlation index and nothing else; it lacks information regarding any other market parameter, it is partially ignorant. We will extend the model and add a simple, possibly the simplest, measures of price momentum and returns volatility as information parameters looking for improved behavior. Of course, we could also look for very complex momentum and volatility measures and drive them through an optimization grid. We would be possibly overfitting to the past in this case and should be fully aware of this.
We will fit into the model a function to compute simple momentum and volatility from a history call to the daily closing prices of SPY. Keeping it as simple as possible:
def compute_vol_mom(self, prices): rets = prices.pct_change().dropna() vol = np.std(rets)[-1] mom = np.mean(rets)[-1] return vol, mom
In a single go, we obtain the standard deviation of the daily returns and the mean of the returns for the given period. We call the history first in our correlation computation block. Then we use this function, which is a method inside the alpha model class, to obtain an approximation to the volatility and momentum for the period:
# Compute Market Momentum and Volatilty: market_price = algorithm.History(self.market, self.mom_vol_period, Resolution.Daily).unstack(level=0)['close'] self.vol, self.mom = self.compute_vol_mom(market_price)
We articulate the complex condition for exiting the market given a too low momentum or too-high volatility combined with our previous minimum correlation limit:
condition = self.corr < self.min_corr or \ self.mom < self.mom_limit or \ self.vol > self.vol_limit
A new problem is generated here: we need new parameters to compute the momentum, the volatility, and the two limiting values for these factors. This is where a second parameter grid could generate an overfit model. To try and avoid this, we will use the simplest possible values: for momentum, have the 22-day value positive; for volatility, have the same period value below the historical value of 1%. So, now, whenever the correlation drops below the minimum, or the momentum is less than 0, or the volatility is above 0.01, the model will exit the market. We are trying to avoid risk as much as possible, and the result of doing this in the past five years is:
Risk is, indeed, avoided. With this avoidance of risk comes a reduction in total returns. The Sharpe ratio is now around 1.3; the maximum drawdown is just 7%, the returns dropping to 50%, less than half the buy and hold strategy. Maybe we will sleep better at night, or at least many nights in which the model is out of the market. Of course, sleeping better comes with the regrets of seeing the market rise while our risk-averse model stubbornly stays out.
We still want to risk this money. We still want to obtain a minimum return from our capital while the markets are not in our favor. There are two possible solutions for gaining exposure when our prediction for the market is not good: either we short the market with the associated risks or enter alternate positions. For the first deployment type, the riskier one, we will model entering short market positions when our model tells us to do so. We are not flattening our positions; we are using a InsightDirection.Down signal to tell the model to enter these short positions:
Shorting the market destroys our equity. On the one hand, we are trying to avoid risk and risking everything against a possible reversal while the volatility is high on the other. Our model gets out of risky positions, not necessarily meaning that the returns will be negative, just more difficult to predict statistically. We have to look at alternative methods for equity deployment: enter the positions with the least possible risk.
One of the instruments through which we can gain a relatively safe investment exposure are Treasury Inflation-Protected Securities. The TIP ETF provides a convenient although expensive method to do this. We will add its ticker (TIP) to our universe manually and enter positions into it whenever our model decides that SPY risk is too high:
# Add TIPS: self.tip = self.AddEquity('TIP', res).Symbol
We have to be careful and pass the TIPS symbol into the alpha model, prevent it from being used for the calculation of the correlation (as this will really throw the calculation off), and then enter the correct position when the conditions are right:
The risk-reward ratio is not changing much, so we are not exposing our equity too badly now while our returns increase to 90% for this 5 year period. Idle cash was costing us a lot in terms of opportunity.
The model is now informationally "complete": we have the correlation at the top, a sense of general market direction with momentum, and control for risk in the form of volatility limits. This is as far as we dare go. Going further means researching additional parameter grids for momentum and volatility timeframes and the possible cut-off levels, not the obvious 0 and historical value. The risks of overfitting to the past become too high to claim that such a model could perform well in the future. The model, in its final form and ready to venture forth into further optimization, is at the end of the post after the disclaimer and invitation to connect.
Information in ostirion.net does not constitute financial advice; we do not hold positions in any of the companies or assets that we mention in our posts at the time of posting. If you require quantitative model development, deployment, verification, or validation, do not hesitate and contact us. We will also be glad to help you with your machine learning or artificial intelligence challenges when applied to asset management, trading, or risk evaluations.