RSI指标回测实战

photo by Chris Liverani on Unsplash

本文重点概要

  • 文章难度:★★☆☆☆
  • RSI指标策略的表现
  • 阅读建议:本文以 RSI作为交易策略的判断依据,以视觉化的方式观察交易讯号以及买卖点,接著再利用函数计算出报酬率,做出 RSI指标交易的回测。如果对回测或是技术指标不了解的读者,欢迎先行阅读 技术分析简介与回测,详细理解回测的执行流程。

前言

RSI是判断股市当中买卖双方力道强弱的动量技术指标,综合考量上涨下跌的幅度和天数,评估市场买卖超的情形,决定进出场的策略。RSI指标的计算流程如下:

  1. 依据隔日价差,计算 n日内平均的涨跌幅
  2. 计算相对强度(RS):n日内涨幅平均值 / n日内跌幅平均值
  3. 计算相对强弱指标(RSI):相对强度(RS) / (1 + 相对强度(RS)) × 100

RSI指标的判断依据如下:

  1. RSI < 30为卖超情形,RSI > 70为买超情形,RSI在50之间波动为买卖方力量持平,上述(30,70)组合也有较保守的(20,80)判断依据。
  2. RSI指标背离,也就是RSI走势与股价走势不一致,代表市场将出现反转,是买卖讯号的一个依据。
  3. 黄金交叉,短天期 RSI向上突破长天期 RSI,代表市场即将进入多头。
  4. 死亡交叉,短天期 RSI向下突破长天期 RSI,代表市场即将进入空头。

RSI的指标钝化问题:由于 RSI是将涨、跌分开来处理,因此若市场在短期内皆为涨势,则会使跌幅的参考数据过于稀少,导致 RSI指标判断失真,反之亦然。钝化的情形尤其容易发生在高低档阶段,而本文也尝试以综合 RSI区间及长短天期 RSI交叉的方式降低钝化影响。

编辑环境及模组需求

本文使用 MacOS 并以 Jupyter Notebook 作为编辑器

# 基本套件 import pandas as pd import numpy as np import copy # 视觉化套件 import plotly.graph_objects as go import plotly.express as px # TEJ API import tejapi tejapi.ApiConfig.api_key = 'Your Key' tejapi.ApiConfig.ignoretz = True

资料库使用

资料处理

Step 1. 股价资料捞取

