RELATIVE STRENGTH (RS)


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

DM0003(VCP001)


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>

RS0002 (SMBS01)


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>

RS0003 (SQBS01)


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 [ ]: