Testing out a profitable trading idea from X
Thanks to @PyQuantNews and Better System Trader podcast
The Idea
Got an interesting idea from @PyQuantNews tweet and a recent podcast from Better System Trader. on crude oil crack spreads, where you can do a long/short trade on the crack spread vs. a refiner stock such as Valero VLO. The main premise is that when the crack spread increases in value, the refiners profit margins increase hence the stock price should increase. There is a lag on the change of the stock price when the crack spread changes which we can profit from.
Here’s the tweet which got my attention.
I’ve traded the risk premia harvesting strategy before, and it works. I assume the “manager rebalancing in treasury ETFs” refers to the end of month seasonal effect on treasuries and stocks. That one also works well, although it has struggled since 2022.
First time hearing about the other 3: Crack spread vs refiner stocks, factor-based rebalancing against volatility, near expiration option straddles before earnings. Let’s test out the Crack Spread idea….
The crack spread is typically described as 3:2:1 or 5:3:2 ratios of CL, RB, HO markets (crude oil, gasoline, heating oil), short the crude front contract and long the distillates back contract. The appropriate crack spread ratios to use on a given refinery depends on the properties of the oil feed and the refinery configuration. This CME article gives some useful insights on crack spreads and its use for hedging.
Computing the Crack Spread
The code below gets the stock data from Norgate and Futures data from CSI Data. This is from my personal toolkit in R. Stock data is total return series, which includes splits, corporate actions and dividends in the time series. Futures data is for individual contracts.
STK=fGetStockData(symbols=c('VLO', #Valero
'MPC', #Marathon
'PSX' #Phillips 66
))%>%
fAdjustStockDataFrame() #To make time series look like futures back adjusted data (for personal backtest engine)
FUT=fGetFutures(c('CL2', #Crude Oil
'RB2', #Gasoline
'HO2' #Heating Oil
))
Now that we got the futures contracts data, we can compute the crack spread. There are a few challenges:
Heating Oil HO and Gasoline RB contracts on same delivery expire the same day, but Crude Oil CL contract expires 1 week before.
Hence there is ~ 1 week of the month where there is no front futures contract for crude to compute the spread.
Crude price went negative on 20 April 2020 upon contract expiration.
There is 1 crack spread for each expiration month. Which one to use, and how?
Let’s use the crude spot price when no crude front contract is available. Negative crude price would show as ultra-profitable crack spread spike for refiners for that day. That’s fine for now.
Let’s also compute a synthetic crack spread with a fixed expiration dates, such as 45, 60 and 90 days, using linear interpolation.
library(tidyverse)
library(lubridate)
CrackSpread=FUT%>%
mutate(delivery=if_else(delivery=='Cash',' Cash',delivery))%>%
select(symbol,date,delivery,expirydate,close,volume,oi)%>%
pivot_wider(names_from=symbol,values_from=c(close,expirydate,volume,oi))%>%
mutate(expirydate=expirydate_RB2)%>%
#Compute days to expirateion
mutate(dte=as.numeric(expirydate-date))%>%
group_by(date)%>%
arrange(delivery)%>%
fill(close_CL2)%>% #When front crude contract not available, use spot
mutate(
crack.spread.321=-1*(3*lag(close_CL2)
-2*42*close_RB2
-1*42*close_HO2)/3,
crack.spread.532=-1*(5*lag(close_CL2)
-3*42*close_RB2
-2*42*close_HO2)/5
)%>%
ungroup()%>%
filter(delivery!=' Cash')%>%
group_by(date)%>%
arrange(delivery)%>%
mutate(
#Compute the synthetic fixed expiration crack spreads
cs321.90D=approx(x=dte,y=crack.spread.321,xout=90)$y,
cs321.60D=approx(x=dte,y=crack.spread.321,xout=60)$y,
cs321.45D=approx(x=dte,y=crack.spread.321,xout=45)$y,
cs532.90D=approx(x=dte,y=crack.spread.532,xout=90)$y,
cs532.60D=approx(x=dte,y=crack.spread.532,xout=60)$y,
cs532.45D=approx(x=dte,y=crack.spread.532,xout=45)$y
)%>%
ungroup()
#Write to a CSV file, to use in RealTest
CrackSpread%>%
distinct(date,.keep_all=T)%>%
select(date,starts_with('cs321'),starts_with('cs532'))%>%
pivot_longer(cols=-date,names_to='symbol',values_to='close')%>%
write.csv('D:/RESEARCH/Crack.csv',row.names = F)
Code blocks look pretty ugly in Substack, without syntax highlighting. Sorry.
Testing strategy ideas
We got the crack spread time series. Now let’s test a very simple long-only idea:
Go long the refiner company stock when the crack spread is increasing. Exit when it’s decreasing. Trend defined by ROC (rate of change) indicator. Short term ROCs would be required since this effect doesn’t take too long to be arbitraged away.
This should be enough for you to experiment. This is how it looks in my simulation tool for a given set of parameters:
Very nice. Now let’s do the same test with RealTest software, to verify:
These are the stats:
Note the max exposure is 100% more than 99% of the time. There are 2 blips when PSX and MPC stocks gets listed in the exchange. Will figure out how to handle it better in the code.. but this does not affect the result.
Conclusion
Overall a decent strategy to add to the portfolio. If you haven’t yet, please listen to Better System Trader podcast. Big thanks to the podcast guests for sharing profitable ideas.
On the next posts I’ll make an update on the portfolio of trading strategies, and how I’m implementing multiple Futures and Stocks/ETF strategies together.