使用Pandas.DataFrame的自定义函数加权移动平均值,由于某种原因,该值在26次迭代后下降到0.0

bwntbbo3  于 2022-12-28  发布在  其他
关注(0)|答案(2)|浏览(111)

我正在测试我的函数,计算价格指标,我有一个奇怪的错误,我不知道如何解决。
编辑:列中的csv我已经分享都是小写,在测试的情况下,这个csv的功能,你想使用这个代码:

data = pd.read_csv(csv_path)
    data = data.drop(['symbol'], axis=1)
    data.rename(columns={'open': 'Open', 'high': 'High', 'low': 'Low', 'close': 'Close', 'volume': 'Volume'}, inplace=True)

Link to data .csv file您可以尝试使用默认参数的函数。(在帖子的底部,我还分享了一个input_type辅助函数,只是要确保不要使用高于4的输入模式,因为HL2HLC3OHLC4HLCC4输入模式不计算此csv。
所以我用这个函数来计算Weighted Moving Average
(我正在使用默认参数测试此函数)

def wma(price_df: PandasDataFrame, n: int = 14, input_mode: int = 2, from_price: bool = True, *,
        indicator_name: str = 'None') -> PandasDataFrame:
    if from_price:
        name_var, state = input_type(__input_mode__=input_mode)
    else:
        if indicator_name == 'None':
            raise TypeError('Invalid input argument. indicator_name cannot be set to None if from_price is False.')
        else:
            name_var = indicator_name

    wma_n = pd.DataFrame(index=range(price_df.shape[0]), columns=range(1))
    wma_n.rename(columns={0: f'WMA{n}'}, inplace=True)
    weight = np.arange(1, (n + 1)).astype('float64')
    weight = weight * n
    norm = sum(weight)
    weight_df = pd.DataFrame(weight)
    weight_df.rename(columns={0: 'weight'}, inplace=True)
    product = pd.DataFrame()
    product_sum = 0
    for i in range(price_df.shape[0]):
        if i < (n - 1):
            # creating NaN values where it is impossible to calculate EMA to drop the later
            wma_n[f'WMA{n}'].iloc[i] = np.nan
        elif i == (n - 1):
            product = price_df[f'{name_var}'].iloc[:(i + 1)] * weight_df['weight']
            product_sum = product.sum()
            wma_n[f'WMA{n}'].iloc[i] = product_sum / norm
            print(f'index: {i}, wma: ', wma_n[f'WMA{n}'].iloc[i])
            print(product_sum)
            print(norm)
            product = product.iloc[0:0]
            product_sum = 0

        elif i > (n - 1):
            product = price_df[f'{name_var}'].iloc[(i - (n - 1)): (i + 1)] * weight_df['weight']
            product_sum = product.sum()
            wma_n[f'WMA{n}'].iloc[i] = product_sum / norm
            print(f'index: {i}, wma: ', wma_n[f'WMA{n}'].iloc[i])
            print(product_sum)
            print(norm)
            product = product.iloc[0:0]
            product_sum = 0

    return wma_n

由于某种原因,在26次迭代后,值下降到0.0,我不知道为什么。有人能帮帮我吗?
我的输出:

index: 13, wma:  14467.42857142857
product_sum:  21267120.0
norm 1470.0
index: 14, wma:  14329.609523809524
product_sum:  21064526.0
norm 1470.0
index: 15, wma:  14053.980952380953
product_sum:  20659352.0
norm 1470.0
index: 16, wma:  13640.480952380953
product_sum:  20051507.0
norm 1470.0
index: 17, wma:  13089.029523809522
product_sum:  19240873.4
norm 1470.0
index: 18, wma:  12399.72
product_sum:  18227588.4
norm 1470.0
index: 19, wma:  11572.234285714285
product_sum:  17011184.4
norm 1470.0
index: 20, wma:  10607.100952380953
product_sum:  15592438.4
norm 1470.0
index: 21, wma:  9504.32
product_sum:  13971350.4
norm 1470.0
index: 22, wma:  8263.905714285715
product_sum:  12147941.4
norm 1470.0
index: 23, wma:  6885.667619047619
product_sum:  10121931.4
norm 1470.0
index: 24, wma:  5369.710476190477
product_sum:  7893474.4
norm 1470.0
index: 25, wma:  3716.270476190476
product_sum:  5462917.6
norm 1470.0
index: 26, wma:  1926.48
product_sum:  2831925.6
norm 1470.0
index: 27, wma:  0.0
product_sum:  0.0
norm 1470.0
index: 28, wma:  0.0
product_sum:  0.0
norm 1470.0

运行我的函数所需的辅助函数。

def input_type(__input_mode__: int) -> (str, bool):
    list_of_inputs = ['Open', 'Close', 'High', 'Low', 'HL2', 'HLC3', 'OHLC4', 'HLCC4']
    if __input_mode__ in range(1, 10, 1):
        input_name = list_of_inputs[__input_mode__ - 1]
        state = True
        return input_name, state
    else:
        raise TypeError('__input_mode__ out of range.')
iklwldmw

iklwldmw1#

这个问题是由Pandas的alignment特性引起的。假设你有两个 Dataframe 。一个 Dataframe 显示你持有的每只股票的数量。另一个 Dataframe 显示每只股票的价格。但是,它们的顺序不一样,并且有数据丢失。

df_shares_held = pd.DataFrame({'shares': [1, 5, 10]}, index=['ABC', 'DEF', 'XYZ'])
df_price_per_share = pd.DataFrame({'price': [0.54, 1.1]}, index=['XYZ', 'ABC'])

这些 Dataframe 如下所示:

shares
ABC       1
DEF       5
XYZ      10
     price
XYZ   0.54
ABC   1.10

Pandas会让你把这两列相乘。

print(df_shares_held['shares'] * df_price_per_share['price'])

ABC    1.1
DEF    NaN
XYZ    5.4
dtype: float64

请注意,它将ABC的价格与ABC的股票数量匹配起来,尽管它们在原始 Dataframe 中的顺序不同,DEF缺少一个股票价格,现在变成了NaN,因为乘法的一边缺少一个值。
Pandas在这里做了类似的事情,这是price_df[f'{name_var}'].iloc[(i - (n - 1)): (i + 1)]在循环过程中的值:
请注意,这从1开始,到14结束。
这是同一循环中weights_df['weights']的值:

0      14.0
1      28.0
2      42.0
3      56.0
4      70.0
5      84.0
6      98.0
7     112.0
8     126.0
9     140.0
10    154.0
11    168.0
12    182.0
13    196.0
Name: weight, dtype: float64

请注意,这从0开始,到13结束。
这是两者的乘积:

0           NaN
1      405174.0
2      607845.0
3      810633.6
4     1013285.0
5     1216404.0
6     1418746.0
7     1621088.0
8     1823409.0
9     2026010.0
10    2228457.0
11    2430556.8
12    2630992.0
13    2831925.6
14          NaN
dtype: float64

现在,第一个和最后一个值都有NaN,并且只有13个真实的值。每次循环,它都会多丢失一个值。
但是为什么它返回零而不是NaN呢?Pandas在对列求和时忽略NaN值。如果只对NaN值求和,那么它返回零。
那么,如何才能避免对齐呢?有很多方法。
1.方法1:可以调用reset_index()

product = price_df[f'{name_var}'].iloc[(i - (n - 1)): (i + 1)].reset_index(drop=True) * weight_df['weight']

这将使索引恢复为从零开始。
1.方法#2:你可以用numpy来计算,Numpy不关心对齐与否。

product = price_df[f'{name_var}'].iloc[(i - (n - 1)): (i + 1)].values * weight_df['weight'].values

1.方法#3:Pandas已经有了一种方法来计算你要找的东西--它们被称为rolling window计算。

import numpy as np
def wma2(price_df, n: int = 14, input_mode: int = 2, from_price: bool = True, *,
        indicator_name: str = 'None'):
    if from_price:
        name_var, state = input_type(__input_mode__=input_mode)
    else:
        if indicator_name == 'None':
            raise TypeError('Invalid input argument. indicator_name cannot be set to None if from_price is False.')
        else:
            name_var = indicator_name
    weights = np.arange(1, (n + 1)).astype('float64')
    weights_normalized = weights / weights.sum()
    wma_series = price_df['Close'].rolling(n).apply(
        lambda window: np.dot(window, weights_normalized)
    )
    return pd.DataFrame({f'WMA{n}': wma_series})

这不仅更简单,而且更快。

8fq7wneg

8fq7wneg2#

我认为发生这种情况的原因是因为weight_df的索引为0 - 13,但当您迭代price_df时,索引最初为0 - 13,然后为1 - 14,然后为2 - 15、3 - 16、4 - 17等。这意味着当您将这些索引相乘时:

product = price_df[f'{name_var}'].iloc[(i - (n - 1)): (i + 1)] * weight_df['weight']

由于索引不对齐,您将得到一大堆NaN值!下面是一个越来越糟糕的示例:

import pandas as pd

a = pd.Series([4, 5, 6], index=[1, 2, 3])
b = pd.Series([1, 2, 3], index=[3, 4, 5])

out = a * b

输出:
在您的示例中,weight_dfprice_df的索引在迭代过程中越来越远离,从而创建了越来越多的NaN。
我相信这个问题是可以解决的,但我强烈建议你用一种更"Pandas"的方式来解决这个问题。https://stackoverflow.com/a/53833851/9499196
Pandas DataFrames提供了.rolling方法,该方法可以生成您想要手动创建的窗口,然后您可以通过在price_df[your_col].rolling()返回的Rolling对象上调用.apply,对每个窗口应用一个函数(加权平均值)。

相关问题