Overall Statistics
Total Orders
1673
Average Win
1.24%
Average Loss
-0.54%
Compounding Annual Return
-0.925%
Drawdown
15.000%
Expectancy
0.011
Start Equity
100000
End Equity
95455.69
Net Profit
-4.544%
Sharpe Ratio
-0.66
Sortino Ratio
-0.761
Probabilistic Sharpe Ratio
0.198%
Loss Rate
69%
Win Rate
31%
Profit-Loss Ratio
2.30
Alpha
-0.041
Beta
0.01
Annual Standard Deviation
0.062
Annual Variance
0.004
Information Ratio
-0.734
Tracking Error
0.153
Treynor Ratio
-4.13
Total Fees
$0.00
Estimated Strategy Capacity
$0
Lowest Capacity Asset
BCOUSD 8I
Portfolio Turnover
53.85%
Drawdown Recovery
287
#region imports
from AlgorithmImports import *

from sklearn.linear_model import LinearRegression
#endregion


class TradeWtiBrentSpreadAlgorithm(QCAlgorithm):

    def initialize(self):
        self.set_start_date(self.end_date - timedelta(5*365))
        self.set_cash(100000)

        # import the spot price data
        self._wti = self.add_cfd("WTICOUSD", Resolution.DAILY, Market.OANDA)
        self._b = self.add_cfd("BCOUSD", Resolution.DAILY, Market.OANDA)

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

        # create the moving average indicator of the pread = WTI price - BRENT price
        self._spread_sma = SimpleMovingAverage(20)
        wti_hist, b_hist = self._get_history()
        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 _get_history(self):
        hist = self.history([self._wti, self._b], 252, Resolution.DAILY).unstack(level=0)['close'].ffill().dropna()
        return hist[self._wti.symbol], hist[self._b.symbol]

    def _my_training_method(self):
        wti_hist, b_hist = self._get_history()
        # 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 = self._wti.price - self._b.price
        self.plot("Spread Plot", "Spread", spread)
        self._spread_sma.update(self.time, spread)
        
        fair_value = self._wti.price - self._regr.predict(np.array([self._wti.price]).reshape(1, -1))[0]    
        
        if spread > self._spread_sma.current.value and not (self._wti.holdings.is_short and self._b.holdings.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._wti.holdings.is_long and self._b.holdings.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._wti.holdings.is_short and self._b.holdings.is_long and spread < fair_value:
            self.liquidate()
        
        if self._wti.holdings.is_long and self._b.holdings.is_short and spread > fair_value:
            self.liquidate()