Monday, 26 February 2018

Meucci MOOC and Marathon

A quik update on what's new at Attilio Meucci's ARPM course offerngs in 2018.

  • ARPM MOOC : a light package for self-learners: 25 hours of online instruction, split into 134 micro video lectures.
  • ARPM Marathon : provides a deep absorption of the materials: 120 hours of online instruction, with graded homework and one-on-one discussions.

And of course the original ARPM Bootcamp is still held in person in NYC this year too. Enjoy!

Wednesday, 6 July 2016

mini-Meucci : Applying The Checklist - Steps 10+

In this final leg of The Checklist tour we'll be looking at the Dynamic Allocation step and touch briefly on ex-post Performance Analysis.

Dynamic Allocation

Essentially this involves repeating the previous 9-steps on a periodic basis (e.g. a sequence of monthly allocations) according to a chosen allocation policy.

Examples of dynamic allocations include systematic strategies (based on signals) and portfolio insurance.

See slide #55 Dynamic Allocation (general case).

Quantitative/Systematic Strategies

Those interested in active portfolio management and who attend the ARPM bootcamp, will also have access to the ARPM Lab. In it are 2 very interesting chapters covering both the theory and practice (with code) of quant strategies, where you'll learn among other things, how to construct a characteristic portfolio (Grinold and Easton) based on your signals. For an example of such a characteristic portfolio strategy, see the youtube video on slide #56 Dynamic Allocation (video).

Example Python Code

In our toy example with the goal of constructing a low volatility equity portfolio, our chosen allocation policy will be to weight the 30 DJIA stocks according to the ex-ante minimum variance portfolio, and rebalance the portfolio at the end of each month.

We'll use an expanding historical data window of at least 3 years, apply time-conditioned weights to the observations when estimating the ex-ante distribution, and also use a simple form of shrinkage before optimizing.

To simulate such a sequence of allocations over a 3 year period, we'll use the open source Zipline package.

In [2]:
%matplotlib inline
import rnr_meucci_functions as rnr
import numpy as np
from zipline.api import (set_slippage, slippage, set_commission, commission, 
 order_target_percent, record, schedule_function,
 date_rules, time_rules, get_datetime, symbol)
# Set tickers for data loading i.e. DJIA constituents and DIA ETF for benchmark
tickers = ['MMM','AXP','AAPL','BA','CAT','CVX','CSCO','KO','DD','XOM','GE','GS',
 'HD','INTC','IBM','JNJ','JPM','MCD','MRK','MSFT','NKE','PFE','PG',
 'TRV','UNH','UTX','VZ','V','WMT','DIS', 'DIA']
# Set investable asset tickers
asset_tickers = ['MMM','AXP','AAPL','BA','CAT','CVX','CSCO','KO','DD','XOM','GE',
 'GS','HD','INTC','IBM','JNJ','JPM','MCD','MRK','MSFT','NKE','PFE','PG',
 'TRV','UNH','UTX','VZ','V','WMT','DIS']
 
def initialize(context):
 # Turn off the slippage model
 set_slippage(slippage.FixedSlippage(spread=0.0))
 # Set the commission model
 set_commission(commission.PerShare(cost=0.01, min_trade_cost=1.0))
 context.day = -1 # using zero-based counter for days
 context.set_benchmark(symbol('DIA'))
 context.assets = []
 print('Setup investable assets...')
 for ticker in asset_tickers:
 #print(ticker)
 context.assets.append(symbol(ticker))
 context.n_asset = len(context.assets)
 context.n_portfolio = 40 # num mean-variance efficient portfolios to compute
 context.today = None
 context.tau = None
 context.min_data_window = 756 # min of 3 yrs data for calculations
 context.first_rebal_date = None
 context.first_rebal_idx = None
 context.weights = None
 # Schedule dynamic allocation calcs to occur 1 day before month end - note that
 # actual trading will occur on the close on the last trading day of the month
 schedule_function(rebalance,
 date_rule=date_rules.month_end(days_offset=1),
 time_rule=time_rules.market_close())
 # Record some stuff every day
 schedule_function(record_vars,
 date_rule=date_rules.every_day(),
 time_rule=time_rules.market_close())
def handle_data(context, data):
 context.day += 1
 #print(context.day)
 
