SQL - 使用 CTE 或聚合计算指数移动平均值?

Ava*_*lla 4 sql postgresql recursion weighted-average

EMA的一般公式:

EMA(x n ) = α * x n + (1 - α) * EMA(x n-1 )

在哪里:

x n = 价格
α = 0.5 -- 给定 3 天 SMA

下面的递归 CTE 可以完成这项工作:

WITH recursive
ewma_3 (DATE, PRICE, EMA_3, rn)
AS (

    -- Anchor
    -- Feed SMA_3 to recursive CTE
    SELECT rows."DATE", rows."PRICE", sma.sma AS ewma, rows.rn
    FROM (
        SELECT "DATE", "PRICE", ROW_NUMBER() OVER(ORDER BY "DATE") rn
        FROM PRICE_TBL
    ) rows
    JOIN (
        SELECT "DATE",
           ROUND(AVG("PRICE"::numeric)
              OVER(ORDER BY "DATE" ROWS BETWEEN 2 PRECEDING AND CURRENT ROW), 6) AS sma
        FROM PRICE_TBL
    ) sma ON sma."DATE" = rows."DATE"
    WHERE rows.rn = 3

    UNION ALL

    -- Recursive Member
    SELECT rows."DATE", rows."PRICE"
    -- Calculate EMA_3 below
    ,ROUND(((rows."PRICE"::numeric * 0.5) +
            (ewma.EMA_3 * (1 - 0.5))), 6) AS ewma
    , rows.rn
    FROM ewma_3 ewma
    JOIN (
        SELECT "DATE", "PRICE", ROW_NUMBER() OVER(ORDER BY "DATE") rn
        FROM PRICE_TBL
    ) rows ON ewma.rn + 1 = rows.rn
    WHERE rows.rn > 3
)

SELECT ewma_3.rn AS "ID", DATE, PRICE, EMA_3
FROM ewma_3
;
Run Code Online (Sandbox Code Playgroud)

这更多的是效率和速度的问题。11 s 631 ms完成一组包含 9852 行的示例。


我读到聚合器保留最后计算元素的结果,如果是这样:

  • 有人可以提供使用聚合函数的工作示例吗?

我也愿意接受任何改进 CTE 的建议,但是,我相信aggregates会更快。我也知道这是一个较旧的主题,但我是个新手,posgres因此非常感谢任何帮助。谢谢!


更新

样本数据:

EMA_3

我的 7 天期间 CTE 回报(不含DATE):

ID  PRICE       EMA_7
--+----------+-----------
7   0.529018    0.4888393
8   0.551339    0.5044642
9   0.580357    0.5234374
10  0.633929    0.5510603
11  0.642857    0.5740095
12  0.627232    0.5873151
Run Code Online (Sandbox Code Playgroud)

尽管 @GordonLinoff 提供的递归 CTE 快了一瞬间,但聚合器(聚合函数)对于速度来说是最佳的。我尝试过这个但得到:

错误:函数 ema(numeric, numeric) 不存在

显然,没有函数与给定的名称和参数类型匹配。显式类型转换?无能

Gor*_*off 5

我会将递归 CTE 写为:

with recursive p as (
      select p.*, row_number() over (order by date) as seqnum
      from price_tbl p
     ),
     cte as (
      select seqnum, date, price, price * 1.0 as exp_avg
      from p
      where seqnum = 1
      union all
      select p.seqnum, p.date, p.price, (cte.exp_avg * 0.5  + p.price * 0.5) 
      from cte join
           p
           on p.seqnum = cte.seqnum + 1
     )
select *
from cte;
Run Code Online (Sandbox Code Playgroud)

确实0.50.51 - 0.5。您可以轻松地针对不同的 alpha 进行调整。

您还可以使用窗口函数来执行此操作:

select p.*,
       (sum(power((1 / 0.5), seqnum) * price) over (order by seqnum) +
        first_value(price) over (order by seqnum)
       ) / power((1 / 0.5), seqnum + 1)
from (select p.*,
             row_number() over (order by date) - 1 as seqnum
      from price_tbl p
     ) p;
Run Code Online (Sandbox Code Playgroud)

first_value()是因为计算的一个怪癖。第一个和第二个值实际上计算相同的数量,因此第一个数量需要“加回”。

也就是说,如果您的序列甚至有几十行长,则很容易出现溢出和除零错误。

是一个 db<>fiddle。