写在前面
- 文章标题:FactorVAE: A Probabilistic Dynamic Factor Model Based on Variational Autoencoder for Predicting Cross-sectional Stock Returns
- 论文链接:【1】
- 部分Pytorch复现代码链接:【2】 【3】
- 部分Tensorflow复现代码链接:【4】
- 仅作个人学习记录用
1. 摘要
因子模型作为经济学和金融学中的一种资产定价模型,在量化投资中得到了广泛的应用。为了构建更有效的因子模型,近年来见证了从线性模型到更灵活的非线性数据驱动的机器学习模型的范式转变。然而,由于金融数据的信噪比较低,学习有效因子模型具有很大的挑战性。本文提出了一种新的因子模型FactorVAE,它是一种具有固有随机性的概率模型。该模型将机器学习中的动态因子模型(DFM)与变分自编码器(VAE)相结合 ,提出了一种基于变分自编码器(VAE)的先验-后验学习方法,通过逼近具有未来信息的最优后验因子模型,有效地指导了模型的学习。特别是,考虑到风险建模对于有噪声的股票数据的重要性,除了预测收益外,FactorVAE还可以从VAE的潜在空间分布中估计方差。在实际股票市场数据上的实验证明了因子vae的有效性,优于各种基线方法。
2. 引言
本文提出了一种新的基于变分自编码器(VAE)的概率动态因子模型,称为FactorVAE,以弥合噪声数据与有效因子之间的差距。本质上,我们将因子视为VAE中的潜在随机变量,通过VAE在潜在空间上的分布对数据中的噪声进行建模,然后引入了一种先验-后验学习方法来指导有效因素的提取,以预测横截面回转。更具体地说,如图1所示,我们首先采用一个编码器-解码器架构,可以访问未来的股票收益,以提取重建收益的最佳因素,然后训练一个预测器,仅给定可观察的历史数据,来预测因素以近似最佳因素。在预测阶段,只使用预测器和解码器,没有任何未来的信息泄漏。还需要注意的是,我们的模型通过随机因素来计算股票收益,除了预测收益外,还衍生出一个估计风险的概率模型。
本文贡献如下:
- 提出FactorVAE作为动态因子模型,从有噪声的市场数据中提取有效因子,并设计了基于VAE的先验后验学习方法,进一步指导模型在高噪声市场数据中的学习。
- 据我们所知,我们是第一个将因素作为VAE中的潜在随机变量,这增强了对噪声数据建模的能力,并导出了风险估计的概率模型。
- 我们对真实股票市场数据进行了大量的实验,结果表明我们的模型不仅优于其他动态因子模型。
3. 问题定义
输入:一组样本
[
(
x
s
,
y
s
)
]
\left[ (x_s, y_s) \right]
[(xs,ys)],其中
x
s
∈
R
N
s
×
T
×
C
x_s∈R^{N_s×T ×C}
xs∈RNs×T×C为股票的序列特征,
y
s
∈
R
N
s
y_s∈R^{N_s}
ys∈RNs为横断面股票的未来收益。其中
N
s
N_s
Ns为时间步长
s
s
s的横截面上的股票数量(只考虑所有
T
T
T个时间步长横截面上存在的股票),
C
C
C为特征数量。
输出:如公式
y
^
s
=
f
(
x
s
;
Θ
)
=
α
(
x
s
)
+
β
(
x
s
)
z
(
x
s
)
\hat{y}_s=f(x_s;\Theta)=\alpha(x_s)+\beta(x_s)z(x_s)
y^s=f(xs;Θ)=α(xs)+β(xs)z(xs) 所示的动态因子模型,它输出预测结果
y
^
s
\hat{y}_s
y^s。
4. 模型
FactorVAE模型遵循VAE的Encoder-Decoder架构,在此基础上学习一个最优因子模型,该模型可以很好地由多个因子重构股票的横截面收益。如图3所示,在获得未来股票收益的情况下,编码器扮演了一个预言者的角色,它可以从未来的数据中提取最优因素,称为后验因素,然后解码器通过后验因素重建未来的股票收益。特别地,模型中的因素被视为VAE的潜在变量,具有对噪声数据建模的能力。
在对模型进行介绍的同时附上了我修改过的对应部分的 Yaotian Liu 所复现的本文模型的Pytorch代码.
4.1 特征提取器(Feature Extractor)
特征提取器使用GRU从历史序列特征
x
x
x中提取股票潜在特征
e
e
e:
代码如下:
class FeatureExtractor(nn.Module):
def __init__(
self,
time_span,
characteristic_size,
latent_size,
stock_size,
gru_input_size,
):
super(FeatureExtractor, self).__init__()
self.time_span = time_span #(int): T of data.
self.characteristic_size = characteristic_size #(int): Size of characteristic.
self.stock_size = stock_size #(int): Num of stocks.
self.gru_input_size = gru_input_size #(int): Size of a hidden layers of GRU input.
self.latent_size = latent_size #(int): Size of latent features.
self.proj = MLP(
input_size=characteristic_size,
output_size=gru_input_size,
hidden_size=32,
activation=nn.LeakyReLU(),
out_activation=nn.LeakyReLU(),
)
self.gru = nn.GRU(input_size=gru_input_size, hidden_size=latent_size)
def forward(self, x):
assert x.shape[1:] == (
self.time_span,
self.stock_size,
self.characteristic_size
), "input shape incorrect"
x = torch.permute(x, (1, 0, 2, 3)).reshape(self.time_span, -1, self.characteristic_size)
h_proj = self.proj(x)
out, hidden = self.gru(h_proj)
# e is the latent features of stock with the shape of (1, stock_size, latent_size)
e = hidden.view((-1, self.stock_size, self.latent_size))
return e
4.2 因子编码器(Factor Encoder)
因子编码器从未来股票收益率
y
y
y和潜在特征
e
e
e中提取后验因子
z
p
o
s
t
z_{post}
zpost:
其中
z
p
o
s
t
z_{post}
zpost是一个遵循独立高斯分布的随机向量,可以用均值
μ
p
o
s
t
∈
R
K
\mu_{post}∈R^K
μpost∈RK和标准差
σ
p
o
s
t
∈
R
K
\sigma_{post}∈R^K
σpost∈RK来描述,
K
K
K是因子的数量。
φ
e
n
c
φ_{enc}
φenc的架构如上图(a)所示。由于横截面上的个股数量很大且随时间而变化,因此不直接使用股票收益
y
y
y,而是根据(Gu, Kelly, and Xiu 2021)的启发构建了一组投资组合,这些投资组合根据股票潜在特征动态重新加权,即
y
p
=
y
⋅
ϕ
p
(
e
)
=
y
⋅
a
p
y_p = y·ϕ_p(e) = y·a_p
yp=y⋅ϕp(e)=y⋅ap,其中
a
p
∈
R
M
a_p∈R^M
ap∈RM表示
M
M
M个投资组合的权重。公式如下:
其中
a
p
(
i
,
j
)
a^{(i,j)}_p
ap(i,j)表示第j个投资组合中第i只股票的权重,
y
p
∈
R
M
y_p∈R^M
yp∈RM是投资组合收益的向量。构建投资组合的主要优点在于:1)减少了输入维度,避免了过多的参数。2)对横截面上缺失的股票具有鲁棒性,因此适合于市场。然后后验因子的均值和标准差通过映射层
[
µ
p
o
s
t
,
σ
p
o
s
t
]
=
ϕ
m
a
p
(
y
p
)
[µ_{post}, σ_{post}] = ϕmap(y_p)
[µpost,σpost]=ϕmap(yp)输出,即:
其中 Softplus(x) = log(1 + exp(x)))。(Softplus函数可以看作是ReLU函数的平滑)
代码如下:
class FactorEncoder(nn.Module):
def __init__(self, latent_size, stock_size, factor_size, hidden_size=32):
super(FactorEncoder, self).__init__()
self.portfolio_layer = PortfolioLayer(latent_size, stock_size, hidden_size)
self.mapping_layer = MappingLayer(stock_size, factor_size, hidden_size)
def forward(self, latent_features, future_returns):
portfolio_weights = self.portfolio_layer(latent_features)
portfolio_returns = portfolio_weights * future_returns
mu_post, sigma_post = self.mapping_layer(portfolio_returns)
mu_post = mu_post.unsqueeze(-1)
sigma_post = sigma_post.unsqueeze(-1)
return mu_post, sigma_post
class MappingLayer(nn.Module):
def __init__(self, stock_size, factor_size, hidden_size=16):
super(MappingLayer, self).__init__()
self.mu_net = MLP(
input_size=stock_size,
output_size=factor_size,
hidden_size=hidden_size,
activation=nn.LeakyReLU(),
out_activation=nn.LeakyReLU()
)
self.sigma_net = MLP(
input_size=stock_size,
output_size=factor_size,
hidden_size=hidden_size,
activation=nn.LeakyReLU(),
out_activation=nn.Softplus()
)
def forward(self, portfolio_returns):
mu_post = self.mu_net(portfolio_returns) #portfolio_returns。shape = (batch_size, stock_size)
sigma_post = self.sigma_net(portfolio_returns)
return mu_post, sigma_post
class PortfolioLayer(nn.Module):
def __init__(self, latent_size, stock_size, hidden_size=32):
super(PortfolioLayer, self).__init__()
self.net = MLP(
input_size=latent_size,
output_size=1,
hidden_size=hidden_size
)
def forward(self, latent_features):
out = self.net(latent_features)
out = torch.softmax(out, dim=1).squeeze(-1) #out.shape = (batch_size, stock_size)
return out
4.3 因子解码器(Factor Decoder)
因子解码器使用因子 z z z和潜在特征 e e e来计算股票收益率: y ^ = φ d e c ( z , e ) = α + β z \hat{y} = φ_{dec}(z, e) = α + βz y^=φdec(z,e)=α+βz。
本质上,解码器网络 φ d e c φ_{dec} φdec由Alpha层和Beta层组成,如4.2的图(b)所示。
4.3.1 Alpha层
Alpha层从潜在特征
e
e
e输出特质返回
α
α
α。假设
α
α
α是由
α
∼
N
(
μ
α
,
d
i
a
g
(
σ
α
2
)
)
α \sim N (μ_α,diag (σ^2_α))
α∼N(μα,diag(σα2))描述的高斯随机向量,其中平均值
μ
α
∈
R
N
μ_α∈R^N
μα∈RN和标准差
σ
α
∈
R
N
σ_α∈R^N
σα∈RN是一个分布网络
π
a
l
p
h
a
\pi_{alpha}
πalpha的输出。公式如下:
其中
h
α
(
i
)
h_α^{(i)}
hα(i)是隐藏状态。
代码如下:
class AlphaLayer(nn.Module):
def __init__(self, latent_size, h_size, hidden_size, stock_size):
super(AlphaLayer, self).__init__()
self.h_size = h_size
self.stock_size = stock_size
self.hidden_layer = MLP(
input_size=latent_size,
output_size=h_size,
hidden_size=hidden_size,
activation=nn.LeakyReLU(),
out_activation=nn.LeakyReLU(),
)
self.mu_alpha_layer = MLP(
input_size=h_size,
output_size=1,
hidden_size=hidden_size,
activation=nn.LeakyReLU(),
out_activation=nn.LeakyReLU(),
)
self.sigma_alpha_layer = MLP(
input_size=h_size,
output_size=1,
hidden_size=hidden_size,
activation=nn.LeakyReLU(),
out_activation=nn.Softplus(),
)
def forward(self, latent_features):
# latent_features.shape = (batch_size, stock_size, latent_size)
hidden_state = self.hidden_layer(latent_features)
mu_alpha = self.mu_alpha_layer(hidden_state)
sigma_alpha = self.sigma_alpha_layer(hidden_state)# (batch_size, stock_size, 1)
return mu_alpha, sigma_alpha
4.3.2 Beta层
Beta层通过线性映射,从潜在的特征 e e e中得到数据,然后由Beta层计算因子暴露 β ∈ R N × K \beta∈R^{N×K} β∈RN×K。
其中 β ( i ) = w β e ( i ) + b β \beta^{(i)}=w_{\beta}e^{(i)}+b_{\beta} β(i)=wβe(i)+bβ。
α
\alpha
α和
z
z
z都遵循独立的高斯分布,因此解码器的输出
y
^
∼
N
(
μ
y
(
i
)
,
σ
y
(
i
)
)
\hat{y} \sim N (μ_y^{(i)},σ_y^{(i)})
y^∼N(μy(i),σy(i))。其中:
代码如下:
class BetaLayer(nn.Module):
def __init__(self, latent_size, stock_size, factor_size, hidden_size=64):
super(BetaLayer, self).__init__()
self.factor_size = factor_size
self.stock_size = stock_size
self.beta_layer = MLP(
input_size=latent_size,
output_size=factor_size,
hidden_size=hidden_size,
activation=nn.LeakyReLU(),
out_activation=nn.LeakyReLU(),
)
def forward(self, latent_features):
# (bs, stock_size, latent_size) -> (bs, stock_size, factor_size)
beta = self.beta_layer(latent_features)
return beta
4.4 先验-后验学习(Prior-Posterior Learning)
如前所述,论文目标是弥合嘈杂的市场数据和预测收益的有效因子模型之间的差距。用端到端方法训练的模型可能无法从噪声数据中提取有效因子。为此,本文提出了一种基于VAE的先验-后验学习方法来实现这一目标:在给定历史观测数据的情况下,训练一个因子预测器,该因子预测器可以近似上述最优后验因子,称为先验因子。然后,使用因子解码器在没有任何未来信息的情况下,通过先前因子计算股票收益。
4.4.1 因子预测器(Factor Predictor)
因子预测器从股票潜在特征
e
e
e中提取先验因子
z
p
r
i
o
r
z_{prior}
zprior:
考虑到一个因素通常代表市场上某一类型的风险溢价(如规模因子关注小盘股的风险溢价),设计了多头全局注意力机制,将市场的多种全局表征并行整合,从中提取代表市场不同风险溢价的因素,如图所示。单头注意力公式如下:
多头注意力
h
m
u
l
t
i
h_{multi}
hmulti是将
K
K
K个单头串联起来,即多重全局表征(the multi-global representation)。
然后使用分布网络 π p r i o r \pi_{prior} πprior来预测先验因子的均值 μ p r i o r μ_{prior} μprior和标准差 σ p r i o r σ_{prior} σprior。
代码如下:
class FactorPredictor(nn.Module):
def __init__(self, latent_size, factor_size, stock_size):
super(FactorPredictor, self).__init__()
self.multi_head_attention = nn.MultiheadAttention(latent_size, factor_size)
self.distribution_network_mu = MLP(
input_size=stock_size * latent_size, output_size=factor_size, hidden_size=64
)
self.distribution_network_sigma = MLP(
input_size=stock_size * latent_size,
output_size=factor_size,
hidden_size=64,
out_activation=nn.Softplus(),
)
def forward(self, latent_features):
h = self.multi_head_attention(latent_features, latent_features, latent_features)[0]
h = h.reshape(h.shape[0], -1) # concatenate
mu_prior = self.distribution_network_mu(h).unsqueeze(-1)
sigma_prior = self.distribution_network_sigma(h).unsqueeze(-1) # (bs, factor_size, 1)
return mu_prior, sigma_prior
4.4.2 目标函数(Objective Function)
目标函数包括两部分,第一部分是训练最优的后验因子模型,第二部分是通过后验因子有效地指导因子预测器的学习。因此,模型的损失函数为:
这里的第一损失项是负对数似然,以减少后验因子模型的重建误差,
y
^
r
e
c
(
i
)
\hat{y}^{(i)}_{rec}
y^rec(i)是第i只股票的重建收益(reconstructed return)。第二个损失项是先验因子和后验因子分布之间的Kullback-Leibler散度(KLD),为了使先验因子近似于后验因子,
γ
γ
γ为KLD损失的权重。
代码如下(本文并没有明确gamma的具体值):
def run_model(self, characteristics, future_returns, gamma=1):
latent_features = self.feature_extractor(characteristics) # (batch_size, stock_size, latent_size)
mu_post, sigma_post = self.factor_encoder(latent_features, future_returns) # (batch_size, factor_size)
m_encoder = Normal(mu_post, sigma_post)
factors_post = m_encoder.sample() # (batch_size, factor_size, 1)
reconstruct_returns, mu_alpha, sigma_alpha, beta = self.factor_decoder(
factors_post, latent_features
)
mu_dec, sigma_dec = self.get_decoder_distribution(
mu_alpha, sigma_alpha, mu_post, sigma_post, beta
)
loss_negloglike = (
Normal(mu_dec, sigma_dec).log_prob(future_returns.unsqueeze(-1)).sum()
)
loss_negloglike = loss_negloglike * (-1 / (self.stock_size * latent_features.shape[0]))
# latent_features.shape[0] is the batch_size
mu_prior, sigma_prior = self.factor_predictor(latent_features)
m_predictor = Normal(mu_prior, sigma_prior)
loss_KL = kl_divergence(m_encoder, m_predictor).sum()
loss = loss_negloglike + gamma * loss_KL
return loss
4.5 预测(Prediction)
常规的预测方法,公式如下:
其中预测的回报率
y
^
p
r
e
d
(
i
)
\hat{y}_{pred}^{(i)}
y^pred(i)遵循高斯分布,其中均值代表第i只股票的预期收益,标准差可用作衡量投资风险(实验三)。
代码如下:
def prediction(self, characteristics):
with torch.no_grad():
latent_features = self.feature_extractor(characteristics)
mu_prior, sigma_prior = self.factor_predictor(latent_features)
m_prior = Normal(mu_prior, sigma_prior)
factor_prior = m_prior.sample()
pred_returns, mu_alpha, sigma_alpha, beta = self.factor_decoder(
factor_prior, latent_features
)
mu_dec, sigma_dec = self.get_decoder_distribution(
mu_alpha, sigma_alpha, mu_prior, sigma_prior, beta
)
return pred_returns, mu_dec, sigma_dec
5. 实验
本部分主要包括三个实验,分别对应以下三个问题:
- RQ1:先验后验学习方法是否有效地指导了模型的学习?
- RQ2:我们的模型对在训练阶段从未学习过的缺失股票是否稳健?
- RQ3:我们模型的风险估计对股票投资有什么帮助?
5.1 实验数据
本文使用A股数据,使用Qlib平台的Alpha158数据作为股票输入的特征,而非作为是因子。文章将2010-01-01至2017-12-31的数据用作训练,将2018-01-01至2018-12-31用作验证,将2019-01-01至2020-12-31用作测试。
比较的基线模型包括动态因子模型和基于机器学习的预测模型:Linear、CA、GRU、ALSTM、GAT、Transformers、SFM。
5.2 实验一:截面回报预测(Cross-Sectional Returns Prediction)
采用等级信息系数(Rank IC)作为衡量标准,与基线模型相比较,结果如下:
该表总结了测试数据集上性能的全面比较,从中我们可以得出以下观察结果:
- 在所有比较的方法中,FactorVAE的性能最好,这说明了所提出方法的有效性。
- FactorVAE -prior是模型的一个变体,没有先验-后验学习方法,它被训练成直接通过先验因素预测收益。从结果中可以看出,如果没有后验因素的指导,很难从真实的市场数据中学习到有效的因素模型,这表明先验-后验学习方法对我们的模型至关重要(RQ1)。
5.3 实验二:鲁棒性(Robustness)
在这个实验中评估了模型对训练数据集中缺失股票的鲁棒性。具体来说,随机剔除m只股票,并使用新的训练数据集来训练模型。然后对测试数据集
D
t
e
s
t
D_{test}
Dtest上的股票收益进行预测,并选择这m只股票的预测收益率。最后,通过计算股票集S在测试数据集上的Rank IC和Rank ICIR来评估模型在缺失股票上的表现。表中列出了5个随机种子下不同缺失股票数量的结果:
由表可得,FactorVAE在所有m上都优于其他基准方法,这表明模型对以前从未学习过的股票(RQ2)具有更强的鲁棒性,因此适合于真实市场的情况(例如预测新发行股票的收益)。
5.4 实验三:组合投资(Portfolio Investment)
在本实验中,基于模型的预测构建投资组合,并通过回测比较它们的表现。采用TopK-Drop策略,在每个交易日维持投资组合。形式上,在第t个交易日,TopK-Drop构建了k只股票的等权重投资组合
P
t
P_t
Pt,在交叉股票数量
P
t
∩
P
t
−
1
≥
k
−
n
P_t∩P_{t−1}≥k−n
Pt∩Pt−1≥k−n的周转约束下,根据预测收益排序选择的交叉股票,实验中我们设置k = 50, n = 5。沪深300指数是中国a股市场广泛使用的基准指数,由300只规模最大、流动性最强的A股股票组成,旨在反映市场的整体表现。因此,我们以沪深300指数为基准,在每个交易日从沪深300成分股中选取50只股票构建投资组合。在实验中,我们采用严格的回测程序来模拟真实市场,其中我们考虑了a股市场的交易费、停牌和涨停板。对比方法的组合表现如图6所示,其中(a)显示了方法的累积超额收益(相对于沪深300指数),(b)显示了方法的累积收益(相对于绝对收益)。同时测量年化收益率(AR)、夏普比率(SR)和累积超额收益的最大回收量(MDD),总结如表所示。
从回测结果可以看出:TDrisk是考虑风险厌恶的TopK-Drop的变体,它根据风险调整后的收益 µ p r e d ( i ) − η σ p r e d ( i ) µ^{(i)} _{pred}−ησ^{(i)}_{pred} µpred(i)−ησpred(i)选择k只股票,其中 η η η为风险厌恶权重。结合TDrisk,模型进一步提高了投资组合的AR和SR,显示了风险估计在投资中的有效性(RQ3)。