LoginSignup
1
0

More than 5 years have passed since last update.

ブル・ベアファンド自動裁定 〜QuantX APIでリスク回避・保有日数編〜

Last updated at Posted at 2019-03-31

115 [更新済み]-01.jpg

導入

人気のあるETFブルベア型の取引の自動裁定システムをQuantX Factory上で開発します。
シリーズ化していますので

「QuantXって何?」
「ブルベア取引って?」
「まず名を名乗れよ。」

って方は以下のURLの1を参照下さい。

  1. ブル・ベアファンド自動裁定 〜はじめの一歩〜
  2. ブル・ベアファンド自動裁定 〜利確損切り機能実装〜
  3. ブル・ベアファンド自動裁定 〜QuantX APIで買い増し機能実装〜(前回)

今回の目標

今回は取引結果の優劣はおいておくとして以下を目標とします。

前回の完成コードに保有日数に制限を付けリスク回避?をする
前回のコードはこちら

具体的には以下のようなアルゴリズムです

  • 保有日数をカウントし指定の日数に達したら売りをかける

何故実装するのか?

基本的にブルベアETFはその特性上値動きが激しく、長期で保有する事はあまり得策とは言えません。
この長期保有のリスクを回避?する為に指定した日数より保有しないアルゴを実装します。

早速やっていく

一応コードの全体像を眺めておきましょう、今回はこのコードの

initialize
handle_signals

の部分をいじって行く形になります。
※前回のコードでctx.localStorageだった物を便宜上ctx.flagsに改名しています。
リンク先との多少のコードのズレは許容ください。

コードの全体像
#必要なライブラリの読み込み
import numpy as np
import pandas as pd
import talib as ta

#RCIを計算する関数
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 initialize(ctx):
    # 設定
    ctx.logger.debug("initialize() called")
    ctx.configure(
      channels={          # 利用チャンネル
            "jp.stock": {
                "symbols": [
                    "jp.stock.1321", #日経225連動型上場投資信託
                    "jp.stock.1357", #日経ダブルインバース上場投信 bear
                    "jp.stock.1570", #日経平均レバレッジ上場投信 bull
                ],
                "columns": [
                  "close_price_adj"
                ]
            }

      }
    )
    ctx.flags = {
      "jp.stock.1570":0,
      "jp.stock.1357":0
    }

    def _my_signal(data):
      cp = data["close_price_adj"].fillna(method='ffill')
      # ctx.logger.debug(cp)
      rci9 = pd.DataFrame(0, index=cp.index, columns=cp.columns)
      rsi7 = pd.DataFrame(0,index=cp.index, columns=cp.columns)

      for (sym,val) in cp.items():
        rci9[sym]=get_rci(cp[sym], 9)
        rsi7[sym] = ta.RSI(cp[sym].values.astype(np.double), timeperiod=7)
      # ctx.logger.debug(rci9)
      # ctx.logger.debug(rsi7) 
      buy_sig= (rci9 < -80) | (rsi7 < 30)
      sell_sig= (rci9 > 80) | (rsi7 > 70)

      # ctx.logger.debug(buy_sig)
      # ctx.logger.debug(buy_sig.shift(1))
      return {
        "incbull:sig":buy_sig.shift(1),
        "incbear:sig":sell_sig.shift(1),
        "buy:sig":buy_sig,
        "sell:sig":sell_sig,
        "rci9:g2":rci9,
        "rsi7:g2":rsi7,
      }

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

