Overall Statistics
Total Trades
266
Average Win
1.36%
Average Loss
-1.21%
Compounding Annual Return
72.073%
Drawdown
35.200%
Expectancy
0.304
Net Profit
37.255%
Sharpe Ratio
1.202
Probabilistic Sharpe Ratio
45.685%
Loss Rate
39%
Win Rate
61%
Profit-Loss Ratio
1.12
Alpha
0.808
Beta
0.134
Annual Standard Deviation
0.657
Annual Variance
0.432
Information Ratio
1.359
Tracking Error
0.68
Treynor Ratio
5.877
Total Fees
$0.00
Estimated Strategy Capacity
$3500000.00
Lowest Capacity Asset
EURCAD 8G
#region imports
from AlgorithmImports import *
#endregion
from scipy.stats import linregress
import numpy as np


class MeanReverse(QCAlgorithm):
    
    def Initialize(self):
        # Define backtest window and portfolio cash
        self.SetStartDate(2022, 1, 1)
        self.SetEndDate(2022, 8, 1)
        self.CashDispoInitial=10000
        self.SetCash(self.CashDispoInitial)
        self.sizelot=30000
        
        # Add the assets to be fed into the algorithm and save the symbol objects (to be referred later)
        self.NameAsset1 = "EURCAD"
        self.NameAsset2 = "GBPCAD"
        self.asset1 = self.AddForex(self.NameAsset1, Resolution.Hour, Market.Oanda).Symbol
        self.asset2 = self.AddForex(self.NameAsset2, Resolution.Hour, Market.Oanda).Symbol
        
        # set the starting flag as not invested and initiate paramaters
        self.entry1 = 0
        self.entry2 = 0
        self.entry_slope = 0
        self.entry1_second = 0
        self.entry2_second = 0
        self.entry_slope_second = 0
        self.is_invested = None

        # set paramaters that should be optimized
        self.lookback = 4
        bb_lookback = 48
        bb_std = 1.8
        self.out_pips=6

        # We then create a bollinger band with 120 steps for lookback period
        self.bb1 = BollingerBands(bb_lookback, bb_std, MovingAverageType.Simple)
        
        #Define charts showing when buy and sell
        stockPlot1 = Chart('Trade Plot Asset1')
        stockPlot1.AddSeries(Series('Buy1', SeriesType.Scatter, '$', Color.Green, ScatterMarkerSymbol.Triangle))
        stockPlot1.AddSeries(Series('Sell1', SeriesType.Scatter, '$', Color.Red, ScatterMarkerSymbol.TriangleDown))
        stockPlot1.AddSeries(Series('Liquidate1', SeriesType.Scatter, '$', Color.Blue, ScatterMarkerSymbol.Diamond))
        self.AddChart(stockPlot1)
        stockPlot2 = Chart('Trade Plot Asset2')
        stockPlot2.AddSeries(Series('Buy2', SeriesType.Scatter, '$', Color.Green, ScatterMarkerSymbol.Triangle))
        stockPlot2.AddSeries(Series('Sell2', SeriesType.Scatter, '$', Color.Red, ScatterMarkerSymbol.TriangleDown))
        stockPlot2.AddSeries(Series('Liquidate2', SeriesType.Scatter, '$', Color.Blue, ScatterMarkerSymbol.Diamond))
        self.AddChart(stockPlot2)

    def OnData(self, data):
        
        #Compute lot quantity
        self.CashDispo = self.Portfolio.TotalProfit
        self.QuantityAsset = self.sizelot #*(1+ 2*(self.CashDispo/self.CashDispoInitial-1))
    
        #Compute and refresh slope and intercept
        history = self.History([self.asset1, self.asset2], self.lookback, Resolution.Hour)
        history = history.unstack(level=0).dropna()

        asset1 = np.log(history['close', self.asset1.Value])
        asset2 = np.log(history['close', self.asset2.Value])
        
        reg = linregress(asset1, asset2)

        #Define portfolio and update BB
        portfolio = np.log(data[self.asset2].Close) - np.log(data[self.asset1].Close) * reg.slope - reg.intercept
        ass1=data[self.asset1].Close
        ass2=data[self.asset2].Close
        self.bb1.Update(self.Time, float(portfolio))
        
        # Plot the portfolio (to see if it is working, and the bb bands)
        self.Plot("Indicators", "Portfolio", float(portfolio))
        self.Plot("Indicators", "Upper", self.bb1.UpperBand.Current.Value)
        self.Plot("Indicators", "Lower", self.bb1.LowerBand.Current.Value)

        #self.Plot("Slope", "Slope", float(reg.slope))
        self.Plot("Cash", "Cash", float(self.CashDispo))
        
        # check if the bolllinger band indicator is ready 
        if not self.bb1.IsReady:
            self.Debug('BB not ready')
        
        upper_band = self.bb1.UpperBand.Current.Value
        lower_band = self.bb1.LowerBand.Current.Value
        middle_band = self.bb1.MiddleBand.Current.Value

        # Trading mechanism
        # if it is not invested, see if there is an entry point
        if not self.is_invested:
            # if our portfolio is bellow the lower band, enter long --> sell A1 and buy A2
            if portfolio < lower_band:
                self.MarketOrder(self.NameAsset1, -self.QuantityAsset*reg.slope)
                self.MarketOrder(self.NameAsset2, self.QuantityAsset)
                self.Plot('Trade Plot Asset1','Sell1',ass1)
                self.Plot('Trade Plot Asset2','Buy2',ass2)
                self.Debug('Entering Long')
                self.is_invested = 'long'
                self.entry1=ass1
                self.entry2=ass2
                self.entry_slope = reg.slope
                self.Debug(self.CashDispo/self.CashDispoInitial)

            # if our portfolio is above the upper band, go short --> buy A1 and sell A2
            if portfolio > upper_band:
                self.MarketOrder(self.NameAsset1, self.QuantityAsset*reg.slope)
                self.MarketOrder(self.NameAsset2, -self.QuantityAsset)
                self.Plot('Trade Plot Asset1','Buy1',ass1)
                self.Plot('Trade Plot Asset2','Sell2',ass2)
                self.Debug('Entering Short')
                self.is_invested = 'short'
                self.entry1=ass1
                self.entry2=ass2
                self.entry_slope = reg.slope
                self.Debug(self.CashDispo/self.CashDispoInitial)
        
        # if it is invested in something, check the exiting signal (when it crosses the mean)   
        else:
            #liquidate position
            if self.is_invested == 'long' and (ass2 - self.entry2) + self.entry_slope*(self.entry1-ass1)>self.out_pips/10000:
                self.Liquidate()
                self.Plot('Trade Plot Asset1','Liquidate1',ass1)
                self.Plot('Trade Plot Asset2','Liquidate2',ass2)
                self.Debug('Exiting Long')
                self.Debug(self.CashDispo/self.CashDispoInitial)
                self.is_invested = None
            elif self.is_invested == 'short' and (self.entry2-ass2) + self.entry_slope*(ass1 - self.entry1)>self.out_pips/10000:
                self.Liquidate()
                self.Plot('Trade Plot Asset1','Liquidate1',ass1)
                self.Plot('Trade Plot Asset2','Liquidate2',ass2)
                self.Debug('Exiting Short')
                self.Debug(self.CashDispo/self.CashDispoInitial)
                self.is_invested = None
            #liquidate position
            elif self.is_invested == 'longlong' and (ass2 - self.entry2_second) + (ass2 - self.entry2) + self.entry_slope_second*(self.entry1_second-ass1) +self.entry_slope*(self.entry1-ass1)>1.5*self.out_pips/10000:
                self.Liquidate()
                self.Plot('Trade Plot Asset1','Liquidate1',ass1)
                self.Plot('Trade Plot Asset2','Liquidate2',ass2)
                self.Debug('Exiting Second Long')
                self.Debug(self.CashDispo/self.CashDispoInitial)
                self.is_invested = None
            elif self.is_invested == 'shortshort' and (self.entry2_second-ass2) + (self.entry2-ass2) + self.entry_slope_second*(ass1 - self.entry1_second) + self.entry_slope*(ass1 - self.entry1)>1.5*self.out_pips/10000:
                self.Liquidate()
                self.Plot('Trade Plot Asset1','Liquidate1',ass1)
                self.Plot('Trade Plot Asset2','Liquidate2',ass2)
                self.Debug('Exiting Second Short')
                self.Debug(self.CashDispo/self.CashDispoInitial)
                self.is_invested = None
            #create second position
            elif self.is_invested == 'long' and portfolio < 1.3*lower_band:
                self.MarketOrder(self.NameAsset1, -2*self.QuantityAsset*reg.slope)
                self.MarketOrder(self.NameAsset2, 2*self.QuantityAsset)
                self.Debug('Entering Second Long')
                self.is_invested = 'longlong'
                self.entry1_second=ass1
                self.entry2_second=ass2
                self.entry_slope_second = reg.slope
            elif self.is_invested == 'short' and portfolio > 1.3*upper_band:
                self.MarketOrder(self.NameAsset1, 2*self.QuantityAsset*reg.slope)
                self.MarketOrder(self.NameAsset2, -2*self.QuantityAsset)
                self.Debug('Entering Second Short')
                self.is_invested = 'shortshort'
                self.entry1_second=ass1
                self.entry2_second=ass2
                self.entry_slope_second = reg.slope