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