Introduction
The WTI-Brent spread is the difference between the prices of two types of crude oil: West Texas Intermediate (WTI) on the long side and Brent Crude (Brent) on the short side. For years, the price difference between the two has only been a few dollars on average. As both oils are very similar, their spread shows signs of strong predictability and usually oscillates around some average value. Therefore, it is possible to use deviations from the fair spread value to bet on convergence back to fair value. Here we present a trading strategy based on the price deviations of the spread.
Method
We use the WTI and Brent crude oil price their CFDs traded in Oanda exchange.
def initialize(self):
# import the spot price data
self.wti = self.add_cfd("WTICOUSD", Resolution.DAILY, Market.OANDA).symbol
self.b = self.add_cfd("BCOUSD", Resolution.DAILY, Market.OANDA).symbol
The spread is defined as the difference between WTI price and Brent price. Next, we need to calculate the moving average of the spread series using the the SimpleMovingAverage indicator. As the indicator uses the price difference instead of the price series, we need to manually initialize the indicator with the history request.
self.spreadSMA = SimpleMovingAverage(20)
hist = self.history([self.wti, self.b], 252, Resolution.DAILY).unstack(level=0)['close'].ffill().dropna()
wti_hist = hist[self.wti.id.to_string()]
b_hist = hist[self.b.id.to_string()]
spread = wti_hist - b_hist
for index, value in spread.iloc[-20:].items():
self.spreadSMA.update(index, value)
To get the fair value of the spread, we perform the linear regression between WTI and Brent price over the last one year history price.
\[P_{Brent}=\beta \cdot P_{WTI}+\alpha\]
Then the fair value of the spread is
\[Fair \ value = (1-\beta)\cdot P_{WTI}-\alpha\]
# linear regression to decide the fair value
self.regr = linear_model.linear_regression()
self.regr.fit(wti_hist.values.reshape(-1, 1), b_hist.values.reshape(-1, 1))
The fair value is calculated every day. If the current spread value is above SMA 20, then we enter a short position in the spread on close (betting that the spread will decrease to fair value represented by SMA 20). The trade is closed at the close of the trading day when the spread crosses below fair value. If the current spread value is below SMA 20 then we enter a long position betting that the spread will increase and the trade is closed at the close of the trading day when the spread crosses above fair value.
def on_data(self, data):
if not (data.contains_key(self.wti) and data.contains_key(self.b)):
return
spread = data[self.wti].price - data[self.b].price
self.plot("Spread Plot", "Spread", spread)
self.spreadSMA.update(self.time, spread)
fair_value = data[self.wti].price - self.regr.predict(np.array([data[self.wti].price]).reshape(1, -1))[0]
if spread > self.spreadSMA.current.value and not (self.portfolio[self.wti].is_short and self.portfolio[self.b].is_long):
self.log("spread > self.spreadSMA.current.value")
self.set_holdings(self.wti, -0.5)
self.set_holdings(self.b, 0.5)
self.plot("Spread Plot", "Long Spread Trade", spread)
elif spread < self.spreadSMA.current.value and not (self.portfolio[self.wti].is_long and self.portfolio[self.b].is_short):
self.log("spread < self.spreadSMA.current.value")
self.set_holdings(self.wti, 0.5)
self.set_holdings(self.b, -0.5)
self.plot("Spread Plot", "Short Spread Trade", spread)
if self.portfolio[self.wti].is_short and self.portfolio[self.b].is_long and spread < fair_value:
self.liquidate()
if self.portfolio[self.wti].is_long and self.portfolio[self.b].is_short and spread > fair_value:
self.liquidate()
We also set up a scheduled linear regression model re-training every month start.
def initialize(self):
# Recalibrate model every month
self.train(self.date_rules.month_start(), self.time_rules.at(0,0), self.my_training_method)
def my_training_method(self):
hist = self.history([self.wti, self.b], 252, Resolution.DAILY).unstack(level=0)['close'].ffill().dropna()
wti_hist = hist[self.wti.id.to_string()]
b_hist = hist[self.b.id.to_string()]
# linear regression to decide the fair value
self.regr.fit(wti_hist.values.reshape(-1, 1), b_hist.values.reshape(-1, 1))
To demonstrate the trend of the spread series, we add the spread plot and mark the spread long/short point on the spread curve.
spreadPlot = Chart("Spread Plot")
spreadPlot.add_series(Series("Spread", SeriesType.LINE, 0))
spreadPlot.add_series(Series("Long Spread Trade", SeriesType.SCATTER, 0))
spreadPlot.add_series(Series("Short Spread Trade", SeriesType.SCATTER, 0))
self.add_chart(spreadPlot)
Derek Melchin
See the attached backtest for an updated version of the algorithm in PEP8 style.
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.
Jing Wu
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!