Lorsque l'on tente de calculer la moyenne mobile exponentielle (EMA) à partir de données financières dans une trame de données, il semble que l'approche ewm de Pandas soit incorrecte.
Les bases sont bien expliquées dans le lien suivant: http://stockcharts.com/school/doku.php?id=chart_school:technical_indicators:moving_averages
En allant à Pandas explication, l'approche adoptée est la suivante (en utilisant le paramètre "adjust" comme False):
weighted_average[0] = arg[0];
weighted_average[i] = (1-alpha) * weighted_average[i-1] + alpha * arg[i]
À mon avis, cela est incorrect. Le "arg" doit être (par exemple) les valeurs de fermeture, cependant, arg [0] est la première moyenne (c'est-à-dire la moyenne simple de la première série de données de la longueur de la période sélectionnée), mais PAS la première valeur de fermeture . arg [0] et arg [i] ne peuvent donc jamais provenir des mêmes données. L'utilisation du paramètre "min_periods" ne semble pas résoudre ce problème.
Quelqu'un peut-il m'expliquer comment (ou si) Pandas peut être utilisé pour calculer correctement l'EMA des données?
Il existe plusieurs façons d'initialiser une moyenne mobile exponentielle, donc je ne dirais pas pandas le fait mal, juste différent.
Voici un moyen de le calculer comme vous le souhaitez:
In [20]: s.head()
Out[20]:
0 22.27
1 22.19
2 22.08
3 22.17
4 22.18
Name: Price, dtype: float64
In [21]: span = 10
In [22]: sma = s.rolling(window=span, min_periods=span).mean()[:span]
In [24]: rest = s[span:]
In [25]: pd.concat([sma, rest]).ewm(span=span, adjust=False).mean()
Out[25]:
0 NaN
1 NaN
2 NaN
3 NaN
4 NaN
5 NaN
6 NaN
7 NaN
8 NaN
9 22.221000
10 22.208091
11 22.241165
12 22.266408
13 22.328879
14 22.516356
15 22.795200
16 22.968800
17 23.125382
18 23.275312
19 23.339801
20 23.427110
21 23.507635
22 23.533520
23 23.471062
24 23.403596
25 23.390215
26 23.261085
27 23.231797
28 23.080561
29 22.915004
Name: Price, dtype: float64
Vous pouvez calculer l'EWMA en utilisant l'alpha ou le coefficient (span
) dans la fonction Pandas ewm
.
Formule d'utilisation de l'alpha: (1 - alpha) * previous_val + alpha * current_val
Où alpha = 1 / period
Formule d'utilisation de coeff: ((current_val - previous_val) * coeff) + previous_val
Où coeff = 2 / (period + 1)
Voici comment utiliser Pandas pour calculer les formules ci-dessus:
con = pd.concat([df[:period][base].rolling(window=period).mean(), df[period:][base]])
if (alpha == True):
df[target] = con.ewm(alpha=1 / period, adjust=False).mean()
else:
df[target] = con.ewm(span=period, adjust=False).mean()
Voici un exemple de la façon dont Pandas calcule les ewm ajustés et non ajustés:
name = 'closing'
series = pd.Series([1, 2, 3, 5, 8, 13, 21, 34], name=name).to_frame()
period = 4
alpha = 2/(1+period)
series[name+'_ewma'] = np.nan
series.loc[0, name+'_ewma'] = series[name].iloc[0]
series[name+'_ewma_adjust'] = np.nan
series.loc[0, name+'_ewma_adjust'] = series[name].iloc[0]
for i in range(1, len(series)):
series.loc[i, name+'_ewma'] = (1-alpha) * series.loc[i-1, name+'_ewma'] + alpha * series.loc[i, name]
ajusted_weights = np.array([(1-alpha)**(i-t) for t in range(i+1)])
series.loc[i, name+'_ewma_adjust'] = np.sum(series.iloc[0:i+1][name].values * ajusted_weights) / ajusted_weights.sum()
print(series)
print("diff adjusted=False -> ", np.sum(series[name+'_ewma'] - series[name].ewm(span=period, adjust=False).mean()))
print("diff adjusted=True -> ", np.sum(series[name+'_ewma_adjust'] - series[name].ewm(span=period, adjust=True).mean()))
La formule mathématique peut être trouvée à https://github.com/pandas-dev/pandas/issues/8861
Si vous calculez ewm de ewm (comme la formule MACD), vous obtiendrez de mauvais résultats car le deuxième ewm et les suivants utiliseront l'index commençant par 0 et se terminant par la période. J'utilise la solution suivante.
sma = df['Close'].rolling(period, min_periods=period).mean()
#this variable is used to shift index by non null start minus period
idx_start = sma.isna().sum() + 1 - period
idx_end = idx_start + period
sma = sma[idx_start: idx_end]
rest = df[item][idx_end:]
ema = pd.concat([sma, rest]).ewm(span=period, adjust=False).mean()