Reputation: 179
Context: I'm the developer of PyPortfolioOpt, a python portfolio optimisation library, and I'm trying to allow users to add constraints to a maximum Sharpe ratio problem.
Currently, users can pass their constraints as a lambda function, e.g to make all weights greater than 1%:
ef = EfficientFrontier(mu, S) # mu and S are expected return and covariance
ef.add_constraint(lambda w: w >= 0.01) # example new constraint
ef.min_volatility() # optimise with constraint
On the backend, I pass a cvxpy variable w = cp.Variable(n)
to the constraint lambda function, to create a valid cvxpy constraint, then I pass this to cp.Problem
and solve it.
The trouble I am having is that maximising the Sharpe ratio requires you to make a variable substitution. Constraints of the form Ax ~ b
(where ~
denotes either equality or inequality) must become Ax ~ k * b
where k
is a nonnegative optimisation variable.
One thing I tried was to pass w / k
into the lambda function. This would then result in a constraint w / k >= 0.01
, which I hoped would be equivalent to w >= k * 0.01
, but sadly this gives:
DCPError: Problem does not follow DCP rules. Specifically:
The following constraints are not DCP:
0.01 <= var2817 / Promote(var2818, (20,)) , because the following subexpressions are not:
|-- var2817 / Promote(var2818, (20,))
I then thought that I might be able to take the nonlinear constraint constr = (w / k >= 0.01)
and multiply it by k
to give k * constr = (w >= 0.01 * k)
, but you can't multiply constraints in cvxpy.
TL;DR: how can I convert the cvxpy constraint object (already instantiated) representing w / k >= 0.01
to a cvxpy constraint object representing w >= k * 0.01
?
Or failing that, is there any way I can re-engineer this? I'd like to keep the lambda function API.
Upvotes: 2
Views: 740
Reputation: 922
Perhaps there is some API for decomposing an already instantiated constraint so that I can put in a variable?
Constraints are immutable by design. Immutability simplifies much of CVXPY’s logic.
Why not construct a new constraint? You can certainly inspect the left and right hand sides of the constraint. Right now, that can be done by inspecting the args
attribute (see https://github.com/cvxgrp/cvxpy/blob/master/cvxpy/constraints/nonpos.py#L97).
Upvotes: 2