【问题标题】:Calculate RSI indicator from pandas DataFrame?从 pandas DataFrame 计算 RSI 指标?
【发布时间】:2019-11-22 04:33:57
【问题描述】:

我的问题

我在 Github 上尝试了许多库,但所有库都没有为 TradingView 生成匹配结果,因此我按照link 上的公式计算 RSI 指标。我用 Excel 计算它并用 TradingView 整理结果。我知道这是绝对正确,但是我没有找到用Pandas计算它的方法。

公式

              100
RSI = 100 - --------
             1 + RS

RS = Average Gain / Average Loss

The very first calculations for average gain and average loss are simple
14-period averages:

First Average Gain = Sum of Gains over the past 14 periods / 14.
First Average Loss = Sum of Losses over the past 14 periods / 14

The second, and subsequent, calculations are based on the prior averages
and the current gain loss:

Average Gain = [(previous Average Gain) x 13 + current Gain] / 14.
Average Loss = [(previous Average Loss) x 13 + current Loss] / 14.

预期结果

     close   change     gain     loss     avg_gian    avg_loss        rs  \
0    4724.89      NaN      NaN      NaN          NaN         NaN       NaN   
1    4378.51  -346.38     0.00   346.38          NaN         NaN       NaN   
2    6463.00  2084.49  2084.49     0.00          NaN         NaN       NaN   
3    9838.96  3375.96  3375.96     0.00          NaN         NaN       NaN   
4   13716.36  3877.40  3877.40     0.00          NaN         NaN       NaN   
5   10285.10 -3431.26     0.00  3431.26          NaN         NaN       NaN   
6   10326.76    41.66    41.66     0.00          NaN         NaN       NaN   
7    6923.91 -3402.85     0.00  3402.85          NaN         NaN       NaN   
8    9246.01  2322.10  2322.10     0.00          NaN         NaN       NaN   
9    7485.01 -1761.00     0.00  1761.00          NaN         NaN       NaN   
10   6390.07 -1094.94     0.00  1094.94          NaN         NaN       NaN   
11   7730.93  1340.86  1340.86     0.00          NaN         NaN       NaN   
12   7011.21  -719.72     0.00   719.72          NaN         NaN       NaN   
13   6626.57  -384.64     0.00   384.64          NaN         NaN       NaN   
14   6371.93  -254.64     0.00   254.64   931.605000  813.959286  1.144535   
15   4041.32 -2330.61     0.00  2330.61   865.061786  922.291480  0.937948   
16   3702.90  -338.42     0.00   338.42   803.271658  880.586374  0.912201   
17   3434.10  -268.80     0.00   268.80   745.895111  836.887347  0.891273   
18   3813.69   379.59   379.59     0.00   719.730460  777.109680  0.926163   
19   4103.95   290.26   290.26     0.00   689.053999  721.601845  0.954895   
20   5320.81  1216.86  1216.86     0.00   726.754428  670.058856  1.084613   
21   8555.00  3234.19  3234.19     0.00   905.856968  622.197509  1.455899   
22  10854.10  2299.10  2299.10     0.00  1005.374328  577.754830  1.740140   

       rsi_14  
0         NaN  
1         NaN  
2         NaN  
3         NaN  
4         NaN  
5         NaN  
6         NaN  
7         NaN  
8         NaN  
9         NaN  
10        NaN  
11        NaN  
12        NaN  
13        NaN  
14  53.369848  
15  48.399038  
16  47.704239  
17  47.125561  
18  48.083322  
19  48.846358  
20  52.029461  
21  59.281719  
22  63.505515  

我的代码

导入

import pandas as pd
import numpy as np

加载数据

df = pd.read_csv("rsi_14_test_data.csv")
close = df['close']
print(close)

