跳至正文
返回文库全部文档

基于特征的资产定价条件自编码器

代码 《交易机器学习》

总结

该实现介绍用于资产定价的条件自编码器。Beta 网络将每只股票的特征映射为潜在因子载荷,线性因子网络则将管理型投资组合收益映射为因子收益。模型通过配对的载荷与因子值乘积之和预测股票收益,因此风险敞口可随公司特征变化,而因子则由投资组合工具推导。

代码还提供已学习载荷和因子的访问器,可用于检查模型组件。其 L1 惩罚仅作用于 Beta 网络隐藏层的权重,不包括偏置和最终载荷层;若没有隐藏层,则返回零。这是架构参考而非实证研究:其中没有训练流程、数据集、预测结果,也未与其他资产定价模型比较。

核心观点

  • Beta 网络将股票特征转换为特定于股票的潜在因子载荷。
  • 线性网络将管理型投资组合输入映射为因子收益。
  • 预测收益由载荷与因子乘积之和构成。
  • 正则化器鼓励 Beta 网络隐藏层权重稀疏,同时不惩罚输出层。
  • 该实现定义了模型结构,但没有提供实证评估。

标签

全文
# cae.py


```py
"""Conditional Autoencoder for Asset Pricing (GKX 2021).

Architecture:


- ConditionalAutoencoder: predicted return = dot(betas, factors)

Reference: Gu, Kelly, Xiu (2021) "Autoencoder Asset Pricing Models"
"""

from __future__ import annotations

import torch
import torch.nn as nn


class BetaNetwork(nn.Module):
    """Maps stock characteristics to per-stock factor loadings."""

    def __init__(self, n_characteristics: int, n_factors: int, hidden_units: tuple = (32,)):
        super().__init__()

        layers: list[nn.Module] = []
        in_features = n_characteristics

        for units in hidden_units:
            layers.append(nn.Linear(in_features, units))
            layers.append(nn.ReLU())
            in_features = units

        layers.append(nn.Linear(in_features, n_factors))
        self.network = nn.Sequential(*layers)

    def forward(self, x: torch.Tensor) -> torch.Tensor:
        return self.network(x)


class FactorNetwork(nn.Module):
    """Maps managed portfolio returns to factor returns (linear, no activation)."""

    def __init__(self, n_instruments: int, n_factors: int):
        super().__init__()
        self.linear = nn.Linear(n_instruments, n_factors, bias=False)

    def forward(self, x: torch.Tensor) -> torch.Tensor:
        return self.linear(x)


class ConditionalAutoencoder(nn.Module):
    """Conditional Autoencoder for Asset Pricing (GKX Architecture).

    Architecture:
    - Beta Network: characteristics -> ReLU -> factor loadings (with L1 regularization)
    - Factor Network: managed portfolios -> factor returns (linear)
    - Output: dot product of betas and factors

    Args:
        n_characteristics: Number of stock characteristics (L)
        n_instruments: Number of managed portfolio instruments (L+1 typically)
        n_factors: Number of latent factors (K)
        hidden_units: Tuple of hidden layer sizes for BetaNetwork
    """

    def __init__(
        self,
        n_characteristics: int,
        n_instruments: int,
        n_factors: int = 6,
        hidden_units: tuple = (32,),
    ):
        super().__init__()

        self.n_factors = n_factors
        self.n_characteristics = n_characteristics
        self.n_instruments = n_instruments

        self.beta_net = BetaNetwork(n_characteristics, n_factors, hidden_units)
        self.factor_net = FactorNetwork(n_instruments, n_factors)

    def forward(self, characteristics: torch.Tensor, portfolios: torch.Tensor) -> torch.Tensor:
        """Forward pass.

        Args:
            characteristics: (batch_size, n_characteristics) per-stock
            portfolios: (batch_size, n_instruments) managed portfolio features

        Returns:
            predicted_returns: (batch_size,)
        """
        betas = self.beta_net(characteristics)  # (batch, n_factors)
        factors = self.factor_net(portfolios)  # (batch, n_factors)
        pred = (betas * factors).sum(dim=1)
        return pred

    def get_betas(self, characteristics: torch.Tensor) -> torch.Tensor:
        """Extract factor loadings."""
        return self.beta_net(characteristics)

    def get_factors(self, portfolios: torch.Tensor) -> torch.Tensor:
        """Extract factor returns."""
        return self.factor_net(portfolios)


def l1_regularization(model: ConditionalAutoencoder, lambda_l1: float) -> torch.Tensor:
    """L1 regularization on BetaNetwork hidden Dense weights only.

    Matches the GKX (2021) reference, which applies ``kernel_regularizer='L1'``
    only to the *hidden* Dense layers. We exclude biases (sparsity is not
    meaningful) and the final output ``Linear(in, n_factors)`` layer (factor
    loadings should not be pushed to zero by an external regularizer — the
    autoencoder itself learns their magnitudes).
    """
    layers = [m for m in model.beta_net.network if isinstance(m, nn.Linear)]
    if len(layers) <= 1:
        # No hidden layers (CA0 — direct linear projection); no L1 to apply.
        return torch.zeros((), device=next(model.parameters()).device)
    hidden_layers = layers[:-1]  # exclude the final output Linear
    l1 = sum(layer.weight.abs().sum() for layer in hidden_layers)
    return lambda_l1 * l1

```

在遵守原作品许可的前提下,附作者信息全文展示。 许可协议: MIT

此摘要由 Stratmill 研究智能体根据原文撰写,并非原文副本。