👋 Introduction
Hello fellow quants — I want to share a recent learning project I worked on that finally ran smoothly in backtest. A quick disclaimer: I’m a beginner, and my primary goal here was to learn QuantConnect and the LEAN framework, not to build a profitable trading system.
The project was inspired by a blog post from Quantitativo, who explored a gap-down reversal strategy. I used that concept as a jumping-off point to build my own version.
💡 Hypothesis
Stocks in an uptrend often revert after a sharp overnight gap down.
This strategy attempts to capture that short-term reversion by entering positions right after the open and closing them the same day.
🧠 Strategy
- Universe Selection: S&P 500 stocks trading above their 100-day SMA.
- Entry Rule: At market open, if a stock gaps down more than 1.2× its 14-day ATR relative to yesterday’s close, we go long.
- Exit Rule: Liquidate all positions 15 minutes after the open.
I drew ideas from two QC Bootcamp 101 lessons but had to combine them to meet the needs of this intraday strategy:
- Lesson 6 – Fading the Gap
Had intraday data and volatility-based entry logic, but lacked a dynamic universe manager. - Lesson 7 – 200/50 EMA Momentum Universe
Had universe filtering via momentum, but used daily resolution and no volatility measures.
📊 Backtest
You can check out my backtest here. While this strategy isn’t optimal (I imagine I overcomplicated the SMA universe selection and ATR update method) it served its purpose as a learning tool.
If you’re new to QuantConnect and working through:
- Universe selection
- Intraday timing logic
- Indicator warm-ups and updates
…this code might serve as a helpful reference. It took time to get everything running correctly, but I learned a ton — and I’m glad to share it.
🔍 Key Challenges & Takeaways
- Time handling: Understanding how scheduled events, data updates, and indicator values interact over time was critical.
- Indicator updates: Indicators like ATR, which require TradeBar inputs, must be handled differently than those like SMA.
- Universe selection: Managing a dynamic symbol universe (and cleaning up unused symbols/indicators) was trickier than expected. In the codes current form, backtesting is slow.
🚀 Final Thoughts
I’m just getting started, but I believe in sharing progress even before it's perfect. This strategy may not beat the market, but it helped me understand the platform, debug tricky issues, and build confidence.
If you're exploring something similar or want to exchange ideas, feel free to reach out — I’d love to compare notes.
Mia Alissi
Here are the key functional or logical bugs in Robert's code (excluding naming, allocation, and imports):
The algorithm never sets
self.is_warming_up = False
after warm-up completes, so normal logic inon_data
and scheduled functions likely never runs as intended.The line
self.set_warm_up(2, Resolution.DAILY)
is not valid QuantConnect Python API. The official call isself.set_warm_up(time, resolution)
(case-sensitive), but the double underscored, camelCase method isn’t defined in Python.The use of
self.add_universe(self.universe.etf(spy), ...)
is incorrect: there is noself.universe.etf(...)
API. The correct approach is to useself.add_universe(...)
with a coarse or fine selection function.UniverseSettings is set after the universe is added:
self.UniverseSettings.Resolution = Resolution.MINUTE
should be set before callingadd_universe
for it to affect universe subscription data.The ATR indicator is only updated at 9:31 in
on_data
, so it becomes stale (not updated with each new trade bar, as ATR typically requires) if using minute resolution.The
algorithm.warm_up_indicator(...)
call inSelectionData.__init__
is not the correct API—it should beself.warm_up_indicator(indicator, resolution, symbol)
or useself.warm_up_indicator
with the right argument order. Python LEAN may expect a different pattern, such as usingself.register_indicator
.The history data access pattern
if symbol in history.index.levels[0] and len(history.loc[symbol]) >= 1:
may raise errors ifsymbol
is not present in the history index. This is not safe for all multi-index DataFrame structures, and can result in runtime exceptions if a symbol is missing.The use of
self.set_holdings([PortfolioTarget(s, weight) for s in symbols_to_trade], True)
is not correct—set_holdings
is not designed to accept a list;set_portfolio_targets
should be used instead.The routine call to
self.liquidate()
inclose_positions
will liquidate all positions, including any that might be held for other purposes (e.g., SPY anchor if added as a security).The plotting call has a syntax issue:
self.plot("Universe", "Symbols Traded", len(symbols_to_trade))
should work, but again, double-check for proper usage and initialization of charts.These are the core technical and logical bugs affecting universe construction, indicator plumbing, update frequency, data handling, and scheduled execution.
Sources: Opening Range Breakout for Stocks in Play, Standardized Unexpected Earnings, Universe Selection Regression Algorithm, E T F Constituent Universe Filter Function Regression Algorithm, Universe Data - QuantConnect.com
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.
Robert Wiener
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!