用 FinRL 复现 NeurIPS 2018 论文:从数据工程到 DRL 量化交易智能体的完整工作流
2026/9/21 1:37:02 网站建设 项目流程
  • 金融科技
  • 强化学习
  • 人工智能

【免费下载链接】FinRL

FinRL®: Financial Reinforcement Learning. 🔥

项目地址:https://gitcode.com/gh_mirrors/fi/FinRL-Library
点击查看免费下载

本指南以 FinRL 开源仓库中的 Stock_NeurIPS2018 系列为蓝本,系统讲解如何用强化学习(RL)复现 NeurIPS 2018 论文《Practical Deep Reinforcement Learning Approach for Stock Trading》的完整流程。该系列由三个 Jupyter Notebook 构成(数据准备 → 智能体训练 → 回测对比),覆盖 OHLCV 行情获取、技术指标与波动率特征工程、OpenAI Gym 风格交易环境构建、A2C/PPO/TD3/SAC 等 5 种 DRL 算法训练,以及均值方差优化(MVO)与道琼斯指数(DJIA)双基线回测。读完本文,你将掌握一条可端到端复现、可迁移到自定义股票池的 DRL 量化研究流水线,并理解 FinRL 分层架构(数据层、环境层、智能体层)背后的实现细节。

一、任务背景与仓库定位

Stock_NeurIPS2018 系列位于仓库的 finrl/applications/Stock_NeurIPS2018 目录,是 FinRL 官方提供的"论文复现型"应用示例之一。其核心思想是:把学术论文中的 RL 交易流程转化为可运行的工程代码,让研究者能用同一套框架在真实股票数据上完成"训练 - 回测 - 对比"闭环。

Stock_NeurIPS2018/ ├── README.md # 三步流程总览(本文主体) ├── Stock_NeurIPS2018_1_Data.ipynb # Step I: 数据下载与预处理 ├── Stock_NeurIPS2018_2_Train.ipynb # Step II: 构建环境并训练 DRL 智能体 └── Stock_NeurIPS2018_3_Backtest.ipynb # Step III: 回测与基线对比

三个 Notebook 各自对应一个独立阶段,前一阶段的输出(train_data.csvtrade_data.csvtrained_models/下的模型 zip)是后一阶段的输入,串联后即构成完整的量化研究流水线。

二、Step I:数据获取与特征工程(Stock_NeurIPS2018_1_Data.ipynb)

第一步的目标是生成两份 CSV:训练集train_data.csv与交易(回测)集trade_data.csv,仓库中还提供了对应的样例数据文件。Notebook 输出显示,下载 30 只道指成分股从 2009-01-01 到 2021-10-29 的日线数据,得到 94301 行 × 8 列的原始 DataFrame。

2.1 安装依赖

Notebook 首先通过 pip 安装所需依赖,其中pyportfolioopt用于第三步的均值方差优化基线:

!pip install swig !pip install wrds !pip install pyportfolioopt ## install finrl library !pip install git+https://github.com/AI4Finance-Foundation/FinRL.git

2.2 OHLCV 数据与两种获取方式

OHLCV即 Open(开盘)、High(最高)、Low(最低)、Close(收盘)、Volume(成交量),是股票时间序列中最核心的数值信息载体,交易者可以据此推断动量、市场情绪与趋势。Notebook 演示了两种获取方式:

方式一:直接使用 yfinance 库

import yfinance as yf aapl_df_yf = yf.download(tickers="aapl", start='2020-01-01', end='2020-01-31')

方式二:使用 FinRL 封装的 YahooDownloader

from finrl.meta.preprocessor.yahoodownloader import YahooDownloader aapl_df_finrl = YahooDownloader(start_date='2020-01-01', end_date='2020-01-31', ticker_list=['aapl']).fetch_data()

两种方式返回的数据格式不同。FinRL 的 YahooDownloader 做了面向后续流水线的标准化改造,源码中的fetch_data()_adjust_prices()揭示了这些差异:

  • 列名标准化Date/Adj Close/Close/...统一重命名为小写date/close/open/high/low/volume/tic
  • 用复权价替代收盘价_adjust_prices()用调整因子adj = adjcp / close对 open/high/low/close 全部进行复权修正,消除拆股与分红影响;
  • 新增星期列day(周一 = 0 … 周五 = 4),为智能体提供时间上下文特征;
  • 清洗缺失数据dropna()删除空值行,并按date + tic排序。

输出为 20 行 × 8 列的 DataFrame,列结构为date, open, high, low, close, volume, tic, day

2.3 股票池与时间窗口划分

本示例的股票池是道琼斯工业平均指数(DJIA)的 30 只成分股,仓库 config_tickers.py 中定义了DOW_30_TICKER列表(AXP、AMGN、AAPL、BA、CAT、CSCO…)。训练与回测时间窗按论文惯例划分——用 2009-2020 的长期数据训练,用 2020-07 之后的"未来"数据做样本外回测:

from finrl import config_tickers TRAIN_START_DATE = '2009-01-01' TRAIN_END_DATE = '2020-07-01' TRADE_START_DATE = '2020-07-01' TRADE_END_DATE = '2021-10-29' df_raw = YahooDownloader(start_date=TRAIN_START_DATE, end_date=TRADE_END_DATE, ticker_list=config_tickers.DOW_30_TICKER).fetch_data()

2.4 特征工程:技术指标、VIX 与湍流指数

原始 OHLCV 不能直接作为 RL 状态,需要经过 FeatureEngineer 加工成信息丰富的特征空间。Notebook 的构造参数如下:

from finrl.config import INDICATORS from finrl.meta.preprocessor.preprocessors import FeatureEngineer fe = FeatureEngineer(use_technical_indicator=True, tech_indicator_list=INDICATORS, use_vix=True, use_turbulence=True, user_defined_feature=False) processed = fe.preprocess_data(df_raw)

对应源码中preprocess_data()的调用链依次完成四件事:

  1. clean_data():按日期透视出完整收盘价矩阵,剔除"某日停牌/未上市"导致缺失过多的股票;
  2. add_technical_indicator():基于stockstats包计算趋势类技术指标。仓库 config.py 中默认的 8 个指标为:macd(MACD)、boll_ub/boll_lb(布林带上/下轨)、rsi_30(相对强弱)、cci_30(顺势指标)、dx_30(动向指数)、close_30_sma/close_60_sma(30/60 日均线);
  3. add_vix():下载恐慌指数^VIX并合并到每个交易日,反映市场整体风险情绪;
  4. add_turbulence():计算湍流指数。源码calculate_turbulence()以 252 个交易日(约一年)为滚动窗口估计收益的协方差矩阵,再通过马氏距离度量当日市场偏离正常状态的极端程度——这是论文为应对 2007-2008 式金融危机而引入的风险控制机制。

预处理后每个样本包含 18 列(8 个原始字段 + day + 8 个技术指标 + vix + turbulence)。为满足训练环境对"每个交易日出现全部股票"的要求,Notebook 还执行了笛卡尔积补全 + 缺失值填 0 的操作:

import itertools list_ticker = processed["tic"].unique().tolist() list_date = list(pd.date_range(processed['date'].min(), processed['date'].max()).astype(str)) combination = list(itertools.product(list_date, list_ticker)) processed_full = pd.DataFrame(combination, columns=["date", "tic"]).merge( processed, on=["date", "tic"], how="left") processed_full = processed_full[processed_full['date'].isin(processed['date'])] processed_full = processed_full.sort_values(['date', 'tic']).fillna(0)

2.5 按时间窗切分并落盘

最后用 preprocessors.py 中的data_split()按日期区间切分数据。该函数同时完成按[date, tic]排序并将索引重置为日期编码(factorize),使后续环境可以直接按day索引取数:

from finrl.meta.preprocessor.preprocessors import data_split train = data_split(processed_full, TRAIN_START_DATE, TRAIN_END_DATE) trade = data_split(processed_full, TRADE_START_DATE, TRADE_END_DATE) print(len(train)) # 83897 print(len(trade)) # 9715 train.to_csv('train_data.csv') trade.to_csv('trade_data.csv')

三、Step II:构建 Gym 环境并训练 DRL 智能体(Stock_NeurIPS2018_2_Train.ipynb)

第二步将处理好的数据转化为标准的强化学习交互环境,并用 Stable Baselines 3 训练深度强化学习(DRL)智能体。

3.1 RL 形式化:状态、动作、奖励

Notebook 先用教科书式的三要素定义了交易问题的 MDP:

  • 状态 s:智能体对市场环境的感知,即历史价格与上一节生成的技术指标。智能体通过"回放"历史数据与环境交互学习;
  • 动作 a:每个状态下允许采取的操作。单标的场景可定义a ∈ {−1, 0, 1}分别表示卖出、持有、买入;当操作涉及多股时扩展为a ∈ {−k, …, −1, 0, 1, …, k},例如 "买入 10 股 AAPL" 记为10、"卖出 10 股" 记为−10
  • 奖励函数 r(s, a, s′):奖励是引导智能体学习更优策略的激励信号,本任务取组合价值的变动量r(s, a, s′) = v′ − v,其中v′v分别是新状态s′与旧状态s下的组合价值。

3.2 从 DataFrame 到 StockTradingEnv

FinRL 的核心环境实现是 env_stocktrading.py 中的StockTradingEnv,它继承gym.Env并提供 OpenAI Gym 风格接口。Notebook 中先根据股票数与指标数推导状态空间维度:

from finrl.meta.env_stock_trading.env_stocktrading import StockTradingEnv stock_dimension = len(train.tic.unique()) state_space = 1 + 2*stock_dimension + len(INDICATORS)*stock_dimension print(f"Stock Dimension: {stock_dimension}, State Space: {state_space}") # 输出: Stock Dimension: 29, State Space: 291

状态空间公式1 + 2×N + 8×N的含义是:1 个当前现金 + 2N 个价格类字段(每只股票的价格与持股数量)+ 8N 个技术指标(每只股票 8 个指标)。随后构造环境参数并实例化:

buy_cost_list = sell_cost_list = [0.001] * stock_dimension num_stock_shares = [0] * stock_dimension env_kwargs = { "hmax": 100, # 单次单资产最大交易额(手数上限) "initial_amount": 1000000, # 初始资金 100 万美元 "num_stock_shares": num_stock_shares, # 每只股票初始持仓为 0 "buy_cost_pct": buy_cost_list, # 买入费率 0.1% "sell_cost_pct": sell_cost_list,# 卖出费率 0.1% "state_space": state_space, # 状态维度 291 "stock_dim": stock_dimension, # 股票数 29 "tech_indicator_list": INDICATORS, # 8 个技术指标 "action_space": stock_dimension, # 动作维度与股票数一致 "reward_scaling": 1e-4 # 奖励缩放系数 } e_train_gym = StockTradingEnv(df=train, **env_kwargs) env_train, _ = e_train_gym.get_sb_env() # 包装为 DummyVecEnv

从 env_stocktrading.py 的源码可见关键设计:动作空间定义为spaces.Box(low=-1, high=1, shape=(action_space,)),即每个维度输出 −1 到 1 之间的连续值,再结合hmax换算为具体股数;交易成本通过buy_cost_pct/sell_cost_pct在买卖函数中扣除;reward_scaling=1e-4将量级过大的组合收益差缩放,以稳定神经网络训练。get_sb_env()将单个环境包装为 Stable Baselines 3 所需的DummyVecEnv(向量化环境)。

3.3 训练 A2C 智能体(含 DDPG/PPO/TD3/SAC 模板)

DRL 算法统一由 stablebaselines3/models.py 中的DRLAgent封装,内部维护MODELS = {"a2c": A2C, "ddpg": DDPG, "td3": TD3, "sac": SAC, "ppo": PPO},并把仓库 config.py 中的默认超参(A2C_PARAMSPPO_PARAMSDDPG_PARAMSTD3_PARAMSSAC_PARAMS)注入各算法。Notebook 用开关变量控制训练哪些算法:

from finrl.agents.stablebaselines3.models import DRLAgent from stable_baselines3.common.logger import configure from finrl.config import INDICATORS, TRAINED_MODEL_DIR, RESULTS_DIR if_using_a2c = True if_using_ddpg = False if_using_ppo = False if_using_td3 = False if_using_sac = False agent = DRLAgent(env=env_train) model_a2c = agent.get_model("a2c") # 配置日志输出:stdout + CSV + TensorBoard tmp_path = RESULTS_DIR + '/a2c' new_logger_a2c = configure(tmp_path, ["stdout", "csv", "tensorboard"]) model_a2c.set_logger(new_logger_a2c) trained_a2c = agent.train_model(model=model_a2c, tb_log_name='a2c', total_timesteps=50000) if if_using_a2c else None trained_a2c.save(TRAINED_MODEL_DIR + "/agent_a2c") if if_using_a2c else None

Notebook 中 A2C 的实际超参为{'n_steps': 5, 'ent_coef': 0.01, 'learning_rate': 0.0007},与 config.py 中A2C_PARAMS一致。训练过程会打印每个 rollout 的policy_lossvalue_lossreward等指标;一个完整 episode 结束时还会输出总资产、总奖励、总交易成本、交易次数与 Sharpe 比率。其余四种算法同样提供了完整模板(可复制粘贴后翻转开关启用):

算法关键超参(config.py 默认)默认训练步数
A2Cn_steps=5, ent_coef=0.01, learning_rate=0.000750,000
PPOn_steps=2048, ent_coef=0.01, learning_rate=0.00025, batch_size=64200,000
DDPGbatch_size=128, buffer_size=50000, learning_rate=0.00150,000
TD3batch_size=100, buffer_size=1000000, learning_rate=0.00150,000
SACbatch_size=64, buffer_size=100000, learning_rate=0.0001, learning_starts=100, ent_coef="auto_0.1"70,000

训练完成后模型以.zip格式保存到trained_models/目录(本地路径为./trained_models,Colab 为/content/trained_models)。本仓库的 cryptocurrency_trading 等应用目录也提供了预训练模型(如actor.pth)作为参照。

四、Step III:回测与双基线对比(Stock_NeurIPS2018_3_Backtest.ipynb)

第三步加载训练好的模型,在样本外数据(trade_data.csv)上回测,并与两个经典基线对比:均值方差优化组合(MVO)与道琼斯指数(DJIA)。

4.1 加载模型并进行样本外交易

先把trade_data.csv与训练好的模型放在本目录,读取数据并设置开关变量,然后通过A2C.load()加载模型:

from stable_baselines3 import A2C, DDPG, PPO, SAC, TD3 trade = pd.read_csv('trade_data.csv') trade = trade.set_index(trade.columns[0]); trade.index.names = [''] trained_a2c = A2C.load("trained_models/agent_a2c") if if_using_a2c else None

随后构造与训练阶段相同参数的回测环境,但额外启用湍流阈值风控——turbulence_threshold=70且风险指标列设为vix,即当市场波动(VIX 相关湍流指标)超过阈值时强制清仓,这是论文应对极端行情的核心风控手段:

e_trade_gym = StockTradingEnv(df=trade, turbulence_threshold=70, risk_indicator_col='vix', **env_kwargs) df_account_value_a2c, df_actions_a2c = DRLAgent.DRL_prediction( model=trained_a2c, environment=e_trade_gym) if if_using_a2c else (None, None)

DRLAgent.DRL_prediction()(见 models.py)的流程是:重置向量化环境后逐日调用model.predict(obs)得到动作、执行env.step(action)推进一天,直至终止(打印 "hit end!"),最后返回账户价值序列与动作序列。Notebook 强调,为了充分利用数据应周期性重训(如按季度/月度/周度),本例只在 2009-01 至 2020-07 的样本内调参一次,回测区间拉长后存在一定的 alpha 衰减。

4.2 基线一:均值方差优化(MVO)

Notebook 先用process_df_for_mvo()将长表(date × tic)透视成宽表(每行一个交易日、每列一只股票的收盘价),再用StockReturnsComputing()计算资产日收益率序列,进而得到均值向量meanReturns与协方差矩阵covReturns。最后借助安装阶段引入的pyportfolioopt求最大夏普组合权重:

from pypfopt.efficient_frontier import EfficientFrontier ef_mean = EfficientFrontier(meanReturns, covReturns, weight_bounds=(0, 0.5)) raw_weights_mean = ef_mean.max_sharpe() cleaned_weights_mean = ef_mean.clean_weights() mvo_weights = np.array([1000000 * cleaned_weights_mean[i] for i in range(29)])

weight_bounds=(0, 0.5)限制单只股票权重在 0~50%,max_sharpe()求解最大化夏普比率的资产配置,最后按初始资金 100 万美元折算成各股票的初始买入市值。组合净值曲线由Portfolio_Assets = TradeData @ Initial_Portfolio计算得到。

4.3 基线二:道琼斯指数(DJIA)

YahooDownloader下载dji在回测区间的收盘价,并以回测起始日的收盘价为基准归一化到 100 万,与 DRL 智能体的初始资金对齐,从而保证三者在同一量纲下可比:

df_dji = YahooDownloader(start_date=TRADE_START_DATE, end_date=TRADE_END_DATE, ticker_list=['dji']).fetch_data() df_dji = df_dji[['date','close']] fst_day = df_dji['close'][0] dji = pd.merge(df_dji['date'], df_dji['close'].div(fst_day).mul(1000000), how='outer', left_index=True, right_index=True).set_index('date')

4.4 汇总净值曲线并可视化

将各智能体账户价值、MVO 净值与 DJIA 净值按日期外连接合并,缺失值用bfill回填,得到统一的结果表:

result = pd.DataFrame() if if_using_a2c: result = pd.merge(result, df_result_a2c, how='outer', left_index=True, right_index=True) # ... 其余算法同理 result = pd.merge(result, MVO_result, how='outer', left_index=True, right_index=True) result = pd.merge(result, dji, how='outer', left_index=True, right_index=True).fillna(method='bfill') col_name = ['A2C', 'DDPG', 'PPO', 'TD3', 'SAC'][:len(result.columns)-2] + ['Mean Var', 'djia'] result.columns = col_name plt.rcParams["figure.figsize"] = (15, 5) plt.figure() result.plot()

Notebook 中仅启用 A2C 的运行结果显示:在 2020-07 至 2021-10 的样本外区间(336 个交易日),A2C 智能体净值从 100 万增长至约 186 万,MVO 基线约 153 万,DJIA 约 139 万;但要注意这是单次实验的演示结果,并非对算法优劣的普适结论——回测结果受超参、时间窗、费率与市场状态影响显著,应保持审慎解读。

五、从示例到自定义策略:流水线复用要点

该系列的工程价值在于每一阶段都可独立替换:

  1. 换股票池:修改 config_tickers.py 中的 ticker 列表,或直接向YahooDownloader(ticker_list=[...])传入自定义代码;如需其他数据源,仓库 data_processors 还提供了 Alpaca、CCXT、EODHD、JoinQuant、WRDS 等多种实现;
  2. 换特征:在FeatureEngineer中增删tech_indicator_list(参考 config.py 的INDICATORS),或开启user_defined_feature=True添加自定义特征列,需同步更新state_space公式;
  3. 换算法/超参:通过agent.get_model("ppo", model_kwargs=PPO_PARAMS)传入自定义超参字典;仓库还提供了 ElegantRL 与 Ray RLlib 的接入实现;
  4. 换回测区间与风控:调整TRADE_START_DATE/TRADE_END_DATEturbulence_threshold,观察不同市场状态下的策略表现;
  5. 延伸应用:同一套"环境层 - 智能体层"结构被复用于 portfolio_allocation、cryptocurrency_trading、high_frequency_trading 等应用;环境层的其他实现见 env_stock_trading 与 env_portfolio_allocation 目录。

六、总结

Stock_NeurIPS2018 系列展示了 FinRL 将论文方法论落地的完整链路:YahooDownloader负责标准化的 OHLCV 获取,FeatureEngineer完成技术指标、VIX 与湍流指数的特征工程,StockTradingEnv提供带交易成本、风控与奖励缩放的市场模拟器,DRLAgent统一封装 5 种 Stable Baselines 3 算法,最后以 MVO 与 DJIA 为基线完成样本外回测。这套"数据层 → 环境层 → 智能体层 → 回测层"的分层设计,也正是 FinRL 三层架构文档 所倡导的核心思想,可平滑迁移到研究者自定义的股票池、特征体系与交易场景中。

  • 金融科技
  • 强化学习
  • 人工智能

【免费下载链接】FinRL

FinRL®: Financial Reinforcement Learning. 🔥

项目地址:https://gitcode.com/gh_mirrors/fi/FinRL-Library
点击查看免费下载

相关推荐

上一篇:如何快速上手BitCPM4-CANN-3B-unquantized:从环境搭建到首次微调的完整教程
下一篇:BiliTools:跨平台哔哩哔哩工具箱终极指南,一键下载B站所有资源

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

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询