SQL - Рассчитать экспоненциальную скользящую среднюю с CTE или агрегатами? - PullRequest
3 голосов
/ 02 февраля 2020

Общая формула для EMA:

EMA(x<sub>n</sub>) = &alpha; * x<sub>n</sub> + (1 - &alpha;) * EMA(x<sub>n-1</sub>)

Где:

x<sub>n</sub> = PRICE
&alpha;  = 0.5 -- Given 3-day 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
;

Это довольно много вопрос эффективности и быстроты. Для набора образцов из 9852 строк требуется 11 s 631 ms.


Я читал, что агрегатор сохраняет результат последнего вычисленного элемента, если так:

  • Может ли кто-нибудь предоставить рабочий пример с использованием агрегатных функций?

Я также открыт для любых предложений по улучшению CTE, но я почему-то верю, что aggregates будет быстрее. Я также знаю, что это старый топи c, но я немного новичок в posgres, поэтому любая помощь очень ценится. Спасибо!


ОБНОВЛЕНИЕ

Пример данных:

EMA_3

Мой CTE на 7-дневный период возвращает (исключая 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

Хотя рекурсивный CTE, предоставленный @GordonLinoff, на долю секунды быстрее, агрегатор (агрегированное веселье c) будет оптимальным для скорости. Я пытался это , но получаю:

ОШИБКА: функция EMA (цифра c, цифра c) не существует

Видимо, ни одна функция не соответствует заданному имени и типу аргумента. Явный тип бросает? Бестолковые

1 Ответ

4 голосов
/ 02 февраля 2020

Я бы написал рекурсивный 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;

0.5 - это действительно 0.5 и 1 - 0.5. Вы можете легко отрегулировать это для разных альфа.

Вы также можете сделать это, используя оконные функции:

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;

first_value() из-за каприза вычисления. Первое и второе значения фактически подсчитываются в одну и ту же сумму, поэтому первую сумму нужно «добавить обратно».

Тем не менее, это может привести к ошибкам переполнения и деления на ноль, если ваши последовательности даже несколько десятков строк в длину.

Здесь - это скрипта db <>.

...