Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add constraints that ignore NaN scores #593

Open
jamespanning opened this issue Apr 17, 2024 · 0 comments
Open

Add constraints that ignore NaN scores #593

jamespanning opened this issue Apr 17, 2024 · 0 comments
Labels
question Further information is requested

Comments

@jamespanning
Copy link

What are you trying to do?
I have a table of asset class scores that I am trying to add various constraints from. However, some of these scores are only applicable to certain asset classes. For example, 'Duration' would have values for Bond sectors, but are 'NaN' for Equity sectors. I don't want to use 0s for equities, as any equity allocation would understate the calculated duration of the total bonds.

		Score	Duration
Sector		
Bond A		30	10.0
Bond B		40	5.0
Equity A	50	NaN
Equity B	60	NaN

What have you tried?
I have tried the following:

  1. Add constraints based on scores
    https://pyportfolioopt.readthedocs.io/en/latest/FAQ.html#constraining-a-score
ef.add_constraint(lambda w: Constraint['Duration'].values @ w >= 0)
ef.add_constraint(lambda w: Constraint['Duration'].values @ w <= 7)

Error message:

ERROR in LDL_factor: Error in KKT matrix LDL factorization when computing the nonzero elements. The problem seems to be non-convex
ERROR in osqp_setup: KKT matrix factorization.
The problem seems to be non-convex.
...
SolverError: Workspace allocation error!
  1. Add custom constraints that would take a weighted average of the Duration ignoring sectors with 'NaN'. The np.nansum() functions work outside these constraint definitions when I try calculating the portfolio Duration scores given a weight vector.
def my_constraint_Duration_Min(weight_vector):
    return np.nansum(Constraint['Duration'].values * weight_vector) / np.nansum(np.where(np.isnan(Constraint['Duration'].values),0,1) * weight_vector) >= 0
def my_constraint_Duration_Max(weight_vector):
    return np.nansum(Constraint['Duration'].values * weight_vector) / np.nansum(np.where(np.isnan(Constraint['Duration'].values),0,1) * weight_vector) <= 7

ef.add_constraint(my_constraint_Duration_Min)
ef.add_constraint(my_constraint_Duration_Max)

Error message:

C:\ProgramData\anaconda3\Lib\site-packages\cvxpy\expressions\expression.py:621: UserWarning: 
This use of ``*`` has resulted in matrix multiplication.
Using ``*`` for matrix multiplication has been deprecated since CVXPY 1.1.
    Use ``*`` for matrix-scalar and vector-scalar multiplication.
    Use ``@`` for matrix-matrix and matrix-vector multiplication.
    Use ``multiply`` for elementwise multiplication.
...
Exception: Cannot evaluate the truth value of a constraint or chain constraints, e.g., 1 >= x >= 0.

What data are you using?
See example below. The code is working for me with the basic 'Score' constraint, but fails when I tried adding either 'Duration' constraint from above.

from pypfopt import EfficientFrontier, plotting
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt

#Sectors
Return_data = {'Sector': ['Bond A', 'Bond B', 'Equity A', 'Equity B'],
               'Return': [0.10, 0.05, 0.15, 0.20]}

Covar_data = {'Sector': ['Bond A', 'Bond B', 'Equity A', 'Equity B'],
              'Bond A': [0.0222698592925932, 0.0102208239112873, 0.00203379607967045, 0.00170610486559161],
              'Bond B': [0.0102208239112873, 0.00570949705146003, 7.05105100768718E-06, -0.000203014876458396],
              'Equity A': [0.00203379607967045, 7.05105100768718E-06, 0.013319494914958, 0.0136714010608255],
              'Equity B': [0.00170610486559161, -0.000203014876458396, 0.0136714010608255, 0.0146027220944363]}

Constraint_data = {'Sector': ['Bond A', 'Bond B', 'Equity A', 'Equity B'],
                   'Score': [30, 40, 50, 60],
                   'Duration': [10, 5, None, None]}

mu = pd.DataFrame(Return_data).set_index('Sector')
mu = mu['Return']

S = pd.DataFrame(Cov_data).set_index('Sector')
S = np.array(S, dtype=np.float64)

Constraint = pd.DataFrame(Constraint_data).set_index('Sector')


ef = EfficientFrontier(mu, S, weight_bounds=(0,1))

## Add constraints
ef.add_constraint(lambda w: Constraint['Score'].values @ w >= 0)
ef.add_constraint(lambda w: Constraint['Score'].values @ w <= 55)

# How to add constraints with NaN?

fig, ax = plt.subplots()
plotting.plot_efficient_frontier(ef, ax=ax, show_assets=True, show_tickers=True, showfig=True)
@jamespanning jamespanning added the question Further information is requested label Apr 17, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
question Further information is requested
Projects
None yet
Development

No branches or pull requests

1 participant