Introduction
I saw the following paper on SSRN and thought it would be interesting to implement within QuantConnect. I thought I'd share it with the community to start a discussion and see if there are any interesting ways to improve.
The strategy is essentially an intraday breakout strategy based on the average absolute deviation from the open over the last 14 days.
The paper uses the max between the intraday VWAP and upper band as a trailing stop for longs or the min between VWAP and lower band for shorts.
Method
To build the backtest, I created a custom indicator that calculates the average absolute deviation from the day's open at each time step and returns the current average over the last 14 days.
Results
Overall the results are positive, but no where near as impressive as in the original paper. Let me know your thoughts!
Mia Alissi
Based on the provided Python code, there are a few potential issues that might be causing problems:
The
self._vwap
is initialized but never updated with new data. You might want to update it in theconsolidate
method.The
long_stop_price
is calculated but never used. If it's supposed to be a stop price for long positions, you might want to implement logic that uses this value to exit long positions.The
self._deviation
indicator is updated with the entirebar
object, but it might be expecting a specific value likebar.close
. Make sure to pass the correct data to the indicator's update method.The
consolidate
method doesn't handle the case whenself._vol
is not ready orself.previous_close
isNone
. You might want to add logic to handle these cases.The
end_of_day
method is scheduled to be called every day before the market close, but it's not implemented in the provided code. You should implement this method if it's necessary for your strategy.Remember, these are just potential issues based on the provided code. The actual issues might be different depending on the rest of your code and your specific strategy.
Here is the relevant part of the code with the potential issues:
Please review your code and make sure everything is implemented correctly. If you're still having issues, you might want to consider asking for help on the QuantConnect forum.
The material on this website is provided for informational purposes only and does not constitute an offer to sell, a solicitation to buy, or a recommendation or endorsement for any security or strategy, nor does it constitute an offer to provide investment advisory services by QuantConnect. In addition, the material offers no opinion with respect to the suitability of any security or specific investment. QuantConnect makes no guarantees as to the accuracy or completeness of the views expressed in the website. The views are subject to change, and may have become unreliable for various reasons, including changes in market conditions or economic circumstances. All investments involve risk, including loss of principal. You should consult with an investment professional before making any investment decisions.
Flame
The attached backtest provides a way to implement the strategy across a portfolio of assets
Mia Alissi
Based on the provided Python code, there are a few potential issues that might be causing problems:
The
security._vwap
is initialized but never updated with new data. You might want to update it in theconsolidate_handler
method.The
long_stop_price
andshort_stop_price
are calculated but never used. If they're supposed to be stop prices for long and short positions, you might want to implement logic that uses these values to exit positions.The
security._deviation
indicator is updated with the entirebar
object, but it might be expecting a specific value likebar.close
. Make sure to pass the correct data to the indicator's update method.The
consolidate_handler
method doesn't handle the case whensecurity._vol
is not ready,security._previous_close
isNone
, orsecurity._deviation
is not ready. You might want to add logic to handle these cases.The
end_of_day
method is scheduled to be called every day before the market close, but it's not implemented in the provided code. You should implement this method if it's necessary for your strategy.The
vol_target
variable is defined but never used. If it's supposed to be a target for position sizing, you might want to implement logic that uses this value to size positions.Remember, these are just potential issues based on the provided code. The actual issues might be different depending on the rest of your code and your specific strategy.
Here is the relevant part of the code with the potential issues:
Please review your code and make sure everything is implemented correctly. If you're still having issues, you might want to consider asking for help on the QuantConnect forum.
The material on this website is provided for informational purposes only and does not constitute an offer to sell, a solicitation to buy, or a recommendation or endorsement for any security or strategy, nor does it constitute an offer to provide investment advisory services by QuantConnect. In addition, the material offers no opinion with respect to the suitability of any security or specific investment. QuantConnect makes no guarantees as to the accuracy or completeness of the views expressed in the website. The views are subject to change, and may have become unreliable for various reasons, including changes in market conditions or economic circumstances. All investments involve risk, including loss of principal. You should consult with an investment professional before making any investment decisions.
AlexKarim
Nicolai ten Brinke
Hi Flame,
I know it's been a while since you posted this. I have taken a shot at this algorithm and compared your QuantConnect implementation with the authors python implementation that they gratefully shared:
I compared trade by trade on a couple days. From what I can tell your algo runs as intended, but at QuantConnect the real bid/ask spread is considered compared to the authors backtest, which is a drag on the results. I tried to simulate “no spread” on QuantConnect and got almost the same performance as in the paper as a result.
Speaking of almost, the daily open/close prices of SPY seem to deviate a couple cents between different data sources which leads to trades firing a little different.
I would be glad to connect to talk about this algo, it's been a while, have people around here successfully traded with it? The authors added a section to their paper that an implementation with futures works as well, for example.
Best Regards
Nico
Flame
Hi Nico
Sorry I meant to reply last weekend but ended up losing track of time. I haven't been trading this live, mainly because I prefer to trade strategies over a portfolio of assets, and I'm not sure that this held up as well as hoped in other asset classes from memory, although it was a while ago and I'm sure more in-depth research would be needed. Happy to connect to discuss further!
Best
Flame
Trader Ostburg
Dear Nicolai ten Brinke,
You stated: "I have tested it on a portfolio as well and my results were that it was consistently very good only for SPY, QQQ and TSLA." Could you please specify the backtesting period you used?
I ask because I have live deployed this strategy, and frankly, its performance over the past six months has not been good, as shown in the image below:
You are welcome to add me on Discord to discuss the live deployment issues:
discordapp.com/users/1313085859385315348
Yuri Lopukhov
So, based on the Python code in the Colab they shared, there are a few problems IMO:
I've sent an email to authors with my questions. If I get any useful response, I will add a comment.
I've attached my implementation. It is a single-asset algorithm, I'm sure you can extend it on multiple assets easily, or just use Flame's algorithm.
These are results with mid-close-price order fills and no margin calls.
Curiously, I noticed that in the custom fill model I don't have access to next bar's opening price, because that would be peeking into the future, however, standard fill model, apparently, can do that.
Yuri Lopukhov
And here are results with proper order fills and margin calls. You can see how hard even this simulation hits the results. And this only based on historical data, now add to this network delays in real-time, which can be even worse because of this behavior: Delay on the first security order of the day in live trading by Yuri Lopukhov - QuantConnect.com
So we have to be very carefull when examining academic research papers which only use trade prices at the bar close for backtesting. At least for intraday strategies.
CloudQuant2
Thanks to Yuri and all others for coding and the clear words and all your observations.
I have tested first on the authors provided Colab Notebook and fully agree to Yuri's mentioned topics.
In addition I would like to add a few more strategy challenges, most important the amount of trades and brokerage slippage would eat your profit.
To be precise with a leverage of 1 = fair benchmark comparison you'll get a Sharpe Ratio of 0.399 and a PSR of 12.329%, so the risk-adjusted performance is fairly low. Second the Total fees of $27,317.98 (16.7 % of your return) and 6940 total orders suggest that transaction costs may be eating into returns. Next the low Alpha and Negative Information Ratio: Alpha of 0.022 and Information Ratio of -0.257 mean the strategy is not adding much value above the benchmark and may be slightly inefficient in its risk-taking.
And finally the Win Rate and Loss Rate: The 63% loss rate vs 37% win rate might raise concern for confidence in execution, even though your gains are bigger than your losses.
Yuri Lopukhov
I've gotten response from Carlo on some of my questions (in italic):
1. regarding close/open gap:
You can test the strategy assuming signals are computed at the close of minute m, with execution at the open of minute m+1. The results should remain largely unchanged
2. difference between quote (bid/ask) and trades prices:
If you're working with minute-level OHLC data, some approximation is inevitable. To stay conservative, you can assume a slippage of $0.005 per share.
3. regarding slippage simulation
Since the strategy trades every 30 minutes, it’s not uncommon for the price movement in the 10 seconds preceding order placement to go against the actual trade direction. As a result, slippage is generally less of a concern compared to pure breakout strategies, where assuming execution at the exact breakout level may be overly optimistic.Keep in mind that the approach proposed in the paper is a baseline model. Investors should adjust the backtesting assumptions based on their specific trading constraints. For a more formal and academic discussion on slippage, please refer to Q15 in the FAQ section of the paper.We also recently shared some real-world slippage figures from an Interactive Brokers account where we tested a similar intraday momentum strategy on SPY with approximately $100k. You can find the details in this post.
https://x.com/ConcretumR/status/1920144786864169133
note that values of slippage and commission in this post are $0.005 (vs $0.001 in paper) and $0.012 per share (vs $0.0035 in paper).
4. Why did you choose 2% as volatility target? Is there a reasoning behind this specific value? Does it work equally good for all symbols?
This is quite subjective… you can use whatever volatility target makes you feel more comfortable. Since the strategy captures only a portion of intraday moves and often remains in cash during the day, a 2% volatility target does not actually translate into a 2% × √252 annualized volatility.
ok, well it looks like volatility target + volatility multiplier could be tools for overfitting to history
5. Sharpe and drawdown calculations:
I noticed that you are using end of day portfolio value for drawdown and volatility calculations, but doesn't it hide intraday fluctuations? If at the end of the day we have +1%, but intraday it was -4%, that will not be affected in drawdown calculation or volatility and hence Sharpe ratio. That may produce too optimistic values
This applies to all strategies: the higher the observation frequency, the higher the measured maximum drawdown (MDD). In this case, I'm not particularly concerned, as we're using a trend-following approach. It would be a more serious concern if the underlying strategy were mean-reverting, especially if combined with a martingale-style position sizing methodology.
6. Margin calls:
You can use micro futures to avoid 4x leverage issue
7. regarding alternative exit conditions
In our live implementation, we use more sophisticated trailing methods that ensemble multiple independent trailing stops.
hmm, if they don't use algorithm from paper for live-trading, does it makes sense to use live results to support it? I guess they kept some of the details out of the paper, not sure if this was used to get the main results that are in the body, but the follow up Q&A with actual live results should be taken as not directly related to the paper itself, as they may affect returns and risks heavily.
Flame
The material on this website is provided for informational purposes only and does not constitute an offer to sell, a solicitation to buy, or a recommendation or endorsement for any security or strategy, nor does it constitute an offer to provide investment advisory services by QuantConnect. In addition, the material offers no opinion with respect to the suitability of any security or specific investment. QuantConnect makes no guarantees as to the accuracy or completeness of the views expressed in the website. The views are subject to change, and may have become unreliable for various reasons, including changes in market conditions or economic circumstances. All investments involve risk, including loss of principal. You should consult with an investment professional before making any investment decisions.
To unlock posting to the community forums please complete at least 30% of Boot Camp.
You can continue your Boot Camp training progress from the terminal. We hope to see you in the community soon!