Source code for fynance.models.loss.sharpe

#!/usr/bin/env python3
# coding: utf-8

""" Differentiable Sharpe ratio loss for PyTorch training loops. """

from __future__ import annotations

# Third-party packages
import torch

# Local packages
from ._base import BaseLoss

__all__ = ['SharpeLoss']


[docs] class SharpeLoss(BaseLoss): r""" Negative Sharpe ratio as a differentiable loss. Minimizing this loss is equivalent to maximizing the Sharpe ratio of the predicted return series. All operations are pure PyTorch, so gradients flow through ``forward`` without any NumPy conversion. Notes ----- The loss is defined as: .. math:: \mathcal{L} = -\frac{\mu(r - rf_p)}{\sigma(r - rf_p) + \varepsilon} where :math:`r` is ``y_pred``, :math:`rf_p = rf / period` is the per-period risk-free rate, :math:`\mu` and :math:`\sigma` are the mean and population standard deviation (``correction=0``, consistent with :func:`~fynance.metrics.sharpe`), and :math:`\varepsilon` is the numerical stabilizer (``eps``). **This is a training proxy** — the value is not comparable to the numpy :func:`~fynance.metrics.sharpe` evaluation metric, which annualizes over a price series. Parameters ---------- rf : float, optional Annualized risk-free rate. Default is 0. period : int, optional Number of periods per year. Default is 252. eps : float, optional Numerical stabilizer. Default is 1e-8. Examples -------- >>> import torch >>> from fynance.models.loss import SharpeLoss >>> torch.manual_seed(0) <...> >>> returns = torch.randn(50) * 0.01 + 0.001 >>> loss_fn = SharpeLoss() >>> loss = loss_fn(returns) >>> loss.shape torch.Size([]) >>> loss.item() < 0 # positive mean excess return → negative loss True Using with :class:`~fynance.models.mlp.MultiLayerPerceptron`: >>> from fynance.models import MultiLayerPerceptron, SharpeLoss >>> import torch.optim >>> X = torch.randn(100, 4) >>> y = torch.randn(100, 1) >>> model = MultiLayerPerceptron(X, y, layers=[16]) >>> _ = model.set_optimizer(SharpeLoss, torch.optim.Adam, lr=1e-3) >>> loss = model.train_on(X, y) >>> loss.shape torch.Size([]) See Also -------- SortinoLoss, DirectionalAccuracyLoss """
[docs] def forward( self, y_pred: torch.Tensor, y_true: torch.Tensor | None = None, ) -> torch.Tensor: """ Compute the negative Sharpe ratio. Parameters ---------- y_pred : torch.Tensor Predicted return series, shape ``(T,)`` or ``(T, M)``. y_true : torch.Tensor, optional Not used; accepted for API compatibility with PyTorch criterions. Returns ------- torch.Tensor Scalar loss value (negative Sharpe ratio). Raises ------ TypeError If ``y_pred`` is not a :class:`torch.Tensor`. """ self._check_tensor(y_pred) excess = y_pred - self._rf_per_period return -(excess.mean() / (excess.std(correction=0) + self.eps))