def rebalance(context, data):
 # Wait for 756 trading days (3 yrs) of historical prices before trading
 if context.day < context.min_data_window - 1:
 return
 # Get expanding window of past prices and compute returns
 context.today = get_datetime().date() 
 prices = data.history(context.assets, "price", context.day, "1d")
 if context.first_rebal_date is None:
 context.first_rebal_date = context.today
 context.first_rebal_idx = context.day
 print('Starting dynamic allocation simulation...')
 # Get investment horizon in days ie number of trading days next month
 context.tau = rnr.get_num_days_nxt_month(context.today.month, context.today.year)
 # Calculate HFP distribution
 asset_rets = np.array(prices.pct_change(context.tau).iloc[context.tau:, :])
 num_scenarios = len(asset_rets)
 # Set Flexible Probabilities Using Exponential Smoothing
 half_life_prjn = 252 * 2 # in days
 lambda_prjn = np.log(2) / half_life_prjn
 probs_prjn = np.exp(-lambda_prjn * (np.arange(0, num_scenarios)[::-1]))
 probs_prjn = probs_prjn / sum(probs_prjn)
 mu_pc, sigma2_pc = rnr.fp_mean_cov(asset_rets.T, probs_prjn)
 # Perform shrinkage to mitigate estimation risk
 mu_shrk, sigma2_shrk = rnr.simple_shrinkage(mu_pc, sigma2_pc)
 weights, _, _ = rnr.efficient_frontier_qp_rets(context.n_portfolio, 
 sigma2_shrk, mu_shrk)
 print('Optimal weights calculated 1 day before month end on %s (day=%s)' \
 % (context.today, context.day))
 #print(weights)
 min_var_weights = weights[0,:]
 # Rebalance portfolio accordingly
 for stock, weight in zip(prices.columns, min_var_weights):
 order_target_percent(stock, np.asscalar(weight))
 context.weights = min_var_weights
 
def record_vars(context, data):
 record(weights=context.weights, tau=context.tau)
 
def analyze(perf, bm_value, start_idx):
 pd.DataFrame({'portfolio':results.portfolio_value,'benchmark':bm_value})\
 .iloc[start_idx:,:].plot(title='Portfolio Performance vs Benchmark',\
 figsize=(10, 8))
if __name__ == '__main__':
 from datetime import datetime
 import pytz
 from zipline.algorithm import TradingAlgorithm
 from zipline.utils.factory import load_bars_from_yahoo
 import pandas as pd
 import matplotlib.pyplot as plt
 
 # Create and run the algorithm.
 algo = TradingAlgorithm(initialize=initialize, handle_data=handle_data)
 start = datetime(2010, 5, 1, 0, 0, 0, 0, pytz.utc)
 end = datetime(2016, 5, 31, 0, 0, 0, 0, pytz.utc)
 print('Getting Yahoo data for 30 DJIA stocks and DIA ETF as benchmark...')
 data = load_bars_from_yahoo(stocks=tickers, start=start, end=end)
 # Check price data
 data.loc[:, :, 'price'].plot(figsize=(8,7), title='Input Price Data')
 plt.ylabel('price in $');
 plt.legend(loc='center left', bbox_to_anchor=(1.0, 0.5))
 plt.show()
 
 # Run algorithm
 results = algo.run(data)
 
 # Fix possible issue with timezone
 results.index = results.index.normalize()
 if results.index.tzinfo is None:
 results.index = results.index.tz_localize('UTC')
 
 # Adjust benchmark returns for delayed trading due to 3 year min data window 
 bm_rets = algo.perf_tracker.all_benchmark_returns
 bm_rets[0:algo.first_rebal_idx + 2] = 0
 bm_rets.name = 'DIA'
 bm_rets.index.freq = None
 bm_value = algo.capital_base * np.cumprod(1+bm_rets)
 
 # Plot portfolio and benchmark values
 analyze(results, bm_value, algo.first_rebal_idx + 1)
 print('End value portfolio = {:.0f}'.format(results.portfolio_value.ix[-1]))
 print('End value benchmark = {:.0f}'.format(bm_value[-1]))
 
 # Plot end weights
 pd.DataFrame(results.weights.ix[-1], index=asset_tickers, columns=['w'])\
 .sort_values('w', ascending=False).plot(kind='bar', \
 title='End Simulation Weights', legend=None, figsize=(10, 8));
Getting Yahoo data for 30 DJIA stocks and DIA ETF as benchmark...
C:\Anaconda3\envs\py34\lib\site-packages\ipykernel\__main__.py:106: DeprecationWarning:
load_bars_from_yahoo is deprecated, please register a yahoo_equities data bundle instead
Setup investable assets...
Starting dynamic allocation simulation...
Optimal weights calculated 1 day before month end on 2013年05月30日 (day=774)
Optimal weights calculated 1 day before month end on 2013年06月27日 (day=794)
Optimal weights calculated 1 day before month end on 2013年07月30日 (day=816)
.
.
.
Optimal weights calculated 1 day before month end on 2016年03月30日 (day=1487)
Optimal weights calculated 1 day before month end on 2016年04月28日 (day=1508)
Optimal weights calculated 1 day before month end on 2016年05月27日 (day=1529)
End value portfolio = 130872
End value benchmark = 125771

