The package PortfolioAnalytics will be used throughout this course for portfolio optimization and analysis. We will use the indexes dataset that is included with the PortfolioAnalytics package for the remaining exercises in this chapter. In this exercise, we will load the package and prepare the data for the portfolio optimization problem in the next exercise.
# Load the package
library(PortfolioAnalytics)
# Load the data
data(indexes)
# Subset the data
index_returns <- indexes[, c(1:4)]
# Print the head of the data
head(index_returns)
US Bonds US Equities Int'l Equities Commodities
1980-01-31 -0.0272 0.0610 0.0462 0.0568
1980-02-29 -0.0669 0.0031 -0.0040 -0.0093
1980-03-31 0.0053 -0.0987 -0.1188 -0.1625
1980-04-30 0.0992 0.0429 0.0864 0.0357
1980-05-31 0.0000 0.0562 0.0446 0.0573
1980-06-30 0.0605 0.0296 0.0600 0.0533
This first exercise will teach you how to solve a simple portfolio optimization problem using PortfolioAnalytics. You will learn how to create a portfolio specification object, add constraints and objectives, and solve the optimization problem. The portfolio problem is to form a minimum variance portfolio subject to full investment and long only constraints. The objective is to minimize portfolio variance. There are two constraints in this problem: the full investment constraint means that the weights must sum to 1, and the long only constraint means that all weights must be greater than or equal to 0 (i.e. no short positions are allowed).
You need to install below libraries for ROI to work:
# Create the portfolio specification
port_spec <- portfolio.spec(colnames(index_returns))
# Add a full investment constraint such that the weights sum to 1
port_spec <- add.constraint(portfolio =port_spec, type = "full_investment")
# Add a long only constraint such that the weight of an asset is between 0 and 1
port_spec <- add.constraint(portfolio = port_spec, type = "long_only")
# Add an objective to minimize portfolio standard deviation
port_spec <- add.objective(portfolio = port_spec, type = "risk", name = "StdDev")
# Solve the optimization problem
opt <- optimize.portfolio(index_returns, portfolio = port_spec, optimize_method = "ROI")
As you will see, the modular nature of the contraints and objectives makes this very flexible!
Now that we have run the optimization, we would like to take a look at the output and results. Recall that the optimization output is in a variable named opt. In our case, for the portfolio optimization in the previous exercise, we are interested in the optimal weights and estimated objective value. The weights are considered optimal in the sense that the set of weights minimizes the objective value, portfolio standard deviation, and satisfies the full investment and long only constraints based on historical data.
# Print the results of the optimization
print(opt)
***********************************
PortfolioAnalytics Optimization
***********************************
Call:
optimize.portfolio(R = index_returns, portfolio = port_spec,
optimize_method = "ROI")
Optimal Weights:
US Bonds US Equities Int'l Equities Commodities
0.8544 0.0587 0.0000 0.0869
Objective Measure:
StdDev
0.01668
# Extract the optimal weights
extractWeights(opt)
US Bonds US Equities Int'l Equities Commodities
0.85441777 0.05872385 0.00000000 0.08685838
# Chart the optimal weights
chart.Weights(opt)
Visualization is important for any analysis, especially portfolio optimization. Modern Portfolio Theory (MPT) is all about maximizing gain while simultaneously controlling for risk. Academically, risk and gain are defined as standard deviation and mean return. In practice, many other measures of risk and gain are also used!
The formulation of quadratic utility is:
Maximize \(w^T∗μ−λ∗w^T∗Σ∗w\)
This formula maximizes the expected return while penalizing for the standard deviation scaled by the risk aversion parameter, lambda!
In the video on challenges of portfolio optimization, you saw how to solve a quadratic utility optimization problem with the package quadprog. This exercise will show you how to solve a quadratic utility problem using the PortfolioAnalytics package. Recall the quadratic utility formulation has two terms, one for portfolio mean return and another for portfolio variance with a risk aversion parameter, lambda.
# Create the portfolio specification
port_spec <- portfolio.spec(assets = colnames(index_returns))
# Add a full investment constraint such that the weights sum to 1
port_spec <- add.constraint(portfolio = port_spec, type = "full_investment")
# Add a long only constraint such that the weight of an asset is between 0 and 1
port_spec <- add.constraint(portfolio = port_spec, type = "long_only")
# Add an objective to maximize portfolio mean return
port_spec <- add.objective(portfolio = port_spec, type = "return", name = "mean")
# Add an objective to minimize portfolio variance
port_spec <- add.objective(portfolio = port_spec, type = "risk", name = "var", risk_aversion = 10)
# Solve the optimization problem
opt <- optimize.portfolio(R = index_returns, portfolio = port_spec, optimize_method = "ROI")
Modular constraints and objectives to add, remove, and combine multiple constraint and objective types very easily
Support for user defined moment functions
Visualizations to build intuition about the problem and understand the feasible space of portfolios
PortfolioAnalytics switches seamlessly through a number of solvers.
The first step in the workflow of PortfolioAnalytics is to create the portfolio specification object. The portfolio specification holds portfolio level data, constraints and objectives. The only required argument to portfolio.spec() is assets. assets can be the number of assets, a named vector of seed weights, or a character vector of the asset names. The category_labels argument is used for labeling assets by a category such as sector, industry, region, asset class, or currency. The weight_seq argument allows you to specify a seed sequence of weights that are used by the random portfolios algorithms. Common practice is to pass in the column names of the returns object for the assets argument.
data(edhec)
asset_returns <- edhec
# Get the column names of the returns data
asset_names <- colnames(asset_returns)
# Create a portfolio specification object using asset_names
port_spec <- portfolio.spec(assets = asset_names)
# Get the class of the portfolio specification object
class(port_spec)
[1] "portfolio.spec" "portfolio"
# Print the portfolio specification object
print(port_spec)
**************************************************
PortfolioAnalytics Portfolio Specification
**************************************************
Call:
portfolio.spec(assets = asset_names)
Number of assets: 13
Asset Names
[1] "Convertible Arbitrage" "CTA Global"
[3] "Distressed Securities" "Emerging Markets"
[5] "Equity Market Neutral" "Event Driven"
[7] "Fixed Income Arbitrage" "Global Macro"
[9] "Long/Short Equity" "Merger Arbitrage"
More than 10 assets, only printing the first 10
Now that you have the base specification, let’s add some constraints.
Constraints are added to the portfolio specification object with the add.constraint() function. Each constraint added is a separate object and stored in the constraints slot in the portfolio object. In this way, the constraints are modular and one can easily add, remove, or modify the constraints in the portfolio object. The required arguments for add.constraint() are the portfolio the constraint is added to, the constraint type, and named arguments passed via … to the constructor of the constraint type. Basic constraint types:
In this exercise, you will add a few of the more common constraint types. In addition to the basic constraint types listed above, PortfolioAnalytics also supports position limit, turnover, diversification, factor exposure, and leverage exposure constraint types. If you are interested in the other constraint types, look at the help files for the constraint constructors. The help files include a description of the constraint type as well as example code.
# Add the weight sum constraint
port_spec <- add.constraint(portfolio = port_spec, type = "weight_sum", min_sum = 1, max_sum = 1)
# Add the box constraint
port_spec <- add.constraint(portfolio = port_spec, type = "box", min = c(0.1, 0.1, 0.1, 0.1, 0.1, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05, 0.05), max = 0.4)
# Add the group constraint
port_spec <- add.constraint(portfolio = port_spec, type = "group", groups = list(c(1, 5, 7, 9, 10, 11), c(2, 3, 4, 6, 8, 12)), group_min = 0.4, group_max = 0.6)
# Print the portfolio specification object
print(port_spec)
**************************************************
PortfolioAnalytics Portfolio Specification
**************************************************
Call:
portfolio.spec(assets = asset_names)
Number of assets: 13
Asset Names
[1] "Convertible Arbitrage" "CTA Global"
[3] "Distressed Securities" "Emerging Markets"
[5] "Equity Market Neutral" "Event Driven"
[7] "Fixed Income Arbitrage" "Global Macro"
[9] "Long/Short Equity" "Merger Arbitrage"
More than 10 assets, only printing the first 10
Constraints
Enabled constraint types
- weight_sum
- box
- group
There are a number of different constraint types, check them out using ?add.constraint
Objectives are added to the portfolio object with the add.objective() function. Each objective added is a separate object and stored in the objectives slot in the portfolio specification object. In this way, the objectives are modular and one can easily add, remove, or modify the objective objects. The name argument must be a valid R function. Several functions are available in the PerformanceAnalytics package, but user defined functions can also be used as objective functions. The required arguments for add.objective() are the portfolio the objective is added to, the objective type, the objective name, and named arguments passed via … to the constructor of the objective type. Arguments for the objective function are specified as a named list to arguments.
Basic objective types:
In addition to the objective types listed above, PortfolioAnalytics also supports quadratic utility and weight concentration objective types.
# Add a return objective to maximize mean return
port_spec <- add.objective(portfolio = port_spec, type = "return", name = "mean")
# Add a risk objective to minimize portfolio standard deviation
port_spec <- add.objective(portfolio = port_spec, type = "risk", name = "StdDev")
# Add a risk budget objective
port_spec <- add.objective(portfolio = port_spec, type = "risk_budget", name = "StdDev", min_prisk = 0.05, max_prisk = 0.1)
# Print the portfolio specification object
print(port_spec)
**************************************************
PortfolioAnalytics Portfolio Specification
**************************************************
Call:
portfolio.spec(assets = asset_names)
Number of assets: 13
Asset Names
[1] "Convertible Arbitrage" "CTA Global"
[3] "Distressed Securities" "Emerging Markets"
[5] "Equity Market Neutral" "Event Driven"
[7] "Fixed Income Arbitrage" "Global Macro"
[9] "Long/Short Equity" "Merger Arbitrage"
More than 10 assets, only printing the first 10
Constraints
Enabled constraint types
- weight_sum
- box
- group
Objectives:
Enabled objective names
- mean
- StdDev
- StdDev
There are two functions for running the optimization, optimize.portfolio() and optimize.portfolio.rebalancing(). This exercise will focus on single period optimization and the next exercise will use optimize.portfolio.rebalancing() for optimization with periodic rebalancing. optimize.portfolio() supports single-period optimization. Key arguments include R for the asset returns, portfolio for the portfolio specification object, and optimize_method to specify the optimization method used to solve the problem. In many cases, it is useful to specify trace = TRUE to store additional information for each iteration/trial of the optimization.
The following optimization methods are supported:
The optimization method you choose should be based on the type of problem you are solving. For example, a problem that can be formulated as a quadratic programming problem should be solved using a quadratic programming solver, whereas a non-convex problem should be solved using a global solver such as DEoptim.
In this exercise, we will define the portfolio optimization problem to maximize mean return and minimize portfolio standard deviation with a standard deviation risk budget where the minimum percentage risk is 5% and the maximum percentage risk is 10%, subject to full investment and long only constraints. The risk budget objective requires a global solver so we will solve the problem using random portfolios. The set of random portfolios, rp, is generated using 500 permutations for this exercise.
rp <- random_portfolios(portfolio=port_spec, permutations = 500, rp_method ='simplex')
#rp <- random_portfolios(portfolio=port_spec, permutations=500, #rp_method='sample')
#rp <- random_portfolios(portfolio=port_spec, permutations=500, #rp_method='grid')
# Run a single period optimization using random portfolios as the optimization method
opt <- optimize.portfolio(R = asset_returns, portfolio = port_spec, optimize_method = "random", rp = rp, trace = TRUE)
Leverage constraint min_sum and max_sum are restrictive,
consider relaxing. e.g. 'full_investment' constraint should be min_sum=0.99 and max_sum=1.01
# Print the output of the single-period optimization
print(opt)
***********************************
PortfolioAnalytics Optimization
***********************************
Call:
optimize.portfolio(R = asset_returns, portfolio = port_spec,
optimize_method = "random", trace = TRUE, rp = rp)
Optimal Weights:
Convertible Arbitrage CTA Global Distressed Securities
0.10 0.10 0.10
Emerging Markets Equity Market Neutral Event Driven
0.10 0.10 0.05
Fixed Income Arbitrage Global Macro Long/Short Equity
0.05 0.05 0.05
Merger Arbitrage Relative Value Short Selling
0.05 0.05 0.15
Funds of Funds
0.05
Objective Measures:
mean
0.004265
StdDev
0.008989
contribution :
Convertible Arbitrage CTA Global Distressed Securities
0.0010590 0.0010412 0.0010417
Emerging Markets Equity Market Neutral Event Driven
0.0018036 0.0004904 0.0005022
Fixed Income Arbitrage Global Macro Long/Short Equity
0.0003724 0.0005020 0.0005084
Merger Arbitrage Relative Value Short Selling
0.0002266 0.0003762 0.0005633
Funds of Funds
0.0005016
pct_contrib_StdDev :
Convertible Arbitrage CTA Global Distressed Securities
0.11782 0.11584 0.11589
Emerging Markets Equity Market Neutral Event Driven
0.20065 0.05456 0.05587
Fixed Income Arbitrage Global Macro Long/Short Equity
0.04144 0.05585 0.05656
Merger Arbitrage Relative Value Short Selling
0.02521 0.04185 0.06267
Funds of Funds
0.05580
You just optimized a portfolio that minimized the standard deviation and maximized the mean return!
Running the optimization with periodic rebalancing and analyzing the out-of-sample results of the backtest is an important step to better understand and potentially refine the constraints and objectives. optimize.portfolio.rebalancing() supports optimization with periodic rebalancing (backtesting) to examine out of sample performance. In addition to the arguments for optimize.portfolio(), a periodic rebalancing frequency must be specified with rebalance_on, training_period to specify the number of periods to use as the training data for the initial optimization, and rolling_window to specify the number of periods for the window width of the optimization. If rolling_window is set to NULL each optimization will use all data available at the given period the optimization is run.
To reduce computation time for this exercise, the set of random portfolios, rp, is generated using 50 permutations, and search_size, how many portfolios to test, is set to 1000. If you are actually optimizing portfolios yourself, you’ll probably want to test more portfolios (the default value for search_size is 20,000)!
Run the optimization with quarterly rebalancing. Set the training period and rolling window to 60 periods. The dataset is monthly data so we are using 5 years of historical data. Assign the optimization output to a variable named opt_rebal.
# Create a portfolio specification object using asset_names
port_spec <- portfolio.spec(assets = asset_names)
# Add a full investment constraint such that the weights sum to 1
port_spec <- add.constraint(portfolio =port_spec, type = "long_only")
# Add the weight sum constraint
port_spec <- add.constraint(portfolio = port_spec, type = "weight_sum", min_sum = 0.99, max_sum = 1.01)
# Add a return objective to maximize mean return
port_spec <- add.objective(portfolio = port_spec, type = "return", name = "mean")
# Add a risk objective to minimize portfolio standard deviation
port_spec <- add.objective(portfolio = port_spec, type = "risk", name = "StdDev")
# Add a risk budget objective
port_spec <- add.objective(portfolio = port_spec,
type = "risk_budget", name = "StdDev", min_prisk = 0.05,
max_prisk = 0.1)
rp <- random_portfolios(portfolio=port_spec,
permutations = 50,
rp_method='simplex')
# Run the optimization backtest with quarterly rebalancing
opt_rebal <- optimize.portfolio.rebalancing(R = asset_returns, portfolio = port_spec, optimize_method = "random", rp = rp, trace = TRUE, search_size = 1000, rebalance_on = "quarters", training_period = 60, rolling_window = 60)
# Print the output of the optimization backtest
print(opt_rebal)
**************************************************
PortfolioAnalytics Optimization with Rebalancing
**************************************************
Call:
optimize.portfolio.rebalancing(R = asset_returns, portfolio = port_spec,
optimize_method = "random", search_size = 1000, trace = TRUE,
rp = rp, rebalance_on = "quarters", training_period = 60,
rolling_window = 60)
Number of rebalancing dates: 73
First rebalance date:
[1] "2001-12-31"
Last rebalance date:
[1] "2019-11-30"
Annualized Portfolio Rebalancing Return:
[1] 0.04308334
Annualized Portfolio Standard Deviation:
[1] 0.03502795
Using backtesting can give useful insights on how your optimization setup performs under different circumstances.
This exercise is a continuation of the last exercises analysing the output of opt and opt_rebal. All objectives in the portfolio specification are evaluated during the optimization and the values can be extracted with extractObjectiveMeasures(). The objective measures are an important component of the optimization output to analyze. An important use case looks at the result of each iteration/trial of the optimizer. This can give you insight into how the optimizer approaches the optimal solution and potentially tune parameters. We can also analyse the distribution of the in-sample objective values of the optimal portfolios and compare to out-of-sample performance.
# Extract the objective measures for the single period optimization
extractObjectiveMeasures(opt)
$mean
mean
0.004265327
$StdDev
$StdDev$StdDev
[1] 0.008988572
$StdDev$contribution
Convertible Arbitrage CTA Global Distressed Securities
0.0010590433 0.0010411982 0.0010416617
Emerging Markets Equity Market Neutral Event Driven
0.0018035800 0.0004903910 0.0005022044
Fixed Income Arbitrage Global Macro Long/Short Equity
0.0003724447 0.0005019871 0.0005084311
Merger Arbitrage Relative Value Short Selling
0.0002265651 0.0003761851 0.0005632954
Funds of Funds
0.0005015844
$StdDev$pct_contrib_StdDev
Convertible Arbitrage CTA Global Distressed Securities
0.11782108 0.11583578 0.11588735
Emerging Markets Equity Market Neutral Event Driven
0.20065257 0.05455717 0.05587143
Fixed Income Arbitrage Global Macro Long/Short Equity
0.04143536 0.05584726 0.05656417
Merger Arbitrage Relative Value Short Selling
0.02520591 0.04185149 0.06266795
Funds of Funds
0.05580246
# Extract the objective measures for the optimization backtest
head(extractObjectiveMeasures(opt_rebal))
mean StdDev
2001-12-31 0.008784722 0.007922754
2002-03-31 0.008271627 0.007722497
2002-06-30 0.008328058 0.007720750
2002-09-30 0.007900130 0.007430129
2002-12-31 0.007571272 0.007584567
2003-03-31 0.007347474 0.007692301
StdDev.contribution.Convertible Arbitrage
2001-12-31 0.0003401262
2002-03-31 0.0003596149
2002-06-30 0.0003634403
2002-09-30 0.0003805327
2002-12-31 0.0003708623
2003-03-31 0.0003861341
StdDev.contribution.CTA Global
2001-12-31 0.0010331223
2002-03-31 0.0009535650
2002-06-30 0.0008925825
2002-09-30 0.0005477816
2002-12-31 0.0007286846
2003-03-31 0.0009089203
StdDev.contribution.Distressed Securities
2001-12-31 5.456254e-05
2002-03-31 5.452174e-05
2002-06-30 5.618569e-05
2002-09-30 5.667865e-05
2002-12-31 5.390294e-05
2003-03-31 5.284363e-05
StdDev.contribution.Emerging Markets
2001-12-31 1.033830e-04
2002-03-31 6.934062e-05
2002-06-30 7.350974e-05
2002-09-30 5.277680e-05
2002-12-31 4.523016e-05
2003-03-31 3.776604e-05
StdDev.contribution.Equity Market Neutral
2001-12-31 2.182620e-05
2002-03-31 2.169650e-05
2002-06-30 2.250135e-05
2002-09-30 1.817923e-05
2002-12-31 1.836090e-05
2003-03-31 1.895013e-05
StdDev.contribution.Event Driven
2001-12-31 0.0002727352
2002-03-31 0.0002730606
2002-06-30 0.0002842180
2002-09-30 0.0002840013
2002-12-31 0.0002491465
2003-03-31 0.0002296462
StdDev.contribution.Fixed Income Arbitrage
2001-12-31 0.0009139324
2002-03-31 0.0008734091
2002-06-30 0.0008824052
2002-09-30 0.0008965251
2002-12-31 0.0009065155
2003-03-31 0.0008966847
StdDev.contribution.Global Macro
2001-12-31 0.0009543666
2002-03-31 0.0008605850
2002-06-30 0.0008751872
2002-09-30 0.0006867022
2002-12-31 0.0006883180
2003-03-31 0.0007050678
StdDev.contribution.Long/Short Equity
2001-12-31 1.088709e-05
2002-03-31 1.170751e-05
2002-06-30 1.327361e-05
2002-09-30 1.214673e-05
2002-12-31 4.444858e-06
2003-03-31 1.884263e-06
StdDev.contribution.Merger Arbitrage
2001-12-31 0.0004516729
2002-03-31 0.0004842594
2002-06-30 0.0004961724
2002-09-30 0.0005077632
2002-12-31 0.0004669544
2003-03-31 0.0004389722
StdDev.contribution.Relative Value
2001-12-31 0.001566572
2002-03-31 0.001539066
2002-06-30 0.001596227
2002-09-30 0.001707684
2002-12-31 0.001527948
2003-03-31 0.001489758
StdDev.contribution.Short Selling
2001-12-31 0.002145965
2002-03-31 0.002172074
2002-06-30 0.002114347
2002-09-30 0.002236516
2002-12-31 0.002484087
2003-03-31 0.002487312
StdDev.contribution.Funds of Funds
2001-12-31 5.360247e-05
2002-03-31 4.959677e-05
2002-06-30 5.069969e-05
2002-09-30 4.284070e-05
2002-12-31 4.011111e-05
2003-03-31 3.836093e-05
StdDev.pct_contrib_StdDev.Convertible Arbitrage
2001-12-31 0.04293030
2002-03-31 0.04656718
2002-06-30 0.04707319
2002-09-30 0.05121482
2002-12-31 0.04889697
2003-03-31 0.05019748
StdDev.pct_contrib_StdDev.CTA Global
2001-12-31 0.13039939
2002-03-31 0.12347885
2002-06-30 0.11560827
2002-09-30 0.07372437
2002-12-31 0.09607464
2003-03-31 0.11815974
StdDev.pct_contrib_StdDev.Distressed Securities
2001-12-31 0.006886815
2002-03-31 0.007060118
2002-06-30 0.007277233
2002-09-30 0.007628219
2002-12-31 0.007106924
2003-03-31 0.006869678
StdDev.pct_contrib_StdDev.Emerging Markets
2001-12-31 0.013048873
2002-03-31 0.008979042
2002-06-30 0.009521063
2002-09-30 0.007103080
2002-12-31 0.005963446
2003-03-31 0.004909589
StdDev.pct_contrib_StdDev.Equity Market Neutral
2001-12-31 0.002754876
2002-03-31 0.002809519
2002-06-30 0.002914400
2002-09-30 0.002446692
2002-12-31 0.002420824
2003-03-31 0.002463519
StdDev.pct_contrib_StdDev.Event Driven
2001-12-31 0.03442429
2002-03-31 0.03535911
2002-06-30 0.03681223
2002-09-30 0.03822293
2002-12-31 0.03284914
2003-03-31 0.02985403
StdDev.pct_contrib_StdDev.Fixed Income Arbitrage
2001-12-31 0.1153554
2002-03-31 0.1130993
2002-06-30 0.1142901
2002-09-30 0.1206608
2002-12-31 0.1195211
2003-03-31 0.1165691
StdDev.pct_contrib_StdDev.Global Macro
2001-12-31 0.12045894
2002-03-31 0.11143870
2002-06-30 0.11335521
2002-09-30 0.09242131
2002-12-31 0.09075245
2003-03-31 0.09165890
StdDev.pct_contrib_StdDev.Long/Short Equity
2001-12-31 0.0013741542
2002-03-31 0.0015160259
2002-06-30 0.0017192129
2002-09-30 0.0016347937
2002-12-31 0.0005860398
2003-03-31 0.0002449543
StdDev.pct_contrib_StdDev.Merger Arbitrage
2001-12-31 0.05700958
2002-03-31 0.06270761
2002-06-30 0.06426479
2002-09-30 0.06833841
2002-12-31 0.06156639
2003-03-31 0.05706644
StdDev.pct_contrib_StdDev.Relative Value
2001-12-31 0.1977307
2002-03-31 0.1992964
2002-06-30 0.2067450
2002-09-30 0.2298324
2002-12-31 0.2014549
2003-03-31 0.1936688
StdDev.pct_contrib_StdDev.Short Selling
2001-12-31 0.2708610
2002-03-31 0.2812658
2002-06-30 0.2738526
2002-09-30 0.3010064
2002-12-31 0.3275186
2003-03-31 0.3233509
StdDev.pct_contrib_StdDev.Funds of Funds
2001-12-31 0.006765636
2002-03-31 0.006422375
2002-06-30 0.006566680
2002-09-30 0.005765809
2002-12-31 0.005288517
2003-03-31 0.004986925
Studing the objective measures for a rebalanced portfolio can provide insights into how it changed over time.
This exercise is a continuation of the last exercises analysing the the output of opt and opt_rebal. Extracting and visualizing the optimal weights is an important component of the optimization. The optimal weights can be extracted with extractWeights() and charted with chart.Weights(). This is particularly useful for backtests to understand the evolution of weights over time. We can then answer questions about how allocations change through time.
# Extract the optimal weights for the single period optimization
extractWeights(opt)
Convertible Arbitrage CTA Global Distressed Securities
0.10 0.10 0.10
Emerging Markets Equity Market Neutral Event Driven
0.10 0.10 0.05
Fixed Income Arbitrage Global Macro Long/Short Equity
0.05 0.05 0.05
Merger Arbitrage Relative Value Short Selling
0.05 0.05 0.15
Funds of Funds
0.05
# Chart the weights for the single period optimization
chart.Weights(opt)
# Extract the optimal weights for the optimization backtest
head(extractWeights(opt_rebal))
Convertible Arbitrage CTA Global Distressed Securities
2001-12-31 0.06825613 0.105605 0.01061614
2002-03-31 0.06825613 0.105605 0.01061614
2002-06-30 0.06825613 0.105605 0.01061614
2002-09-30 0.06825613 0.105605 0.01061614
2002-12-31 0.06825613 0.105605 0.01061614
2003-03-31 0.06825613 0.105605 0.01061614
Emerging Markets Equity Market Neutral Event Driven
2001-12-31 0.01130702 0.00941978 0.04783457
2002-03-31 0.01130702 0.00941978 0.04783457
2002-06-30 0.01130702 0.00941978 0.04783457
2002-09-30 0.01130702 0.00941978 0.04783457
2002-12-31 0.01130702 0.00941978 0.04783457
2003-03-31 0.01130702 0.00941978 0.04783457
Fixed Income Arbitrage Global Macro Long/Short Equity
2001-12-31 0.09509155 0.08732321 0.006100986
2002-03-31 0.09509155 0.08732321 0.006100986
2002-06-30 0.09509155 0.08732321 0.006100986
2002-09-30 0.09509155 0.08732321 0.006100986
2002-12-31 0.09509155 0.08732321 0.006100986
2003-03-31 0.09509155 0.08732321 0.006100986
Merger Arbitrage Relative Value Short Selling Funds of Funds
2001-12-31 0.1163051 0.3366911 0.0953108 0.01013861
2002-03-31 0.1163051 0.3366911 0.0953108 0.01013861
2002-06-30 0.1163051 0.3366911 0.0953108 0.01013861
2002-09-30 0.1163051 0.3366911 0.0953108 0.01013861
2002-12-31 0.1163051 0.3366911 0.0953108 0.01013861
2003-03-31 0.1163051 0.3366911 0.0953108 0.01013861
# Chart the weights for the optimization backtest
chart.Weights(opt_rebal)
Visualizing a rebalanced portfolio’s weights over time can help you identify potential problems in your model.
The default method for estimating portfolio moments is the sample method. The moments are calculated in optimize.portfolio() by evaluating the function passed to the momentFUN argument. The default for momentFUN is set.portfolio.moments() which defaults to calculating the sample moments. The moments are then used as inputs to the objective functions. The moments that must be estimated depend on the objectives. For example, an objective to minimize portfolio standard deviation requires only an estimate of the second moment. Compare that to the objective to maximize Sharpe Ratio which requires the first and second moments to be estimated. Sample estimates of the moments have disadvantages including estimation error and the curse of dimensionality. There is an increased risk of estimation error as the dimension of assets and parameters to estimate increase.
# Add a return objective with "mean" as the objective name
port_spec <- add.objective(portfolio = port_spec, type = "return", name = "mean")
# Calculate the sample moments
moments <- set.portfolio.moments(R = asset_returns, portfolio = port_spec)
# Check if moments$mu is equal to the sample estimate of mean returns
moments$mu == colMeans(asset_returns)
[,1]
[1,] TRUE
[2,] TRUE
[3,] TRUE
[4,] TRUE
[5,] TRUE
[6,] TRUE
[7,] TRUE
[8,] TRUE
[9,] TRUE
[10,] TRUE
[11,] TRUE
[12,] TRUE
[13,] TRUE
# Add a risk objective with "StdDev" as the objective name
port_spec <- add.objective(portfolio = port_spec, type = "risk", name = "StdDev")
# Calculate the sample moments using set.portfolio.moments. Assign to a variable named moments.
moments <- set.portfolio.moments(R = asset_returns, portfolio = port_spec)
# Check if moments$sigma is equal to the sample estimate of the variance-covariance matrix
moments$sigma == cov(asset_returns)
Convertible Arbitrage CTA Global
Convertible Arbitrage TRUE TRUE
CTA Global TRUE TRUE
Distressed Securities TRUE TRUE
Emerging Markets TRUE TRUE
Equity Market Neutral TRUE TRUE
Event Driven TRUE TRUE
Fixed Income Arbitrage TRUE TRUE
Global Macro TRUE TRUE
Long/Short Equity TRUE TRUE
Merger Arbitrage TRUE TRUE
Relative Value TRUE TRUE
Short Selling TRUE TRUE
Funds of Funds TRUE TRUE
Distressed Securities Emerging Markets
Convertible Arbitrage TRUE TRUE
CTA Global TRUE TRUE
Distressed Securities TRUE TRUE
Emerging Markets TRUE TRUE
Equity Market Neutral TRUE TRUE
Event Driven TRUE TRUE
Fixed Income Arbitrage TRUE TRUE
Global Macro TRUE TRUE
Long/Short Equity TRUE TRUE
Merger Arbitrage TRUE TRUE
Relative Value TRUE TRUE
Short Selling TRUE TRUE
Funds of Funds TRUE TRUE
Equity Market Neutral Event Driven
Convertible Arbitrage TRUE TRUE
CTA Global TRUE TRUE
Distressed Securities TRUE TRUE
Emerging Markets TRUE TRUE
Equity Market Neutral TRUE TRUE
Event Driven TRUE TRUE
Fixed Income Arbitrage TRUE TRUE
Global Macro TRUE TRUE
Long/Short Equity TRUE TRUE
Merger Arbitrage TRUE TRUE
Relative Value TRUE TRUE
Short Selling TRUE TRUE
Funds of Funds TRUE TRUE
Fixed Income Arbitrage Global Macro
Convertible Arbitrage TRUE TRUE
CTA Global TRUE TRUE
Distressed Securities TRUE TRUE
Emerging Markets TRUE TRUE
Equity Market Neutral TRUE TRUE
Event Driven TRUE TRUE
Fixed Income Arbitrage TRUE TRUE
Global Macro TRUE TRUE
Long/Short Equity TRUE TRUE
Merger Arbitrage TRUE TRUE
Relative Value TRUE TRUE
Short Selling TRUE TRUE
Funds of Funds TRUE TRUE
Long/Short Equity Merger Arbitrage Relative Value
Convertible Arbitrage TRUE TRUE TRUE
CTA Global TRUE TRUE TRUE
Distressed Securities TRUE TRUE TRUE
Emerging Markets TRUE TRUE TRUE
Equity Market Neutral TRUE TRUE TRUE
Event Driven TRUE TRUE TRUE
Fixed Income Arbitrage TRUE TRUE TRUE
Global Macro TRUE TRUE TRUE
Long/Short Equity TRUE TRUE TRUE
Merger Arbitrage TRUE TRUE TRUE
Relative Value TRUE TRUE TRUE
Short Selling TRUE TRUE TRUE
Funds of Funds TRUE TRUE TRUE
Short Selling Funds of Funds
Convertible Arbitrage TRUE TRUE
CTA Global TRUE TRUE
Distressed Securities TRUE TRUE
Emerging Markets TRUE TRUE
Equity Market Neutral TRUE TRUE
Event Driven TRUE TRUE
Fixed Income Arbitrage TRUE TRUE
Global Macro TRUE TRUE
Long/Short Equity TRUE TRUE
Merger Arbitrage TRUE TRUE
Relative Value TRUE TRUE
Short Selling TRUE TRUE
Funds of Funds TRUE TRUE
Simple sample moments might not always be a good estimate. Let’s look at some other methods for estimating moments. ### Advanced moment estimates PortfolioAnalytics supports the “sample” method as well as three more advanced methods for estimating portfolio moments.
In this exercise, you will estimate the second moment using the “boudt” method. A portfolio specification object named port_spec with a “StdDev” objective has already been created.
# Print the portfolio specification object
print(port_spec)
**************************************************
PortfolioAnalytics Portfolio Specification
**************************************************
Call:
portfolio.spec(assets = asset_names)
Number of assets: 13
Asset Names
[1] "Convertible Arbitrage" "CTA Global"
[3] "Distressed Securities" "Emerging Markets"
[5] "Equity Market Neutral" "Event Driven"
[7] "Fixed Income Arbitrage" "Global Macro"
[9] "Long/Short Equity" "Merger Arbitrage"
More than 10 assets, only printing the first 10
Constraints
Enabled constraint types
- long_only
- weight_sum
Objectives:
Enabled objective names
- mean
- StdDev
- StdDev
- mean
- StdDev
# Fit a statistical factor model to the asset returns
fit <- statistical.factor.model(R = asset_returns, k = 3)
# Estimate the portfolio moments using the "boudt" method with 3 factors
moments_boudt <- set.portfolio.moments(R = asset_returns, portfolio = port_spec, method = "boudt", k = 3)
# Check if the covariance matrix extracted from the model fit is equal to the estimate in `moments_boudt`
moments_boudt$sigma == extractCovariance(fit)
Convertible Arbitrage CTA Global
Convertible Arbitrage TRUE TRUE
CTA Global TRUE TRUE
Distressed Securities TRUE TRUE
Emerging Markets TRUE TRUE
Equity Market Neutral TRUE TRUE
Event Driven TRUE TRUE
Fixed Income Arbitrage TRUE TRUE
Global Macro TRUE TRUE
Long/Short Equity TRUE TRUE
Merger Arbitrage TRUE TRUE
Relative Value TRUE TRUE
Short Selling TRUE TRUE
Funds of Funds TRUE TRUE
Distressed Securities Emerging Markets
Convertible Arbitrage TRUE TRUE
CTA Global TRUE TRUE
Distressed Securities TRUE TRUE
Emerging Markets TRUE TRUE
Equity Market Neutral TRUE TRUE
Event Driven TRUE TRUE
Fixed Income Arbitrage TRUE TRUE
Global Macro TRUE TRUE
Long/Short Equity TRUE TRUE
Merger Arbitrage TRUE TRUE
Relative Value TRUE TRUE
Short Selling TRUE TRUE
Funds of Funds TRUE TRUE
Equity Market Neutral Event Driven
Convertible Arbitrage TRUE TRUE
CTA Global TRUE TRUE
Distressed Securities TRUE TRUE
Emerging Markets TRUE TRUE
Equity Market Neutral TRUE TRUE
Event Driven TRUE TRUE
Fixed Income Arbitrage TRUE TRUE
Global Macro TRUE TRUE
Long/Short Equity TRUE TRUE
Merger Arbitrage TRUE TRUE
Relative Value TRUE TRUE
Short Selling TRUE TRUE
Funds of Funds TRUE TRUE
Fixed Income Arbitrage Global Macro
Convertible Arbitrage TRUE TRUE
CTA Global TRUE TRUE
Distressed Securities TRUE TRUE
Emerging Markets TRUE TRUE
Equity Market Neutral TRUE TRUE
Event Driven TRUE TRUE
Fixed Income Arbitrage TRUE TRUE
Global Macro TRUE TRUE
Long/Short Equity TRUE TRUE
Merger Arbitrage TRUE TRUE
Relative Value TRUE TRUE
Short Selling TRUE TRUE
Funds of Funds TRUE TRUE
Long/Short Equity Merger Arbitrage Relative Value
Convertible Arbitrage TRUE TRUE TRUE
CTA Global TRUE TRUE TRUE
Distressed Securities TRUE TRUE TRUE
Emerging Markets TRUE TRUE TRUE
Equity Market Neutral TRUE TRUE TRUE
Event Driven TRUE TRUE TRUE
Fixed Income Arbitrage TRUE TRUE TRUE
Global Macro TRUE TRUE TRUE
Long/Short Equity TRUE TRUE TRUE
Merger Arbitrage TRUE TRUE TRUE
Relative Value TRUE TRUE TRUE
Short Selling TRUE TRUE TRUE
Funds of Funds TRUE TRUE TRUE
Short Selling Funds of Funds
Convertible Arbitrage TRUE TRUE
CTA Global TRUE TRUE
Distressed Securities TRUE TRUE
Emerging Markets TRUE TRUE
Equity Market Neutral TRUE TRUE
Event Driven TRUE TRUE
Fixed Income Arbitrage TRUE TRUE
Global Macro TRUE TRUE
Long/Short Equity TRUE TRUE
Merger Arbitrage TRUE TRUE
Relative Value TRUE TRUE
Short Selling TRUE TRUE
Funds of Funds TRUE TRUE
Some of the pitfalls of sample moments are avoided using these other estimation methods.
In many cases for constrained optimization problems, the portfolio manager or analyst may want to estimate moments for a specific technique and/or further extend the idea of set.portfolio.moments(). A user defined custom moment function can have any arbitrary named arguments. However, arguments named R for the asset returns and portfolio for the portfolio object will be detected automatically and handled in an efficient manner. Because of this, it is strongly encouraged to use R for the asset returns object and portfolio for the portfolio object.
The custom moment function should return a named list where the elements represent the moments:
In this exercise, you will write a custom moment function to estimate the variance-covariance matrix using a robust method. We will use the cov.rob() function from the MASS package. The function signature should have arguments named R for the asset returns and portfolio for the specification object. The function should return a named list. Because you are only estimating the second moment, you only need to return a list with one element appropriately named. You can apply these rules to write custom moment functions for other models such as factor models, GARCH models, or any other class of models that theoretically should be a better estimate than the sample estimate.
library(MASS)
# Define custom moment function
moments_robust <- function(R, portfolio){
out <- list()
out$sigma <- cov.rob(R, method = "mcd")$cov
out
}
# Estimate the portfolio moments using the function you just defined
moments <- moments_robust(R = asset_returns, portfolio = port_spec)
# Check the moment estimate
cov.rob(asset_returns, method = "mcd")$cov == moments$sigma
Convertible Arbitrage CTA Global
Convertible Arbitrage FALSE FALSE
CTA Global FALSE FALSE
Distressed Securities FALSE FALSE
Emerging Markets FALSE FALSE
Equity Market Neutral FALSE FALSE
Event Driven FALSE FALSE
Fixed Income Arbitrage FALSE FALSE
Global Macro FALSE FALSE
Long/Short Equity FALSE FALSE
Merger Arbitrage FALSE FALSE
Relative Value FALSE FALSE
Short Selling FALSE FALSE
Funds of Funds FALSE FALSE
Distressed Securities Emerging Markets
Convertible Arbitrage FALSE FALSE
CTA Global FALSE FALSE
Distressed Securities FALSE FALSE
Emerging Markets FALSE FALSE
Equity Market Neutral FALSE FALSE
Event Driven FALSE FALSE
Fixed Income Arbitrage FALSE FALSE
Global Macro FALSE FALSE
Long/Short Equity FALSE FALSE
Merger Arbitrage FALSE FALSE
Relative Value FALSE FALSE
Short Selling FALSE FALSE
Funds of Funds FALSE FALSE
Equity Market Neutral Event Driven
Convertible Arbitrage FALSE FALSE
CTA Global FALSE FALSE
Distressed Securities FALSE FALSE
Emerging Markets FALSE FALSE
Equity Market Neutral FALSE FALSE
Event Driven FALSE FALSE
Fixed Income Arbitrage FALSE FALSE
Global Macro FALSE FALSE
Long/Short Equity FALSE FALSE
Merger Arbitrage FALSE FALSE
Relative Value FALSE FALSE
Short Selling FALSE FALSE
Funds of Funds FALSE FALSE
Fixed Income Arbitrage Global Macro
Convertible Arbitrage FALSE FALSE
CTA Global FALSE FALSE
Distressed Securities FALSE FALSE
Emerging Markets FALSE FALSE
Equity Market Neutral FALSE FALSE
Event Driven FALSE FALSE
Fixed Income Arbitrage FALSE FALSE
Global Macro FALSE FALSE
Long/Short Equity FALSE FALSE
Merger Arbitrage FALSE FALSE
Relative Value FALSE FALSE
Short Selling FALSE FALSE
Funds of Funds FALSE FALSE
Long/Short Equity Merger Arbitrage Relative Value
Convertible Arbitrage FALSE FALSE FALSE
CTA Global FALSE FALSE FALSE
Distressed Securities FALSE FALSE FALSE
Emerging Markets FALSE FALSE FALSE
Equity Market Neutral FALSE FALSE FALSE
Event Driven FALSE FALSE FALSE
Fixed Income Arbitrage FALSE FALSE FALSE
Global Macro FALSE FALSE FALSE
Long/Short Equity FALSE FALSE FALSE
Merger Arbitrage FALSE FALSE FALSE
Relative Value FALSE FALSE FALSE
Short Selling FALSE FALSE FALSE
Funds of Funds FALSE FALSE FALSE
Short Selling Funds of Funds
Convertible Arbitrage FALSE FALSE
CTA Global FALSE FALSE
Distressed Securities FALSE FALSE
Emerging Markets FALSE FALSE
Equity Market Neutral FALSE FALSE
Event Driven FALSE FALSE
Fixed Income Arbitrage FALSE FALSE
Global Macro FALSE FALSE
Long/Short Equity FALSE FALSE
Merger Arbitrage FALSE FALSE
Relative Value FALSE FALSE
Short Selling FALSE FALSE
Funds of Funds FALSE FALSE
Now we would like to run the optimization using our custom moment function. Recall that the portfolio moments are set in optimize.portfolio() when the moment function is evaluated. We use the custom moment function by passing in the name to the momentFUN argument in optimize.portfolio(). Note how we can use PortfolioAnalytics to easily run optimizations using different methods for estimating moments, which will allow us to evaluate different techniques for moment estimates and refine those estimates by analyzing the optimization results.
# Create the portfolio specification
port_spec <- portfolio.spec(colnames(asset_returns))
# Add a full investment constraint such that the weights sum to 1
port_spec <- add.constraint(portfolio =port_spec, type = "full_investment")
# Add a long only constraint such that the weight of an asset is between 0 and 1
port_spec <- add.constraint(portfolio = port_spec, type = "long_only")
# Add an objective to minimize portfolio standard deviation
port_spec <- add.objective(portfolio = port_spec, type = "risk", name = "StdDev")
rp <- random_portfolios(portfolio=port_spec,
permutations = 50,
rp_method='sample')
# Run the optimization with custom moment estimates
opt_custom <- optimize.portfolio(R = asset_returns, portfolio = port_spec, optimize_method = "random", rp = rp, momentFUN = "moments_robust")
Leverage constraint min_sum and max_sum are restrictive,
consider relaxing. e.g. 'full_investment' constraint should be min_sum=0.99 and max_sum=1.01
# Print the results of the optimization with custom moment estimates
print(opt_custom)
***********************************
PortfolioAnalytics Optimization
***********************************
Call:
optimize.portfolio(R = asset_returns, portfolio = port_spec,
optimize_method = "random", rp = rp, momentFUN = "moments_robust")
Optimal Weights:
Convertible Arbitrage CTA Global Distressed Securities
0.178 0.050 0.014
Emerging Markets Equity Market Neutral Event Driven
0.000 0.042 0.000
Fixed Income Arbitrage Global Macro Long/Short Equity
0.420 0.046 0.170
Merger Arbitrage Relative Value Short Selling
0.058 0.004 0.014
Funds of Funds
0.004
Objective Measures:
StdDev
0.006572
# Run the optimization with sample moment estimates
opt_sample <- optimize.portfolio(R = asset_returns, portfolio = port_spec, optimize_method = "random", rp = rp)
Leverage constraint min_sum and max_sum are restrictive,
consider relaxing. e.g. 'full_investment' constraint should be min_sum=0.99 and max_sum=1.01
# Print the results of the optimization with sample moment estimates
print(opt_sample)
***********************************
PortfolioAnalytics Optimization
***********************************
Call:
optimize.portfolio(R = asset_returns, portfolio = port_spec,
optimize_method = "random", rp = rp)
Optimal Weights:
Convertible Arbitrage CTA Global Distressed Securities
0.0769 0.0769 0.0769
Emerging Markets Equity Market Neutral Event Driven
0.0769 0.0769 0.0769
Fixed Income Arbitrage Global Macro Long/Short Equity
0.0769 0.0769 0.0769
Merger Arbitrage Relative Value Short Selling
0.0769 0.0769 0.0769
Funds of Funds
0.0769
Objective Measures:
StdDev
0.009955
Looks like our custom moment function had a lower standard deviation than the sample method.
A key feature of PortfolioAnalytics is that the name for an objective is a valid R function. The package was designed to be flexible and modular, and custom objective functions are a great example of this. A few guidelines should be followed for defining a custom moment function:
The objective function must return a single value for the optimizer to minimize or maximize. It is strongly encouraged to use R for the asset returns and weights for the portfolio weights. These argument names are detected automatically and handled in an efficient manner. Any other arguments for the objective function can be passed in as a named list to arguments in the add.objective() function.
# Custom annualized portfolio standard deviation
pasd <- function(R, weights, sigma, scale = 12){
sqrt(as.numeric(t(weights) %*% sigma %*% weights)) * sqrt(scale)
}
How would you use this to optimize your portfolio?
This exercise builds on the previous exercise and we will run the optimization with the custom objective function that computes portfolio annualized standard deviation. Because an objective function can be any valid R function, we add a risk objective for the pasd() function. The set.portfolio.moments() function will not recognize the pasd() objective name, so we need to create a custom moment function to calculate the second moment, sigma. We will solve the problem using random portfolios as the optimization method.
set_sigma <- function(R){
out <- list()
out$sigma <- cov(R)
out
}
# Create the portfolio specification
port_spec <- portfolio.spec(colnames(asset_returns))
# Add a full investment constraint such that the weights sum to 1
port_spec <- add.constraint(portfolio =port_spec, type = "full_investment")
# Add a long only constraint such that the weight of an asset is between 0 and 1
port_spec <- add.constraint(portfolio = port_spec, type = "long_only", )
rp <- random_portfolios(portfolio=port_spec,
permutations = 40,
rp_method='simplex')
# Add custom objective to portfolio specification
port_spec <- add.objective(portfolio = port_spec, type = "risk", name = "pasd")
# Print the portfolio specificaton object
print(port_spec)
**************************************************
PortfolioAnalytics Portfolio Specification
**************************************************
Call:
portfolio.spec(assets = colnames(asset_returns))
Number of assets: 13
Asset Names
[1] "Convertible Arbitrage" "CTA Global"
[3] "Distressed Securities" "Emerging Markets"
[5] "Equity Market Neutral" "Event Driven"
[7] "Fixed Income Arbitrage" "Global Macro"
[9] "Long/Short Equity" "Merger Arbitrage"
More than 10 assets, only printing the first 10
Constraints
Enabled constraint types
- full_investment
- long_only
Objectives:
Enabled objective names
- pasd
# Run the optimization
opt <- optimize.portfolio(R = asset_returns, portfolio = port_spec, momentFUN = set_sigma, optimize_method = "random", rp = rp)
Leverage constraint min_sum and max_sum are restrictive,
consider relaxing. e.g. 'full_investment' constraint should be min_sum=0.99 and max_sum=1.01
# Print the results of the optimization
print(opt)
***********************************
PortfolioAnalytics Optimization
***********************************
Call:
optimize.portfolio(R = asset_returns, portfolio = port_spec,
optimize_method = "random", rp = rp, momentFUN = set_sigma)
Optimal Weights:
Convertible Arbitrage CTA Global Distressed Securities
0.2647 0.0142 0.1670
Emerging Markets Equity Market Neutral Event Driven
0.0986 0.1576 0.0036
Fixed Income Arbitrage Global Macro Long/Short Equity
0.0389 0.0161 0.0100
Merger Arbitrage Relative Value Short Selling
0.0339 0.0978 0.0806
Funds of Funds
0.0169
Objective Measures:
pasd
0.03656
In this exercise, we will create a benchmark to evaluate the performance of the optimization models in later exercises. An equal weight benchmark is a simple weighting scheme to construct the benchmark portfolio. The intuition of an equal weight approach is that there is no preference for any given asset. We are setting this up to answer the question, “Can optimization outperform a simple weighting scheme to construct a portfolio?”
# Load the package
library(PortfolioAnalytics)
# Load the data
data(edhec)
# Assign the data to a variable
asset_returns <- edhec
# Create a vector of equal weights
equal_weights <- rep(1 / ncol(edhec), ncol(edhec))
# Compute the benchmark returns
r_benchmark <- Return.portfolio(R = asset_returns, weights = equal_weights, rebalance_on = "quarters")
colnames(r_benchmark) <- "benchmark"
# Plot the benchmark returns
plot(r_benchmark)
We define the portfolio optimization problem to minimize portfolio standard deviation subject to full investment and long only constraints. In this problem, we will set up the portfolio specification based on the defined problem. The following exercises in this chapter will build on the initial portfolio specification set up here.
# Create the portfolio specification
port_spec <- portfolio.spec(assets = colnames(asset_returns))
# Add a full investment constraint such that the weights sum to 1
port_spec <- add.constraint(portfolio = port_spec, type = "full_investment")
port_spec <- add.constraint(portfolio = port_spec, type = "weight_sum_constraint", min_sum=0.98, max_sum=1.02)
# Add a long only constraint such that the weight of an asset is between 0 and 1
port_spec <- add.constraint(portfolio = port_spec, type = "long_only")
# Add an objective to minimize portfolio standard deviation
port_spec <- add.objective(portfolio = port_spec, type = "risk", name = "StdDev")
# Print the portfolio specification
print(port_spec)
**************************************************
PortfolioAnalytics Portfolio Specification
**************************************************
Call:
portfolio.spec(assets = colnames(asset_returns))
Number of assets: 13
Asset Names
[1] "Convertible Arbitrage" "CTA Global"
[3] "Distressed Securities" "Emerging Markets"
[5] "Equity Market Neutral" "Event Driven"
[7] "Fixed Income Arbitrage" "Global Macro"
[9] "Long/Short Equity" "Merger Arbitrage"
More than 10 assets, only printing the first 10
Constraints
Enabled constraint types
- full_investment
- long_only
Objectives:
Enabled objective names
- StdDev
A reasonable benchmark is important for accurately measuring the relative performance of a portfolio The benchmark should reflect the universe of assets and/or style of the portfolio. Benchmarks should never be purposefully designed to have poor performance, as this would skew your results.
Now we will run the backtest using the portfolio specification created in the last exercise with quarterly rebalancing to evaluate out-of-sample performance. The other backtest parameters we need to set are the training period and rolling window. The training period sets the number of data points to use for the initial optimization. The rolling window sets the number of periods to use in the window. This problem can be solved with a quadratic programming solver so we will use “ROI” for the optimization method.
# Run the optimization
opt_rebal_base <- optimize.portfolio.rebalancing(R = asset_returns,
portfolio = port_spec,
optimize_method = "ROI",
rebalance_on = "quarters",
training_period = 60,
rolling_window = 60)
# Print the results
print(opt_rebal_base)
**************************************************
PortfolioAnalytics Optimization with Rebalancing
**************************************************
Call:
optimize.portfolio.rebalancing(R = asset_returns, portfolio = port_spec,
optimize_method = "ROI", rebalance_on = "quarters", training_period = 60,
rolling_window = 60)
Number of rebalancing dates: 73
First rebalance date:
[1] "2001-12-31"
Last rebalance date:
[1] "2019-11-30"
Annualized Portfolio Rebalancing Return:
[1] 0.03124747
Annualized Portfolio Standard Deviation:
[1] 0.01725335
# Chart the weights
chart.Weights(opt_rebal_base)
# Compute the portfolio returns
returns_base <- Return.portfolio(R = asset_returns, weights = extractWeights(opt_rebal_base))
colnames(returns_base) <- "base"
Here we hypothesize that refining constraints and/or objectives will improve performance. Let us add a risk budget objective to set a minimum and maximum percentage contribution to risk for each asset. We will be building on the portfolio specification we created. This is a more complex optimization problem and will require a global solver so we will use random portfolios as the optimization method.
rp <- readRDS("data/rp_fi_lo_ret.rds")
# Add a risk budge objective
port_spec <- add.objective(portfolio = port_spec,
type = "risk_budget",
name = "StdDev",
min_prisk = 0.05,
max_prisk = 0.1)
# Run the optimization
opt_rebal_rb <- optimize.portfolio.rebalancing(R = asset_returns,
portfolio = port_spec,
optimize_method = "random", rp = rp,
trace = TRUE,
rebalance_on = "quarters",
training_period = 60,
rolling_window = 60)
# Chart the weights
chart.Weights(opt_rebal_rb)
# Chart the percentage contribution to risk
chart.RiskBudget(opt_rebal_rb, match.col = "StdDev", risk.type = "percentage")
# Compute the portfolio returns
returns_rb <- Return.portfolio(R = asset_returns, weights = extractWeights(opt_rebal_rb))
colnames(returns_rb) <- "risk_budget"
Let us hypothesize that using a robust estimate of the variance-covariance matrix will outperform the sample variance covariance matrix. In theory, better estimates should lead to better results. We will use the moments_robust() function that was defined in chapter 3 and the portfolio specification from the last exercise.
# Run the optimization
opt_rebal_rb_robust <- optimize.portfolio.rebalancing(R = asset_returns,
momentFUN = "moments_robust",
portfolio = port_spec,
optimize_method = "random", rp = rp,
trace = TRUE,
rebalance_on = "quarters",
training_period = 60,
rolling_window = 60)
# Chart the weights
chart.Weights(opt_rebal_rb_robust)
# Chart the percentage contribution to risk
chart.RiskBudget(opt_rebal_rb_robust, match.col = "StdDev", risk.type = "percentage")
# Compute the portfolio returns
returns_rb_robust <- Return.portfolio(R = asset_returns, weights = extractWeights(opt_rebal_rb_robust))
colnames(returns_rb_robust) <- "rb_robust"
n the previous exercises of the chapter, we created an equal weight benchmark r_benchmark and ran the following optimizations:
# Combine the returns
ret <- cbind(r_benchmark, returns_base, returns_rb, returns_rb_robust)
# Compute annualized returns
table.AnnualizedReturns(R = ret)
# Chart the performance summary
charts.PerformanceSummary(R = ret)