0      4724.89
1      4378.51
2      6463.00
3      9838.96
4     13716.36
5     10285.10
6     10326.76
7      6923.91
8      9246.01
9      7485.01
10     6390.07
11     7730.93
12     7011.21
13     6626.57
14     6371.93
15     4041.32
16     3702.90
17     3434.10
18     3813.69
19     4103.95
20     5320.81
21     8555.00
22    10854.10
Name: close, dtype: float64

改变

计算每一行的变化

change = close.diff(1)
print(change)

0         NaN
1     -346.38
2     2084.49
3     3375.96
4     3877.40
5    -3431.26
6       41.66
7    -3402.85
8     2322.10
9    -1761.00
10   -1094.94
11    1340.86
12    -719.72
13    -384.64
14    -254.64
15   -2330.61
16    -338.42
17    -268.80
18     379.59
19     290.26
20    1216.86
21    3234.19
22    2299.10
Name: close, dtype: float64

得失

从变化中获得收益和损失

is_gain, is_loss = change > 0, change < 0
gain, loss = change, -change
gain[is_loss] = 0
loss[is_gain] = 0
​
gain.name = 'gain'
loss.name = 'loss'
print(loss)

0         NaN
1      346.38
2        0.00
3        0.00
4        0.00
5     3431.26
6        0.00
7     3402.85
8        0.00
9     1761.00
10    1094.94
11       0.00
12     719.72
13     384.64
14     254.64
15    2330.61
16     338.42
17     268.80
18       0.00
19       0.00
20       0.00
21       0.00
22       0.00
Name: loss, dtype: float64

计算拳头平均得失

前 n 行的平均值

n = 14
avg_gain = change * np.nan
avg_loss = change * np.nan
​
avg_gain[n] = gain[:n+1].mean()
avg_loss[n] = loss[:n+1].mean()
​
avg_gain.name = 'avg_gain'
avg_loss.name = 'avg_loss'
​
avg_df = pd.concat([gain, loss, avg_gain, avg_loss], axis=1)
print(avg_df)

       gain     loss  avg_gain    avg_loss
0       NaN      NaN       NaN         NaN
1      0.00   346.38       NaN         NaN
2   2084.49     0.00       NaN         NaN
3   3375.96     0.00       NaN         NaN
4   3877.40     0.00       NaN         NaN
5      0.00  3431.26       NaN         NaN
6     41.66     0.00       NaN         NaN
7      0.00  3402.85       NaN         NaN
8   2322.10     0.00       NaN         NaN
9      0.00  1761.00       NaN         NaN
10     0.00  1094.94       NaN         NaN
11  1340.86     0.00       NaN         NaN
12     0.00   719.72       NaN         NaN
13     0.00   384.64       NaN         NaN
14     0.00   254.64   931.605  813.959286
15     0.00  2330.61       NaN         NaN
16     0.00   338.42       NaN         NaN
17     0.00   268.80       NaN         NaN
18   379.59     0.00       NaN         NaN
19   290.26     0.00       NaN         NaN
20  1216.86     0.00       NaN         NaN
21  3234.19     0.00       NaN         NaN
22  2299.10     0.00       NaN         NaN

平均收益和平均损失的第一次计算是可以的,但我不知道如何将 pandas.core.window.Rolling.apply 应用于第二次和后续,因为它们位于许多行和不同的列中。 可能是这样的:

avg_gain[n] = (avg_gain[n-1]*13 + gain[n]) / 14

我的愿望 - 我的问题

  • 计算和使用技术指标的最佳方式?
  • 以“熊猫风格”完成以上代码。
  • 与 Pandas 相比,传统的循环编码方式会降低性能吗?

【问题讨论】:

  • 欢迎来到 Stackoverflow。这个写得很好的问题做得很好!
  • 你最好只使用(或复制/粘贴)github.com/peerchemist/finta 的实现,而不是重新发明轮子
  • @GustavoBezerra 我不知道为什么我在 Github 中找到的所有库都具有相同的 RSI 功能,但它们没有像我使用 Excel 和 TradingView 那样产生正确的结果

标签: python pandas dataframe


【解决方案1】:

