2.5 应用层:用户态进程——权益资产的“并发与沙箱”

2010年,一个名叫Navinder Singh Sarao的英国交易员,在他父母家的卧室里,用一台改装过的电脑和一套自己编写的交易程序,做了一件让全球金融系统颤抖的事。他的程序不断地向市场发送巨量的虚假卖单,然后在成交前的一瞬间撤掉。这些订单像幽灵一样,从来不会真正成交,但它们的存在让市场上的其他算法误以为有巨大的抛压,纷纷跟着抛售。他的幽灵订单层层叠叠地堆在订单簿上,像内存泄漏一样占据着交易系统的处理能力。

然后,2010年5月6日,闪电崩盘来了。道琼斯在几分钟内暴跌近1000点。Sarao后来被引渡到美国,面临操纵市场的刑事指控。

我讲这个故事,不是要教你怎么写一个攻击交易所的程序。我是要你注意一件事:Sarao的程序做的事情,和你账户里那些乱七八糟的持仓做的事情,在底层原理上是一样的------它们都是没有配额的、可以随意占满整个系统资源的、一旦失控就会拖垮宿主机器的恶意进程。

区别只在于,Sarao知道自己写的是一个攻击程序。而你不知道自己正在攻击自己。

沙箱隔离:每一只股票都是一条独立的进程

你写过Dockerfile吗?或者至少,你用过容器。

容器化最核心的设计理念不是虚拟化,不是编排,不是微服务------是限制。你启动一个容器的时候,你会给它指定CPU配额、内存上限、IO带宽。它可以用满你分配给它的那些资源,但绝对不能越界。它的文件系统是隔离的,看不到宿主机的敏感数据。它的网络是命名空间隔离的,访问不了你没有授权的端口。它崩溃了,只是那个容器重启,宿主机毫发无损。

现在请你打开你的股票账户,看看你的持仓列表。

有几只股票占了你的总资产超过10%?如果有,请你在这只股票的名字后面打个标签:【容器逃逸】。

一只股票占你总资产超过10%,意味着什么?意味着你的沙箱配额已经失效了。你给这只股票分配的CPU核数没有上限,它可以随时吃掉你所有的算力。你给它的内存没有limit,它可以在五分钟内把整个宿主机的物理内存吃光。它出了问题,不是它这一个容器重启,是整台机器直接OOM。

我不是在吓你。你回忆一下你自己有没有做过这样的事:一只股票从盈利15%跌到亏损5%,你舍不得卖。它继续跌,亏到15%,你心里想"都跌这么多了,反弹一下我就走"。它再跌,亏到25%,你开始研究这家公司的基本面,给自己找长期持有的理由。它最终跌到40%,你关了软件,跟自己说"只要不卖就不算亏"。

这个从亏5%到亏40%的完整路径,就是一个恶意进程从触发OOM警报到最终吞噬整个宿主机的完整时间线。如果你的单票仓位上限是10%,它在跌到25%的时候对你总资产的伤害是2.5%。疼,但不致命。如果你单票仓位是40%,它跌25%的时候你已经亏了总资产的10%。你的内核层开始紧张,你的系统服务层开始动摇,你的生活开始被一只股票的K线遥控。

沙箱配额不是对你的束缚。沙箱是对你人性最脆弱那一面的提前约束。你无法在它跌了20%的时候理性决策,因为那时候你的大脑已经被亏损接管了。你能做的,是在你理性的、平静的、一切正常的时候,把配额写死在你的投资系统里。

多少合适?单票不超过总资产10%,单个行业不超过30%。这个数字不是我拍脑袋拍的。巴菲特的伯克希尔哈撒韦在最集中的时候,单票也没超过他投资组合的20%。他手上有几百亿美元的浮存金,有被投公司的董事会席位,有对管理层的直接影响力。你没有这些。你凭哪一点比巴菲特更集中?

进程优先级:你不需要每一笔交易都拼尽全力

在Linux里,每一个进程都有一个Nice值。范围是-20到19,数值越低优先级越高,数值越高优先级越低。Nice值高的进程在CPU忙的时候主动让出时间片,等那些重要的任务执行完了,再把剩下来的算力捡起来用。

你的持仓,也需要Nice值。

我最推荐工程师采用的持仓结构,叫"核心-卫星策略"。核心仓位,是你的指数ETF------沪深300、中证500、或者一只你信任的主动型宽基基金。这部分仓位占你权益资产的60%到80%。它的Nice值是0------优先级最高,占最多的CPU配额,跑最长的运行时间。你不交易它。你只在每个季度的资产再平衡时检查一次,如果偏离目标配比太多就调整回来。其他时间,它就像一个写好了的守护进程,在后台安静地跑着,不占你的注意力资源。

卫星仓位,是你的个股选择、行业ETF、以及那些你基于自己的技术背景判断出来的"高确定性机会"。这部分仓位占你权益资产的20%到40%。它的Nice值是15------低优先级,可以随时被kill,不影响系统整体运行。你可以在卫星仓位里尝试你的技术尽调方法,测试你对某个行业的判断是否准确,甚至小赌一下Crypto的短期波动。但它的内存配额是严格上限的。亏完了,就是进程结束,重启一个新进程就好。

