Help us understand the problem. What is going on with this article?

RSIっていう指標、長期短期で使ってみた

お前だれ

都内某T大学部4年、機械学習・統計手法でバイオインフォマ、ケモインフォマでケモケモしたりFinTechしようとしているSmartTrade社インターンのRic.です。

初心者がQuantX FactoryでRSI弄ってみた

「QuantX Factoryってなんだよ」って方はこちら

URL:QuantX Factoryホームページ

ここでいう初心者(私の事)のレベルとは

  • Python大学の授業で触った事あるくらい(若しくは触った事ない)
  • 金融?経済?株?高い時に売って安い時に買えばええんやろ?
  • データサイエンス? 完全に理解した(わかってない)

これくらいの感じで実際に資金が無いと出来ない訳ではなく
あくまでもシミュレーションなのでガチガチの初心者の方もご安心下さい。

早速本題行きます

今回はRSIという分析法を用いて株のシステムトレードアルゴリズムを
QuantX Factory上(以下の画面)で書いてシミュレーションしてみました。(実際自分で書いたのは10行位なのでご安心下さい)
QuantX Factoryの使い方についてはコチラ(会員登録は済ませて下さい)
スクリーンショット 2018-11-21 18.03.30.png

「RSIって何?」(コードがメインなのでザックリ行きます)

株取引における分析法の一つ

ざっくり言ってしまうと、その株の
買われすぎ・売られすぎなどの市場の過熱感を%で測るものです。

計算式としては以下のようになります。

$RSI = {\frac{A}{A+B}\times100}$ [%]

A:n日間の値上がり幅の合計
B:n日間の値下がり幅の合計

この値が50%を中心に動き
75%以上 → 買われすぎ → 下落する可能性が高い
30%以下 → 売られすぎ → 上昇する可能性が高い
と考え14日間で取ることが多いです。

イメージとしては下の画像のような感じ
(株)イチケン - 1847 (2018/08/30 - 2018/11/28)
スクリーンショット 2018-11-28 15.31.21.png

割と優秀で、市場が激しい値動き(トレンド)でない限り"短期中期に強い"効果的なオシレーター系分析の指標です。トレンド系分析(系についてはまた後日…)の指標と組み合わせて使うのがベター。
もっと詳しくRSIについて知りたい方はこちら(株の達人)

「これ使って何するの?」(本題)

”RSIの期間違いで組み合わせたらどうなる?”

この考えは割と素人の突拍子もない考え?で
基本RSIはその指標単独で使うより先述したように

”トレンド分析(SMA,MACDなど)と組み合わせるのがベター”

です。が。自分はあえて、
分析法はRSIのみ

”期間を変え二種のRSIでトレードを行う”
(短期、中期に強いなら期間長くしたらもっと強くなれる?)

という根拠のない自信の元、実装してみました。

実装してみる

まずは一種類のみの期間でトレード

実際のコードはこちら(Ric.のGithubに飛びます)

workshop.py
import pandas as pd
import talib as ta
import numpy as np

