Overall Statistics
Total Trades
1516
Average Win
1.51%
Average Loss
-0.67%
Compounding Annual Return
-6.064%
Drawdown
28.100%
Expectancy
-0.020
Net Profit
-26.505%
Sharpe Ratio
-0.378
Probabilistic Sharpe Ratio
0.011%
Loss Rate
70%
Win Rate
30%
Profit-Loss Ratio
2.24
Alpha
-0.044
Beta
0.068
Annual Standard Deviation
0.099
Annual Variance
0.01
Information Ratio
-0.661
Tracking Error
0.194
Treynor Ratio
-0.551
Total Fees
$0.00
Estimated Strategy Capacity
$1000.00
Lowest Capacity Asset
WTICOUSD 8I
#region imports
from AlgorithmImports import *
from sklearn import linear_model
#endregion

class TradeWtiBrentSpreadAlgorithm(QCAlgorithm):

    def Initialize(self):
        self.SetStartDate(2018, 1, 1)
        self.SetEndDate(2022, 12, 1)
        self.SetCash(100000)

        # import the spot price data
        self.wti = self.AddCfd("WTICOUSD", Resolution.Daily, Market.Oanda).Symbol
        self.b = self.AddCfd("BCOUSD", Resolution.Daily, Market.Oanda).Symbol

        # Recalibrate model every month
        self.Train(self.DateRules.MonthStart(), self.TimeRules.At(0,0), self.MyTrainingMethod)

        # create the moving average indicator of the pread = WTI price - BRENT price
        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.ToString()]
        b_hist = hist[self.b.ID.ToString()]
        spread = wti_hist - b_hist
        for index, value in spread.iloc[-20:].items():
            self.SpreadSMA.Update(index, value) 

        # linear regression to decide the fair value
        self.regr = linear_model.LinearRegression()
        self.regr.fit(wti_hist.values.reshape(-1, 1), b_hist.values.reshape(-1, 1)) 

        # Add the spread plot and mark the long/short spread point
        spreadPlot = Chart("Spread Plot")
        spreadPlot.AddSeries(Series("Spread", SeriesType.Line, 0))
        spreadPlot.AddSeries(Series("Long Spread Trade", SeriesType.Scatter, 0))
        spreadPlot.AddSeries(Series("Short Spread Trade", SeriesType.Scatter, 0))
        self.AddChart(spreadPlot)

    def MyTrainingMethod(self):
        hist = self.History([self.wti, self.b], 252, Resolution.Daily).unstack(level=0)['close'].ffill().dropna()
        wti_hist = hist[self.wti.ID.ToString()]
        b_hist = hist[self.b.ID.ToString()]

        # linear regression to decide the fair value
        self.regr.fit(wti_hist.values.reshape(-1, 1), b_hist.values.reshape(-1, 1)) 
        
    def OnData(self, data):
        if not (data.ContainsKey(self.wti) and data.ContainsKey(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].IsShort and self.Portfolio[self.b].IsLong):
            self.Log("spread > self.SpreadSMA.Current.Value")
            self.SetHoldings(self.wti, -0.5)
            self.SetHoldings(self.b, 0.5)
            self.Plot("Spread Plot", "Long Spread Trade", spread)

        elif spread < self.SpreadSMA.Current.Value and not (self.Portfolio[self.wti].IsLong and self.Portfolio[self.b].IsShort):
            self.Log("spread < self.SpreadSMA.Current.Value")
            self.SetHoldings(self.wti, 0.5)
            self.SetHoldings(self.b, -0.5)
            self.Plot("Spread Plot", "Short Spread Trade", spread)
            
        if self.Portfolio[self.wti].IsShort and self.Portfolio[self.b].IsLong and spread < fair_value:
            self.Liquidate()
        
        if self.Portfolio[self.wti].IsLong and self.Portfolio[self.b].IsShort and spread > fair_value:
            self.Liquidate()