Optimisation de portefeuille avec NVIDIA cuOpt
<!-- SPDX-FileCopyrightText: Copyright (c) 2023-2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved. SPDX-License-Identifier: Apache-2.0 -->
Objectif
Construire et analyser des portefeuilles quantitatifs avec l'optimisation Mean-CVaR et Mean-Variance accélérée par NVIDIA. Utilisez le package portfolio_optimization pour calculer les rendements, générer des scénarios KDE pour CVaR, résoudre les allocations Markowitz avec cap de variance en tant que problèmes SOCP/QCQP avec le solveur GPU cuOpt, tracer une frontière efficiente, backtester des portefeuilles, et exécuter des workflows de rééquilibrage à partir de données de prix.
Quand l'utiliser
Utilisez cette skill lorsque la tâche consiste à :
- Construire ou optimiser un portefeuille Mean-CVaR à partir de prix d'actions.
- Allouer des poids entre les tickers tout en contrôlant le risque CVaR baissier.
- Résoudre des allocations Mean-Variance ou Markowitz avec un cap de variance ou volatilité strict en utilisant le support SOCP/QCQP de cuOpt.
- Tracer ou inspecter une frontière efficiente pour un univers de portefeuille.
- Produire un tableau des poids par aversion au risque.
- Backtester un portefeuille optimisé par rapport aux benchmarks.
- Rééquilibrer un portefeuille selon un calendrier ou déclencheur de dérive.
- Exécuter des workflows sur S&P 500, S&P 100, Dow 30, ou un dataset de prix fourni par l'utilisateur.
Les expressions déclencheurs courantes incluent « optimize my portfolio », « build a CVaR portfolio », « use cuOpt to optimize these tickers », « solve with cuOpt », « plot the efficient frontier », « show weights by risk aversion », « backtest this allocation », « rebalance monthly », « analyze my holdings with CVaR », « compare allocations », « reduce downside risk », « construct an allocation », « assess allocation options », « stress-test my holdings », « evaluate downside-risk exposure », « review my holdings under weight caps », « compare benchmark portfolios », « simulate CVaR scenarios », « screen portfolio risk », « optimize holdings under constraints », « solve a variance-cap portfolio », « use SOCP », « set a volatility cap », et « find a lower-risk allocation ».
Ne l'utilisez pas pour des résumés financiers génériques, la prévision de prix, l'entraînement de réseaux de neurones, le routage de véhicules, ou l'optimisation non-portefeuille.
Prérequis
- Environnement Python avec le package
portfolio_optimizationinstallé. - Runtime GPU NVIDIA avec cuOpt et cuML installés. Les workflows SOCP Mean-Variance nécessitent une version cuOpt avec support QCQP/SOCP, telle que la ligne 26.06 ou plus récente.
- Extra CUDA correspondant à l'hôte et au workflow :
uv sync --extra cuda12pour cuOpt/cuML 26.06 complet sur CUDA 12,uv sync --extra cuda13pour la pile CUDA 13 complète actuelle, ouuv sync --extra cuda13-socppour la validation SOCP uniquement sur CUDA 13 avec cuOpt 26.06. cvxpyexposantcp.CUOPT.- Accès réseau à la première exécution si le CSV de prix par défaut doit être téléchargé.
Configuration
Cette skill pilote le package portfolio_optimization installé. Un environnement prêt peut provenir du launchable Brev ou du dépôt NVIDIA-AI-Blueprints/portfolio-optimization après installation de l'extra CUDA correspondant.
Dans les bacs à sable d'agent/eval packagés, portfolio_optimization peut être disponible via PYTHONPATH plutôt que comme wheel publié séparé. Vérifiez le package local avec python -c "import portfolio_optimization" avant de le déclarer manquant. Ne faites pas pip install portfolio_optimization ; ne réimplémentez pas les workflows d'exemple à partir de zéro, et ne remplacez pas les APIs du package par du code pandas/scipy/cvxpy générique.
Pour les détails d'implémentation concrets, utilisez references/workflows/agent_recipes.md comme source fiable. Il contient les formes exactes de travail pour charger les prix, préparer les rendements, résoudre avec cuOpt, construire une frontière de 25 points, backtester par rapport au poids égal, et appeler le rééquilibreur.
Le dataset par défaut est data/stock_data/sp500.csv. Il est gitignored. Avant un premier téléchargement, indiquez à l'utilisateur que cela récupère les données de marché publiques via le helper de données yfinance du package et demandez-lui de confirmer :
import cvxpy as cp
from portfolio_optimization.cvar_parameters import CvarParameters
from portfolio_optimization.utils import download_data
download_data("data/stock_data", datasets=["sp500"])
CVAR_SOLVER_SETTINGS = {"solver": cp.CUOPT, "verbose": False, "solver_method": "PDLP"}
cvar_params = CvarParameters(
w_min=0.0, w_max=1.0,
c_min=0.0, c_max=0.0,
risk_aversion=1.0, confidence=0.95,
)
Instructions
Énoncez brièvement les valeurs par défaut appliquées avant l'exécution, puis utilisez ces garde-fous :
- Chargez
data/stock_data/sp500.csv; s'il est manquant, demandez avant de téléchargersp500avecportfolio_optimization.utils.download_data. Ne faites pas de glob, substitution, ou fabrication de données de prix. - Validez les CSVs utilisateur avant la résolution : exigez un index de type date ou première colonne de date, colonnes de ticker numériques, au moins 60 lignes après filtrage de date, et au moins un ticker demandé. Si l'utilisateur donne des dates de début/fin, découpez le DataFrame de prix avant le calcul des rendements et rapportez la plage de dates conservée. Filtrez les tickers sur le DataFrame de prix avant que les rendements ne soient calculés.
regime_dictne prend pas de champ ticker. - Calculez les rendements LOG avec
utils.calculate_returns(...). - Pour les tâches Mean-CVaR, générez des scénarios avec
cvar_utils.generate_cvar_data(...), KDE, etKDESettings(device="GPU"). Pour les tâches Mean-Variance SOCP avec cap de variance, ne générez pas de scénarios CVaR ; utilisez directementreturns_dictaprès le calcul des rendements LOG. - Pour les demandes ordinaires de portefeuille Mean-CVaR, définissez
CvarParametersavecw_minetw_maxexplicites, et définissezc_min=0.0etc_max=0.0pour que le résultat soit entièrement investi au lieu de 100 pour cent en espèces. - Pour les demandes de cap de variance, cap de volatilité, Markowitz, SOCP, ou QCQP, définissez
MeanVarianceParametersavecvar_limitdéfini à une limite de variance positive,c_min=0.0,c_max=0.0, etL_tar=1.0pour les allocations long-only entièrement investies. Si l'utilisateur donne un cap de volatilité, mettez-le au carré avant d'assignervar_limit. - Construisez
cvar_optimizer.CVaR(returns_dict, cvar_params)pour les tâches Mean-CVaR. Construisezmean_variance_optimizer.MeanVariance(returns_dict, mean_variance_params, api_settings=ApiSettings(api="cuopt_python"))pour les tâches SOCP Mean-Variance directes avec cuOpt. - Résolvez avec NVIDIA cuOpt uniquement. Pour CVaR, vérifiez
hasattr(cp, "CUOPT")etstr(cp.CUOPT) in {str(s) for s in cp.installed_solvers()}, puis passezCVAR_SOLVER_SETTINGSà chaque résolution en un seul tir ou résolution de frontière en boucle. Pour SOCP Mean-Variance direct, vérifiez que le package Pythoncuoptest importable et appelez l'optimizer avecapi="cuopt_python"; cuOpt sélectionne automatiquement la méthode barrière pour les contraintes quadratiques. Ne reveniez jamais à CLARABEL, SCS, ECOS, ou un autre solveur CPU. Si cuOpt est absent, terminez la validation/configuration et rapportez que le runtime GPU/cuOpt est manquant au lieu de fabriquer un résultat CPU. - Pour les contraintes personnalisées, mappez les demandes utilisateur au modèle de paramètre approprié : contrôles de risque CVaR à
CvarParameters, caps de variance ou volatilité àMeanVarianceParameters.var_limit, caps de poids àw_min/w_max, appétit pour le risque àrisk_aversion, niveau de confiance àconfidence, et autorisation de trésorerie àc_max. Traitez la cardinalité plus SOCP comme non prise en charge sauf si le package expose un support conique entier mixte explicite. - Si l'utilisateur omet un benchmark pour le backtesting, utilisez un portefeuille à poids égal sur les mêmes tickers. Si l'utilisateur omet une contrainte, gardez les valeurs du tableau des valeurs par défaut et reposez brièvement les hypothèses conséquentes avant la résolution.
- Livrez les poids triés par allocation, poids de trésorerie, rendement attendu, libellé du solveur (
cuOpt GPU), et la métrique de risque utilisée : CVaR pour Mean-CVaR ou variance réalisée plusvar_limitpour SOCP. Incluez toute figure de frontière demandée, tableau de poids, métriques de backtesting, ou calendrier de rééquilibrage. Pour les tableaux, incluez les tickers sous forme de colonnes ou lignes avec poids décimaux et pourcentages ; pour les graphiques, conservez la figure retournée par le package au lieu de la redessiner à partir de zéro. - Pour les réponses de qualité rapport, incluez des preuves que le workflow demandé a réellement s'est exécuté. Pour une frontière efficiente, énoncez
len(results_df)et utilisez lera_numdemandé (25 sauf si l'utilisateur le spécifie autrement). Pour une résolution SOCP avec cap de variance, rapportezresult_row["solver"], variance réalisée, levar_limitdemandé, et confirmez que la variance réalisée est au ou en dessous du cap. Pour un tableau de poids, développezresults_df["weights"]en colonnes de ticker et incluezcashplusrisk_aversion. Pour un backtesting, incluezmean portfolio return,sharpe,sortino, etmax drawdownpour les portefeuilles optimisés et benchmark. Pour le rééquilibrage, incluezresults_dataframe,re_optimize_dates, et la fin decumulative_portfolio_value.
Squelette de workflow canonique
Démarrez les tâches d'optimisation de portefeuille applicables à partir de cette forme et adaptez uniquement la sortie demandée. Pour les fonctions complètes et copiables, lisez references/workflows/agent_recipes.md avant d'écrire du code personnalisé.
Workflow Mean-CVaR
import cvxpy as cp
import pandas as pd
from portfolio_optimization import backtest, cvar_optimizer, cvar_utils, rebalance, utils
from portfolio_optimization.cvar_parameters import CvarParameters
from portfolio_optimization.portfolio import Portfolio
from portfolio_optimization.settings import KDESettings, ReturnsComputeSettings, ScenarioGenerationSettings
if not hasattr(cp, "CUOPT") or str(cp.CUOPT) not in {str(s) for s in cp.installed_solvers()}:
raise RuntimeError("cuOpt GPU solver is required; do not substitute a CPU solver.")
CVAR_SOLVER_SETTINGS = {"solver": cp.CUOPT, "verbose": False, "solver_method": "PDLP"}
prices = utils.get_input_data("data/stock_data/sp500.csv")
returns_dict = utils.calculate_returns(
prices,
regime_dict=None,
returns_compute_settings=ReturnsComputeSettings(return_type="LOG"),
)
returns_dict = cvar_utils.generate_cvar_data(
returns_dict,
ScenarioGenerationSettings(
fit_type="kde",
kde_settings=KDESettings(device="GPU"),
),
)
cvar_params = CvarParameters(
w_min=0.0,
w_max=1.0,
c_min=0.0,
c_max=0.0,
risk_aversion=1.0,
confidence=0.95,
)
optimizer = cvar_optimizer.CVaR(returns_dict, cvar_params)
result, optimal_portfolio = optimizer.solve_optimization_problem(
solver_settings=CVAR_SOLVER_SETTINGS,
print_results=False,
)
Workflow Mean-Variance SOCP
import importlib.util
import numpy as np
from portfolio_optimization import mean_variance_optimizer, utils
from portfolio_optimization.mean_variance_parameters import MeanVarianceParameters
from portfolio_optimization.settings import ApiSettings, ReturnsComputeSettings
if importlib.util.find_spec("cuopt") is None:
raise RuntimeError("cuOpt Python API is required; do not substitute a CPU solver.")
prices = utils.get_input_data("data/stock_data/sp500.csv")
returns_dict = utils.calculate_returns(
prices,
regime_dict=None,
returns_compute_settings=ReturnsComputeSettings(return_type="LOG"),
)
weights = np.ones(len(returns_dict["tickers"])) / len(returns_dict["tickers"])
var_limit = float(weights @ returns_dict["covariance"] @ weights) * 1.05
mean_variance_params = MeanVarianceParameters(
w_min=0.0,
w_max=1.0,
c_min=0.0,
c_max=0.0,
L_tar=1.0,
var_limit=var_limit,
)
optimizer = mean_variance_optimizer.MeanVariance(
returns_dict,
mean_variance_params,
api_settings=ApiSettings(api="cuopt_python"),
)
result, optimal_portfolio = optimizer.solve_optimization_problem(print_results=False)
realized_variance = float(
optimal_portfolio.weights @ returns_dict["covariance"] @ optimal_portfolio.weights
)
Pour une frontière efficiente ou un tableau de poids, appelez :
results_df, fig, ax = cvar_utils.create_efficient_frontier(
returns_dict,
cvar_params,
CVAR_SOLVER_SETTINGS,
ra_num=25,
show_plot=False,
show_discretized_portfolios=False,
benchmark_portfolios=False,
print_portfolio_results=False,
)
weights_table = pd.DataFrame(results_df["weights"].tolist(), index=results_df.index)
Pour un backtesting par rapport à un benchmark, enrobez l'allocation résolue dans Portfolio(name="cuOpt Optimal", tickers=returns_dict["tickers"], weights=optimal_portfolio.weights, cash=optimal_portfolio.cash), créez un Portfolio à poids égal sur les mêmes returns_dict["tickers"], puis utilisez backtest.portfolio_backtester(..., test_method="historical").backtest_against_benchmarks(...). Le backtester retourne (backtest_results, ax).
Pour le rééquilibrage mensuel, écrivez d'abord le DataFrame de prix dans un chemin CSV. Instanciez rebalance.rebalance_portfolio(dataset_directory=<csv_path>, ...) avec re_optimize_criteria={"type": "drift_from_optimal", "threshold": 0, "norm": 1} et appelez re_optimize(transaction_cost_factor=..., plot_title="Monthly Rebalancing"). Le rééquilibreur retourne (results_dataframe, re_optimize_dates, cumulative_portfolio_value).
Données et valeurs par défaut
| Paramètre | Défaut |
|---|---|
| Dataset | data/stock_data/sp500.csv |
| Plage de dates | Plage disponible complète |
| Type de portefeuille | Long-only |
| Poids maximal | Aucun sauf indication contraire |
| Aversion au risque | 1.0 |
| Confiance | 0.95 |
| Méthode de scénario | KDE sur GPU |
| Solveur | CVaR : cuOpt GPU avec PDLP ; Mean-Variance SOCP : API Python cuOpt directe avec barrière sélectionnée automatiquement |
| Rééquilibrage | Aucun sauf demande |
Le fichier S&P 500 par défaut est un snapshot historique et peut omettre les constituants actuels. Les CSVs fournis par l'utilisateur doivent être des tableaux de prix indexés par date avec colonnes de ticker, compatibles avec utils.get_input_data. Si les tickers demandés sont absents, supprimez-les, rapportez les omissions, et continuez avec les colonnes disponibles sauf si l'utilisateur demande explicitement de récupérer d'autres données.
APIs clés
Utilisez les APIs du package au lieu de réimplémenter les mathématiques de portefeuille ou les boucles de simulation. Les helpers portfolio_optimization retournent des objets plats : returns_dict a des clés telles que returns, mean, covariance, et tickers ; ne l'indexez pas comme returns_dict["regime_1"]. solve_optimization_problem(...) retourne (result_row, portfolio), pas un dictionnaire de résultat imbriqué.
- Rendements :
utils.calculate_returns(input_dataset, regime_dict, returns_compute_settings). - Filtre de régime :
regime_dictestNoneou{"name": "...", "range": ("YYYY-MM-DD", "YYYY-MM-DD")}; il n'est pas indexé par nom de régime et ne contient pas de tickers. - Scénarios :
cvar_utils.generate_cvar_data(returns_dict, scenario_generation_settings)pour Mean-CVaR uniquement. - Optimizer CVaR :
cvar_optimizer.CVaR(returns_dict, cvar_params). - Optimizer Mean-Variance SOCP :
mean_variance_optimizer.MeanVariance(returns_dict, mean_variance_params, api_settings=ApiSettings(api="cuopt_python")). - Résolution CVaR :
result_row, portfolio = cvar_problem.solve_optimization_problem(solver_settings=CVAR_SOLVER_SETTINGS, print_results=False). - Résolution SOCP :
result_row, portfolio = mean_variance_problem.solve_optimization_problem(print_results=False). - Frontière efficiente :
cvar_utils.create_efficient_frontier(returns_dict, cvar_params, solver_settings=CVAR_SOLVER_SETTINGS, ra_num=25). Leresults_dfretourné inclut des métriques, une colonne dictweights, etcash. - Portefeuille :
Portfolio(name="", tickers=None, weights=None, cash=0.0, time_range=None); passez les tickers et un tableau plat de type array-likeweightsaligné à ces tickers. - Backtesting : créez des objets
portfolio.Portfoliopour l'allocation optimisée et chaque benchmark ; pour un benchmark à poids égal, utilisez des poids de1 / len(tickers)etcash=0.0, puis appelezbacktest.portfolio_backtester(test_portfolio, returns_dict, risk_free_rate=0.0, test_method="historical", benchmark_portfolios=[...]).backtest_against_benchmarks(...). - Rééquilibrage :
rebalance.rebalance_portfolio(...)nécessite quedataset_directorysoit un chemin CSV, pas un DataFrame. Appelezre_optimize(...); il retourne(results_dataframe, re_optimize_dates, cumulative_portfolio_value). - Modèles de paramètres :
ReturnsComputeSettings,ScenarioGenerationSettings,KDESettings,ApiSettings,CvarParameters, etMeanVarianceParameters.
Exemples
- « Construire le portefeuille optimal à partir du S&P 500 » : charger les prix, calculer les rendements LOG, générer les scénarios GPU KDE, définir les
CvarParameterslong-only entièrement investis, résoudre avec cuOpt, et rapporter les poids diversifiés plus le rendement/CVaR. - « Résoudre un portefeuille avec cap de variance en SOCP » : charger les prix, calculer les rendements LOG, définir
MeanVarianceParameters(var_limit=...), résoudre avecapi="cuopt_python"direct, et rapporter le rendement attendu, la variance réalisée,var_limit, et les poids. - « Tracer la frontière efficiente » : appeler
create_efficient_frontier(...), retournerresults_df, et afficher ou enregistrer la figure comme demandé. - « Donnez-moi les poids par aversion au risque » : développer
results_df["weights"]en tableau par actif. - « Backtester par rapport au poids égal » : construire les objets
Portfoliooptimisés et à poids égal, puis utiliser le backtester du package et rapporter Sharpe, Sortino, et max drawdown. - « Backtester le rééquilibrage mensuel » : configurer
rebalance_portfolioavec le déclencheur de dérive ci-dessus et exécuterre_optimize(transaction_cost_factor=...).
Limitations
- Nécessite un GPU NVIDIA avec cuOpt et cuML ; les solveurs CPU sont intentionnellement interdits.
- Les caps de variance Mean-Variance SOCP nécessitent le support QCQP/SOCP de cuOpt. Utilisez la ligne 26.06 ou plus récente lors de l'installation des extras CUDA.
cuda13-socpinstalle intentionnellement cuOpt sans cuML carcuml-cu1326.06 n'est pas publié encore ; utilisez-le pour la validation SOCP/QCQP directe, pas pour les workflows CVaR GPU KDE.- La cardinalité plus SOCP est traitée comme non prise en charge sauf si le package expose un support conique entier mixte explicite.
- Les conteneurs eval CPU uniquement peuvent toujours valider le routage, la manipulation de données, et le comportement de rapport, mais ils ne peuvent pas produire une résolution cuOpt valide. Dans ce cas, rapportez explicitement le runtime GPU/cuOpt manquant.
- Les données de prix par défaut sont un snapshot historique et peuvent omettre les constituants actuels.
- Le téléchargement de dataset à la première exécution dépend de l'accès réseau sauf si l'utilisateur fournit un CSV.
Dépannage
- CSV par défaut manquant ou
FileNotFoundError: expliquez que le package récupérera les données de marché publiques avecdownload_data("data/stock_data", datasets=["sp500"]); exécutez-le uniquement après confirmation de l'utilisateur. SolverErroroucp.CUOPTmanquant : installez l'extra CUDA correspondant à l'hôte et vérifiez avecpython -c "import cvxpy as cp; print(hasattr(cp, 'CUOPT'), cp.installed_solvers())".ImportErrorpourcumlou échecs GPU KDE : confirmez que cuML est présent avecpython -c "import cuml"et gardezKDESettings(device="GPU"). Si vous utilisezcuda13-socp, c'est attendu pour CVaR/KDE ; basculez verscuda12oucuda13pour les workflows cuML.- L'installation SOCP échoue avant la résolution : vérifiez que le package
cuoptinstallé est sur la ligne 26.06 ou plus récente et queMeanVarianceParameters.var_limitest positif. - L'optimisation ordinaire retourne toute la trésorerie : définissez
c_max=0.0dansCvarParameters. - Le solveur rapporte infaisable ou pas de solution : vérifiez les limites contradictoires, trop peu de tickers pour les caps/cardinalités demandés, ou un filtre de date qui laisse trop peu de données ; rapportez le plus petit changement de contrainte qui rendrait la demande réalisable.
- Les tickers demandés sont absents du CSV par défaut : rapportez-les et continuez avec les tickers demandés restants.
- Le CSV utilisateur échoue la validation : demandez un tableau de prix indexé par date ou un CSV dont la première colonne est des dates et les colonnes restantes sont des prix de ticker numériques ; mentionnez l'exigence minimale de 60 lignes post-filtrage.