10. 投资组合优化实战

10.投资组合优化实战

投资组合优化是量化交易中一个经典而复杂的问题。与前几章讨论的单股票或多股票交易策略不同,这里的核心挑战不再是判断买卖时机,而是如何在多个资产之间动态分配资金权重。想象手里有一笔钱,面对几十只股票的备选池,每天需要决定每只股票配置多少比例的资金,这本质上是一个连续的决策过程。

传统方法如马科维茨的均值-方差模型(MPT)依赖历史收益率和协方差矩阵,计算静态最优权重。但市场瞬息万变,静态配置难以适应动态环境。深度强化学习提供了一种新思路:将组合优化建模为马尔可夫决策过程,让智能体在模拟市场中自主学习最优的权重调整策略。

组合优化问题数学建模

问题定义与MDP框架

投资组合优化问题可以形式化为一个马尔可夫决策过程(MDP)。与多股票交易环境不同,这里的动作空间不再是买卖信号,而是每只股票的资金权重向量。

状态空间的设计需要捕捉市场动态和组合特征。FinRL 的 PortfolioOptimizationEnv 采用了一个三维结构:(features, stocks, time_window)。具体来说,状态包含:

  • 协方差矩阵:量化股票间的风险关联
  • 技术指标:MACD、RSI、CCI、ADX 等
  • 历史价格序列:通过时间窗口捕捉时序模式

这种设计让智能体不仅能看到单只股票的特征,还能理解整个组合的风险结构。协方差矩阵尤其重要,它反映了不同资产价格联动的风险,是传统组合理论的基石。

动作空间定义为每只股票的资金权重向量 W_t = [w_0, w_1, ..., w_n],其中 w_0 代表现金比例,w_i 代表第 i 只股票的权重。所有权重之和为 1,且每个分量在 [0,1] 区间内。环境会自动对动作进行 softmax 归一化,确保满足约束条件。

奖励函数的设计直接决定了智能体的学习目标。FinRL 采用对数收益率形式:

r_t = ln(V_t / V_{t-1})

其中 V_t 是 t 时刻的组合总价值。这种设计有几个优点:首先,对数收益率具有时间可加性,便于计算长期累积收益;其次,它能惩罚价值下跌,奖励价值增长;最后,这种形式与金融理论中的连续复利概念一致。

与传统MPT的对比

现代投资组合理论(MPT)通过求解二次规划问题来最大化夏普比率。但这种方法有几个局限:一是依赖历史数据计算期望收益和协方差,对未来预测能力有限;二是无法灵活融入技术指标等非结构化信息;三是权重调整频率低,通常是月度或季度再平衡。

强化学习方法的优势在于:能够处理高维状态空间,融合多种信息源;支持高频动态调整;通过试错学习非线性策略。当然,这也带来了新的挑战:训练不稳定、过拟合风险、计算成本高等。

PortfolioOptimizationEnv环境使用

环境初始化与配置

FinRL 提供了专门的 PortfolioOptimizationEnv 类来模拟组合优化场景。使用前需要准备符合格式的数据,环境会从 DataFrame 中读取时间序列数据。

数据格式要求包含以下列:

  • date:时间戳
  • tic:股票代码
  • close:收盘价
  • 其他特征列:如 high、low 等

环境的核心参数包括:

env = PortfolioOptimizationEnv(
    df=data,                    # 数据DataFrame
    stock_dim=30,              # 股票数量
    hmax=100,                  # 最大交易股数
    initial_amount=1000000,    # 初始资金
    transaction_cost_pct=0.001, # 交易成本
    reward_scaling=1e-4,       # 奖励缩放因子
    state_space=30,            # 状态空间维度
    action_space=30,           # 动作空间维度
    tech_indicator_list=['macd', 'rsi', 'cci', 'adx'], # 技术指标列表
    turbulence_threshold=140,  # 市场动荡阈值
    lookback=252               # 回溯期
)

lookback 参数定义了时间窗口长度,默认 252 个交易日(约一年)。这个窗口决定了状态中包含多少历史信息,是平衡计算效率和策略记忆能力的关键超参。

状态与动作处理机制

环境的 step 方法实现了核心逻辑。当智能体输出动作 actions 后,环境会执行以下流程:

首先,对动作进行归一化处理。如果动作向量之和不为 1,环境会自动应用 softmax 函数:

norm_actions = (np.array(actions) - np.array(actions).min()) / (np.array(actions) - np.array(actions).min()).sum()
weights = norm_actions

然后,计算组合收益。假设前一天权重为 W_{t-1},当天权重调整为 W_t,价格变化为 P_t,则组合收益为:

portfolio_return = sum(((self.data.close.values / last_day_memory.close.values) - 1) * weights)

接着,更新组合价值:

new_portfolio_value = self.portfolio_value * (1 + portfolio_return)
self.portfolio_value = new_portfolio_value

最后,返回新的状态。状态由协方差矩阵和技术指标拼接而成:

self.state = np.append(np.array(self.covs), 
                      [self.data[tech].values.tolist() for tech in self.tech_indicator_list], 
                      axis=0)

