
简介深度Q学习DQN在金融量化领域的典型落地项目面向有Python基础、对强化学习交易策略感兴趣的开发者或研究人员。资源包共24个文件以Python脚本、CSV数据、TensorFlow模型权重及索引文件为主压缩后约12.21MB。其中核心脚本实现DQN训练与交易决策配套数据预处理、股票交易环境模拟、策略回测等模块数据文件提供分钟级与日级行情训练好的网络检查点便于断点续训。压缩包内目录组织清晰可按序号对比不同训练阶段的模型效果。项目从数据清洗到模型评估形成完整流程可帮助读者理解状态建模、奖励设计及经验回放等关键环节。目前已有330人学习适合希望将深度学习用于股票交易策略设计、需要参考完整工程结构的初学者与进阶者。1. 深度Q学习股票交易这套源码到底能做什么做量化交易的人大概率都经历过这样一幕手工写好一个基于技术指标的策略回测时收益曲线漂亮得像假的一上实盘就被市场按在地上摩擦。原因不复杂——传统策略的买卖规则是人为写死的突破买入、均线金叉买入规则一旦固定面对市场风格的切换就没有任何自适应能力。这套基于深度Q学习网络的股票交易策略源码核心逻辑是让智能体自己“试错”着学交易它不看你有几根均线也不管你背了多少种K线形态而是通过与模拟环境不断交互在每一次买卖动作后根据奖励反馈逐步调整自己的决策网络。经过若干轮训练后网络会形成一套自己的交易策略——可能是追涨也可能是抄底完全由历史数据说了算。适合的人群很明确已经会Python和基本机器学习但想了解强化学习怎么落到金融时序数据上或者需要一个能跑起来的基线框架去改造自己的交易思路的开发者。这份源码的完成度比很多教学Demo要高它不只是把DQN算法跑通还配了数据预处理、交易环境模拟、训练脚本、回测脚本和已保存的网络权重是一个能直接端到端跑起来的完整闭环。2. 模块拆解从数据预处理到核心算法的完整链路2.1 数据层设计分钟线与日线到底选哪个拿到源码包后先看清楚数据文件。data.csv是通用数据集大概率是训练用的主数据sz000001_min.csv和sz000001_day.csv是平安银行sz000001的分钟级和日级行情数据。你不需要纠结这两个文件是不是真实行情重要的是理解它们代表两种不同的决策粒度。日线数据的样本量少但单条数据的信息密度高一条日K包含了当天所有交易博弈的结果分钟数据样本量大训练时模型见过的“画面”更多但噪音也更大——一分钟内的价格波动很多是市场情绪而不是理性交易的结果。源码默认用分钟级数据做训练这个选型逻辑是对的DQN是数据饥渴型算法MiniBatch的采样需要有足够多的状态转移样本日线数据几百条根本喂不饱一个深层网络。prepare_data.py做的事情就是把CSV里的原始行情转换成DQN能消费的状态-动作-奖励序列。它负责剔除停牌日、对齐时间戳、计算技术指标、归一化。这里的归一化尤其要注意源码里大概率用的是MinMaxScaler或者Z-Score千万不要跳过这一步裸喂原始价格否则价值网络的输出会因为价格尺度问题直接爆炸。# prepare_data.py 核心逻辑依据源码梳理的常规实现 import pandas as pd import numpy as np def load_and_preprocess(csv_path, window_size10): df pd.read_csv(csv_path) # 用百分比收益率替代原始价格作为核心特征 df[returns] df[close].pct_change() # 简单移动平均作为趋势特征 df[ma5] df[close].rolling(window5).mean() # 删除缺失值pct_change会产生NaN df df.dropna().reset_index(dropTrue) # 归一化——注意分列做不要整表一起标准化 from sklearn.preprocessing import StandardScaler scaler StandardScaler() feature_cols [returns, ma5] df[feature_cols] scaler.fit_transform(df[feature_cols]) return df代码的逻辑很简单但几个细节要注意。用pct_change而不是原始价格是为了让模型看到的是相对变化而非绝对价格不同价格区间的股票才能共用一套策略rolling(5).mean()是在做趋势特征提取这里窗口大小是个超参数取值5意味着看一周的交易趋势如果你想做更长期的策略把它调到10或20会产生截然不同的决策行为。dropna是必须的因为pct_change在序列首尾会产生NaN任何NaN流入神经网络都会让梯度计算失效。如果你有自己的数据源比如从Wind或聚宽导出的CSV只要保证列名是open、high、low、close、volume、datetime基本可以无缝替换。列名不一致时需要在prepare_data.py里做一次rename。2.2 交易环境stock_env.py里的状态转移与奖励函数stock_env.py定义了一个OpenAI Gym风格的环境这是整个源码的灵魂所在。强化学习里环境决定了智能体“看什么”、“能做什么”和“做对了有什么奖赏”。交易环境的经典设计是状态是当前持仓状态加上最近N个时间步的市场特征动作有三种——买入、卖出、持有奖励是执行动作后账户总资产的变化。写交易环境的难点在奖励塑形Reward Shaping。直接用账户总资产变化作为奖励是理论正确但实践中很难收敛的做法——奖励稀疏且噪音大一次错误的卖出可能要很多步之后才能被“追认”。常见的补救做法是加入持仓惩罚项。源码里的奖励函数几乎可以肯定是基于组合收益率微调的版本账户总资产的百分比变化再加上一个小的持仓惩罚系数目的是防止模型学会“永远满仓不动”这种偷懒策略。# stock_env.py 内容复位——环境与DQN交互的接口 class StockEnv: def __init__(self, df, initial_balance10000, max_hold1000): self.df df self.balance initial_balance self.max_hold max_hold self.current_step 0 self.holdings 0 def step(self, action): # 动作映射: 0卖出, 1持有, 2买入 price self.df.loc[self.current_step, close] if action 2 and self.holdings self.max_hold: # 买入全仓买入 buy_amount self.balance // price cost buy_amount * price self.balance - cost self.holdings buy_amount elif action 0 and self.holdings 0: # 清仓卖出 self.balance self.holdings * price self.holdings 0 self.current_step 1 total_asset self.balance self.holdings * price reward (total_asset - self.prev_asset) / self.prev_asset self.prev_asset total_asset # 终止条件数据走完或资产归零 done self.current_step len(self.df) - 1 or total_asset 0 next_state self._build_state() return next_state, reward, done, {asset: total_asset}这段代码里最值得品味的是终止条件设计。资产归零才终止是一个隐含的风险预警——DQN训练早期模型不会选股买什么都可能连续下跌如果不设单笔亏损上限一次灾难性的交易就能清空整个账户的余额而模型不会从中吸取教训只会觉得“这个世界全是负奖励”。实战中我一般会在环境里加一个单步亏损阈值参数比如单步亏损超过5%就强制平仓并给一个额外惩罚这样训练过程会稳定很多。这里的max_hold本质上是仓位限制把它调到极大值等价于允许无限持仓但这会让模型学会一种危险的策略——反正买错也就是跌几个点我就一直买买买。设置一个合理的最大持仓量能约束模型的激进程度。2.3 DQN算法核心DQN_trade.py里的经验回放与目标网络这个文件是全项目的高潮也是检验源码成色的关键。深度Q学习网络的基础公式是Bellman方程Q(s,a) r γ * max(Q(s,a))。它表达的意思是当前状态下执行某个动作的价值等于立即得到的奖励加上未来状态的折扣价值。DQN最大的贡献是解决了Q-learning用表格存储导致维度爆炸的问题让神经网络来拟合Q值。一个合格的DQN实现必须包含三个关键部件经验回放池Replay Buffer、目标网络Target Network、以及ε-greedy探索策略。经验回放池的作用是打破时序数据之间的相关性——股票数据高度自相关连续样本之间几乎没有独立性脑抽一样地按顺序训练网络一定会导致不收敛目标网络的作用是解决训练震荡问题因为Q值更新时用的是同一个网络的输出作为目标往左跳一步又被自己的左边拉回来容易原地抖。# DQN_trade.py 核心训练框架 class ReplayBuffer: def __init__(self, capacity10000): self.buffer deque(maxlencapacity) def add(self, state, action, reward, next_state, done): self.buffer.append((state, action, reward, next_state, done)) def sample(self, batch_size32): batch random.sample(self.buffer, batch_size) # 转成适合网络输入的批量张量格式 states np.array([x[0] for x in batch]) actions np.array([x[1] for x in batch]) rewards np.array([x[2] for x in batch]) next_states np.array([x[3] for x in batch]) dones np.array([x[4] for x in batch]) return states, actions, rewards, next_states, dones class DQN: def __init__(self, state_dim, action_dim3): self.q_net self._build_net() # 在线网络 self.target_net self._build_net() # 目标网络 self.target_net.load_state_dict(self.q_net.state_dict()) self.optimizer torch.optim.Adam(self.q_net.parameters(), lr1e-4) self.buffer ReplayBuffer(capacity20000) self.gamma 0.99 # 折扣因子 self.epsilon 1.0 # 探索率 self.epsilon_min 0.01 self.epsilon_decay 0.995 def act(self, state): if np.random.rand() self.epsilon: return np.random.randint(0, 3) # 随机动作探索 with torch.no_grad(): q_values self.q_net(state) return q_values.argmax().item() # 利用网络决策 def learn(self, batch_size32): if len(self.buffer) batch_size: return states, actions, rewards, next_states, dones self.buffer.sample(batch_size) # 计算当前Q值 q_values self.q_net(states).gather(1, actions.unsqueeze(1)) # 计算目标Q值用目标网络增加训练稳定性 with torch.no_grad(): next_q_values self.target_net(next_states).max(1)[0] targets rewards self.gamma * next_q_values * (1 - dones) loss F.mse_loss(q_values.squeeze(), targets) self.optimizer.zero_grad() loss.backward() self.optimizer.step()核心参数的作用需要说明。gamma是折扣因子取0.99意味着模型重视长期收益——今天卖掉赚的1%和未来可能赚的1%在模型眼里几乎等价——这是长周期交易的合理假设如果你做的是高频策略gamma应该收到0.9以下因为未来的不确定性太高今天的钱更值钱。epsilon从1.0开始指数衰减到0.01这代表训练前期主要靠随机动作探索环境市场行情后期才会逐渐收敛到利用学到的知识来做决策。epsilon_decay0.995意味着每轮训练后探索率乘0.995大约100轮后探索率只剩0.6左右这个衰减速度适合小规模数据集如果训练数据量大调成0.999会更稳妥。目标网络和在线网络之间没有做Soft Update软更新也就是说不是每步都微调目标网络而是每隔固定步数将在线网络的参数硬拷贝过去。源码里用checkpoint来管理模型保存。这个机制是DQN收敛的关键调节旋钮——目标网络更新频率太低模型学得慢更新太频繁目标随时在变训练震荡。2.4 训练流程与checkpoint机制从零训练断点续跑readme.txt里如果写了运行方式多半是“先运行prepare_data.py再跑DQN_trade.py训练最后用test.py回测验证”这个三段式流程。训练脚本的核心逻辑是在每个episode结束后做一次模型评估把得分最高的权重存下来。这个机制本身没问题但新手一定要把训练好的模型到底是什么搞明白saved_network文件夹下的network-dqn-5.meta到network-dqn-9.meta这些文件是TensorFlow的checkpoint里面保存的是网络结构和图结构。注意看文件里既有.meta又有.data和.index说明这份源码是在TensorFlow 1.x时代的产物。# test.py 模型加载与回测调用 def load_model_for_testing(model_path): # 老版本TF的加载方式源码依赖 checkpoint 恢复 saver.restore(sess, model_path) # 回测主循环 state env.reset() while not done: action agent.act(state, exploitTrue) # 关闭探索 state, reward, done, info env.step(action) portfolio_values.append(info[asset])这里exploitTrue是关键回测时必须把epsilon强制设为0让模型完全按照其学到的策略来决策而不是继续随机试。很多第一次跑源码的人拿到一个很高的回测收益兴奋之余忘了确认自己是否真的关闭了探索——如果回测时模型还在以1%的概率随机乱动那多出来的收益完全是噪音不是策略的本事。2.5 目标网络更新的隐藏细节每轮训练结束后源码会把在线网络的权重复制到目标网络。这看起来简单但在金融环境下有个容易被忽视的问题DQN假设环境是平稳的可市场是非平稳的。目标网络的定期硬更新会导致策略出现周期性波动——一旦目标网络被同步下一轮的策略就会突然“变调”周而复始。所以有些改良版本会采用Polyak Averaging让目标网络缓慢跟踪在线网络一步一微调。不过这是后话先用硬更新版本的源码跑通流程再谈改造。3. 从零跑通训练数据准备、参数调试与模型保存3.1 环境准备与依赖安装这份源码的运行环境是Python 3.6/3.7配合TensorFlow 1.x。如果你想在一个干净的虚拟环境里复现依赖清单大致是numpy、pandas、scikit-learn、tensorflow1.14或1.15。注意不要直接用pip install tensorflow因为那会装到最新的2.x版本代码里的sess.run和saver.restore那一整套API全部失效。这个坑几乎每个拆源码的人都会踩一遍。# 创建虚拟环境并安装对应版本依赖 python3 -m venv dqn_trade_env source dqn_trade_env/bin/activate pip install numpy1.16.0 pandas0.25.0 scikit-learn0.21.3 tensorflow1.15.0TensorFlow 1.15是最后一代兼容旧API的版本安装时如果提示找不到对应版本的numpy就按提示降级numpy。Python版本建议用3.7或更低因为TF1.15官方不支持3.8以上的Python即使装上了也会出现莫名其妙的segmentation fault。3.2 数据预处理从sz000001分钟数据到状态张量先用sz000001_min.csv跑一遍。这是一个135分钟级别的数据文件大约一天半的交易分钟数样例量不大适合快速验证训练流程是否畅通。如果直接拿整个data.csv训练第一轮迭代就要等很久且一旦超参数设错白白浪费算力。# 从CSV到DQN可用的状态张量的完整流程 from prepare_data import load_and_preprocess import numpy as np # 选择分钟级数据作为训练集 df load_and_preprocess(sz000001_min.csv) print(f数据长度: {len(df)}) print(f特征列: {df.columns.tolist()}) # 可视化的状态构建函数 def build_state(df, t, window_size10): # 取过去window_size个时间步的特征作为当前状态 if t window_size: state df.iloc[:t1].values state np.pad(state, ((window_size-t-1, 0), (0, 0)), modeconstant) else: state df.iloc[t-window_size1: t1].values return state.reshape(-1)状态张量的维度等于window_size乘以特征列数。如果prepare_data.py里算了5列特征收益率、MA5等而window_size取10状态维度就是50。这个维度大小直接影响Q网络的输入层节点数改window_size前记得同步修改DQN_trade.py里网络的输入维度不然前向传播直接报shape mismatch。3.3 训练参数网格哪些参数值得优先调第一次训练建议把所有参数保持源码默认跑通之后再去动旋钮。如果你有精力做网格搜索优先级从高到低是learning rate、batch size、gamma、epsilon_decay。learning rate是模型能不能收敛的命门——设大了损失函数发散设小了训练半天参数纹丝不动。源码里默认1e-3到1e-4之间是比较合理的。# DQN_trade.py 中训练主循环的参数控制区 training_epochs 100 # 训练轮数 batch_size 64 # Mini-Batch大小 replay_capacity 20000 # 经验回放池容量 target_update_freq 200 # 目标网络更新步频 log_interval 10 # 每多少轮打印一次日志 for epoch in range(training_epochs): state env.reset() done False total_reward 0 while not done: action agent.act(state) next_state, reward, done, info env.step(action) agent.buffer.add(state, action, reward, next_state, done) if len(agent.buffer) batch_size: agent.learn(batch_size) state next_state total_reward reward # 每个epoch结束衰减探索率并保存checkpoint agent.epsilon max(agent.epsilon_min, agent.epsilon * agent.epsilon_decay) saver.save(sess, fsaved_network/network-dqn-{epoch}.ckpt)看这个结构target_update_freq参数并没有出现在循环里——很多简化版源码会在learn函数里用一句if self.learn_step % target_update_freq 0来做软更新。如果你发现目标网络从未被更新的话训练会非常不稳定因为在线网络每步都在变目标也跟着每步变。检查checkpoint里保存的meta文件数量如果连续多个epoch保存的模型在回测里表现都差不多说明目标网络可能没更新到位。batch_size对金融数据的影响比图像任务更大——股票数据的有效样本量远小于看似庞大的数据行数因为相邻样本高度相关一个64大小的batch里可能有30条样本来自连续的10分钟数据信息冗余严重。提升batch_size可以在一定程度上对冲这种相关性代价是每次权重更新的计算量变大了训练时间变长。我一般建议在32到128之间调。经验回放池的容量同样重要容量太小重复采样的概率高模型会“背”下这批数据而不是学到规律。3.4 从data.csv训练与分钟级数据训练的本质区别如果换上data.csv做全量训练相当于把模型的“视野”拉长到多只股票、多天的数据。这时数据的非平稳性问题就暴露出来了——训练集里包含牛市的暴涨段和熊市的急跌段模型在训练时会频繁遇到截然不同的行情特征Q值的收敛会明显变慢。如果你只关心sz000001这一只股票的策略就别换data.csv用它做泛化验证更合适。实战中我一般会这样安排先用分钟级数据训一个模型看看训练曲线能不能走出来能走出来再把数据扩充到日线级别看看模型是否出现严重的过拟合。如果一个在分钟线上学到的策略拿到日线上效果崩了说明策略本质上是在拟合分钟线的噪音而不是学到了真正的交易规律。4. 避坑指南训练TensorFlow DQN的五个典型翻车现场第一次拆这套源码的训练过程大概率会遇到一系列问题下面尽可能地记录下来。这些坑不是源码写得烂而是深度学习项目在金融数据上天然会踩的坎。4.1 训练Loss下降但收益曲线长期为负现象loss看上去在不停往下走但每个epoch结束打印出来的总资产在稳步缩水模型越训越“坚定的亏钱”。原因loss下降只能说明Q值的预测误差在减小但Q值本身可能是负的——模型的预测越来越准地指向“买任何股票都会亏”。这本质上是数据分布的问题如果训练数据大部分时间处于下跌趋势模型学到的最优动作就是永远持有现金。解决更换训练数据的时间段选择一个震荡或上涨行情的数据。如果只能用现有的下跌数据修改奖励函数增加“未能持仓踏空”的负奖励让模型意识到空仓也不是最优解。最重要的是设计一个对抗样本数据让环境里同时包含上涨和下跌的干净样本。4.2 模型参数保存后无法恢复现象训练结束时把checkpoint存进saved_network隔天开新终端把代码重跑一遍saver.restore报错说tensor name不匹配。原因这多半是运行环境不一致造成的。TensorFlow 1.x的checkpoint文件高度敏感于网络结构、变量名、以及操作顺序。你在训练脚本里是先建网络再建saver但测试脚本里如果先建了saver再去加载网络结构变量名就对不上。解决在test.py里先用训练脚本的DQN类完整地构建网络包括两层隐藏层和输出层的维度然后再建saver去restore。不要试图用tf.train.import_meta_graph来加载因为.meta文件的图结构是固化在训练那一刻的和当前脚本里的变量命名空间往往存在差异。4.3 训练过程中Loss是NaN现象训练跑到第几十步loss突然变成nan随后整个模型瘫痪。这个现象特别容易在第一次跑金融数据时出现。原因绝大部分情况是数据预处理把NaN漏进了输入张量。金融数据比图像数据脏得多——停牌日、涨跌停、开盘竞价异常都会在pct_change计算中产生无穷大或NaN值。另一个概率较低的可能是learning rate过大导致梯度爆炸。解决在prepare_data.py里加一个强制检查对任何包含非有限值的数据行直接drop。另外在reward计算处也做一层保护如果reward或next_state里出现NaN强制结束当前episode并清理回放池中的异常样本。# 数据清洗中的强制保护务必追加在预处理末尾 df df.replace([np.inf, -np.inf], np.nan) df df.dropna(subset[returns, ma5]) assert np.isfinite(df.values).all(), 存在非有限值检查数据源4.4 训练了500轮策略仍然像随机漫步现象epsilon已经衰减到接近0.01按理说模型应该开始利用学到的知识了但每一步的动作看起来还是随机买卖毫无规律。原因Q网络的拟合能力不够。金融数据的状态维度可能只有几十维但决策规律非常隐晦——你是看过去10分钟的价格和成交量变化但真正影响下一分钟走势的因素可能藏在几十个交易指标里网络容量不够时只能强行记住一部分模式表现出“部分随机”的假象。解决增加网络隐藏层的节点数或者增加一层。另一个重要旋钮是经验的利用效率——默认的replay buffer按均匀分布采样但金融数据里“大波动”时刻的信息价值远大于“死水微澜”的时刻。可以按时序误差TD-error做优先采样让网络多看那些预测错的样本。先不要改算法结构把hidden_size从32调到128试试。4.5 回测表现优秀但换一段数据就彻底拉胯现象在training数据的时间段内回测收益曲线相当陡峭拉长到另一段时间后就变成了一条直线成交量基本为零——模型几乎不再交易。原因这是策略过拟合的典型症状——模型学到的不是“交易能力”而是“这段历史行情的背诵答案”。一旦市场形态变了它对任何状态给出的Q值都无法区分买卖最好的选择就是什么都不做。解决把数据按时间顺序切分成训练集和验证集而不是随机切分比如前80%的时间段训练后20%的时间段做validation回测。如果验证集表现差在reward里增加交易惩罚成本让模型不那么容易陷入背诵模式。还有一个好习惯训练过程中每个epoch都在验证集上评估一次保存“验证集得分最高”而非“最后一轮的模型”这个checkpoint才能算真正有价值的资产。这些坑里最隐蔽的是第一个——loss下降和策略变聪明之间隔着一道鸿沟。新手会盯着loss曲线以为一切正常实际上模型的决策正在滑向深渊。我后来养成一个习惯每轮训练结束不只是看总资产和loss还要打印出当前模型的买卖次数占比。如果买卖比例超过7:3甚至接近满仓操作说明模型已经学歪了需要立刻停下来检查数据分布和奖励函数。5. 策略验证的完整流程如何判断模型是真会交易还是假把式模型训练完毕接下来要做的是严谨的回测验证。跳过这一步直接上模拟盘甚至实盘等于蒙眼开车。验证一个交易策略的最低标准是看它在“没见过的数据”上的表现。回测脚本test.py已经给了基本框架但你还需要在此基础上加上更多维度的评估指标。最基本的三个指标总收益率、最大回撤从峰值跌到谷底的最大幅度、以及Sharpe比率单位波动带来的超额收益。总收益率只能衡量“赚了多少”最大回撤衡量“这个过程有多煎熬”Sharpe比率则直接把风险和收益统一到一个量纲里。一个总收益率100%但最大回撤60%的策略在实盘里几乎不可能拿得住——回撤36%就需要涨50%才回本心理压力早让人在底部割肉了。源码本身可能没有完整实现这些指标的计算这些是自行补全的部分。# 用numpy计算最大回撤和Sharpe比率的核心代码 def max_drawdown(equity_curve): # 计算累计收益曲线 cumulative np.cumprod(1 np.diff(equity_curve) / equity_curve[:-1]) peak np.maximum.accumulate(cumulative) drawdown (cumulative - peak) / peak return drawdown.min() # 最小值为最大回撤 def sharpe_ratio(returns, risk_free_rate0.03, periods_per_year252): # 年化夏普比率日线数据用252分钟数据用252*240 excess_returns returns - risk_free_rate / periods_per_year return np.sqrt(periods_per_year) * np.mean(excess_returns) / np.std(excess_returns) # 回测时每天每根K线记录账户总资产 from stock_env import StockEnv import numpy as np env StockEnv(df_test, initial_balance10000) state env.reset() equity_curve [env.prev_asset] done False while not done: action agent.act(state, exploitTrue) # 关闭探索 state, reward, done, info env.step(action) equity_curve.append(info[asset]) equity_returns np.diff(equity_curve) / equity_curve[:-1] print(f总收益率: {(equity_curve[-1]/equity_curve[0] - 1)*100:.2f}%) print(f最大回撤: {max_drawdown(equity_curve)*100:.2f}%) print(f夏普比率: {sharpe_ratio(equity_returns):.2f})回测时需要注意一个重要细节account总资产序列里包含了未平仓的浮动盈亏。如果一个策略在最后一天恰好重仓持有且当天大涨总收益率就会虚高但这部分收益没有真正“落袋为安”。我建议把未平仓部分按收盘价折价0.5%再计入总资产——这是考虑了卖出冲击成本的保守估算。验证时段的选取也要讲究。训练数据是sz000001_min.csv验证数据就用sz000001_day.csv这是天然的跨时间粒度验证——如果分钟线学到的策略在日线上也能work说明模型学到的是日内交易信号和日间趋势之间的某种稳定关系这种东西才是真正有迁移价值的。如果验证集表现一塌糊涂并不意味着代码写的就有问题larger可能是训练数据过少或模型容量不足回到第4章的第二节再调整。对于参数的边界理解还有一层回测代码里如果直接把整段数据都用来训练然后同一段数据上做评估这种所谓的“回测”完全没有意义。正确的做法是在时间轴上做滑动窗口——用前80%训练后20%验证。更进一步应该做Walk-Forward验证每训练一段时间就用最近一小段数据做验证然后滚动到下一段模拟实盘环境下“永远用过去的数据决策未来”的真实约束。在那之后每次我拿到一个新的交易策略源码都会先跑一遍这个验证流程——看最大回撤是否超过10%بيع的策略是否频繁在收盘前清盘以及策略在极端行情下的表现。有一说一这套基于深度Q学习的源码框架有价值的地方就在于它强制你从“规则编写”转到“数据驱动”的思路上来。但强化学习永远只是工具模型学到的策略上限由你的数据质量和奖励函数决定。我在跑通代码后最大的收获不是那个回测收益数字而是理解了AI选股和人类选股的边界在哪里——模型同样会过拟合同样会犯错只是犯错的方式和人类不同。希望帮到你。本文还有配套的精品资源点击获取