利用 Python 实现随机相对强弱指数 StochRSI
作者:佚名 发布时间:2023-03-23 22:31:08
随机相对强弱指数简称为StochRSI
,是一种技术分析指标,用于确定资产是否处于超买或超卖状态,也用于确定当前市场的态势。顾名思义,StochRSI
是标准相对强弱指数(RSI)的衍生,因此被视为是一种能够衡量指数的指数。它是一种振荡器,在中心线的上方和下方波动。
StochRSI
最初是在1994年由Stanley Kroll
和Tushar Chande
撰写的题为《The NewTechnical Trader》的书中描述。它经常被股票交易者使用。
一、StochRSI如何运作?
通过应用随机振荡器生成公式,从标准RSI生成StochRSI
。其生成结果是单个数字评级,围绕中心线(0.5)在0-1的值域范围内上下摆动。但是,StochRSI的修改版本将结果乘以100,因此该值是介于0和100之间而不是0和1之间。通常还会参考3天内的简单移动平均线(SMA)以及StochRSI
趋势,作为信号线,旨在降低虚假信号交易的风险。
标准随机震荡指数公式取决于资产的收盘价以及设定周期内的最高价和最低价。但是,当使用公式计算StochRSI时,它直接使用RSI数据(不考虑价格)。
Stoch RSI = (Current RSI - Lowest RSI)/(Highest RSI - Lowest RSI)
与标准RSI一样,StochRSI
使用的最常见时间周期为14。StochRSI
计算中涉及的14个周期基于图表时间范围。因此,每日图表会显示过去14天(K线图),每小时图表会显示过去14小时生成的StochRSI
。
周期可以设置为几天、几小时甚至几分钟,并且它们的使用方式也因交易者而异(根据他们的情况和策略而定)。还可以向上或向下调整周期数,以确定长期或短期趋势。将周期值设置为20,是StochRSI指标一个相当受欢迎的选择。
如上所述,某些StochRSI
图表模式指定的范围值为0到100而不是0到1。在这些图表中,中心线为50而不是0.5。因此,通常在0.8处出现的超买信号将表示为80,而超卖信号表示为20而不是0.2。具有0-100设置的图表可能看起来略有不同,但实际原理解释是基本相同的。
二、如何使用StochRSI?
StochRSI指数如果出现在其范围的上限和下限附近,此时的意义是最重大的。因此,该指标的主要用途是确定潜在的买入和卖出点,以及价格发生的逆转。因此,0.2或以下的数值,会表明资产可能发生超卖,而0.8或以上的数值则表明该资产可能会发生超买。
此外,更接近中心线的数值也可以为交易者提供有关市场趋势的信息。例如,当中心线作为支撑线并且StochRSI线稳定移动到0.5以上时,尤其是数值趋近于0.8,则可能表明其继续看涨或呈上升趋势。同样,当数值始终低于0.5,趋近于0.2时,则表明下跌或呈下降趋势趋势。
我们将通过 Python 中的回测来介绍 RSI
和 StochRSI
这两种方法。
三、基于均值回归的StochRSI 策略
最常见的 StochRSI
策略基于均值回归。与 RSI 一样,StochRSI
通常使用 80 来表示做空的超买水平,使用 20 来表示要买入的超卖水平。此外,14 天的回顾和平滑期很常见。出于我们的目的,我们将坚持使用这些标准值。
现在编写代码,让我们在 Python 中导入一些标准包。
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import yfinance as yf
接下来,我们将构建一个函数来计算我们的指标。我们将其称为 calcStochRSI(),
它将依靠一些函数来计算 RSI 和随机振荡器,以获得我们选择的指标。
def calcRSI(data, P=14):
# Calculate gains and losses
data['diff_close'] = data['Close'] - data['Close'].shift(1)
data['gain'] = np.where(data['diff_close']>0,
data['diff_close'], 0)
data['loss'] = np.where(data['diff_close']<0,
np.abs(data['diff_close']), 0)
# Get initial values
data[['init_avg_gain', 'init_avg_loss']] = data[
['gain', 'loss']].rolling(P)
# Calculate smoothed avg gains and losses for all t > P
avg_gain = np.zeros(len(data))
avg_loss = np.zeros(len(data))
for i, _row in enumerate(data.iterrows()):
row = _row[1]
if i < P - 1:
last_row = row.copy()
continue
elif i == P-1:
avg_gain[i] += row['init_avg_gain']
avg_loss[i] += row['init_avg_loss']
else:
avg_gain[i] += ((P - 1) * avg_gain[i] +
row['gain']) / P
avg_loss[i] += ((P - 1) * avg_loss[i] +
row['loss']) / P
last_row = row.copy()
data['avg_gain'] = avg_gain
data['avg_loss'] = avg_loss
# Calculate RS and RSI
data['RS'] = data['avg_gain'] / data['avg_loss']
data['RSI'] = 100 - 100 / (1 + data['RS'])
return data
def calcStochOscillator(data):
data['low_N'] = data['RSI'].rolling(N).min()
data['high_N'] = data['RSI'].rolling(N).max()
data['StochRSI'] = 100 * (data['RSI'] - data['low_N']) / \
(data['high_N'] - data['low_N'])
return data
def calcStochRSI(data, P=14, N=14):
data = calcRSI(data)
data = calcStochOscillator(data)
return data
def calcReturns(df):
# Helper function to avoid repeating too much code
df['returns'] = df['Close'] / df['Close'].shift(1)
df['log_returns'] = np.log(df['returns'])
df['strat_returns'] = df['position'].shift(1) * df['returns']
df['strat_log_returns'] = df['position'].shift(1) * df['log_returns']
df['cum_returns'] = np.exp(df['log_returns'].cumsum()) - 1
df['strat_cum_returns'] = np.exp(df['strat_log_returns'].cumsum()) - 1
df['peak'] = df['cum_returns'].cummax()
df['strat_peak'] = df['strat_cum_returns'].cummax()
return df
有了这些功能,我们只需要为我们的策略构建逻辑就可以了。还要注意,我们有一个名为 calcReturns
的辅助函数,我们可以快速将其应用于回测的结果以从中获取所有返回值。
这意味着回归模型将在 StochRSI
高于 80 时做空或卖出,并在低于 20 时买入。
def StochRSIReversionStrategy(data, P=14, N=14, short_level=80,
buy_level=20, shorts=True):
'''Buys when the StochRSI is oversold and sells when it's overbought'''
df = calcStochRSI(data, P, N)
df['position'] = np
df['position'] = np.where(df['StochRSI']<buy_level, 1, df['position'])
if shorts:
df['position'] = np.where(df['StochRSI']>short_level, -1, df['position'])
else:
df['position'] = np.where(df['StochRSI']>short_level, 0, df['position'])
df['position'] = df['position'].ffill()
return calcReturns(df)
table = pd.read_html('https://en.wikipedia.org/wiki/List_of_S%26P_500_companies')
df = table[0]
syms = df['Symbol']
# Sample symbols
# ticker = np.random.choice(syms.values)
ticker = "BSX"
print(f"Ticker Symbol: {ticker}")
start = '2000-01-01'
end = '2020-12-31'
# Get Data
yfyfObj = yf.Ticker(ticker)
data = yfObj.history(startstart=start, endend=end)
data.drop(['Open', 'High', 'Low', 'Volume', 'Dividends',
'Stock Splits'], inplace=True, axis=1)
# Run test
df_rev = StochRSIReversionStrategy(data.copy())
# Plot results
colors = plt.rcParams['axes.prop_cycle'].by_key()['color']
fig, ax = plt.subplots(2, figsize=(12, 8))
ax[0].plot(df_rev['strat_cum_returns']*100, label='Mean Reversion')
ax[0].plot(df_rev['cum_returns']*100, label='Buy and Hold')
ax[0].set_ylabel('Returns (%)')
ax[0].set_title('Cumulative Returns for Mean Reversion and' +
f' Buy and Hold Strategies for {ticker}')
ax[0].legend(bbox_to_anchor=[1, 0.6])
ax[1].plot(df_rev['StochRSI'], label='StochRSI', linewidth=0.5)
ax[1].plot(df_rev['RSI'], label='RSI', linewidth=1)
ax[1].axhline(80, label='Over Bought', color=colors[1], linestyle=':')
ax[1].axhline(20, label='Over Sold', color=colors[2], linestyle=':')
ax[1].axhline(50, label='Centerline', color='k', linestyle=':')
ax[1].set_ylabel('Stochastic RSI')
ax[1].set_xlabel('Date')
ax[1].set_title(f'Stochastic RSI for {ticker}')
ax[1].legend(bbox_to_anchor=[1, 0.75])
plt.tight_layout()
plt.show()
在我们研究的 21 年期间,均值回归策略击败了Boston Scientific(BSX
)的买入和持有策略,回报率为 28 倍,而后者为 2 倍。
在第二个图中显示了 StochRSI
和一些关键指标。我还添加了 RSI 以与更不稳定的 StochRSI
进行比较。这导致交易频繁,如果您的账户较小且交易成本相对较高,这可能会严重影响您的实际回报。我们只是在一个工具上运行它,所以最终进行了 443 笔交易,或者每 12 天交易一次,这看起来并不多。但是,如果我们要使用该指标管理适当的工具组合并频繁进行交易,我们每天可能会进出多笔交易,交易成本会变得很高。
# Get trades
diff = df_rev['position'].diff().dropna()
trade_idx = diff.index[np.where(diff!=0)]
fig, ax = plt.subplots(figsize=(12, 8))
ax.plot(df_rev['Close'], linewidth=1, label=f'{ticker}')
ax.scatter(trade_idx, df_rev[trade_idx]['Close'], c=colors[1],
marker='^', label='Trade')
ax.set_ylabel('Price')
ax.set_title(f'{ticker} Price Chart and Trades for' +
'StochRSI Mean Reversion Strategy')
ax.legend()
plt.show()
要查看整体策略的一些关键指标,让我们看看使用以下 getStratStats
函数。
def getStratStats(log_returns: pd.Series, risk_free_rate: float = 0.02):
stats = {}
# Total Returns
stats['tot_returns'] = np.exp(log_returns.sum()) - 1
# Mean Annual Returns
stats['annual_returns'] = np.exp(log_returns.mean() * 252) - 1
# Annual Volatility
stats['annual_volatility'] = log_returns * np.sqrt(252)
# Sortino Ratio
annualized_downside = log_returns.loc[log_returns<0].std() * np.sqrt(252)
stats['sortino_ratio'] = (stats['annual_returns'] - risk_free_rate) \
/ annualized_downside
# Sharpe Ratio
stats['sharpe_ratio'] = (stats['annual_returns'] - risk_free_rate) \
/ stats['annual_volatility']
# Max Drawdown
cum_returns = log_returns.cumsum() - 1
peak = cum_returns.cummax()
drawdown = peak - cum_returns
stats['max_drawdown'] = drawdown.max()
# Max Drawdown Duration
strat_dd = drawdown[drawdown==0]
strat_ddstrat_dd_diff = strat_dd.index[1:] - strat_dd.index[:-1]
strat_dd_days = strat_dd_diff.map(lambda x: x.days)
strat_dd_days = np.hstack([strat_dd_days,
(drawdown.index[-1] - strat_dd.index[-1]).days])
stats['max_drawdown_duration'] = strat_dd_days.max()
return stats
rev_stats = getStratStats(df_rev['strat_log_returns'])
bh_stats = getStratStats(df_rev['log_returns'])
pd.concat([pd.DataFrame(rev_stats, index=['Mean Reversion']),
pd.DataFrame(bh_stats, index=['Buy and Hold'])])
在这里,我们看到该策略的回报率为 28 倍,而基础资产的年度波动率大致相同。此外,根据 Sortino
和 Sharpe
Ratios 衡量,我们有更好的风险调整回报。
在 2020 年的新冠疫情中,我们确实看到了均值回归策略的潜在问题之一。该策略的总回报大幅下降,因为该策略的定位是向上回归,但市场继续低迷,该模型只是保持不变 . 它恢复了其中的一部分,但在这次测试中从未达到过疫情之前的高点。正确使用止损有助于限制这些巨大的损失,并有可能增加整体回报。
四、StochRSI 和动量策略
我们之前提到的另一个基本策略是使用 StochRSI
作为动量指标。当指标穿过中心线时,我们会根据其方向买入或做空股票。
def StochRSIMomentumStrategy(data, P=14, N=14,
centerline=50, shorts=True):
'''
Buys when the StochRSI moves above the centerline,
sells when it moves below
'''
df = calcStochRSI(data, P)
df['position'] = np.nan
df['position'] = np.where(df['StochRSI']>50, 1, df['position'])
if shorts:
df['position'] = np.where(df['StochRSI']<50, -1, df['position'])
else:
df['position'] = np.where(df['StochRSI']<50, 0, df['position'])
df['position'] = df['position'].ffill()
return calcReturns(df)
运行我们的回测:
# Run test
df_mom = StochRSIMomentumStrategy(data.copy())
# Plot results
colors = plt.rcParams['axes.prop_cycle'].by_key()['color']
fig, ax = plt.subplots(2, figsize=(12, 8))
ax[0].plot(df_mom['strat_cum_returns']*100, label='Momentum')
ax[0].plot(df_mom['cum_returns']*100, label='Buy and Hold')
ax[0].set_ylabel('Returns (%)')
ax[0].set_title('Cumulative Returns for Momentum and' +
f' Buy and Hold Strategies for {ticker}')
ax[0].legend(bbox_to_anchor=[1, 0.6])
ax[1].plot(df_mom['StochRSI'], label='StochRSI', linewidth=0.5)
ax[1].plot(df_mom['RSI'], label='RSI', linewidth=1)
ax[1].axhline(50, label='Centerline', color='k', linestyle=':')
ax[1].set_ylabel('Stochastic RSI')
ax[1].set_xlabel('Date')
ax[1].set_title(f'Stochastic RSI for {ticker}')
ax[1].legend(bbox_to_anchor=[1, 0.75])
plt.tight_layout()
plt.show()
在这种情况下,我们的动量策略表现非常糟糕,在我们假设的时间段内几乎损失了我们所有的初始投资。
查看我们策略的统计数据,该模型的唯一优势是比买入并持有方法的回撤时间略短。
mom_stats = getStratStats(df_mom['strat_log_returns'])
bh_stats = getStratStats(df_mom['log_returns'])
pd.concat([pd.DataFrame(mom_stats, index=['Momentum']),
pd.DataFrame(rev_stats, index=['Mean Reversion']),
pd.DataFrame(bh_stats, index=['Buy and Hold'])])
这并不意味着StochRSI
不适合此类应用。一次糟糕的回测并不意味着该策略毫无价值。相反,一个很好的回测并不意味着你有一些你应该立即开始交易的东西。我们需要与其他指标结合使用以改善结果。
来源:https://developer.51cto.com/art/202109/682827.htm


