portfolio-optimization

Par nvidia · skills

À utiliser lorsqu'un utilisateur demande de construire, optimiser, backtester, rééquilibrer ou analyser un portefeuille d'actions avec Mean-CVaR, Mean-Variance/contraintes SOCP sur la variance, frontières efficientes, génération de scénarios ou NVIDIA cuOpt.

npx skills add https://github.com/nvidia/skills --skill portfolio-optimization

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_optimization installé.
  • 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 cuda12 pour cuOpt/cuML 26.06 complet sur CUDA 12, uv sync --extra cuda13 pour la pile CUDA 13 complète actuelle, ou uv sync --extra cuda13-socp pour la validation SOCP uniquement sur CUDA 13 avec cuOpt 26.06.
  • cvxpy exposant cp.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 :

  1. Chargez data/stock_data/sp500.csv ; s'il est manquant, demandez avant de télécharger sp500 avec portfolio_optimization.utils.download_data. Ne faites pas de glob, substitution, ou fabrication de données de prix.
  2. 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_dict ne prend pas de champ ticker.
  3. Calculez les rendements LOG avec utils.calculate_returns(...).
  4. Pour les tâches Mean-CVaR, générez des scénarios avec cvar_utils.generate_cvar_data(...), KDE, et KDESettings(device="GPU"). Pour les tâches Mean-Variance SOCP avec cap de variance, ne générez pas de scénarios CVaR ; utilisez directement returns_dict après le calcul des rendements LOG.
  5. Pour les demandes ordinaires de portefeuille Mean-CVaR, définissez CvarParameters avec w_min et w_max explicites, et définissez c_min=0.0 et c_max=0.0 pour que le résultat soit entièrement investi au lieu de 100 pour cent en espèces.
  6. Pour les demandes de cap de variance, cap de volatilité, Markowitz, SOCP, ou QCQP, définissez MeanVarianceParameters avec var_limit défini à une limite de variance positive, c_min=0.0, c_max=0.0, et L_tar=1.0 pour les allocations long-only entièrement investies. Si l'utilisateur donne un cap de volatilité, mettez-le au carré avant d'assigner var_limit.
  7. Construisez cvar_optimizer.CVaR(returns_dict, cvar_params) pour les tâches Mean-CVaR. Construisez mean_variance_optimizer.MeanVariance(returns_dict, mean_variance_params, api_settings=ApiSettings(api="cuopt_python")) pour les tâches SOCP Mean-Variance directes avec cuOpt.
  8. Résolvez avec NVIDIA cuOpt uniquement. Pour CVaR, vérifiez hasattr(cp, "CUOPT") et str(cp.CUOPT) in {str(s) for s in cp.installed_solvers()}, puis passez CVAR_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 Python cuopt est importable et appelez l'optimizer avec api="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.
  9. 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.
  10. 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.
  11. 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 plus var_limit pour 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.
  12. 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 le ra_num demandé (25 sauf si l'utilisateur le spécifie autrement). Pour une résolution SOCP avec cap de variance, rapportez result_row["solver"], variance réalisée, le var_limit demandé, et confirmez que la variance réalisée est au ou en dessous du cap. Pour un tableau de poids, développez results_df["weights"] en colonnes de ticker et incluez cash plus risk_aversion. Pour un backtesting, incluez mean portfolio return, sharpe, sortino, et max drawdown pour les portefeuilles optimisés et benchmark. Pour le rééquilibrage, incluez results_dataframe, re_optimize_dates, et la fin de cumulative_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_dict est None ou {"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). Le results_df retourné inclut des métriques, une colonne dict weights, et cash.
  • Portefeuille : Portfolio(name="", tickers=None, weights=None, cash=0.0, time_range=None) ; passez les tickers et un tableau plat de type array-like weights aligné à ces tickers.
  • Backtesting : créez des objets portfolio.Portfolio pour l'allocation optimisée et chaque benchmark ; pour un benchmark à poids égal, utilisez des poids de 1 / len(tickers) et cash=0.0, puis appelez backtest.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 que dataset_directory soit un chemin CSV, pas un DataFrame. Appelez re_optimize(...) ; il retourne (results_dataframe, re_optimize_dates, cumulative_portfolio_value).
  • Modèles de paramètres : ReturnsComputeSettings, ScenarioGenerationSettings, KDESettings, ApiSettings, CvarParameters, et MeanVarianceParameters.

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 CvarParameters long-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 avec api="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(...), retourner results_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 Portfolio optimisés et à poids égal, puis utiliser le backtester du package et rapporter Sharpe, Sortino, et max drawdown.
  • « Backtester le rééquilibrage mensuel » : configurer rebalance_portfolio avec le déclencheur de dérive ci-dessus et exécuter re_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-socp installe intentionnellement cuOpt sans cuML car cuml-cu13 26.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 avec download_data("data/stock_data", datasets=["sp500"]) ; exécutez-le uniquement après confirmation de l'utilisateur.
  • SolverError ou cp.CUOPT manquant : installez l'extra CUDA correspondant à l'hôte et vérifiez avec python -c "import cvxpy as cp; print(hasattr(cp, 'CUOPT'), cp.installed_solvers())".
  • ImportError pour cuml ou échecs GPU KDE : confirmez que cuML est présent avec python -c "import cuml" et gardez KDESettings(device="GPU"). Si vous utilisez cuda13-socp, c'est attendu pour CVaR/KDE ; basculez vers cuda12 ou cuda13 pour les workflows cuML.
  • L'installation SOCP échoue avant la résolution : vérifiez que le package cuopt installé est sur la ligne 26.06 ou plus récente et que MeanVarianceParameters.var_limit est positif.
  • L'optimisation ordinaire retourne toute la trésorerie : définissez c_max=0.0 dans CvarParameters.
  • 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.

Skills similaires