数字货币量化之 Catalyst 相对强弱指数 (RSI) 策略

策略原理

  • 根据一定时期内(通常为14个周期,针对股票)上涨点数之和下跌点数之和的比率制作出的一种技术曲线
  • 任何市价的大涨或大跌,均在0-100之间变动,根据常态分配,认为RSI值多在 30-70之间变动
  • 当价格低跌至30以下即被认为是超卖状态,价格即将出现上涨。
  • 当价格升至70以上即被认为是超买状态,价格即将出现下跌。

上边的计算方法简单来讲就是一定周期内,假设为过去14天,我们把这14天内上涨的点数加起来,把下跌的也加起来,然后根据这两个值,就可以算出RSI了。

策略逻辑

  • 空仓状态下,RSI值上穿(大于)超卖阀值,且该数字货币可以交易,满仓买入。
  • 持仓状态下,RSI值下穿(小于)超买阀值,且该数字货币可以交易,满仓卖出。

file

计算公式

file

file

开发准备

在jupyter notebook中使用虚拟环境

  • 启用虚拟环境
    • windows: activate catalyst
    • Mac OS: source activate catalyst
  • pip install ipykemel
  • python -m ipykemel install --user --name=catalyst
  • jupyter kemelspec list

安装TA-Lib(Technical Analysis Library)

代码实战

rsi_alg.py

import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import talib

from catalyst import run_algorithm
from catalyst.api import record, symbol, order_target_percent
from catalyst.exchange.utils.stats_utils import extract_transactions

# 需要先加载数据
# catalyst ingest-exchange -x binance -i btc_usdt -f daily

NAMESPACE = 'relative_strength_index'
SIGNAL_BUY = 'buy'           # 买入信号
SIGNAL_SELL = 'sell'         # 卖出信号
SIGNAL_INIT = ''             # 观望信号
RSI_PERIODS = 7              # RSI计算周期,如果周期比较短,对价格比较敏感
RSI_OVER_SOLD_THRESH = 25    # 超卖阈值 30,20
RSI_OVER_BOUGHT_THRESH = 75  # 超买阈值 70, 80

def initialize(context):
    """
        初始化
    """
    context.i = 0                       # 经历过的交易周期
    context.asset = symbol('btc_usdt')  # 交易对
    context.base_price = None           # 初始价格
    context.signal = SIGNAL_INIT  # 交易信号
    context.set_commission(maker=0.001, taker=0.001)    # 设置手续费
    context.set_slippage(slippage=0.001)                # 设置滑点

def handle_data(context, data):
    """
        在每个交易周期上运行的策略
    """
    context.i += 1  # 记录交易周期
    if context.i < RSI_PERIODS + 3:
        # 如果交易周期过短,无法计算RSI,则跳过循环
        return

    # 获得历史价格
    hitory_data = data.history(context.asset,
                               'close',
                               bar_count=RSI_PERIODS + 3,
                               frequency='1D',
                               )
    # 获取当前持仓数量
    pos_amount = context.portfolio.positions[context.asset].amount

    # 计算RSI
    rsi_vals = talib.RSI(hitory_data, timeperiod=RSI_PERIODS)

    # RSI 交易策略
    if (rsi_vals[-3] <= RSI_OVER_SOLD_THRESH) and (rsi_vals[-2] >= RSI_OVER_SOLD_THRESH) and pos_amount == 0:
        # RSI值上穿超卖阈值,买入
        order_target_percent(context.asset, 1)
        context.signal = SIGNAL_BUY

    if (rsi_vals[-3] >= RSI_OVER_BOUGHT_THRESH) and (rsi_vals[-2] <= RSI_OVER_BOUGHT_THRESH) and pos_amount > 0:
        # RSI值下穿超卖阈值,卖出
        order_target_percent(context.asset, 0)
        context.signal = SIGNAL_SELL

    # 获取当前的价格
    price = data.current(context.asset, 'price')
    if context.base_price is None:
        # 如果没有设置初始价格,将第一个周期的价格作为初始价格
        context.base_price = price

    # 计算价格变化百分比,作为基准
    price_change = (price - context.base_price) / context.base_price

    # 记录每个交易周期的信息
    # 1. 价格, 2. 现金, 3. 价格变化率, 4. 快线均值, 5. 慢线均值
    record(price=price,
           cash=context.portfolio.cash,
           price_change=price_change,
           rsi=rsi_vals[-1],
           signal=context.signal)
    # 输出信息
    print('日期:{},价格:{:.4f},资产:{:.2f},持仓量:{:.8f}, {}'.format(
        data.current_dt, price, context.portfolio.portfolio_value, pos_amount, context.signal))

    # 进行下一次交易前重置交易信号
    context.signal = SIGNAL_INIT

