利用 Python 實現(xiàn)隨機相對強弱指數(shù) StochRSI
隨機相對強弱指數(shù)簡稱為StochRSI
,是一種技術(shù)分析指標,用于確定資產(chǎn)是否處于超買或超賣狀態(tài),也用于確定當前市場的態(tài)勢。顧名思義,StochRSI
是標準相對強弱指數(shù)(RSI)的衍生,因此被視為是一種能夠衡量指數(shù)的指數(shù)。它是一種振蕩器,在中心線的上方和下方波動。
StochRSI
最初是在1994年由Stanley Kroll
和Tushar Chande
撰寫的題為《The NewTechnical Trader》的書中描述。它經(jīng)常被股票交易者使用。
一、StochRSI如何運作?
通過應(yīng)用隨機振蕩器生成公式,從標準RSI生成StochRSI
。其生成結(jié)果是單個數(shù)字評級,圍繞中心線(0.5)在0-1的值域范圍內(nèi)上下擺動。但是,StochRSI的修改版本將結(jié)果乘以100,因此該值是介于0和100之間而不是0和1之間。通常還會參考3天內(nèi)的簡單移動平均線(SMA)以及StochRSI
趨勢,作為信號線,旨在降低虛假信號交易的風險。
標準隨機震蕩指數(shù)公式取決于資產(chǎn)的收盤價以及設(shè)定周期內(nèi)的最高價和最低價。但是,當使用公式計算StochRSI時,它直接使用RSI數(shù)據(jù)(不考慮價格)。
Stoch RSI = (Current RSI - Lowest RSI)/(Highest RSI - Lowest RSI)
與標準RSI一樣,StochRSI
使用的最常見時間周期為14。StochRSI
計算中涉及的14個周期基于圖表時間范圍。因此,每日圖表會顯示過去14天(K線圖),每小時圖表會顯示過去14小時生成的StochRSI
。
周期可以設(shè)置為幾天、幾小時甚至幾分鐘,并且它們的使用方式也因交易者而異(根據(jù)他們的情況和策略而定)。還可以向上或向下調(diào)整周期數(shù),以確定長期或短期趨勢。將周期值設(shè)置為20,是StochRSI指標一個相當受歡迎的選擇。
如上所述,某些StochRSI
圖表模式指定的范圍值為0到100而不是0到1。在這些圖表中,中心線為50而不是0.5。因此,通常在0.8處出現(xiàn)的超買信號將表示為80,而超賣信號表示為20而不是0.2。具有0-100設(shè)置的圖表可能看起來略有不同,但實際原理解釋是基本相同的。
二、如何使用StochRSI?
StochRSI指數(shù)如果出現(xiàn)在其范圍的上限和下限附近,此時的意義是最重大的。因此,該指標的主要用途是確定潛在的買入和賣出點,以及價格發(fā)生的逆轉(zhuǎn)。因此,0.2或以下的數(shù)值,會表明資產(chǎn)可能發(fā)生超賣,而0.8或以上的數(shù)值則表明該資產(chǎn)可能會發(fā)生超買。
此外,更接近中心線的數(shù)值也可以為交易者提供有關(guān)市場趨勢的信息。例如,當中心線作為支撐線并且StochRSI線穩(wěn)定移動到0.5以上時,尤其是數(shù)值趨近于0.8,則可能表明其繼續(xù)看漲或呈上升趨勢。同樣,當數(shù)值始終低于0.5,趨近于0.2時,則表明下跌或呈下降趨勢趨勢。
我們將通過 Python 中的回測來介紹 RSI
和 StochRSI
這兩種方法。
三、基于均值回歸的StochRSI 策略
最常見的 StochRSI
策略基于均值回歸。與 RSI 一樣,StochRSI
通常使用 80 來表示做空的超買水平,使用 20 來表示要買入的超賣水平。此外,14 天的回顧和平滑期很常見。出于我們的目的,我們將堅持使用這些標準值。
現(xiàn)在編寫代碼,讓我們在 Python 中導(dǎo)入一些標準包。
import numpy as np import pandas as pd import matplotlib.pyplot as plt import yfinance as yf
接下來,我們將構(gòu)建一個函數(shù)來計算我們的指標。我們將其稱為 calcStochRSI(),
它將依靠一些函數(shù)來計算 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
有了這些功能,我們只需要為我們的策略構(gòu)建邏輯就可以了。還要注意,我們有一個名為 calcReturns
的輔助函數(shù),我們可以快速將其應(yīng)用于回測的結(jié)果以從中獲取所有返回值。
這意味著回歸模型將在 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
和一些關(guān)鍵指標。我還添加了 RSI 以與更不穩(wěn)定的 StochRSI
進行比較。這導(dǎo)致交易頻繁,如果您的賬戶較小且交易成本相對較高,這可能會嚴重影響您的實際回報。我們只是在一個工具上運行它,所以最終進行了 443 筆交易,或者每 12 天交易一次,這看起來并不多。但是,如果我們要使用該指標管理適當?shù)墓ぞ呓M合并頻繁進行交易,我們每天可能會進出多筆交易,交易成本會變得很高。
# 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()
要查看整體策略的一些關(guān)鍵指標,讓我們看看使用以下 getStratStats
函數(shù)。
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 倍,而基礎(chǔ)資產(chǎn)的年度波動率大致相同。此外,根據(jù) Sortino
和 Sharpe
Ratios 衡量,我們有更好的風險調(diào)整回報。
在 2020 年的新冠疫情中,我們確實看到了均值回歸策略的潛在問題之一。該策略的總回報大幅下降,因為該策略的定位是向上回歸,但市場繼續(xù)低迷,該模型只是保持不變 . 它恢復(fù)了其中的一部分,但在這次測試中從未達到過疫情之前的高點。正確使用止損有助于限制這些巨大的損失,并有可能增加整體回報。
四、StochRSI 和動量策略
我們之前提到的另一個基本策略是使用 StochRSI
作為動量指標。當指標穿過中心線時,我們會根據(jù)其方向買入或做空股票。
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()
在這種情況下,我們的動量策略表現(xiàn)非常糟糕,在我們假設(shè)的時間段內(nèi)幾乎損失了我們所有的初始投資。
查看我們策略的統(tǒng)計數(shù)據(jù),該模型的唯一優(yōu)勢是比買入并持有方法的回撤時間略短。
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
不適合此類應(yīng)用。一次糟糕的回測并不意味著該策略毫無價值。相反,一個很好的回測并不意味著你有一些你應(yīng)該立即開始交易的東西。我們需要與其他指標結(jié)合使用以改善結(jié)果。
到此這篇關(guān)于利用 Python
實現(xiàn)隨機相對強弱指數(shù) StochRSI
的文章就介紹到這了,更多相關(guān)Python
實現(xiàn)隨機相對強弱指數(shù) StochRSI
內(nèi)容請搜索本站以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持本站!
版權(quán)聲明:本站文章來源標注為YINGSOO的內(nèi)容版權(quán)均為本站所有,歡迎引用、轉(zhuǎn)載,請保持原文完整并注明來源及原文鏈接。禁止復(fù)制或仿造本網(wǎng)站,禁止在非www.sddonglingsh.com所屬的服務(wù)器上建立鏡像,否則將依法追究法律責任。本站部分內(nèi)容來源于網(wǎng)友推薦、互聯(lián)網(wǎng)收集整理而來,僅供學(xué)習參考,不代表本站立場,如有內(nèi)容涉嫌侵權(quán),請聯(lián)系alex-e#qq.com處理。