def initialize(ctx):
    # 設定
    ctx.logger.debug("initialize() called")
    ctx.configure(
      channels={          # 利用チャンネル
        "jp.stock": {
          "symbols": [
            "jp.stock.8840", #大京
            "jp.stock.7897", #ホクシン
            "jp.stock.6138", #ダイジェト
            "jp.stock.8685", #AIG
            "jp.stock.3289", #東急不HD
            "jp.stock.1847", #イチケン
            "jp.stock.9739", #NSW
            "jp.stock.6345", #アイチコーポ
            "jp.stock.7554", #幸楽苑HD
            "jp.stock.2908", #フジッコ
            "jp.stock.8860", #フジ住
            "jp.stock.6436", #アマノ
            "jp.stock.8011", #三陽商
            "jp.stock.8848", #レオパレス
            "jp.stock.7433", #伯東
            "jp.stock.3724", #ベリサーブ
          ],
          "columns": [
            #"open_price_adj",    # 始値(株式分割調整後)
            #"high_price_adj",    # 高値(株式分割調整後)
            #"low_price_adj",     # 安値(株式分割調整後)
            #"volume_adj",         # 出来高
            #"txn_volume",         # 売買代金
            "close_price",        # 終値
            "close_price_adj",    # 終値(株式分割調整後) 
          ]
        }
      }
    )

    def _my_signal(data):
      cp = data["close_price_adj"].fillna(method="ffill")
      rsi_S = pd.DataFrame(data=0,columns=[], index=cp.index)
      #rsi_L = pd.DataFrame(data=0,columns=[], index=cp.index)
      for (sym,val) in cp.items():
        rsi_S[sym] = ta.RSI(cp[sym].values.astype(np.double), timeperiod=9)
        #rsi_L[sym] = ta.RSI(cp[sym].values.astype(np.double), timeperiod=22)
        df_buy = rsi_S[(rsi_S < 15)]
        df_sell = rsi_S[(rsi_S > 85)]
        #条件にrsi_L  < 30, rsi_L > 70を追加

      return {
        "rsi_S": rsi_S,
        #"rsi_L": rsi_L,
        "buy:sig": df_buy,
        "sell:sig": df_sell
      }

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

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

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

    for (sym,val) in df_sell.items():
        sec = ctx.getSecurity(sym)
        sec.order_target_percent(0, comment="SIGNAL SELL")
        #ctx.logger.debug("SELL: %s,  %f" % (sec.code(), val))

一応条件文等の説明をしておきます。(42行目辺りから)

workshop.py
def _my_signal(data):
      cp = data["close_price_adj"].fillna(method="ffill")
      rsi_S = pd.DataFrame(data=0,columns=[], index=cp.index)
      #rsi_S用の容れ物を作ります。
      #rsi_L = pd.DataFrame(data=0,columns=[], index=cp.index)
      for (sym,val) in cp.items():
        rsi_S[sym] = ta.RSI(cp[sym].values.astype(np.double), timeperiod=9)
        #Ta-LibライブラリでRSIを9日間で取ります。
        #rsi_L[sym] = ta.RSI(cp[sym].values.astype(np.double), timeperiod=22)
        df_buy = rsi_S[(rsi_S < 15)]
        #9日間のRSIが15%未満になったら買い
        df_sell = rsi_S[(rsi_S > 85)]
        #9日間のRSIが85%より大きくなったら売り

RSIの期間として今回は9日間です。
全体のコードを新規プロジェクトにコピーして貼り付け(ペースト)して
コピペめんどい!って方はこちら
スクリーンショット 2018-11-28 16.27.51.png
以上のようにバックテストをすればとりあえずは動くかと思います。
RSI自体の実装はこちらを参照下さい(My teacher, Kozzyさんの神スライド)

結果はこちら
スクリーンショット 2018-11-28 16.26.55.png

2015/11/29 ~ 2018/11/28の3年間、初期資金量100万
損益率+18.56%(+18万)
ぼちぼちと言ったところでしょうか

ついに無謀な挑戦へ。

今回は以下の2つの期間でトレードします。

  • 短期?の方は9日間(先ほど実装済み)
  • 長期の方は22日間(これを実装していきます)

まずはデータの容れ物を用意

