Files
quanxiel/course/07-consolidators-rolling-windows.md
T

26 lines
23 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 07 · Consolidators & Rolling Windows(合并器、滚动窗口、事件调度)
- **系列**:Full Algorithmic Trading Using Python
- **频道**:TradeOptionsWithMe | **本集**:Consolidators & Rolling Windows(合并器、滚动窗口、事件调度)
- **时长**:22 分 40 秒 | **原视频**:https://youtu.be/rQOn9iTchIg
- **本地视频**:[videos/07-consolidators-rolling-windows.mp4](videos/07-consolidators-rolling-windows.mp4)
## 🎯 本集要点(中文导读)
本集讲 **Rolling Window(滚动窗口)**、**Consolidator(数据合并器)**、**事件调度**。
- **Rolling Window(滚动窗口)**:定长的"数据数组",自动"加最新、去最旧"。
- 比反复用 History 请求近期数据**高效得多**(History 每次都重复取大量相同数据)。
- 创建:`RollingWindow[数据类型](容量)`(如存 30 个收盘价,或 10 个 TradeBar);通常在 `Initialize()` 里创建。
- 添加:`.Add(x)`;访问:像列表,但**反向索引**——最新数据在**索引 0**,最旧在最后。
- 常与**指标**搭配:把指标 `Updated` 事件里的值存进 rolling window(存 `IndicatorDataPoint`)。
- **Consolidator(合并器)**:把细粒度数据合并成自定义周期的 bar(如把分钟数据合并成 4 小时 bar)。用 `Consolidate(...)` 注册;合并出的 bar 会触发事件。
- **事件调度(Scheduling)**:可在指定时间/日期触发方法(如每月调仓),用于定时逻辑,而不是依赖每根 bar。
- 本集用一个"**跳空缺口(gap)**"策略示例:检测近 30 天数据里的向下跳空 → 买入;向上跳空 → 做空;并用回测图**人工核对**订单发生日的行情,验证逻辑是否正确(交叉验证是很实用的排错手段)。
## 📝 完整文稿(英文 · 自动转写)
> 由本地 ASR(faster-whisper)从视频音轨转录,未人工校对,供检索/精读使用。原始讲解以上方视频为准。
Hello and welcome back to the 7th video of this algorithmic trading course. If you are new to this course, I recommend going back to the first video and starting this series from the beginning. In this video we will cover rolling windows, consolidating data and scheduling events inside of QuantConnect. As in the past few videos, we will start with a theory based portion and end the video with an example Python bot that will show you how to apply the learned concepts to an actual bot. As I had said, let's not waste any more time and start by looking at rolling windows inside of QuantConnect. The rolling window simply is an array of data that allows you to keep track of data over a specified time frame. For instance, you could create a rolling window that keeps track of the closing prices of SPY over the past 30 days. From the things we learned in the previous video, we could also accomplish this with history requests. However, the problem with history requests is that they aren't very efficient if you're requesting recent data over and over again. For example, requesting price data for the past 30 days every day is very inefficient since you are basically requesting almost the same data over and over again. A far more efficient way to accomplish this is by using a rolling window since this just updates the most recent data point and removes the last data point from the array. Let me show you how you can create and use the rolling window inside of QuantConnect. You can create a rolling window object with the rolling window constructor. As an argument, you can specify how many data points this rolling window should be able to hold. Besides price data, rolling windows can also hold all other kinds of data. For this, you just have to specify the type within the square brackets. For example, a rolling window that can hold up to 10 trade bar objects can be created like this. Usually, you would want to create a rolling window in the initialized method of your algorithm or when new data for a given security is added. To add data to a rolling window, you can use the add method. For example, adding the close price of SPY to this close window rolling window would look like this. You can access the data in a rolling window in the same way as you would access the data in a normal Python list. Just note that rolling windows use reverse list access semantics. This means the most recent item in a rolling window is in the first spot and the least recently added item is in the last place. Like I already mentioned, rolling windows allow you to store all kinds of data. This includes indicator values. This is a particularly common use case for rolling windows, which is why I quickly want to cover it before moving on to consolidating data. These are the same values emit a so called updated event every time they receive an update. We can use this to our advantage by creating an event handler and attaching it to this updated event. This can be accomplished with this line of code. This line makes sure that our custom SMA updated event handler method is called every time the SMA indicator for SPY gets updated. To make sure that the correct data is saved into a rolling window, we create a rolling window for the type indicator data point like this. Inside of our custom SMA updated method, we then simply add the updated data to our rolling window. With that, we now have a rolling window that can be used to access the five most recent values of our SPY SMA indicator. To learn more about indicators, I recommend checking out the previous video of this series. Next up, let's take a look at how consolidating data inside of QuantConnect works. So far, we've only been able to use data in one of the existing resolutions. The existing resolutions cover tick, secondly, minutely, hourly and daily data. However, in some cases, you might want to use data that covers another period. For instance, you might want to create 5 minute bars, 30 minute bars, weekly bars or bars covering some other time frame. For this, we can consolidate the data from one of the existing resolutions to a custom resolution. For instance, you could consolidate 5 1 minute bars into 1 5 minute bar. You could then use your custom 5 minute bars for indicators, rolling windows or in any other way that you normally would use the 1 minute bars. So let's now look at exactly how we can accomplish this. By default, for all asset classes except for Forex, the bars are consolidated to trade bars. Since Forex only supports quote bars, the consolidated bars obviously also are quote bars. Consolidating bars can be done with self dot consolidate, which takes 3 arguments. The first argument is the symbol for which you want to consolidate data. The next is a period specifier, here you can go with a time delta, resolution or calendar object. The final argument is an event handler function that you need to create, more than that in a few seconds. Here you can see 3 ways of consolidating SPY trade bars. The first line specifies the time by using time delta. The second line uses resolution dot hour and the third line uses calendar dot weekly to consolidate the data to weekly trade bars. After specifying an event handler function as the third argument, you should create and implement such a function. The option event handler takes one argument, which is the resulting bar. Unless you're trading Forex, this will be a trade bar object. Note that you can choose whatever name you want to for this event handler as long as it's not taking already. Just make sure to specify the correct name as the third argument in the self dot consolidate function call. You can treat these event handlers like the on data method except that they are exclusively for the consolidated bars for the symbol that you specified in the consolidate helper function. In other words, this is where the resulting bars of the consolidation are sent. Depending on your algorithm, you could use them for your trading decision making only for logs or not at all. I already mentioned earlier that besides using these custom resolution bars for basic trading decisions, you can also use them for indicators. To demonstrate this, let me show you an example of a simple moving average that uses 10 7 minute bars for its calculations. To accomplish this, we can't use the shortcut indicator helper methods. Instead, we need to use the written out versions. So for a 10 bar simple moving average, we use the simple moving average constructor and pass 10 as the argument. Then we use self dot register indicator to register this indicator for automatic updates. As the third argument, we specify a time delta of 7 minutes. These three lines of code now allow you to access a 10 7 minute bar simple moving average for SPY through the self dot SMA variable. For more details on indicators, check out the previous video of this series. Next up, let's look at how we can combine consolidating data with rolling windows, since it can sometimes be useful to have a rolling window that stores data with the custom bar resolution. This is very simple to do. All you have to do is first create a standard rolling window object for your bar type. So a quote by rolling window for Forex and a trade by rolling window for other assets. Then you use the self dot consolidate helper function to consolidate the data and finally you use window dot add inside of the consolidated event handler to add the consolidated data to your rolling window. Note that if you don't want to do anything else in your consolidated event handler, you can even use a lambda function to directly implement this functionality in one line. Last but not least, let me quickly present to you how you can remove consolidated from your algorithm. You might be asking yourself why you'd want to remove a consolidated in the first place. Some algorithms will dynamically add consolidated. This is especially common if your algorithm has a dynamic universe of assets that it trades. In that case, you would usually create a consolidated once a new security enters your tradable universe and you would want to remove that consolidated once the security exits your universe. You would want your algorithm to do this since consolidateds take up computing power and space. So removing unnecessary consolidateds can boost the efficiency of your algorithm. To do so, you can use the remove consolidated method of the subscription manager like this. As arguments, you need to specify the symbol and your consolidated. You will learn more about dynamic universes in one of the next videos, so don't worry if my explanation for why this is useful went over your head. Before we move on to the coding part of this video, let me briefly demonstrate how you can schedule your own events. This is very easy to do so it won't take very long. Most of the code for the algorithms that we created thus far was inside of the undata event handler. Besides using such predefined event handlers, QuantConnect also allows you to create and schedule your own methods. You can do so with self.scheduled.on which takes three arguments. The first and second argument need to be a date and time rule respectively. You specify on which dates and at which time your scheduled event will take place. The final argument is the method that you want to schedule. Since these are quite simple, I'll just give you one or two examples. For all the rules, make sure to check out the documentation page. You could for example, schedule a method 10 minutes after SPY is open every day that SPY trades as follows. With this, the everyday aftermarket open method will automatically be called at the specified time. Scheduling a method on every Monday and Friday at 12 o'clock would look like this. If you only want your method to be called once at a specific date and time, the schedule.on functionality also allows you to do so. But like I said, for all the details on date and time rules, check out the documentation page that I linked below. With that, we are now done with the theory part of this video. Next up, let me quickly present to you what strategy we will be implementing for the second part of this video. In the past few videos, we've only coded bots that focus on longer timeframes such as multiple days or even months. In this video, we will switch things up a little and create a simple intraday trading bot. This bot will once again only trade in one security. For this video, I will use the SPY ETF, but like always, you can try this with whatever security you want to. The idea behind this bot is that every morning the bot will compare SPY's last closing price to the current market open price. In case of a gap up, the bot will open a short position and in the case of a gap down, the bot will open a long position. In other words, we are betting on a reversal of the overnight gap. For the market closes, the bot will always liquidate any open positions to take profits or cut losses. This means there should never be any overnight risk. As in the past videos, my goal is to demonstrate the just learned concept with this simple bot, so please don't expect it to perform particularly well. This is not yet an algorithm that you would actually want to trade real money with. That said, let's now head over to QuantConnect and start coding. As always, we create a new algorithm in the lab tab. Here, we will not use the strategy builder and instead click on exit to create a new blank template algorithm. The first thing we do here is implement the initialize method. The first few lines of code here are the same as in the previous videos, which is why I won't cover them in any detail. In short, we just set dates and a balance for back testing purposes. Thereafter, we add data for SPY and save SPY symbol to the self.symbol variable. As to resolution, we will choose immediately data. Next up, we will create a rolling window that stores trade bar objects. We will use this rolling window to compare the current price of SPY to its last closing price. For this, we don't need a long rolling window, which is why we initialize it to only hold up to two elements. After that, we create a consolidator for the Minidly SPY data. The objective of this consolidator is to consolidate SPY's Minidly bars to daily bars. As the third argument, Consolidate takes a consolidation event handler that we have to create. This is called every time a new daily bar is consolidated out of the Minidly bars. I will call this method custom bar handler and define it below. Note that it is important not to put brackets behind the specified method when using self.consolidate. Since we will use our rolling window to access the previous day's closing price, we want it to store daily trade bar objects. To add such a trade bar object, we can use the add method. That's also exactly what we will do inside of our custom bar handler. The bar parameter of this method is the consolidated daily bar which we want to add to our rolling window. Besides that, we don't really need to do anything else inside of this custom bar handler method. Next up, let's move on to the on data method and start writing some code here. The first thing that we want to do inside of the on data method is check whether our rolling window is ready. Rolling window is ready once it is filled up with data. Just like with indicators, we can use the isready attribute to check this. If it's not yet ready, we simply return. This return statement should only be reached in the beginning of our algorithm since thereafter the rolling window should always be filled with data. In the next step, we want to check the current time. We do this since we want our algorithm to trade right after the market open and not at any other time. To check this, we check if the hour equals 9 and the minute equals 31. We use 931 instead of 930 because bars are passed at their end time. This means the 1 minute 930 bar is first accessible at 931. If this is not the current time, we once again just return since we don't want this algorithm to make any opening trades at any other time. So in the remainder of the on data method, we can now safely assume that the market has just opened since we otherwise would not have surpassed this return statement. What we have left to do now is implement the trade logic. For this, we first check if there was a gap up in SPR's price. We do this by comparing the open price of the 931 minute bar with the close price of the daily bar of the previous day. This daily bar can be accessed through our rolling window. Since the most recent data is in the beginning of rolling windows, we index our rolling window with index 0. Since we are only interested in those days where there was an actual gap up, we multiply the previous day's closing price by 1.01. This will ensure that this condition is only fulfilled if SPR's price gaped up by 1% or more. Note that this 1% number is an absolutely arbitrary choice. When developing your own algorithms, you should try to put some research into finding meaningful values for parameters such as this one. You will learn more about research and optimization procedures later in this series. For now, just note that generally speaking, you should try to keep the number of arbitrary parameters in your algorithm relatively low. Often, it is better to use dynamic models instead of fixed parameters for values such as this one. You could, for instance, couple this parameter to the 30-day volatility of SPY so that your algorithm dynamically adjusts its behavior depending on the market conditions. But like I said, more in this in later videos. If this condition is fulfilled, we know that SPY's price gaped up by at least 1% since yesterday's close. In that case, we want to establish a short SPY position which we can achieve by passing minus 1 to set holdings. Now all that's left to do is implement the same logic for a gap down and going long. Here, we simply flip the gray through or equal sign the other way and multiply yesterday's closing price by 0.99 instead of 1.01. This condition will equal true if SPY's price gaped down by at least 1%. If that's the case, we establish a long position. For this, we once again used the self.set holdings helper method. So far, we've only implemented the logic for opening a long or short SPY position. All that's missing now is the part that closes this position before the market closes. For the exit logic, we will schedule a custom method that is called 15 minutes before the market closes. We can accomplish this with self.scheduled.on inside of the initialised method. As the day drool, we specify that our method should be called every day that SPY trades. As the time rule, we choose 15 minutes before the market closes. Note that this will account for days where the markets close early. On Christmas Eve for instance, our scheduled method will be called at 1245 instead of 1545 since the markets close early on that day. We will name the method that we want to schedule exit positions. Inside of this exit positions method, we will close any open positions. For this, we can use self.liquidate and optionally specify self.symbol as an argument. Without passing a symbol, the positions in all securities will be liquidated, but since this algorithm does not trade any other securities, this won't really make a difference. Now this exit positions method is called every day that SPY trades 15 minutes before its close. If we don't have any open positions, self.liquidate won't have an effect on your algorithm. With that, we are already done implementing this trading bot. I hope this example bot shows you how powerful QuantConnect and the Lean Trading Engine are. With only about 30 lines of code, we've just coded a fully functional trading bot. Granted, this is a very simple bot and you should not actually use it for trading, but in my opinion 30 lines of code is still very impressive for such a trading bot. Let's now build and back to this bot to check if it's actually doing what it's supposed to do. Once again, I won't look at the performance of this bot in any detail since the purpose of this bot is to show you different aspects of the API and not to perform particularly well. But a quick glance at the equity chart already tells you that this does not seem to be a very well performing strategy. Furthermore, since this bot trades quite actively and never holds a position for more than a day, it seems to generate relatively high fees. What I actually want to look at however is the overview of the orders that this bot sent. We can do this inside of the orders tab at the bottom of the page. From the first few orders, we can already see that this bot does in fact open its trades at 9.31am and close them 15 minutes before the market close, which is what we wanted to do. Let's now look at a daily chart of SPY over the same period as some of these trades and check if they're actually where your gap ups or gap downs on these days. For this, I will just use daily candlestick SPY chart from TradingView, but feel free to use whatever charting platform you want to. Note that if the prices inside of QuantConnect deviate from those shown on a chart, this likely is due to the default data normalization mode which is split and dividend adjusted. For more details on data normalization modes, check out the fourth video of this series. The first order that we sent was sent on the 6th of February 2018. Since this was a buy order, we would expect there to have been a gap down of at least 1% from the previous days close on this day. So let's check if that was the case on our SPY chart. As we can see, there was in fact quite a big gap down on this day, but later during the day, SPY's price did rise quite a bit, which likely led to a good profit on this particular day. The next trade was in the 9th of February 2018, and this was a short trade which means the SPY should have gapped up. If we look at the chart of SPY on this day and compare its price to the closing price of the day before, we can see that there actually was a gap up. So judging from these two days, everything seems to be working properly. Sometimes cross-referencing what your alginess is doing to other data can be a great way to find out if it's doing everything as planned, or if not, this can be a great way to potentially find out why it's not functioning properly. That said, if now hopefully learned how to consolidate data, what rolling windows are, and how to use them, and how to schedule events inside of QuantConnect. In the next video, we will look at how to create a dynamic universe of securities so that you aren't only limited to algorithms that only trade one or a few of the same securities over and over again. Besides that, we will learn how to use fundamental data for your decision making and much more. If you enjoyed this video and want to support this series, make sure to smash the like button, subscribe, and turn on the notification bell for more content like this. Thanks for watching.