Overall Statistics |
Total Orders 123 Average Win 1.84% Average Loss -2.15% Compounding Annual Return -2.041% Drawdown 19.600% Expectancy -0.056 Start Equity 10000.00 End Equity 9398.55 Net Profit -6.015% Sharpe Ratio -0.131 Sortino Ratio -0.153 Probabilistic Sharpe Ratio 1.321% Loss Rate 49% Win Rate 51% Profit-Loss Ratio 0.86 Alpha -0.027 Beta 0.163 Annual Standard Deviation 0.117 Annual Variance 0.014 Information Ratio -0.6 Tracking Error 0.148 Treynor Ratio -0.094 Total Fees $0.00 Estimated Strategy Capacity $48000.00 Lowest Capacity Asset USDJPY 8G Portfolio Turnover 11.16% |
#region imports from AlgorithmImports import * import statsmodels.formula.api as sm from scipy import stats #endregion class MeanReversionAndMomentumForexAlgorithm(QCAlgorithm): def _get_history(self,symbol, num): data = {} dates = [] history = self.history([symbol], num, Resolution.DAILY).loc[symbol]['close'] #request the historical data for a single symbol for time in history.index: t = time.to_pydatetime().date() dates.append(t) dates = pd.to_datetime(dates) df = pd.DataFrame(history) df.reset_index(drop=True) df.index = dates df.columns = ['price'] return df def _calculate_return(self,df): #calculate the mean for further use mean = np.mean(df.price) # cauculate the standard deviation sd = np.std(df.price) # pandas method to take the last datapoint of each month. df = df.resample('BM').last() # the following three lines are for further experiment purpose # df['j1'] = df.price.shift(1) - df.price.shift(2) # df['j2'] = df.price.shift(2) - df.price.shift(3) # df['j3'] = df.price.shift(3) - df.price.shift(4) # take the return as depend variable df['log_return'] = df.price - df.price.shift(1) # calculate the reversal factor df['reversal'] = (df.price.shift(1) - mean)/sd # calculate the momentum factor df['mom'] = df.price.shift(1) - df.price.shift(4) df = df.dropna() #remove nan value return (df,mean,sd) def _calculate_input(self,df,mean,sd): # df['j1'] = df.price - df.price.shift(1) # df['j2'] = df.price.shift(1) - df.price.shift(2) # df['j3'] = df.price.shift(2) - df.price.shift(3) df['reversal'] = (df.price - mean)/sd df['mom'] = df.price - df.price.shift(3) df = df.dropna() return df def _OLS(self,df): res = sm.ols(formula = 'log_return ~ reversal + mom',data = df).fit() return res def _concat(self): # we requested as many daily tradebars as we can his = self._get_history(self._quoted[0].value,20*365) # get the clean DataFrame for linear regression his = self._calculate_return(his) # add property to the symbol object for further use. self._quoted[0].mean = his[1] self._quoted[0].sd = his[2] df = his[0] # repeat the above procedure for each symbols, and concat the dataframes for i in range(1,len(self._quoted)): his = self._get_history(self._quoted[i].value,20*365) his = self._calculate_return(his) self._quoted[i].mean = his[1] self._quoted[i].sd = his[2] df = pd.concat([df,his[0]]) df = df.sort_index() # remove outliers that outside the 99.9% confidence interval df = df[df.apply(lambda x: np.abs(x - x.mean()) / x.std() < 3).all(axis=1)] return df def _predict(self,symbol): # get current month in string month = str(self.time).split(' ')[0][5:7] # request the data in the last three months res = self._get_history(symbol.value,33*3) # pandas method to take the last datapoint of each month res = res.resample('BM').last() # remove the data points in the current month res = res[res.index.month != int(month)] # calculate the variables res = self._calculate_input(res,symbol.mean,symbol.sd) res = res.iloc[0] # take the coefficient. The first one will not be used for sum-product because it's the intercept params = self._formula.params[1:] # calculate the expected return re = sum([a*b for a,b in zip(res[1:],params)]) + self._formula.params[0] return re def initialize(self): self.set_start_date(2013,6,1) self.set_end_date(2016,6,1) self.set_cash(10000) syls = ['EURUSD','GBPUSD','USDCAD','USDJPY'] self._quoted = [] for i in range(len(syls)): self._quoted.append(self.add_forex(syls[i],Resolution.DAILY,Market.OANDA).symbol) df = self._concat() self.log(str(df)) self._formula = self._OLS(df) self.log(str(self._formula.summary())) self.log(str(df)) self.log(str(df.describe())) for i in self._quoted: self.log(str(i.mean) + ' ' + str(i.sd)) self.schedule.on(self.date_rules.month_start(), self.time_rules.at(9,31), self._action) def on_data(self,data): self.data = data def _action(self): rank = [] long_short = [] for i in self._quoted: rank.append((i,self._predict(i))) # rank the symbols by their expected return rank.sort(key = lambda x: x[1],reverse = True) # the first element in long_short is the one with the highest expected return, which we are going to long, and the second one is going to be shorted. long_short.append(rank[0]) long_short.append(rank[-1]) self.liquidate() # the product < 0 means the expected return of the first one is positive and that of the second one is negative--we are going to long and short. if long_short[0][1]*long_short[1][1] < 0: self.set_holdings(long_short[0][0],1) self.set_holdings(long_short[1][0],-1) # this means we long only because all of the expected return is positive elif long_short[0][1] > 0 and long_short[1][1] > 0: self.set_holdings(long_short[0][0],1) # short only else: self.set_holdings(long_short[1][0],-1)