braverock / PortfolioAnalytics

76 stars 46 forks source link

Maximize STARR for a given Expected Shortfall #11

Closed flodase closed 6 years ago

flodase commented 6 years ago

Dear developer(s),

first of all, thank you very much for this great package. I am currently trying to us it for optimizing portfolios for expected return given a predefined Expected Shortfall. When applying the below written code to my data set I get expected results. However, I cannot find a way to predefine a target ES, the way I understand the function is that it always calculates the optimal benchmark weights, given the minimized ES, is that true? Should it not be possible to simply optimize portfolio returns given an ES of e.g. 0.2?

Thank you very much in advance!

> # Maximize starr
> data(edhec)
> R <- edhec[, 1:4]
> funds <- colnames(R)
> 
> # Construct initial portfolio
> init.portf <- portfolio.spec(funds)
> init.portf <- add.constraint(portfolio=init.portf, type="weight_sum",
+                              min_sum=0.99, max_sum=1.01)
> init.portf <- add.constraint(portfolio=init.portf, type="long_only")
> 
> starr.portf <- add.objective(portfolio=init.portf, type="return", name="mean")
> starr.portf <- add.objective(portfolio=starr.portf, type="risk", name="ES")
> 
> max_starr_opt <- optimize.portfolio(R, portfolio=starr.portf, optimize_method="ROI", maxSTARR=TRUE)
> max_starr_opt
***********************************
PortfolioAnalytics Optimization
***********************************

Call:
optimize.portfolio(R = R, portfolio = starr.portf, optimize_method = "ROI", 
    maxSTARR = TRUE)

Optimal Weights:
Convertible Arbitrage            CTA Global Distressed Securities      Emerging Markets 
               0.0000                0.4274                0.5626                0.0000 

Objective Measure:
    mean 
0.007248 

     ES 
0.02176 
braverock commented 6 years ago

In your example, you are trying to maximize mean return subject to minimizing ES, so you are looking for the tangency portfolio on the mean/ES efficient frontier.

To target a specific level of ES, instead of type='risk', use type='min_max' and specify the threshold you wish. You can create a 'penalty band', by specifying both a min and a max. Note that you should probably think about what is reasonable, and realize that you may create an infeasible portfolio. So these are penalization methods, not hard and fast rules. This objective type will instruct the optimization solver to do the best it can to construct the optimal portfolio you want.

see ?minmax_objective

flodase commented 6 years ago

Thank you for your quick answer. In fact, my goal is indeed to specify the range of solving for a feasible portfolio. However, I cannot seem to get my head around the syntax of the minmax objective function.

Substituting type = "risk" withtype = "min_max" yields the following output:

> starr.portf <- add.objective(portfolio=init.portf, type="return", name="mean")
> starr.portf <- add.objective(portfolio=starr.portf, type= "min_max", name = "ES", min = 0.05, max = 0.05)
> starr.portf
**************************************************
PortfolioAnalytics Portfolio Specification 
**************************************************

Call:
portfolio.spec(assets = funds)

Number of assets: 4 
Asset Names
[1] "Convertible Arbitrage" "CTA Global"            "Distressed Securities" "Emerging Markets"     

Constraints
Enabled constraint types
        - weight_sum 
        - long_only 

Objectives:
Enabled objective names
        - mean 

I.e., the objective is not being passed through to the portfolio specifications. When I keep starr.portf <- add.objective(portfolio=starr.portf, type="risk", name="ES") there's only one difference: ES is mentioned as an objective again. Also, in the structure of the portfolio.spec the minmax objective doesn't appear:

> str(starr.portf)
[...]
 $ objectives     :List of 1
  ..$ :List of 6
 $ name      : chr "mean"
 $ target    : NULL
 $ arguments : list()
 $ enabled   : logi TRUE
 $ multiplier: num -1
 $ call      : language add.objective(portfolio = init.portf, type = "return", name = "mean")
 - attr(*, "class")= chr "\"return_objective\" \"objective\""
 $ call           : language portfolio.spec(assets = funds)
 - attr(*, "class")= chr [1:2] "portfolio.spec" "portfolio"

I tried passing the objective through in the optimization function, but that also gives the same output as before:

`> starr.portf <- add.objective(portfolio=init.portf, type="return", name="mean")
> 
> minmax <- minmax_objective(name = "ES", min = 0.05, max = 0.05)
> 
> max_starr_opt <- optimize.portfolio(R, portfolio=starr.portf, optimize_method="ROI", objectives = list(minmax), maxSTARR=TRUE)
> max_starr_opt
***********************************
PortfolioAnalytics Optimization
***********************************

Call:
optimize.portfolio(R = R, portfolio = starr.portf, objectives = list(minmax), 
    optimize_method = "ROI", maxSTARR = TRUE)

Optimal Weights:
Convertible Arbitrage            CTA Global Distressed Securities      Emerging Markets 
               0.0000                0.4274                0.5626                0.0000 

Objective Measure:
     ES 
0.02176 `

Would love to know what I'm doing wrong. I also checked all the available documentation but couldn't find a demo example with the minmax objective.

braverock commented 6 years ago

I don't have time to poke at this right now, but my first guess is that solver ROI doesn't support this for ES.

Expected Shortfall is not guaranteed to be convex in the case where asset returns are non-normal. Convex solvers may provide incorrect results, or may fail to converge in this case.

I'll try to poke at it over the weekend, or perhaps @rossb34 has some input.