45行目の#を外して有効なコードにしましょう。
(#はコメントアウトといいその行のコードを無効化する役割があります)

workshop.py
def _my_signal(data):
      cp = data["close_price_adj"].fillna(method="ffill")
      rsi_S = pd.DataFrame(data=0,columns=[], index=cp.index)
      rsi_L = pd.DataFrame(data=0,columns=[], index=cp.index) #ココ
      #rsi_L用の容れ物を用意する

長期のRSIを適用

48行目の#を外し、22日間のRSIを有効化します。

workshop.py
        rsi_L[sym] = ta.RSI(cp[sym].values.astype(np.double), timeperiod=22)

条件の更新

  • 9日間(短期)のRSIが15%より低く かつ22日間(長期)のRSIが30%より低い →買い
  • 9日間(短期)のRSIが85%より高く かつ22日間(長期)のRSIが70%より高い →売り

と言う条件に書き換えます。(49,50行目)

before

workshop.py
        df_buy = rsi_S[(rsi_S < 15)]
        df_sell = rsi_S[(rsi_S > 85)]

after

workshop.py
        df_buy = rsi_S[(rsi_S < 15) & (rsi_L < 30)]
        df_sell = rsi_S[(rsi_S > 85) & (rsi_L > 70)]

(&:かつ、|:または)
注.各条件を()でくくらないとエラーが出るので注意!

rsi_Lの可視化

最後にrsi_Lを以下の様に”見える化”しておきましょう。
(55行目の#を削除し有効化)

スクリーンショット 2018-11-28 19.06.47.png

workshop.py
        return {
        "rsi_S": rsi_S,
        "rsi_L": rsi_L,
        "buy:sig": df_buy,
        "sell:sig": df_sell
      }

ちなみにここでは

  • rsi_S
  • rsi_L
  • df_buy “買うタイミング”
  • df_sell “売るタイミング”

これらが

  • “rsi_S”
  • “rsi_L”
  • “buy:sig”
  • “sell:sig”

として以上の様に見える(関数外で使える)ようになっています。

ここでようやく完成です。

workshop.py
import pandas as pd
import talib as ta
import numpy as np

def initialize(ctx):
    # 設定
    ctx.logger.debug("initialize() called")
    ctx.configure(
      channels={          # 利用チャンネル
        "jp.stock": {
          "symbols": [
            "jp.stock.8840", #大京
            "jp.stock.7897", #ホクシン
            "jp.stock.6138", #ダイジェト
            "jp.stock.8685", #AIG
            "jp.stock.3289", #東急不HD
            "jp.stock.1847", #イチケン
            "jp.stock.9739", #NSW
            "jp.stock.6345", #アイチコーポ
            "jp.stock.7554", #幸楽苑HD
            "jp.stock.2908", #フジッコ
            "jp.stock.8860", #フジ住
            "jp.stock.6436", #アマノ
            "jp.stock.8011", #三陽商
            "jp.stock.8848", #レオパレス
            "jp.stock.7433", #伯東
            "jp.stock.3724", #ベリサーブ
          ],
          "columns": [
            #"open_price_adj",    # 始値(株式分割調整後)
            #"high_price_adj",    # 高値(株式分割調整後)
            #"low_price_adj",     # 安値(株式分割調整後)
            #"volume_adj",         # 出来高
            #"txn_volume",         # 売買代金
            "close_price",        # 終値
            "close_price_adj",    # 終値(株式分割調整後) 
          ]
        }
      }
    )

    def _my_signal(data):
      cp = data["close_price_adj"].fillna(method="ffill")
      rsi_S = pd.DataFrame(data=0,columns=[], index=cp.index)
      rsi_L = pd.DataFrame(data=0,columns=[], index=cp.index)
      for (sym,val) in cp.items():
        rsi_S[sym] = ta.RSI(cp[sym].values.astype(np.double), timeperiod=9)
        rsi_L[sym] = ta.RSI(cp[sym].values.astype(np.double), timeperiod=22)
        df_buy = rsi_S[(rsi_S < 15) & (rsi_L < 30)]
        df_sell = rsi_S[(rsi_S > 85) & (rsi_L > 70)]

      return {
        "rsi_S": rsi_S,
        "rsi_L": rsi_L,
        "buy:sig": df_buy,
        "sell:sig": df_sell
      }

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

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

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

    for (sym,val) in df_sell.items():
        sec = ctx.getSecurity(sym)
        sec.order_target_percent(0, comment="SIGNAL SELL")
        #ctx.logger.debug("SELL: %s,  %f" % (sec.code(), val))

最終的にはこんな感じのコードになります。
それでは早速シミュレーション(バックテスト)をしてみましょう。

結果(before&after)

結果をRSI、1期間の物と2期間の物で比較してみましょう。

1期間の物

スクリーンショット 2018-11-28 16.26.55.png

2期間の物

スクリーンショット 2018-11-29 11.10.32.png

2015/11/29 ~ 2018/11/28の3年間、初期資金量100万

  • 1期間:損益率+18.56%(+18万)
  • 2期間:損益率+35.56%(+35万)

ここでは損益率のみに言及していますが個人的に注目したのは
MaxDrawdown(期間内での最大の損出率)の減少です。
(0に近い方がいいです)

  • 1期間:MaxDrawdown -22.8%(最大22.8%の損出)
  • 2期間:MaxDrawdown -12.2%(最大12.2%の損出)

と10%以上の減少になっています。
1期間加えただけで損益率が+17%、MaxDrawdownが10%減
と言う一石二鳥(本当は他の値もみるべきですがまた後日…)な物でした。

終わりに

今回はこれで終わりですが、完成した2期間のRSIの以下の数字を条件や期間を変えるだけで(コード自体を書き換える必要なし)

workshop.py
        rsi_S[sym] = ta.RSI(cp[sym].values.astype(np.double), timeperiod=9) 
        rsi_L[sym] = ta.RSI(cp[sym].values.astype(np.double), timeperiod=22)
        #ココのtimeperiod

        df_buy = rsi_S[(rsi_S < 15) & (rsi_L < 30)] #ココの条件の数字
        df_sell = rsi_S[(rsi_S > 85) & (rsi_L > 70)] #ココの条件の数字

より改善された結果となります。
スクリーンショット 2018-11-29 11.57.53.png

損益率:+63.90%
MaxDrawdown:-9.8%

また以下の株の銘柄の選定を変える事でも結果が大きく変わってきます。
(銘柄選定の仕方については勉強中ですのでお待ち下さい…)

workshop.py
"jp.stock": {
          "symbols": [
            "jp.stock.8840", #大京
            "jp.stock.7897", #ホクシン
            "jp.stock.6138", #ダイジェト
            "jp.stock.8685", #AIG
            "jp.stock.3289", #東急不HD
            "jp.stock.1847", #イチケン
            "jp.stock.9739", #NSW
            "jp.stock.6345", #アイチコーポ
            "jp.stock.7554", #幸楽苑HD
            "jp.stock.2908", #フジッコ
            "jp.stock.8860", #フジ住
            "jp.stock.6436", #アマノ
            "jp.stock.8011", #三陽商
            "jp.stock.8848", #レオパレス
            "jp.stock.7433", #伯東
            "jp.stock.3724", #ベリサーブ
          ],

以上の様にコードそんなにゴリゴリ書けないと言う場合は最初は今回の様な
サンプルコードの数字などだけいじって形から入って行きましょう!
(自分も最初はそうでした。)

宣伝

勉強会やってるよ

日時:毎週金曜日19時〜
場所:神田 千代田共同ビル4階 SmartTrade社オフィス
内容:初心者(プログラミングってものを知らなくてもOK)向けに初心者(私とか)がこんな内容をハンズオン(一緒にやる事)で解説しています
備考:猛者の方も是非御鞭撻にいらして下さい、そして開発・伝導者になりましょう

もくもく会もやってるよ

日時:毎週水曜日18時〜
場所:神田 千代田共同ビル4階 SmartTrade社オフィス
内容:基本黙々と自習しながら猛者の方に質問して強くなっていく会
備考:お菓子と終わりにお酒が出るよ

詳細はこちらだよ

Pythonアルゴリズム勉強会HP:https://python-algo.connpass.com/
(conpassって言うイベントサイトに飛びます)

センチメントデータって言うのを使った奴も解説してるよ

QuantX Factoryでセンチメントデータいじってみた

免責注意事項

このコードや知識を使った実際の取引で生じた損益に関しては一切の責任を負いかねますので御了承下さい。

Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away