You need to enable JavaScript to run this app. R语言中增量计算新增交易日RSI、DEMA等技术指标的实现方法
优惠活动
大模型
产品
解决方案
定价
更多

R语言中增量计算新增交易日RSI、DEMA等技术指标的实现方法

核心问题梳理

当前使用的计算代码

library(quantmod)
library(TTR)
getSymbols("AAPL")
chartSeries(AAPL, TA=NULL)
data=AAPL[,4]
AAPL$rsi = TTR::RSI(data)
AAPL$dema = TTR::DEMA(data)

# 对象B存储AAPL对象的副本并保存为CSV文件
B = AAPL 

现存痛点与诉求

  • 目前每次新增1行最新交易日的AAPL数据,都需要对12年以上的全量历史数据重跑RSI、DEMA计算,会浪费大量CPU资源与时间
  • 核心诉求1:能否仅计算最新交易日对应的RSI、DEMA值,直接追加到原有数据集B中,无需全量重算
  • 核心诉求2:高频场景下(每秒获取tick数据、需要基于新数据实时计算RSI这类指标),量化从业者是如何解决全量重算耗时长、行情滞后的问题的

具体实现方案

一、日线场景下的增量指标计算方法

RSI、DEMA都属于 有状态的递推类指标,计算最新时点的指标值根本不需要全量历史数据参与,只要保留上一步的递推状态,用单条新数据就能算出最新的指标值,完全可以避免全量重算。
每个指标需要维护的状态值非常少:

  • DEMA的递推核心是嵌套的EMA计算,只需要存最新一阶EMA的结果、二阶嵌套EMA的结果两个值,公式为DEMA = 2*EMA(n) - EMA(EMA(n))
  • 默认14周期Wilder平滑的RSI,只需要存上一个时点的平均涨幅、平均跌幅两个值,公式为RSI = 100 - 100/(1 + 平均涨幅/平均跌幅),Wilder平滑的递推规则为「新的平滑值 = (上期平滑值*(周期数-1) + 当期值)/周期数」

你可以按照下面的逻辑改造代码,实现零全量重算的增量更新:

# 第一次全量初始化,仅跑一次,把递推状态持久化到本地
init_metric_state <- function(price_vec, rsi_period = 14, dema_period = 20) {
  # 计算RSI需要的初始状态
  daily_diff <- diff(price_vec)
  up_vec <- ifelse(daily_diff > 0, daily_diff, 0)
  down_vec <- ifelse(daily_diff < 0, -daily_diff, 0)
  # 取Wilder平滑最后一步的平均涨、跌幅作为初始状态
  initial_gain <- SMA(up_vec, rsi_period)[length(up_vec)]
  initial_loss <- SMA(down_vec, rsi_period)[length(down_vec)]
  
  # 计算DEMA需要的初始状态
  ema_alpha <- 2/(dema_period + 1)
  initial_ema1 <- EMA(price_vec, dema_period)[length(price_vec)]
  initial_ema2 <- EMA(EMA(price_vec, dema_period), dema_period)[length(price_vec)]
  
  # 把所有需要的状态存成本地rds文件,之后直接读取即可
  state <- list(
    last_price = tail(price_vec, 1),
    rsi_period = rsi_period,
    rsi_gain = initial_gain,
    rsi_loss = initial_loss,
    dema_period = dema_period,
    ema_alpha = ema_alpha,
    ema1 = initial_ema1,
    ema2 = initial_ema2
  )
  saveRDS(state, "metric_state.rds")
  return(state)
}

# 增量更新函数:每次新数据来了直接调用,不需要碰历史数据
update_latest_metric <- function(new_price) {
  # 读取之前存好的状态
  state <- readRDS("metric_state.rds")
  
  # 计算最新RSI
  price_change <- new_price - state$last_price
  curr_gain <- max(price_change, 0)
  curr_loss <- max(-price_change, 0)
  # 递推更新Wilder平滑值
  new_gain <- (state$rsi_gain * (state$rsi_period - 1) + curr_gain) / state$rsi_period
  new_loss <- (state$rsi_loss * (state$rsi_period - 1) + curr_loss) / state$rsi_period
  new_rsi <- 100 - 100/(1 + new_gain/new_loss)
  
  # 计算最新DEMA
  new_ema1 <- state$ema_alpha * new_price + (1 - state$ema_alpha) * state$ema1
  new_ema2 <- state$ema_alpha * new_ema1 + (1 - state$ema_alpha) * state$ema2
  new_dema <- 2 * new_ema1 - new_ema2
  
  # 更新状态文件
  new_state <- list(
    last_price = new_price,
    rsi_period = state$rsi_period,
    rsi_gain = new_gain,
    rsi_loss = new_loss,
    dema_period = state$dema_period,
    ema_alpha = state$ema_alpha,
    ema1 = new_ema1,
    ema2 = new_ema2
  )
  saveRDS(new_state, "metric_state.rds")
  
  # 返回最新的两个指标值,直接rbind追加到数据集B即可
  return(list(rsi = new_rsi, dema = new_dema))
}

整个增量计算过程的耗时是微秒级的,完全不需要读取12年的历史数据,也不会浪费CPU资源。

二、高频tick场景下的实时指标计算方案

量化行业里处理每秒甚至更高频率的tick数据、实时计算RSI这类指标,从来不会采用全量重算的方式,通用的实现逻辑非常简单:

  • 核心原则:状态全量常驻内存。程序启动时,先一次性加载全量历史数据,跑完所有指标的递推流程,把每个指标计算需要的所有中间状态(比如RSI的平均涨/跌幅、EMA的平滑值、固定窗口的价格缓存)全部存在内存里,不涉及磁盘读写。
  • 单步递推计算:每收到一个新的tick数据,直接在内存里读取对应指标的状态值,用递推公式算出最新的指标值,再更新内存里的状态值。整个过程的计算复杂度是O(1),和历史数据的总长度完全无关,单条tick的计算耗时基本在微秒级,完全赶得上行情推送的速度,根本不会出现行情已经变动完才算出指标的情况。
  • 环形缓冲区适配窗口类指标:针对需要固定窗口计算的指标(比如20周期最高价、简单移动平均),会用环形缓冲区存储最近N个周期的有效数据,新数据到来时直接覆盖缓冲区里最老的记录,计算时直接读取缓冲区的N个值即可,不会加载窗口范围外的历史数据。

很多新手的误区是:必须把全量时间序列传给TTR、pandas-ta这类指标库才能算出结果。实际上这类库的全量计算接口,本质也是内部从时间序列起点开始一路递推,把每一步的计算结果全部返回出来而已,生产环境里根本不会调用这种全量返回的接口,都是调用库内部的单步递推能力,自己维护状态。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.11.01 22:36:05