コンテンツへスキップ
ライブラリの全資料

ポートフォリオ学習のための売買回転コスト付きロバストシャープ損失

コード Machine Learning for Trading

サマリー

このモジュールでは、エンドツーエンドのポートフォリオ学習に使う微分可能な目的関数を定義します。ボラティリティ調整により、資産ごとの上限付きリスクウェイトをポートフォリオのエクスポージャーに変換し、利用可能な資産のグロスリターンを平均します。また、前期のウェイトからの変化に基づく取引コストを任意で差し引きます。リターン系列の評価から初期学習期間を除外できます。

この目的関数では、全期間をまとめたシャープレシオと期間ごとのシャープレシオのソフト最小値を、調整可能なパラメータで重み付けして組み合わせます。学習では組み合わせスコアの負値を最小化します。この構造は全体のリスク調整後パフォーマンスを重視するとともに、成績の弱い期間も考慮します。関数では、年率換算、数値安定化、コストのスケーリング、ソフト最小値の温度パラメータを設定できます。文書は実装の定義を示すもので、実証結果、較正方法、目的関数が実現パフォーマンスを改善する証拠はありません。その結果はウェイト、マスク、コスト、ハイパーパラメータの選択に依存します。

主なアイデア

  • 各時点で利用可能な資産について、リスクウェイトとボラティリティ調整からポートフォリオリターンを計算し、平均します。
  • 前のステップからのポートフォリオエクスポージャーの変化に対するペナルティとして、取引コストをモデル化できます。
  • 損失関数では、全期間のシャープレシオと期間単位のシャープレシオのソフト最小値を組み合わせます。
  • シャープレシオの計算からバーンイン期間の観測値を除外できます。
  • このモジュールは目的関数を定義しますが、実証的な証拠や推奨パラメータ値は示していません。

タグ

全文
# 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(),
    )

```

出典を明記したうえで、ライセンスに従って全文を掲載しています。 ライセンス: MIT

この要約は原文をもとにStratmillのリサーチエージェントが作成したもので、出典の複製ではありません。