robertmartin8 / PyPortfolioOpt

Financial portfolio optimisation in python, including classical efficient frontier, Black-Litterman, Hierarchical Risk Parity
https://pyportfolioopt.readthedocs.io/
MIT License
4.26k stars 929 forks source link

How to add portfolio yield objective? #481

Closed Token-DAO closed 1 year ago

Token-DAO commented 1 year ago

When optimizing a portfolio using the efficient_risk objective function, how can I add an objective that says the resulting portfolio must have a total yield (dividends and interest) of say 4%? Here is the work I've done so far.

I wrote the function for portfolio yield and tried to add constraint and add objective:

def portfolio_yield(w, yields, negative=True):
    sign = -1 if negative else 1
    port_yield = w @ yields
    return _objective_value(w, sign * port_yield)

target_yield = cp.Parameter(name='target_yield', value=target_yield)
portfolio_yield = portfolio_yield(w, yields, negative=False)
ef.add_constraint(lambda w: w >= target_yield)
ef.add_objective(portfolio_yield(yields=yields))

After that, I am kind of stuck. Thanks for the help.

phschiele commented 1 year ago

It looks like you have the yields as the objective as well as the constraints. Usually, it is either maximizing the return for a given level of risk or minimizing the risk for a given level of return.

In your case, you try to maximize return, but if it's not possible to achieve target_yield as per your constraint, the optimization will fail. Is this the intended problem you are trying to solve?

nathanramoscfa commented 1 year ago

This seems to work for me.

def _objective_value(w, obj):
    if isinstance(w, np.ndarray):
        if np.isscalar(obj):
            return obj
        elif np.isscalar(obj.value):
            return obj.value
        else:
            return obj.value.item()
    else:
        return obj

def portfolio_yield(w, yields, negative=True):
    sign = -1 if negative else 1
    port_yield = w @ yields
    return _objective_value(w, sign * port_yield)

if add_yield_constraint==True:
    ef.add_constraint(lambda w: w @ yields >= target_yield)
    ef.add_objective(portfolio_yield, yields=yields)
nathanramoscfa commented 1 year ago

@phschiele

It looks like you have the yields as the objective as well as the constraints. Usually, it is either maximizing the return for a given level of risk or minimizing the risk for a given level of return.

In your case, you try to maximize return, but if it's not possible to achieve target_yield as per your constraint, the optimization will fail. Is this the intended problem you are trying to solve?

Hmm, you might be right. I don't think ef.add_objective(portfolio_yield, yields=yields) is needed as we're just trying to maximize portfolio return with a target yield constraint...not trying to maximize portfolio yield AND return.