stock_data = tejapi.get('TWN/APRCD',            coid= '2324', # 仁宝            mdate={'gte': '2020-01-01', 'lte':'2021-12-14'},            opts={'columns': ['coid', 'mdate', 'open_d', 'close_d']},            chinese_column_name=True,            paginate=True)

本文以仁宝(2324)从2020年初至今(2021–12–14)的资料范围作为示范,借此了解笔记型电脑代工厂商在疫情下的股价波动,以及如何使用RSI,决定进出场时机。

 资料表(一)

Step 2. 计算14日RSI、7日RSI

# 计算隔日价差以及分类每日涨跌 stock_data['隔日差价'] = stock_data['收盘价(元)'].diff() stock_data['上涨'] = stock_data['隔日差价'].clip(lower = 0) stock_data['下跌'] = (-1) * stock_data['隔日差价'].clip( upper = 0) # 14日RSI指标 stock_data['14日上涨均值'] = stock_data['上涨'].ewm(com = 14, adjust = False).mean() stock_data['14日下跌均值'] = stock_data['下跌'].ewm(com = 14, adjust = False).mean() stock_data['14日相对强弱值'] = stock_data['14日上涨均值'] / stock_data['14日下跌均值'] stock_data['14日相对强弱指标'] = stock_data['14日相对强弱值'].apply(lambda rs : rs/(1+rs)*100) # 7日RSI指标 stock_data['7日上涨均值'] = stock_data['上涨'].ewm(com = 7, adjust = False).mean() stock_data['7日下跌均值'] = stock_data['下跌'].ewm(com = 7, adjust = False).mean() stock_data['7日相对强弱值'] = stock_data['7日上涨均值'] / stock_data['7日下跌均值'] stock_data['7日相对强弱指标'] = stock_data['7日相对强弱值'].apply(lambda rs : rs/(1+rs)*100)
 资料表(二)

Step 3. RSI指标走势图 & 买卖超区间(30,70)(详见完整程式码)

透过比较RSI长短天期走势以及买卖超区间,找到黄金交叉以及死亡交叉。需注意的是高档阶段仍有较严重的钝化问题,但仍要在第一次死亡交叉时就出场,毕竟在实际投资的时候,无法绝对精准得预测未来。

图(一)

Step 4. 找出买卖讯号与视觉化买卖点(详见完整程式码)

卖超阶段:RSI < 30,准备买入;此外,降低RSI在低档的钝化影响,增加一项需要7日RSI向上突破14日RSI的条件,作为价格将要反转的黄金交叉依据。

买超阶段:RSI > 70,准备卖出;此外,降低RSI在高档的钝化影响,增加一项需要7日RSI向下突破14日RSI的条件,作为价格将要反转死亡交叉依据。

signal = []
trade = 0
# 交易讯号只发生在买卖超阶段与黄金交叉或死亡交叉同时发生的情境下
for i in range(len(stock_data)):
    if stock_data.loc[i, '14日相对强弱指标']  <= 30 and stock_data.loc[i-1, '14日相对强弱指标'] > stock_data.loc[i-1, '7日相对强弱指标'] and stock_data.loc[i, '14日相对强弱指标'] <= stock_data.loc[i, '7日相对强弱指标'] and trade == 0:
        signal.append(1000)
        trade = trade + 1
    elif stock_data.loc[i, '14日相对强弱指标'] >= 70 and stock_data.loc[i-1, '14日相对强弱指标'] < stock_data.loc[i-1, '7日相对强弱指标'] and stock_data.loc[i, '14日相对强弱指标'] >= stock_data.loc[i, '7日相对强弱指标'] and trade == 1:
        signal.append(-1000)
        trade = trade - 1
    else:
        signal.append(0)
stock_data['买卖股数'] = signal
图(二)

计算报酬

MACD指标回测实战里,我们有详细介绍考虑手续费、现金部位计算报酬率的方式,因为流程固定,本文以下方函数包装。

def target_return(data, principal):     #计算成本     data['手续费'] = data['开盘价(元)']* abs(data['买卖股数'])*0.001425     data['手续费'] = np.where((data['手续费']>0)&(data['手续费'] <20), 20, data['手续费'])     data['证交税'] = np.where(data['买卖股数']<0, data['开盘价(元)']* abs(data['买卖股数'])*0.003, 0)     data['摩擦成本'] = (data['手续费'] + data['证交税']).apply(np.floor)     #计算资产价值     data['股票价值'] = data['买卖股数'].cumsum() * data['收盘价(元)']     data['现金价值'] = principal - data['摩擦成本'] + (data['开盘价(元)']* -data['买卖股数']).cumsum()      data['资产价值'] = data['股票价值'] + data['现金价值']     #计算报酬率     data['当日价值变动(%)'] = (data['资产价值']/data['资产价值'].shift(1) - 1)*100     data['累计报酬(%)'] = (data['资产价值']/principal - 1)*100     return data

本文单纯考量一般股票买卖,并无考虑融券融资情形,所以没有计算保证金的流程,只要将买卖股数的资料整理好,并决定期初的现金部位,即可算出交易成本以及报酬率。本文选择投入20000元

RSI_return = target_return(data = stock_data, principal = 20000)
资料表(三)

加入买入持有策略以及大盘绩效,作为此次RSI交易策略的比较基准。

・买入持有策略

bh_data = copy.deepcopy(stock_data) bh_data['买卖股数'] = 0 bh_data.loc[0, '买卖股数'] = 1000 bh_data.loc[len(bh_data)-1, '买卖股数'] = -1000 # 去除RSI指标策略才会用到的参数 bh_data = bh_data.drop(['隔日差价', '上涨', '下跌', '14日上涨均值', '14日下跌均值', '14日相对强弱值', '14日相对强弱指标','7日上涨均值', '7日下跌均值', '7日相对强弱值', '7日相对强弱指标'], axis = 1) bh_return = target_return(data=bh_data, principal = 20000)
资料表(四)

・大盘绩效

market = tejapi.get('TWN/APRCD',
                   coid = 'Y9997',
                   mdate = {'gte':'2020-01-01', 'lte':'2021-12-14'},
                   opts = {'columns':['coid','mdate',   'close_d','roi']},
                   chinese_column_name = True)
market['累计报酬(%)'] = (market['报酬率%'].apply(lambda x:0.01*x +1).cumprod() - 1)*100

以市场指数报酬(Y9997)代表市场的表现,并计算出累计报酬率。

资料表(五)

报酬绩效比较

Step 1. 累计报酬比较(详见完整程式码)

图(三)

从累计报酬率知道RSI策略交易在疫情爆发后的低档阶段进场,并且在市场卖超电子股前的高档阶段出场(因大宗商品上涨而市场转投入传产股),此外,也刚好避开台湾疫情彻底失守对股市的影响,可是在这之间还是有一些相对高低档的波段,因为交易讯号的触发标准较严格,而没有抓准。

Step 2. 累计报酬比较(详见完整程式码)

资料表(六)

透过绩效比较可以发现运用RSI的交易策略,在报酬率或波动度上的表现都优于买入持有,代表RSI是有机会找到适当的进出场时机,提高投资效益;此外,跟大盘绩效的比较,RSI策略没有突出的报酬率,但是在波动度上则更加稳定,这是因为避开2020年初的全球股灾以及2021年台湾本土疫情失守的空头阶段,让RSI策略的夏普值优于大盘绩效。

结论

借由上述内容,读者应该可以发现RSI在高低档的钝化情形相当严重,所以本文加入长短天期交叉的条件,使RSI策略更有稳健性,当然也相对牺牲更多的交易机会,这是个别投资人需要自己去衡量的结果,毕竟技术指标的运用因人而异,所以欢迎对各种交易回测有兴趣的读者,选购TEJ E-Shop的相关方案,用高品质的资料库,建构出适合自己的交易策略。

完整程式码

延伸阅读

相关连结

返回总览页