11from typing import Union
22
33import numpy as np
4+ from numba import njit
45
56from jesse .helpers import get_candle_source , slice_candles
67
78
9+ @njit
10+ def _ema (source : np .ndarray , period : int ) -> np .ndarray :
11+ """
12+ Compute the Exponential Moving Average using a loop.
13+ """
14+ n = len (source )
15+ result = np .full (n , np .nan )
16+ if n < period :
17+ return result
18+ alpha = 2 / (period + 1 )
19+ # Initialize EMA with the simple average of the first "period" values
20+ initial = np .mean (source [:period ])
21+ result [period - 1 ] = initial
22+ prev = initial
23+ for i in range (period , n ):
24+ current = alpha * source [i ] + (1 - alpha ) * prev
25+ result [i ] = current
26+ prev = current
27+ return result
28+
829def ema (candles : np .ndarray , period : int = 5 , source_type : str = "close" , sequential : bool = False ) -> Union [
930 float , np .ndarray ]:
1031 """
11- EMA - Exponential Moving Average
32+ EMA - Exponential Moving Average using Numba for optimization
1233
1334 :param candles: np.ndarray
1435 :param period: int - default: 5
@@ -23,27 +44,5 @@ def ema(candles: np.ndarray, period: int = 5, source_type: str = "close", sequen
2344 candles = slice_candles (candles , sequential )
2445 source = get_candle_source (candles , source_type = source_type )
2546
26- if len (source ) < period :
27- result = np .full_like (source , np .nan , dtype = float )
28- else :
29- alpha = 2 / (period + 1 )
30-
31- # Compute EMA using vectorized operations
32- f = period - 1 # the index at which EMA calculation begins
33- initial = np .mean (source [:period ])
34- L = len (source ) - f # number of points from the start of EMA calculation
35- if L > 1 :
36- # X contains the source values after the initial period
37- X = source [f + 1 :]
38- # Create a lower-triangular matrix T of shape (L, L-1) where T[m, j] = (1-alpha)**(m-1-j) for j < m, else 0
39- m_idx , j_idx = np .indices ((L , L - 1 ))
40- T = np .where (j_idx < m_idx , (1 - alpha )** (m_idx - 1 - j_idx ), 0 )
41- dot_sums = T .dot (X )
42- else :
43- dot_sums = np .zeros (L )
44- m_vec = np .arange (L )
45- y = (1 - alpha )** m_vec * initial + alpha * dot_sums
46- result = np .full_like (source , np .nan , dtype = float )
47- result [f :] = y
48-
47+ result = _ema (source , period )
4948 return result if sequential else result [- 1 ]
0 commit comments