Overall Statistics
Total Orders
62
Average Win
1.46%
Average Loss
-1.67%
Compounding Annual Return
-1.613%
Drawdown
7.000%
Expectancy
0.004
Start Equity
100000
End Equity
98786.38
Net Profit
-1.214%
Sharpe Ratio
-0.535
Sortino Ratio
-0.657
Probabilistic Sharpe Ratio
11.741%
Loss Rate
46%
Win Rate
54%
Profit-Loss Ratio
0.87
Alpha
-0.039
Beta
0.033
Annual Standard Deviation
0.07
Annual Variance
0.005
Information Ratio
-0.483
Tracking Error
0.149
Treynor Ratio
-1.127
Total Fees
$0.00
Estimated Strategy Capacity
$0
Lowest Capacity Asset
XAGUSD 8I
Portfolio Turnover
19.83%
from AlgorithmImports import *
from QuantConnect.DataSource import *

class SMAPairsTrading(QCAlgorithm):

    def initialize(self) -> None:
        self.set_start_date(2018, 7, 1)   
        self.set_end_date(2019, 3, 31)
        self.set_cash(100000)

        # Request gold and sliver spot CFDs for trading their spread difference, assuming their spread series is cointegrated
        self.add_cfd('XAUUSD', Resolution.HOUR)
        self.add_cfd('XAGUSD', Resolution.HOUR)

        # Use 500-step mean and SD indicator on determine the spread relative difference for trading signal generation
        self.pair = [ ]
        self.spread_mean = SimpleMovingAverage(500)
        self.spread_std = StandardDeviation(500)
        
    def on_data(self, slice: Slice) -> None:
        # Update the indicator with updated spread difference, such that the an updated cointegration threshold is calculated for trade inception
        spread = self.pair[1].price - self.pair[0].price
        self.spread_mean.update(self.time, spread)
        self.spread_std.update(self.time, spread) 
        
        spread_mean = self.spread_mean.current.value
        upperthreshold = spread_mean  + self.spread_std.current.value
        lowerthreshold = spread_mean  - self.spread_std.current.value

        # If the spread is higher than upper threshold, bet theie spread series will revert to mean
        if spread > upperthreshold:
            self.set_holdings(self.pair[0].symbol, 1)
            self.set_holdings(self.pair[1].symbol, -1)
        elif spread < lowerthreshold:
            self.set_holdings(self.pair[0].symbol, -1)
            self.set_holdings(self.pair[1].symbol, 1)
        # Close positions if mean reverted
        elif (self.portfolio[self.pair[0].symbol].quantity > 0 and spread < spread_mean)\
        or (self.portfolio[self.pair[0].symbol].quantity < 0 and spread > spread_mean):
            self.liquidate()
    
    def on_securities_changed(self, changes: SecurityChanges) -> None:
        self.pair = [x for x in changes.added_securities]
        
        #1. Call for 500 bars of history data for each symbol in the pair and save to the variable history
        history = self.history([x.symbol for x in self.pair], 500)
        #2. Unstack the Pandas data frame to reduce it to the history close price
        history = history.close.unstack(level=0)
        #3. Iterate through the history tuple and update the mean and standard deviation with historical data 
        for tuple in history.itertuples():
            self.spread_mean.update(tuple[0], tuple[2]-tuple[1])
            self.spread_std.update(tuple[0], tuple[2]-tuple[1])