Files

23 lines
22 KiB
Markdown
Raw Permalink 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.
# 13 · Options Code-Along(保护性看跌期权实战)
- **系列**:Full Algorithmic Trading Using Python
- **频道**:TradeOptionsWithMe | **本集**:Options Code-Along(保护性看跌期权实战)
- **时长**:26 分 6 秒 | **原视频**:https://youtu.be/Lq-Ri7YU5fU
- **本地视频**:[videos/13-options-code-along.mp4](videos/13-options-code-along.mp4)
## 🎯 本集要点(中文导读)
**完整代码实战**:给现有股票多头仓位买**保护性看跌期权(protective put)**。
- **策略目标**:降低组合波动、对冲下行风险,同时不浪费太多钱在"不必要的保护"上。
- **为什么用 put**:负 delta(标的下跌时获利)、正 vega(隐含波动率上升时获利,而 IV 通常在下跌时上升)、风险有限而保留了上行空间。
- **入场条件**:只在**隐含波动率相对较高**时才买 put(用过去半年的**标准化 IV**判断)——即"等市场已经跌了一些再买保险",而不是一直买。
- **结果解读**:作者演示如何用回测图表/指标/订单/日志评估;⚠️ 明确说明这是**教学示例,不保证盈利**。
- 建议:逐行读代码、自己动手改,是熟悉 QuantConnect 期权 API 的好方法。
## 📝 完整文稿(英文 · 自动转写)
> 由本地 ASR(faster-whisper)从视频音轨转录,未人工校对,供检索/精读使用。原始讲解以上方视频为准。
Welcome back to another video. In this video we're going to develop and implement an options trading algorithm inside of the QuantConnect platform using Python. If you want to code along, make sure to first create a free QuantConnect account. There's a link in the description box below. Otherwise, I want to start with a short disclaimer. Name me that the main purpose of this video is to give you an overview of the trading algorithm development process, the QuantConnect platform and its API. Therefore, I'm not claiming that the strategy that we're going to implement is necessarily a very good or profitable strategy. So please keep this in mind when watching this video. With that being said, let's cut right to the chase and start developing a trading strategy. Before we start actually implementing anything, though, let me first present the strategy on a conceptual level so that you better understand what our code is supposed to do. The strategy that we're going to implement is not a very active strategy. The goal of the strategy is to reduce volatility and protect against downside moves in an existing long equity position. To accomplish this, we need an investment with an inverse correlation to our long stock position. For this, we will be using put options because of several reasons. First of all, they have a negative delta, which means that their profit from downward moves in the underlying asset. Secondly, they have a positive vega, which means that their profit from an increase in implied volatility. And this is good because implied volatility usually goes up when prices go down. In other words, implied volatility typically has an inverse correlation to the underlying asset. Last but not least, put options have limited risk while leaving a lot of upside potential. The problem with this strategy so far is that constantly buying put options as protection can be very expensive and negatively impact the overall performance. That's why we only want to buy put options if we suspect an upcoming market decline. We measure this by looking at the normalized implied volatility of the past half year. If implied volatility is relatively high, we buy a put option as protection. Usually, this will mean that the market has already dropped by a certain amount. But in the long run, we hope that buying protection a little later will be better than wasting money on unnecessary protection on a constant basis. Before we head over to the QuantConnect platform, let me quickly recap the strategy one more time. The goal of the strategy is to reduce downside risk and volatility of an existing long equity position without wasting too much money on unnecessary protection. We accomplished this by buying put options when volatility is relatively high. We used this high level of implied volatility as a sign for a potential upcoming market decline. With that being said, let's now head over to the QuantConnect platform and start writing some Python code. If you haven't done so already, make sure to quickly create a free account before continuing to watch this video. If you have done so, navigate to the lab tab and click on Create New Algorithm. Then a collection of template modules will pop up, but we won't be using any of them in this video. Instead, we click on Create Algorithm to create the new algorithm. For now, you should see an on data and initialize method. We will use both of these methods for our algorithm. But before we start implementing them, let's first import two things. Firstly, we will have to import time delta from date time, and then we will import everything from the QuantConnect.data.custom.cboe module. We will use this custom cboe data to get pricing data for the volatility index or VIX, which measures the implied volatility of the S&P 500 index. We will use this data to create the indicator that measures how high or low implied volatility is compared to recent history. But before we get to the volatility indicator, let's first implement our initialize method. We will first set the start and end date for our back tests. We will choose the first of October 2017 as a start date and the first October 2020 as an end date, but feel free to try out different periods if you want to. Next up, we will set a starting cash balance for the back tests. I will go with $100,000 for now. Then we will have to add the underlying security that we are going to trade. For this video, I will choose the ETF SPY as an underlying security since it tracks the S&P 500 index and has great options liquidity. We will add minutely data, since as of right now, the QuantConnect API only supports minutely data for options. Furthermore, only raw pricing data is supported with options, which means that the historical data does not adjust prices for stock splits or other similar events, but this shouldn't create any problems for us. Lastly, we save the symbol of our underlying asset into the variables self.symbol. Then we also want to add VIX pricing data, which we can do with the add data method. We save the symbol in self.vix so that we can later access the data. Then we initialize the volatility indicator to zero. We will later use this indicator as an entry signal for our options position. Besides that, we also have to initialize a few more variables. First, we initialize self.contract to an empty string and self.contract added to an empty set. self.contract added will keep track of which options contracts we have added so that we don't unnecessarily add options data multiple times. Next up, we will initialize most of our parameters. The first parameter is called self.days before exp and it represents how many days before expiration we will close our options position. We want to close the position before expiration so that we don't exercise any of our options. This means that two days before expiration we will close our long put position if we have one. I quote the next parameter self.dte and it represents our target time to expiration in days. So a value of 25 means that we aim to buy a put option with about 25 days until expiration. self.otm is the target percentage that our option should be out of the money. So 0.01 means that we want to buy a put option that is out of the money by 1%. Next up, we have self.lookbackiv which represents the number of days our volatility indicator looked into the past. So 150 means that it compares the current VIX level to that over the past 150 days. Self.iv level is a level of our volatility indicator above which we want to buy put options as a protection against the potential market crash. In other words, this is the signal line. Then we specify what percentage of our portfolio will go to the underlying asset. Since this strategy is a passive strategy, I will initialize this variable to 0.9 which means that about 90% of our portfolio will go to SPY. Last but not least, self.options.alloc specifies how many options contracts we buy compared to the number of shares of the underlying asset that we own. So a value of 90 here means that we buy one option for every 90 shares that we own. The standard option contract covers 100 shares of the underlying. So a value of 90 here means that when we buy put options as protection, we buy more than enough to cover the entire portfolio. The lower this value is, the more protection we buy. I know the initialized method can be a little boring, but we're almost done with it, so stick with me. We just quickly have to schedule two methods that we want to be called on a regular basis. First, we schedule our so called plotting function, which we use to create custom charts. We wanted to be called every market day that SPY trades. Or more specifically, we wanted to be called 30 minutes after every market open. We then scheduled the VIX rank method in the exact same way. This method will be used to calculate our volatility indicator value. Last but not least, we have to set a warmer period for our algorithm so that the volatility indicator has enough data to be used correctly right from the get-go. Now that we are done implementing the initialized method, let's create and implement the VIX rank method so that we can use our volatility indicator for entry signals. To create and update the volatility indicator, we first need to look at the past VIX price data. We can access VIX's price history by calling the history method. We then specify the symbol, time frame and resolution of the requested data. For the symbol, we use self.vix and for the time frame, we use the self.lookback.iv variable to get the price history of the past 150 days. Furthermore, we request daily data since we are only interested in the high price, low price and current price over this time frame. The history method then returns a data frame with a high, low, open, close price and volume for each day over the specified time. The next step is to actually calculate the value of our volatility indicator which we will call self.rank. To calculate this value, we have to normalize the current VIX level by looking at its high and low price. The exact formula that we are going to use is the current IV level minus the low price divided by the price range. In simple terms, the VIX rank method calculates how high or low the current VIX level is compared to recent price history. So a value of 1 would mean that the current VIX price is at a new high, whereas the value of 0 would mean that it is at a new low, and a value of 0.5 means that the current VIX price is right in the middle. Now we are finished implementing the VIX rank method, which means that we can move on to the on data method, which is called every time our algorithm receives new data. This should be pretty much every minute, since we request it minutely SPY data in the initialize method. The first thing we have to do is make sure that our algorithm is finished warming up. We can do this with a simple if statement. If it's not finished warming up, we just return and do nothing. Next up, we check whether we already are invested in the underlying asset. If we aren't, we use self.setHollings to buy shares until I put four years' holdings, and this asset are about the percentage of self.percentage. The next step is to check whether our volatility indicator self.rank is above the entry signal level, which we initialize to 0.5. If it is, it means that SPY's implied volatility is relatively high, and we therefore want to buy put options as protection. We do so by calling the self.buyput method, which is a method that we will implement in a few minutes. Now, there only is one thing left to implement in the on data method, namely checking whether we are too close to the expiration date. If we are, we want to close any existing put positions. Obviously, we only want to do this if we already are invested. That's why we first check whether self.contract is non-empty. If it is empty, we should be invested in a put option. We then check whether the expiration of this option is within two days of today. If it is, we want to close it so that it doesn't expire as exercised. To close the option, we use self.liquidate, there after we lock that we close the option and reset self.contract to an empty string. Now that we are done with the on data method, let's implement the buyput method, which will actually buy the put options. But before this method can buy any options, it first has to get the data for these options. So if you currently don't have a contract to trade, we first get it by calling options filter, which is another method that we have to implement here after. After saving this contract to self.contract, we return. We do this because adding new options data usually takes at least one iteration of your algorithm. If we already have a contract to trade, we aren't already invested in it and its data is ready, we want to buy this put option. This can be accomplished with a self.buy, which takes two arguments, the first is the symbol of the actual security, and the second one is the quantity. Since we want to buy the earlier specified put option, we use self.contract for the first argument. And for the quantity, we divide the number of shares of the underlying that we own by the self.options.alloc parameter, which we initialized earlier. But before we can use this value, we have to round it to the nearest integer. This method will now buy one put option for every 90 shares of the underlying security that we own. Note that the 90 shares is the value of self.options.alloc, which we initialized in the beginning. Next up, let's implement the options filter method, which we use to find out which option we want to trade and add the correct data for this option. The QuantConnect API has multiple ways to trade options. The easiest way to trade options is by using set filter and by iterating over option chain objects. The problem with this is that it is unnecessarily as huge amounts of options data, which makes the algorithm very inefficient. In fact, it makes it so inefficient that it can take many hours for you to back test your algorithm over any noteworthy length of time. That's why we won't be doing it this way. Instead, we will be using the so-called option chain provider, which allows you to manually add the data for only those options that you actually want to trade. Doing it this way allows you to back test strategies that would normally take hours in a couple of seconds or minutes. But one disadvantage of the option chain provider is that it currently does not allow you to use options, Greeks and implied volatility values. Luckily, we don't need them for this algorithm, but for many other options algorithms, we will want to use the Greeks to select option contracts to trade. So if that's your goal, the option chain provider is currently not the best way to do so. But that's a topic for another video. Now, let's implement the options filter method. We will first add the currently available options contracts for our underlying by using self.optionchainprovider.getOptioncontractList. We can now manually filter this contract list as strike price, expiration, option type, and option style. But before we do that, let's quickly save the current price of the underlying asset to self.underlying price. We can do this with self.securities.price. Now, we first filter out all the out-of-the-money-put options that expire close to our desired expiration date and save them into OTM puts. To filter out put options only, we check that the option write equals put for each option. To only select sufficiently out-of-the-money options, we make sure that the underlying price minus the strike price of each option is greater than the underlying price multiplied by the amount we want our option to be out of the money. This amount, self.otm, was initialized to 1% in the initialized method. Next up, we check that the number of days from now until the expiration date of each option is within plus or minus eight days of our desired expiration date. If the OTM puts list now isn't empty, we sort it in a way so that the options that are closest to our desired strike and desired expiration date come first. Thereafter, we select the first option of this sorted list. This is the option that we will want to trade. But before we can actually trade it, we must first add its data to the algorithm. We only do this however, if we haven't done so already. If we haven't, we use add option contract to subscribe the data for the specified contract. Then we return this contract so that the buy put method can buy it. If the OTM puts list is empty, we just return an empty string since there's no option that meets our criteria. Now, we are pretty much done with implementing the functionalities of this trading algorithm. Nevertheless, we want to add two more small things. Firstly, we implement the plotting method, which will create two charts that can help us understand our algorithm's performance. To create a custom plot, we use self dot plot, which takes three arguments. The first is the name of the chart that we want to create. And the second is the name of a specific plot on this chart. And the final argument is the actual data for this plot. We will create two custom charts with two plots each. The first will plot our IV indicator, as well as the entry signal line. And the second chart will plot the price of the underlying asset, as well as the strike price of any open put positions. To plot the strike price of an open put position, we must first check whether we are invested. We do this by creating lists of all option positions in our portfolio. If this list is not empty, we have an open put position. If that's the case, we use self dot plot to plot this option strike price. Last but not least, we will use one more method for one more tiny thing. The name of this method is an unorder event. And like the name implies, it is automatically called on every order event. All we want to do is use self dot log to log each order event. This is great for backtesting, debugging and live trading purposes, since it can give you a good overview of what order events are happening. With that being said, we have now successfully created our options trading algorithm. All that's left to do for us now is click the back test button in the top right. This will then back test our trading strategy over the time frame that was specified in the initialize method. Since I specified the time frame to be the last three years, the results will be those generated from this data. Please keep in mind that depending over which time frame you choose to back test this algorithm, the results might be significantly different and potentially much worse than over this time frame. When the back test has finished, a performance overview and report will be generated for you to analyze your strategy and its performance. I will select the data chart and voil chart since these are the custom charts that we created for this algorithm. Since we didn't use most of these other charts, I will deselect them. As you hopefully can see over this time, this strategy seemed to work pretty good. We can directly compare our strategy's performance with that of SPY by looking at the data chart below this equity chart. As you can see from the data chart, SPY also managed to generate a pretty solid return. However, the key difference here is that SPY achieved these returns with a pretty high degree of volatility. Over these three years, there were multiple times where SPY actually was down overall. Our strategy on the other hand was profitable pretty much the entire time. Especially the very steep crash of early 2020 was handled very well. Generally speaking, the put options protect us best against short and fast crashes. It doesn't perform nearly as well during long term bear markets such as the one in 2007 and 2008. Below the data chart, we can see the custom voil chart which displays our custom VIX indicator as well as the entry signal level. Below that chart, we can see an overview of many more metrics that can be helpful when analyzing and evaluating a trading strategy and its performance. Down here, you can also view all the orders that the algorithm places, access the logs, the code and much more. If you want to learn how to best evaluate a trading strategy with the help of these metrics, charts and more, I highly recommend checking out my latest video in which I talk about how to best evaluate your trading strategies. Otherwise, if you want to copy the code from this video and play around with this algorithm yourself, make sure to check out the link in the description box below to clone this algorithm. I highly encourage you to go through each line of code and try to really understand what is going on. If you want to, you can also try adding some improvements or just changing a few things here and there. In my opinion, that's one of the best ways to learn and get familiar with the new API. I know that the strategy presented in this video was relatively simple, but the QuantConnect options API can be confusing especially for newcomers. That's why I wanted to start with a relatively simple example. But if you want me to create a similar video in which I implement a more complex multi-legged option strategy or some other trading algorithm, definitely let me know in the comments section below. If you liked this video, you should also check out one of my other reason videos in which I implement a stock trading strategy in QuantConnect using Python. There's a link in the description box below. Otherwise, definitely make sure to like this video, subscribe to my channel and turn on the notification bell for more content like this. Thanks for watching.