In [1]:
import sys
import datetime
print('Python:', sys.version)
import pandas as pd
print('Pandas:', pd.__version__)
import pandas_datareader as pdr
print('Pandas datareader:', pdr.__version__)


Python: 3.7.2 (default, Feb 11 2019, 14:11:50) [MSC v.1915 64 bit (AMD64)]
Pandas: 0.24.1
Pandas datareader: 0.7.0

Exponential Weighted Moving Average (EWMA)

Pro jednoduchost výpočtu patří exponenciální vážený klouzavý průměr (EWMA) k hojně využívaným nástrojům pro analýzu dat, machine learning, atd. Narozdíl od jiných typů klouzavých průměrů (Simple, Exponential Moving Average, atd.) s větší periodou než 2, nepotřebuje EWMA k výpočtu aktuální hodnoty znát historii předchozích hodnot. Stačí k tomu pouze fixní váha w (ta určuje jak velká historie se bere v potaz), hodnota aktuálního prvku a předchozí vypočítaná hodnota EWMA.

Obecný výpočet EWMA

Hodnota EWMA pro každý prvek EWMA se vypočítá podle vzorce:

$$ y_t = wy_{t-1} + (1-w)x_t $$

$y_t$ je výsledek EWMA
$w$ je definovaná váha, kde $w \in (0,1)$
$y_{t-1}$ je přechozí výsledek EWMA, pokud jde o první prvek, obvykle se inicializuje hodnotou 0, nebo aktuální měřenou hodnotou
$x_t$ je hodnota aktuálního prvku

Vztah váhy a historie, která má vliv na aktuální výsledek

Po matematickém odvození můžeme zjistit, kolik hodnot zpětně má vliv na aktuální výsledek. Vzorec pro výpočet periody $p \approx \frac{1}{1-w}$, který uvádí Andrew Ng ve výukovém video na youtube, bohužel obsahuje nepřesnost, správně by mělo být:

$$ p \approx \frac{2}{1-w} $$

$w$ je definovaná váha, kde $w \in (0,1)$
$p$ je perioda, kde $p \geq 1$

Perioda nám říká, že další prvky v hlouběji v historii mají zanedbatelný vliv na výsledek EWMA pro aktuální prvek. Jednoduše z předchozího vzorce lze odvodit jakou váhu w mám zvolit:

$$ w \approx 1 - \frac{2}{p} $$

$w$ je definovaná váha, kde $w \in (0,1)$
$p$ je perioda, kde $p \geq 1$

Příklad EWMA a cenový graf

Nejprve ale získám data:


In [2]:
import datetime
start = datetime.datetime(2018, 1, 1)
end = datetime.datetime(2019, 1, 1)

spy_data = pdr.data.DataReader('SPY', 'yahoo', start, end)
spy_data.drop(['High', 'Low', 'Open', 'Close', 'Volume'], axis=1, inplace=True) # these columns are not needed
spy_data.head(5)


Out[2]:
Adj Close
Date
2018-01-02 263.759949
2018-01-03 265.428253
2018-01-04 266.546997
2018-01-05 268.323273
2018-01-08 268.813934

EWMA za posledních 20 prvků

Pokud chci vypočítat EWMA s vlivem posledních 20 prvků na výsledek, váhu vypočítám podle výše zmíněného vzorce:


In [3]:
p = 20
w = 1-(2/p)
w


Out[3]:
0.9

EWMA na cenovém grafu se obvykle počítá z Close/Last ceny, vzorec bude vypadat takto:

$$ y_t = wy_{y-1} + (1-w)c_t $$

$y_t$ je hodnota EWMA pro aktuální cenu
$w$ je definovaná váha, kde $w \in (0,1)$
$y_{t-1}$ je přechozí výsledek EWMA, pokud jde o první hodnotu, obvykle se inicializuje hodnotou 0, nebo aktuální měřenou hodnotou
$c_t$ je aktuální hodnota Close ceny

Následující kód slouží pouze pro demonstrativní účely použití vzorce.


In [4]:
spy_data['EWMA_mannualy'] = spy_data['Adj Close']
for i in range(spy_data.shape[0]):
    if i==0:
        spy_data['EWMA_mannualy'][i] = spy_data['Adj Close'][i]
        continue
    spy_data['EWMA_mannualy'][i] = w*spy_data['EWMA_mannualy'][i-1] + (1-w)*spy_data['Adj Close'][i]

spy_data.head()


Out[4]:
Adj Close EWMA_mannualy
Date
2018-01-02 263.759949 263.759949
2018-01-03 265.428253 263.926779
2018-01-04 266.546997 264.188801
2018-01-05 268.323273 264.602248
2018-01-08 268.813934 265.023417

Pandas využívá tzv. smoothing factor namísto váhy

Jednoduché a optimalizované řešení lze naleznout v knihovně Pandas po použití exponenciálně vážené rolling funkce df.ewm(). Tahle funkce obsahuje parametr alpha, který reprezentuje tzv. smoothing factor. Pandas namísto váhy používá tento smoothing factor k výpočtu takto (zdroj: dokumentace k pandas):

$$ y_t = (1-\alpha)y_{t-1} + \alpha C_t $$

$y_t$ je hodnota EWMA pro aktuální cenu
$\alpha$ smoothing factor, kde $\alpha \in (0,1)$
$y_{t-1}$ je přechozí výsledek EWMA, pokud jde o první hodnotu, obvykle se inicializuje hodnotou 0, nebo aktuální měřenou hodnotou
$C_t$ je aktuální hodnota Close ceny

Po odvození lze jednoduše vypočítat smoothing factor z váhy:

$$ \alpha = 1-w $$

$w$ je váha, kde $w \in (0,1)$
$\alpha$ smoothing factor, kde $\alpha \in (0,1)$

Nebo smoothing factor z periody:

$$ \alpha \approx \frac{2}{p} $$

$p$ je perioda, kde $p \geq 1$
$\alpha$ smoothing factor, kde $\alpha \in (0,1)$

Pozn.: zde se neshodnu s pandas, který periodu označuje jako span parametr a v dokumentaci figuruje vzorec $\alpha = \frac{2}{p+1}$. To by mi pak ale nekorespondovalo s Andrew Ng.


In [5]:
a = 1-w
a


Out[5]:
0.09999999999999998

Případná nepřesnost může být způsobena přesností čísel s desetinnou čárkou - více zde: https://en.wikipedia.org/wiki/Floating-point_arithmetic#Accuracy_problems


In [6]:
spy_data['EWMA'] = spy_data['Adj Close'].ewm(alpha=0.1, adjust=False).mean()
spy_data['EWMA_period'] = spy_data['Adj Close'].ewm(span=p, adjust=False).mean()
spy_data.head()


Out[6]:
Adj Close EWMA_mannualy EWMA EWMA_period
Date
2018-01-02 263.759949 263.759949 263.759949 263.759949
2018-01-03 265.428253 263.926779 263.926779 263.918835
2018-01-04 266.546997 264.188801 264.188801 264.169136
2018-01-05 268.323273 264.602248 264.602248 264.564768
2018-01-08 268.813934 265.023417 265.023417 264.969451

Pro názornost rozdílu mezi jednoduchým klouzavým průměrem (Simple Moving Average) a EWMA uvedu příklad:


In [7]:
spy_data['SMA'] = spy_data['Adj Close'].rolling(p).mean()

A nakonec si průmery zobrazím v grafu:


In [8]:
spy_data[['Adj Close', 'EWMA_mannualy', 'EWMA', 'EWMA_period', 'SMA']].plot(figsize=(16,10));

Je jednoznačně vidět, že všechny výpočty EWMA se téměř překrývají. Výpočty by měly být správné. SMA je zde jako příklad, že EWMA se dokáže rychleji přizpůsobovat posledním hodnotám.

Shrnutí

Klouzavé průměry se používají ke zjištění průměrné hodnoty za nejbližší poslední dobu, nebo k vyhlazení roztroušených dat do jedné průměrné hodnoty. Doba je dána periodou (např. 30 dní zpět). Exponenciálně vážené klouzavé průměry (EWMA) dávají vyšší váhu bližší historii, pro čím dál starší data klesá důležitost exponenciálně.

Rozdíl mezi SMA (jednoduchý klouzavý průměr) a EWMA pro stejnou periodu je, že EWMA se rychleji přizpůsobuje posledním změnám. Pokud je EWMA v této jednoduché formě, SMA může být výpočetně náročenější. Např. SMA s periodou 20 musí získat data 20 posledních period, ty sečíst a nakonec vydělit 20. U EWMA stačí vědět předchozí hodnota EWMA vynásobit určitou váhou a přičíst aktuální měřenou hodnotu vynásobenou o mnohem větší váhu.

EWMA lze využít i u dalších jiných odvětví, nejen ve financích. EWMA pro jednoduchost výpočtu může být použito k agregaci velkého množství dat třeba v Data miningu nebo umělé inteligenci. Díky klouzavým průměrům lze zjistit jak jsou hodnoty vzdálené od svého průměru, popřípadě jaké má průměr tendence a zjišťovat trend - klesá, stoupá.

Mně osobně se líbí jednoduchost a nenáročnost výpočtu EWMA a schopnost EWMA se rychleji adaptovat na aktuální trendy.