Как рассчитать экспоненциальную скользящую среднюю по postgres?

Я пытаюсь реализовать экспоненциальную скользящую среднюю (EMA) в postgres, но когда я проверяю документацию и думаю об этом, тем больше я стараюсь больше смутить.

Формула для EMA(x):

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

Кажется, что он идеально подходит для агрегатора, поэтому результат последнего расчетного элемента - именно то, что нужно сделать здесь. Однако агрегатор производит один единственный результат (как сокращение или сгиб), и здесь нам нужен список (столбец) результатов (как карта). Я проверяю, как работают процедуры и функции, но 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);

который дает мне:

[email protected]@[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

Эти цифры, похоже, совпадают с таблицей, которую вы добавили в вопрос.

Кроме того, вы можете определить функцию для передачи альфы в качестве параметра из инструкции:

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
$$;

Ответ 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;

Ответ 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;