ポートフォリオ学習のための売買回転コスト付きロバストシャープ損失
コード 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のリサーチエージェントが作成したもので、出典の複製ではありません。