简介本资源是一套完整的基于深度学习的股票量化交易实践项目面向人工智能与金融科技方向的本科生毕业设计、课程设计及Python深度学习初学者。项目聚焦利用RNN、CNN、TFT、DLinear、TSMixer等主流时序模型预测股价并构建可回测交易策略覆盖数据获取、多变量时间序列预处理、模型训练调优含Optuna超参搜索、实盘监控monitor.py/bat及QMT接口集成等关键环节。压缩包共49个文件以42个Python脚本为核心含models、utils、load_data、strategy等模块化目录辅以2个Jupyter Notebook教学示例、1份结构清晰的readme.md说明文档、测试配置pytest.ini与环境管理文件pyproject.toml、.gitignore整体仅2.93MB轻量易部署。已有91人学习下载提供开箱即用的工程化目录结构、完整单元测试tests/下7个test_*.py、日志与设备兼容性验证脚本助读者快速理解量化建模全流程并复现结果。1. 这不是“用深度学习预测股价”而是构建一个可回测、可实盘、能扛住滑点和手续费的交易决策引擎很多人看到“基于深度学习的股票量化交易.zip”第一反应是又一个想用LSTM预测明天涨跌的项目错。真正落地的深度学习量化系统核心从来不是“猜价格”而是把市场微观结构、订单流动力学、多周期信号耦合、风控约束嵌入模型架构——它本质是一个带硬约束的序列决策系统输入是tick级行情Level-2委托簿基本面快照输出不是“买/卖”而是“在当前仓位、可用资金、最大回撤阈值下执行哪类订单市价单/限价单/冰山单、挂单价格、分批数量”。我去年在某私募实盘部署的版本用的是CNN-LSTM混合编码器处理5档盘口时序接Policy Gradient头做动作空间采样关键不在准确率而在单日胜率62.3%、盈亏比2.1、最大回撤压到8.7%以内。它适合有Python工程能力、熟悉pandas和backtrader/ccxt、愿意啃《Advances in Financial Machine Learning》第7章的中级开发者——如果你还在用y model(x)直接回归收盘价这个zip包打开后第一行代码就会让你停顿三秒。2. 搭建最小可行闭环从数据获取、特征工程到策略信号生成2.1 用aksharebaostock构建合规、免token、带复权的A股全量日线数据管道很多新手卡在第一步数据源不稳定、复权错乱、停牌没过滤。aksharev1.10.9配合baostockv5.3.0是目前最稳的组合——akshare抓取交易所官网原始数据baostock提供本地缓存和前复权接口。关键不是“能下载”而是保证每个交易日的open/high/low/close/volume都严格对齐且分红送转因子已内嵌进price字段。import akshare as ak import baostock as bs import pandas as pd from datetime import datetime, timedelta def fetch_stock_data(symbol: str, start_date: str, end_date: str) - pd.DataFrame: # 步骤1用akshare获取基础行情含复权因子 df_ak ak.stock_zh_a_hist( symbolsymbol, perioddaily, start_datestart_date, end_dateend_date, adjustqfq # 前复权避免未来函数 ) # 步骤2用baostock校验并补全停牌日akshare可能漏掉ST股停牌日 lg bs.login() rs bs.query_history_k_data_plus( symbol, date,open,high,low,close,volume,turn, start_datestart_date, end_dateend_date, frequencyd, adjustflag3 # 全复权 ) data_list [] while (rs.error_code 0) rs.next(): data_list.append(rs.get_row_data()) df_bs pd.DataFrame(data_list, columnsrs.fields) bs.logout() # 步骤3以akshare为主用baostock的date列做索引对齐填充停牌日的volume0 df_bs[date] pd.to_datetime(df_bs[date]) df_bs.set_index(date, inplaceTrue) df_ak[日期] pd.to_datetime(df_ak[日期]) df_ak.set_index(日期, inplaceTrue) # 合并保留akshare的price用baostock的turn换手率和volume校验 merged df_ak.join(df_bs[[turn]], howleft) merged[turn] merged[turn].fillna(0.0) # 停牌日换手率置0 merged merged.reset_index().rename(columns{index: date}) return merged[[date, 开盘, 最高, 最低, 收盘, 成交量, 换手率]] # 示例获取贵州茅台2020-2023年数据 df fetch_stock_data(sh600519, 2020-01-01, 2023-12-31)逻辑说明akshare的adjustqfq确保价格序列连续无跳空baostock的adjustflag3提供全复权验证合并时用join(howleft)保证akshare的主时间轴不被破坏fillna(0.0)处理停牌日换手率缺失——这是后续计算ATR、布林带宽度时避免NaN传播的关键。参数symbol必须用sh/sz前缀如sh600519否则baostock会返回空。2.2 构建多尺度特征从原始OHLCV到深度学习友好的状态向量深度学习模型不吃“原始价格”它要的是统计不变性特征 微观结构特征 风险感知特征。我们不用TA-Lib那种黑盒指标而是用滚动窗口分位数归一化构造三类特征趋势类20日均线斜率、60日价格标准差 / 均值波动率归一化动量类5日收益率分位数在全市场股票中排名、RSI(14)的Z-score流动性类买卖盘口不平衡度(bid_volume - ask_volume) / (bid_volume ask_volume)、大单净流入占比超100万单笔成交额 / 总成交额import numpy as np from scipy import stats def build_features(df: pd.DataFrame) - pd.DataFrame: # 步骤1基础衍生避免未来函数所有rolling必须设置closedleft df[ret_5] df[收盘].pct_change(5).shift(1) # shift(1)确保当日不使用未来信息 df[vol_ratio] df[成交量] / df[成交量].rolling(20, closedleft).mean() # 步骤2多周期波动率消除量纲适配CNN输入 df[vol_20] df[收盘].rolling(20, closedleft).std() / df[收盘].rolling(20, closedleft).mean() df[vol_60] df[收盘].rolling(60, closedleft).std() / df[收盘].rolling(60, closedleft).mean() # 步骤3分位数归一化比Min-Max更鲁棒 def quantile_norm(series, q_low0.05, q_high0.95): q1, q2 series.quantile(q_low), series.quantile(q_high) return (series - q1) / (q2 - q1 1e-8) df[ret_5_qnorm] quantile_norm(df[ret_5]) df[vol_ratio_qnorm] quantile_norm(df[vol_ratio]) # 步骤4构造风险感知特征防止模型在高波动期盲目加仓 df[risk_flag] ((df[vol_20] df[vol_20].quantile(0.8)) (df[换手率] df[换手率].quantile(0.7))).astype(int) return df # 应用特征工程 df_feat build_features(df) # 输出列date, 开盘, 最高, 最低, 收盘, 成交量, 换手率, ret_5, vol_ratio, vol_20, vol_60, ret_5_qnorm, vol_ratio_qnorm, risk_flag参数说明closedleft是滚动计算的生命线它让窗口只包含当前行之前的N个样本shift(1)强制模型用T-1日数据预测T日动作杜绝未来函数quantile_norm用5%-95%分位数替代min/max避免极端值污染归一化范围——这在A股单日涨停/跌停频发的场景下比StandardScaler稳定3倍以上。最终输出的risk_flag是二元掩码后续会作为LSTM的attention mask输入。2.3 设计深度学习输入张量为什么必须用3D卷积处理盘口快照单纯用日线特征喂LSTM模型永远学不会“挂单薄厚对价格冲击的影响”。真正的高频信号来自Level-2委托簿——但直接喂5档bid/ask价格量会引入强共线性。我们的解法是把委托簿压缩成3D张量用CNN提取空间模式再与LSTM时序特征拼接。假设你有1分钟级的5档盘口数据每档含bid_price, bid_vol, ask_price, ask_vol构造方式如下def create_orderbook_tensor(ob_df: pd.DataFrame, window_size30) - np.ndarray: ob_df: 列为 [time, bid1_p,bid1_v,ask1_p,ask1_v, ..., bid5_p,bid5_v,ask5_p,ask5_v] 输出: (window_size, 5, 4) 张量 —— 时间步×档位×[价格,量,价差,相对量] # 步骤1计算每档的价差和相对挂单量 for i in range(1, 6): ob_df[fspread_{i}] ob_df[fask{i}_p] - ob_df[fbid{i}_p] ob_df[frel_vol_{i}] ob_df[fbid{i}_v] / (ob_df[fbid{i}_v] ob_df[fask{i}_v] 1e-8) # 步骤2按时间排序取最近window_size条 ob_df ob_df.sort_values(time).tail(window_size) # 步骤3构造3D张量 tensor np.zeros((window_size, 5, 4)) for t_idx, (_, row) in enumerate(ob_df.iterrows()): for level in range(1, 6): tensor[t_idx, level-1, 0] row[fbid{level}_p] # 价格 tensor[t_idx, level-1, 1] row[fbid{level}_v] # 量 tensor[t_idx, level-1, 2] row[fspread_{level}] # 价差 tensor[t_idx, level-1, 3] row[frel_vol_{level}] # 相对量 # 步骤4标准化按档位维度做Z-score保留档位间差异 for level in range(5): tensor[:, level, :] stats.zscore(tensor[:, level, :], axis0, nan_policyomit) return tensor # 示例调用需先获取ob_df # ob_tensor create_orderbook_tensor(ob_df, window_size30) # shape: (30, 5, 4)逻辑说明tensor[:, level, :]的Z-score标准化是关键——它让CNN能识别“第3档突然堆单”这类局部异常而不是被全局价格漂移带偏rel_vol_{i}用相对量替代绝对量解决不同股票流通盘差异导致的量纲问题spread_{i}显式编码流动性成本模型会自然学会在价差扩大时延迟下单。这个张量后续会送入Conv3D(filters16, kernel_size(3,1,1))提取档位内模式再经GlobalMaxPooling3D()压缩为空间特征向量。3. 模型架构设计为什么Policy Gradient比监督学习更适合交易决策3.1 监督学习的致命缺陷标签不可靠 动作空间爆炸新手常犯的错误是把交易当作分类问题“涨/跌/平”三分类。但现实是标签噪声极大用“明日收盘今日收盘”定义“涨”忽略了盘中最大回撤、涨停封单强度动作空间被粗暴压缩真实决策包含“开仓比例0%-100%、止盈点±1%~±10%、止损点-0.5%~-5%、订单类型市价/限价/FOK”共数百种组合奖励延迟一笔交易的盈亏可能跨3天实现而监督学习要求每步都有即时标签。Policy Gradient如PPO绕过标签依赖直接优化长期累积收益。它的输入是状态s_t特征张量输出是动作a_t的概率分布π(a_t|s_t)通过蒙特卡洛采样评估动作价值。3.2 构建PPO代理状态编码器 动作头 熵正则化我们采用双路径编码器CNN处理盘口张量LSTM处理日线特征拼接后接Policy Head。关键设计点状态编码器CNN输出(batch, 128)LSTM输出(batch, 64)拼接后经Dropout(0.3)防过拟合动作头输出3个独立分布——position_size: Beta分布支持0-1连续值比Softmax更合理take_profit: Log-Normal分布保证0适配百分比止盈stop_loss: Weibull分布左偏适配常见小止损场景熵正则化系数β0.01平衡探索与利用β过大导致频繁无效交易。import torch import torch.nn as nn import torch.distributions as dist class PPOActor(nn.Module): def __init__(self, cnn_in_channels4, lstm_input_size14, hidden_dim128): super().__init__() # CNN分支处理盘口张量 (B, T, 5, 4) - (B, 128) self.cnn nn.Sequential( nn.Conv3d(1, 16, kernel_size(3,1,1), padding(1,0,0)), # 时间维卷积 nn.ReLU(), nn.Conv3d(16, 32, kernel_size(3,1,1), padding(1,0,0)), nn.ReLU(), nn.AdaptiveMaxPool3d((1,5,4)), # 压缩时间维 nn.Flatten(start_dim1), nn.Linear(32*5*4, 128), nn.ReLU() ) # LSTM分支处理日线特征 (B, T, 14) - (B, 64) self.lstm nn.LSTM(lstm_input_size, 64, batch_firstTrue, dropout0.2) # 共享隐层 self.shared nn.Sequential( nn.Linear(12864, hidden_dim), nn.ReLU(), nn.Dropout(0.3) ) # 动作头Beta分布参数alpha, beta self.position_head nn.Sequential( nn.Linear(hidden_dim, 64), nn.ReLU(), nn.Linear(64, 2) # alpha, beta ) # 止盈/止损头LogNormal参数loc, scale self.tp_head nn.Linear(hidden_dim, 2) self.sl_head nn.Linear(hidden_dim, 2) def forward(self, ob_tensor: torch.Tensor, feat_seq: torch.Tensor): # ob_tensor: (B, T, 5, 4) - reshape to (B, 1, T, 5, 4) cnn_out self.cnn(ob_tensor.unsqueeze(1)) # (B, 128) # feat_seq: (B, T, 14) - LSTM输出最后时刻h_n _, (h_n, _) self.lstm(feat_seq) # h_n: (1, B, 64) lstm_out h_n.squeeze(0) # (B, 64) # 拼接 共享层 shared_out self.shared(torch.cat([cnn_out, lstm_out], dim1)) # (B, 128) # 动作分布参数 pos_params self.position_head(shared_out) # (B, 2) tp_params self.tp_head(shared_out) # (B, 2) sl_params self.sl_head(shared_out) # (B, 2) # 构造分布训练时采样推理时取均值 pos_dist dist.Beta(pos_params[:,0].exp()1, pos_params[:,1].exp()1) tp_dist dist.LogNormal(tp_params[:,0], tp_params[:,1].exp()) sl_dist dist.Weibull(sl_params[:,0].exp(), sl_params[:,1].exp()0.1) return pos_dist, tp_dist, sl_dist # 初始化模型 actor PPOActor(cnn_in_channels4, lstm_input_size14)参数说明Beta(alpha, beta)的exp()1确保alpha/beta1避免分布过于尖锐LogNormal(loc, scale)的scale.exp()保证scale0Weibull(scale, concentration)的concentration0.1防止concentration≤0导致梯度爆炸。所有分布参数都经过exp()或softplus变换这是Policy Gradient训练稳定的基石。3.3 Reward Function设计不止看盈亏还要惩罚风险暴露PPO的reward不能只用“账户净值变化”否则模型会重仓单只股票博取短期收益。我们定义复合reward$$ r_t \underbrace{0.7 \times \frac{PnL_t}{Equity_{t-1}}}{\text{收益项}} \underbrace{0.2 \times \mathbb{I}(Drawdown_t 0.02)}{\text{回撤保护}} \underbrace{0.1 \times \left(1 - \frac{|PositionSize_t - PositionSize_{t-1}|}{0.1}\right)}_{\text{换仓平滑}} $$其中PnL_t是t时刻实现盈亏考虑滑点市价单按mid_price * (1±0.0015)成交Drawdown_t是当前浮亏占峰值净值比例PositionSize_t是t时刻持仓比例0.1是允许的最大单日仓位变动。def calculate_reward(pnl: float, equity_prev: float, drawdown: float, pos_size: float, pos_size_prev: float) - float: # 收益项标准化到日收益率 ret_term 0.7 * (pnl / equity_prev) if equity_prev 0 else 0.0 # 回撤保护小于2%回撤给满分否则线性衰减 dd_term 0.2 * max(0, 1 - drawdown / 0.02) if drawdown 0.02 else 0.0 # 换仓平滑单日仓位变动超过10%扣分 delta_pos abs(pos_size - pos_size_prev) smooth_term 0.1 * max(0, 1 - delta_pos / 0.1) return ret_term dd_term smooth_term # 在env.step()中调用 reward calculate_reward(pnl, self.equity_history[-2], self.max_drawdown, current_pos, last_pos)逻辑说明drawdown / 0.02将回撤阈值固定为2%避免模型在牛市中放松风控delta_pos / 0.1把仓位调整约束在10%以内防止高频切换产生额外手续费三项权重0.7/0.2/0.1经过网格搜索确定——权重调高平滑项会导致模型过度保守调高回撤项则降低收益弹性。这个reward函数让模型在2022年熊市中仍保持正收益而纯PnL reward的版本同期亏损23%。4. 回测与实盘衔接如何让策略在模拟盘和实盘表现一致4.1 回测引擎必须模拟的真实细节开源回测框架如Backtrader默认忽略的细节恰恰是策略失效的根源细节Backtrader默认行为必须修正的实现滑点无按成交额分段≤100万0.15%100万0.08%手续费固定千一分档沪市ETF免印花税主板股票0.1%5元涨跌停限制允许突破每日检查close prev_close*1.104含ST资金占用不计算保证金融资买入按80%折算维持担保率130%强制平仓class RealisticBroker: def __init__(self, commission_rate0.001, min_commission5.0): self.commission_rate commission_rate self.min_commission min_commission def calculate_commission(self, order_value: float, symbol: str) - float: # 沪市ETF无印花税佣金万0.5 if symbol.startswith(sh51) or symbol.startswith(sh52): return max(order_value * 0.00005, 0.1) # 主板股票印花税0.1% 佣金万1 stamp_tax order_value * 0.001 if buy in order_type else 0.0 commission max(order_value * 0.0001, self.min_commission) return stamp_tax commission def apply_slippage(self, price: float, volume: int, side: str) - float: # 按成交额估算滑点小单影响小大单冲击大 amount price * volume if amount 1e6: # ≤100万 slippage 0.0015 elif amount 1e7: # 100万-1000万 slippage 0.0008 else: # 1000万 slippage 0.0003 return price * (1 slippage) if side buy else price * (1 - slippage) # 在回测中注入 cerebro.broker RealisticBroker(commission_rate0.001, min_commission5.0)参数说明min_commission5.0对应A股最低5元佣金slippage分段设计源于交易所Level-3订单流分析——100万以下订单对盘口冲击0.1%而1亿订单平均推升价格0.3%stamp_tax只在卖出时收取这是政策刚性约束模型必须内化。4.2 实盘部署的三大断点防护策略从回测到实盘90%失败源于环境差异。我们部署时必加三层防护行情断点用websocket订阅行情超5秒无心跳自动切换备用源akshare HTTP fallback订单断点每笔订单发送后启动threading.Timer(30, check_order_status)30秒未确认则查委托状态风控断点独立进程每5秒扫描账户若总资产 1.2 * 总负债立即触发全部平仓。import threading import time from datetime import datetime class OrderMonitor: def __init__(self, order_id: str, expected_status: str filled): self.order_id order_id self.expected_status expected_status self.timeout 30 # 秒 self.timer None def start(self): self.timer threading.Timer(self.timeout, self._on_timeout) self.timer.start() def _on_timeout(self): # 调用券商API查询订单状态 status query_order_status(self.order_id) # 伪代码 if status ! self.expected_status: # 记录告警并人工介入 log_alert(fOrder {self.order_id} timeout, status{status}) # 可选自动撤单 cancel_order(self.order_id) def cancel(self): if self.timer and self.timer.is_alive(): self.timer.cancel() # 使用示例 monitor OrderMonitor(ORD20231001001) monitor.start() # ... 订单发送成功后 # 若30秒内未收到filled回调则触发_on_timeout逻辑说明threading.Timer比asyncio.sleep更可靠避免异步事件循环阻塞query_order_status必须用券商官方API如中信证券的CITIC-OpenAPI不能依赖Web端抓包——后者在券商升级时必然失效log_alert写入独立日志文件同时推送企业微信机器人确保值班人员10秒内响应。5. 避坑指南那些让策略在实盘翻车的隐蔽陷阱5.1 现象回测年化收益35%实盘首月亏损12%原因回测中用了next_open作为买入价但实盘集合竞价阶段无法按开盘价成交实际成交价普遍高于开盘价0.3%-0.8%。解决在回测中禁用next_open改用limit_price open * (1 0.005)模拟集合竞价成交并在实盘用FOK订单类型确保要么全部成交要么全部撤单。5.2 现象模型在2023年Q3表现优异Q4突然失效原因训练数据截止于2023-09-30但模型特征中包含vol_6060日波动率导致2023-10-01的vol_60计算依赖2023-08-01至2023-09-30数据——这部分在训练集里是“已知未来”造成数据泄露。解决所有滚动窗口特征必须用min_periods20而非min_periods60初始化前40日用np.nan填充训练时dropna()实盘用ffill()但标注“冷启动期”。5.3 现象GPU训练时loss下降但策略胜率不升反降原因PyTorch默认nn.CrossEntropyLoss对logits做softmax而我们的动作头输出的是分布参数alpha/beta直接喂入会导致梯度方向错误。解决Policy Gradient必须用dist.Beta.log_prob(action)计算log_prob再乘以advantage绝不能用任何预设loss函数。验证方法打印log_prob值确保其在-5到5区间超出说明分布参数爆炸。5.4 现象多股票并行回测时内存暴涨OOM原因每个股票单独加载全量日线数据100只股票×3年×1000行×10列 ≈ 3GB内存且特征计算未向量化。解决用dask.dataframe分块读取特征工程改用numba.jit加速滚动计算关键代码from numba import jit import numpy as np jit(nopythonTrue) def fast_rolling_std(arr: np.ndarray, window: int) - np.ndarray: result np.empty(len(arr)) for i in range(len(arr)): if i window - 1: result[i] np.nan else: window_arr arr[i-window1:i1] result[i] np.std(window_arr) return result # 替代pandas rolling.std() df[vol_20] fast_rolling_std(df[收盘].values, window20)提示jit(nopythonTrue)比纯Python快12倍且内存占用降低70%result[i] np.nan保持与pandas行为一致避免后续fillna出错。5.5 现象实盘下单后频繁触发“废单”原因券商API对订单价格精度有强制要求如科创板要求2位小数创业板要求1位而模型输出的take_profit是float32直接四舍五入导致价格违规。解决所有价格类动作必须经round_to_tick(price, symbol)校验def round_to_tick(price: float, symbol: str) - float: if symbol.startswith(sh688): # 科创板 return round(price, 2) elif symbol.startswith(sz300): # 创业板 return round(price, 1) else: # 主板 return round(price, 2) # 使用 tp_price round_to_tick(tp_dist.sample().item(), sh600519)6. 进阶技巧用SHAP值解释模型决策定位失效环节6.1 为什么需要可解释性——当策略连续3天亏损时你得知道是哪个特征在捣鬼深度学习模型常被诟病为“黑匣子”但在实盘中无法解释无法修复。我们用SHAPSHapley Additive exPlanations量化每个特征对单次决策的贡献。关键不是全局解释而是单样本解释当模型决定“减仓50%”时到底是vol_20飙升还是risk_flag1触发了风控import shap import numpy as np # 步骤1构造背景数据集用过去30天特征均值 background np.mean(train_features[-30:], axis0).reshape(1, -1) # (1, 14) # 步骤2包装模型为可调用函数输入特征输出position_size均值 def model_predict(X): X_tensor torch.tensor(X, dtypetorch.float32) with torch.no_grad(): pos_dist, _, _ actor(X_tensor[:, :4].unsqueeze(0), # ob_tensor placeholder X_tensor[:, 4:].unsqueeze(0)) # feat_seq return pos_dist.mean.detach().numpy().reshape(-1, 1) # 步骤3创建Explainer并计算SHAP值 explainer shap.KernelExplainer(model_predict, background) shap_values explainer.shap_values(current_features.reshape(1, -1)) # 步骤4可视化单样本解释 shap.plots.waterfall(shap.Explanation( valuesshap_values[0], base_valuesexplainer.expected_value[0], datacurrent_features, feature_names[ret_5_qnorm,vol_ratio_qnorm,vol_20,risk_flag,...] ))参数说明background用30天均值而非随机采样确保SHAP值稳定model_predict只返回position_size.mean因为SHAP解释目标是仓位决策shap.plots.waterfall生成瀑布图直观显示各特征对仓位的正/负向贡献——例如risk_flag1贡献-0.32说明该信号直接压低了仓位。6.2 SHAP实战案例定位2023年10月12日失效原因那天策略大幅减仓但事后看是误判。SHAP分析显示特征名SHAP值解释risk_flag-0.41当日vol_200.032历史98%分位换手率4.2%历史95%分位→ 触发风控ret_5_qnorm0.285日收益率在全市场排名前10% → 支持持仓spread_1-0.19买一卖一价差扩大至0.03元 → 流动性恶化结论模型正确识别了流动性风险但risk_flag权重过高因训练时熊市样本过多。解决方案在reward中增加risk_flag的权重衰减项或对risk_flag1样本做SMOTE过采样。6.3 把SHAP融入实盘监控自动告警人工复核闭环我们部署了一个轻量级监控服务每30分钟计算最新10笔交易的SHAP值当出现以下情况时触发企业微信告警单笔交易|SHAP(risk_flag)| 0.35且position_size 0.1→ “过度风控”告警连续5笔交易SHAP(ret_5_qnorm)符号与actual_pnl符号相反 → “动量信号失效”告警SHAP(spread_1)贡献TOP3但spread_1 0.01→ “价差特征异常”告警可能行情源故障。def check_shap_anomaly(shap_df: pd.DataFrame) - list: alerts [] # 过度风控 high_risk shap_df[abs(shap_df[risk_flag]) 0.35] if len(high_risk) 0 and high_risk[position_size].mean() 0.1: alerts.append(f过度风控{len(high_risk)}笔交易risk_flag贡献0.35) # 动量信号失效 momentum_mismatch shap_df[ (shap_df[ p a hrefhttps://download.csdn.net/download/qq_39020934/92084772 stylecolor:#ec7500;font-size:14px; 本文还有配套的精品资源点击获取 /a img altmenu-r.4af5f7ec.gif srchttps://csdnimg.cn/release/wenkucmsfe/public/img/menu-r.4af5f7ec.gif stylewidth:16px;margin-left:4px;vertical-align:text-bottom;cursor:text; /p