这种设计确保了状态包含足够的市场信息,同时保持了计算效率。协方差矩阵通过滚动窗口计算,反映了近期市场波动特征。

环境使用示例

完整的使用流程包括数据下载、特征工程、环境创建和训练。FinRL 提供了 EnvSetup 类简化环境配置:

from finrl.env.environment import EnvSetup

# 初始化环境配置
env_setup = EnvSetup(
    stock_dim=30,
    state_space=30,
    hmax=100,
    initial_amount=1000000,
    transaction_cost_pct=0.001,
    tech_indicator_list=['macd', 'rsi', 'cci', 'adx']
)

# 创建训练环境
env_train = env_setup.create_env_training(
    data=train_data,
    env_class=StockPortfolioEnv
)

训练完成后,可以在测试集上评估策略表现。环境会自动记录每日组合价值和权重变化,便于后续分析。

Policy Gradient组合权重优化

策略梯度算法原理

FinRL 的 PolicyGradient 类实现了专门针对组合优化的策略梯度算法。与传统 DDPG 不同,这个算法没有独立的 Critic 网络,而是直接使用组合价值作为价值函数。

算法核心思想是:通过蒙特卡洛采样估计策略梯度,沿着能提升组合价值的方向更新网络参数。具体步骤如下:

  1. 初始化:创建策略网络和经验回放缓冲区
  2. 交互采样:在每个 episode 中,智能体与环境交互,收集状态、动作、奖励序列
  3. 批量更新:每收集 batch_size 个时间步后,从缓冲区采样序列数据
  4. 价值计算:计算价值函数 V = Σ ln(μ_t * (W_t · P_t)),其中 μ_t 是交易成本因子
  5. 梯度上升:根据价值函数对策略网络进行梯度上升更新

这种设计有几个特点:一是采用序列采样而非随机采样,保持了时间依赖性;二是全利用(full-exploit)策略,训练过程中不添加探索噪声;三是直接使用组合价值作为优化目标,避免了价值函数近似的误差。

网络架构与实现

策略网络通常采用卷积结构处理三维状态输入。EIIE(Ensemble of Identical Independent Evaluators)是经典架构,它使用一维卷积层分别处理每只股票的时间序列,然后聚合输出权重。

from finrl.agents.portfolio_optimization.models import DRLAgent
from finrl.agents.portfolio_optimization.architectures import EIIE

# 配置策略梯度参数
model_kwargs = {
    "lr": 0.01,          # 学习率
    "policy": EIIE,      # 网络架构
    "batch_size": 128,   # 批量大小
    "episodes": 10       # 训练轮数
}

# 配置网络超参
policy_kwargs = {
    "k_size": 4,         # 卷积核大小
    "time_window": 50    # 时间窗口
}

# 创建模型
model = DRLAgent(train_env).get_model("pg", model_kwargs, policy_kwargs)

# 训练模型
DRLAgent.train_model(model, episodes=10)

训练过程中,智能体会逐步学习到有效的权重分配模式。通常,训练初期权重变化剧烈,后期趋于稳定。可以通过监控组合价值的增长曲线判断训练效果。

超参数调优要点

策略梯度算法对超参数敏感,几个关键参数需要特别注意:

学习率(lr):控制策略更新步长。太大导致不稳定,太小收敛慢。建议从 0.01 开始,根据训练曲线调整。

批量大小(batch_size):影响梯度估计的方差。组合优化问题中,较大的批量(如 128-256)通常更稳定,因为能更好地捕捉时间序列的统计特性。

时间窗口(time_window):决定状态的历史深度。太短无法捕捉长期趋势,太长增加计算负担。50-252 个交易日是常见选择。

奖励缩放(reward_scaling):原始组合价值可能很大,需要缩放至合适范围(如 1e-4 量级)以保证数值稳定性。

可以使用 Optuna 或 Ray Tune 进行自动化超参搜索,FinRL 的教程中有相关示例。

EI3架构与高级策略

EIIE架构回顾

EIIE(Ensemble of Identical Independent Evaluators)是组合优化领域的开创性工作。其核心设计是:对每只股票使用相同的卷积网络独立处理,最后通过 softmax 层输出权重。

网络结构通常包括:

  • 输入层:(features, stocks, time_window)
  • 一维卷积层:分别处理每只股票的时间序列
  • 特征聚合层:合并所有股票的特征
  • 输出层:生成权重向量

这种架构的优势在于参数共享,减少了模型复杂度,同时保持了处理多资产的能力。但缺点是忽略了股票间的相互影响,所有股票使用相同的特征提取器。

EI3架构创新

EI3(Inception-based Ensemble of Identical Independent Evaluators)在 EIIE 基础上引入了 Inception 模块,支持多尺度时间特征聚合。这是受到 Google Inception 网络启发,通过不同大小的卷积核捕捉不同频率的市场模式。

EI3 的关键改进:

  • 多尺度卷积:并行使用 3x3、5x5、7x7 等不同大小的卷积核
  • 特征融合:将不同尺度的特征图拼接后送入后续层
  • 残差连接:缓解梯度消失问题,支持更深网络

