Skip to Content
Enter
Skip to Menu
Enter
Skip to Footer
Enter
Blog
Quantitative Analysis

The Complete Guide to Portfolio Optimization in R PART2

Category:
Quantitative Analysis

29

min read

Share this post
The Complete Guide to Portfolio Optimization in R PART2

Read Next Identifying Anomalies in Capital Markets: Accrual Anomaly Congratulations you made it to part2 of our tutorial. Give yourself a round of applause. If you stumbled upon part2 before reading part1 we advise you to start from the beginning and read part1 first.

In Part2 we dive into mean variance portfolio optimization, mean CVar portfolios and backtesting. As mentioned in part1 we conclude this tutorial with a full blown portfolio optimization process with a real world example. After going through all of the content you should have acquired profound knowledge of portfolio optimization in R and be able to optimize any kind of portfolio with your eyes closed.

Content

PART1:

  • Working with data
    • Loading times series data sets
    • Reading data from CSV files
    • Downloading data from the internet
    • Modifying time series data
    • Sample time series data randomly
    • Sorting in ascending order
    • Reverse data
    • Alignment of timeSeries objects
    • Merging time series data
    • Binding time series
    • Merging columns and rows
    • Subsetting data and replacing parts of a data set
    • Find first and last records in a time series
    • Subset by column names
    • Extract specific date
    • Aggregating data
  • Computing rolling statistics
  • Manipulate data with financial functions
  • Robust statistical methods
    • Robust covariance estimators
    • Comparing robust covariances
  • Implementing the portfolio framework
    • Specify a portfolio of assets
    • Choosing an objective when optimizing a portfolio
    • Estimating mean and covariance of asset returns
    • Working with tail risk
    • Set and modify portfolio parameters

PART2:

  • Representing data with timeSeries objects
  • Set portfolio constraints
    • Constructing portfolio constraints
    • Set lower and upper bounds
  • Optimizing a portfolio
    • Mean variance portfolio
    • Minimum risk efficient portfolio
    • Global minimum variance portfolio
  • Case study portfolio optimization
    • Mean-CVaR portfolio optimization
    • Unlimited short portfolio
    • Long only portfolio
    • Box constrained portfolio
    • Group constrained portfolio
  • Portfolio backtesting
    • Backtesting sector rotation portfolio
    • Plot backtest
  • What to do next

Representing data with timeSeries objects

With S4 timeSeries objects found in the rmetrics package we can represent data for portfolio optimization. To create an S4 object of class fPFOLIODATA we use the function portfolioData().

1>showClass("fPFOLIODATA")
2Class "fPFOLIODATA" [package "fPortfolio"]
3 
4Slots:
5                                        
6Name:        data statistics   tailRisk
7Class:       list       list       list

The function portfolioData() allows us to define data settings that we can use in portfolio functions. To illustrate this with an example we use the LPP2005.RET data set working with columns “SBI”,”SPI”,”LMI” and “MPI”. We create a portfolio object with the data set and the default portfolio specification.

1>args(portfolioData)
2function (data, spec = portfolioSpec()) 
3NULL
4>lppAssets <- 100 * LPP2005.RET[, c("SBI", "SPI", "LMI", "MPI")]
5>lppData <- portfolioData(data = lppAssets, spec = portfolioSpec())

With the function str() we take a look inside the data structure of a portfolio.

1>str(lppData, width = 65, strict.width = "cut")
2Formal class 'fPFOLIODATA' [package "fPortfolio"] with 3 slots
3  ..@ data      :List of 3
4  .. ..$ series :Time Series:          
5 Name:               object
6Data Matrix:        
7 Dimension:          377 4
8 Column Names:       SBI SPI LMI MPI
9 Row Names:          2005-11-01  ...  2007-04-11
10Positions:          
11 Start:              2005-11-01
12 End:                2007-04-11
13With:               
14 Format:             %Y-%m-%d
15 FinCenter:          GMT
16 Units:              SBI SPI LMI MPI
17 Title:              Time Series Object
18 Documentation:      Tue Jan 20 17:49:06 2009 by user: 
19  .. ..$ nAssets: int 4
20  .. ..$ names  : chr [1:4] "SBI" "SPI" "LMI" "MPI"
21  ..@ statistics:List of 5
22  .. ..$ mean     : Named num [1:4] 4.07e-05 8.42e-02 5.53e-03 ..
23  .. .. ..- attr(*, "names")= chr [1:4] "SBI" "SPI" "LMI" "MPI"
24  .. ..$ Cov      : num [1:4, 1:4] 0.0159 -0.0127 0.0098 -0.015..
25  .. .. ..- attr(*, "dimnames")=List of 2
26  .. .. .. ..$ : chr [1:4] "SBI" "SPI" "LMI" "MPI"
27  .. .. .. ..$ : chr [1:4] "SBI" "SPI" "LMI" "MPI"
28  .. ..$ estimator: chr "covEstimator"
29  .. ..$ mu       : Named num [1:4] 4.07e-05 8.42e-02 5.53e-03 ..
30  .. .. ..- attr(*, "names")= chr [1:4] "SBI" "SPI" "LMI" "MPI"
31  .. ..$ Sigma    : num [1:4, 1:4] 0.0159 -0.0127 0.0098 -0.015..
32  .. .. ..- attr(*, "dimnames")=List of 2
33  .. .. .. ..$ : chr [1:4] "SBI" "SPI" "LMI" "MPI"
34  .. .. .. ..$ : chr [1:4] "SBI" "SPI" "LMI" "MPI"
35  ..@ tailRisk  : list()

With the command print(lppData) we can print a portfolio data object. The output prints the first and last three lines of the data set alongside the sample mean and covariance estimates found in the @statistics slot.

1>print(lppData)
2 
3Head/Tail Series Data:
4 
5GMT 
6                  SBI       SPI        LMI       MPI
72005-11-01 -0.0612745 0.8414595 -0.1108882 0.1548062
82005-11-02 -0.2762009 0.2519342 -0.1175939 0.0342876
92005-11-03 -0.1153092 1.2707292 -0.0992456 1.0502959
10GMT 
11                  SBI        SPI        LMI        MPI
122007-04-09  0.0000000  0.0000000 -0.1032441  0.8179152
132007-04-10 -0.0688995  0.6329425 -0.0031500 -0.1428291
142007-04-11  0.0306279 -0.1044170 -0.0090900 -0.0991064
15 
16Statistics:
17 
18$mean
19         SBI          SPI          LMI          MPI 
200.0000406634 0.0841754390 0.0055315332 0.0590515119 
21 
22$Cov
23             SBI         SPI          LMI         MPI
24SBI  0.015899554 -0.01274142  0.009803865 -0.01588837
25SPI -0.012741418  0.58461212 -0.014074691  0.41159843
26LMI  0.009803865 -0.01407469  0.014951108 -0.02332223
27MPI -0.015888368  0.41159843 -0.023322233  0.53503263
28 
29$estimator
30[1] "covEstimator"
31 
32$mu
33         SBI          SPI          LMI          MPI 
340.0000406634 0.0841754390 0.0055315332 0.0590515119 
35 
36$Sigma
37             SBI         SPI          LMI         MPI
38SBI  0.015899554 -0.01274142  0.009803865 -0.01588837
39SPI -0.012741418  0.58461212 -0.014074691  0.41159843
40LMI  0.009803865 -0.01407469  0.014951108 -0.02332223
41MPI -0.015888368  0.41159843 -0.023322233  0.53503263

The S4 time series object is kept in the @data slot. We can extract the content with the function getData().

1>Data &lt;- portfolioData(lppData)
2>getData(Data)[-1]
3$nAssets
4[1] 4
5 
6$names
7[1] "SBI" "SPI" "LMI" "MPI"

The content of the @statistics slot holds information on the mean and covariance matrix and can be called with the getStatistics() function.

1>getStatistics(Data)
2$mean
3         SBI          SPI          LMI          MPI 
40.0000406634 0.0841754390 0.0055315332 0.0590515119 
5 
6$Cov
7             SBI         SPI          LMI         MPI
8SBI  0.015899554 -0.01274142  0.009803865 -0.01588837
9SPI -0.012741418  0.58461212 -0.014074691  0.41159843
10LMI  0.009803865 -0.01407469  0.014951108 -0.02332223
11MPI -0.015888368  0.41159843 -0.023322233  0.53503263
12 
13$estimator
14[1] "covEstimator"
15 
16$mu
17         SBI          SPI          LMI          MPI 
180.0000406634 0.0841754390 0.0055315332 0.0590515119 
19 
20$Sigma
21             SBI         SPI          LMI         MPI
22SBI  0.015899554 -0.01274142  0.009803865 -0.01588837
23SPI -0.012741418  0.58461212 -0.014074691  0.41159843
24LMI  0.009803865 -0.01407469  0.014951108 -0.02332223
25MPI -0.015888368  0.41159843 -0.023322233  0.53503263

Set portfolio constraints

By using constraints we are defining restrictions on portfolio weights. In the r metrics package the function portfolioConstraints() creates the default settings and reports on all constraints.