def analyze(context, perf):
    # 保存交易记录
    perf.to_csv('./rsi_performance.csv')

    # 获取交易所的计价货币
    exchange = list(context.exchanges.values())[0]
    quote_currency = exchange.quote_currency.upper()

    # 图1:可视化资产值
    ax1 = plt.subplot(411)
    perf['portfolio_value'].plot(ax=ax1)
    ax1.set_ylabel('Portfolio Value\n({})'.format(quote_currency))
    start, end = ax1.get_ylim()
    ax1.yaxis.set_ticks(np.arange(start, end, (end - start) / 5))

    # 图2:可视化货币价格,RSI和买入卖出点
    ax2 = plt.subplot(412, sharex=ax1)
    perf[['price', 'rsi']].plot(ax=ax2)
    ax2.set_ylabel('{asset}\n({quote})'.format(
        asset=context.asset.symbol,
        quote=quote_currency
    ))
    start, end = ax2.get_ylim()
    ax2.yaxis.set_ticks(np.arange(start, end, (end - start) / 5))

    # 提取交易时间点
    transaction_df = extract_transactions(perf)
    if not transaction_df.empty:
        buy_df = transaction_df[transaction_df['amount'] > 0]   # 买入点
        sell_df = transaction_df[transaction_df['amount'] < 0]  # 卖出点
        ax2.scatter(
            buy_df.index.to_pydatetime(),
            perf.loc[buy_df.index, 'price'],
            marker='^',
            s=100,
            c='green',
            label=''
        )
        ax2.scatter(
            sell_df.index.to_pydatetime(),
            perf.loc[sell_df.index, 'price'],
            marker='v',
            s=100,
            c='red',
            label=''
        )

    # 图3:比较价格变化率和资产变化率
    ax3 = plt.subplot(413, sharex=ax1)
    perf[['algorithm_period_return', 'price_change']].plot(ax=ax3)
    ax3.set_ylabel('Percent Change')
    start, end = ax3.get_ylim()
    ax3.yaxis.set_ticks(np.arange(start, end, (end - start) / 5))

    # 图4:可视化现金数量
    ax4 = plt.subplot(414, sharex=ax1)
    perf['cash'].plot(ax=ax4)
    ax4.set_ylabel('Cash\n({})'.format(quote_currency))
    start, end = ax4.get_ylim()
    ax4.yaxis.set_ticks(np.arange(0, end, end / 5))

    plt.tight_layout()
    plt.show()

if __name__ == '__main__':
    run_algorithm(
        capital_base=1000,
        data_frequency='daily',
        initialize=initialize,
        handle_data=handle_data,
        analyze=analyze,
        exchange_name='binance',
        algo_namespace=NAMESPACE,
        quote_currency='usdt',
        start=pd.to_datetime('2019-02-01', utc=True),
        end=pd.to_datetime('2019-12-22', utc=True)
    )

file

输出打印:

[2019-12-23 13:12:57.347171] INFO: exchange_algorithm: initialized trading algorithm in backtest mode
日期:2019-01-12 23:59:00+00:00,价格:3583.1300,资产:1000.00,持仓量:0.00000000, 
日期:2019-01-13 23:59:00+00:00,价格:3476.8100,资产:1000.00,持仓量:0.00000000, 
日期:2019-01-14 23:59:00+00:00,价格:3626.0900,资产:1000.00,持仓量:0.00000000, 
日期:2019-01-15 23:59:00+00:00,价格:3553.0600,资产:1000.00,持仓量:0.00000000, buy
日期:2019-01-16 23:59:00+00:00,价格:3591.8400,资产:997.98,持仓量:0.28144754, 
日期:2019-01-17 23:59:00+00:00,价格:3616.2100,资产:1004.84,持仓量:0.28144754, 
日期:2019-01-18 23:59:00+00:00,价格:3594.8700,资产:998.83,持仓量:0.28144754, 
...
日期:2019-12-17 23:59:00+00:00,价格:6623.8200,资产:1736.18,持仓量:0.26638699, 
日期:2019-12-18 23:59:00+00:00,价格:7277.8300,资产:1910.40,持仓量:0.26638699, 
日期:2019-12-19 23:59:00+00:00,价格:7150.3000,资产:1876.42,持仓量:0.26638699, 
日期:2019-12-20 23:59:00+00:00,价格:7187.8300,资产:1886.42,持仓量:0.26638699, 

通过对交易周期,超买,超卖的参数调整,可以看到RSI策略收益还不错。

为者常成,行者常至