平均收益和损失是通过递归公式计算的,不能用numpy向量化。然而,我们可以尝试找到一个分析(即非递归)解决方案来计算单个元素。然后可以使用 numpy 实现这样的解决方案。

将平均增益表示为y,将当前增益表示为x,我们得到y[i] = a*y[i-1] + b*x[i],其中a = 13/14b = 1/14 表示n = 14。展开递归会导致: (抱歉图片,只是打字比较麻烦)

这可以在 numpy 中使用cumsum 高效计算(rma = 移动平均):

import pandas as pd
import numpy as np

df = pd.DataFrame({'close':[4724.89, 4378.51,6463.00,9838.96,13716.36,10285.10,
                          10326.76,6923.91,9246.01,7485.01,6390.07,7730.93,
                          7011.21,6626.57,6371.93,4041.32,3702.90,3434.10,
                          3813.69,4103.95,5320.81,8555.00,10854.10]})
n = 14


def rma(x, n, y0):
    a = (n-1) / n
    ak = a**np.arange(len(x)-1, -1, -1)
    return np.r_[np.full(n, np.nan), y0, np.cumsum(ak * x) / ak / n + y0 * a**np.arange(1, len(x)+1)]

df['change'] = df['close'].diff()
df['gain'] = df.change.mask(df.change < 0, 0.0)
df['loss'] = -df.change.mask(df.change > 0, -0.0)
df['avg_gain'] = rma(df.gain[n+1:].to_numpy(), n, np.nansum(df.gain.to_numpy()[:n+1])/n)
df['avg_loss'] = rma(df.loss[n+1:].to_numpy(), n, np.nansum(df.loss.to_numpy()[:n+1])/n)
df['rs'] = df.avg_gain / df.avg_loss
df['rsi_14'] = 100 - (100 / (1 + df.rs))

df.round(2) 的输出:

         close   change     gain     loss  avg_gain  avg_loss    rs    rsi  rsi_14
0      4724.89      NaN      NaN      NaN       NaN       NaN   NaN    NaN     NaN
1      4378.51  -346.38     0.00   346.38       NaN       NaN   NaN    NaN     NaN
2      6463.00  2084.49  2084.49     0.00       NaN       NaN   NaN    NaN     NaN
3      9838.96  3375.96  3375.96     0.00       NaN       NaN   NaN    NaN     NaN
4     13716.36  3877.40  3877.40     0.00       NaN       NaN   NaN    NaN     NaN
5     10285.10 -3431.26     0.00  3431.26       NaN       NaN   NaN    NaN     NaN
6     10326.76    41.66    41.66     0.00       NaN       NaN   NaN    NaN     NaN
7      6923.91 -3402.85     0.00  3402.85       NaN       NaN   NaN    NaN     NaN
8      9246.01  2322.10  2322.10     0.00       NaN       NaN   NaN    NaN     NaN
9      7485.01 -1761.00     0.00  1761.00       NaN       NaN   NaN    NaN     NaN
10     6390.07 -1094.94     0.00  1094.94       NaN       NaN   NaN    NaN     NaN
11     7730.93  1340.86  1340.86     0.00       NaN       NaN   NaN    NaN     NaN
12     7011.21  -719.72     0.00   719.72       NaN       NaN   NaN    NaN     NaN
13     6626.57  -384.64     0.00   384.64       NaN       NaN   NaN    NaN     NaN
14     6371.93  -254.64     0.00   254.64    931.61    813.96  1.14  53.37   53.37
15     4041.32 -2330.61     0.00  2330.61    865.06    922.29  0.94  48.40   48.40
16     3702.90  -338.42     0.00   338.42    803.27    880.59  0.91  47.70   47.70
17     3434.10  -268.80     0.00   268.80    745.90    836.89  0.89  47.13   47.13
18     3813.69   379.59   379.59     0.00    719.73    777.11  0.93  48.08   48.08
19     4103.95   290.26   290.26     0.00    689.05    721.60  0.95  48.85   48.85
20     5320.81  1216.86  1216.86     0.00    726.75    670.06  1.08  52.03   52.03
21     8555.00  3234.19  3234.19     0.00    905.86    622.20  1.46  59.28   59.28
22    10854.10  2299.10  2299.10     0.00   1005.37    577.75  1.74  63.51   63.51