这种设计让智能体能同时捕捉短期波动和长期趋势,对非平稳的金融时间序列更有效。实验表明,EI3 在波动率较大的市场环境中表现优于 EIIE。

高级策略技巧

除了网络架构,还有一些实用技巧可以提升策略表现:

集成学习:训练多个不同随机种子的模型,对输出权重取平均。这种方法能降低方差,提高鲁棒性。FinRL 的教程中有集成策略的完整实现。

风险约束:在奖励函数中加入波动率惩罚项:

reward = portfolio_return - λ * portfolio_variance

其中 λ 是风险厌恶系数。这种调整能让策略更保守,降低最大回撤。

动态时间窗口:根据市场波动率自适应调整 lookback 参数。高波动时期使用更长的历史数据,低波动时期缩短窗口以提高响应速度。

交易成本建模:虽然简化示例通常忽略交易成本,但实盘必须考虑。可以在奖励函数中加入:

transaction_cost = sum(abs(weights - last_weights)) * cost_rate
reward = raw_reward - transaction_cost

这些技巧需要在 PortfolioOptimizationEnv 中自定义奖励函数和环境逻辑。

基准对比与风险分析

回测流程实现

FinRL 使用 PyFolio 库进行回测分析。完整的回测流程包括:

from finrl.trade.backtest import backtest_strat, baseline_strat
from pyfolio import timeseries

# 生成策略收益序列
df_daily_return, df_actions = DRLAgent.DRL_prediction(
    model=model_a2c,
    test_data=test_data,
    test_env=env_trade,
    test_obs=obs_trade
)

# 计算绩效指标
DRL_strat = backtest_strat(df_daily_return)
perf_func = timeseries.perf_stats
perf_stats_all = perf_func(
    returns=DRL_strat,
    factor_returns=DRL_strat,
    positions=None,
    transactions=None,
    turnover_denom="AGB"
)

print("==============DRL Strategy Stats===========")
print(perf_stats_all)

关键绩效指标包括:

  • 年化收益率:策略的年化回报水平
  • 累计收益率:整个回测期的总回报
  • 年化波动率:收益的标准差,衡量风险
  • 夏普比率:风险调整后的收益,(252**0.5)*mean/std
  • 最大回撤:从峰值到谷底的最大损失
  • 卡玛比率:年化收益/最大回撤

基准策略对比

合理的基准对比是评估策略有效性的关键。常用基准包括:

买入持有(Buy & Hold):等权重买入所有股票并持有到期。这是最简单的被动策略,但在牛市中往往难以超越。

道琼斯工业平均指数(DJIA):市场指数基准,代表整体市场表现。

等权重组合:每天将资金均等分配给所有股票。这种策略自动实现"高抛低吸"的再平衡效果。

对比代码示例:

# 获取基准数据
dji_data, dji_strat = baseline_strat(
    ticker='^DJI',
    start_date='2019-01-01',
    end_date='2020-12-01'
)

# 绘制对比图
import pyfolio
with pyfolio.plotting.plotting_context(font_scale=1.1):
    pyfolio.create_full_tear_sheet(
        returns=DRL_strat,
        benchmark_rets=dji_strat,
        set_context=False
    )

风险分析深度解读

除了常规指标,还需要关注几个风险维度:

波动率聚集:检查高收益波动是否集中在特定时期。可以通过滚动窗口计算 30 天波动率,观察其时间序列特征。

回撤持续时间:最大回撤的幅度固然重要,但恢复时间同样关键。一个 20% 回撤如果用 3 个月恢复,比用 1 年恢复的策略更优。

换手率分析:过高的换手率意味着高昂的交易成本。计算日均换手率:

turnover = np.mean(np.abs(df_actions.diff()).sum(axis=1))

如果换手率超过 100%,说明策略交易过于频繁,实盘可能不可行。

权重集中度:检查策略是否过度集中持仓。计算权重的赫芬达尔指数:

hhi = np.mean((df_actions**2).sum(axis=1))

指数接近 1 说明集中度高,风险大;接近 1/n 说明分散性好。

市场环境适应性:将回测期划分为牛市、熊市、震荡市,分别计算策略表现。优秀的策略应在不同市场环境下都能取得正收益,至少控制回撤。

通过这些分析,可以全面评估策略的优劣,为参数调整和模型改进提供方向。记住,实盘部署前必须在多个不同时间段和数据集上验证策略的稳健性。


投资组合优化是 FinRL 框架中最具挑战性的应用场景之一。从数学建模到环境配置,从算法实现到绩效评估,每个环节都需要仔细推敲。本章介绍了核心的 PortfolioOptimizationEnv 环境、策略梯度算法以及 EIIE/EI3 架构,并详细讲解了回测分析方法。

下一章将探讨如何通过向量环境和并行化技术加速训练过程,这对于处理大规模资产组合和长周期数据尤为重要。高性能训练不仅能缩短研发周期,还能支持更复杂的模型架构和超参数搜索。