QuantXでRCIを実装してみた

#今回やったこと
QuantXでRCIの指標を実装してみました。

#RCIとは
RCIは相場の過熱感を測り、現在の株価が割安か割高か判断するために用いられるテクニカル指標です。日付と株価それぞれに順位をつけ、両者の相関関係を評価します。
$$RCI = \Bigl(1 - \frac{6d}{n(n^2-1)}\Bigr) * 100$$
d:日付の順位と価格の差を2乗し、合計した数値
n:期間

例：

| 日付 | 終値 | 日付順位 | 価格順位 | 日付と価格の差の二乗 | d |
|:-----------------|:------------------|:------------------|:-----------------|:------------------|:------------------|
| 1/1 | 500円 | 5 | 5 | $(5-5)^2 = 0$ |   |
| 1/2 | 510円 | 4 | 4 | $(4-4)^2 = 0$ |   |
| 1/3 | 520円 | 3 | 3 | $(3-3)^2 = 0$ |   |
| 1/4 | 530円 | 2 | 2 | $(2-2)^2 = 0$ |   |
| 1/5 | 540円 | 1 | 1 | $(1-1)^2 = 0$ | 0 |

日付の順位は期間中の最新の日付を1とし、そこから遡るごとに2,3...とします。
価格の順位は、計算期間の中で最も終値が高いものを1とし、そこから高い順に2,3...としていきます。
上記の表では、RCI=100となります。

これを価格を逆にすると、

| 日付 | 終値 | 日付順位 | 価格順位 | 日付と価格の差の二乗 | d |
|:-----------------|:------------------|:------------------|:-----------------|:------------------|:------------------|
| 1/1 | 540円 | 5 | 1 | $(5-1)^2 = 16$ |   |
| 1/2 | 530円 | 4 | 2 | $(4-2)^2 = 4$ |   |
| 1/3 | 520円 | 3 | 3 | $(3-3)^2 = 0$ |   |
| 1/4 | 510円 | 2 | 4 | $(2-4)^2 = 4$ |   |
| 1/5 | 500円 | 1 | 5 | $(1-5)^2 = 16$ | 40 |

このとき、RCI=-100となります。

このようにRCIは-100~100の間を動く連続値となります。
100の場合、直近になるにつれて株価が上昇している状態にあり、上昇トレンドであることを意味します。
反対に、-100の場合、直近になるにつれて株価が下落している状態にあり、下落トレンドであることを意味します。
つまり、100に近づきつつあることは連日高値を更新していることを指し、買われすぎであることを表しています。
こうしてRCIの数値域を見ながら、売買シグナルを出していくのです。

#アルゴリズム
今回書いたコードは[こちら](https://factory.quantx.io/developer/093beb98670c4ea0b265f086b0ec34f9)

```python
import numpy as np
import pandas as pd

def initialize(ctx):
    # 設定
    ctx.logger.debug("initialize() called")
    ctx.configure(
      channels={          # 利用チャンネル
        "jp.stock": {
          "symbols": [
            "jp.stock.1305",
            "jp.stock.9984",
            "jp.stock.9983",
            "jp.stock.7201",
            "jp.stock.9201",
            "jp.stock.9202",
            "jp.stock.7203"
          ],
          "columns": [
            "close_price_adj"    # 終値(株式分割調整後) 
          ]
        }
      }
    )
    
    def get_rci(close, period):
      rank_period = np.arange(period, 0, -1)
      length = len(close)
      rci = np.zeros(length)
      for i in range(length):
          if i < period - 1:
              rci[i] = 0
          else :
              rank_price = close[i - period + 1: i + 1].rank(method='min', ascending = False).values
              rci[i] = (1 - (6 * sum((rank_period - rank_price)**2)) / (period**3 - period)) * 100
      return rci

    def _my_signal(data):
      cp = data["close_price_adj"].fillna(method='ffill')
      rci9 = pd.DataFrame(0, index = cp.index, columns = cp.columns)
      for (sym,val) in cp.items():
        rci9[sym] = get_rci(cp[sym], 9)
        
      buy_sig = rci9[(rci9 < -70)]
      sell_sig = rci9[(rci9 > 70)]
      
      return {
        "buy:sig":buy_sig,
        "sell:sig":sell_sig,
        "rci9:g2":rci9
      }

    # シグナル登録
    ctx.regist_signal("my_signal", _my_signal)

def handle_signals(ctx, date, current):
    '''
    current: pd.DataFrame
    '''

    done_syms = set([])
    for (sym,val) in ctx.portfolio.positions.items():
        returns = val["returns"]
        if returns < -0.03:
          sec = ctx.getSecurity(sym)
          sec.order(-val["amount"], comment="損切り(%f)" % returns)
          done_syms.add(sym)
        elif returns > 0.05:
          sec = ctx.getSecurity(sym)
          sec.order(-val["amount"], comment="利益確定売(%f)" % returns)
          done_syms.add(sym)


    buy = current["buy:sig"].dropna()
    for (sym,val) in buy.items():
        if sym in done_syms:
          continue
        
        sec = ctx.getSecurity(sym)
        sec.order(sec.unit() * 1, comment="SIGNAL BUY")
        ctx.logger.debug("BUY: %s,  %f" % (sec.code(), val))
        pass

    sell = current["sell:sig"].dropna()
    for (sym,val) in sell.items():
        if sym in done_syms:
          continue
    
        sec = ctx.getSecurity(sym)
        sec.order(sec.unit() * -1, comment="SIGNAL SELL")
        ctx.logger.debug("SELL: %s,  %f" % (sec.code(), val))
        pass

```

RCIを算出する関数がta-libで用意されていなかったので、[こちらの記事](http://lowcost-greatidea.jp/technicalanalysis/rci/)のコードを参照させていただきました。

今回は、RCI<-70の時に買い、RCI>70の時に売りのシグナルを出しています。
一般的には、80~100を買われすぎ、-80~-100を売られすぎと見ることが多いようです。