Note
Go to the end to download the full example code. or to run this example in your browser via JupyterLite or Binder
Threshold Constraints#
This tutorial shows how to use threshold constraints with the
MeanRisk
optimization.
Threshold constraints ensure that invested assets have sufficiently large weights. This can help eliminate insignificant allocations.
Both long and short position thresholds can be controlled using threshold_long
and
threshold_short
.
Threshold constraints require a mixed-integer solver. For an open-source option,
we recommend using SCIP by setting solver="SCIP"
. To install it, use:
pip install cvxpy[SCIP]
. For commercial solvers, supported options include
MOSEK, GUROBI, or CPLEX.
Mixed-Integer Programming (MIP) involves optimization with both continuous and integer variables and is inherently non-convex due to the discrete nature of integer variables. Over recent decades, MIP solvers have significantly advanced, utilizing methods like Branch and Bound and cutting planes to improve efficiency.
By leveraging specialized techniques such as homogenization and the Big M method, combined with problem-specific calibration, Skfolio can reformulate these complex problems into a Mixed-Integer Program that can be efficiently solved using these solvers.
Data#
We load the S&P 500 dataset composed of the daily prices of 20 assets from the S&P 500 Index composition starting from 2018-01-02 up to 2022-12-28.
from plotly.io import show
from skfolio import RiskMeasure, Population
from skfolio.datasets import load_sp500_dataset
from skfolio.optimization import MeanRisk
from skfolio.preprocessing import prices_to_returns
prices = load_sp500_dataset()
prices = prices["2018":]
X = prices_to_returns(prices)
Model#
let’s use a long-short Minimum CVaR model:
model = MeanRisk(
min_weights=-1,
risk_measure=RiskMeasure.CVAR,
)
model.fit(X)
model.weights_
array([-0.0433679 , 0.00122628, -0.15054114, -0.02295189, -0.06321762,
-0.01085526, 0.0836907 , 0.12708155, 0.0279316 , 0.20613357,
-0.01153713, 0.27084789, 0.00618136, 0.00118977, 0.10447746,
0.07189762, 0.03339536, 0.04061729, 0.25238053, 0.07541995])
Now, let’s assume we don’t want weights that are too small. This means that, let’s say, if an asset is invested (non-zero weight), it needs to be between -100% to -10% or +15% to +100%:
model_threshold = MeanRisk(
min_weights=-1,
risk_measure=RiskMeasure.CVAR,
threshold_long=0.15,
threshold_short=-0.10,
solver="SCIP",
)
model_threshold.fit(X)
model_threshold.weights_
array([ 0. , 0. , -0.1476994 , 0. , -0.1 ,
0. , 0. , 0.15274862, 0. , 0.23496164,
0. , 0.28011081, 0. , 0. , 0.15 ,
0. , 0. , 0. , 0.25077844, 0.17909988])
We notice that the long and short threshold constraints have been respected.
You can change the default solver parameters using solver_params
.
For more details about solver arguments, check the CVXPY
documentation: https://www.cvxpy.org/tutorial/solvers
To visualize both portfolio compositions, let’s plot them:
ptf = model.predict(X)
ptf.name = "Min CVaR"
ptf_threshold = model_threshold.predict(X)
ptf_threshold.name = "Min CVaR with Threshold Constraints"
population = Population([ptf, ptf_threshold])
fig = population.plot_composition()
show(fig)
Total running time of the script: (0 minutes 8.918 seconds)