技术博客

事前风控指标实现:可用资金与持仓预占的更新时机

第 60 / 141 篇 · 2026-09-25 · secfin · 风控并发控制交易系统

事前风控检查链路(《事前风控检查链路》讲过全链路)里最核心的两个指标——可用资金、可用持仓——的实现看起来简单:查询、比较、放行。真正的难点藏在"可用"两个字里:可用余额必须是现在这一刻的准确值,而系统没有时间去实时算它。这篇讲这两个指标怎么用预占模型实现,以及预占的三个更新时机各自在防什么。

预占模型:把实时计算变成增量维护

朴素思路:委托进来时算一次可用资金(查余额、汇总全部未成交委托的冻结金额、相减),算完比较。这条路在两个地方走不通。一是性能:汇总未成交委托在委托量大的账户上是几十条记录的聚合查询,毫秒预算内跑不完;二是并发:两个线程同时算,都看到同样的余额,都放行,账户就透支了。

预占模型把思路反过来:不每次重算,而是把每次委托的影响提前记到账上。账户维护两个数:余额(真实发生变动的部分)和预占(已承诺但未交割的部分)。可用资金就是:

$$ \text{可用资金} = \text{资金余额} - \sum \text{未成交买入委托预占} + \sum \text{已卖出未回款} $$

关键操作从"查询时计算"变成"事件时更新":委托进入时预占扣减、成交回报时把预占转成真实变动、撤单时释放预占。查询退化为一次减法,亚毫秒天然达标。这和会计里的"权责发生"是同一个思想——影响在承诺发生时确认,而不是在交割发生时补记。

三个更新时机:每个都在堵一种错

预占的生命周期跨越三个事件,每个时机的处理都在堵特定的一致性漏洞。

委托进入(预占扣减):委托通过校验的瞬间,按委托金额(买入)或数量(卖出)扣减预占。为什么按委托全额而不是按潜在成交部分——因为下单时不知道会成交多少,按最大可能占用预占,宁可多占不可少占。这个时点要解决的就是上面说的并发问题:同一账户两笔委托同时到达,扣减必须原子。实现上用账户行的行锁,或者更精细的无锁原子操作(UPDATE ... SET frozen = frozen + ? WHERE acct = ? AND balance - frozen >= ?,用影响行数为 0 即拒单——把校验和扣减合成一条原子语句,数据库帮你解决并发)。

成交回报(预占转实):成交发生,预占金额转成真实的资金扣减或股份过户。这里有一个精度修正:委托按限价冻结(买入按限价 × 数量),实际成交可能低于限价,多占的部分要释放。忽略这个释放,账户的可用资金会越用越少—— клиентs notice 的方式是"我没多少钱了但什么都买不了"。修正发生在回报处理时:按实际成交金额重算本次应占金额,差额回补。

撤单(预占释放):撤单成功,对应预占全额释放。看起来 trivial,实则有一个坑:撤单请求到达时委托可能正在撮合——请求和成交回报在路上交叉,释放和转实两条路径竞争同一笔预占。处理顺序必须靠委托状态机保证:只有确认委托已退出订单簿(《撤单与改单的处理》的状态迁移),撤单确认才允许释放预占。先释放后成交(成交回报迟到)会凭空多出可用资金,这才是真正的事故源。

持仓预占:同一模型的镜像

卖出方向的可用持仓是完全对称的模型:持仓余额、卖出预占、可用持仓 = 余额 − 卖出预占。T+1 市场的特殊点在于持仓的余额本身带时间维度:今天买入的持仓余额里有"在途"部分(已成交未交收),这部分 tomorrow 才变成可卖。所以可用持仓的完整公式是:

$$ \text{可用持仓} = \underbrace{\text{持仓余额} - \text{当日买入在途}}_{\text{T+1 可交割部分}} - \sum \text{未成交卖出预占} $$

三个减项的来源不同:在途部分是清算维度的时间延迟,卖出预占是交易维度的并发保护。把两者混为一个"冻结"字段是最常见的实现错误——T+1 在途的释放由交收批次驱动(时间触发),卖出预占的释放由撤单/成交驱动(事件触发),两个释放机制的生命周期完全不同,字段混在一起会在对账时互相污染。

指标缓存:预占之外的那部分

预占解决的是"自己发出去的委托"的影响,但可用资金还受外部因素影响:红利入账、费用扣收、其它业务线(两融、逆回购)的负债变动。这些变化的频率远低于委托频率,适合做成事件驱动的缓存刷新:任何影响账户余额的事件发生时,向风控指标缓存发失效通知;下一次查询时重建。重建本身是减法(余额减预占),快的。

缓存的一致性问题在于失效通知的丢失:通知队列抖一下,缓存里就是旧余额,此时放行一笔委托就是按错误数据放行。兜底设计是缓存条目带版本号,源头数据每次变更递增版本;风控查询时校验版本新鲜度,超过阈值(比如 500ms 没更新)直接回源查询——宁可慢一次,不可用旧值放行。这个"新鲜度兜底"的阈值就是业务对一致性和延迟的妥协点:阈值设太短,缓存形同虚设;设太长,错误放行窗口变大。实践中按行情波动率定—— calm 期间错误放行的风险低,极端行情里缓存错误会批量放大成事故,阈值应该随市场状态动态收紧。

预占生命周期:委托进入 → 成交/撤单 → 转实或释放可用余额余额 − 预占委托进入原子扣减预占成交回报预占转实+差额修正撤单确认全额释放预占并发防护:校验与扣减合成一条原子 UPDATE,影响行数为 0 即拒单T+1 在途与卖出预占分字段:时间触发释放 vs 事件触发释放缓存一致性:条目带版本号,超阈值未刷新则回源查询阈值随市场状态动态收紧,宁可慢一次不用旧值放行