Ex-post Performance Analysis

"Ex-post performance analysis is a broad subject that attracts tremendous attention from practitioners, as their compensation is ultimately tied to the results of this analysis. Ex-post performance can be broken down into two components: performance of the target portfolio from the Optimization [Construction] Step P 8 and slippage performance from the Execution Step P 9."
The Prayer (Ex-post Analysis section, former Checklist)

Python Code Example

We'll use the open-source Pyfolio package that works nicely with Zipline, and is quite comprehensive in its analytics.

As you can see below, our toy example of a DJIA stock portfolio was able to achieve lower annualised volatility of 11.74% compared to the DIA ETF of 13.05%. If the volatility is not low enough for you, then you could always expand the universe of stocks or add a negatively correlated bond ETF, like TLT, into the mix - which I'll leave as an exercise for the interested reader.

In [3]:
# Sequel Step - Ex-post performance analysis
import pyfolio as pf
returns, positions, transactions, gross_lev = pf.utils.\
 extract_rets_pos_txn_from_zipline(results)
trade_start = results.index[algo.first_rebal_idx + 1]
trade_end = datetime(2016, 5, 31, 0, 0, 0, 0, pytz.utc)
print('Annualised volatility of the portfolio = {:.4}'.\
 format(pf.timeseries.annual_volatility(returns[trade_start:trade_end])))
print('Annualised volatility of the benchmark = {:.4}'.\
 format(pf.timeseries.annual_volatility(bm_rets[trade_start:trade_end])))
print('')
pf.create_returns_tear_sheet(returns[trade_start:trade_end], 
 benchmark_rets=bm_rets[trade_start:trade_end])
Annualised volatility of the portfolio = 0.1174
Annualised volatility of the benchmark = 0.1305
Entire data start date: 2013年05月31日
Entire data end date: 2016年05月31日
Backtest Months: 36
 Backtest
annual_return 0.09
annual_volatility 0.12
sharpe_ratio 0.82
calmar_ratio 0.77
stability 0.85
max_drawdown -0.12
omega_ratio 1.15
sortino_ratio 1.19
skewness -0.04
kurtosis 2.57
information_ratio 0.01
alpha 0.03
beta 0.82
[see appendix below for full Pyfolio returns analytics]



That's all folks! Hope you have enjoyed the journey and learnt something along the way...

P.S. If you do sign-up for the Bootcamp, please let the good folks at ARPM know you learnt about it at returnandrisk.com!

References to Attilio Meucci's Work

  1. The Checklist slides
  2. The Prayer (former Checklist)

Download Python Code

Click here for the GitHub repo

Appendix - Full Pyfolio Returns Analytics

Annualised volatility of the portfolio = 0.1174
Annualised volatility of the benchmark = 0.1305
Entire data start date: 2013年05月31日
Entire data end date: 2016年05月31日
Backtest Months: 36
 Backtest
annual_return 0.09
annual_volatility 0.12
sharpe_ratio 0.82
calmar_ratio 0.77
stability 0.85
max_drawdown -0.12
omega_ratio 1.15
sortino_ratio 1.19
skewness -0.04
kurtosis 2.57
information_ratio 0.01
alpha 0.03
beta 0.82
Worst Drawdown Periods
 net drawdown in % peak date valley date recovery date duration
0 12.23 2015年07月20日 2015年08月25日 2015年10月23日 70
1 6.46 2015年12月29日 2016年01月20日 2016年02月25日 43
4 6.42 2013年08月05日 2013年10月08日 2013年11月06日 68
2 6.39 2014年11月28日 2014年12月16日 2015年05月19日 123
3 6.39 2013年11月29日 2014年02月03日 2014年03月28日 86
2-sigma returns daily -0.014
2-sigma returns weekly -0.027
dtype: float64
C:\Anaconda3\envs\py34\lib\site-packages\pyfolio\plotting.py:1356: FutureWarning:
sort(columns=....) is deprecated, use sort_values(by=.....) print(drawdown_df.sort('net drawdown in %', ascending=False))
Subscribe to: Comments (Atom)

AltStyle によって変換されたページ (->オリジナル) /