使用 FinRL 训练你的第一个强化学习交易机器人
一份使用 FinRL 构建首个强化学习交易机器人的实操指南,涵盖环境配置、PPO 与 SAC 训练,以及与买入持有策略的对比。
强化学习已经从游戏领域的头条新闻走向了实际的金融实验。训练一个能够根据原始价格数据做出买卖持有决策的智能体听起来像是未来科技,但如今任何会写 Python 的人都可以使用相关工具。FinRL 是一个面向量化金融的开源深度强化学习框架,它降低了入门门槛,让你可以在一个下午训练出自己的第一个交易机器人。
本教程将带你走完整个流程:安装 FinRL、配置交易环境、训练两种流行算法 PPO 和 SAC,并将学习到的策略与简单的买入持有基准进行对比。我们将使用广为人知的 Stock_NeurIPS2018 示例,这样你无需寻找私有数据就能复现每一步。本教程的目标不是给你一个稳赚的策略,而是教会你强化学习如何融入算法交易、它在哪些方面表现出色,以及在哪些方面可能误导你。
本指南面向教育和研究。包括强化学习智能体在内的任何模型的历史表现都不能保证未来收益。在投入真实资金之前,务必使用样本外数据和模拟交易进行验证。
什么是 FinRL?
FinRL 是一个开源 Python 库,构建在稳定且熟悉的机器学习工具之上。它将 OpenAI Gym 风格的环境、Yahoo Finance 数据下载器和 Stable Baselines3 智能体整合成一个用于金融强化学习的统一框架。该项目由 AI4Finance Foundation 维护,已成为学生和研究人员探索市场中深度强化学习的标准起点。
核心思想很简单。你定义一个市场环境,智能体在其中观察状态,例如价格、技术指标和当前持仓。在每个时间步,智能体选择一个动作,例如买入股票、卖出股票或持有现金。环境返回一个奖励,通常是投资组合价值的变化,并转移到下一个状态。经过数千个回合,智能体学会一个能够最大化累积奖励的策略。
FinRL 处理了大量样板代码。它下载历史数据、计算技术指标、将数据划分为训练集和测试集、定义动作和观察空间,并与 Stable Baselines3 集成进行训练。它还为常见数据集提供了预置的配置字典,包括我们在这里要使用的数据集。
| 组件 | 在 FinRL 中的作用 | 典型选择 |
|---|---|---|
| 数据层 | 下载和预处理价格数据 | 通过 yfinance 获取 Yahoo Finance 数据 |
| 特征工程 | 添加技术指标 | MACD、RSI、CCI、ADX、简单移动平均线 |
| 环境 | 定义状态、动作、奖励和转移 | StockTradingEnv 或自定义 Gym 环境 |
| 智能体 | 从经验中学习策略 | PPO、SAC、A2C、DDPG、TD3 |
| 回测 | 在未见数据上评估训练好的策略 | Pyfolio、QuantStats 或自定义指标 |
FinRL 不是一个黑箱印钞机,而是一个研究工具。它的价值在于结构:数据、环境、智能体和评估之间的清晰分离。一旦你理解了这种结构,就可以在不重写所有代码的情况下更换数据集、修改奖励函数,并尝试更复杂的智能体。
为什么将强化学习用于交易?
传统交易策略通常是基于规则的。一个移动平均线交叉系统可能在短期均线上穿长期均线时买入,在反向穿越时卖出。这些规则易于理解和测试,但很死板。它们不会适应变化的波动率,不会从经验中学习,也无法以连续方式优化仓位大小。
强化学习重新定义了这个问题。你不用编写固定规则,而是定义一个奖励信号,让智能体去发现策略。智能体可以学会在不确定时期持有更多现金、在趋势一致时增加敞口、在大幅回撤前平仓。由于策略是从数据中学到的,它原则上可以适应那些难以用显式规则表达的模式。
这种灵活性是有代价的。RL 智能体需要大量数据,对超参数敏感,并且容易过拟合。一个设计不当的奖励函数可能导致奖励作弊:智能体找到训练期间最大化奖励的漏洞,但在实际市场中失效。例如,一个只奖励收益的智能体可能在牛市期间学会使用巨大杠杆,然后在第一次回调中崩盘。在奖励函数中控制风险,通常通过夏普比率或回撤惩罚,是必不可少的。
| 方法 | 优势 | 劣势 |
|---|---|---|
| 基于规则的策略 | 透明、回测快速、易于解释 | 死板、难以优化仓位 |
| 监督学习价格预测 | 直接预测价格 | 动作选择是独立的;忽略序列决策 |
| 强化学习 | 序列决策、自适应策略 | 样本效率低、对奖励设计敏感、难以调试 |
在本教程中,我们选择强化学习,因为它自然地处理了交易的序列性质。今天的每一个决策都会影响明天可用的决策集合。RL 以一种静态预测模型无法做到的方式捕捉了这种依赖关系。
环境配置
在训练任何智能体之前,你需要一个可用的 Python 环境。FinRL 有多个依赖项,因此最干净的方法是创建专用的虚拟环境或 conda 环境。这可以避免与其他包冲突,也便于复现。
首先创建并激活一个 conda 环境。如果你更喜欢 venv,命令类似。
conda create -n finrl python=3.10 -y
conda activate finrl安装核心包。FinRL 依赖 Stable Baselines3、Gymnasium、pandas、numpy 和一些金融工具包。最小安装如下:
pip install finrl stable-baselines3 gymnasium yfinance如果遇到依赖冲突,可以在全新环境中从源码安装 FinRL。维护者针对特定版本的 Gymnasium 和 Stable Baselines3 进行了测试,而快速发展的 RL 生态系统可能导致版本不匹配。
git clone https://github.com/AI4Finance-Foundation/FinRL.git
cd FinRL
pip install -e .安装完成后,在 Python 会话中验证导入是否正常。
import pandas as pd
import numpy as np
import gymnasium as gym
from stable_baselines3 import PPO, SAC
from finrl import config
print("FinRL ready")| 包 | 用途 | 说明 |
|---|---|---|
finrl | 交易环境和数据管道 | 如果 pip 版本滞后,可从源码安装 |
stable-baselines3 | PPO、SAC 等 RL 智能体 | 兼容 Gymnasium |
gymnasium | 开源 RL 接口 | 替代旧版 OpenAI Gym |
yfinance | 下载 Yahoo Finance 数据 | 被 FinRL 数据处理器使用 |
pyfolio / quantstats | 业绩报告 | 可选但有助于分析 |
RL 金融技术栈中依赖冲突很常见。当你找到一组可用的版本组合时,务必将其固定到 requirements.txt 中,以便后续复现实验。
Stock_NeurIPS2018 示例
Stock_NeurIPS2018 数据集是金融强化学习中的经典基准。它最初用于 NeurIPS 2018 年关于投资组合管理深度强化学习的论文,后来成为 FinRL 的"Hello World"。该数据集涵盖 2009 年至 2022 年道琼斯工业平均指数成分股的每日价格,并划分为训练期、验证期和测试期。
使用公开基准很重要。它让你能与其他研究者的结果进行比较,通过复现已知数值来发现 bug,并避免在私有数据上不断挖掘直到结果好看的诱惑。道琼斯 30 成分股 universe 流动性好、知名度高,也减少了小样本中常见的生存偏差和数据质量问题。
在 FinRL 中,加载数据集非常简单。该库提供了辅助函数,可以下载数据、添加技术指标并返回干净的 pandas DataFrame。
from finrl import config_tickers
from finrl.meta.preprocessor.yahoodownloader import YahooDownloader
from finrl.meta.preprocessor.preprocessors import FeatureEngineer, data_split
from finrl import config
# FinRL 配置中的道琼斯 30 成分股
tickers = config_tickers.DOW_30_TICKER
# 下载数据
df = YahooDownloader(
start_date="2009-01-01",
end_date="2022-12-31",
ticker_list=tickers
).fetch_data()
# 添加技术指标
fe = FeatureEngineer(
use_technical_indicator=True,
technical_indicator_list=config.INDICATORS,
use_vix=True,
use_turbulence=True,
user_defined_feature=False
)
processed = fe.preprocess_data(df)处理后的 DataFrame 包含每只股票的开盘、最高、最低、收盘、成交量以及一组技术指标。FinRL 的默认指标列表包括 MACD、RSI、CCI、DX 和简单移动平均线。你可以扩展这个列表,但默认值已经是一个坚实的基准。
| 数据集区间 | 开始日期 | 结束日期 | 用途 |
|---|---|---|---|
| 训练集 | 2009-01-01 | 2014-12-31 | 智能体学习策略 |
| 验证集 | 2015-01-01 | 2015-12-31 | 超参数调优 |
| 测试集 | 2016-01-01 | 2022-12-31 | 最终样本外评估 |
这种方式划分数据至关重要。你绝不能在训练期同时测试。一个记住了 2020 年崩盘和复苏的模型会在那段数据上表现出色,但当 2023 年表现不同时就会失败。验证集为你提供了一种在不偷看最终测试集的情况下调优超参数的方法。
定义交易环境
FinRL 的 StockTradingEnv 是市场数据与 RL 智能体之间的桥梁。它跟踪智能体的投资组合、计算奖励,并强制执行交易成本、最大持仓等约束。
每个时间步的状态包括每只股票的市场特征以及账户层面的信息。账户信息通常包含当前持股、可用现金和技术指标。动作空间取决于你使用离散还是连续形式。在连续设置的常见做法中,智能体输出一个目标投资组合权重向量,每只股票一个权重再加上现金。环境随后根据这些权重进行再平衡,并在此过程中支付交易成本。
from finrl.meta.env_stock_trading.env_stocktrading import StockTradingEnv
# 划分处理后的数据
train = data_split(processed, "2009-01-01", "2014-12-31")
trade = data_split(processed, "2016-01-01", "2022-12-31")
# 环境参数
stock_dimension = len(train.tic.unique())
state_space = 1 + 2 * stock_dimension + len(config.INDICATORS) * stock_dimension
buy_cost_list = sell_cost_list = [0.001] * stock_dimension
env_kwargs = {
"hmax": 100,
"initial_amount": 1000000,
"num_stock_shares": [0] * stock_dimension,
"buy_cost_pct": buy_cost_list,
"sell_cost_pct": sell_cost_list,
"state_space": state_space,
"stock_dim": stock_dimension,
"tech_indicator_list": config.INDICATORS,
"action_space": stock_dimension,
"reward_scaling": 1e-4,
}
e_train_gym = StockTradingEnv(df=train, **env_kwargs)| 参数 | 典型值 | 含义 |
|---|---|---|
initial_amount | 1,000,000 | 账户起始现金 |
hmax | 100 | 每次交易的最大股数 |
buy_cost_pct / sell_cost_pct | 0.001 | 交易成本占交易金额的比例 |
reward_scaling | 1e-4 | 在作为奖励传递前缩放日收益 |
action_space | stock_dimension | 连续动作输出的数量 |
交易成本比初学者想象的更重要。0.1% 的往返成本听起来很小,但一个每日交易的智能体每月可能因摩擦损失几个百分点。始终在环境中包含现实的成本。如果一个策略只在零手续费下有效,那它就不是真正的策略。
训练 PPO
近端策略优化,即 PPO,是最受欢迎的深度强化学习算法之一。它是一种同策略方法,意味着它从当前策略收集的数据中学习。PPO 通过裁剪策略更新来稳定训练,使新策略不会与旧策略偏离太远。这使其对新手友好,也是交易实验中可靠的首选。
在 FinRL 中训练 PPO 智能体遵循 Stable Baselines3 的 API。你创建一个向量化环境,实例化模型,然后调用 learn。
from stable_baselines3 import PPO
from stable_baselines3.common.logger import configure
# 为 Stable Baselines3 包装环境
env_train, _ = e_train_gym.get_sb_env()
# PPO 模型
ppo_model = PPO(
"MlpPolicy",
env_train,
learning_rate=0.0003,
n_steps=2048,
batch_size=64,
n_epochs=10,
gamma=0.99,
gae_lambda=0.95,
clip_range=0.2,
verbose=1,
tensorboard_log="./tensorboard/"
)
# 训练
ppo_model.learn(total_timesteps=100000)上面的超参数是合理的默认值。n_steps=2048 控制智能体在执行策略更新前收集多少时间步。clip_range=0.2 限制一次更新中策略可以变化的幅度。gamma=0.99 决定未来奖励的折扣程度。这些数值并不神奇,它们是起点。你应该在验证集上调优它们。
训练期间,监控回合奖励。在早期回合中,智能体经常亏损或仅与现金持平。经过数千个时间步后,你应该看到训练数据上的累积奖励呈上升趋势。先别庆祝。训练表现不是目标,样本外表现才是。
| 超参数 | PPO 取值 | 作用 |
|---|---|---|
learning_rate | 3e-4 | 梯度更新的步长 |
n_steps | 2048 | 每次更新前的 rollout 时间步数 |
batch_size | 64 | 每个梯度小批量的样本数 |
n_epochs | 10 | 对 rollout 数据的遍历次数 |
gamma | 0.99 | 未来奖励的折扣因子 |
gae_lambda | 0.95 | 优势估计中偏差与方差的权衡 |
clip_range | 0.2 | 策略更新的裁剪范围 |
训练 SAC
软演员-评论家,即 SAC,是一种为连续动作空间设计的异策略算法。与 PPO 不同,SAC 可以从旧版本策略收集的数据中学习,这使其在许多连续控制任务中样本效率更高。SAC 还最大化一个熵项,鼓励探索,并通常产生更多样化的策略。
对于交易,SAC 很有吸引力,因为投资组合权重天然是连续的。智能体不是从少数离散动作中选择,而是输出一个平滑的资产配置向量。这可以带来更细腻的仓位管理。
from stable_baselines3 import SAC
# SAC 模型
sac_model = SAC(
"MlpPolicy",
env_train,
learning_rate=0.0003,
buffer_size=100000,
batch_size=64,
ent_coef="auto",
gamma=0.99,
tau=0.005,
train_freq=1,
gradient_steps=1,
verbose=1,
tensorboard_log="./tensorboard/"
)
# 训练
sac_model.learn(total_timesteps=100000)SAC 维护一个经验回放缓冲区,并在训练期间从中采样。buffer_size=100000 控制智能体记住多少历史。ent_coef="auto" 让 SAC 自动调节探索与利用之间的权衡。由于 SAC 是异策略的,它可以在当前策略探索新行为的同时,继续从旧经验中学习。
| 超参数 | SAC 取值 | 作用 |
|---|---|---|
learning_rate | 3e-4 | 评论家和演员更新的步长 |
buffer_size | 100000 | 经验回放缓冲区的容量 |
batch_size | 64 | 每次训练更新的样本数 |
ent_coef | auto | 自动熵调节以促进探索 |
gamma | 0.99 | 未来奖励的折扣因子 |
tau | 0.005 | 目标网络的软更新系数 |
像 SAC 这样的异策略算法可能会对训练 rollout 的分布过拟合。务必在留出测试期上评估,并与简单基准进行比较。
与买入持有策略对比结果
两个模型训练完成后,在测试期上对它们进行评估。测试期必须完全是在训练和验证期间未见过的数据。这是判断智能体学到了可泛化规律,还是仅仅记住了训练数据的地方。
FinRL 提供了一个预测辅助函数,可以在环境中运行训练好的模型并记录每日账户价值。然后你可以计算收益、波动率、夏普比率、最大回撤等指标。
from finrl.agents.stablebaselines3.models import DRLAgent
# 测试环境
e_trade_gym = StockTradingEnv(df=trade, **env_kwargs)
# PPO 预测
ppo_df_account_value, ppo_df_actions = DRLAgent.DRL_prediction(
model=ppo_model,
environment=e_trade_gym
)
# SAC 预测
sac_df_account_value, sac_df_actions = DRLAgent.DRL_prediction(
model=sac_model,
environment=e_trade_gym
)最重要的对比是与买入持有。买入持有意味着在测试期开始时将所有初始资金平均分配到投资组合中的所有股票,之后不再操作。这是一个强有力的基准,因为它捕捉了股市的长期向上趋势,并且几乎不产生交易成本。
| 指标 | PPO 智能体 | SAC 智能体 | 买入持有 |
|---|---|---|---|
| 累计收益 | 视运行而定 | 视运行而定 | 基准 |
| 年化收益 | 视运行而定 | 视运行而定 | 市场平均 |
| 年化波动率 | 通常较低 | 相似或更高 | 市场波动 |
| 夏普比率 | 关键对比指标 | 关键对比指标 | 关键对比指标 |
| 最大回撤 | 关键风险指标 | 关键风险指标 | 关键风险指标 |
| 换手率 | 较高 | 较高 | 接近零 |
对比结果时,关注风险调整后的收益,而不是原始收益。一个策略如果让市场收益翻倍但波动率变为三倍,不一定更好。夏普比率和最大回撤捕捉了利润与风险之间的权衡。
在许多 FinRL 实验中,PPO 和 SAC 在测试集上可以匹配或略微跑赢买入持有,尤其是在趋势明显的时期。然而,优势通常很小,结果会因随机种子、超参数和交易成本而变化。如果你的智能体跑输买入持有,这并不是失败,而是有价值的信息。它意味着 RL 智能体尚未捕捉到超越市场 beta 的预测信号。
RL 交易的风险
强化学习功能强大,但如果不加约束地应用于交易,可能是危险的。最大的风险是过拟合。一个拥有数百万参数的代理可以记住训练期的各种怪癖,包括确切的崩盘日期和复苏模式。它在回测中看起来非常出色,但在实际交易中毫无用处。
分布偏移是另一个主要问题。市场会变化。从低波动牛市转向高通胀熊市的机制变化,可能破坏基于前一种机制训练的策略。除非明确在多样化条件下训练或添加稳健性技术,否则 RL 智能体无法自然地跨机制泛化。
奖励作弊很微妙但常见。如果你的奖励只是日收益,智能体可能在检测到上涨动量时学会建立巨大仓位,其他时候持有现金。这在动量反转之前有效。如果你的奖励包含交易成本惩罚但忽略了滑点,智能体可能交易得比现实更频繁。如果你在短窗口上奖励夏普比率,智能体可能回避所有风险,产生平坦的权益曲线。
| 风险 | 症状 | 缓解方法 |
|---|---|---|
| 过拟合 | 训练结果很好,测试结果很差 | 更长的测试集、滚动验证、正则化 |
| 分布偏移 | 直到市场机制变化前都表现良好 | 多机制训练、添加市场特征、使用集成 |
| 奖励作弊 | 最大化代理奖励的异常行为 | 设计与真实交易目标一致的奖励、加入风险项 |
| 低估成本 | 只在零手续费下有效 | 使用现实的佣金、滑点和价差假设 |
| 随机种子依赖 | 不同种子结果差异巨大 | 运行多个种子、报告置信区间 |
| 前视偏差 | 在消息公布前获得不可能收益 | 严格的时间戳对齐,特征中不含未来信息 |
回测只是假设,不是证明。即使最谨慎的样本外测试也可能在实盘市场中失败,因为滑点、延迟、市场冲击和行为变化都是无法完全模拟的。
另一个风险是模拟与现实之间的差距。回测假设你可以以收盘价执行、你的订单不会影响市场、你可以瞬间再平衡。现实中,滑点、部分成交和延迟都会侵蚀收益。先从模拟交易开始,然后小额实盘,只有在验证了执行质量后才扩大规模。
扩展实验
当你掌握了基本流程后,有很多方向可以探索。你可以将奖励函数改为优化索提诺比率而不是收益,添加回撤惩罚,或使用同时平衡利润和风险的多目标奖励。你可以包含更多特征,例如情绪分数、宏观经济指标或另类数据。你可以尝试不同算法,例如 A2C、DDPG 或 TD3。
你也可以超越单期训练-测试划分。滚动分析在滚动窗口上训练,在下一个窗口上测试,并在整个数据集上重复。这给出了一个更真实的画面:如果智能体被定期重新训练,它的表现会如何。计算成本更高,但更接近真实部署。
| 扩展方向 | 思路 | 难度 |
|---|---|---|
| 自定义奖励函数 | 索提诺、卡玛或回撤惩罚 | 中等 |
| 滚动验证 | 滚动训练-测试窗口 | 中等 |
| 智能体集成 | 结合 PPO、SAC 和基于规则的 signals | 高级 |
| 另类数据 | 情绪、期权流、宏观特征 | 高级 |
| 多资产配置 | 包含 ETF、加密货币或外汇 | 高级 |
FinRL 生态系统还包含投资组合配置、加密货币交易和高频风格环境的 notebook。每个变体都引入了新的状态空间和动作空间,但核心模式保持不变:数据、环境、智能体、评估。
常见问题
什么是 FinRL,它适合谁?
FinRL 是一个将强化学习应用于量化金融的开源库。它面向研究人员、学生以及希望使用 PPO、SAC、A2C 等深度强化学习智能体来探索算法交易策略的散户交易者。
训练 FinRL 交易机器人需要强大的 GPU 吗?
小型实验不需要。在 Stock_NeurIPS2018 数据集上,用少量道琼斯 30 成分股训练,在现代笔记本 CPU 上几分钟就能完成。GPU 可以加速大规模投资组合实验或基于 Transformer 的智能体,但入门阶段并非必需。
PPO 和 SAC 在交易场景中如何比较?
PPO 是一种同策略算法,通常能生成稳定、可解释的策略,适用于离散或连续动作空间。SAC 是一种针对连续动作空间优化的异策略算法,样本效率更高。在交易中,PPO 往往带来更平稳的收益,而 SAC 可以学到更丰富的仓位调整行为。
为什么应该将我的 RL 机器人与买入持有策略对比?
买入持有是一个简单、几乎无交易成本的基准。如果你的强化学习智能体在扣除手续费、滑点和税费后,无法在风险调整基础上跑赢它,那就说明这个智能体只是在增加复杂度,而没有增加价值。
使用强化学习进行交易有哪些主要风险?
主要风险包括对历史数据的过拟合、奖励作弊、市场机制变化时的分布偏移、低估交易成本,以及高估样本外表现。当市场条件与训练期不同时,RL 智能体可能悄无声息地失效。
我可以将这个机器人部署到真实资金交易吗?
本教程代码仅用于教育和研究。在部署任何真实资金之前,你需要进行严格的样本外测试、模拟交易、风险控制、合规审查以及稳健的执行基础设施。本指南不构成投资建议。
在哪里可以了解更多关于 FinRL 的内容?
可以从 FinRL 官方 GitHub 仓库和文档开始。该项目包含投资组合配置、股票交易和加密货币交易等方面的教程、示例 notebook 和论文。论文《FinRL: Deep Reinforcement Learning Framework to Automate Trading in Quantitative Finance》是一个很好的学术入门资料。
总结
使用 FinRL 训练你的第一个强化学习交易机器人,是理解序列决策如何映射到金融市场的绝佳方式。你将学习如何构建 RL 问题、如何训练 PPO 和 SAC 智能体,以及如何根据简单的买入持有基准评估结果。
诚实的结论是,RL 是一种研究工具,而不是现成策略。大多数入门智能体在扣除成本后很难跑赢买入持有。这很正常。真正的价值在于这个过程:建立对奖励设计、过拟合和机制风险的直觉。从小处着手,保持严格的测试集,在至少经历一个完整的市场周期模拟交易之前,绝不要投入真实资金。