Skill cuFOLIO
<!-- 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 accélérée par NVIDIA. Utilisez cuFOLIO pour calculer les rendements, générer des scénarios KDE, résoudre les allocations avec le solveur GPU cuOpt, tracer une frontière efficace, backtester des portefeuilles et exécuter des workflows de rééquilibrage à partir de données de prix.
Quand l'utiliser
Utilisez ce skill quand la tâche est de :
- 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 à la baisse.
- Tracer ou inspecter une frontière efficace pour un univers de portefeuille.
- Produire un tableau de poids par aversion au risque.
- Backtester un portefeuille optimisé par rapport à des indices de référence.
- Rééquilibrer un portefeuille selon un calendrier ou un déclencheur de dérive.
- Exécuter des workflows sur un ensemble de données S&P 500, S&P 100, Dow 30 ou fourni par l'utilisateur.
Les phrases déclencheurs communes incluent « optimiser mon portefeuille », « construire un portefeuille CVaR », « utiliser cuFOLIO sur ces tickers », « résoudre avec cuOpt », « tracer la frontière efficace », « montrer les poids par aversion au risque », « backtester cette allocation », « rééquilibrer mensuellement », « analyser mes positions avec CVaR », « comparer les allocations », « réduire le risque à la baisse », « construire une allocation », « évaluer les options d'allocation », « stress-tester mes positions », « évaluer l'exposition au risque à la baisse », « revoir mes positions sous les limites de poids », « comparer les portefeuilles de référence », « simuler des scénarios CVaR », « dépister le risque du portefeuille », « optimiser les positions sous contraintes » et « trouver une allocation à risque plus faible ».
Ne l'utilisez pas pour des résumés financiers génériques, des prévisions de prix, l'entraînement de réseaux de neurones, le routage de véhicules ou l'optimisation de portefeuille non standard.
Conditions préalables
- Environnement Python avec le package
cufolioinstallé. - Runtime GPU NVIDIA avec cuOpt et cuML installés.
- Extension CUDA correspondant à l'hôte, comme
uv sync --extra cuda12ouuv sync --extra cuda13. 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
Ce skill pilote le package cufolio installé. Un environnement prêt peut provenir de la version Brev launchable ou de NVIDIA-AI-Blueprints/cuFOLIO après installation de l'extension CUDA correspondante.
Dans les sandbox packagés d'agent/eval, cufolio peut être disponible via PYTHONPATH plutôt que comme une wheel publiée séparément. Vérifiez le package local avec python -c "import cufolio" avant de le déclarer manquant. Ne faites pas pip install cufolio, ne réimplémentez pas les workflows cuFOLIO de zéro et ne remplacez pas les APIs du package par du code générique pandas/scipy/cvxpy.
Pour les détails d'implémentation concrets, utilisez references/workflows/agent_recipes.md comme source d'autorité. 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 contre le poids égal et appeler le rééquilibreur.
Le dataset par défaut est data/stock_data/sp500.csv. Il est dans gitignore. Avant un premier téléchargement, informez l'utilisateur que cela récupère des données de marché publiques via l'assistant de données cuFOLIO/yfinance et demandez-lui de confirmer :
import cvxpy as cp
from cufolio.cvar_parameters import CvarParameters
from cufolio.utils import download_data
download_data("data/stock_data", datasets=["sp500"])
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 paramètres par défaut appliqués avant l'exécution, puis utilisez ces garde-fous :
- Chargez
data/stock_data/sp500.csv; s'il est manquant, demandez avant de téléchargersp500aveccufolio.utils.download_data. Ne globalisez pas, ne substituez pas et ne fabriquez pas de données de prix. - Validez les CSV utilisateur avant de résoudre : exigez un index de type date ou une première colonne de date, des colonnes numériques de ticker, 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 signalez la plage de dates conservée. Filtrez les tickers sur le DataFrame de prix avant le calcul des rendements.
regime_dictn'accepte pas de champ ticker. - Calculez les rendements LOG avec
utils.calculate_returns(...). - Générez des scénarios avec
cvar_utils.generate_cvar_data(...), KDE etKDESettings(device="GPU"). - Définissez
CvarParametersavecw_minetw_maxexplicites. Pour les demandes ordinaires « construire le portefeuille optimal », définissezc_min=0.0etc_max=0.0pour que le résultat soit entièrement investi au lieu de 100 % en espèces. - Construisez
cvar_optimizer.CVaR(returns_dict, cvar_params)directement à partir de ce dictionnaire de rendements ; conservez les tickers, les tableaux de scénarios, les moyennes et la covariance dans les formes retournées par les assistants cuFOLIO. - Résolvez avec NVIDIA cuOpt uniquement. Avant de résoudre, vérifiez
hasattr(cp, "CUOPT")etstr(cp.CUOPT) in {str(s) for s in cp.installed_solvers()}. PassezSOLVER_SETTINGSà chaque résolution unique ou résolution de frontière en boucle. N'abandonnez jamais à CLARABEL, SCS, ECOS ou un autre solveur CPU. Si cuOpt est absent, terminez la validation/configuration et signalez que le runtime GPU/cuOpt manque au lieu de fabriquer un résultat CPU. - Pour les contraintes personnalisées, mappez les demandes utilisateur à
CvarParameters: les limites de poids àw_min/w_max, l'appétit de risque àrisk_aversion, le niveau de confiance àconfidence, l'allocation en espèces àc_maxet la cardinalité uniquement quand le package expose une contrainte explicite de nombre d'actifs pour le workflow. Si les contraintes entrent en conflit (par exemple, un poids maximal trop bas pour investir sur le nombre de tickers demandé), expliquez le conflit et demandez à la contrainte de se détendre au lieu de deviner. - Si l'utilisateur omet un indice de référence pour le backtesting, utilisez un portefeuille de poids égal sur les mêmes tickers. Si l'utilisateur omet une contrainte, conservez les valeurs de la table des paramètres par défaut et réitérez brièvement les hypothèses conséquentes avant de résoudre.
- Livrez les poids triés par allocation, poids d'espèces, rendement attendu, CVaR, étiquette du solveur (
cuOpt GPU) et tout chiffre de frontière, tableau de poids, métriques de backtest ou calendrier de rééquilibrage demandé. Pour les tableaux, incluez les tickers en colonnes ou lignes avec les poids décimaux et les pourcentages ; pour les tracés, conservez la figure cuFOLIO retournée au lieu de la redessiner de zéro. - Pour les réponses de niveau rapport, incluez des preuves que le workflow demandé a réellement fonctionné. Pour une frontière efficace, énoncez
len(results_df)et utilisez lera_numdemandé (25 sauf si l'utilisateur en spécifie un autre). Pour un tableau de poids, développezresults_df["weights"]en colonnes de ticker et incluezcashplusrisk_aversion. Pour un backtest, incluezrendement du portefeuille moyen,sharpe,sortinoetdrawdown maximalpour les portefeuilles optimisés et de référence. Pour le rééquilibrage, incluezresults_dataframe,re_optimize_dateset la queue decumulative_portfolio_value.
Squelette Workflow Canonique
Démarrez les tâches positives cuFOLIO à partir de cette forme et adaptez uniquement la sortie demandée. Pour les fonctions copyables complètes, lisez references/workflows/agent_recipes.md avant d'écrire du code personnalisé.
import cvxpy as cp
import pandas as pd
from cufolio import backtest, cvar_optimizer, cvar_utils, rebalance, utils
from cufolio.cvar_parameters import CvarParameters
from cufolio.portfolio import Portfolio
from cufolio.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.")
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=SOLVER_SETTINGS,
print_results=False,
)
Pour une frontière efficace ou un tableau de poids, appelez :
results_df, fig, ax = cvar_utils.create_efficient_frontier(
returns_dict,
cvar_params,
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 backtest d'indice de référence, enveloppez 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 de poids égal sur le même 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 vers 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 paramètres par défaut
| Paramètre | Par défaut |
|---|---|
| Dataset | data/stock_data/sp500.csv |
| Plage de dates | Plage complète disponible |
| Type de portefeuille | Long only |
| Poids maximal | Aucun sauf spécification |
| Aversion au risque | 1.0 |
| Confiance | 0.95 |
| Méthode de scénario | KDE sur GPU |
| Solveur | cuOpt GPU avec PDLP |
| Rééquilibrage | Aucun sauf demande |
Le fichier S&P 500 par défaut est un snapshot historique et peut omettre les constituants actuels. Les CSV fournis par l'utilisateur doivent être des tableaux de prix indexés par date avec des colonnes de ticker, compatibles avec utils.get_input_data. Si les tickers demandés sont absents, supprimez-les, signalez les omissions et continuez avec les colonnes disponibles sauf si l'utilisateur vous 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 du portefeuille ou les boucles de simulation. Les assistants cuFOLIO 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). - Optimiseur :
cvar_optimizer.CVaR(returns_dict, cvar_params). - Résoudre :
result_row, portfolio = cvar_problem.solve_optimization_problem(solver_settings=SOLVER_SETTINGS, print_results=False). - Frontière efficace :
cvar_utils.create_efficient_frontier(returns_dict, cvar_params, solver_settings=SOLVER_SETTINGS, ra_num=25). Leresults_dfretourné inclut les métriques, une colonne dictionnaireweightsetcash. - Portefeuille :
Portfolio(name="", tickers=None, weights=None, cash=0.0, time_range=None); passez les tickers et un tableauweightsplat aligné sur ces tickers. - Backtest : créez des objets
portfolio.Portfoliopour l'allocation optimisée et chaque indice de référence ; pour un indice de référence de 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(...)exige 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,ApiSettingsetCvarParameters.
Exemples
- « Construire le portefeuille optimal à partir du S&P 500 » : charger les prix, calculer les rendements LOG, générer des scénarios KDE GPU, définir les
CvarParameterslong-only entièrement investis, résoudre avec cuOpt et rapporter les poids diversifiés plus rendement/CVaR. - « Tracer la frontière efficace » : appeler
create_efficient_frontier(...), retournerresults_dfet afficher ou enregistrer la figure comme demandé. - « Donnez-moi les poids par aversion au risque » : développez
results_df["weights"]en un tableau par actif. - « Backtester contre poids égal » : construire les objets
Portfoliooptimisés et poids égal, puis utiliser le backtester cuFOLIO et signaler Sharpe, Sortino et drawdown maximal. - « 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 conteneurs eval CPU-only peuvent toujours valider le routage, la gestion des données et le comportement de rapportage, mais ils ne peuvent pas produire une résolution cuOpt valide. Dans ce cas, signalez 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 du 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 cuFOLIO récupérera les données de marché publiques avecdownload_data("data/stock_data", datasets=["sp500"]); exécutez-la seulement après confirmation de l'utilisateur. SolverErroroucp.CUOPTmanquant : installez l'extension CUDA correspondant à l'hôte et vérifiez avecpython -c "import cvxpy as cp; print(hasattr(cp, 'CUOPT'), cp.installed_solvers())".ImportErrorpourcumlou échecs KDE GPU : confirmez que cuML est présent avecpython -c "import cuml"et conservezKDESettings(device="GPU").- L'optimisation ordinaire retourne tout en espèces : définissez
c_max=0.0dansCvarParameters. - Le solveur signale infaisable ou pas de solution : vérifiez les limites contradictoires, trop peu de tickers pour les limites/cardinalité demandées ou un filtre de date qui laisse trop peu de données ; signalez le changement de contrainte le plus petit qui rendrait la demande faisable.
- Les tickers demandés sont absents du CSV par défaut : signalez-les et poursuivez 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 les dates et les colonnes restantes sont les prix numériques des tickers ; mentionnez la exigence minimale de 60 lignes après filtrage.