如何实时计算时间序列变量?滚动方差实现方法问询
滚动计算时间序列波动率(方差)实现方案
现有计算逻辑
你当前用于计算fv和指数移动平均ma的代码如下:
// 计算当前fv值 fv = exactMath.add(exactMath.mul(busdVolume, fv_busd), exactMath.div(exactMath.mul(fv_usdt, usdtVolume), busd_price)); // 存储fv到时间序列数组 fvs[n] = fv; // 计算指数移动平均ma if(n == 0){ ma[n] = fvs[n]; }else if(n > 0){ ma[n] = exactMath.add(exactMath.mul(0.1, fv), exactMath.mul(0.9, ma[n-1])); }
需求说明
你需要实现滚动窗口式的波动率(方差)计算:
- 当索引
n=200时,用前200个fvs值(索引0~199)计算方差 - 当索引
n=400时,用索引200~399的200个fvs值计算方差 - 本质是固定窗口大小(比如200),每新增一个数据就滑动窗口,丢弃最旧的一个值,用最新的窗口数据计算方差
滚动方差实现方案
直接遍历窗口内所有元素计算会浪费算力(尤其是链上环境),推荐用递推方式维护窗口的总和、平方和,从而实现O(1)复杂度的滚动计算。
核心思路
方差公式可以变形为:
方差 = (窗口内元素平方和 - (窗口内元素总和² / 窗口大小)) / (窗口大小 - 1)
我们只需要维护三个变量,每次新增数据时更新它们,就能快速计算当前窗口的方差:
window_sum:窗口内所有fvs值的总和window_sq_sum:窗口内所有fvs值的平方和window_size:固定的窗口长度(比如你需要的200)
代码实现
// 定义固定窗口大小,比如200 uint256 constant WINDOW_SIZE = 200; // 维护全局状态变量,用于滚动计算 uint256 window_sum; uint256 window_sq_sum; // 在每次计算fv后,执行滚动方差计算 function calculateRollingVariance(uint256 n, uint256 fv) returns (uint256 var_fv) { // 1. 更新窗口的总和与平方和 if (n < WINDOW_SIZE) { // 窗口还没填满,直接累加当前值 window_sum = exactMath.add(window_sum, fv); window_sq_sum = exactMath.add(window_sq_sum, exactMath.mul(fv, fv)); } else { // 窗口已满,移除最旧的元素(n-WINDOW_SIZE位置的值),加入当前值 uint256 oldest_fv = fvs[n - WINDOW_SIZE]; window_sum = exactMath.sub(window_sum, oldest_fv); window_sum = exactMath.add(window_sum, fv); window_sq_sum = exactMath.sub(window_sq_sum, exactMath.mul(oldest_fv, oldest_fv)); window_sq_sum = exactMath.add(window_sq_sum, exactMath.mul(fv, fv)); } // 2. 计算当前窗口的方差 uint256 window_count = n < WINDOW_SIZE ? n + 1 : WINDOW_SIZE; if (window_count < 2) { // 元素不足2个时,方差无意义,返回0或自定义值 return 0; } // 计算(总和²)/窗口大小 uint256 sum_squared = exactMath.mul(window_sum, window_sum); uint256 avg_sq_term = exactMath.div(sum_squared, window_count); // 分子:平方和 - (总和²/窗口大小) uint256 numerator = exactMath.sub(window_sq_sum, avg_sq_term); // 方差 = 分子 / (窗口大小-1) var_fv = exactMath.div(numerator, exactMath.sub(window_count, 1)); // 存储当前方差到数组 var_fvs[n] = var_fv; return var_fv; }
说明
- 这个方案不需要每次遍历窗口内的所有元素,每次更新只做几次加减乘除,计算效率极高
- 如果你的窗口大小不是固定值(比如随n变化),只需要调整
WINDOW_SIZE的逻辑,比如当n=200时窗口200,n=400时窗口200,本质还是固定窗口滑动 - 注意
exactMath的精度问题,确保除法操作不会丢失精度(如果是整数运算,可能需要用乘法逆元或者定点数处理)
内容的提问来源于stack exchange,提问作者Alexey
相关产品推荐
相关产品推荐

