Skip to content

Commit f1e9191

Browse files
committed
Optimize EMA indicator with Numba JIT compilation
- Implement Numba-accelerated EMA calculation function - Replace vectorized implementation with loop-based Numba implementation - Improve computational efficiency for Exponential Moving Average calculation - Maintain consistent function interface and return types
1 parent 7a01f99 commit f1e9191

File tree

1 file changed

+23
-24
lines changed

1 file changed

+23
-24
lines changed

jesse/indicators/ema.py

Lines changed: 23 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,35 @@
11
from typing import Union
22

33
import numpy as np
4+
from numba import njit
45

56
from 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+
829
def 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

Comments
 (0)