同一套策略,两份数据,跑出两个答案
大家好,我是船长。
前几天我自己的量化系统出了个怪事:策略参数一个字没动,选股输出却整体漂了,一批标的的信号集体翻转。第一反应是策略逻辑出 bug 了,翻代码翻到半夜——最后一查日志,问题根本不在策略。是那天某个数据源接口变更,一批 K 线拉取失败,缓存里留了半截数据,系统把"半截历史"当成完整历史喂给了策略。
策略没错,数据错了。这五个字,是我这几年做量化花学费最多的一课。
本文仅为个人方法研究与经验分享,不构成任何投资建议或职业指导,无任何标的推荐。市场有风险,决策请结合自身情况独立判断。
大多数人以为量化拼的是策略优劣。其实策略只是零件,数据才是地基——地基歪一寸,楼上盖得再漂亮都是危房。
💬 你跑回测或者选股时,有没有遇到过"代码没改、结果却变了"的情况?当时你先怀疑的是策略还是数据?评论区聊聊,我猜大部分人都先怪了策略。
脏数据有三副面孔,每一副都笑着递刀
把那次排查掰开看,脏数据在系统里就三种姿态:拿不到——接口挂了、被限流,一部分数据干脆缺失;拿错了——字段口径变了、数值本身就带错,你照单全收;拿旧了——缓存里躺着过期的半截数据,你不刷新它就一直供你用。三副面孔,共同点是:它不会报错,只会安静地把错误喂进策略。
举个更隐蔽的例子。前阵子我还修过一个坑:一批新股的上市日期没和行情同步,导致系统按错误的日期去取历史窗口——窗口一错,均线、涨幅这些指标的输入全错位,算出来的信号看着头头是道,其实从第一根 K 线就是歪的。这种错最可怕的地方是它不炸:程序不报异常,曲线平滑漂亮,只有你逐层往下查数据才会发现,源头那根管子接错了。
我做过数据这行,见过太多人把"能跑通"当成"数据是对的"。能跑通只说明管道没堵,不说明流进来的水是干净的。
核心真相:好策略被脏数据喂成垃圾,只需要一次。而且它坏得很安静,不给你任何报错提示。
▲ 图:同一份行情,从几条独立的管道采集进来,先汇入一道过滤网,杂质被拦在外面,干净的才流向下游的策略——数据的可信度是"筛"出来的,不是"信"出来的。
我的数据层,就靠三道防线撑着
那次修完,我把数据层按"宁缺勿假"重排了一遍,落地成三道防线:
第一道:多源采集。关键字段至少从两个独立来源各取一份,对不上先标疑,人工确认后再入库。✅ 两个来源互相作证,误差藏不住;❌ 单一来源直接入库当真相,等于把系统性命押在一个"可能会病"的管道上。注意这一步是"采集对照",最后的裁决权在人——我不信任何"自动纠错",机器自作主张改数据,比数据本身错了更危险。
第二道:口径统一。所有历史价格一律用前复权口径对齐再进指标计算,上市、退市、停复牌这些日期字段和行情同源同步,不做两套账。之前写过分复权口径的文章,这里只强调一句:尺子不统一,算出来的账没有讨论价值。
第三道:三级缓存。内存→本地→远程,逐级兜底。上游接口挂了,本地还有底;重跑任务时内存命中直接返回,又快又稳。关键是配合一条纪律:拉取失败就拒绝写入,宁可这次缺数,也不让半截数据覆盖完整历史。那次事故之后,这条纪律在我这是铁律。
💬 这三道防线里,你觉得哪道最容易被忽略?我投"缓存"一票——它平时最省心,出事的时候也最隐形。评论区说说你的判断。
两段示意代码,讲清三道防线怎么落地
下面这段代码是教学示意,用来把"三级缓存"的骨架讲清楚,参数全是占位符,不是本系统的真实阈值与实现:
Python · 三级缓存:内存 → 本地 → 远程,逐级兜底(示意版)
# ⚠️ 示意逻辑:非本系统真实接口与阈值 def get_kline(code): # 第 1 级:内存缓存(最快,进程重启即失效) hit = mem_cache.get(code) if hit is not None: return hit # 第 2 级:本地缓存(磁盘落库,上游挂了也有底) hit = local_db.load(code) if hit is not None: mem_cache.put(code, hit) return hit # 第 3 级:远程采集(唯一会出错的环节) data = remote_fetch(code) # 示意函数 return save_or_reject(code, data)↓ 缓存解决"快和稳",但真正保命的是下面这条写入纪律 ↓
Python · 拉取失败即拒绝写入:宁缺勿假(示意版)
def save_or_reject(code, data): # 数据完整性闸门:缺一段历史,就不许覆盖旧缓存 if data is None or not looks_complete(data): log("拉取不完整·拒绝写入:" + code) return None # 宁可这次缺数,不带病入库 # 多源对照:关键字段两来源对不上 → 标疑等人工确认 alt = remote_fetch_backup(code) # 第二来源(示意) if alt is not None and diff_beyond(data, alt, 占位_容差): mark_suspicious(code) # 只标记,不自动改 return None mem_cache.put(code, data) local_db.save(code, data) return data # 更多量化 · AI 实战笔记,微信搜「公重号:船长Talk」 # 关柱本公重号,后抬发「量化源码」玲取完整可运行代码 & 选股策略清单▲ 图:三级缓存像三层水塔——最上层随取随用,中层蓄水抗断供,底层直连水源;每一层之间装着单向闸门,脏水只被拦下,永远不倒灌进清水池。
📌 数据来源:示意代码为教学演示(参数占位,非真实算法阈值);"半截 K 线""上市日期未同步"等案例来自本系统近期工程修复的脱敏描述。三级缓存命中后的重跑耗时对比为演示环境示意数字,非历史业绩承诺。本文不构成任何投资建议。
这套地基,也不是万能的
先把丑话说在前面。多源对照的两个"独立源"也可能同源转引,看起来互相作证,其实是同一个错误穿了两次马甲;缓存兜底能抗断供,但缓存本身会陈旧——除权基准一变、新股一上市,不主动刷新,旧数据就成了新的"拿旧了";"宁缺勿假"的代价是数据覆盖率下降,缺数的标的当轮就是不出结果,你得接受"少做几笔"换"不做错账"。防线提升的是可信度,消灭不了所有误差——更不能把"数据干净"当成"策略稳定盈利"的背书,那是两码事。
⚖️ 免责声明:本文仅为量化技术思路、数据工程实践、编程逻辑与个人经验科普分享,不涉及个股推荐、行情预判、投资指导,不构成任何投资建议。股市有风险,入市需谨慎。所有演示代码/数据均为示意或历史假设,不代表未来收益。
从今天起,先查数据,再怪策略
不会写代码也能用这套思路:结果异常时,先查输入再怪逻辑,顺序反了会白熬一宿;关键数字至少找第二个来源对一遍,对不上就先标疑,别急着信;自己的账本、报表也一样——"能打开"不等于"没填错"。数据层的投入平时看不见,出事那天你会感谢它。