Overall Statistics |
Total Trades 142 Average Win 1.86% Average Loss -0.82% Compounding Annual Return 11.771% Drawdown 11.200% Expectancy 0.637 Net Profit 39.754% Sharpe Ratio 0.857 Loss Rate 50% Win Rate 50% Profit-Loss Ratio 2.27 Alpha 0.092 Beta -0.166 Annual Standard Deviation 0.095 Annual Variance 0.009 Information Ratio 0.108 Tracking Error 0.16 Treynor Ratio -0.491 Total Fees $0.00 |
import numpy as np import pandas as pd from System.Collections.Generic import List import statsmodels.formula.api as sm from scipy import stats import datetime as datetime class Forex(QCAlgorithm): def __init__(self): self.quoted = ['EURUSD','GBPUSD','USDCAD','USDJPY'] def get_history(self,symbol,num): data = {} dates, price = [],[] sym = List[Symbol]() sym.Add(symbol) #Add the simbol to a List named sym history = self.History(sym,num) #request the historical data for a single symbol for i in map(lambda x: x[symbol],history): time = str(i.EndTime).split(' ')[0] try: time = map(int,time.split('-')) t = datetime.date(time[0],time[1],time[2]) except: time = map(int,time.split('/')) t = datetime.date(time[2],time[0],time[1]) dates.append(t) price.append(np.log(float(i.Close))) #take log price here dates = pd.to_datetime(dates) df = pd.DataFrame(price,index = dates, columns = ['price']) #return a pandas DataFrame contain log price and python datetime 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',how = lambda x: x[-1]) # 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 out_params(self,symbol,num): his = self.get_history(symbol,num) his = self.calculate_return(his) res = self.OLS(his[0]) return (res,his[1]) def concat(self): # we requested as many daily tradebars as we can his = self.get_history(self.quoted[0],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],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 assign(self,list): for i in range(len(list)): add = self.AddForex(list[i],Resolution.Daily,Market.Oanda) list[i] = add.Symbol 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,33*3) # pandas method to take the last datapoint of each month res = res.resample('BM',how = lambda x: x[-1]) # 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.ix[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.SetStartDate(2013,6,1) self.SetEndDate(2016,6,1) self.SetCash(10000) self.assign(self.quoted) df = self.concat() 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.DateRules.MonthStart(), self.TimeRules.At(9,31), Action(self.action)) def OnData(self,data): self.data = data pass 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.SetHoldings(long_short[0][0],1) self.SetHoldings(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.SetHoldings(long_short[0][0],1) # short only else: self.SetHoldings(long_short[1][0],-1)