关于你关于性能的最后一个问题:python/pandas 中的显式循环很糟糕,尽可能避免使用它们。如果不能,请尝试cython or numba

为了说明这一点,我将我的 numpy 解决方案与 dimitris_ps'loop solution 做了一个小比较:

import pandas as pd
import numpy as np
import timeit

mult = 1        # length of dataframe = 23 * mult
number = 1000   # number of loop for timeit

df0 = pd.DataFrame({'close':[4724.89, 4378.51,6463.00,9838.96,13716.36,10285.10,
                          10326.76,6923.91,9246.01,7485.01,6390.07,7730.93,
                          7011.21,6626.57,6371.93,4041.32,3702.90,3434.10,
                          3813.69,4103.95,5320.81,8555.00,10854.10] * mult })
n = 14

def rsi_np():
    # my numpy solution from above
    return df
    
def rsi_loop():
    # loop solution https://stackoverflow.com/a/57008625/3944322
    # without the wrong alternative calculation of df['avg_gain'][14]
    return df

df = df0.copy()
time_np = timeit.timeit('rsi_np()', globals=globals(), number = number) / 1000 * number

df = df0.copy()
time_loop = timeit.timeit('rsi_loop()', globals=globals(), number = number) / 1000 * number

print(f'rows\tnp\tloop\n{len(df0)}\t{time_np:.1f}\t{time_loop:.1f}')

assert np.allclose(rsi_np(), rsi_loop(), equal_nan=True)

结果(毫秒/循环):

rows    np    loop
23      4.9   9.2
230     5.0   112.3
2300    5.5   1122.7

因此,即使对于 8 行(第 15...22 行),循环解决方案所需的时间大约是 numpy 解决方案的两倍。 Numpy 可以很好地扩展,而循环解决方案不适用于大型数据集。

