ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

FinRL 深度强化学习股票交易完整指南:5 种 DRL 算法,从 OHLCV 行情到样本外回测

FinRL 深度强化学习股票交易完整指南:5 种 DRL 算法,从 OHLCV 行情到样本外回测

FinRL 深度强化学习股票交易完整指南:5 种 DRL 算法,从 OHLCV 行情到样本外回测

【免费下载链接】FinRLFinRL®: Financial Reinforcement Learning. 🔥项目地址: https://gitcode.com/gh_mirrors/fi/FinRL-Library

FinRL 是一个金融强化学习框架:它把历史行情封装成 Gym 风格的市场环境,让深度强化学习(DRL)智能体在历史数据上训练股票交易策略,训练完再拿样本外数据检验。官方 First Glance 章节推荐用 Stock_NeurIPS2018 系列三个 notebook 作为入门路径——读完本文,你会清楚一条行情如何变成状态向量、环境如何撮合交易、五种算法各该怎么训,以及如何与经典基线对比出可辩护的回测结论。

🧭 为什么金融需要 DRL:状态、动作、奖励三问

规则式策略的天花板很明显:30 只股票、8 个技术指标的 if/else 组合写不完,指标之间怎么加权也没有依据。交易本质是"在不确定性下连续做决策",这正是 DRL 擅长的事——智能体不需要规则库,只需要在历史数据上不断试错并获得反馈。

类比学车:你记不住每一个路况,只能靠"看路 → 打方向 → 收到反馈"的循环练出来。FinRL 的整条流水线可以拆成三问,后文每个模块都在回答其中之一:

  • 状态 s:智能体此刻看到什么?FinRL 中是现金、各股价格、当前持仓与技术指标拼成的向量;
  • 动作 a:它能做什么?每只股票一个数:正数买入、负数卖出、0 持有;
  • 奖励 r:这一步做得好不好?FinRL 取"本步后组合总资产 − 之前总资产"。

官方建议从 finrl/applications/Stock_NeurIPS2018/ 的数据、训练、回测三个 notebook 走完全流程,实验基于 NeurIPS 2018 论文Practical deep reinforcement learning approach for stock trading。本文用同一套数据逐层拆解。

📊 数据层:如何把 OHLCV 行情加工成状态向量

如何拉取 OHLCV 行情

OHLCV 即 Open、High、Low、Close、Volume,一个交易日五个数,几乎覆盖趋势分析所需的全部数值信息。FinRL 把yfinance封装成YahooDownloader,统一列名并把复权收盘价折算进各价格列。演示用的股票池是 finrl/config_tickers.py 里的DOW_30_TICKER(道琼斯工业平均指数成分股),训练期取 2009-01-01 至 2020-07-01,交易期(样本外)取 2020-07-01 至 2021-10-29:

df_raw = YahooDownloader( start_date="2009-01-01", end_date="2021-10-29", ticker_list=config_tickers.DOW_30_TICKER ).fetch_data() fe = FeatureEngineer(use_technical_indicator=True, tech_indicator_list=INDICATORS, use_vix=True, use_turbulence=True) processed = fe.preprocess_data(df_raw)

FeatureEngineer.preprocess_data的实现见 preprocessors.py,依次做三件事:用stockstats算技术指标、下载 VIX(CBOE 波动率指数,市场恐慌的标尺)按日期并入、追加 turbulence 指数。INDICATORS在 finrl/config.py 中默认含macd、boll_ub、boll_lb、rsi_30、cci_30、dx_30、close_30_sma、close_60_sma共 8 个指标。

状态空间维度怎么算

turbulence 指数回答"市场是否危机":用 252 个交易日的滚动窗口算收益率协方差矩阵,再度量当天收益偏离历史均值的程度——本质是马氏距离式度量,数值越大波动越极端。2008 年式崩盘中它会显著飙升,为后面的环境风控提供信号。

对齐缺失数据并拆出两份数据集

不同股票上市日期不同,个别日子还有停牌。notebook 先用itertools.product构造 (date, ticker) 全组合、左连接真实数据、缺失补 0;再用data_split按日期拆段(半开区间,start <= date < end):

train = data_split(processed_full, "2009-01-01", "2020-07-01") trade = data_split(processed_full, "2020-07-01", "2021-10-29") train.to_csv("train_data.csv") trade.to_csv("trade_data.csv")

train_data.csv给训练用,trade_data.csv留给样本外评估。两者绝不能混用,否则回测数字全是自欺。

⚙️ 环境层:StockTradingEnv 把市场变成可交互对象

关键参数各是什么意思

数据只是表格,要让智能体"操作"它,必须包成 Gym 环境。StockTradingEnv 就是本系列的"市场模拟器":

参数本系列取值含义
hmax100单只股票单步最多买卖的股数
initial_amount1,000,000初始现金
num_stock_shares[0] * 股票数各股初始持仓(全空仓)
buy_cost_pct/sell_cost_pct各 0.001买入/卖出手续费比例
action_space股票数动作维度,每股一个连续值
state_space公式算出观测向量维度
reward_scaling1e-4奖励缩放系数,稳定训练
e_train_gym = StockTradingEnv(df=train, hmax=100, initial_amount=1_000_000, num_stock_shares=[0] * stock_dimension, buy_cost_pct=[0.001] * stock_dimension, sell_cost_pct=[0.001] * stock_dimension, state_space=state_space, stock_dim=stock_dimension, tech_indicator_list=INDICATORS, action_space=stock_dimension, reward_scaling=1e-4) env_train, _ = e_train_gym.get_sb_env()

get_sb_env()把环境包装成 Stable-Baselines3(SB3,PyTorch 实现的常用 DRL 算法库)要求的DummyVecEnv格式。

状态怎么拼、撮合怎么算

状态拼装顺序固定(见_initiate_state与_update_state):现金 → 各股收盘价 → 各股持仓 → 各股 8 个技术指标,维度由此算出:

stock_dimension = len(train.tic.unique()) state_space = 1 + 2 * stock_dimension + len(INDICATORS) * stock_dimension # 股票数为 N 时即 1 + 10N

动作空间是[-1, 1]的连续盒。step中每个动作值乘以hmax再取整得到股数:+0.5 即"买 50 股"。撮合时先处理全部卖出(按动作升序),再处理全部买入,避免同一步内现金错配。买入数量受"现金 ÷ (价格 × (1 + 成本))"约束,卖出数量取min(|动作|, 当前持仓);手续费逐笔累计到cost,训练结束打印。奖励 = 期末总资产 − 期初总资产,再乘reward_scaling压到神经网络好用的量纲。

📈 智能体层:五种算法怎么训、怎么选

训练脚本只有三行

models.py 的DRLAgent把五种算法收进MODELS字典:get_model创建模型,train_model负责训练并挂 TensorBoard 日志,model_kwargs可覆盖 finrl/config.py 里的默认超参数:

agent = DRLAgent(env=env_train) model = agent.get_model("a2c") trained = agent.train_model(model=model, tb_log_name="a2c", total_timesteps=50000) trained.save(TRAINED_MODEL_DIR + "/agent_a2c")

换算法只换一个字符串,notebook 用五个布尔开关决定这次训哪些。训练完模型以 SB3 zip 格式落到trained_models/目录。

五种算法对比与选型

算法策略类型关键超参数(notebook 版)训练步数特点
A2Con-policy默认参数50,000最轻快,先跑通流程用它
PPOon-policyn_steps=2048, ent_coef=0.01, lr=2.5e-4, batch_size=128200,000最稳,步数给得最多
DDPGoff-policy默认参数50,000连续动作经典方案
TD3off-policybatch_size=100, buffer_size=1e6, lr=1e-350,000双评价 + 延迟更新,缓解过估计
SACoff-policybatch_size=128, buffer_size=1e5, lr=1e-4, ent_coef="auto_0.1"70,000最大熵框架,探索充分

选型口诀:先用 A2C 五千步的十分之一量级(5 万步)验证环境和数据管线没写错,再上 PPO 跑满 20 万步拿"稳"的答案;怀疑评价网络高估价值就换 TD3,想让探索更充分就换 SAC。

⚖️ 验证层:样本外回测与 MVO、DJIA 双基线对比

样本外预测只是"逐日跑 predict"

把训练好的模型与trade_data.csv放同一目录,按训练时完全相同的参数重建环境,只是额外加上turbulence_threshold=70与risk_indicator_col="vix"(作用见最后一节):

trained_a2c = A2C.load("trained_models/agent_a2c") e_trade_gym = StockTradingEnv(df=trade, turbulence_threshold=70, risk_indicator_col="vix", **env_kwargs) df_value, df_actions = DRLAgent.DRL_prediction( model=trained_a2c, environment=e_trade_gym)

DRL_prediction逐日调用model.predict推进环境,返回账户净值序列与每日动作矩阵。其余四个算法写法完全相同,只换模型变量。

两条基线怎么搭

DRL 曲线单独看没有意义,notebook 设了两条经典基线:

  • MVO(均值方差优化):用训练期收盘价算平均收益向量与协方差矩阵,用 PyPortfolioOpt 的EfficientFrontier求最大夏普组合,单股权重上限 0.5,整个交易期持有不动;
  • DJIA:买入持有道琼斯工业平均指数(ticker 为dji),首日指数归一到 1,000,000,与 DRL 账户初始资金对齐。

MVO 核心就几行:

ef = EfficientFrontier(meanReturns, covReturns, weight_bounds=(0, 0.5)) weights = ef.clean_weights() mvo_weights = np.array([1_000_000 * weights[i] for i in range(29)])

最后把五条 DRL 净值、MVO、DJIA 横向拼成一张图——这就是"我到底比躺平强在哪"的标准回答方式。

一个提醒:训练期(2009 至 2020)与交易期不同,样本外越拉越长,收益优势(alpha)会衰减。官方建议按月或按季度重训,而不是一次训练用到永远。

🛡️ 落地与扩展:真实摩擦、turbulence 风控与命令行入口

市场摩擦在环境里如何体现

上面的回测与真实盘口的差距,来自环境内置的细节:买卖各收 0.1% 手续费;hmax限制单日单股交易量;现金不足就买不了那么多;没有持仓就不能卖。这些约束让净值曲线"像真的"。

turbulence 风控的触发方式是:回测时risk_indicator_col="vix"让环境读 VIX 列作为风险信号,一旦达到 70,step会把当天动作强制改成全部卖出(每只股票-hmax),智能体没有选择权,只能清仓——市场崩盘时"自动缩头",这正是数据层那个 turbulence/VIX 指数的下游用途。

命令行跑同一套流程

不写 notebook 的话,finrl/main.py 提供脚本入口,日期、股票池、算法超参数都集中在 finrl/config.py:

python main.py --mode=train # 训练 python main.py --mode=test # 回测 python main.py --mode=trade # 纸面/实盘,需在 config_private.py 填 Alpaca 参数

下一步深入两条路

  1. 用一只股票的一天走通三层:不要先读架构文档。取 processed 数据里某只股票某一天的行,手算它的状态向量,放进StockTradingEnv.step走一遍,逐行看现金、持仓怎么变。把这个最小闭环走通后再看 docs/source/start/three_layer.rst,数据层、环境层、智能体层的每段文字都会对上图中的位置。
  2. 改完配置再跑脚本:命令行路线的第一步不是运行,而是改config.py——把日期区间与股票列表换成自己的数据范围,或替换INDICATORS里的指标,再跑--mode=train,对比改动前后的 TensorBoard 曲线。只有亲手拨动过变量,才知道哪些配置真的影响结果。

【免费下载链接】FinRLFinRL®: Financial Reinforcement Learning. 🔥项目地址: https://gitcode.com/gh_mirrors/fi/FinRL-Library

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进