Overall Statistics
Total Orders
1525
Average Win
1.49%
Average Loss
-0.68%
Compounding Annual Return
-6.097%
Drawdown
28.300%
Expectancy
-0.020
Start Equity
100000
End Equity
73369.74
Net Profit
-26.630%
Sharpe Ratio
-0.535
Sortino Ratio
-0.568
Probabilistic Sharpe Ratio
0.010%
Loss Rate
69%
Win Rate
31%
Profit-Loss Ratio
2.20
Alpha
-0.058
Beta
0.068
Annual Standard Deviation
0.099
Annual Variance
0.01
Information Ratio
-0.662
Tracking Error
0.194
Treynor Ratio
-0.78
Total Fees
$0.00
Estimated Strategy Capacity
$1000.00
Lowest Capacity Asset
WTICOUSD 8I
Portfolio Turnover
50.14%
#region imports
from AlgorithmImports import *

from sklearn.linear_model import LinearRegression
#endregion


class TradeWtiBrentSpreadAlgorithm(QCAlgorithm):

    def initialize(self):
        self.set_start_date(2018, 1, 1)
        self.set_end_date(2022, 12, 1)
        self.set_cash(100000)

        # 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

        # Recalibrate model every month
        self.train(self.date_rules.month_start(), self.time_rules.at(0,0), self._my_training_method)

        # create the moving average indicator of the pread = WTI price - BRENT price
        self._spread_sma = 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._spread_sma.update(index, value) 

        # linear regression to decide the fair value
        self._regr = 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
        spread_plot = Chart("Spread Plot")
        spread_plot.add_series(Series("Spread", SeriesType.LINE, 0))
        spread_plot.add_series(Series("Long Spread Trade", SeriesType.SCATTER, 0))
        spread_plot.add_series(Series("Short Spread Trade", SeriesType.SCATTER, 0))
        self.add_chart(spread_plot)

    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)) 
        
    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._spread_sma.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._spread_sma.current.value and not (self.portfolio[self._wti].is_short and self.portfolio[self._b].is_long):
            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._spread_sma.current.value and not (self.portfolio[self._wti].is_long and self.portfolio[self._b].is_short):
            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()