cufolio

Par nvidia · skills

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

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

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 cufolio installé.
  • Runtime GPU NVIDIA avec cuOpt et cuML installés.
  • Extension CUDA correspondant à l'hôte, comme uv sync --extra cuda12 ou uv sync --extra cuda13.
  • 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

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 :

  1. Chargez data/stock_data/sp500.csv ; s'il est manquant, demandez avant de télécharger sp500 avec cufolio.utils.download_data. Ne globalisez pas, ne substituez pas et ne fabriquez pas de données de prix.
  2. 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_dict n'accepte pas de champ ticker.
  3. Calculez les rendements LOG avec utils.calculate_returns(...).
  4. Générez des scénarios avec cvar_utils.generate_cvar_data(...), KDE et KDESettings(device="GPU").
  5. Définissez CvarParameters avec w_min et w_max explicites. Pour les demandes ordinaires « construire le portefeuille optimal », définissez c_min=0.0 et c_max=0.0 pour que le résultat soit entièrement investi au lieu de 100 % en espèces.
  6. 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.
  7. Résolvez avec NVIDIA cuOpt uniquement. Avant de résoudre, vérifiez hasattr(cp, "CUOPT") et str(cp.CUOPT) in {str(s) for s in cp.installed_solvers()}. Passez SOLVER_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.
  8. 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_max et 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.
  9. 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.
  10. 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.
  11. 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 le ra_num demandé (25 sauf si l'utilisateur en spécifie un autre). Pour un tableau de poids, développez results_df["weights"] en colonnes de ticker et incluez cash plus risk_aversion. Pour un backtest, incluez rendement du portefeuille moyen, sharpe, sortino et drawdown maximal pour les portefeuilles optimisés et de référence. Pour le rééquilibrage, incluez results_dataframe, re_optimize_dates et la queue de cumulative_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_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).
  • 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). Le results_df retourné inclut les métriques, une colonne dictionnaire weights et cash.
  • Portefeuille : Portfolio(name="", tickers=None, weights=None, cash=0.0, time_range=None) ; passez les tickers et un tableau weights plat aligné sur ces tickers.
  • Backtest : créez des objets portfolio.Portfolio pour l'allocation optimisée et chaque indice de référence ; pour un indice de référence de 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(...) exige 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 et CvarParameters.

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 CvarParameters long-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(...), retourner results_df et 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 Portfolio optimisés et poids égal, puis utiliser le backtester cuFOLIO et signaler Sharpe, Sortino et drawdown maximal.
  • « 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 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 avec download_data("data/stock_data", datasets=["sp500"]); exécutez-la seulement après confirmation de l'utilisateur.
  • SolverError ou cp.CUOPT manquant : installez l'extension 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 KDE GPU : confirmez que cuML est présent avec python -c "import cuml" et conservez KDESettings(device="GPU").
  • L'optimisation ordinaire retourne tout en espèces : définissez c_max=0.0 dans CvarParameters.
  • 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.

Skills similaires