白板前,尹航还在盯著那道拆盲盒题。
他的大脑正在高速运转,试图在这张错综复杂的概率网络里找到哪怕一丝破绽。
姚思雨站在另一侧,手里握著板擦,已经把旧递推公式毫不留情地划掉。
江临站在一旁,却没有继续往下讲。
这几道题到这里就已经足够了。
阿里全球数学竞赛预赛的题目,真正的价值从来不在於他把最终那个光禿禿的答案餵给別人,而在於看到那条线的过程。
先抽象。
把现实世界里极其复杂的盲盒概率、卡池保底机制,剥离掉所有花哨的商业外衣,变成纯粹的马尔可夫链。
再压缩。
把数以万计的节点状態,根据对称性压缩成一个只与剩余未收集种类数相关的最小一维数组。
最后验证。
用严密的逻辑去证明,这种降维压缩没有在任何一个微小的概率分岔口上丟掉必要信息。
尹航手里的马克笔在指尖转了半圈,笔帽啪地一声敲在掌心。
他转过头,用一种近乎看怪物的眼神看著江临,忽然问:“你別告诉我,这十一道题,全都是像刚才这样,一眼看穿底层结构,然后没有任何卡顿地写完的?”
江临想了想,认真地摇了摇头:“不全是,有一道代数变形题更偏纯粹的计算和排版表达,没有这么强的结构感,纯粹靠算力堆砌。而且,我也试了几条错路。”
尹航听到错路两个字,肩膀猛地塌了下来,似乎长长地鬆了口气:“靠,原来你也会走错路啊,我还以为你脑子里自带量子计算机呢。”
“我走的错路可能比你们任何人都多。”
江临说著,还举例说明將自己当初在一道数论题上被复杂的表象迷惑,走进死胡同的过程说了一遍。
“所以,你是找正確路线,排除错误方向,大概花了四十分钟。后面用latex整理提交版排版,花了一个多小时。”
尹航被江临这番一本正经的诉苦气笑了。
“谢谢你啊,你终於承认自己花了时间,终於像个人了。但我光是找这道盲盒题的路线,花的时间就是你的四倍,而且还没找全。”
屋里的气氛终於彻底鬆弛了下来。
那种因为高强度智力对抗而紧绷的空气,隨著尹航的自嘲烟消云散。
姚思雨也忍不住笑了一下:“所以陆导让你把b304当成自己家常来,不是没有原因的。你在这里,至少能让我们知道,题目还能被拆到什么程度,不至於刚摸到门槛就以为自己看见了天花板。”
边上的孟澈见江临开始收拾东西,知道他要回去,於是把下载好的几份数据说明与论文列印稿递过去,隨口问了一句:“最近除了阿里预赛,还在忙什么,感觉你这个月都是神出鬼没的。”
“写一个数据检查工具。”
“做什么的?”
“量化相关吧。”
“你还搞量化?”孟澈讶然道。
他正在研究机器学习,很清楚量化金融领域的水有多深。
“你才多大啊,你就搞量化?”尹航也是一副难以置信的模样,“你別告诉我你已经开始炒股了。”
“也就是隨便看看,帮人做点基础的数据梳理。”
但b304三人组显然不相信。
一个能把阿里预赛十一道题当切菜一样做完的人,嘴里的隨便看看,绝对不可能是用excel画几张k线图那么简单。
不过三人见江临没有进一步解释的意思,也就识趣地不去多问。
天才总有自己的秘密领域,这点默契他们还是有的。
傍晚,江临伴著江城的晚霞回到家里。
吃过晚饭,陪父母聊了会閒话,便回臥室忙自己的事情。
工作站电脑打开,有一封新邮件。
发件人:沈承业。
標题:qf-oldlib-001第一批脱敏材料已上传
正文只有简短的一句话:江临,小型私募旧因子库研究流程审计,正式开始,资源已开通。
江临点击连结,开始下载材料。
文件和之前他处理过的那些动輒十几gb 的tick级高频行情数据比起来,不算太大,压缩包只有不到3个g。
但在量化研究的语境里,这种体量的信息密度是极其恐怖的。
解压后,目录树展开。
几张庞大的元数据表。
几份回测配置摘要。
一个脱敏后的因子输出矩阵。
一份很粗糙的歷史备註记录。
还有一张极其关键的状態表。
江临点开状態表。表里密密麻麻地列著四百三十七个因子编號。
在状態这一列里:
有些后面写著:active(目前仍在实盘交易中提供信號的因子)。
有些写著:deprecated(因为绩效衰减,已被淘汰的因子)。
有些写著:merged(因为与其他因子相关性过高,被合併的因子)。
有些写著:deleted(被刪除的因子)。
还有一批,令人触目惊心地写著:unknown(未知状態,连研究员自己都不知道这东西还在不在跑)。
江临的目光在deleted和unknown这两个词上停留了很久,思绪一下子发散开来。
在任何一个量化机构,乃至任何一个科研系统里,成功的东西,总会有人记得。
它们会被写进ppt,会被掛在年报里,会被用来向投资人吹嘘。
但是,失败的东西,常常最先被刪掉。
研究员为了掩盖自己几个月毫无建树的尷尬,或者为了让代码库看起来乾净,会毫不犹豫地按下delete键。
而一个没有失败记录的研究系统,最容易把倖存者当成真理。
在金融市场里,这叫倖存者偏差。
当你只看到那些成功的策略时,你会觉得市场充满规律。
但如果你看不到那99%因为过度擬合而在实盘中亏得血本无归的废弃因子,你就会在下一次研究中,重蹈覆辙。
江临深吸了一口气,熟练地打开终端,新建项目文件夹。
然后用vim打开audit_log.md,敲下了这个审计项目的最高纲领。
第一行:旧因子库首先不是宝库,而是失败记录的墓地。
第二行:本项目不找神因子,不预测未来,只找“研究流程如何骗过自己”。
第三行:所有结论必须严格绑定四个维度:数据版本、样本池版本、因子版本、回测配置版本。脱离版本谈绩效,一律视为学术造假。
写完这三行,他才正式开始第一轮代码层面的扫描。
最初的审计脚本逻辑並不复杂。
江临用python配合pandas和dask,写了几个守卫器。
唯一性检查: 检查因子编號是否唯一,版本號是否连续,每个因子是否严格绑定了当时所用的清洗数据版本。
样本池漂移: 每个回测结果是否绑定了確定的样本池定义?
失败记录完整性: 失败因子有没有保留详细的刪除原因和失效日期的环境切片?
同源性检查: 同名或近似同名因子是否重复出现?
摩擦成本检查: 交易成本假设有没有在不同版本里发生人为的漂移?
回车,运行。
十五分钟后,第一轮扫描结果出来了。很难看,触目惊心的难看。
四百三十七个因子里,有八十多个没有完整的数据版本號约束。这意味著如果现在重跑回测,根本不知道当初是用什么数据跑出来的。
二十七个因子版本断裂,从 v1.2 直接跳到了 v3.0,中间经歷了什么,无人知晓。
十三个因子名称完全不同,但备註里的数学逻辑高度相似。
还有几个因子,名字看起来像是三个截然不同的方向。
但江临的脚本对它们的脱敏输出矩阵求了横截面相关性后发现,它们的皮尔逊相关係数竟然高达0.98。
说明这根本就是同一类想法,在绩效压力和流程失控里被反覆复製、微调参数、改名重跑,试图碰出一个更好看的夏普比率,最后在歷史里留下了三具极其相似的影子。
这就是纯粹的数据挖掘灾难。
江临面无表情,继续跑第二轮:因子输出版本敏感性测试。
第三轮:样本池时间序列漂移测试。
第四轮:失败因子记录完整性穿透。
隨著任务越来越重,工作站脚下的机箱风扇开始持续低鸣,发出沉闷的嗡嗡声。
江临瞥了一眼副屏上的系统监控。
温度:68c,正常。
硬碟读写:正常。
內存占用:正常。
cpu 占用率,却在几个特定的脚本执行时,反覆拉高到100%,红得刺眼。
程序並没有卡死,终端里的进度条还在走。
但它比江临预期的慢,慢得非常不合理。
对於现实世界里的大多数数据科学家或者量化研究员来说,遇到这个情况,第一反应绝对是,机器不够强。
“老板,我们要买更好的 cpu,换 amd 的线程撕裂者。”
“要加更大的內存,把数据全塞进內存里。”
“上aws云伺服器,开个 124 核的实例並行跑。”
用硬体的暴力去掩盖软体的低效,这是和平年代,资源充沛环境下的通病。
但这不是江临的第一反应。
在他的脑海深处,那个属於【废土世界】的倒计时始终存在。
在资源枯竭的废土里,永远没有再买一台的选项。
在那里,机器慢,不能先要资源,必须先问。为什么慢?是什么在吃掉算力?
江临果断按下了 ctrl+c,停掉了后续的扫描任务。
打开python的性能分析工具,將刚才那段跑得极慢的代码用cprofile 重新包起来,然后接入snakeviz进行可视化分析。
二十分钟后,一份详细的调用栈火焰图出现在屏幕上。
真正的瓶颈浮出水面。
在调用栈里吞掉最多墙钟时间的怪物,不是那些几百万行的大矩阵乘法。
不是复杂的hdf5文件读取,不是前端渲染的图表生成,也不是某个玄学的机器学习复杂模型。
而是几个小得近乎不起眼的动作:排序、排名、分桶。
在量化回测中,经常需要在特定的行业特定的市值区间內,对股票的因子暴露值进行中性化和排序。
每次参与排序的股票可能不多,只有五个、八个、或者十六个。
单独看,给五个数排序,不管用什么算法,每一次都快得像没有成本,连一毫秒都不需要。
但问题在於乘数效应。
qf-oldlib-001里有四百多个因子。
三年歷史版本。
多个动態调整的样本池。