In [1]:
import pandas as pd
import itable
import ffn
import talib
import datetime as dt
%matplotlib inline
def side_by_side(*objs, **kwds):
from pandas.formats.printing import adjoin
space = kwds.get('space', 4)
reprs = [repr(obj).split('\n') for obj in objs]
print (adjoin(space, *reprs))
In [2]:
def monthly_return_table (daily_prices) :
#monthly_returns = daily_prices.resample('M').last().pct_change()
monthly_returns = daily_prices.resample('M', how='last').pct_change()
df = pd.DataFrame(monthly_returns.values, columns=['Data'])
df['Month'] = monthly_returns.index.month
df['Year']= monthly_returns.index.year
table = df.pivot_table(index='Year', columns='Month').fillna(0).round(4) * 100
#annual_returns = daily_prices.resample('12M').last().pct_change()[1:].values.round(4) * 100
annual_returns = daily_prices.resample('12M', how='last').pct_change()[1:].values.round(4) * 100
if len(table) > len(annual_returns) :
table = table[1:]
table['Annual Returns'] = annual_returns
return table
In [3]:
def endpoints(start=None, end=None, period='m', trading_days=None) :
if trading_days is not None:
dates = trading_days
# the following 2 lines cause python 3.4.2 to crash, so removed them
# elif start is not None and end is not None:
# dates = tradingcalendar.get_trading_days(start, end)
else:
print ('\n** ERROR : must either provide pandas series (or df) of trading days \n')
print (' or a start and end date\n')
if isinstance(period, int) :
dates = [dates[i] for i in range(0, len(dates), period)]
else :
if period == 'm' : months = 1
elif period == 'q' : months = 3
elif period == 'b' : months = 6
elif period == 'y' : months = 12
e_dates = [dates[i - 1] for i in range(1,len(dates))\
if dates[i].month > dates[i-1].month\
or dates[i].year > dates[i-1].year ]+ list([dates[-1]])
dates = [e_dates[i] for i in range(0,len(e_dates),months)]
return dates
In [4]:
# THIS ONE MATCHES PV
# SEE PV backtest :https://goo.gl/lBR4K9
# AND spreadsheet : https://goo.gl/8KGp58
# and Quantopian backtest : https://goo.gl/xytT5L
def backtest(prices, weights, capital, offset=1, commission=0.) :
rebalance_dates = weights.index
buy_dates = [prices.index[d + offset] for d in range(len(prices.index)-1) if prices.index[d] in rebalance_dates ]
print ('FIRST BUY DATE = {}\n'.format(buy_dates[0]))
p_holdings = pd.DataFrame(0, index=prices.index, columns=prices.columns)
cash = 0.
for i, date in enumerate(prices.index):
if date in rebalance_dates :
# print ('--------------------------------------------------------------------')
new_weights = weights.loc[date]
p_holdings.iloc [i] = p_holdings.iloc [i - 1]
if date in buy_dates :
if date == buy_dates[0] :
p_holdings.loc[date] = (capital * weights.iloc[0] / prices.loc[date])
# print ('INIT', cash, p_holdings.iloc[i-1],prices.loc[date], new_weights)
else :
portfolio_value = cash + (p_holdings.iloc[i - 1] * prices.loc[date]).sum() * new_weights
p_holdings.iloc[i] = (portfolio_value / prices.loc[date]).fillna(0)
# print ('{} BUY \n{}\n{}\n{}\n{}\n{}\nHOLDINGS\n{}\n'.format(date,cash,portfolio_value,p_holdings.iloc[i-1],
# prices.loc[date],new_weights,p_holdings.iloc[i]))
cash = (portfolio_value - p_holdings.iloc[i] * prices.loc[date]).sum()
# print ('{}\nPORTFOLIO VALUE\n{}\nCASH = {}'.format(date, portfolio_value,cash))
else :
p_holdings.iloc [i] = p_holdings.iloc [i - 1]
#print ('{} HOLDINGS UNCHANGED'.format(date))
p_value = (p_holdings * prices).sum(1)[p_holdings.index>=buy_dates[0]]
# print(p_holdings, )
p_weights = p_holdings.mul(prices).div(p_holdings.mul(prices).sum(axis=1), axis=0).fillna(0)
return p_value, p_holdings, p_weights
In [ ]:
symbols =['VCVSX','VGHCX', 'VWEHX','VFIIX','DRGIX','VWAHX']
# symbols =['CWB','VHT', 'HYG','HYD','MBB','IEF']
cash_proxy = 'VFIIX'
# cash_proxy = 'SHY'
risk_free = 0
rs_lookback = 1
risk_lookback = 1
n_top = 2
# get data
tickers = symbols.copy()
if cash_proxy != 'CASHX' :
tickers = list(set(tickers + [cash_proxy]))
if isinstance(risk_free, str) :
tickers = list(set(tickers + [risk_free]))
data = pd.DataFrame (columns=tickers)
for symbol in tickers :
url = 'http://chart.finance.yahoo.com/table.csv?s=' + symbol + '&ignore=.csv'
data[symbol] = pd.read_csv(url, parse_dates=True, index_col='Date').sort_index(ascending=True)['Adj Close']
inception_dates = pd.DataFrame([data[ticker].first_valid_index() for ticker in data.columns],
index=data.keys(), columns=['inception'])
print (inception_dates)
prices = data.copy().dropna()
end_points = endpoints(period='m', trading_days=prices.index)
prices_m = prices.loc[end_points]
returns = prices_m[symbols].pct_change(rs_lookback)[rs_lookback:]
MA = pd.rolling_mean(prices_m,2)[symbols]
# absolute_momentum_rule = returns > 0
absolute_momentum_rule = MA > 0
if isinstance(risk_free, int) :
excess_returns = returns
else :
risk_free_returns = prices_m[risk_free].pct_change(rs_lookback)[rs_lookback:]
excess_returns = returns.subtract(risk_free_returns, axis=0).dropna()
rebalance_dates = excess_returns.index.join(absolute_momentum_rule.index, how='inner')
# relative strength ranking
ranked = excess_returns.loc[rebalance_dates][symbols].rank(ascending=False, axis=1, method='dense')
# elligibility rule - top n_top ranked securities
elligible = ranked[ranked<=n_top] > 0
# equal weight allocations
elligible = elligible.multiply(1./elligible.sum(1), axis=0)
# downside protection
weights = pd.DataFrame(0.,index=elligible.index, columns=prices.columns)
if cash_proxy == 'CASHX' :
weights[cash_proxy] = 0
prices[cash_proxy] = 1.
weights[symbols] = (elligible * absolute_momentum_rule).dropna()
weights[cash_proxy] += 1 - weights[symbols].sum(axis=1)
# backtest
p_value, p_holdings, p_weights = backtest(prices, weights, 10000., offset=0, commission=10.)
p_value.plot(figsize=(15,10), grid=True)
In [6]:
import datetime as dt
p_value[p_value.index > dt.datetime(2011,1,1)].plot(figsize=(15,10), grid=True)
Out[6]:
<matplotlib.axes._subplots.AxesSubplot at 0xb73c908>
In [8]:
p_weights[p_weights.index > dt.datetime(2011,1,1)].loc[rebalance_dates].round(3).dropna()[:5]
Out[8]:
CWB
IEF
SHY
MBB
HYG
HYD
Date
2011-01-31
0.5
0.0
0
0.0
0.5
0.0
2011-02-28
0.5
0.0
0
0.0
0.0
0.5
2011-03-31
0.0
0.0
0
0.5
0.5
0.0
2011-04-29
0.5
0.5
0
0.0
0.0
0.0
2011-05-31
0.0
0.5
0
0.0
0.0
0.5
In [11]:
p_holdings[p_holdings.index > dt.datetime(2011,1,1)].loc[rebalance_dates].round(2).dropna()[:5]
Out[11]:
CWB
IEF
SHY
MBB
HYG
HYD
Date
2011-01-31
207.86
0.00
0
0.00
98.91
0.00
2011-02-28
207.40
0.00
0
0.00
0.00
304.82
2011-03-31
0.00
0.00
0
69.14
98.74
0.00
2011-04-29
205.95
77.62
0
0.00
0.00
0.00
2011-05-31
0.00
76.67
0
0.00
0.00
304.79
In [12]:
prices_m[prices_m.index > dt.datetime(2011,1,1)][:5]
Out[12]:
CWB
IEF
SHY
MBB
HYG
HYD
Date
2011-01-31
30.797647
83.492345
81.687149
93.520028
64.719870
20.749959
2011-02-28
31.376494
83.318340
81.587986
93.538675
65.648892
21.349132
2011-03-31
31.378728
83.189543
81.481940
93.792456
65.676742
21.199888
2011-04-29
31.929808
84.721287
81.917182
94.916737
66.725964
21.421149
2011-05-31
31.932052
86.844055
82.215374
95.879471
66.814904
21.845546
In [13]:
MA[MA.index > dt.datetime(2011,1,1)][:5]
Out[13]:
CWB
HYG
HYD
MBB
IEF
Date
2011-01-31
30.630671
64.194524
20.890112
93.471361
83.501244
2011-02-28
31.087070
65.184381
21.049546
93.529351
83.405342
2011-03-31
31.377611
65.662817
21.274510
93.665565
83.253941
2011-04-29
31.654268
66.201353
21.310519
94.354596
83.955415
2011-05-31
31.930930
66.770434
21.633348
95.398104
85.782671
In [7]:
# algo stats
ffn.calc_perf_stats(p_value).display()
Stats for None from 1987-04-30 00:00:00 - 2016-12-06 00:00:00
Annual risk-free rate considered: 0.00%
Summary:
Total Return Sharpe CAGR Max Drawdown
-------------- -------- ------ --------------
2601.66% 1.57 11.78% -19.31%
Annualized Returns:
mtd 3m 6m ytd 1y 3y 5y 10y incep.
------ ------ ------ ------ ------ ----- ----- ----- --------
-0.17% -6.15% -9.48% -7.02% -6.49% 4.34% 8.02% 9.64% 11.78%
Periodic:
daily monthly yearly
------ ------- --------- --------
sharpe 1.57 1.57 1.45
mean 11.40% 11.43% 12.57%
vol 7.27% 7.27% 8.69%
skew -1.05 -0.38 0.00
kurt 26.42 3.23 -0.30
best 5.63% 7.94% 29.92%
worst -8.74% -11.27% -7.02%
Drawdowns:
max avg # days
------- ------ --------
-19.31% -1.09% 20.82
Misc:
--------------- ------
avg. up month 1.88%
avg. down month -1.44%
up year % 96.55%
12m up % 95.38%
--------------- ------
In [20]:
def highlight_pos_neg (s) :
is_positive = s > 0
return ['background-color : rgb(127,255,0)' if v else 'background-color : rgb(255,99,71)' for v in is_positive]
df = monthly_return_table (p_value)
df.style.\
apply(highlight_pos_neg)
Out[20]:
Data
Data
Data
Data
Data
Data
Data
Data
Data
Data
Data
Data
Annual Returns
1
2
3
4
5
6
7
8
9
10
11
12
1992
1.38
1.76
-0.78
0.84
1.93
1.61
3.65
-0.44
1.02
0.13
1.83
1.43
16.08
1993
2.42
2.33
-0.69
-0.75
0.23
1.0
0.45
0.62
1.17
1.73
-0.55
0.93
9.72
1994
1.39
-0.93
-3.34
-0.76
0.86
-0.53
1.37
0.26
-0.38
0.5
-2.27
0.97
-2.97
1995
2.31
2.95
0.96
0.04
3.62
-0.3
2.24
0.0
0.65
1.25
1.82
0.45
17.71
1996
0.97
0.88
-0.08
1.98
1.07
-0.62
0.23
0.66
3.81
-0.58
1.81
-0.81
11.02
1997
1.93
0.39
-1.52
1.28
0.91
1.85
3.36
-0.38
2.88
-1.05
0.3
1.57
9.38
1998
1.43
0.07
2.36
0.05
0.86
0.65
0.52
-1.42
2.41
-1.18
2.43
2.08
10.09
1999
2.0
-2.71
0.4
0.87
-0.2
1.27
-0.75
-1.12
-0.16
0.35
3.39
4.74
5.4
2000
0.84
5.79
1.5
-0.38
-0.41
1.96
-0.59
1.63
-1.42
0.74
1.31
1.87
16.61
2001
4.27
-2.52
0.87
-0.45
0.41
-0.94
1.97
0.91
0.6
1.79
1.03
-0.2
10.11
2002
-0.5
1.07
-1.76
0.52
0.86
1.32
1.99
1.66
1.07
-1.56
3.67
0.43
8.32
2003
-0.54
0.24
-0.11
4.19
2.51
0.3
0.02
1.1
2.0
-0.8
1.73
1.69
11.51
2004
2.62
0.13
0.27
-1.95
-0.9
0.64
-0.45
1.86
0.71
0.78
-0.61
1.57
4.82
2005
-0.68
-0.39
-2.34
1.36
1.12
1.57
2.74
0.32
-0.89
-0.73
0.61
1.82
4.23
2006
2.54
0.81
0.89
-0.04
0.1
-0.17
1.34
1.57
0.79
1.48
1.64
0.59
13.51
2007
0.97
1.19
0.08
1.33
1.6
-0.93
-0.12
1.61
1.63
2.31
-0.07
0.16
10.61
2008
1.19
0.99
0.57
-0.43
0.71
-2.04
0.41
1.0
-2.26
-1.44
4.68
2.7
3.41
2009
3.29
-0.98
0.65
3.33
4.37
1.18
6.17
1.37
5.11
-2.83
1.01
0.94
28.11
2010
-0.45
0.42
2.37
1.23
-2.21
1.71
2.25
-0.86
0.22
2.86
-0.65
2.13
7.97
2011
2.12
1.87
0.1
1.38
1.18
0.3
1.13
-0.24
0.5
-0.36
-2.03
1.29
8.31
2012
2.82
2.44
0.15
-0.17
1.15
-0.09
1.85
0.59
1.7
0.24
1.28
0.3
14.03
2013
2.43
0.3
-0.06
1.37
0.85
-2.21
1.91
-1.25
1.6
1.36
0.22
0.92
6.94
2014
0.7
0.88
0.0
1.03
1.38
0.9
-1.62
1.27
-0.81
0.91
0.15
-0.09
5.82
2015
1.37
-1.31
0.31
0.35
0.67
-1.42
0.81
0.14
0.93
0.01
-0.72
0.48
1.03
2016
1.17
0.56
1.54
2.11
0.72
0.47
0.0
1.19
0.73
-0.72
-1.65
0.0
6.76
In [21]:
frame = df['Annual Returns'].to_frame()
frame['positive'] = df['Annual Returns'] >= 0
frame['Annual Returns'].plot(figsize=(15,10),kind='bar',color=frame.positive.map({True: 'g', False: 'r'}), grid=True)
Out[21]:
<matplotlib.axes._subplots.AxesSubplot at 0xf54278>
In [9]:
symbols =['MMHYX','FAGIX','VFIIX']
cash_proxy = 'CASHX'
risk_free = 0
rs_lookback = 3
risk_lookback = 2
n_top = 1
# get data
tickers = symbols.copy()
if cash_proxy != 'CASHX' :
tickers = list(set(tickers + [cash_proxy]))
if isinstance(risk_free, str) :
tickers = list(set(tickers + [risk_free]))
data = pd.DataFrame (columns=tickers)
for symbol in tickers :
url = 'http://chart.finance.yahoo.com/table.csv?s=' + symbol + '&ignore=.csv'
data[symbol] = pd.read_csv(url, parse_dates=True, index_col='Date').sort_index(ascending=True)['Adj Close']
inception_dates = pd.DataFrame([data[ticker].first_valid_index() for ticker in data.columns],
index=data.keys(), columns=['inception'])
print (inception_dates)
prices = data.copy().dropna()
end_points = endpoints(period='m', trading_days=prices.index)
prices_m = prices.loc[end_points]
returns = prices_m[symbols].pct_change(rs_lookback)[rs_lookback:]
absolute_momentum_rule = returns > 0
if isinstance(risk_free, int) :
excess_returns = returns
else :
risk_free_returns = prices_m[risk_free].pct_change(rs_lookback)[rs_lookback:]
excess_returns = returns.subtract(risk_free_returns, axis=0).dropna()
rebalance_dates = excess_returns.index.join(absolute_momentum_rule.index, how='inner')
# relative strength ranking
ranked = excess_returns.loc[rebalance_dates][symbols].rank(ascending=False, axis=1, method='dense')
# elligibility rule - top n_top ranked securities
elligible = ranked[ranked<=n_top] > 0
# equal weight allocations
elligible = elligible.multiply(1./elligible.sum(1), axis=0)
# downside protection
weights = pd.DataFrame(0.,index=elligible.index, columns=prices.columns)
if cash_proxy == 'CASHX' :
weights[cash_proxy] = 0
prices[cash_proxy] = 1.
weights[symbols] = (elligible * absolute_momentum_rule).dropna()
weights[cash_proxy] += 1 - weights[symbols].sum(axis=1)
# backtest
p_value, p_holdings, p_weights = backtest(prices, weights, 10000., offset=0, commission=10.)
p_value.plot(figsize=(15,10), grid=True)
inception
MMHYX 1984-02-23
FAGIX 1984-02-23
VFIIX 1984-02-23
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
<ipython-input-9-247e52321ff2> in <module>()
26 prices = data.copy().dropna()
27
---> 28 end_points = endpoints(period='m', trading_days=prices.index)
29 prices_m = prices.loc[end_points]
30
NameError: name 'endpoints' is not defined
In [7]:
# algo stats
ffn.calc_perf_stats(p_value).display()
Stats for None from 1984-05-31 00:00:00 - 2016-11-23 00:00:00
Annual risk-free rate considered: 0.00%
Summary:
Total Return Sharpe CAGR Max Drawdown
-------------- -------- ------ --------------
3650.34% 2.52 11.80% -8.40%
Annualized Returns:
mtd 3m 6m ytd 1y 3y 5y 10y incep.
------ ----- ----- ----- ----- ----- ----- ------ --------
-0.10% 0.18% 3.01% 5.87% 7.43% 6.88% 7.38% 11.76% 11.80%
Periodic:
daily monthly yearly
------ ------- --------- --------
sharpe 2.52 1.91 1.06
mean 11.25% 11.38% 12.34%
vol 4.47% 5.96% 11.69%
skew 0.61 0.81 2.35
kurt 10.60 3.98 6.81
best 3.86% 9.07% 56.88%
worst -2.57% -5.33% -0.57%
Drawdowns:
max avg # days
------ ------ --------
-8.40% -0.70% 20.21
Misc:
--------------- ------
avg. up month 1.56%
avg. down month -0.92%
up year % 96.88%
12m up % 95.79%
--------------- ------
In [8]:
def highlight_pos_neg (s) :
is_positive = s > 0
return ['background-color : rgb(127,255,0)' if v else 'background-color : rgb(255,99,71)' for v in is_positive]
df = monthly_return_table (p_value)
df.style.\
apply(highlight_pos_neg)
Out[8]:
Data
Data
Data
Data
Data
Data
Data
Data
Data
Data
Data
Data
Annual Returns
1
2
3
4
5
6
7
8
9
10
11
12
1984
0.0
0.0
0.0
0.0
0.0
0.0
0.0
0.77
0.08
3.86
0.06
0.08
12.59
1985
1.79
-1.78
1.13
1.69
4.4
1.21
0.84
1.48
0.36
1.94
2.64
1.89
17.19
1986
-0.88
0.65
3.33
1.43
1.13
1.31
-0.76
1.26
0.3
1.3
0.31
-0.66
3.72
1987
1.36
0.94
0.89
-2.5
0.0
0.0
0.0
0.51
-1.63
0.0
0.89
1.35
6.4
1988
4.5
0.92
-0.99
0.74
0.06
2.0
1.08
0.06
1.07
1.77
-1.62
0.19
8.72
1989
1.05
0.57
-0.59
1.77
1.12
2.68
1.81
-1.08
0.59
0.29
1.07
0.7
6.01
1990
-0.95
0.55
0.26
0.0
0.0
2.26
2.19
-2.39
0.69
1.13
2.32
1.78
20.66
1991
1.36
0.82
4.47
4.1
0.36
2.18
3.18
1.39
1.34
2.45
0.51
0.98
24.1
1992
-1.9
4.12
4.39
2.05
1.26
1.12
1.99
1.32
0.96
-0.99
0.59
0.89
19.19
1993
3.82
2.08
3.18
0.8
2.0
3.91
0.93
0.82
0.54
2.01
1.07
1.4
10.3
1994
3.01
-0.05
-2.59
-1.05
0.0
0.0
1.42
0.3
-1.22
-0.3
0.0
0.0
8.34
1995
2.0
2.49
0.97
0.63
1.81
0.61
0.01
0.33
1.38
0.32
1.18
1.02
8.23
1996
0.56
0.01
0.12
1.65
0.75
-0.32
-0.35
0.09
1.72
0.49
1.33
-0.57
4.35
1997
0.7
2.01
-2.0
0.24
0.99
1.28
2.28
0.62
4.64
-1.68
0.59
1.72
17.31
1998
1.16
2.11
2.61
1.14
-0.27
-0.24
0.56
1.27
1.04
-0.22
0.27
0.09
15.29
1999
4.15
-0.93
4.98
5.37
-1.74
1.58
-0.89
0.0
-1.09
0.57
0.06
-0.44
1.53
2000
-0.07
2.28
-0.68
-0.13
0.38
1.71
0.48
1.59
-0.19
0.57
1.57
1.35
4.61
2001
1.64
0.55
-5.33
0.06
0.73
0.76
1.48
1.83
-0.21
1.36
-1.0
-0.37
2.7
2002
-0.39
-2.11
-1.22
1.96
0.68
0.97
1.32
0.9
1.07
0.15
8.52
3.44
44.51
2003
4.31
2.16
4.26
8.14
2.55
3.46
-1.54
1.71
2.32
2.49
1.15
4.59
14.32
2004
1.88
-0.47
0.03
-1.93
0.0
0.0
0.0
1.46
2.14
2.37
2.67
2.42
13.53
2005
-0.1
2.1
-2.67
1.67
0.82
0.84
0.01
0.47
-0.47
-1.17
0.7
0.0
4.35
2006
1.71
0.81
0.98
0.85
-0.43
-0.44
0.92
1.27
0.63
1.9
2.09
1.28
15.2
2007
1.06
1.23
0.82
2.03
1.46
-1.16
-3.56
0.0
0.84
1.03
-2.4
0.04
-2.03
2008
1.39
0.62
0.21
-0.17
1.24
-2.28
-2.43
0.0
0.62
-1.52
3.77
1.74
14.75
2009
-0.08
0.47
3.68
2.92
7.4
4.34
9.07
2.91
7.66
0.39
2.73
4.87
39.47
2010
0.07
0.81
4.11
2.7
-5.18
1.34
0.99
0.4
0.71
4.0
-0.95
3.31
17.43
2011
2.99
1.94
0.11
2.05
-0.6
1.72
1.41
0.59
1.91
0.04
0.43
1.9
12.11
2012
4.2
0.76
0.39
0.69
-2.44
0.25
2.11
0.5
1.2
0.57
1.08
-1.03
10.12
2013
1.72
0.5
1.37
1.85
-0.4
-2.87
0.0
0.0
0.0
2.94
1.11
1.15
8.3
2014
0.08
1.45
-0.02
1.96
2.34
0.0
0.25
1.37
-2.21
1.11
0.36
0.87
5.09
2015
2.23
-0.87
0.27
0.6
1.06
-1.51
0.01
0.25
0.84
0.61
0.6
0.98
6.25
2016
1.08
0.45
0.96
0.93
0.9
-0.41
-0.02
1.93
0.54
-0.5
-0.1
0.0
1.43
In [9]:
frame = df['Annual Returns'].to_frame()
frame['positive'] = df['Annual Returns'] >= 0
frame['Annual Returns'].plot(figsize=(15,10),kind='bar',color=frame.positive.map({True: 'g', False: 'r'}), grid=True)
Out[9]:
<matplotlib.axes._subplots.AxesSubplot at 0xbd13a90>
In [28]:
start = dt.datetime(2003,1,1)
end = dt.datetime(2016,11,1)
symbols =['MMHYX','FAGIX','VFIIX']
cash_proxy = 'CASHX'
risk_free = 0
rs_lookback = 1
risk_lookback =1
n_top = 1
# get data
tickers = symbols.copy()
if cash_proxy != 'CASHX' :
tickers = list(set(tickers + [cash_proxy]))
if isinstance(risk_free, str) :
tickers = list(set(tickers + [risk_free]))
data = pd.DataFrame (columns=tickers)
for symbol in tickers :
url = 'http://chart.finance.yahoo.com/table.csv?s=' + symbol + '&ignore=.csv'
data[symbol] = pd.read_csv(url, parse_dates=True, index_col='Date').sort_index(ascending=True)['Adj Close']
inception_dates = pd.DataFrame([data[ticker].first_valid_index() for ticker in data.columns],
index=data.keys(), columns=['inception'])
print (inception_dates)
prices = data.copy().dropna()[start:end]
end_points = endpoints(period='q', trading_days=prices.index)
prices_m = prices.loc[end_points]
returns = prices_m[symbols].pct_change(rs_lookback)[rs_lookback:]
absolute_momentum_rule = returns > 0
if isinstance(risk_free, int) :
excess_returns = returns
else :
risk_free_returns = prices_m[risk_free].pct_change(rs_lookback)[rs_lookback:]
excess_returns = returns.subtract(risk_free_returns, axis=0).dropna()
rebalance_dates = excess_returns.index.join(absolute_momentum_rule.index, how='inner')
# relative strength ranking
ranked = excess_returns.loc[rebalance_dates][symbols].rank(ascending=False, axis=1, method='dense')
# elligibility rule - top n_top ranked securities
elligible = ranked[ranked<=n_top] > 0
# equal weight allocations
elligible = elligible.multiply(1./elligible.sum(1), axis=0)
# downside protection
weights = pd.DataFrame(0.,index=elligible.index, columns=prices.columns)
if cash_proxy == 'CASHX' :
weights[cash_proxy] = 0
prices[cash_proxy] = 1.
weights[symbols] = (elligible * absolute_momentum_rule).dropna()
weights[cash_proxy] += 1 - weights[symbols].sum(axis=1)
# backtest
p_value, p_holdings, p_weights = backtest(prices, weights, 10000., offset=0, commission=10.)
p_value.plot(figsize=(15,10), grid=True)
inception
MMHYX 1984-02-23
FAGIX 1984-02-23
VFIIX 1984-02-23
FIRST BUY DATE = 2003-04-30 00:00:00
Out[28]:
<matplotlib.axes._subplots.AxesSubplot at 0x113e5fd0>
In [29]:
# algo stats
ffn.calc_perf_stats(p_value).display()
Stats for None from 2003-04-30 00:00:00 - 2016-11-01 00:00:00
Annual risk-free rate considered: 0.00%
Summary:
Total Return Sharpe CAGR Max Drawdown
-------------- -------- ------ --------------
286.09% 2.27 10.52% -12.72%
Annualized Returns:
mtd 3m 6m ytd 1y 3y 5y 10y incep.
------ ----- ----- ----- ------ ----- ----- ------ --------
-0.52% 1.44% 5.51% 9.15% 10.89% 9.20% 9.34% 11.02% 10.52%
Periodic:
daily monthly yearly
------ ------- --------- --------
sharpe 2.27 1.62 0.79
mean 10.11% 10.18% 10.14%
vol 4.45% 6.28% 12.78%
skew -0.01 0.88 2.80
kurt 7.11 4.46 9.17
best 2.35% 9.07% 49.91%
worst -1.93% -5.18% -0.55%
Drawdowns:
max avg # days
------- ------ --------
-12.72% -0.77% 24.15
Misc:
--------------- ------
avg. up month 1.55%
avg. down month -0.94%
up year % 84.62%
12m up % 90.20%
--------------- ------
In [30]:
def highlight_pos_neg (s) :
is_positive = s > 0
return ['background-color : rgb(127,255,0)' if v else 'background-color : rgb(255,99,71)' for v in is_positive]
df = monthly_return_table (p_value)
df.style.\
apply(highlight_pos_neg)
Out[30]:
Data
Data
Data
Data
Data
Data
Data
Data
Data
Data
Data
Data
Annual Returns
1
2
3
4
5
6
7
8
9
10
11
12
2003
0.0
0.0
0.0
0.0
2.55
3.46
-1.54
1.71
2.32
2.49
1.15
4.59
18.67
2004
1.88
-0.47
0.03
-0.73
0.0
0.0
0.0
1.46
0.1
0.86
2.67
2.42
5.62
2005
-0.1
2.1
-2.67
-1.23
0.82
0.84
0.01
0.47
-0.47
-1.17
0.7
1.05
5.32
2006
0.32
0.81
0.98
0.85
-0.43
-0.44
0.76
1.27
0.79
0.79
2.09
1.28
11.83
2007
1.06
1.23
0.82
2.03
1.46
-1.16
-3.56
0.0
0.0
0.0
-2.4
0.4
-7.52
2008
-3.05
0.62
0.21
-0.17
1.24
-2.28
-2.43
0.0
0.0
0.0
3.77
1.74
4.32
2009
-0.08
0.47
1.6
0.37
7.4
4.34
9.07
2.91
7.66
0.39
2.73
4.87
57.97
2010
0.07
0.81
4.11
2.7
-5.18
-0.32
3.92
0.4
-0.33
1.11
-0.95
3.31
9.06
2011
2.99
1.94
0.11
2.05
-0.6
-1.61
-0.01
0.59
1.91
0.04
0.43
1.9
9.35
2012
3.69
0.76
0.17
1.8
-2.44
1.63
1.8
0.5
0.62
1.01
1.08
1.77
11.97
2013
1.72
0.5
1.37
1.85
-0.4
-2.87
2.01
0.0
0.0
0.0
1.11
1.15
5.19
2014
0.08
1.45
0.68
1.96
2.34
0.0
0.25
1.37
0.61
1.11
0.36
0.87
8.82
2015
2.23
-0.87
0.5
-0.24
1.06
-1.51
0.01
0.25
0.84
0.61
0.6
0.98
6.42
2016
1.08
0.45
0.96
0.93
0.9
-0.41
3.5
1.93
0.54
-0.5
-0.52
0.0
5.51
In [31]:
frame = df['Annual Returns'].to_frame()
frame['positive'] = df['Annual Returns'] >= 0
frame['Annual Returns'].plot(figsize=(15,10),kind='bar',color=frame.positive.map({True: 'g', False: 'r'}), grid=True)
Out[31]:
<matplotlib.axes._subplots.AxesSubplot at 0x113bad30>
In [25]:
# Zipline results (from Pycharm)
p_value1 = pd.read_pickle('E:\\NOTEBOOKS\\Quantopian\\Strategies\\rs0003F.pkl').portfolio_value
p_value1.plot(figsize=(15, 10), grid=True)
Out[25]:
<matplotlib.axes._subplots.AxesSubplot at 0x120dca20>
In [109]:
start = dt.datetime(2010,4,30)
end = dt.datetime(2011,11,1)
df = pd.DataFrame(index=p_value[start:end][:-1].index)
p1 = p_value[start:end][:-1]
p2 = p_value1[start:end]
df['BT'] = (p1 / p1.iloc[0]).values
df['Z'] = (p2 / p2.iloc[0]).values
In [117]:
df.plot(figsize=(15, 10), grid=True)
Out[117]:
<matplotlib.axes._subplots.AxesSubplot at 0x144c8eb8>
In [26]:
# algo stats
ffn.calc_perf_stats(p_value1).display()
Stats for portfolio_value from 2003-01-02 21:00:00 - 2016-11-01 20:00:00
Annual risk-free rate considered: 0.00%
Summary:
Total Return Sharpe CAGR Max Drawdown
-------------- -------- ------ --------------
79.03% 1.04 4.30% -17.45%
Annualized Returns:
mtd 3m 6m ytd 1y 3y 5y 10y incep.
------ ----- ----- ----- ----- ----- ----- ----- --------
-0.21% 0.95% 3.45% 5.63% 6.30% 3.79% 4.26% 4.95% 4.30%
Periodic:
daily monthly yearly
------ ------- --------- --------
sharpe 1.04 0.73 0.39
mean 4.30% 4.40% 4.29%
vol 4.15% 6.03% 11.03%
skew -0.55 0.72 2.23
kurt 7.58 5.39 6.61
best 1.64% 8.05% 36.64%
worst -1.94% -5.91% -7.76%
Drawdowns:
max avg # days
------- ------ --------
-17.45% -1.04% 46.43
Misc:
--------------- ------
avg. up month 1.25%
avg. down month -1.01%
up year % 69.23%
12m up % 71.79%
--------------- ------
In [27]:
def highlight_pos_neg (s) :
is_positive = s > 0
return ['background-color : rgb(127,255,0)' if v else 'background-color : rgb(255,99,71)' for v in is_positive]
df = monthly_return_table (p_value1)
df.style.\
apply(highlight_pos_neg)
Out[27]:
Data
Data
Data
Data
Data
Data
Data
Data
Data
Data
Data
Data
Annual Returns
1
2
3
4
5
6
7
8
9
10
11
12
2003
0.0
0.0
0.0
0.45
2.05
3.21
-1.56
0.26
2.1
2.06
0.63
0.5
11.71
2004
1.5
-1.23
-0.51
-0.58
-0.38
-0.19
0.1
1.44
0.76
0.46
2.56
0.59
2.17
2005
-0.83
2.03
-3.27
-1.35
0.78
0.54
0.15
-0.35
-0.71
-1.96
0.1
0.1
-4.23
2006
-0.26
0.36
0.71
0.12
-0.94
-1.18
0.29
1.04
0.44
0.09
1.5
0.79
3.69
2007
0.45
0.67
0.56
1.55
0.87
-1.62
-4.68
0.78
0.39
-0.12
-3.02
-0.23
-8.61
2008
-3.88
-0.19
0.19
-1.17
0.47
-2.46
-2.91
0.28
0.28
0.28
0.83
0.37
-4.85
2009
-0.86
0.19
1.24
1.48
7.37
4.27
8.05
3.39
7.07
-1.02
-0.68
1.7
39.57
2010
1.27
-0.12
3.84
2.47
-5.91
-0.7
3.13
0.0
-0.54
0.69
-0.85
1.29
5.4
2011
2.34
1.77
-0.31
1.23
-0.81
-2.65
0.17
-0.2
1.76
-0.85
0.36
-0.81
-0.19
2012
0.23
0.96
0.51
1.38
1.61
0.24
2.07
0.42
0.54
-0.09
0.32
0.85
10.87
2013
1.58
-0.31
1.04
1.34
-0.3
-3.57
1.56
-0.19
0.38
-0.04
0.82
-0.2
-0.01
2014
-0.43
1.77
0.56
1.3
2.26
0.0
-0.36
1.14
0.18
0.88
0.12
0.7
10.38
2015
1.4
-1.26
0.11
-1.05
0.4
-2.49
-0.55
-0.19
0.37
-0.31
-0.06
0.7
-3.83
2016
0.47
-0.23
1.28
0.57
0.0
-1.19
3.82
1.58
-0.21
-0.31
-0.21
0.0
5.14
In [8]:
frame = df['Annual Returns'].to_frame()
frame['positive'] = df['Annual Returns'] >= 0
frame['Annual Returns'].plot(figsize=(15,10),kind='bar',color=frame.positive.map({True: 'g', False: 'r'}), grid=True)
Out[8]:
<matplotlib.axes._subplots.AxesSubplot at 0xddd7dd8>
In [ ]: