简介:一个基于深度强化学习PPO算法的期货量化交易项目,覆盖交易环境构建、模型设计、训练与测试完整流程,定位清晰:主要面向计算机、数据科学、人工智能、金融科技等专业学生,以及希望在量化方向快速入门的高年级开发者和企业员工,适合课程设计、毕业设计、大作业或初期项目演示。整体压缩包共70个文件,大小约85KB,包含26个Python源码文件(如FutureEnv交易环境、PPO模型、Informer与Multihead系列特征网络)、20个pyc编译文件、12个工程配置xml、4个txt使用说明、2个csv数据与2个md文档,并附带venv虚拟环境配置,解压后按说明将项目目录改为英文名即可正常运行。通过FutureEnv可自定义交易动作、状态与奖励,结合Informer、LSTM等模型提取期货特征,再由PPO智能体完成策略优化,可让学习者同时掌握强化学习框架与金融时序建模思路。目前已有228人浏览学习,代码完整且验证稳定,训练与测试脚本齐备,也便于后续二次开发扩展交易策略。
1. 拿到这份 PPO 期货量化源码,先别急着双击运行
先泼一盆冷水:标题里“直接使用”四个字,指的是 venv 环境已经备好、依赖基本齐了,指的不是“你双击 train.py 就能躺着赚钱”。很多人在拿到这种带环境的量化交易项目时,第一反应是激活 venv、跑回测、看收益曲线,然后被一个漂亮数字冲昏头,最后实盘直接翻车。真正的用法是把它当做一个“能跑的强化学习交易脚手架”,把数据、奖励、参数、风险控制全部重新确认一遍,再谈下一步。
这个 zip 的价值在于三件事:省掉你搭建 venv 环境的时间,省掉你从零写 PPO 更新循环的时间,省掉你对交易环境建模的试错成本。适合的读者是有一定 Python 基础、想用深度强化学习做期货策略但不想从理论推导开始的从业者。它不能解决“策略是否有效”的问题,只解决“策略能不能被训练、被回测、被复盘”的问题。下面按我接手这类项目时习惯的顺序,从建模选型讲到参数坑,把这套东西拆开讲清楚。
2. 为什么期货量化的强化学习方案选了 PPO:状态、动作、奖励的建模
2.1 期货交易环境与 PPO 的匹配点
期货和股票的差异在于三件事:双向交易、杠杆、合约到期换月。这三件事直接决定了动作空间和奖励函数的设计方式。股票策略常用“买入并持有”作为基线,而期货策略天然需要多空仓位,动作不能只是 0 或 1,更像一个连续区间内的仓位控制。
PPO(Proximal Policy Optimization)是深度强化学习里适合连续动作空间的算法之一。它比 DQN 稳定,比 A2C 样本效率高,比 SAC 调参成本低——这是它成为量化交易项目常见选型的原因。PPO 通过裁剪(clip)限制每次策略更新的幅度,避免一步更新过大导致奖励崩塌,这在非平稳的行情数据里尤其重要。行情数据本身就是时变的,如果策略更新太激进,模型会快速拟合最近一段行情,然后行情切换时大面积回撤。
另外一个关键匹配点是 PPO 支持离散和连续动作混用。期货交易里,动作可以是“手数”这样的小范围离散值,也可以是“仓位比例”这样的连续值。大多数项目选择连续动作空间,然后在执行层做取整和仓位限制,这样策略表达力更强,也方便加滑点成本约束。
2.2 一个可复现的交易环境类:状态空间与动作空间怎么定
在强化学习里,环境是核心。标题里的源码包通常会包含一个自定义 Gym 环境,接口一般长这样。下面这个示例是我见过的大多数交易项目的共同结构,不是某个特定项目的原码,但思路是一致的:
# trading_env.py import numpy as np import gymnasium as gym from gymnasium import spaces class FuturesTradingEnv(gym.Env): def __init__(self, data, lookback=60, max_pos=1.0, cost_rate=0.0001): super().__init__() self.data = data # DataFrame: open, high, low, close, volume, funding_rate self.lookback = lookback self.max_pos = max_pos self.cost_rate = cost_rate self.idx = lookback self.pos = 0.0 # 状态: 最近 lookback 根的归一化价格序列 + 持仓 + 未实现盈亏 self.observation_space = spaces.Box( low=-np.inf, high=np.inf, shape=(lookback * 5 + 2,), dtype=np.float32 ) # 动作: 连续仓位, -1 表示满仓空, 1 表示满仓多 self.action_space = spaces.Box(low=-1.0, high=1.0, shape=(1,), dtype=np.float32) def _get_obs(self): window = self.data.iloc[self.idx - self.lookback: self.idx] close = window["close"].values ret = np.diff(close) / close[:-1] obs = np.concatenate([ (window["open"] - close) / close, (window["high"] - close) / close, (window["low"] - close) / close, ret[:self.lookback - 1], np.array([self.pos, self.pos * close[-1]]), ]).astype(np.float32) return obs def step(self, action): target_pos = np.clip(action[0], -self.max_pos, self.max_pos) trade_cost = abs(target_pos - self.pos) * self.cost_rate price = self.data.iloc[self.idx]["close"] # 以中间价成交, 只算成本, 不算滑点 self.pos = target_pos self.idx += 1 next_price = self.data.iloc[self.idx]["close"] pnl = self.pos * (next_price - price) reward = pnl - trade_cost done = self.idx >= len(self.data) - 1 return self._get_obs(), reward, done, False, {} def reset(self, seed=None): self.idx = self.lookback self.pos = 0.0 return self._get_obs(), {}这个环境类的关键参数有三个:lookback决定模型看到多长的历史窗口,max_pos限制最大仓位倍数,cost_rate是每笔换仓的固定成本比例。我一般会把cost_rate设为 0.0001 的级别,对应期货的交易所手续费加冲击成本,而不是只设成交价差。如果项目自带环境里成本设成 0,那它回测出来的收益曲线没有任何参考意义。
观察空间这里用的是“相对变化率”而不是绝对价格,原因是绝对价格在不同的合约期、不同品种之间数值差异巨大,模型很难统一处理。你把开盘价、最高价、最低价都除以当前收盘价,就得到一组无量纲分布,这能显著提升 PPO 的训练稳定性。
2.3 奖励函数设计:别只用“浮盈”当 reward
很多第一次接触这个项目的人会困惑:明明奖励就是“赚的钱”,为什么还要单独设计?问题出在时间尺度上。如果每一步的奖励是当前浮盈减去上一步的持仓成本,那么在趋势启动早期,模型得到的奖励信号非常稀疏且波动巨大,PPO 的优势估计(advantage estimation)会被噪声淹没。
更稳的做法是把奖励拆成三块:单步盈亏、交易成本惩罚、风险惩罚。一个常见的设计如下:
# reward_design.py def compute_reward(cur_price, prev_price, pos, prev_pos, max_pos, gamma=0.0): # 价格变动带来的持仓盈亏 pnl = pos * (cur_price - prev_price) / prev_price # 换仓成本: 按仓位变化的绝对值计 cost = abs(cur_price - prev_price) / prev_price * abs(pos - prev_pos) / max_pos # 风险惩罚: 仓位越接近上限, 惩罚越大, 抑制满仓梭哈 risk_penalty = 0.1 * (pos / max_pos) ** 2 reward = pnl - cost - risk_penalty return reward三个项的作用分别对应:pnl是真实盈亏,让模型学习赚钱的方向;cost是交易摩擦,防止模型频繁换手;risk_penalty是风险预算,防止模型在不确定性高时把仓位推到极限。参数gamma在 reward 这里指的不是强化学习的折扣因子,而是你对风险惩罚的缩放系数,一般取 0 到 0.2 之间。
“只用浮盈当奖励”是新手最容易踩的暗坑。不扣手续费和滑点,PPO 模型会发现“频繁买卖”是最省力的赚钱方式,因为奖励变化完全由价格波动主导,换手成本被忽略。最终你会得到一个训练曲线很漂亮、实盘完全跑不动的策略。这个坑几乎每个接这种项目的人都会遇到一次。
2.4 PyTorch + Stable-Baselines3 还是自写 PPO 更新循环
这决定了你后续调参和排错的复杂度。如果项目里只有一套“手写 PPO”,你就要做好阅读几百行更新逻辑的心理准备。手写 PPO 的优势是完全可控,任何组件都能改——比如把 GAE 换成 TD(λ),或者在 loss 里加一个均衡多空的约束项。但缺点也很直接:手写版本在数值稳定性上不容易保证,clipping 边界和 advantage normalization 一旦写错,训练曲线看起来正常,实际策略已经废了。
我建议的路径是项目如果有 Stable-Baselines3 的依赖,优先用 SB3 的 PPO 实现来跑通基线,然后再把手写版本作为对照。这不是说 SB3 一定更好,而是 SB3 的默认参数经过大量环境验证,能帮你快速判断问题是出在环境还是出在算法。下面是一段典型的训练调用:
# train.py from stable_baselines3 import PPO from trading_env import FuturesTradingEnv import pandas as pd df = pd.read_csv("data/rb_daily.csv", parse_dates=["date"]) df.set_index("date", inplace=True) env = FuturesTradingEnv(data=df, lookback=60, max_pos=1.0, cost_rate=0.0001) model = PPO( "MlpPolicy", env, learning_rate=3e-4, n_steps=2048, batch_size=64, gamma=0.99, gae_lambda=0.95, clip_range=0.2, ent_coef=0.01, verbose=1, ) model.learn(total_timesteps=500_000)ent_coef=0.01是我在这个项目上最常动的参数。它控制探索熵的权重,值太高会让策略像随机交易,值太低会让策略过早固化。期货行情有明显的趋势与震荡周期,熵系数设得太死,模型会在一段行情上过拟合,行情切换后瞬间翻车。启动时可以偏大一点(0.01 到 0.03),然后逐步降到 0 附近,看回测曲线是否还能保持稳定。
3. 把 zip 里的项目在本地跑通:venv 环境与最小可运行命令
3.1 解压后的目录结构:一个可直接用的强化学习量化项目的常见布局
拿到 zip 解压后,先不要直接运行。先花两分钟看目录结构。一个带 venv 环境的 Python 量化项目,常见布局是这样:
venv/虚拟环境目录,里面是完整的 Python 解释器和 site-packagesdata/行情数据文件,常见格式是 CSV,通常包含日线或分钟线数据src/或strategies/策略源码,包括环境类、数据预处理、特征工程train.py训练入口backtest.py回测入口config/或config.yaml参数配置文件requirements.txt依赖清单
要注意一个现象:带venv的压缩包往往体积很大,因为里面包含了整个 Python 运行时。如果你解压后不能在本地正常激活,先检查是不是压缩包在传输过程中破坏了符号链接,这在 Windows 上比较常见,Linux 和 macOS 上也会遇到。
我一般会先把venv目录单独放在一边,先用系统 Python 的python -m venv venv_new重建一个干净的虚拟环境,再按requirements.txt重新安装依赖。这样做的原因不是怀疑作者的venv不可用,而是压缩包里的venv路径通常写死了解压目录,如果你把项目放在不同路径下,激活后会出现 Python 解释器路径不对的问题。
3.2 venv 环境的正确打开方式:不要直接 site-packages
正确操作如下:
cd your_project source venv/bin/activate # Linux/macOS # 或者 Windows: venv\Scripts\activate.bat python --version pip list | grep torch这一段要看两件事。第一,python --version显示的解释器版本必须和项目要求一致。带虚拟环境的项目最容易出现的问题就是作者用 Python 3.9,你的系统装了 3.11,而 PyTorch 编译版本的兼容性导致 import torch 报错。第二,pip list输出的核心包版本要记录,特别是torch、stable-baselines3、numpy、pandas这四个。这四个版本是后续排错的坐标基准。
如果你激活 venv 之后发现python命令还是指向系统目录,不要怀疑是命令输错了。先检查有没有在项目目录内执行命令,再检查 PATH 是否被其他工具覆盖。我见过最隐蔽的问题是 Conda 环境激活脚本覆盖了 PATH,导致 venv 里的 Python 失效。解决办法是临时把 Conda 的路径从 PATH 前位移到最后。
3.3 训练命令与参数对齐:从“能跑”到“能看结果”
假设一切正常,接下来就是最小可运行命令:
python train.py --config config/ppo_rb.yaml这时候你会看到一堆日志输出,包括观测空间 shape、policy 结构、每次 episode 的平均 reward 等。这个阶段最要紧的不是看 reward 涨没涨,而是确认以下三件事没有异常:第一个是环境初始化是否正常,日志里如果出现nan分量,说明数据预处理有问题;第二个是训练步数和总步数的比例,PPO 每轮更新的步数由n_steps决定,你至少要跑 20 轮以上更新,才能说训练过程走完了一个有效阶段;第三个是 tensorboard 日志是否落盘。
大多数项目会默认打开 TensorBoard 日志,你可以直接读取:
tensorboard --logdir logs/打开浏览器到 http://localhost:6006,重点看rollout_ep_rew_mean这个量。它表示每个 rollout 阶段的平均回报。如果这个指标在前 10 万步内持续不升,大概率不是超参问题,而是环境与模型之间存在结构性 bug。常见的一种是环境每次 reset 后,序列长度小于lookback,导致 obs 维数不定期出现震荡。
3.4 验证输出:agents 目录、模型 checkpoint、回测结果怎么读
训练结束后,项目一般会在models/或checkpoints/下生成 PPO 模型的 checkpoint 文件,常见格式是对应的.zip(SB3 的默认格式)。你不用急着打开看权重,先确认文件时间戳和训练结束时间是否一致。接下来看回测脚本:
python backtest.py --model models/ppo_final.zip --plot resutls/backtest.png这里的坑是回测数据必须和训练数据的时间段对齐。如果模型是在 2024 年数据上训练的,你却拿 2022 年数据回测,不是不行,但你要知道那测的是“跨周期泛化”,不是“同一策略在一个完整样本内的表现”。这两者的评判标准完全不同。大多数人把这两个放在一起混着看,导致对策略能力产生误判。
产出的回测结果里,至少要包含以下几项指标:累计收益率、年化波动率、最大回撤、夏普比率、胜率和盈亏比。如果脚本只输出一个收益率数字,那这个回测脚本是不完整的,不必对这个结果投入太多信任。
4. 跑通之后最常见的问题与排查:环境、数据与训练三处坑
4.1 现象:venv 激活后 import torch 报错
这是我遇到最多的头号问题。错误提示是ModuleNotFoundError: No module named 'torch',或者ImportError: libcudart.so.xx: cannot open shared object file。前者出现在 venv 没有正确加载时,你实际用的解释器是系统 Python,而系统 Python 没装 torch。后者出现在 torch 版本与本地 CUDA 驱动不匹配时。
原因通常是两层面:一是 activate 这一步失败,二是 torch 版本是带 CUDA 编译的,而你机器上 GPU 驱动版本偏旧或根本没有 NVIDIA GPU。解决方法是先查一眼解释器路径:
which python python -c "import sys; print(sys.prefix)"输出指向 venv 目录才行。如果 torch 报的是 CUDA 相关错误,最省事的做法是卸载现有 torch,安装 CPU 版本。很多初学者觉得装了 GPU 版就万事大吉,结果项目在只有 CPU 的机器上直接崩溃。哪怕你的机器有 GPU,训练前也要确认torch.cuda.is_available()返回 True。
4.2 现象:训练 loss 不下降,reward 一直横盘
PPO 的训练 loss 不像监督学习那样一定会下降。你要关注的是核心指标 divergence 和 clip fraction。但如果你发现跑了 20 万步,reward 均值始终在 0 附近波动,没有任何上升趋势,问题大概率出在状态空间。
最常见的一个系统性 bug 是:数据没有做归一化,或者归一化在时间维度上“泄漏”。比如你按照全部数据的均值方差做归一化,再切 train/test,这就是标准的未来函数。归一化必须只使用训练集前 60 天滚动窗口内的统计信息。另一个问题是 reward 的数量级太小。如果你的价格数值在 3000 到 6000 之间,单步收益通常是零点几个点,reward 算出来是 0.0001 这个量级,策略网络很难学到有意义的梯度。
解决方法是先增大 reward 权重,或者使用百分数收益(ret_pct = (price - prev) / prev * 10000),让 reward 落在 0 到 10 的范围内。这一步在几乎所有 PPO 交易项目里都是必需品。我见过太多人把 reward 卡在 1e-4 量级还怀疑是算法不收敛,其实只是数值太小说不出话。
4.3 现象:回测收益很高,但仔细一看是用未来信息买卖的
这是量化交易项目里最隐蔽的杀手。常见的爆发点有三个:
- 数据文件里的价格列已经做过复权,而你的换仓决策用的是“当日收盘后”价格,却在同一根 K 线的收盘价上成交。严格来说,你只能在下根 K 线开盘成交。
- 特征计算用了未来窗口。比如你用了 “下一周期的最高价” 来计算止损位,这在数据表里看起来正常,但实盘根本拿不到。
- 训练、验证、测试三个集合混合做过标准化参数估计,导致验证集间接依赖测试统计量。
排查方法是在回测引擎里加一行assert,确保信号索引比成交索引大 1:
# backtest.py 中典型检查 assert signal_time.index >= exec_time.index.shift(1).fillna(method="bfill")如果这个断言过不了,你的回测结果就是一个纯粹的数字游戏。处理方式是重构数据流,让特征只依赖过去的数据,成交价用shift(1)对齐下一根 K 线。
4.4 现象:同一套代码,模型在 A 机器上跑得通,移到 B 机器上直接崩
原因来自 PyTorch 版本和 Python 版本的微差异。尤其是 torch 1.x 与 torch 2.x 之间的 API 不兼容性,在 checkpoint 加载时表现突出。解决办法是加载模型时指定 map_location,并保持训练时的库版本一致:
model = PPO.load("models/ppo_final.zip", map_location="cpu")如果你的项目架构和原环境不一致,加载时也要先确认 policy 参数。很多人没有意识到,Stable-Baselines3 保存的 zip 里包含的是整个模型,不是单纯的权重文件,直接torch.load会报各种路径错误,这不是模型坏了,是你加载姿势不对。
4.5 现象:中文路径导致数据读取失败
压缩包的文件名如果是量化交易-...zip,解压到中文目录后,pandas 读取 CSV 在某些编码下会乱码,或者某些库对中文路径处理不好导致文件打不开。看似是玄学,其实由两个原因组成:一是 Windows 控制台默认编码不是 UTF-8,二是 Python 在 Windows 上对非 ASCII 路径的支持参差。
解决办法是双管齐下,先改项目路径为纯英文,再把 CSV 的头部列名改成英文。如果数据文件本身有中文列名,读取时强制指定编码:
df = pd.read_csv("data/rb_daily.csv", encoding="utf-8-sig")尽量不要依赖sys.setdefaultencoding这种全局修改,因为新版 Python 已经不支持了。把路径和编码问题在项目启动时一次性解决,比在训练中排查省时间得多。
5. PPO 在期货策略上的参数调优与回测验证
5.1 PPO 超参数在期货场景的起点值
期货行情和跑步机器人不同,数据信噪比极低,PPO 的默认超参往往对市场是失配的。Stable-Baselines3 的默认参数是面向 MuJoCo 控制任务设计的,直接拿来用在期货上不能算错,但结果通常平庸。下面是一张我尝试过多次的基准参数表:
| 参数 | SB3默认值 | 期货日线建议起点 | 说明 |
|---|---|---|---|
learning_rate | 3e-4 | 5e-5 ~ 1e-4 | 日线样本少,学习率太大容易过拟合 |
n_steps | 2048 | 4096 ~ 8192 | 覆盖更多交易日,让优势估计更稳 |
batch_size | 64 | 128 ~ 256 | 与 n_steps 保持比例 |
gamma | 0.99 | 0.99 ~ 0.999 | 日线策略要看长周期收益 |
gae_lambda | 0.95 | 0.97 ~ 0.99 | 放大长期信号 |
clip_range | 0.2 | 0.1 ~ 0.2 | 行情不平稳时取小值 |
ent_coef | 0.0 | 0.005 ~ 0.03 | 防止策略过早固化 |
n_epochs | 10 | 3 ~ 5 | 每轮更新次数,过大容易策略漂移 |
口径有两点值得强调。第一,learning_rate要和n_epochs联动。如果你减小学习率但保持n_epochs为 10,训练时间会大幅拉长,而收益没有本质提升。第二,日线策略建议n_steps取 4000 以上,因为一天只有一根 K 线,2048 步只覆盖大约 8 个交易年,对策略来说数据量远远不够在一段完整牛熊周期里学习。
5.2 奖励缩放和交易成本的敏感性
训练完成后,我一般会做双重验证。第一重是把回测里的cost_rate从初始值翻倍,重新执行步进逻辑,看看收益曲线的最大回撤是否还在可控区间。第二重是把 reward 的尺度从“原始收益”放大 1000 倍,重新训练一小段时间,观察学习曲线的稳定性。
这两步是常被跳过的环节,但必要性很强。因为深度强化学习在交易场景里的一个严重隐患是:策略会在“忽略交易成本”的边缘疯狂试探。很多模型在无摩擦环境下训练得很好,一旦加上真实成本就开始反复换仓、利润被手续费吃掉。提前用成本翻倍做压力测试,能帮你看出策略是否具备基本的鲁棒性。
我建议把成本压力测试作为模型发布的必备门槛。如果成本翻倍后夏普比率下降超过 30%,那这个模型的性能就过于依赖摩擦假设,不满足上实盘的最低标准。
5.3 用滚动窗口和交易成本压力测试判断是否过拟合
很多人会用“回测收益率很高”来评价一个模型,这非常危险。期货行情中存在大量低效时间窗口,PPO 这样高容量的模型很容易在单一时间段内找到貌似显式的规律,换到下一个时间段即失效。正确做法是固定训练窗口,滚动测试样本外表现。
# walk_forward.py from stable_baselines3 import PPO from trading_env import FuturesTradingEnv results = [] for fold in range(5): train_data = df[df.index < f"202{fold}-01-01"] test_data = df[(df.index >= f"202{fold}-01-01") & (df.index < f"202{fold + 1}-01-01")] env = FuturesTradingEnv(data=train_data, lookback=60, cost_rate=0.0001) model = PPO("MlpPolicy", env, learning_rate=1e-4, n_steps=4096, verbose=0) model.learn(total_timesteps=300_000) test_env = FuturesTradingEnv(data=test_data, lookback=60, cost_rate=0.0001) obs, _ = test_env.reset() done = False total_ret = 0.0 while not done: action, _ = model.predict(obs, deterministic=True) obs, reward, done, _, _ = test_env.step(action) total_ret += reward results.append(total_ret) print("fold returns:", results)这段代码做了五折滚动验证,每一折都用前一段训练、后一段测试。如果五折结果的正负不一致,说明策略不具备跨周期稳定性。参数deterministic=True格外重要,它让预测过程不使用随机采样,只看当前策略的确定性输出。这个技巧在发布模型前是必用的,否则你测的其实是策略的随机噪声期望,而不是策略的真实能力。
6. 从“能跑”到“可持续迭代”:多品种扩展与实盘接手的现实路径
如果整个项目只是单品种日线策略,跑通只是第一天的事。真正让它产生长期价值,是把单品种环境扩展成多品种,同时调整时间尺度。把环境类稍作改造,让data换成包含多个品种的字典,动作空间保持同一维度,但奖励需要重新设计——因为你不能拿不同品种的绝对收益直接相加。
我一般会把每个品种的收益先按波动率标准化,再按资金权重汇总。核心代码里是这样处理的:
# multi_symbol_reward.py def portfolio_reward(pnls, vols, weights=None): # pnls: dict, 每个品种的本期盈亏 # vols: dict, 每个品种的滚动年化波动率 # 先按波动率归一化, 防止高波动品种主导奖励 norm_pnls = {k: pnls[k] / vols[k] for k in pnls} total = sum(norm_pnls.values()) return total这一步是直接把奖励从“单个品种赚多少钱”变成“单位风险下的边际回报”,训练出来的策略整体仓位分配会更稳定。接手这类项目后,我还会做三件事:第一是用pytest补上数据加载和向量化的最小单元测试;第二是把cost_rate、lookback、ent_coef写进config.yaml,而不是散落在 train.py 里;第三是给回测脚本接上导出功能,把每天的仓位、收益、成交记录落盘成 CSV,方便后续归因分析。
最后一条经验:把模型从日线切到分钟线之前,先把特征工程的周期对齐。日线模型直接套用分钟线数据,奖励序列的方差会瞬间膨胀,PPO 的 rollout 统计量全乱。这时老手会选择把gamma从 0.99 调到 0.995 甚至 0.999,并且把lookback从 60 根 K 线提高到一个交易日对应的根数。做这些调整时,必须同步修改环境的步进逻辑,否则回测时间戳对不上,结果不可信。
我在把第一个 PPO 期货策略部署到模拟盘时,得到的最大教训是:环境里的“下一根 K 线开盘成交”不是保守做法,而是唯一能上实盘的规则。提前在环境里严谨一点点,后面写复盘代码能省下大把时间。PPO 能帮你发现行情里的模式,但它不能替你过滤掉数据泄漏和成本幻觉。希望这些踩坑经验帮到你,让这份源码真正变成你迭代策略的起点,而不是又一个“跑完就扔”的黑匣子。
本文还有配套的精品资源,点击获取