| 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)