1>showClass("fPFOLIOCON")
2Class "fPFOLIOCON" [package "fPortfolio"]
3 
4Slots:
5                                                                                       
6Name:    stringConstraints     minWConstraints     maxWConstraints   eqsumWConstraints
7Class:           character             numeric             numeric              matrix
8                                                                                       
9Name:   minsumWConstraints  maxsumWConstraints     minBConstraints     maxBConstraints
10Class:              matrix              matrix             numeric             numeric
11                                                                                       
12Name:     listFConstraints     minFConstraints     maxFConstraints minBuyinConstraints
13Class:                list             numeric             numeric             numeric
14                                                                                       
15Name:  maxBuyinConstraints    nCardConstraints  minCardConstraints  maxCardConstraints
16Class:             numeric             integer             numeric             numeric

The function portfolioConstraints() consists of the following arguments:

Argument: constraints

Values:

  • “LongOnly” long-only constraints [0,1]
  • “Short” unlimited short selling, [-Inf,Inf]
  • “minW[<…>]=<…> lower box bounds
  • “maxw[<…>]=<…> upper box bounds
  • “minsumW[<…>]=<…> lower group bounds
  • “maxsumW[<…>]=<…> upper group bounds
  • “minB[<…>]=<…> lower covariance risk budget bounds
  • “maxB[<…>]=<…> upper covariance risk budget bounds
  • “listF=list(<…>)” list of non-linear functions
  • “minf[<…>]=<…>” lower non-linear function bounds
  • “maxf[<…>]=<…>” upper covariance risk budget

Constructing portfolio constraints

The example below creates Long-only settings for returns from the LPP2005 data set ,displays the structure of a portfolio constrains object and prints the results.

1>Data <- 100 * LPP2005.RET[, 1:3]
2>Spec <- portfolioSpec()
3>setTargetReturn(Spec) <- mean(Data)
4>Constraints <- "LongOnly"
5>defaultConstraints <- portfolioConstraints(Data, Spec, Constraints)
6>str(defaultConstraints, width = 65, strict.width = "cut")
7Formal class 'fPFOLIOCON' [package "fPortfolio"] with 16 slots
8  ..@ stringConstraints  : chr "LongOnly"
9  ..@ minWConstraints    : Named num [1:3] 0 0 0
10  .. ..- attr(*, "names")= chr [1:3] "SBI" "SPI" "SII"
11  ..@ maxWConstraints    : Named num [1:3] 1 1 1
12  .. ..- attr(*, "names")= chr [1:3] "SBI" "SPI" "SII"
13  ..@ eqsumWConstraints  : num [1:2, 1:4] 3.60e-02 -1.00 4.07e-..
14  .. ..- attr(*, "dimnames")=List of 2
15  .. .. ..$ : chr [1:2] "Return" "Budget"
16  .. .. ..$ : chr [1:4] "ceq" "SBI" "SPI" "SII"
17  ..@ minsumWConstraints : logi [1, 1] NA
18  ..@ maxsumWConstraints : logi [1, 1] NA
19  ..@ minBConstraints    : Named num [1:3] -Inf -Inf -Inf
20  .. ..- attr(*, "names")= chr [1:3] "SBI" "SPI" "SII"
21  ..@ maxBConstraints    : Named num [1:3] 1 1 1
22  .. ..- attr(*, "names")= chr [1:3] "SBI" "SPI" "SII"
23  ..@ listFConstraints   : list()
24  ..@ minFConstraints    : num(0) 
25  ..@ maxFConstraints    : num(0) 
26  ..@ minBuyinConstraints: Named num [1:3] 0 0 0
27  .. ..- attr(*, "names")= chr [1:3] "SBI" "SPI" "SII"
28  ..@ maxBuyinConstraints: Named num [1:3] 1 1 1
29  .. ..- attr(*, "names")= chr [1:3] "SBI" "SPI" "SII"
30  ..@ nCardConstraints   : int 3
31  ..@ minCardConstraints : Named num [1:3] 0 0 0
32  .. ..- attr(*, "names")= chr [1:3] "SBI" "SPI" "SII"
33  ..@ maxCardConstraints : Named num [1:3] 1 1 1
34  .. ..- attr(*, "names")= chr [1:3] "SBI" "SPI" "SII"
35>print(defaultConstraints)
36 
37Title:
38 Portfolio Constraints
39 
40Lower/Upper Bounds:
41      SBI SPI SII
42Lower   0   0   0
43Upper   1   1   1
44 
45Equal Matrix Constraints:
46               ceq          SBI         SPI         SII
47Return  0.03603655  4.06634e-05  0.08417544  0.02389356
48Budget -1.00000000 -1.00000e+00 -1.00000000 -1.00000000
49 
50Cardinality Constraints:
51      SBI SPI SII
52Lower   0   0   0
53Upper   1   1   1

To consider another example we can use short selling constraints instead of a long-only portfolio.

1>shortConstraints <- "Short"
2>portfolioConstraints(Data, Spec, shortConstraints)
3 
4Title:
5 Portfolio Constraints
6 
7Lower/Upper Bounds:
8       SBI  SPI  SII
9Lower -Inf -Inf -Inf
10Upper  Inf  Inf  Inf
11 
12Equal Matrix Constraints:
13               ceq          SBI         SPI         SII
14Return  0.03603655  4.06634e-05  0.08417544  0.02389356
15Budget -1.00000000 -1.00000e+00 -1.00000000 -1.00000000
16 
17Cardinality Constraints:
18      SBI SPI SII
19Lower   0   0   0
20Upper   1   1   1

Using box constraints and setting the lower bounds to negative values we can implement arbitrary short selling. With the two character strings minW and maxW we can limit the portfolio weights by lower and upper bounds.

1>box.1 &lt;- "minW[1:3] = 0.1"
2>box.2 &lt;- "maxW[c(1,3)] = c(0.5, 0.6)"
3>box.3 &lt;- "maxW[2] = 0.4"
4>boxConstraints &lt;- c(box.1, box.2, box.3)
5>boxConstraints
6[1] "minW[1:3] = 0.1"            "maxW[c(1,3)] = c(0.5, 0.6)" "maxW[2] = 0.4"             
7> portfolioConstraints(Data, Spec, boxConstraints)
8 
9Title:
10 Portfolio Constraints
11 
12Lower/Upper Bounds:
13      SBI SPI SII
14Lower 0.1 0.1 0.1 
15Upper 0.5 0.4 0.6
16 
17Equal Matrix Constraints:
18               ceq          SBI         SPI         SII
19Return  0.03603655  4.06634e-05  0.08417544  0.02389356
20Budget -1.00000000 -1.00000e+00 -1.00000000 -1.00000000
21 
22Cardinality Constraints:
23      SBI SPI SII
24Lower   0   0   0
25Upper   1   1   1

The constraints above tell us that we want to invest a minimum of 10% in each asset (see lower bound 0.1, 0.1, 0.1) and not more than 50% in the first asset. A 40% maximum investment for the second asset and 60% maximum investment in the third asset. Another method that we can use is to define the total weight of a group of assets. We can use the following strings to set group constraints:

Constraints Settings:

  • “eqsumW” equality group constraints #total amount invested in a group of assets
  • “minsumW” lower bounds group constraints
  • “maxsumw” upper bounds group constraints

1>group.1 &lt;- "eqsumW[c(1,3)]=0.6"
2>group.2 &lt;- "minsumW[c(2,3)]=0.2"
3>group.3 &lt;- "maxsumW[c(1,2)]=0.7"
4>groupConstraints &lt;- c(group.1, group.2, group.3)
5>groupConstraints
6[1] "eqsumW[c(1,3)]=0.6"  "minsumW[c(2,3)]=0.2" "maxsumW[c(1,2)]=0.7"
7>portfolioConstraints(Data, Spec, groupConstraints)
8 
9Title:
10 Portfolio Constraints
11 
12Lower/Upper Bounds:
13      SBI SPI SII
14Lower   0   0   0
15Upper   1   1   1
16 
17Equal Matrix Constraints:
18               ceq          SBI         SPI         SII
19Return  0.03603655  4.06634e-05  0.08417544  0.02389356
20Budget -1.00000000 -1.00000e+00 -1.00000000 -1.00000000
21eqsumW  0.60000000  1.00000e+00  0.00000000  1.00000000
22 
23Lower Matrix Constraints:
24      avec SBI SPI SII
25lower  0.2   0   1   1
26 
27Upper Matrix Constraints:
28      avec SBI SPI SII
29upper  0.7   1   1   0
30 
31Cardinality Constraints:
32      SBI SPI SII
33Lower   0   0   0
34Upper   1   1   1

The first string means that we invest 60% in asset 1 and 3. For group.2 we invest a minimum of 20% in asset 2 and 3. For group.3 we invest a maximum of 70% in asset 1 and 2.

Set lower and upper bounds

Probably the question running through your mind is, “Sounds interesting but how can I know how to set lower and upper bounds?” This is up to you to decide. However, more often than not the simplest solutions work best. For example, if you are about to set the weights for a sector the best way forward would be to construct spread indices of the sectors you want to invest your money in. Mr. Market already showed us numerous times that when volatility in the market crests, the performance of all stocks tends to be highly correlated. Stocks in different sectors start to wander when volatility goes down and things magically become less correlated. The graph below illustrates this phenomenon. Take a close look.

