从Pandas DataFrame计算RSI指标?

Joh*_*Dev 6 python dataframe pandas

我的问题

我在Github上尝试了许多库,但是它们都没有为TradingView产生匹配的结果,因此我按照此链接上的公式计算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.
Run Code Online (Sandbox Code Playgroud)

预期成绩

     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  
Run Code Online (Sandbox Code Playgroud)

我的密码

进口

import pandas as pd
import numpy as np
Run Code Online (Sandbox Code Playgroud)

载入资料

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
Run Code Online (Sandbox Code Playgroud)

更改

计算每一行的变化

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
Run Code Online (Sandbox Code Playgroud)

得失

从变化中获得收益和损失

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
Run Code Online (Sandbox Code Playgroud)

计算拳头平均损益

前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
Run Code Online (Sandbox Code Playgroud)

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

avg_gain[n] = (avg_gain[n-1]*13 + gain[n]) / 14
Run Code Online (Sandbox Code Playgroud)

我的愿望-我的问题

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

Ste*_*tef 26

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

将平均增益表示为y,将当前增益表示为x,我们得到y[i] = a*y[i-1] + b*x[i]、 wherea = 13/14b = 1/14for 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))
Run Code Online (Sandbox Code Playgroud)

的输出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
Run Code Online (Sandbox Code Playgroud)


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

为了说明这一点,我将我的 numpy 解决方案与 dimitris_ps 的循环解决方案进行了一个小的比较:

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 /sf/answers/3990603781/
    # 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)
Run Code Online (Sandbox Code Playgroud)

结果(毫秒/循环):

rows    np    loop
23      4.9   9.2
230     5.0   112.3
2300    5.5   1122.7
Run Code Online (Sandbox Code Playgroud)

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


Joh*_*ohn 8

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

import talib   
close = df['close']
rsi = talib.RSI(close, timeperiod=14)
Run Code Online (Sandbox Code Playgroud)

如果您希望布林线与您的 RSI 一致,那也很容易。

upperBB, middleBB, lowerBB = talib.BBANDS(close, timeperiod=20, nbdevup=2, nbdevdn=2, matype=0)
Run Code Online (Sandbox Code Playgroud)

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

upperBBrsi, MiddleBBrsi, lowerBBrsi = talib.BBANDS(rsi, timeperiod=50, nbdevup=2, nbdevdn=2, matype=0)
Run Code Online (Sandbox Code Playgroud)

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

normrsi = (rsi - lowerBBrsi) / (upperBBrsi - lowerBBrsi)
Run Code Online (Sandbox Code Playgroud)

有关 talib 的信息 https://mrjbq7.github.io/ta-lib/

有关布林线的信息 https://www.BollingerBands.com

  • TA-lib没有通过tradingview给出对应的值 (5认同)