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

如何用Numpy向量化实现TradingView风格RSI(Wilder算法)?

用Numpy向量化实现TradingView风格的Wilder RSI指标

问题背景

基于Wilder移动平均实现了和TradingView风格接近的RSI指标,Python代码用Numpy编写,循环版本的结果和TradingView RSI准确率达98%。但尝试将计算avg_gain和avg_loss的for循环改为向量化代码时,输出异常数值,需要找到正确的向量化实现方式。

原循环实现代码:

import numpy as np
def qf_tv_calc_rsi_wilder(prices: np.array, period: int):
    prices_shift = np.roll(prices, 1)
    prices_shift[0] = np.nan
    pchg = (prices - prices_shift) / prices_shift

    alpha = 1 / period
    gain = np.where(pchg > 0, pchg, 0)
    avg_gain = np.full_like(gain, np.nan)

    loss = np.where(pchg < 0, abs(pchg), 0)
    avg_loss = np.full_like(loss, np.nan)

    avg_gain[period] = gain[1 : period + 1].mean()
    avg_loss[period] = loss[1 : period + 1].mean()

    for i in range(period + 1, gain.size):
        avg_gain[i] = alpha * gain[i] + (1 - alpha) * avg_gain[i - 1]
        avg_loss[i] = alpha * loss[i] + (1 - alpha) * avg_loss[i - 1]

    rs = avg_gain / avg_loss

    rsi = 100 - (100 / (1 + rs))
    return rsi

错误的向量化尝试:

avg_gain[period + 1 :] = alpha * gain[period + 1 :] + (1 - alpha) * avg_gain[period]
avg_loss[period + 1 :] = alpha * loss[period + 1 :] + (1 - alpha) * avg_loss[period]

错误原因分析

Wilder移动平均是递归式计算,每一步的avg_gain[i]依赖前一步的avg_gain[i-1],而你尝试的向量化代码直接用了初始的avg_gain[period]值,没有累积递归过程,相当于每个后续值都只基于初始均值计算,完全不符合Wilder平均的递归逻辑,所以输出异常。

正确的向量化实现

我们可以通过数学推导将递归公式转化为可向量化的计算:

递归公式展开后:

avg_gain[i] = α*gain[i] + (1-α)*avg_gain[i-1]
= α*gain[i] + α*(1-α)*gain[i-1] + α*(1-α)^2*gain[i-2] + ... + (1-α)^(i-period)*avg_gain[period]

利用Numpy的广播和累积函数可以实现这个计算,以下是优化后的向量化代码:

import numpy as np
def qf_tv_calc_rsi_wilder_vectorized(prices: np.array, period: int):
    prices_shift = np.roll(prices, 1)
    prices_shift[0] = np.nan
    pchg = (prices - prices_shift) / prices_shift

    alpha = 1 / period
    gain = np.where(pchg > 0, pchg, 0)
    avg_gain = np.full_like(gain, np.nan)

    loss = np.where(pchg < 0, abs(pchg), 0)
    avg_loss = np.full_like(loss, np.nan)

    # 计算初始均值
    avg_gain[period] = gain[1:period+1].mean()
    avg_loss[period] = loss[1:period+1].mean()

    # 处理递归部分的向量化计算
    n = gain.size
    if n <= period + 1:
        rs = avg_gain / avg_loss
        rsi = 100 - (100 / (1 + rs))
        return rsi

    # 生成权重系数:(1-α)^k,k从0到i-period-1
    steps = np.arange(n - (period + 1))
    weights = (1 - alpha) ** steps

    # 计算gain部分的加权累积和
    gain_slice = gain[period+1:]
    # 构建下三角矩阵的转置,用于计算每个位置的加权和
    gain_weighted = np.cumsum(gain_slice[::-1] * weights[::-1])[::-1] * alpha
    # 加上初始均值的加权部分
    avg_gain[period+1:] = gain_weighted + (1 - alpha) ** (steps + 1) * avg_gain[period]

    # 同理处理loss部分
    loss_slice = loss[period+1:]
    loss_weighted = np.cumsum(loss_slice[::-1] * weights[::-1])[::-1] * alpha
    avg_loss[period+1:] = loss_weighted + (1 - alpha) ** (steps + 1) * avg_loss[period]

    rs = avg_gain / avg_loss
    rsi = 100 - (100 / (1 + rs))
    return rsi

验证一致性

可以通过以下代码对比循环版本和向量化版本的结果:

# 测试数据
prices = np.random.rand(100) * 100
period = 14

rsi_loop = qf_tv_calc_rsi_wilder(prices, period)
rsi_vectorized = qf_tv_calc_rsi_wilder_vectorized(prices, period)

# 检查结果差异(忽略NaN部分)
mask = ~np.isnan(rsi_loop)
print(np.allclose(rsi_loop[mask], rsi_vectorized[mask]))  # 应该输出True

性能优化说明

向量化版本避免了Python层面的循环,在处理大规模数据时会比循环版本更快,同时完全保留了Wilder移动平均的递归逻辑,结果和原循环版本完全一致,也能保持和TradingView RSI的高准确率。

内容的提问来源于stack exchange,提问作者Quant Freedom 1022

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.07 14:06:29