DOCS
stock-analyzer · 代码审查
← 返回文档中心 · 来源:Languangxun/stock-analyzer · 同步于 2026-09-27
审查范围:stock_gui.py(算法源)、stock_predict.py(生成物)、stock_web.py / pi_stock_web.py、build_cli.py、ai-quant/、根目录一次性回测脚本与重复副本。
原则:只列需要改的问题,不列纯风格。优先级:P0 先改、P1 应改、P2 建议改。
2026-09-26 二轮专项复审(过拟合 / 前视偏差)见文末,覆盖数据层、
factor_lab/、backtests/、Web 版与研究缓存。
P0 · 安全与正确性(先改)
1. API Key 明文落盘,且曾可能进入工作副本
| 位置 | 问题 |
|---|---|
stock_gui.ini [deepseek] api_key |
明文保存 DeepSeek Key。.gitignore 已忽略 ini,本地文件仍在。 |
ai-quant/.env DEEPSEEK_API_KEY |
明文 Key。.gitignore 有 .env,仍在工作树。 |
要改:
- 立刻轮换/作废这两把 Key(本文不写出具体值)。
- Key 只从环境变量读取,ini 只存
api_key_set=1之类占位。 - GUI 设置页不要把完整 Key 回填到输入框。
- 确认 git 历史从未提交过 ini/env;若提交过,从历史剔除并轮换。
2. ai-quant 回测几乎下不了仓(ensemble API 用错)
ai-quant/backtest/engine.py 把当前仓位算成 float 再传给 ensemble.decide(context, current)。
agent/ensemble.py 的 decide() 只在 position_of 可调用时读持仓,否则 current_position = 0。
后果:目标仓位 ≥1% 永远 BUY,几乎从不 SELL;与 sim/run.py(传 callable)行为不一致,回测 PnL 不可信。
要改: 与 live 一致,传 lambda s: account.position_pct(s, navs)。数值仅作测试兜底。
3. 场外基金成交用 T+1 净值,不是 T 日净值(前视)
ai-quant/trading/account.py confirm_orders 用确认日当天的 navs 算份额。
联接基金规则是:T 日净值成交,T+1 确认。用确认日净值 = 用次日涨跌给自己定价。
tests/test_otc_lifecycle.py 把错误规则写成了断言。
要改: 订单记下 trade_date;确认时查 交易日 NAV。补测试:T+1 NAV 变化不得改变份额。
4. 回测把「今天的教训」喂给历史日期(前视)
ai-quant/backtest/engine.py 里 load_lessons(5) 永远读生产环境最新 lessons.json,不按 trade_date 截断。
2024 年回放能看到 2026 年复盘。
要改: 严格 lesson["date"] < trade_date;纯回测默认空 lessons。
5. stock_gui.py 设置页「关于/免责」缩进错位
open_settings() 在关机按钮后结束;「关于 / 免责声明」整段写在 _shutdown_confirm() 方法体内(约 8894–8913 行)。
后果:
- 设置窗口永远不显示免责声明。
- 关机失败后会用未定义的
frm,NameError。
要改: 把该段挪回 open_settings(),与 frm 同作用域。
P1 · 交易 / 回测 / Web
6. 决策上下文用 T 日净值盯市(模型能看到当日涨跌)
engine.build_context / sim/run.py 把当日 NAV 写入 account.total_asset、positions[].nav/pct,注释却写「只看 T-1」。
盘后已公布时 live 也把最新净值标成 prev_nav。
要改: 给模型的估值只用 date < T 的已公布净值;成交确认仍可用 T 净值(内部)。
7. 集成测试与真实 API 对不上
ai-quant/tests/test_ensemble.py 调用 ens.decide({}, current_position=0),该关键字不存在 → pytest 直接 TypeError。
test_ensemble_all_fail_hold 期望保持仓位 15,实现却 hardcode target_position=0.0。
要改: 对齐 position_of;全失败 HOLD 应保留真实当前仓位。CI 里跑通测试。
8. 多模型投票把不同标的的仓位混在一起
agent/ensemble.py 先对所有 voter 的 target_position 做加权平均,再按多数票选 target 名字。
A 投银行 40%、B 投通信 10% → 可能得到「通信 ~25%」。
要改: 先按 target 分组,再对该组平均仓位;或 action/标的联合投票。
9. 现金不足会打崩整天
Executor 按 total_asset * target_pct 下单;已有持仓时现金可能不够。
FundAccount.subscribe() 抛 ValueError("现金不足"),executor 不接,回测/模拟整天中断。
要改: 金额向下取整到可用现金(100 元整数倍);不足最小申购额则 NO CHANGE,execute() 永不抛出。
10. risk.yaml 没接上;没有组合敞口上限
RiskManager 写死 40%/20%。max_daily_loss: 5、cooldown_days: 7 从未读取。
10 只基金 × 40% = 400% 名义敞口。
要改: 加载 yaml;sum(position_pct) ≤ 100(或配置上限);实现日亏损熔断和冷却,否则删掉配置以免误信。
11. 决策解析过松,target_normalizer 是死代码
parse_decision:null/"20%"会float()失败 → 该 voter HOLD。NaN:RiskManager.check比较全 False → 放行。"半导体ETF"不在FUND_MAP仍可下单(空fund_code)。agent/target_normalizer.py从未被 import。
要改: 非有限数字拒绝;clamp 到 [0, max_position];必须 normalize_target() 且 target in FUND_MAP,否则 HOLD。
12. 净值抓取失败会把整只基金从历史里抹掉
ai-quant/data/history/fetch_all.py:ETF 失败会回退旧 market.json,NAV 失败则直接省略该 symbol,再 json.dump 覆盖 fund_nav.json。
要改: merge 进旧文件;失败保留旧点;缺失过多则任务失败。
13. NAV 日期用本机本地时区
data/fund/fund_provider.py:time.localtime(ts/1000)。东财 x 按东八区午夜。UTC/美区机会错一天。
要改: 固定 Asia/Shanghai(zoneinfo)。
14. 夜间复盘写到错误目录
scripts/night_review.py 写 ~/ai-quant/memory/daily/...,本仓库实际是 ai-quant/memory/daily。Windows 上永远找不到文件。
要改: Path(__file__).resolve().parents[1] / "memory" / "daily"。
15. 两套并行栈,旧栈一跑就崩
| 问题 | 位置 |
|---|---|
from trading.account import Account,真实类是 FundAccount |
agent/controller.py |
executor.execute(decision) 缺 navs / trade_date |
同上 |
create_decision(..., position_change=...),参数名是 target_position |
backtest/llm_runner.py |
abs(decision.position_change),常为 None |
agent/risk.py |
models/ensemble.py 空壳,真逻辑在 agent/ensemble.py |
|
VectorMemory.search 读 item["trade"],存储是 text/time |
memory/vector.py vs otc_memory.py |
要改: 删除或明确禁用旧路径(controller / llm_runner / agent/risk / 空 ensemble)。只留 sim.run + SimEngine 一条。
16. 配置路径依赖 CWD
models/config.py、main.py:open("config/model.yaml")。不在 ai-quant 目录启动就挂。
要改: 相对包根解析(同 agent/config_loader.py)。
17. Web 端开放代理 + XSS + 无鉴权
stock_web.py / pi_stock_web.py:
ThreadingHTTPServer(("0.0.0.0", port)),日志却写127.0.0.1。/api?code=把任意代码转到腾讯等行情源(局域网开放代理)。/text把code拼进 HTML:img src="/svg?code=' + code,未转义 → XSS。- 无频率限制,可打爆上游。
要改: 默认绑 127.0.0.1(或明确 --bind);html.escape(code);校验 6 位代码;加简单限流。日志与真实 bind 地址一致。
18. 质量过滤用「10% 涨跌停」一刀切
CFG.MAX_DAILY_CHANGE = 10.0。创业板/科创 20%、北证 30%、ST 5% 会被误杀或漏杀。清洗层已按板块分段,匹配层没有。
要改: 复用清洗那套「当日允许涨跌停 + 缓冲」,或按代码前缀分段。
19. 文档 / 常数互相打架
| 文档/代码 | 现状 |
|---|---|
ARCHITECTURE.md |
仍写 v3.3、W=10、L3 权重 0.1 |
CFG.W_WINDOW |
已是 20 |
CFG.ENABLE_L3 |
默认 False |
| README v4.0.2 | L1 全市场 IC 为负;CFG 注释仍写 W20 IC+0.016 |
要改: 更新 ARCHITECTURE.md 到 v4;注释与全市场实证对齐,避免按过时小样本调参。
20. stock_predict.py 与 GUI 可能漂移
架构约定:算法只改 stock_gui.py,再跑 build_cli.py。
ai-quant/stock_predict.py、ai-quant/scripts/cli/stock_predict.py 是更旧的拷贝。Pi 若跑这些副本,会与 GUI 行为分叉。
要改: 改算法后必须 python build_cli.py;下游只部署生成物;删或改名为 archive 的旧拷贝。
P2 · 工程债(建议改)
21. 重复与垃圾文件(应删或归档)
stock_gui - 副本.py:过期整文件副本。pi_standalone.py:又一份巨型 GUI/算法快照。- 根目录一次性脚本:
backtest_*.py、sweep_risk.py、backfill_*.py与主程序功能重叠。 *.txt研究日志、*_results.json应进research/,不要堆仓库根。
22. 单文件 9000 行不可维护
stock_gui.py 把缓存、HTTP、指标、匹配、v4 Walk-Forward、Tk GUI 揉在一起。build_cli.py 靠字符串锚点切片,锚点一改生成物就坏。
中期应拆:cache / fetch / signals / v4 / gui,CLI 用 import 而不是切源码。短期至少给 build_cli.py 加锚点存在性检查和生成后语法检查。
23. 日历与特征错误
TradingCalendar节假日默认空:春节/国庆当交易日。TechnicalFeature.volatility对价格做全样本 stdev,不是收益率、也不是滚动窗口。len < n时 MA 返回末价,趋势会假up/down。
24. 其它小坑
pending确认后不清理,状态文件只增不减。- 回测里
Trade.timestamp = datetime.now(),不是trade_date。 watchlist.yaml基金代码为空,真正映射在FUND_MAP。- 余弦相似度可能除零(
otc_memory.py/vector.py)。 DeepSeekVoterfallback 会永久改self.client.model,后续都走便宜模型。nav_anomaly/sim/run.py大片except Exception: pass,缺数据看起来像「市场平静」。- Web
/api失败仍 HTTP 200 +{"error":...},前端不好区分。 - 回测佣金/印花税为 0(
_V4_COST),README 已声明;若对外展示应同时报「含成本」口径。 stock_web与 GUI 算法弱同步:Web 仍 W=10、仅 L1,ARCHITECTURE 已说明,但页面应标明「弱化版」。- 无 CI:至少跑
ai-quant/tests+python -m py_compile stock_gui.py。
建议改动顺序
- 立刻:轮换 DeepSeek Key;ini/env 不再存明文。
- ai-quant 回测可信:
decide(position_of=...);成交用 T 日净值;lessons 按日截断;context 用 T-1 净值。 - 修 GUI:免责声明缩进;设置页能看见。
- 风控:yaml 生效、总仓位 ≤100%、现金不足不崩、解析拒绝 NaN/未知标的。
- Web:绑定地址、转义、限流。
- 删重复:副本 py、旧 CLI、空模块;
build_cli.py作为唯一生成路径。 - 文档:
ARCHITECTURE.md对齐 v4 与全市场 IC 结论。 - 测试:修好
test_ensemble.py/ OTC 成交日,接入 CI。
本清单基于 2026-09-12 工作区静态审查,未跑全量回测。不构成投资建议。
处理状态(v6.1.5 热修⑦,2026-09-26)
重要前提:
ai-quant/已于 2026-09-25 拆分为独立仓库 (Languangxun/ai-quant), 与本仓 GUI/Web/构建无关的条目(P0-2~4、P1-6~16、P2-23/24 大部)已不在本仓, 需在独立仓库处理。以下只列本仓范围。
| 条目 | 状态 | 说明 |
|---|---|---|
| P0-1 API Key 明文 | 部分/流程 | 本仓 stock_gui.ini 在 .gitignore(不入库);GUI 设置页已不回填完整 Key(ENV_API_KEY 优先)。Key 轮换需用户在平台侧操作。 |
| P0-5 免责声明缩进 | 已修(早前) | 「关于/免责」已在 open_settings()(row 28~29),不在 _shutdown_confirm。 |
| P1-17 Web 安全 | 已具备 | --bind 默认 127.0.0.1;html.escape 全量转义;re.fullmatch(r"(sh\|sz\|bj)\\d{6}") 校验;_rate_limited 限流;启动日志打印真实 bind。 |
| P1-18 涨跌停一刀切 | 已修 | 死常数 CFG.MAX_DAILY_CHANGE 删除;清洗/回测统一板块口径(_limit_pct / _v4_limit_pct)。 |
| P1-19 文档/常数打架 | 已修 | ARCHITECTURE 已对齐 v6.1.5;W_WINDOW 设置真实生效(热修⑦);L1 注释改为”小样本扫描 W20 略优 + 全市场样本外增量≈0,勿据样本内 IC 调参”。 |
P1-20 stock_predict.py 漂移 |
已修 | 算法改动后 build_cli.py 重新生成;本轮又加锚点存在性检查 + 生成前语法校验。 |
| P2-21 重复文件 | 已清 | 根目录无 *- 副本.py;pi_standalone.py 归档 research/legacy/;一次性脚本收敛到 backtests/。 |
| P2-22 单文件不可维护 | 短期已做,拆分不做 | build_cli.py 锚点检查 + 语法校验;多文件拆分按 legacy 报告 #13 的结论不做。 |
| P2-24 其他小坑(本仓部分) | 见内联 | /api 失败返回 HTTP 200(前端按 error 字段处理,保留);stock_web 标注为弱化版(ARCHITECTURE 第五节)。 |
仍需用户操作:DeepSeek/OpenAI Key 轮换(若曾用过本地明文 Key);新增 CI 可选
(当前手动跑 py_compile + test_settings.py)。
二轮专项复审:过拟合与前视偏差(2026-09-26)
触发:热修⑥⑦ 修复「消融近端门槛误用验证段」「样本窗口重叠」后,对其余链路 (数据层复权与回填、
factor_lab/、backtests/、Web 版、v4 研究缓存)再做一次专项走查。 方法:静态核对 + 只读抽样(SQLite 只读、按 web 逻辑复刻因子计算),未跑改动。 严重度:P0 直接污染全库数据/主结论;P1 结论可信度实质受损;P2 口径漂移/披露/工程债。
0. 结论摘要
- GUI 主算法链(形态匹配 → 多维评分/消融选型 → v4 Walk-Forward → 三档组合回测)在热修⑥⑦后 本轮未再发现新的前视;首轮修复项(近端门槛、样本窗口去重、morning view、purge+embargo)复核通过。
- 真正会让数字与实盘口径不一致的问题集中在「库里存什么」和「另外几套实现」:
- 全库回填按钮写的是腾讯 qfq(前复权),会覆盖 hfq 库 —— P0;
- 三档回测用今天的复权系数缩放历史绝对价/成交额(门槛含未来分红信息)—— P0;
factor_lab股票池用当前元数据过滤,2018 年后 229 只退市股全部被排除(幸存者偏差)—— P0;- Web 版仍 W=10 且样本窗口可重叠/可与当前窗重叠(热修⑦只改了 GUI)—— P1;
research/v4_preds.pkl、factor_lab/panel.npz是 W=10 时代缓存,默认被 v5 系列脚本引用 —— P1。
- 首轮已知的「研究脚本样本内选参」旧账仍然成立,本轮补充 3 个具体点(S3 留出门槛、重叠窗 WF、
sweep_risk网格维度失效)。
1. P0
P0-1 全库回填脚本用腾讯 qfq 覆盖 hfq 库(数据层污染,GUI 按钮直连)
| 位置 | 事实 |
|---|---|
backfill_full.py:60-72 |
_tx_fetch 请求 ...,day,,,{count},qfq,读 qfqday 字段(前复权);_fetch_one:89-106 以腾讯为主源(≥300 根即返回),东财 hfq 仅兜底 |
backfill_full.py:3-8 |
文件头却写「主源:东财 push2his(一次请求拉全量前复权)」——docstring 与代码相反 |
backfill_full.py:116-127 |
_store 无重叠一致性校验、不调用 _sync_adjust,INSERT OR REPLACE 直写 daily_bars |
stock_gui.py:13395-13406 |
GUI「工具→数据工具→全库日K回填」直接 spawn 该脚本 |
- 对照:集成版
backfill_full_market(stock_gui.py:1863起)走_fetch_remote_rows(腾讯 hfq / 东财fqt=2) 并_sync_adjust;data_clean.py:5-14已把腾讯/东财 qfq 定义为历史失真根因。两条路径产出不同口径。 - 影响:执行一次按钮,最近约 1600 根被替换为前复权口径;分红送转后整段历史重定基;
与更早的 hfq 段形成接缝。
_bars_anomalous(stock_gui.py:972-984)对孤立大跳变放行 (本意是放行 ETF 折算),接缝处的单个大跳变未必拦得住;且backfill_full._store绕过全部清洗。 消融 / v4 / tier 读同一库,全受污染。 - 修复:
_tx_fetch改hfq/hfqday;_store增加与现有 hfq 的重叠校验或调用_sync_adjust; GUI 按钮改调集成版backfill_full_market。
P0-2 三档回测:历史绝对价/成交额门槛被「今天」的复权系数污染(前视)
stock_gui.py:8437-8458:tier_load_panel用adjust.k把 hfq 缩放成以今天为锚的乘法前复权C(t) = raw(t)·A(t)/A(now);k由sync_adjust.py:63-112用最新日raw/hfq配对算出 (DB 实测k中位 0.583、p1=0.023)。stock_gui.py:8504-8509amt = V*C、8621-8623C>_TIER_MIN_PRICE(1.0)、amt20>=_TIER_MIN_AMOUNT(3e5)(=3000 万)、8693整手取整,都在历史日 t 使用了A(now)(t 之后才发生的分红/送转)。- 量化(只读抽样):
sh6006012023-06-09 的C=1.948vs 当日不复权raw=2.840(A(t)/A(now)=68.6%)——2023 年的 3000 万流动性门槛实际被抬到约 4370 万;sh600519为 86.0%。 - 边界:
ret20/vol20/beta/闸门是收益率/比值,常数k约掉,不受影响;受损的只有「可交易池/绝对价阈值」。 但同一回测在不同日期重跑,会因新分红改变历史池 —— 不可复现。 - 同类:
daily_picks的仙股过滤r[-1]['close'] >= 2(stock_gui.py:4009)直接读 hfq 原值 (daily_bars.close是后复权),不是现价口径,阈值形同虚设。 - 修复:门槛改用 point-in-time 口径(库中保留历史复权因子,或用成交额/流通市值等不随 k 变化的字段);
至少在文档标注「绝对价/流动性门槛按当前复权锚、历史池不可复现」,并修
daily_picks的现价判断(乘k)。
P0-3 factor_lab 股票池幸存者偏差:2026 年元数据反选 2018 年样本
factor_lab/data.py:25-45:list_codes(require_meta=True)要求stocks.industry非空;factor_lab/panel.py:225/250默认调用。- 只读 DB 证据:
daily_bars中最后交易日落在 2018-01-01~2026-06-01、≥400 根、主板/创业/科创的个股 229 只,其stocks.industry229 只全为空(退市/长停股没有行业快照);stocks表 7594 行中 4344 行 industry 为空。 - 影响:
factor_lab的面板 / 单因子 IC / 枚举 / BH-FDR / 组合回测全部只含「活到 2026-09 的股票」,research/factor_lab/的全A 因子有效性与 Robust 因子集结论存在系统性高估。 - 修复:代码池不要求当前 industry(退市股补行业映射或归入「未知」组);重跑后更新结论。
2. P1
P1-1 factor_lab L2/L3 同伴池用 2026 年市值快照定义历史分组(本轮新增)
factor_lab/panel.py:192-220:_l2_worker/_l3_worker用_G["mktcap"](stocks最新快照, updated 2026-09-24/25)对同行业/跨行业 peer 按log(mktcap)距离排序;panel.py:257-260的tier_of/mktcap同源。- 2018 年某股的「同市值层同伴」由 2026 年市值决定 = 前视信息(
stocks没有历史市值序列)。 - 修复:用滚动市值(价格×股本)或至少用
close横截面分层;无法获得时报告中把 L2/L3 因子 IC 降级为「含时点污染」。
P1-2 factor_lab LASSO 交叉验证同日跨折泄漏(本轮新增)
factor_lab/validate.py:86-99:lasso_select先随机抽样(90-92),按日期 stable sort(95), 再TimeSeriesSplit(5)(96)——没有 purge/embargo,折边界落在同一天内部。- 实测(full2000 面板复现):5 折 train/test 日期交集均恰 1 天、gap=0;同日横截面样本 + 1 日前视标签使 CV 误差被低估 → alpha 选择偏乐观、因子多选。最终系数在训练段重拟合(99 行), 验证段报告不被直接污染。
- 修复:按日期切分(
GroupKFold按date_ord)+ purge = 标签 horizon。
P1-3 backtest_v5_cross_section:用当日收盘涨跌停掩码约束当日开盘成交(确定前视)
backtests/backtest_v5_cross_section.py:92-99:limit_up/limit_dn由ret1=close1/close0-1派生(T 日收盘后才知道);sim:157到期卖出用not M["limit_dn"][ks,t]、171-172入场用~M["limit_up"][:,t];- 默认
--entry open --exit open(236-238)。即 T 日开盘决策时已经知道 T 日是否会涨停/跌停。 - 影响:开仓剔除「当日会涨停」的票、跌停时推迟卖出 —— 确定使用了未来信息。
- 修复:开盘成交口径改用
open/close[-1]-1或open/prev_close;stock_gui._v4_portfolio_sim在EXEC_PX=open时同病(stock_gui.py:7520,7778,默认 close 可接受)。
P1-4 v4 预测缓存/因子面板是 W=10 时代产物,签名不含 W
_v4_factor_matrix的 L1 因子用模块级W_WINDOW(stock_gui.py:6912-6916); 热修⑦把W_WINDOW/TOPK从 10 修回 20(stock_gui.py:4144-4147),但缓存签名只含codes/min_bars/dmax/nbars/ver(stock_gui.py:7951-7952、_V4_CACHE_VER="3")。- 现状:
research/v4_preds.pkl(mtime 2026-09-14)、research/factor_lab/panel.npz(2026-09-15) 均早于口径修复;以下脚本默认引用旧缓存:backtest_v5_cross_section.py:230、backtest_exit_ablation.py:42、backtest_v4_entry_exit_opt.py:36、backtest_exit_roll.py:60。 v5 横截面/因子研究的结论仍是 W=10 口径。 - 修复:缓存签名加入
CFG.W_WINDOW/TOPK/IND_W与数据哈希;重跑v4_preds.pkl、panel.npz后再引用。
P1-5 Web 版形态样本窗口重叠/自匹配(热修⑦只修了 GUI)
stock_web.py:448-454(浏览器 JS)、stock_web.py:993-1001(服务端/text|/svg|/e63)、pi_stock_web.py:241-247/794-802:候选i取到rets.length-2,无 GUI 的k+W<=i-W隔离, Top-K 之间也无去重(GUI 见stock_gui.py:4823-4825、6231-6237)。- 只读复刻(本机 398 只、W=10):67.8% 的股票 Top10 内存在
|i-j|<W的重叠样本对(505 对); 14.3% 的股票存在与当前窗口重叠的候选(样本 T+1 实现日落在当前特征窗内)→up_prob/分位被伪重复和自匹配抬高(与热修⑦在 GUI 修掉的是同一问题)。 - 修复:移植 GUI 两条约束;顺带把 W=10 与 GUI W=20 对齐或页面标注口径。
P1-6 研究脚本「验证段参与选择 / 重叠窗 WF / 网格维度失效」(首轮旧账 + 本轮补充)
| 脚本 | 问题 | 修复 |
|---|---|---|
backtest_v4_entry_exit_opt.py:225-237,268-279 |
robust() 用 S3 留出切片年化决定候选能否进推荐表(排序虽用 T12),违反自述「S3 样本外」 |
稳健门槛只用 T12/滚动 WF,S3 只报告 |
backtest_exit_roll.py:122-128,191-217 |
默认 --win 120 --step 60,相邻窗重叠 60 日,「窗口 k 选 → k+1 验」含半窗共用数据 |
step>=win,或改称「滚动稳定性」不叫 OOS |
backtests/sweep_risk.py:14-19,45-79 |
约 30 组在同一全样本选优(自注「样本内」);buy_th/cooldown 两维实际无效——backtest_bsmatrix.gen_signals:168-177 读 CFG.SIGNAL_SCORE_BUY/COOLDOWN,不读 RISK_PARAMS["_t"] |
修 gen_signals 读被扫描参数;CFG.RISK_PARAMS 注释降级为「ATR/移动止盈两维·样本内扫描」 |
backtests/sweep_risk.py:85 |
_os_boot/_RESULTS_DIR 未定义,脚本必然 NameError(当前不可复现) |
补 import/常量或归档删除 |
backtests/backtest_exit_ablation.py:68-163,181-214 |
68 个退出变体在同一 252 日窗口全样本排序,头部候选直接进 _V4_VARIANTS/生产默认,无留出 |
报告已点名(reports/回测复核报告_20260913.md:125);建议默认只用 train→val 协议重选 |
P1-7 组合层丢选型:chip_peak/l2_ind/sector_rot 在组合回测里无信号
backtests/backtest_strategy_portfolio.py:72-77的_GEN只有macd/kdj/rsi/boll/ma_trend/l1_pattern;stock_gui.strategy_signals_full(stock_gui.py:3745-3752)包含另外三个,消融选中占比合计约 10% (research/strategy_ablation_summary.json)。backtest_portfolio_overlay_opt.py复用_worker_slot同病。- 影响:组合层回测系统性漏掉这部分股票的选型信号,结论与 GUI 展示不同口径。 修复:补齐映射或显式剔除并披露占比。
P1-8 全库回填另一路径:backfill_etf.py 用新浪不复权价写库(本轮新增)
backfill_etf.py:18-25用_fetch_sina(URL 无复权参数,stock_gui.py:1271-1294)写daily_bars;ETF 分红/份额折算会在不复权序列留下向下跳变,而_bars_anomalous对 ETF 孤立跳变放行(stock_gui.py:1024-1031)。- GUI 的「ETF 回填」按钮走集成版
backfill_etf_history(stock_gui.py:2407,hfq +_sync_adjust), 本文件是遗留一次性脚本;但若直接运行会污染 ETF 序列,影响universe=etf回测。建议删除或改 hfq。
3. P2(口径 / 披露 / 工程债)
_set_adjust旧内嵌路径仍用实时价配「已过滤今日」的末根(stock_gui.py:1981-1982,2133-2134)——sync_adjust.py:8-15已修复的 bug①,这两处未同步;盘中运行会把整段历史按今日涨跌幅缩放。_v4_mkt_ind_ctx行业维度幸存者/时点分类(stock_gui.py:6940-6966):退市股industry=''不参与行业均值(行业收益偏高),行业名取当前快照;市场等权混入 ETF/指数。sector_mom_series、sector_l2_series(stock_gui.py:4986-5018、4891-4947)同样按当前 industry 分组。- factor_lab「大盘5日」是零信息列仍被贪心选中:
panel.py:414-432日内方差为 0 的列原样置 0,enumerate.py:66-68再逐日中心化 → 该列贡献恒 0;validate.py:58-59贪心以ic > best免费搭车, 入选频率 0.90,只在validate.py:324-325的abs(single_ic)>1e-4守卫才排除; 同时让 2^21 枚举 / BH 家族出现约一倍重复假设。 - factor_lab
top_val_oracle落盘(enumerate.py:228,239):当前无消费者,但把纯验证段排行持久化, 流程上易被反选;建议标记 diagnostic-only 或移除。 - factor_lab 没有 untouched holdout:
EVAL_DAYS1000→2000、多次换验证段重跑 (full2000/sample1500/…),验证段已被人眼反复查看;报告需区分「一次性留出」与「调参后再看」。 - 研究缓存无 CFG 指纹:
bt_common.write_run_meta不记录W_WINDOW/TOPK/IND_W/RISK_PARAMS与v4_preds.pkl/panel.npz的 mtime/hash,旧口径无声回流(P1-4 的根因)。 - Web 历史统计信号用最新量比回填历史(
stock_web.py:516-528):dVol(j)用vrNow(最后一根量比) 与历史 j 比较,图上历史 B/S 与「最近信号」是事后口径,复盘不可复现(仅展示)。 - Web 文档不实 / 披露不足:
ARCHITECTURE.md:500-502写「同样的加权分位区间法」 「方向交替与冷却过滤器(与 GUI 一致)」;实际是等权原始分位(无相似度/时间衰减权重、 无INTERVAL_K),MACD/KDJ/RSI 各自独立打点(无 prev_dir/cooldown)。 要么补实现,要么改文档;页面仍未标「弱化版/W=10/无置信度」。 - 仪表盘默认展示
segment=full批次且无「样本内」角标(backtests/v61_dashboard.py:954-959等); GUI 单股回测「无手续费」限定在仪表盘/导出 meta 丢失(stock_gui.py:3535-3632无费用, 面板有标注;v61_dashboard.py:738-750未继承)。 另:PAGE_LITE(stock_web.py:1125内嵌 ES5)的 KDJ 模式引用不存在的V.j、 样本无hi_o/lo_o字段,KDJ 副图与 P10/P90 高低区间在/lite上是 NaN——顺带修复。 backtest_aggr_lottery.py:66-68用当前 ST 名单回填历史股票池(今天 ST 的整段剔除、 历史曾 ST 的保留),且shares=mktcap/现价假定股本不变;属研究脚本前视(同文件267)。
4. 本轮复核通过(无需改)
- GUI 形态匹配:
_sig_l1_pattern候选k+W<=i-W;analyze候选窗上界len(rets)-W(与当前窗相邻不重叠)。 - 事件回测:
_bt_events/_bt_simulateT 日收盘信号 → T+1 成交、止损用 T-1 ATR、highest 只在成交后更新。 - 消融:
run_ablation近端门槛取split(训练段末尾),backtest_strategy_ablation.py已同步; 经验核验 10,777 候选recent.trades <= train.trades,验证段只进报告。 - v4:折间 purge+embargo(
tr_end=a-maxH-embargo)、标准化/Lasso/选 H 只用训练段、 已删除「按测试折 IC 弃用预测」的旧泄漏;组合回测_v4_morning_view决策列后移、 涨跌停执行口径默认 close 正确。 - 三档引擎:
gate用 T-1 收盘、d=t-1取分、T 收盘成交、费用/整手/停牌/退市 20 根强平齐全。 - factor_lab:train/val 无重叠(实测 overlap=0,1 日 embargo)、BH-FDR 家族由训练段选出、
验证段只检验;
matching.py/chips.py/factors.py扰动测试确认因子因果。 - Web:逐窗 z-score + logret + 欧氏距离与 GUI 同式、MACD/KDJ/RSI 参数一致、不在未完成 bar 标信号。
5. 修复顺序建议(二轮)
- 数据安全:
backfill_full.py改 hfq / 按钮改调集成版;抽查并重灌被 qfq 覆盖过的代码段。 - 回测口径:三档
tier_load_panel绝对价/流动性门槛改 point-in-time 或明示不可复现;daily_picks仙股过滤乘k。 - 研究可信度:
v4_preds.pkl/panel.npz重跑 + 缓存加 CFG 指纹;backtest_v5_cross_section涨跌停掩码修口径;backtest_v4_entry_exit_optS3 退出选择;backtest_exit_rollstep>=win。 - factor_lab:股票池去掉 industry 硬过滤(含退市);L2/L3 改滚动市值; LASSO CV 按日期分组 + purge;重跑。
- Web:样本窗口隔离+去重、历史量比因果化;文档与页面标注弱化版。
- 工程:删/改
backfill_etf.py、修sweep_risk.py或归档、补_GEN三个信号。
二轮专项基于 2026-09-26 工作区(HEAD f212e05 + 未提交筹码 UI 改动);只读审查,未改代码。 不构成投资建议。
6. P0 处理记录(v6.1.5 热修⑩,2026-09-27)
| 项 | 处理 | 说明 |
|---|---|---|
| P0-1 全库回填 qfq 覆盖 hfq | ✅ 已修 | backfill_full.py:_tx_fetch 改 hfq/hfqday;_store 增重叠日口径校验(收盘偏差>1% 拒写);写后调 sg._sync_adjust 刷新显示 k;docstring 同步。实测 sh600000 腾讯 hfq 与库内重叠日偏差 0.0000%。GUI「数据工具→全库回填」走的即该脚本,现已安全(未改调集成版)。 |
| P0-2 三档门槛用今日复权锚 | ⚠ 部分修复+备案 | daily_picks 仙股过滤改为 hfq×k(显示价)≥2(原直接比 hfq 值形同虚设);_TIER_MIN_PRICE/_TIER_MIN_AMOUNT 处加口径备案:门槛以今天为锚,跨日期重跑历史池不可完全复现(收益率类指标不受影响,仅可交易池/绝对阈值受损)。彻底修复需库内保存历史复权因子(point-in-time),列为后续项。 |
| P0-3 factor_lab 幸存者偏差 | ✅ 已修,重跑中 | factor_lab/data.py:list_codes 不再要求 industry 非空(只要求 stocks 表内);panel.py L2/L3 分组排除「行业/分层未知」组(缺失按中性)。实测代码池 5420 只,2018–2026 退市的 229/232 只已纳入。全量重跑已启动:research/factor_lab/full2000_nosurv/(8 workers,预计 ~4 小时),跑完后再更新 factor_lab 结论。 |
P1/P2 未动(见上节修复顺序建议)。