You can actually vectorize it. Here is a vectorized version using torch, you can swap torch for numpy anytime, just replace "torch" with "np":
Let us consider some time indexed values:
values = [22, 23, 21, 22, 24, 25, 26, 27, 28, 29, 30]
dates = ['2024-01-01', '2024-01-03', '2024-01-06', '2024-01-08', '2024-01-11',
'2024-01-15', '2024-01-17', '2024-01-20', '2024-01-25', '2024-01-30', '2024-02-04']
We build our tensors and convert the dates to millisecond timestamps:
import torch
import pandas as pd
t_values = torch.tensor(values, dtype=torch.float64)
t_dates = torch.tensor(pd.to_datetime(dates).astype(int) / 10**6 , dtype=torch.int64)
Define a time window to build the decay factor:
time_widow = 1000*3600*24*10 # 10 days
alpha = 1-torch.exp(-ts_values.diff()/time_widow)
alpha = torch.cat([torch.ones(1), alpha])
beta = 1-alpha
Note that I am also considering a more generic framework where the decay factor is no longer a constant but a time series itself, depending on the timestamps series. The result for the EMA of the last date is given by:
# flip then cumprod then reflip to put in the right order
beta_cumprod=beta.flip(0).cumprod(0).flip(0)
beta_cumprod=torch.cat([beta_cumprod[1:],torch.ones(1)])
result=(t_values*beta_cumprod*alpha).cumsum(0)[-1]
the result here is:
tensor(28.2698, dtype=torch.float64)
You can go even beyond in the vectorization where you can compute the EMA for each date.
This involves some matrix operations and rotations in space. Everything boils down to the construction of the triangular beta matrix, which we will denote as "beta_m". alpha and t_values remain the same.
Here is how you can do it:
numel = t_values.numel()
lower_ones = torch.tril(torch.ones((numel,numel)))
beta_m=(torch.tile(beta, (numel,1)))
beta_m=(beta_m*lower_ones).flip(1)
Here we are going to fill all the 0 values in beta_m to be able to compute a cumprod. Values affected by this change are above the diagonal, so it does not really matter (apart for the cumprod) since we operate under the diagonal when multiplicating by "lower_ones"
beta_m[beta_m==0]=1
beta_m=beta_m.cumprod(1).flip(1) # reflip
The flips induced an asymmetry that needs to be corrected by an inverse rotation on the second dimension. We leverage torch/np.roll for that:
beta_m=torch.roll(beta_m, shifts=-1, dims=1)
beta_m.fill_diagonal_(1)
result=(torch.tile(t_values*alpha, (numel,1))*lower_ones*beta_m).cumsum(1)[:,-1]
Which gives:
tensor([22.0000, 22.1813, 21.8751, 21.8977, 22.4426, 23.2857, 23.7777, 24.6129,
25.9456, 27.1474, 28.2698], dtype=torch.float64)
If you have a lot of values, I advise you to run this on a GPU device, just insert "device=cuda" every time you build a tensor.
ewm
function in pandas? pandas.pydata.org/pandas-docs/stable/reference/api/… – Matsuyamaalpha
is not the same across all rows. – Mendydecay
s as a list... – Hyperploiddt
value on Monday morning from the preceding Friday. – Matsuyama