def handle_signals(ctx, date, current):
    '''
    current: pd.DataFrame
    '''
    done_syms = set([])

    # ctx.logger.debug(current)
    # ctx.logger.debug(ctx.flags["bulldone"])
    bear = ctx.getSecurity("jp.stock.1357")
    bull = ctx.getSecurity("jp.stock.1570")
    df_buy = current["buy:sig"][0]
    df_sell = current["sell:sig"][0]
    df_inc_bull = current["incbull:sig"][0]
    df_inc_bear = current["incbear:sig"][0]

    #利確損切り
    for (sym,val) in ctx.portfolio.positions.items():
        if (sym == 'jp.stock.1321') | ('jp.stock.1357' in done_syms) | ('jp.stock.1570' in done_syms):
          continue
        returns = val["returns"]
        if returns > 0.05:
          sec = ctx.getSecurity(sym)
          sec.order(-val["amount"], comment="利益確定売(%f)" % returns)
          ctx.flags[sym] = 0
          done_syms.add(sym)
          # ctx.logger.debug(ctx.flags[sym])
        elif returns < -0.01:
          sec = ctx.getSecurity(sym)
          sec.order(-val["amount"], comment="損切り(%f)" % returns)
          ctx.flags[sym] = 0
          done_syms.add(sym)
    # 買いシグナル
    # ctx.logger.debug(df_buy)
    # ctx.logger.debug(ctx.flags["jp.stock.1570"])
    if df_buy & df_inc_bull & (ctx.flags["jp.stock.1570"] == 1):
      if not ('jp.stock.1357' in done_syms) | ('jp.stock.1570' in done_syms):
        bull.order(bull.unit() * 10, comment="BULL買い増し")
        done_syms.add("jp.stock.1570")
    if df_buy & (ctx.flags["jp.stock.1570"] == 0):
      if not ('jp.stock.1357' in done_syms) | ('jp.stock.1570' in done_syms):
        bull.order_target_percent(0.50, comment="BULL BUY")
        bear.order_target_percent(0, comment="BEAR SELL")
        done_syms.add("jp.stock.1570")
        ctx.flags["jp.stock.1570"] = 1
        ctx.flags["jp.stock.1357"] = 0

    # 売りシグナル
    if df_sell & df_inc_bear & (ctx.flags["jp.stock.1357"] == 1):
      if not ('jp.stock.1357' in done_syms) | ('jp.stock.1570' in done_syms):
        bear.order(bear.unit() * 10, comment="BEAR買い増し")
        done_syms.add("jp.stock.1357")
    if df_sell & (ctx.flags["jp.stock.1357"] == 0):
      if not ('jp.stock.1357' in done_syms) | ('jp.stock.1570' in done_syms):
        bear.order_target_percent(0.50, comment="BEAR BUY")
        bull.order_target_percent(0, comment="BULL SELL") 
        done_syms.add("jp.stock.1357")
        ctx.flags["jp.stock.1357"] = 1
        ctx.flags["jp.stock.1570"] = 0

各銘柄のカウント用の辞書を用意

各銘柄の保有日数を保存するための辞書をinitialize内で以下の様に定義します。

    ctx.counts = {
      "jp.stock.1570":0,
      "jp.stock.1357":0
    }

    ctx.count_border = 4

これをhandle_signal内で呼び出し、処理する事で保有日数を管理します。

ちなみにctx.count_borderは保有制限日数のを指定する変数で今回は4、つまり4日以上持ったらダメ、全売りということになります。

保有日数をカウントする

保有日数を保存する辞書が用意できたので実際にカウントしていきます。

以下のコードをhandle_signalの最後に追加します。

    for (sym,val) in ctx.portfolio.positions.items():
      if val["amount"] > 0:
        ctx.counts[sym] = ctx.counts[sym] + 1

これは何をしているかというと

全ての銘柄に対して売買処理を行った後にまだその銘柄を保有していた場合(val["amount"]が0より大きい)
ctx.counts[sym] = ctx.counts[sym] + 1としてカウントするという形になっています。

指定した日数に達した物の売り注文をする

カウントができる様になったのでカウントした数と指定した保有制限日数ctx.count_borderを用いて
保有制限日数に達した銘柄を売り注文に出します。

利確損切りのfor文の部分に以下のコードを追加します。

    #保有日数カウント・利確損切り
    for (sym,val) in ctx.portfolio.positions.items():
        if ctx.counts[sym] == ctx.count_border:
          sec = ctx.getSecurity(sym)
          sec.order(-val["amount"], comment="保有日数超過売り" )
          ctx.flags[sym] = 0
          done_syms.add(sym)
          ctx.counts[sym] = 0
          continue
    #(以下利確損切りのコード)

これによってカウントされた保有日数ctx.counts[sym]が指定した保有制限日数ctx.count_borderと同じになったら保有日数超過売りとして全売りします。

ここで全売りした銘柄に対して

  • 次回のシグナルで総資産割合買いをする為にフラグctx.flagsを0にリセット→ctx.flags[sym] = 0
  • 取引は一切しない為にdone_symsに追加→done_syms.add(sym)
  • 保有日数を0にリセット→ctx.counts[sym] = 0
  • これ以降のfor文を処理せず飛ばす→continue

という処理を行います。
これ以降でも売りを行った銘柄(sym)に関しても
ctx.counts[sym] = 0
として保有日数のカウントをリセットします。

これで完成です!
完成したコードはこちら

実行・結果

前回の結果(保有日数制限無し)

スクリーンショット 2019-04-01 6.36.18.png

今回の結果(保有日数制限有り)

スクリーンショット 2019-04-01 6.35.55.png

結果・考察

保有日数制限を設けた事でMaxDrawDown(最大損失)を抑制することができた。一定期間の長さでのみ取引をしたい場合などに有用であると考えられる。

その一方4日間のみの期間で取引している状態になっている為、本来得ることができる利益を逃している可能性がある。

今後の展望

今回は保有日数によってMaxDrawDownの抑制を狙ったが次は保有割合によって保有率を調整しリスク管理・MaxDrawDown抑制を狙う。

連続日数を4日だけでなく他の日数なども試してみる?

宣伝

もくもく会もやってるよ

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

詳細はこちらだよ

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

免責注意事項

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

materials

bull-bear:Designed by Rawpixel.com

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0