The blue dotted line shows the average correlation for all pairs of sectors based on the S&P Select Sector Indexes. The shaded area shows the correlation amongst different sectors. As we can see sectors tend to decouple when volatility subsides. In order to measure if a sector is “overbought” or “oversold” we can construct a spread between two sectors. Of course there is not a “best way” to construct these sector spreads so they should only be used as a guideline for defining meaningful portfolio weights. By way of example, if a portfolio manager has a view that the technology sector will underperform the market while the energy sector will outperform the market, one can conceivably buy $1 million of the energy sector exposure while short selling $1 million of the technology sector. By using the following equation we can find more relevant coefficients:

We estimate β by regressing the historical returns of the sector on those of the overall market. We can set the exposure in Sector A to one dollar and µ dollar of exposure is sold in Sector B. The total sensitivity to the market returns is defined as (βA–μβB). We eliminate exposure to the overall market by setting µ:

In this example µ is greater than 1 if B is less sensitive than sector A to the overall market return. Translated into trader language it means that we have to adjust our B sector position upward. This is your guideline. The fine tuning happens during the portfolio optimization process. If you want to elaborate on this further we advise you to read the article on the CME website.

Optimizing a portfolio

With the following portfolio optimization functions we compute or optimize a single portfolio. These functions are part of the fPortfolio package.

Portfolio Functions:

  • feasiblePortfolio: returns a feasible portfolio given the
  • vector of portfolio weights
  • efficientPortfolio: returns the portfolio with the lowest risk for a given target return
  • maxratioPortfolio: returns the portfolio with the highest return/risk ratio
  • tangencyPortfolio :synonym for maxratioPortfolio
  • minriskPortfolio: returns a portfolio with the lowest risk at all
  • minvariancePortfolio: synonym for minriskPortfolio
  • maxreturnPortfolio: returns the portfolio with the highest return for a given target risk
  • portfolioFrontier: computes portfolios on the efficient frontier and/or on the minimum covariance locus.

To display the structure of our portfolio object we type:

1>library(fPortfolio)
2>showClass("fPORTFOLIO")
3Class "fPORTFOLIO" [package "fPortfolio"]
4 
5Slots:
6                                                                                           
7Name:         call        data        spec constraints   portfolio       title description
8Class:        call fPFOLIODATA fPFOLIOSPEC  fPFOLIOCON  fPFOLIOVAL   character   character
9>args(feasiblePortfolio)
10function (data, spec = portfolioSpec(), constraints = "LongOnly") 
11NULL
12>tgPortfolio <- tangencyPortfolio(100 * LPP2005.RET[, 1:6])
13>str(tgPortfolio, width = 65, strict.width = "cut")
14Formal class 'fPORTFOLIO' [package "fPortfolio"] with 7 slots
15  ..@ call       : language maxratioPortfolio(data = data, spec..
16  ..@ data       :Formal class 'fPFOLIODATA' [package "fPortfo"..
17  .. .. ..@ data      :List of 3
18  .. .. .. ..$ series :Time Series:          
19 Name:               object
20Data Matrix:        
21 Dimension:          377 6
22 Column Names:       SBI SPI SII LMI MPI ALT
23 Row Names:          2005-11-01  ...  2007-04-11
24Positions:          
25 Start:              2005-11-01
26 End:                2007-04-11
27With:               
28 Format:             %Y-%m-%d
29 FinCenter:          GMT
30 Units:              SBI SPI SII LMI MPI ALT
31 Title:              Time Series Object
32 Documentation:      Tue Jan 20 17:49:06 2009 by user: 
33  .. .. .. ..$ nAssets: int 6
34  .. .. .. ..$ names  : chr [1:6] "SBI" "SPI" "SII" "LMI" ...
35  .. .. ..@ statistics:List of 5
36  .. .. .. ..$ mean     : Named num [1:6] 4.07e-05 8.42e-02 2.3..
37  .. .. .. .. ..- attr(*, "names")= chr [1:6] "SBI" "SPI" "SII"..
38  .. .. .. ..$ Cov      : num [1:6, 1:6] 0.0159 -0.0127 0.0018 ..
39  .. .. .. .. ..- attr(*, "dimnames")=List of 2
40  .. .. .. .. .. ..$ : chr [1:6] "SBI" "SPI" "SII" "LMI" ...
41  .. .. .. .. .. ..$ : chr [1:6] "SBI" "SPI" "SII" "LMI" ...
42  .. .. .. ..$ estimator: chr "covEstimator"
43  .. .. .. ..$ mu       : Named num [1:6] 4.07e-05 8.42e-02 2.3..
44  .. .. .. .. ..- attr(*, "names")= chr [1:6] "SBI" "SPI" "SII"..
45  .. .. .. ..$ Sigma    : num [1:6, 1:6] 0.0159 -0.0127 0.0018 ..
46  .. .. .. .. ..- attr(*, "dimnames")=List of 2
47  .. .. .. .. .. ..$ : chr [1:6] "SBI" "SPI" "SII" "LMI" ...
48  .. .. .. .. .. ..$ : chr [1:6] "SBI" "SPI" "SII" "LMI" ...
49  .. .. ..@ tailRisk  : list()
50  ..@ spec       :Formal class 'fPFOLIOSPEC' [package "fPortfo"..
51  .. .. ..@ model    :List of 5
52  .. .. .. ..$ type     : chr "MV"
53  .. .. .. ..$ optimize : chr "minRisk"
54  .. .. .. ..$ estimator: chr "covEstimator"
55  .. .. .. ..$ tailRisk : list()
56  .. .. .. ..$ params   :List of 1
57  .. .. .. .. ..$ alpha: num 0.05
58  .. .. ..@ portfolio:List of 6
59  .. .. .. ..$ weights        : num [1:6] 0 0.000476 0.182395 0..
60  .. .. .. .. ..- attr(*, "invest")= num 1
61  .. .. .. ..$ targetReturn   : logi NA
62  .. .. .. ..$ targetRisk     : logi NA
63  .. .. .. ..$ riskFreeRate   : num 0
64  .. .. .. ..$ nFrontierPoints: num 50
65  .. .. .. ..$ status         : num 0
66  .. .. ..@ optim    :List of 5
67  .. .. .. ..$ solver   : chr "solveRquadprog"
68  .. .. .. ..$ objective: chr [1:3] "portfolioObjective" "port"..
69  .. .. .. ..$ options  :List of 1
70  .. .. .. .. ..$ meq: num 2
71  .. .. .. ..$ control  : list()
72  .. .. .. ..$ trace    : logi FALSE
73  .. .. ..@ messages :List of 2
74  .. .. .. ..$ messages: logi FALSE
75  .. .. .. ..$ note    : chr ""
76  .. .. ..@ ampl     :List of 5
77  .. .. .. ..$ ampl    : logi FALSE
78  .. .. .. ..$ project : chr "ampl"
79  .. .. .. ..$ solver  : chr "ipopt"
80  .. .. .. ..$ protocol: logi FALSE
81  .. .. .. ..$ trace   : logi FALSE
82  ..@ constraints:Formal class 'fPFOLIOCON' [package "fPortfol"..
83  .. .. ..@ stringConstraints  : chr "LongOnly"
84  .. .. ..@ minWConstraints    : Named num [1:6] 0 0 0 0 0 0
85  .. .. .. ..- attr(*, "names")= chr [1:6] "SBI" "SPI" "SII" ""..
86  .. .. ..@ maxWConstraints    : Named num [1:6] 1 1 1 1 1 1
87  .. .. .. ..- attr(*, "names")= chr [1:6] "SBI" "SPI" "SII" ""..
88  .. .. ..@ eqsumWConstraints  : num [1, 1:7] -1 -1 -1 -1 -1 -1..
89  .. .. .. ..- attr(*, "dimnames")=List of 2
90  .. .. .. .. ..$ : chr "Budget"
91  .. .. .. .. ..$ : chr [1:7] "ceq" "SBI" "SPI" "SII" ...
92  .. .. .. ..- attr(*, "na.action")= 'omit' Named num 1
93  .. .. .. .. ..- attr(*, "names")= chr "Return"
94  .. .. ..@ minsumWConstraints : logi [1, 1] NA
95  .. .. ..@ maxsumWConstraints : logi [1, 1] NA
96  .. .. ..@ minBConstraints    : Named num [1:6] -Inf -Inf -Inf..
97  .. .. .. ..- attr(*, "names")= chr [1:6] "SBI" "SPI" "SII" ""..
98  .. .. ..@ maxBConstraints    : Named num [1:6] 1 1 1 1 1 1
99  .. .. .. ..- attr(*, "names")= chr [1:6] "SBI" "SPI" "SII" ""..
100  .. .. ..@ listFConstraints   : list()
101  .. .. ..@ minFConstraints    : num(0) 
102  .. .. ..@ maxFConstraints    : num(0) 
103  .. .. ..@ minBuyinConstraints: Named num [1:6] 0 0 0 0 0 0
104  .. .. .. ..- attr(*, "names")= chr [1:6] "SBI" "SPI" "SII" ""..
105  .. .. ..@ maxBuyinConstraints: Named num [1:6] 1 1 1 1 1 1
106  .. .. .. ..- attr(*, "names")= chr [1:6] "SBI" "SPI" "SII" ""..
107  .. .. ..@ nCardConstraints   : int 6
108  .. .. ..@ minCardConstraints : Named num [1:6] 0 0 0 0 0 0
109  .. .. .. ..- attr(*, "names")= chr [1:6] "SBI" "SPI" "SII" ""..
110  .. .. ..@ maxCardConstraints : Named num [1:6] 1 1 1 1 1 1
111  .. .. .. ..- attr(*, "names")= chr [1:6] "SBI" "SPI" "SII" ""..
112  ..@ portfolio  :Formal class 'fPFOLIOVAL' [package "fPortfol"..
113  .. .. ..@ portfolio:List of 6
114  .. .. .. ..$ weights       : Named num [1:6] 0 0.000476 0.182..
115  .. .. .. .. ..- attr(*, "names")= chr [1:6] "SBI" "SPI" "SII"..
116  .. .. .. ..$ covRiskBudgets: Named num [1:6] 0 0.00141 0.1538..
117  .. .. .. .. ..- attr(*, "names")= chr [1:6] "SBI" "SPI" "SII"..
118  .. .. .. ..$ targetReturn  : Named num [1:2] 0.0283 0.0283
119  .. .. .. .. ..- attr(*, "names")= chr [1:2] "mean" "mu"
120  .. .. .. ..$ targetRisk    : Named num [1:4] 0.153 0.153 0.31..
121  .. .. .. .. ..- attr(*, "names")= chr [1:4] "Cov" "Sigma" "C"..
122  .. .. .. ..$ targetAlpha   : num 0.05
123  .. .. .. ..$ status        : num 0
124  .. .. ..@ messages : list()
125  ..@ title      : chr "Tangency Portfolio"
126  ..@ description: chr "Tue Jan 26 16:10:30 2021 by user: 1"
127>print(tgPortfolio)
128 
129Title:
130 MV Tangency Portfolio 
131 Estimator:         covEstimator 
132 Solver:            solveRquadprog 
133 Optimize:          minRisk 
134 Constraints:       LongOnly 
135 
136Portfolio Weights:
137   SBI    SPI    SII    LMI    MPI    ALT 
1380.0000 0.0005 0.1824 0.5753 0.0000 0.2418 
139 
140Covariance Risk Budgets:
141   SBI    SPI    SII    LMI    MPI    ALT 
1420.0000 0.0014 0.1539 0.1124 0.0000 0.7324 
143 
144Target Returns and Risks:
145  mean    Cov   CVaR    VaR 
1460.0283 0.1533 0.3096 0.2142

Mean-variance portfolio

Defining a mean-variance portfolio includes three steps:

  • Step1 – create S4 timeSeries objects with the rmetrics timeSeries package as explained in part1 of our tutorial.
  • Step2 – portfolio specification
  • Step3 – setting portfolio constraints

For our first example, we start with a minimum risk mean-variance portfolio. The portfolio has a fixed target return and includes feasible, tangency, efficient and global minimum risk portfolios.

To compute a feasible portfolio we can use equal weights. To specify equal weights we use the LPP2005 data set. We type the following:

1>library(fPortfolio)
2>colnames(LPP2005REC)
3[1] "SBI"   "SPI"   "SII"   "LMI"   "MPI"   "ALT"   "LPP25" "LPP40" "LPP60"
4>lppData <- 100 * LPP2005REC[, 1:6]
5 
6#the function setWeights() adds the vector of weights to the #specification spec
7>ewSpec <- portfolioSpec()
8>nAssets <- ncol(lppData)
9>setWeights(ewSpec) <- rep(1/nAssets, times = nAssets)
10 
11#function feasiblePortfolio() calculates the properties of the portfolio
12>ewPortfolio <- feasiblePortfolio(
13+     data = lppData,
14+     spec = ewSpec,
15+     constraints = "LongOnly")
16>print(ewPortfolio)
17 
18Title:
19 MV Feasible Portfolio 
20 Estimator:         covEstimator 
21 Solver:            solveRquadprog 
22 Optimize:          minRisk 
23 Constraints:       LongOnly 
24 
25Portfolio Weights:
26   SBI    SPI    SII    LMI    MPI    ALT 
270.1667 0.1667 0.1667 0.1667 0.1667 0.1667 
28 
29Covariance Risk Budgets:
30    SBI     SPI     SII     LMI     MPI     ALT 
31-0.0039  0.3526  0.0431 -0.0079  0.3523  0.2638 
32 
33Target Returns and Risks:
34  mean    Cov   CVaR    VaR 
350.0431 0.3198 0.7771 0.4472

Next, we display the results form the equal weights portfolio including the assignment of weights and the attribution of returns and risk.

Minimum risk efficient portfolio

We calculate the efficient mean-variance portfolio with the lowest risk for a given return. With the function portfolioSpec() we define a target return and optimize our portfolio.

1>library("fPortfolio")
2>minriskSpec <- portfolioSpec()
3>targetReturn <- getTargetReturn(ewPortfolio@portfolio)["mean"]
4>setTargetReturn(minriskSpec) <- targetReturn
5>minriskPortfolio <- efficientPortfolio(
6+     data = lppData,
7+     spec = minriskSpec,
8+     constraints = "LongOnly")
9> print(minriskPortfolio)
10 
11Title:
12 MV Efficient Portfolio 
13 Estimator:         covEstimator 
14 Solver:            solveRquadprog 
15 Optimize:          minRisk 
16 Constraints:       LongOnly 
17 
18Portfolio Weights:
19   SBI    SPI    SII    LMI    MPI    ALT 
200.0000 0.0086 0.2543 0.3358 0.0000 0.4013 
21 
22Covariance Risk Budgets:
23    SBI     SPI     SII     LMI     MPI     ALT 
24 0.0000  0.0184  0.1205 -0.0100  0.0000  0.8711 
25 
26Target Returns and Risks:
27  mean    Cov   CVaR    VaR 
280.0431 0.2451 0.5303 0.3412

Weights and plots are generated as follows.

1>col <- qualiPalette(ncol(lppData), "Dark2")
2>col <- qualiPalette(ncol(lppData), "Dark2")
3>mtext(text = "Minimal Risk MV Portfolio", side = 3, line = 1.5,
4+       font = 2, cex = 0.7, adj = 0)
5Error in mtext(text = "Minimal Risk MV Portfolio", side = 3, line = 1.5,  : 
6  plot.new has not been called yet
7>weightedReturnsPie(minriskPortfolio, radius = 0.7, col = col)
8>mtext(text = "Minimal Risk MV Portfolio", side = 3, line = 1.5,
9+       font = 2, cex = 0.7, adj = 0)
10>covRiskBudgetsPie(minriskPortfolio, radius = 0.7, col = col)
11>mtext(text = "Minimal Risk MV Portfolio", side = 3, line = 1.5,
12+       font = 2, cex = 0.7, adj = 0)

Global minimum variance portfolio

This portfolio finds the best asset allocation with the lowest possible return variance (minimum risk).

1>globminSpec <- portfolioSpec()
2>globminPortfolio <- minvariancePortfolio(
3+     data = lppData,
4+     spec = globminSpec,
5+     constraints = "LongOnly")
6>print(globminPortfolio)
7 
8Title:
9 MV Minimum Variance Portfolio 
10 Estimator:         covEstimator 
11 Solver:            solveRquadprog 
12 Optimize:          minRisk 
13 Constraints:       LongOnly 
14 
15Portfolio Weights:
16   SBI    SPI    SII    LMI    MPI    ALT 
170.3555 0.0000 0.0890 0.4893 0.0026 0.0636 
18 
19Covariance Risk Budgets:
20   SBI    SPI    SII    LMI    MPI    ALT 
210.3555 0.0000 0.0890 0.4893 0.0026 0.0636 
22 
23Target Returns and Risks:
24  mean    Cov   CVaR    VaR 
250.0105 0.0986 0.2020 0.1558

In our final step we generate the plots for the global minimum mean-variance portfolio.

1>col <- seqPalette(ncol(lppData), "YlGn")
2>weightsPie(globminPortfolio, box = FALSE, col = col)
3>mtext(text = "Global Minimum Variance MV Portfolio", side = 3,
4line = 1.5, font = 2, cex = 0.7, adj = 0)
5>weightedReturnsPie(globminPortfolio, box = FALSE, col = col)
6>mtext(text = "Global Minimum Variance MV Portfolio", side = 3,
7line = 1.5, font = 2, cex = 0.7, adj = 0)
8>covRiskBudgetsPie(globminPortfolio, box = FALSE, col = col)
9>mtext(text = "Global Minimum Variance MV Portfolio", side = 3,
10line = 1.5, font = 2, cex = 0.7, adj = 0)

Case study portfolio optimization

For our real world example we are going to optimize a portfolio of 30 stocks as given in the Dow Jones Index. First we need to load our data set which can be found in the fBasics package.

1>djiData <- as.timeSeries(DowJones30)
2>djiData.ret <- 100 * returns(djiData)
3>colnames(djiData)
4 [1] "AA"   "AXP"  "T"    "BA"   "CAT"  "C"    "KO"   "DD"   "EK"   "XOM"  "GE"   "GM"   "HWP"
5[14] "HD"   "HON"  "INTC" "IBM"  "IP"   "JPM"  "JNJ"  "MCD"  "MRK"  "MSFT" "MMM"  "MO"   "PG" 
6[27] "SBC"  "UTX"  "WMT"  "DIS"
7>c(start(djiData), end(djiData))
8GMT
9[1] [1990-12-31] [2001-01-02]

We explore the returns series, investigate pairwise dependencies and the distributional properties from a star plot. With hierarchical clustering and PCA analysis we can find out on whether the stocks are similar or not. To investigate all of these properties we can plot the data and visualize the dependencies with the following code.

1>library(fPortfolio)
2>for (i in 1:3) plot(djiData.ret[, (10 * i - 9):(10 * i)])
3>for (i in 1:3) plot(djiData[, (10 * i - 9):(10 * i)])
4>assetsCorImagePlot(djiData.ret)
5>plot(assetsSelect(djiData.ret))
6>assetsCorEigenPlot(djiData.ret)

Output:

Applying the mean-variance portfolio approach we can find the optimal weights for a long only MV portfolio.

To find the optimal weights for a group-constrained MV portfolio equity clustering is performed. The data is grouped into 5 clusters. A maximum of 50% is invested in each cluster. Clustering is one of the most common exploratory data analysis techniques used to get an intuition about the structure of the data. For our example we use the k-means algorithm. The algorithms modus operandi is to try and make the intra-cluster data points as similar as possible while also keeping the clusters as different (far) as possible. It assigns data points to a cluster such that the sum of the squared distance between the data points and the cluster’s arithmetic mean of all the data points that belong to that cluster is at the minimum. Less variation within clusters means a higher degree of similarity between the data points within the same cluster.

1>selection <- assetsSelect(djiData.ret, method = "kmeans")
2>cluster <- selection$cluster
3>cluster[cluster == 1]
4AXP   C  HD JPM WMT 
5  1   1   1   1   1 
6>cluster[cluster == 2]
7INTC MSFT 
8   2    2 
9>cluster[cluster == 3]
10HWP IBM 
11  3   3 
12>cluster[cluster == 4]
13 AA  BA CAT  DD  GM HON  IP MMM UTX 
14  4   4   4   4   4   4   4   4   4 
15>cluster[cluster == 5]
16  T  KO  EK XOM  GE JNJ MCD MRK  MO  PG SBC DIS 
17  5   5   5   5   5   5   5   5   5   5   5   5 
18>constraints <- c(
19+     'maxsumW[c("BA","DD","EK","XOM","GM","HON","MMM","UTX")] = 0.30',
20+     'maxsumW[c(T","KO","GE","HD","JNJ","MCD","MRK","MO","PG","SBC","WMT","DIS")] = 0.30',
21+     'maxsumW[c(AXP","C","JPM")] = 0.30',
22+     'maxsumW[c(AA","CAT","IP")] = 0.30',
23+     'maxsumW[c(HWP","INTC","IBM","MSFT")] = 0.30')
24>djiSpec <- portfolioSpec()
25>setNFrontierPoints(djiSpec) <- 25
26>setEstimator(djiSpec) <- "shrinkEstimator"
27>djiFrontier <- portfolioFrontier(djiData.ret, djiSpec)
28>col = seqPalette(30, "YlOrRd")
29>weightsPlot(djiFrontier, col = col)

Output:

The graph shows the mean-variance frontier for the Dow Jones Index. By using the shrinkage estimator and by computing the weights along the frontier we estimate the covariance matrix.

Mean-CVar portfolio optimization

Portfolio optimization is one of the most important problems from the past that has attracted the attention of investors. In this section we explore mean-CVar portfolio optimization as an alternative approach. Another name for Conditional Value at Risk (CVaR) is Expected Shortfall (ES). Compared to Value at Risk, ES is more sensitive to the tail behavior of the P&L distribution function.

First we select two assets with the smallest and largest returns and divide their range into equidistant parts which determine the target returns for which we try to find efficient portfolios. We compare unlimited short, long-only, box and group constrained efficient frontiers of mean-CVar portfolios.

Unlimited short portfolio

When weights are not restricted we have unlimited short selling. This kind of portfolio cannot be optimized analytically therefore we need to define box constraints with larger lower and upper bounds to circumvent this limitation.

1>setNFrontierPoints(shortSpec) <- 5
2>setSolver(shortSpec) <- "solveRglpk.CVAR"
3>shortFrontier <- portfolioFrontier(data = lppData, spec = shortSpec,
4+                                    constraints = shortConstraints)
5>print(shortFrontier)
6 
7Title:
8 CVaR Portfolio Frontier 
9 Estimator:         covEstimator 
10 Solver:            solveRglpk.CVAR 
11 Optimize:          minRisk 
12 Constraints:       minW maxW 
13 Portfolio Points:  5 of 5 
14 VaR Alpha:         0.05 
15 
16Portfolio Weights:
17      SBI     SPI     SII     LMI     MPI     ALT
181  0.4257 -0.0242  0.0228  0.5661  0.0913 -0.0816
192 -0.0201 -0.0101  0.1746  0.7134 -0.0752  0.2174
203 -0.3275 -0.0196  0.4318  0.6437 -0.2771  0.5486
214 -0.8113  0.0492  0.5704  0.8687 -0.5273  0.8503
225 -1.6975  0.0753  0.6305  1.5485 -0.6683  1.1115
23 
24Covariance Risk Budgets:
25      SBI     SPI     SII     LMI     MPI     ALT
261  0.4056  0.0256  0.0062  0.5384 -0.0730  0.0972
272 -0.0080 -0.0173  0.2124  0.4559 -0.1256  0.4825
283  0.0054 -0.0204  0.3674  0.0592 -0.2787  0.8671
294  0.0572  0.0409  0.2901  0.0223 -0.2984  0.8880
305  0.1513  0.0510  0.1966  0.0215 -0.3235  0.9031
31 
32Target Returns and Risks:
33    mean    Cov   CVaR    VaR
341 0.0000 0.1136 0.2329 0.1859
352 0.0215 0.1172 0.2118 0.1733
363 0.0429 0.2109 0.3610 0.2923
374 0.0643 0.3121 0.5570 0.4175
385 0.0858 0.4201 0.7620 0.5745
39 
40>setNFrontierPoints(shortSpec) <- 25
41>shortFrontier <- portfolioFrontier(data = lppData, spec = shortSpec,
42+                                    constraints = shortConstraints)
43>tailoredFrontierPlot(object = shortFrontier, mText = "Mean-CVaR Portfolio - Short Constraints",
44+                      risk = "CVaR")
45>par(mfrow = c(3, 1), mar = c(3.5, 4, 4, 3) + 0.1)
46>weightsPlot(shortFrontier)
47>text <- "Min-CVaR Portfolio - Short Constrained Portfolio"
48>mtext(text, side = 3, line = 3, font = 2, cex = 0.9)
49>weightedReturnsPlot(shortFrontier)
50>covRiskBudgetsPlot(shortFrontier)

Output:

As shown in the graph above risk is lowered through short selling while keeping the same return.

The graph above shows the weighted returns and covariance risk budgets, along the minimum variance locus and the efficient frontier.

Long only portfolio

For the long only portfolio the weights are bounded between 0 and 1. To compute the efficient frontier of linearly constrained mean-cvar portfolios we can use following functions:

  • portfolioFrontier: efficient portfolios on the frontier
  • frontierPoints: extracts risk/return frontier points
  • frontierPlot: creates an efficient frontier plot
  • cmlPoints: adds market portfolio
  • cmlLines: adds capital market line
  • tangencyPoints: adds tangency portfolio point
  • tangencyLines: adds tangency line
  • equalWeightsPoints: adds point of equal weights portfolio
  • singleAssetPoints: adds points of single asset portfolios
  • twoAssetsLines: adds frontiers of two assets portfolios
  • sharpeRatioLines: adds Sharpe ratio line
  • monteCarloPoints: adds randomly feasible portfolios
  • weightsPlot: weights bar plot along the frontier
  • weightedReturnsPlot: weighted returns bar plot
  • covRiskBudgetsPlot: covariance risk budget bar plot

1>lppData <- 100 * LPP2005.RET[, 1:6]
2>longSpec <- portfolioSpec()
3>setType(longSpec) <- "CVaR"
4Solver set to solveRquadprog
5setSolver: solveRglpk
6>setAlpha(longSpec) <- 0.05
7>setNFrontierPoints(longSpec) <- 5
8>setSolver(longSpec) <- "solveRglpk.CVAR"
9>longFrontier <- portfolioFrontier(data = lppData, spec = longSpec,
10+                                   constraints = "LongOnly")
11> print(longFrontier)
12 
13Title:
14 CVaR Portfolio Frontier 
15 Estimator:         covEstimator 
16 Solver:            solveRglpk.CVAR 
17 Optimize:          minRisk 
18 Constraints:       LongOnly 
19 Portfolio Points:  5 of 5 
20 VaR Alpha:         0.05 
21 
22Portfolio Weights:
23     SBI    SPI    SII    LMI    MPI    ALT
241 1.0000 0.0000 0.0000 0.0000 0.0000 0.0000
252 0.0000 0.0000 0.1988 0.6480 0.0000 0.1532
263 0.0000 0.0000 0.3835 0.2385 0.0000 0.3780
274 0.0000 0.0000 0.3464 0.0000 0.0000 0.6536
285 0.0000 0.0000 0.0000 0.0000 0.0000 1.0000
29 
30Covariance Risk Budgets:
31      SBI     SPI     SII     LMI     MPI     ALT
321  1.0000  0.0000  0.0000  0.0000  0.0000  0.0000
332  0.0000  0.0000  0.2641  0.3126  0.0000  0.4233
343  0.0000  0.0000  0.2432 -0.0101  0.0000  0.7670
354  0.0000  0.0000  0.0884  0.0000  0.0000  0.9116
365  0.0000  0.0000  0.0000  0.0000  0.0000  1.0000
37 
38Target Returns and Risks:
39    mean    Cov   CVaR    VaR
401 0.0000 0.1261 0.2758 0.2177
412 0.0215 0.1224 0.2313 0.1747
423 0.0429 0.2472 0.5076 0.3337
434 0.0643 0.3941 0.8780 0.5830
445 0.0858 0.5684 1.3343 0.8978

The printout lists portfolio weights, covariance risk budgets and target returns and risks along the efficient frontier. Target returns and risks are sorted starting with the portfolio with the lowest return and ending with the portfolio showing the highest return. By repeating the optimization with 25 points at the frontier we can plot the efficient frontier and the results.

Box constrained portfolio

As the name suggests box-constrained portfolios specify upper and lower bounds on the asset weights.

1>boxSpec <- portfolioSpec()
2>setType(boxSpec) <- "CVaR"
3Solver set to solveRquadprog
4setSolver: solveRglpk
5>setAlpha(boxSpec) <- 0.05
6>setNFrontierPoints(boxSpec) <- 15
7>setSolver(boxSpec) <- "solveRglpk.CVAR"
8>boxConstraints <- c("minW[1:6]=0.05", "maxW[1:6]=0.66")
9>boxFrontier <- portfolioFrontier(data = lppData, spec = boxSpec, constraints = boxConstraints)
10>print(boxFrontier)
11 
12Title:
13 CVaR Portfolio Frontier 
14 Estimator:         covEstimator 
15 Solver:            solveRglpk.CVAR 
16 Optimize:          minRisk 
17 Constraints:       minW maxW 
18 Portfolio Points:  5 of 9 
19 VaR Alpha:         0.05 
20 
21Portfolio Weights:
22     SBI    SPI    SII    LMI    MPI    ALT
231 0.0526 0.0500 0.1370 0.6600 0.0500 0.0504
243 0.0500 0.0500 0.2633 0.4127 0.0500 0.1739
255 0.0500 0.0500 0.4109 0.1463 0.0500 0.2928
267 0.0500 0.0500 0.3378 0.0500 0.0500 0.4622
279 0.0500 0.0501 0.1399 0.0500 0.0500 0.6600
28 
29Covariance Risk Budgets:
30      SBI     SPI     SII     LMI     MPI     ALT
311  0.0189  0.1945  0.1435  0.3378  0.1742  0.1312
323  0.0023  0.1528  0.2209  0.0248  0.1582  0.4410
335 -0.0017  0.1042  0.2478 -0.0080  0.1136  0.5440
347 -0.0024  0.0823  0.1099 -0.0035  0.0931  0.7206
359 -0.0022  0.0650  0.0182 -0.0031  0.0752  0.8469
36 
37Target Returns and Risks:
38    mean    Cov   CVaR    VaR
391 0.0184 0.1232 0.2604 0.1913
403 0.0307 0.1838 0.3999 0.2651
415 0.0429 0.2655 0.5787 0.3654
427 0.0552 0.3456 0.7832 0.4818
439 0.0674 0.4388 1.0382 0.6675
44 
45Description:
46 Thu Jan 28 11:54:41 2021 by user: 1 
47>setNFrontierPoints(boxSpec) <- 25
48>boxFrontier <- portfolioFrontier(data = lppData, spec = boxSpec,
49+                                  constraints = boxConstraints)
50>tailoredFrontierPlot(object = boxFrontier, mText = "Mean-CVaR Portfolio - Box Constraints",
51+                      risk = "CVaR")
52>weightsPlot(boxFrontier)
53>text <- "Min-CVaR Portfolio - Box Constrained Portfolio"
54>mtext(text, side = 3, line = 3, font = 2, cex = 0.9)
55>weightedReturnsPlot(boxFrontier)
56>covRiskBudgetsPlot(boxFrontier)

Output:

Group constrained portfolio

In a group constrained portfolio the weights of groups are constrained by lower and upper bounds.

1> groupSpec <- portfolioSpec()
2> setType(groupSpec) <- "CVaR"
3Solver set to solveRquadprog
4setSolver: solveRglpk
5> setAlpha(groupSpec) <- 0.05
6> setNFrontierPoints(groupSpec) <- 10
7> setSolver(groupSpec) <- "solveRglpk.CVAR"
8> groupConstraints <- c("minsumW[c(1,4)]=0.3", "maxsumW[c(2:3,5:6)]=0.66")
9> groupFrontier <- portfolioFrontier(data = lppData, spec = groupSpec,
10+                                    constraints = groupConstraints)
11> print(groupFrontier)
12 
13Title:
14 CVaR Portfolio Frontier 
15 Estimator:         covEstimator 
16 Solver:            solveRglpk.CVAR 
17 Optimize:          minRisk 
18 Constraints:       minsumW maxsumW 
19 Portfolio Points:  5 of 7 
20 VaR Alpha:         0.05 
21 
22Portfolio Weights:
23     SBI    SPI    SII    LMI    MPI    ALT
241 1.0000 0.0000 0.0000 0.0000 0.0000 0.0000
252 0.2440 0.0000 0.0410 0.6574 0.0000 0.0576
264 0.0000 0.0000 0.2744 0.5007 0.0000 0.2249
275 0.0000 0.0000 0.3288 0.3400 0.0000 0.3312
287 0.0000 0.0000 0.0209 0.3400 0.0000 0.6391
29 
30Covariance Risk Budgets:
31      SBI     SPI     SII     LMI     MPI     ALT
321  1.0000  0.0000  0.0000  0.0000  0.0000  0.0000
332  0.2294  0.0000  0.0218  0.7226  0.0000  0.0263
344  0.0000  0.0000  0.3024  0.0780  0.0000  0.6196
355  0.0000  0.0000  0.2388 -0.0024  0.0000  0.7636
367  0.0000  0.0000  0.0020 -0.0159  0.0000  1.0139
37 
38Target Returns and Risks:
39    mean    Cov   CVaR    VaR
401 0.0000 0.1261 0.2758 0.2177
412 0.0096 0.1012 0.2003 0.1622
424 0.0286 0.1575 0.3076 0.2178
435 0.0381 0.2147 0.4392 0.2783
447 0.0572 0.3559 0.8228 0.5517
45  
46> setNFrontierPoints(groupSpec) <- 25
47> groupFrontier <- portfolioFrontier(data = lppData, spec = groupSpec,
48+                                    constraints = groupConstraints)
49> tailoredFrontierPlot(object = groupFrontier, mText = "Mean-CVaR Portfolio - Group Constraints",
50+                      risk = "CVaR")
51> weightsPlot(groupFrontier)
52> text <- "Min-CVaR Portfolio - Group Constrained Portfolio"
53> mtext(text, side = 3, line = 3, font = 2, cex = 0.9)
54> weightedReturnsPlot(groupFrontier)
55> covRiskBudgetsPlot(groupFrontier)

Output:

Portfolio backtesting

For backtesting a portfolio it is common practice to test your strategy performance over a long time frame that encompasses different types of market conditions. We use the S4 object class fPFOLIOBACKTEST to run our backtest. The object of class fPFOLIOBACKTEST has four slots:

@windows

a list, setting the windows function that defines the rolling windows, and the set of window specific parameters params. E.g The window horizon is set as a parameter horizon = "24m"

@strategy

a list, setting the portfolio strategy to implement during the backtest, and any strategy specific parameters are found in params.

@smoother

a list, specifying the smoothing style, given as a smoother function, and any smoother specific parameters are stored in the list params.

@messages

a list, any messages collected during the backtest

We can set the backtest settings with the function portfolioBacktest().

1>library(fPortfolio)
2>showClass("fPFOLIOBACKTEST")
3Class "fPFOLIOBACKTEST" [package "fPortfolio"]
4 
5Slots:
6                                           
7Name:   windows strategy smoother messages
8Class:     list     list     list     list
9>formals(portfolioBacktest)
10$windows
11list(windows = "equidistWindows", params = list(horizon = "12m"))
12 
13$strategy
14list(strategy = "tangencyStrategy", params = list())
15 
16$smoother
17list(smoother = "emaSmoother", params = list(doubleSmoothing = TRUE, 
18    lambda = "3m", skip = 0, initialWeights = NULL))
19 
20$messages
21list()

The above code shows you the default settings for the portfolioBacktest() function consisting of a tangency portfolio strategy, equidistant windows with a horizon of 12 months and a double exponential moving average smoother. To create a new backtest function and print the structure for the default settings we can do the following:

1>backtest &lt;- portfolioBacktest()
2>str(backtest, width = 65, strict.width = "cut")
3Formal class 'fPFOLIOBACKTEST' [package "fPortfolio"] with 4 sl..
4  ..@ windows :List of 2
5  .. ..$ windows: chr "equidistWindows"
6  .. ..$ params :List of 1
7  .. .. ..$ horizon: chr "12m"
8  ..@ strategy:List of 2
9  .. ..$ strategy: chr "tangencyStrategy"
10  .. ..$ params  : list()
11  ..@ smoother:List of 2
12  .. ..$ smoother: chr "emaSmoother"
13  .. ..$ params  :List of 4
14  .. .. ..$ doubleSmoothing: logi TRUE
15  .. .. ..$ lambda         : chr "3m"
16  .. .. ..$ skip           : num 0
17  .. .. ..$ initialWeights : NULL
18  ..@ messages: list()

@windows slot:

The slot consists of two named entries. The rolling windows function defines the backtest windows and the second slot named params holds the parameters, for example the horizon of windows.

@windows SLOT OF AN fPFOLIOBACKTEST OBJECT extractor functions:

getWindows: gets windows slot
getWindowsFun: gets windows function
getWindowsParams: gets windows specific parameters
getWindowsHorizon: gets windows horizon

With the constructor functions we can modify the settings for the portfolio backtest specifications.

Constructor Functions
setWindowsFun: sets the name of the windows function
setWindowsParams: sets parameters for the windows function
setWindowsHorizon: sets the windows horizon measured in months

The default rolling windows can be inspected with following code.

1>defaultBacktest <- portfolioBacktest()
2>getWindowsFun(defaultBacktest)
3[1] "equidistWindows"
4>getWindowsParams(defaultBacktest)
5$horizon
6[1] "12m"
7 
8> getWindowsHorizon(defaultBacktest)
9[1] "12m"
10> args(equidistWindows)
11function (data, backtest = portfolioBacktest()) 
12NULL
13> equidistWindows
14function (data, backtest = portfolioBacktest()) 
15{
16    horizon = getWindowsHorizon(backtest)
17    ans = rollingWindows(x = data, period = horizon, by = "1m")
18    ans
19}
20<bytecode: 0x000001ff58ed37b8>
21<environment: namespace:fPortfolio>

The function getWindowsHorizton() extracts the horizon which is the length of the windows. The function rollingWindows() creates the windows returning a list with two entries named from and to. Both of the list entries give the start and end dates of the whole series.

1>swxData <- 100 * SWX.RET
2>swxBacktest <- portfolioBacktest()
3>setWindowsHorizon(swxBacktest) <- "24m"
4>equidistWindows(data = swxData, backtest = swxBacktest)
5$from
6GMT
7 [1] [2000-01-01] [2000-02-01] [2000-03-01] [2000-04-01] [2000-05-01] [2000-06-01] [2000-07-01]
8 [8] [2000-08-01] [2000-09-01] [2000-10-01] [2000-11-01] [2000-12-01] [2001-01-01] [2001-02-01]
9[15] [2001-03-01] [2001-04-01] [2001-05-01] [2001-06-01] [2001-07-01] [2001-08-01] [2001-09-01]
10[22] [2001-10-01] [2001-11-01] [2001-12-01] [2002-01-01] [2002-02-01] [2002-03-01] [2002-04-01]
11[29] [2002-05-01] [2002-06-01] [2002-07-01] [2002-08-01] [2002-09-01] [2002-10-01] [2002-11-01]
12[36] [2002-12-01] [2003-01-01] [2003-02-01] [2003-03-01] [2003-04-01] [2003-05-01] [2003-06-01]
13[43] [2003-07-01] [2003-08-01] [2003-09-01] [2003-10-01] [2003-11-01] [2003-12-01] [2004-01-01]
14[50] [2004-02-01] [2004-03-01] [2004-04-01] [2004-05-01] [2004-06-01] [2004-07-01] [2004-08-01]
15[57] [2004-09-01] [2004-10-01] [2004-11-01] [2004-12-01] [2005-01-01] [2005-02-01] [2005-03-01]
16[64] [2005-04-01] [2005-05-01] [2005-06-01]
17 
18$to
19GMT
20 [1] [2001-12-31] [2002-01-31] [2002-02-28] [2002-03-31] [2002-04-30] [2002-05-31] [2002-06-30]
21 [8] [2002-07-31] [2002-08-31] [2002-09-30] [2002-10-31] [2002-11-30] [2002-12-31] [2003-01-31]
22[15] [2003-02-28] [2003-03-31] [2003-04-30] [2003-05-31] [2003-06-30] [2003-07-31] [2003-08-31]
23[22] [2003-09-30] [2003-10-31] [2003-11-30] [2003-12-31] [2004-01-31] [2004-02-29] [2004-03-31]
24[29] [2004-04-30] [2004-05-31] [2004-06-30] [2004-07-31] [2004-08-31] [2004-09-30] [2004-10-31]
25[36] [2004-11-30] [2004-12-31] [2005-01-31] [2005-02-28] [2005-03-31] [2005-04-30] [2005-05-31]
26[43] [2005-06-30] [2005-07-31] [2005-08-31] [2005-09-30] [2005-10-31] [2005-11-30] [2005-12-31]
27[50] [2006-01-31] [2006-02-28] [2006-03-31] [2006-04-30] [2006-05-31] [2006-06-30] [2006-07-31]
28[57] [2006-08-31] [2006-09-30] [2006-10-31] [2006-11-30] [2006-12-31] [2007-01-31] [2007-02-28]
29[64] [2007-03-31] [2007-04-30] [2007-05-31]
30 
31attr(,"control")
32attr(,"control")$start
33GMT
34[1] [2000-01-04]
35 
36attr(,"control")$end
37GMT
38[1] [2007-05-08]
39 
40attr(,"control")$period
41[1] "24m"
42 
43attr(,"control")$by
44[1] "1m"

To modify rolling window parameters we do the following.

1>setWindowsHorizon(backtest) <- "24m"
2>getWindowsParams(backtest)
3$horizon
4[1] "24m"

@strategy slot:

The @strategy slot consists of two named entries. The strategy entry holds the name of the strategy function that defines the portfolio strategy we want to backtest and the params entry holds the strategy parameters.

@strategy SLOT of an fPFOLIOBACKTEST object extractor functions:

getStrategy: gets strategy slot
getStrategyFun: gets the name of the strategy function
getStrategyParams: gets strategy specific parameters

Constructor functions:

setStrategyFun: sets the name of the strategy function
setStrategyParams: sets strategy specific parameters

We can inspect default portfolio strategy settingsby suing tangencyStrategy.

1>args(tangencyStrategy)
2function (data, spec = portfolioSpec(), constraints = "LongOnly", 
3    backtest = portfolioBacktest()) 
4NULL
5 
6#The following example invests in a strategy with the highest #Sharpe ratio, if such a portfolio is not found the minimum- #variance portfolio is taken instead.
7>tangencyStrategy
8function (data, spec = portfolioSpec(), constraints = "LongOnly", 
9    backtest = portfolioBacktest()) 
10{
11    strategyPortfolio &lt;- try(tangencyPortfolio(data, spec, constraints))
12    if (class(strategyPortfolio) == "try-error") {
13        strategyPortfolio &lt;- minvariancePortfolio(data, spec, 
14            constraints)
15    }
16    strategyPortfolio
17}
18&lt;bytecode: 0x000001ff54cdb1c0>
19&lt;environment: namespace:fPortfolio>

@smoother slot:

This is a list with two named entries. The entry named smoother holds the name of the smoother function. The function defines the backtest function to smooth the weights over time. The params entry holds the required parameters for the smoother function.

We can inspect the default smoother function with following code.

1>args(emaSmoother)
2function (weights, spec, backtest) 
3NULL
4> emaSmoother
5function (weights, spec, backtest) 
6{
7    ema <- function(x, lambda) {
8        x = as.vector(x)
9        lambda = 2/(lambda + 1)
10        xlam = x * lambda
11        xlam[1] = x[1]
12        ema = filter(xlam, filter = (1 - lambda), method = "rec")
13        ema[is.na(ema)] <- 0
14        as.numeric(ema)
15    }
16    lambda <- getSmootherLambda(backtest)
17    lambdaLength <- as.numeric(substr(lambda, 1, nchar(lambda) - 
18        1))
19    lambdaUnit <- substr(lambda, nchar(lambda), nchar(lambda))
20    stopifnot(lambdaUnit == "m")
21    lambda <- lambdaLength
22    nAssets <- ncol(weights)
23    initialWeights = getSmootherInitialWeights(backtest)
24    if (!is.null(initialWeights)) 
25        weights[1, ] = initialWeights
26    smoothWeights1 = NULL
27    for (i in 1:nAssets) {
28        EMA <- ema(weights[, i], lambda = lambda)
29        smoothWeights1 <- cbind(smoothWeights1, EMA)
30    }
31    doubleSmooth <- getSmootherDoubleSmoothing(backtest)
32    if (doubleSmooth) {
33        smoothWeights = NULL
34        for (i in 1:nAssets) {
35            EMA <- ema(smoothWeights1[, i], lambda = lambda)
36            smoothWeights = cbind(smoothWeights, EMA)
37        }
38    }
39    else {
40        smoothWeights <- smoothWeights1
41    }
42    rownames(smoothWeights) <- rownames(weights)
43    colnames(smoothWeights) <- colnames(weights)
44    smoothWeights
45}
46<bytecode: 0x000001ff549db078>
47<environment: namespace:fPortfolio>

With the setSmoother functions we can modify the control parameters.

1#Change single smoothing type
2>setSmootherDoubleSmoothing(backtest) &lt;- FALSE
3#Modify smoother's decay length
4> setSmootherLambda(backtest) &lt;- "12m"
5#Start rebalancing 12 months after start date
6> setSmootherSkip(backtest) &lt;- "12m"
7#Use equal weights as starting points
8> nAssets &lt;- 5
9> setSmootherInitialWeights(backtest) &lt;- rep(1/nAssets, nAssets)
10#Check your settings after making changes
11> getSmootherParams(backtest)
12$doubleSmoothing
13[1] FALSE
14 
15$lambda
16[1] "12m"
17 
18$skip
19[1] "12m"
20 
21$initialWeights
22[1] 0.2 0.2 0.2 0.2 0.2

Backtesting sector rotation portfolio

The SPI data is used for the purpose of this backtest. The swiss performance index is comprised of nine sectors: finance, technology, materials, consumer goods, industrials, health care, consumer services, utilities and telecommunications. The strategy uses a fixed rolling window of 12 months shifted in monthly intervals. Portfolio optimization is done via the mean-variance Markowitz method. The first thing we need to do is specify the portfolio data for the specification, for the constraints and for the portfolio backtest.

1>library(fPortfolio)
2>colnames(SPISECTOR.RET)
3 [1] "SPI"  "BASI" "INDU" "CONG" "HLTH" "CONS" "TELE" "UTIL" "FINA" "TECH"
4>spiData &lt;- SPISECTOR.RET
5>spiSpec &lt;- portfolioSpec()
6>spiConstraints &lt;- "LongOnly"
7>spiBacktest &lt;- portfolioBacktest()
8 
9#Specify assets for backtesting
10>spiFormula &lt;- SPI ~ BASI + INDU + CONG + HLTH + CONS + TELE +
11+     UTIL + FINA + TECH
12 
13#Optimize rolling portfolios and run backtests
14>spiPortfolios &lt;- portfolioBacktesting(formula = spiFormula,
15+                                       data = spiData, spec = spiSpec, constraints = spiConstraints,
16+                                       backtest = spiBacktest, trace = FALSE)
17 
18#Weights of first 12 months are rebalanced on a monthly basis
19>Weights &lt;- round(100 * spiPortfolios$weights, 2)[1:12, ]
20>Weights
21           BASI INDU  CONG HLTH CONS TELE   UTIL  FINA TECH
222000-12-31    0    0 48.08    0    0    0  27.54 16.06 8.33
232001-01-31    0    0 22.25    0    0    0  28.62 49.13 0.00
242001-02-28    0    0 31.15    0    0    0  32.80 36.05 0.00
252001-03-31    0    0 51.92    0    0    0  48.08  0.00 0.00
262001-04-30    0    0 39.77    0    0    0  46.70 13.53 0.00
272001-05-31    0    0 35.16    0    0    0  64.68  0.16 0.00
282001-06-30    0    0 47.84    0    0    0  52.16  0.00 0.00
292001-07-31    0    0 27.19    0    0    0  72.81  0.00 0.00
302001-08-31    0    0  0.00    0    0    0 100.00  0.00 0.00
312001-09-30    0    0  0.00    0    0  100   0.00  0.00 0.00
322001-10-31    0    0  0.00    0    0  100   0.00  0.00 0.00
332001-11-30    0    0  0.00    0    0  100   0.00  0.00 0.00

To reduce the costs of rebalancing to often we set the smoothing parameter lambda to 12 months. This way we increase the smoothing effect.

1>setSmootherLambda(spiPortfolios$backtest) <- "12m"
2>spiSmoothPortfolios <- portfolioSmoothing(object = spiPortfolios,
3+                                           trace = FALSE)
4>smoothWeights <- round(100 * spiSmoothPortfolios$smoothWeights,
5+                        2)[1:12, ]
6>smoothWeights
7           BASI INDU  CONG HLTH CONS  TELE  UTIL  FINA TECH
82000-12-31    0    0 48.08    0    0  0.00 27.54 16.06 8.33
92001-01-31    0    0 47.47    0    0  0.00 27.56 16.84 8.13
102001-02-28    0    0 46.64    0    0  0.00 27.70 17.86 7.80
112001-03-31    0    0 46.18    0    0  0.00 28.29 18.16 7.37
122001-04-30    0    0 45.70    0    0  0.00 29.14 18.27 6.89
132001-05-31    0    0 45.10    0    0  0.00 30.59 17.92 6.39
142001-06-30    0    0 44.74    0    0  0.00 32.15 17.24 5.88
152001-07-31    0    0 44.06    0    0  0.00 34.22 16.35 5.37
162001-08-31    0    0 42.54    0    0  0.00 37.26 15.32 4.88
172001-09-30    0    0 40.44    0    0  2.37 38.55 14.23 4.41
182001-10-31    0    0 37.98    0    0  6.37 38.57 13.11 3.98
192001-11-30    0    0 35.32    0    0 11.46 37.67 11.99 3.57

Plot backtests

1>backtestPlot(spiSmoothPortfolios, cex = 0.6, font = 1, family = "mono")
2 
3#
4>netPerformance(spiSmoothPortfolios)
5 
6Net Performance % to 2008-10-31: 
7          1 mth 3 mths 6 mths  1 yr 3 yrs 5 yrs 3 yrs p.a. 5 yrs p.a.
8Portfolio -0.21  -0.25  -0.31 -0.44 -0.26  0.49      -0.09       0.10
9Benchmark -0.13  -0.17  -0.22 -0.38 -0.29  0.29      -0.10       0.06
10 
11 
12Net Performance % Calendar Year:
13           2001  2002 2003 2004 2005 2006 2007   YTD Total
14Portfolio -0.10 -0.09 0.25 0.25 0.22 0.29 0.05 -0.39  0.48
15Benchmark -0.25 -0.30 0.20 0.07 0.30 0.19 0.00 -0.32 -0.11

Output:

As shown in above graph if we had started the strategy in January 2001 the total portfolio return would have been 79.60%. During 2002 and 2003 when the market was on a downward trend the portfolio was able to absorb losses better than the benchmark. The amount of rebalancing see graph “Weights Rebalance” was also reasonable with a per month rebalancing of around 8%. Weights are smoothed with a double EMA smoother with a time decay of 6 months. The total return for the time frame tested is 48% for our portfolio vs -11% for the benchmark.

What to do next

If you made it through the end of this tutorial and you still don’t have enough we recommend you read the following books to gain a deeper understanding on the theoretical background of portfolio optimization.

Robust Portfolio Optimization and Management

Quantitative Equity Portfolio Management: An Active Approach to Portfolio Construction and Management

Quantitative Equity Portfolio Management: Modern Techniques and Applications

Advances in Active Portfolio Management: New Developments in Quantitative Investing

————

References

Bacon, C. R. (2008). Practical Portfolio Performance Measurement and
Attribution (2 ed.). JohnWiley & Sons.

DeMiguel, V.; Garlappi, L.; and Uppal, R. (2009) . “Optimal versus Naive Diversification: How Inefficient Is the 1/N Portfolio Strategy?”. The Review of Financial Studies, pp. 1915—195.

DiethelmWürtz, Tobias Setz,William Chen, Yohan Chalabi, Andrew Ellis (2010). “Portfolio Optimization with R/Rmetrics”.

Guofu Zhou (2008) “On the Fundamental Law of Active Portfolio Management: What Happens If Our Estimates Are Wrong?” The Journal of Portfolio Management, pp. 26-33

Taleb N.N. (2019). The Statistical Consequences of Fat Tails (Technical Incerto Collection)

Würtz, D. & Chalabi, Y. (2009a). The fPortfolio Package. cran.r-project.org.

Wang, N. &Raftery, A. (2002). Nearest neighbor variance estimation (nnve):
Robust covariance estimation via nearest neighbor cleaning. Journal of
the American Statistical Association, 97, 994–1019.

Spread Trading Sector Index Futures – CME Group – CME Group https://www.cmegroup.com/education/articles-and-reports/spread-trading-sector-index-futures.html