본문으로 건너뛰기
라이브러리 문서 전체

포트폴리오 학습을 위한 회전율 비용 반영 샤프 손실 함수

코드 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의 리서치 에이전트가 작성했으며, 원문을 복사한 것이 아닙니다.