如何计算postgres的指数移动平均线?

Posted

技术标签:

【中文标题】如何计算postgres的指数移动平均线?【英文标题】:How to calculate an exponential moving average on postgres? 【发布时间】:2012-02-10 21:18:00 【问题描述】:

我正在尝试在 postgres 上实现指数移动平均线 (EMA),但是当我检查文档并考虑它时,我尝试得越多,我就越困惑。

EMA(x) 的公式为:

EMA(x1) = x1
EMA(xn) = α * xn + (1 - α) * EMA(xn-1)

这对于聚合器来说似乎是完美的,保留最后一个计算元素的结果正是这里必须要做的。然而,聚合器产生一个单一的结果(作为 reduce 或 fold),这里我们需要一个结果列表(一列)(作为 map)。我一直在检查程序和函数是如何工作的,但是 AFAIK 它们产生一个单一的输出,而不是一列。我见过很多过程和函数,但我无法真正弄清楚它是如何与关系代数相互作用的,尤其是在做这样的事情时,EMA。

到目前为止,我没有运气搜索互联网。但是 EMA 的定义非常简单,我希望可以将这个定义翻译成在 postgres 中工作的东西,并且简单高效,因为在我的上下文中迁移到 NoSQL 将是过度的。

谢谢。

PD:这里有一个例子:https://docs.google.com/spreadsheet/ccc?key=0AvfclSzBscS6dDJCNWlrT3NYdDJxbkh3cGJ2S2V0cVE

【问题讨论】:

发布示例表源预期结果。这会有所帮助。 【参考方案1】:

您可以定义自己的聚合函数,然后将其与窗口规范一起使用,以获取每个阶段的聚合输出,而不是单个值。

因此,聚合是一个状态,一个用于修改每一行的状态的转换函数,以及一个可选的用于将状态转换为输出值的终结函数。对于这样一个简单的情况,只需要一个变换函数就足够了。

create function ema_func(numeric, numeric) returns numeric
  language plpgsql as $$
declare
  alpha numeric := 0.5;
begin
  -- uncomment the following line to see what the parameters mean
  -- raise info 'ema_func: % %', $1, $2;
  return case
              when $1 is null then $2
              else alpha * $2 + (1 - alpha) * $1
         end;
end
$$;
create aggregate ema(basetype = numeric, sfunc = ema_func, stype = numeric);

这给了我:

steve@steve@[local] =# select x, ema(x, 0.1) over(w), ema(x, 0.2) over(w) from data window w as (order by n asc) limit 5;
     x     |      ema      |      ema      
-----------+---------------+---------------
 44.988564 |     44.988564 |     44.988564
   39.5634 |    44.4460476 |    43.9035312
 38.605724 |   43.86201524 |   42.84396976
 38.209646 |  43.296778316 |  41.917105008
 44.541264 | 43.4212268844 | 42.4419368064

这些数字似乎与您添加到问题中的电子表格相符。

另外,您可以定义函数以将 alpha 作为参数从语句中传递:

create or replace function ema_func(state numeric, inval numeric, alpha numeric)
  returns numeric
  language plpgsql as $$
begin
  return case
         when state is null then inval
         else alpha * inval + (1-alpha) * state
         end;
end
$$;

create aggregate ema(numeric, numeric) (sfunc = ema_func, stype = numeric);

select x, ema(x, 0.5 /* alpha */) over (order by n asc) from data

另外,这个函数实际上非常简单,根本不需要在 plpgsql 中,而可以只是一个 sql 函数,虽然你不能在其中一个中按名称引用参数:

create or replace function ema_func(state numeric, inval numeric, alpha numeric)
  returns numeric
  language sql as $$
select case
       when $1 is null then $2
       else $3 * $2 + (1-$3) * $1
       end
$$;

【讨论】:

+1 我有类似的想法,虽然没有那么详细。 这是在计算为输入数据的每个子列表在每一行中生成结果的聚合吗?因为看起来它正在使用聚合器直到第 n 行,返回结果然后转到第 0 行再次计算到第 n+1 行的聚合。有没有办法使用累积或一些静态变量(如在 C 中),以便必须计算一次?谢谢。 不,是使用累计值。如果您在未注释“raise info”命令的情况下运行查询,您将能够看到该函数仅针对每一行输出调用一次。 Postgresql 输出每一行的状态值(如果定义了 finalfunc,将调用它来将状态转换为输出值)。 我刚刚检查了所需的时间,对于示例数据集,它比其他选项花费的时间少 5 倍。我在更复杂的情况下会出现更多问题,但我认为它们值得一个新话题。谢谢。 创建聚合似乎也有一个使用 msvfunc 移动聚合的选项,msffunc 可能想要修改你的答案以包括那些以防 OP 使用移动窗口【参考方案2】:

这种类型的查询可以通过递归 CTE 来解决 - 尝试:

with recursive cte as (
select n, x ema from my_table where n = 1
union all
select m.n, alpha * m.x + (1 - alpha) * cte.ema
from cte
join my_table m on cte.n = m.n - 1
cross join (select ? alpha) a)
select * from cte;

【讨论】:

我冒昧地应用了一些小修复。删除了前导 ; - 这是 tSQL 中必需的,但在 PostgreSQL 中不需要。格式化代码。改进了 JOIN 条件。从 CTE 中增加一个值比从表中减少所有值要快。 (我实际上在 pg 9.0 上运行了一个快速测试来验证。) 任何基于交叉连接的解决方案对于大于小的数据集来说都太慢了。改用窗口函数。 @PavelStehule:交叉连接的表中只有一个(虚拟)记录 - 它的唯一目的是接受 alpha 作为参数。 @ErwinBrandstetter:我已经恢复了大部分更改 - 对于查询的第一(锚)部分的格式,EMA(x_1)可以用单行清楚地表示 - 这个corespnds 到在问题中定义它的单行。对于查询的递归部分,我在连接条件中使用了m.n - 1 来指示与问题中EMA(x_n-1) 的关系等价,即使这会降低性能;如果性能有问题,OP 可以按照您的建议更改连接条件。 如果我没记错的话,假设 n 是一个索引(因此 n-1)是有效的,因为如果它是一个日期或者我们有示例中的有序表,这将不起作用,会它?我还没有完全理解这一点。【参考方案3】:
--$1 Stock code
--$2 exponential;
create or replace function fn_ema(text,numeric)
    returns numeric as
    $body$
    declare
        alpha numeric := 0.5;
        var_r record;
        result numeric:=0;
        n int;
        p1 numeric;
    begin
        alpha=2/(1+$2);
        n=0;
        for var_r in(select *
        from stock_old_invest
        where code=$1  order by stock_time desc)
        loop
            if n>0 then
                result=result+(1-alpha)^n*var_r.price_now;
            else 
                p1=var_r.price_now;
            end if;
            n=n+1;
        end loop;
        result=alpha*(result+p1);
        return result;
    end
    $body$
    language plpgsql volatile
    cost 100;
    alter function fn_ema(text,numeric)
    owner to postgres;

【讨论】:

以上是关于如何计算postgres的指数移动平均线?的主要内容,如果未能解决你的问题,请参考以下文章

PySpark:计算指数移动平均线

简单移动平均线加权移动平均线指数平滑移动平均

Postgres 移动平均线

Apache Spark:指数移动平均线

火花指数移动平均线

在不同时间采样的指数移动平均线