You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Redis中实时计算成交量加权平均价(VWAP)

Redis侧无应用层全量加载实现VWAP聚合计算方案

核心逻辑

放弃每次报价变动时拉取品种下所有行情源数据全量计算的思路,为每个交易品种维护两个聚合中间值,所有计算在Redis服务端完成,全程不需要回传全量报价到业务层:

  • 加权总成交额(VWAP公式分子:Σ(单源价格 * 单源成交量))
  • 累计总成交量(VWAP公式分母:Σ(单源成交量))
    最终VWAP值直接通过加权总成交额 / 累计总成交量计算得出。单源报价变动时仅做增量差值更新,时间复杂度O(1),不受行情源数量增长影响。

具体实现

1. 存储结构设计

保留原有单源报价的Hash存储结构,额外新增3个固定Hash键存储聚合数据,不需要修改现有单源报价的写入格式:

  • 原有单源报价键:命名规则保持{交易品种}:{行情源ID},Hash字段存price(价格)、volume(成交量)
  • 聚合键agg:vwap:numerator:Hash类型,field为交易品种ID(如XYZ、ABC),value为对应品种的加权总成交额
  • 聚合键agg:vwap:denominator:Hash类型,field为交易品种ID,value为对应品种的累计总成交量
  • 结果键agg:vwap:result:Hash类型,field为交易品种ID,value为对应品种最终计算的VWAP价格

2. 原子更新计算Lua脚本

所有行情源的报价更新操作,统一调用预加载的Lua脚本执行,全程原子完成增量计算,应用层仅需传入新报价参数即可直接拿到最新VWAP结果,不需要拉取任何其他行情源的数据。
脚本内容如下:

-- KEYS入参顺序:单源报价键、聚合分子键、聚合分母键、VWAP结果键
-- ARGV入参顺序:交易品种ID、最新价格、最新成交量
local symbol = ARGV[1]
local new_price = tonumber(ARGV[2])
local new_vol = tonumber(ARGV[3])

-- 读取该行情源旧的报价数据
local old_val = redis.call('HMGET', KEYS[1], 'price', 'volume')
local old_price = tonumber(old_val[1]) or 0
local old_vol = tonumber(old_val[2]) or 0

-- 计算新旧值的贡献差值,做增量更新
local old_contrib = old_price * old_vol
local new_contrib = new_price * new_vol
local delta_num = new_contrib - old_contrib
local delta_den = new_vol - old_vol

-- 更新聚合中间值
redis.call('HINCRBYFLOAT', KEYS[2], symbol, delta_num)
redis.call('HINCRBYFLOAT', KEYS[3], symbol, delta_den)

-- 写入该源最新报价
redis.call('HMSET', KEYS[1], 'price', new_price, 'volume', new_vol)

-- 计算最新VWAP,兼容成交量为0的极端场景
local total_num = tonumber(redis.call('HGET', KEYS[2], symbol)) or 0
local total_den = tonumber(redis.call('HGET', KEYS[3], symbol)) or 0
local vwap = 0
if total_den > 0 then
    vwap = total_num / total_den
end
redis.call('HSET', KEYS[4], symbol, vwap)

return vwap

Node.js侧调用示例(以ioredis客户端为例):

// 预加载脚本生成sha值,后续用evalsha调用减少网络开销
const scriptSha = await redis.script('LOAD', luaScriptContent)
// 收到Source1的XYZ最新报价 price=101, volume=10200时调用
const latestVwap = await redis.evalsha(
  scriptSha,
  4,
  'XYZ:Source1', 'agg:vwap:numerator', 'agg:vwap:denominator', 'agg:vwap:result',
  'XYZ', 101, 10200
)
console.log('XYZ最新VWAP:', latestVwap)

3. 多写入端适配方案

如果存在多个业务方无法统一收口写入入口,不需要强制所有写入方改调用Lua脚本,可以通过以下两种方式实现无侵入计算:

  • Redis 7.0+版本:将上述计算逻辑注册为Redis Function,配合键空间触发器,直接在Redis内部监听单源报价键的Hash写入事件,自动触发增量计算,完全不需要应用层参与
  • 低版本Redis:开启键空间通知(配置notify-keyspace-events Kh),部署一个极轻量的消费进程,仅接收报价变更事件、不拉取全量数据,收到事件后触发增量计算逻辑即可,网络开销可以忽略

首次上线初始化时,只需要执行一次全量遍历脚本,在Redis侧计算出所有品种的初始聚合中间值即可,后续所有更新全程走增量逻辑,不需要再扫描全量键。初始化过程同样不需要把全量数据传回应用层。

方案优势

  • 零全量传输开销:不需要拉取任意品种下的所有行情源数据到应用层,单条更新的网络传输量仅为几个参数的大小
  • 性能稳定:增量计算为O(1)复杂度,单品种挂载上百个行情源也不会增加计算耗时,单Redis节点可轻松支撑10w+ QPS的报价更新
  • 一致性强:Lua/Redis Function为原子执行,不存在并发更新导致的计算结果错乱
  • 结果复用性强:所有业务需要获取VWAP时,直接执行HGET agg:vwap:result 品种ID即可,不需要重复计算

内容的提问来源于stack exchange,提问作者PirateApp

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.29 08:33:10