这种结构有一个极其反人性但极其有效的副作用:它让你的核心仓位和卫星仓位的表现完全分离,你可以清清楚楚地看到,你的超额收益到底来自指数的被动上涨,还是来自你自己的主动选股能力。大多数人在混合持仓的时候会自我欺骗------把指数的上涨当成自己的选股水平,把自己的选股亏损归咎于大盘不好。核心-卫星策略强迫你把这两件事分开记账。你的调试日志从此有了对照组。

如果你的卫星仓位连续两年跑不赢核心仓位,你有一个不需要犹豫的决策:把卫星仓位全部合并进核心仓位,承认自己的主动选股暂时不具备可持续的优势,等有新的方法论再说。这不是认输,是版本回滚到上一个稳定版本。你写代码的时候天天做这件事,投资的时候为什么舍不得?

垃圾回收:不产生价值的持仓就是内存泄漏

C语言里最让新手崩溃的东西是什么?内存泄漏。

你malloc了一块内存,用完了,忘了free。程序继续跑,那块内存被标记为"已占用",但没有人真正使用它。它就在那里,像一个占着工位不干活的人,每天消耗系统的电费,从不产出任何价值。你开发的程序如果每天泄漏几百KB,头几个月用户完全感受不到。然后某一天,系统开始变慢,开始卡顿,开始频繁触发OOM。你查了半天,发现是三个月前那个忘了free的指针。

你的股票账户里,有多少被忘了free的指针?

我说的不是那些正在亏损的股票。亏损的股票可能是你主动承担的、有预期的风险,只要它在你的策略框架内运行,它就是合法的内存占用。我说的是那些你已经没有任何理由持有、只是因为"卖掉了就等于承认亏损"而一直留在账户里的股票。它们是你过去某个错误决策的墓碑,而你还在每个月为它们交管理费。

这类持仓有一个很明显的识别特征:如果有人今天问你"你现在有钱,你还会买这只股票吗",你回答"不会"。但你也没有卖掉它。你在等。等什么?等它回到你的成本线。你不关心这家公司现在的竞争力如何,不关心它的行业前景是否已经恶化,不关心它有没用完你给它分配的内存配额。你只是在等一个和你当初的买入价相等的数字,然后你就可以把这个指针free掉,假装它从来没有泄漏过。

等成本线,是投资世界里最昂贵的一种等待。它花费的不是你的钱------你的钱已经在那只股票里了。它花费的是你的机会成本。你让一笔已经没有生命力的资金,以"持仓"的名义被永久锁定,而它本可以被释放出来,投入到另一个更有希望的机会里去。它占着内存不干活,而你的其他进程在排队等资源。

我给这个问题设计的解决方案极其简单粗暴。每个季度的资产再平衡日,做一件你给代码仓库做的事------垃圾回收。

打开你的持仓列表,一只一只地问自己三个问题:第一,如果今天我有等额的现金,我会不会买它?第二,它在我组合里的存在,除了"等回本"之外,还有没有其他不可替代的战略价值?第三,它在过去四个季度里,有没有创造出超过无风险利率的价值?

三个问题中只要有一个答案是"没有"或者"不会",它就是需要被回收的垃圾对象。不要犹豫,不要给自己留"再看看"的空间。你清理代码仓库的时候不会对着一行半年前写的烂代码说"说不定以后有用"。你是工程师。你知道烂代码留着只会让后面的重构更痛苦。

这一节写到最后,我给你总结一套三层递进的执行标准。它不只是给你看的,是给你明天开盘前执行的:

打开你的证券账户,检查每只股票的仓位占比。超过10%的,在下一周之内降到10%以内。不因为你对它多熟悉,不因为它跌了多久你要等反弹。配额就是配额。操作系统不会跟一个容器谈判。

把你的持仓分成核心和卫星两部分。核心部分定一个你绝对不会打破的调仓频率------比如每季度一次。卫星部分设定总上限------比如权益资产的30%。卫星亏损触及上限的50%,就全部清掉,重新开一个新的沙箱。

把你今天看完这一节之后能识别出来的"垃圾对象"列一个清单,附上它们今天的市值。回答我刚才那三个问题。三个答案里有一个是"否"的,明天挂单卖掉。这笔钱不放在账户里闲置,立刻转入核心仓位的指数ETF。

你的权益资产是你财富操作系统里最有趣、最刺激、也最危险的用户态应用。它给了你超额收益的可能,也给了你超额亏损的自由。沙箱、优先级、垃圾回收------这三样机制不保证你在应用层每一局游戏都赢。它们保证的是,你有一局游戏打输了的时候,你的人生不会跟着输掉。

下一节,我们将进入这张架构图的最后一块拼图------文件系统与快照。你会学到怎么用你熟悉的数据持久化手段,去管理你整个财富系统的元数据,让你在任何时候都能知道自己到底有多少钱、它们在哪里、它们之间是什么关系。这不是记账。这是你财务系统的日志型文件系统。