A recurring help topic in the forums is scheduling intraday universe selection, so I thought to share a template for how I've done this in the past, in the attached backtest,
This is not a tradable strategy, it simply shows one approach to intraday stock universe selection.
____________________________________
General Architecture
There are three universe selection phases: Coarse, Fine and Intraday (scheduled). In the Fine and Intraday selection, a SymbolData object holds each stock's indicators, which are seeded with historical data at the appropriate time. Once an asset meets criteria for all three phases, we open a position, and close it position once exit criteria is met.
The Code
I’ve tried to modularize and comment the code so it's easy to follow, repurpose and replace the screening criteria with your own.
- Coarse Universe Selection
Using LEAN’s standard ‘AddUniverse’ approach for coarse selection, select up to ‘X’ top liquid stocks that meet specified coarse criteria. For example: price > $50.
- Fine universe Selection w/Indicators
Using LEAN’s standard ‘AddUniverse’ approach for fine selection, select up to ‘Y’ stocks that meet specified criteria for Daily Screening: For example: Stock price is above 10 day EMA.
Note: When the Fine universe selection routine is called, data isn't available for the symbols, so we call self.History() for each of the stocks (that passed coarse selection). The stocks that meet fine criteria will be stored for later use in the scheduled intraday routine. Alternatively, if we were not performing any further intraday filtering we could return these assets in a list at the end of the function, instead returning the blank list as we do now.
- Scheduled Intraday Universe Selection w/indicators
At a scheduled time during the day, select stocks that meet intraday criteria. For example: positive 4 hour momentum. Select the top ‘Z’ stocks, ranked by momentum.
- Subscribe to Data for Screened Stocks
When a stock passes all screening, we ‘add’ the security to the algorithm after the intraday screening. This subscribes to the data feed for the symbol, which we will need to calculate indicator values for our exit.
Note: In a scenario where we don’t need intraday filtering, then our fine selection function will invoke the adding of securities, by returning a list of symbols (instead of a blank list) to the LEAN framework.
- Open Positions for Screened Stocks
Adding the security invokes the onSecuritiesChanged handler, where we queue buy orders in a ‘queuedPositions’ list. This queue is processed (ie: positions are opened) in the next call to OnData, when data is available.
- Close positions for stocks when exit condition is met
Exit when stock price is below the EMA. Or, optionally, exit at End of day , if the useEoDExit flag is set.
Customization
To change entry logic for your own needs, modify these methods:
## In main.py:
GetDailyScreenedStocks(stocksToScreen)
GetIntraDayScreenedStocks(stocksToScreen)
## In SymbolData.py:
DailyScreeningCriteriaMet()
IntradayScreeningCriteriaMet()
Known Issue
For some reason the first set of positions take place 30 minutes later than expected. I haven't had the time to investigate it.
____________________________________
Feel free to re-use this, customize it, make it better and re-share.
Mak K
Hi .ekz. ,
Thanks for this, you are doing a great service to the community!
.ekz.
You're very welcome Mak K!
Feel free to post any feedback and/or improvements here on this thread.
Fred Painchaud
Hi ekz,
Nice work!
I've made some modifs here and there. Mostly style (properties, ifs without (), etc). And 1 or 2 additional checks where needed (in ProcessQueuedPositions). 1 or 2 less checks when not needed (in SymbolData). Changed the list shallow copy (in ProcessQueuedPositions) to use the current fastest one in Python (from 3.5) which is [*] (generalized unpacking - see PEP 448 - very useful and powerful in many situations).
Anyway - diff for main.py is here: https://www.diffchecker.com/Bp7Nw9oF
And diff for SymbolData.py is here: https://www.diffchecker.com/ZLZcMORI
Modified code is attached as backtest.
Fred
Brady Murakami
This structure basically what I have been trying to create! Thanks .ekz.!
.ekz.
Thanks Fred Painchaud! I knew there were some needless conditions in there. Thanks for the tip on the faster list copy, too.
I do use parentheses generously, indeed, haha. Over the years, I've found that it's easier to visually scan code blocks (especially conditionals) with strategic use of parentheses and spacing.
Brady Murakami: my pleasure!
Brady Murakami
Hey .ekz. I have some questions about your code and if you could help me understand that would be amazing. I have been staring at it for a few hours and rummaging the internet to try and figure things out. I took Python a few years ago when I was in school but am very new to Quantconnect.
For the SymbolData Class, should there not be an object created for it? Like symbol = SymbolData()? You are obviously doing it somehow but I can quite follow.
When referencing the SymbolData class from the main algorithm, how is it doing this? For example the daily screening criteria looks like it doesnt actually pull anything from the SymbolData class? Again you obviously doing it I just cant figure out how.
I have plenty of other questions but these are my main ones.
.ekz.
Hi Brady Murakami, no worries.
1. Creating instances of SymbolData
For each Symbol we are interested in, we create an instance of SymbolData. We do this in the method: GetDailyScreenedStocks() . In this method we are doing our first round of screening, and is the first time we need SymbolData. We then store these instances in a dictionary: self.SymDataDict for later reference. Here are the relevant lines of code:
2. Referencing the SymbolData
For a given symbol, we access its SymbolData instance that lives in the dictionary mentioned above. We do this in multiple functions: GetDailyScreenedStocks, GetIntradayScreenedStocks, ProcessedQueuedPositions, etc. Here is what that code looks like:
Hope this helps
Kristofferson V Tandoc
Hi .ekz. and Fred Painchaud,
Awesome work btw.
I'm new here and I stumbled on thread. I just wanted to backtest it myself and I get this:
Stack Trace:
Trying to retrieve an element from a collection using a key that does not exist in that collection throws a KeyError exception. To prevent the exception, ensure that the key exist in the collection and/or that collection is not empty.
at ProcessQueuedPositions
symbolData = self.symDataDict[symbol]
in main.py: line 285
at OnData
self.ProcessQueuedPositions()
at Python.Runtime.PythonException.ThrowLastAsClrException()
at Python.Runtime.PyObject.Invoke(PyTuple args in main.py: line 90
Note: I have only changed the minimum asset price in the coarse filter to >10. That's it.
Any thoughts?
Kristofferson V Tandoc
In addition, looking at the orders, it seems like some (if not a lot) orders are not closed within the same trading day. Just thought I'd point this out.
Derek Melchin
Hi Kristofferson V Tandoc,
We were unable to reproduce the stack trace. Maybe try cloning the project again and changing the minimum price in the coarse selector function.
Best,
Derek Melchin
Want to invest in QuantConnect as we build the Linux of quant finance? Checkout our Wefunder campaign to join the revolution.
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.
.ekz.
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!