Perte de Sharpe robuste intégrant les coûts de rotation pour l’apprentissage de portefeuille
Résumé
Ce module définit un objectif différentiable pour l’apprentissage de portefeuille de bout en bout. Il transforme des pondérations de risque limitées au niveau des actifs en expositions du portefeuille à l’aide d’un ajustement par la volatilité, calcule les rendements bruts moyens sur les actifs disponibles et soustrait éventuellement les coûts de transaction en fonction des variations par rapport aux pondérations précédentes. Une période de chauffe peut être exclue de l’évaluation de la série de rendements.
L’objectif combine un ratio de Sharpe global à un minimum souple des ratios de Sharpe par fenêtre, pondéré par un paramètre ajustable ; l’entraînement minimise l’opposé du score combiné. Cette structure récompense la performance ajustée au risque globale tout en tenant compte des fenêtres moins performantes. Les fonctions permettent de configurer l’annualisation, la stabilisation numérique, la mise à l’échelle des coûts et la température du minimum souple. Le document fournit les définitions de l’implémentation, mais aucun résultat empirique, aucune recommandation de calibration ni aucun élément indiquant que l’objectif améliore la performance réalisée ; les résultats dépendent donc des choix de pondérations, de masques, de coûts et d’hyperparamètres.
Idées clés
- Les rendements du portefeuille sont calculés à partir des pondérations de risque et de l’ajustement par la volatilité, puis moyennés sur les actifs disponibles à chaque pas de temps.
- Les coûts de transaction peuvent être modélisés comme une pénalité sur les variations de l’exposition du portefeuille par rapport au pas précédent.
- La fonction de perte combine le ratio de Sharpe global et un minimum souple des ratios de Sharpe par fenêtre.
- Les observations de la période de chauffe peuvent être exclues des calculs du ratio de Sharpe.
- Le module définit un objectif, mais ne fournit ni preuve empirique ni valeurs de paramètres recommandées.
Étiquettes
Texte intégral
# losses.py
```py
"""Loss functions for DeePM-style end-to-end portfolio learning."""
from __future__ import annotations
from dataclasses import dataclass
import torch
@dataclass(frozen=True, slots=True)
class LossOutput:
"""Outputs returned by the robust Sharpe loss."""
loss: torch.Tensor
sharpe_pool: torch.Tensor
softmin_sharpe: torch.Tensor
objective: torch.Tensor
net_returns: torch.Tensor
def compute_net_portfolio_returns(
*,
p: torch.Tensor,
y_fwd1: torch.Tensor,
vol_scale: torch.Tensor,
mask: torch.Tensor,
costs: torch.Tensor | None,
gamma_cost: float,
) -> torch.Tensor:
"""Compute net portfolio returns (Eq. 13).
Parameters
----------
p: Risk weights in (-1, 1), shape (B, T, N).
y_fwd1: Vol-scaled forward returns, shape (B, T, N).
vol_scale: Volatility scaling, shape (B, T, N).
mask: Availability mask, shape (B, T, N).
costs: Per-asset cost coefficients, shape (N,) or (N, 1). None to skip.
gamma_cost: Global cost scaling factor.
Returns
-------
Net portfolio return series, shape (B, T).
"""
b, t, n = p.shape
w = vol_scale * p
w_prev = torch.cat(
[torch.zeros((b, 1, n), device=w.device, dtype=w.dtype), w[:, :-1, :]], dim=1
)
gross = (mask * p * y_fwd1).sum(dim=-1)
n_t = mask.sum(dim=-1).clamp(min=1.0)
gross = gross / n_t
if costs is None:
return gross
if (costs.ndim == 2 and costs.shape[1] == 1) or (costs.ndim == 1 and costs.shape[0] == n):
c = costs.view(1, 1, n)
else:
raise ValueError("costs must have shape (N,) or (N,1)")
turnover = torch.abs(w - w_prev)
cost = (mask * c * turnover).sum(dim=-1)
cost = (gamma_cost * cost) / n_t
return gross - cost
def sharpe_ratio(
returns: torch.Tensor,
*,
annualization_factor: float,
eps: float,
dim: int | None = None,
) -> torch.Tensor:
"""Compute a differentiable Sharpe ratio."""
if dim is None:
mu = returns.mean()
var = returns.var(unbiased=False)
else:
mu = returns.mean(dim=dim)
var = returns.var(dim=dim, unbiased=False)
return (annualization_factor**0.5) * mu / torch.sqrt(var + eps)
def softmin_sharpe(
window_sharpes: torch.Tensor,
*,
tau: float,
) -> torch.Tensor:
"""Soft minimum of window-wise Sharpe ratios (Eq. 33)."""
return -tau * torch.log(torch.mean(torch.exp(-window_sharpes / tau)))
def robust_sharpe_loss(
*,
p: torch.Tensor,
y_fwd1: torch.Tensor,
vol_scale: torch.Tensor,
mask: torch.Tensor,
costs: torch.Tensor | None,
burn_in: int,
gamma_cost: float,
annualization_factor: float,
eps: float,
tau: float,
lambda_soft: float,
) -> LossOutput:
"""Compute DeePM robust objective loss (Eq. 31).
L(theta) = - SR_pool(R) - lambda * SoftMin_tau({SR_b}).
"""
net_r = compute_net_portfolio_returns(
p=p,
y_fwd1=y_fwd1,
vol_scale=vol_scale,
mask=mask,
costs=costs,
gamma_cost=gamma_cost,
)
net_r_eff = net_r[:, burn_in:] if burn_in > 0 else net_r
sr_pool = sharpe_ratio(
net_r_eff.reshape(-1), annualization_factor=annualization_factor, eps=eps
)
sr_windows = sharpe_ratio(net_r_eff, annualization_factor=annualization_factor, eps=eps, dim=1)
sr_softmin = softmin_sharpe(sr_windows, tau=tau)
objective = sr_pool + lambda_soft * sr_softmin
loss = -objective
return LossOutput(
loss=loss,
sharpe_pool=sr_pool.detach(),
softmin_sharpe=sr_softmin.detach(),
objective=objective.detach(),
net_returns=net_r.detach(),
)
```Reproduit dans son intégralité avec attribution, conformément à la licence de la source. Licence: MIT
Ce résumé a été rédigé par l’agent de recherche de Stratmill à partir de la source originale ; il n’en est pas une copie.