猜你喜欢
- 目录先明确几点赋值浅拷贝深拷贝总结先明确几点不可变类型:该数据类型对象所指定内存中的值不可以被改变。(1)、在改变某个对象的值时,由于其内存
- 1069错误(由于登录失败而无法启动服务)解决方法在本版面出现这个问题的频率也算是很高的了,新手通常会比较多遇到这个问题原因很简单,安装SQ
- 如下所示:#coding:utf-8 ''''' Created on 2014-7-24 @aut
- 本文实例讲述了python有证书的加密解密实现方法。分享给大家供大家参考。具体实现方法如下:最近在做python的加解密工作,同时加完密的串
- 动态联接库(DLL)是加快应用程序关键部分的执行速度的重要方法,但有一点恐怕大部分人都不知道,那就是在ASP文件也能通过调用DLL来加快服务
- 1. dbm UNIX键-值数据库dbm是面向DBM数据库的一个前端,DBM数据库使用简单的字符串值作为键来访问包含字符串的记录。dbm使用
- 最近关于HTML5吵得火热,很多人认为HTML5出现会秒杀Flash,以至于在各大web前端开 * 坛吵得不可开交。论坛里三言两语说的不够 尽
- 一、concat()函数1、功能:将多个字符串连接成一个字符串。2、语法:concat(str1, str2,...)返回结果为连接参数产生
- 想找一个可以播放文字的闹钟找不到,自己写一个更简单。TTS实现由很多种办法,百度等都提供了API接口,但类似百度,需要先注册等一系列动作。其
- 关于python网络爬虫selenium打开多窗口与切换页面的方法代码测试与解析。首先打开百度from selenium import we
- set global log_bin_trust_function_creators = 1; DROP FUNCTION IF EXIST
- 前言:目前在研究易信公众号,想给公众号增加一个获取个人交通违章的查询菜单,通过点击返回查询数据。以下是实施过程。一、首先,用火狐浏览器打开X
- 最小生成树的Prim算法也是贪心算法的一大经典应用。Prim算法的特点是时刻维护一棵树,算法不断加边,加的过程始终是一棵树。Prim算法过程
- 此文译自Fred Wilson 2010年2月在迈阿密举行的Web未来应用的年会上的演讲谢谢青云推荐了这篇这么好的演说谢谢卓和百忙中抽空帮我
- 双向绑定Vue 的双向绑定是通过数据劫持和发布-订阅模式实现的。当 Vue 实例初始化时,它会对 data 选项中的每个属性使用 Objec
- 前沿对于iOS开发不要随便拆卸系统自带的Python,因为有很多 library 还是使用 Python2.7。1 安装Xcode1.1 A
- 如下所示:#提取目录下所有图片,更改尺寸后保存到另一目录from PIL import Imageimport os.pathimport
- 这篇文章主要介绍了python enumerate内置函数用法总结,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价
- 如果字典中存储了一些值,我想要取出来该怎么操作呢?1、我要取出字典中所有的键-值对取出字典中所有的键-值对时,可以使用items()返回一个
- 1 注释符注释是指程序代码中不执行的文本字符串,是对程序的说明,可以提高程序的可读性,使程序代码更易于维护,一般嵌入在程序中并以特殊的标记显