【讨论】:

    【解决方案2】:

    有一个更简单的方法,包talib。

    import talib   
    close = df['close']
    rsi = talib.RSI(close, timeperiod=14)
    

    如果您希望布林带与您的 RSI 一起使用,这也很容易。

    upperBB, middleBB, lowerBB = talib.BBANDS(close, timeperiod=20, nbdevup=2, nbdevdn=2, matype=0)
    

    您可以在 RSI 上使用布林带,而不是固定参考水平 70 和 30。

    upperBBrsi, MiddleBBrsi, lowerBBrsi = talib.BBANDS(rsi, timeperiod=50, nbdevup=2, nbdevdn=2, matype=0)
    

    最后,您可以使用 %b 钙化来标准化 RSI。

    normrsi = (rsi - lowerBBrsi) / (upperBBrsi - lowerBBrsi)
    

    关于塔利卜的信息 https://mrjbq7.github.io/ta-lib/

    关于布林带的信息 https://www.BollingerBands.com

    【讨论】:

    • TA-lib 没有通过 tradingview 给出相应的值
    • @Justme talib 和 TradingView 的 RSI 非常适合我。也许您用于生成指标的两个数据集存在细微差别?没有人指出的是,这里的答案是不正确的,因为它们没有使用 Welles Wilder 为 RSI 指定的独特平滑方法。你可以在他的书中看到正确的方法:amazon.com/New-Concepts-Technical-Trading-Systems/dp/0894590278
    • 嗨,约翰,我运行了一个单元测试,并且确实 tablib 给出了相应的值。单元测试基于来自school.stockcharts.com/… 的 xls 表。但是再次与 tradingview 相比,我看到了不同的值(例如,tradingview 有 47,15,而 talib 给出了 46,79)。那么,tradingview 是否使用 Welles Wilders 平滑法?
    • 电视上的人正在调查,还没有回答。顺便说一句,我确实按照您的要求编辑了我的答案。
    • 当电视人回答时请更新,我也有同样的问题
    【解决方案3】:

    这是一个选项。

    我只会触摸你的第二个子弹

    # libraries required
    import pandas as pd
    import numpy as np
    
    # create dataframe
    df = pd.DataFrame({'close':[4724.89, 4378.51,6463.00,9838.96,13716.36,10285.10,
                              10326.76,6923.91,9246.01,7485.01,6390.07,7730.93,
                              7011.21,6626.57,6371.93,4041.32,3702.90,3434.10,
                              3813.69,4103.95,5320.81,8555.00,10854.10]})
    
    df['change'] = df['close'].diff(1) # Calculate change
    
    # calculate gain / loss from every change
    df['gain'] = np.select([df['change']>0, df['change'].isna()], 
                           [df['change'], np.nan], 
                           default=0) 
    df['loss'] = np.select([df['change']<0, df['change'].isna()], 
                           [-df['change'], np.nan], 
                           default=0)
    
    # create avg_gain /  avg_loss columns with all nan
    df['avg_gain'] = np.nan 
    df['avg_loss'] = np.nan
    
    n = 14 # what is the window
    
    # keep first occurrence of rolling mean
    df['avg_gain'][n] = df['gain'].rolling(window=n).mean().dropna().iloc[0] 
    df['avg_loss'][n] = df['loss'].rolling(window=n).mean().dropna().iloc[0]
    # Alternatively
    df['avg_gain'][n] = df.loc[:n, 'gain'].mean()
    df['avg_loss'][n] = df.loc[:n, 'loss'].mean()
    
    # This is not a pandas way, looping through the pandas series, but it does what you need
    for i in range(n+1, df.shape[0]):
        df['avg_gain'].iloc[i] = (df['avg_gain'].iloc[i-1] * (n - 1) + df['gain'].iloc[i]) / n
        df['avg_loss'].iloc[i] = (df['avg_loss'].iloc[i-1] * (n - 1) + df['loss'].iloc[i]) / n
    
    # calculate rs and rsi
    df['rs'] = df['avg_gain'] / df['avg_loss']
    df['rsi'] = 100 - (100 / (1 + df['rs'] ))
    

    【讨论】:

    • 我认为这部分: df['avg_gain'][n] = df['gain'].rolling(window=n).mean().dropna().iloc[0] 应该是: avg_gain[n] = gain[:n+1].mean() 因为对于大的DataFrame,rolling,mean和dropnan左右DataFrame 只计算 1 个值是浪费的。但基本上,您的代码运行良好。谢谢
    • avg_gain[n] = gain[:n+1].mean() 错了,一定是 avg_gain[n] = gain[:n].mean()`。 与通常的 python 切片相反,当索引中存在时,开始和停止都包含在 pandas 切片中
    【解决方案4】:

    大家好,圣诞老人来了: 这是rsi代码,替换所有有“aa”的东西:

    import pandas as pd
    rsi_period = 14
    df = pd.Series(coinaalist)
    chg = df.diff(1)
    gain = chg.mask(chg<0,0)
    loss = chg.mask(chg>0,0)
    avg_gain = gain.ewm(com = rsi_period-1,min_periods=rsi_period).mean()
    avg_loss = loss.ewm(com = rsi_period-1,min_periods=rsi_period).mean()
    rs = abs(avg_gain / avg_loss)
    crplaa = 100 - (100/(1+rs))
    coinaarsi = crplaa.iloc[-1]
    

    【讨论】:

      猜你喜欢
      • 2020-07-23
      • 2017-12-15
      • 2017-11-26
      • 2018-10-12
      • 2020-12-25
      • 2016-11-23
      • 2021-08-05
      • 2023-03-13
      • 2021-07-22
      相关资源
      最近更新 更多