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

![115 [更新済み]-01.jpg](https://qiita-image-store.s3.amazonaws.com/0/318424/2ddff4af-d160-958b-bdc1-f2930bb9a7c3.jpeg)
## 導入
人気のあるETFブルベア型の取引の自動裁定システムをQuantX Factory上で開発します。
シリーズ化していますので

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

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

1. [ブル・ベアファンド自動裁定 〜はじめの一歩〜](https://qiita.com/Ric418/items/ac539b72f6368079b400)
2. [ブル・ベアファンド自動裁定 〜利確損切り機能実装〜](https://qiita.com/Ric418/items/ac539b72f6368079b400)
3. [ブル・ベアファンド自動裁定 〜QuantX APIで買い増し機能実装〜（前回）](https://qiita.com/Ric418/items/3c73c6f8124e65cd8544)

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

**前回の完成コードに保有日数に制限を付けリスク回避？をする**
[前回のコードはこちら](https://factory.quantx.io/developer/3fb085dfd8e64531a65386ae2ae78725)

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

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

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

###早速やっていく
一応コードの全体像を眺めておきましょう、今回はこのコードの

`initialize`
`handle_signals`

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

<details><summary>コードの全体像</summary><div>

```py
#必要なライブラリの読み込み
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
        
```
</div></details>

####各銘柄のカウント用の辞書を用意
各銘柄の保有日数を保存するための辞書を`initialize`内で以下の様に定義します。

```py
    ctx.counts = {
      "jp.stock.1570":0,
      "jp.stock.1357":0
    }
   
    ctx.count_border = 4
```

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

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

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

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

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

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

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

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

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

```py
    #保有日数カウント・利確損切り
    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`
として保有日数のカウントをリセットします。

**これで完成です！**
完成したコードは[こちら](https://factory.quantx.io/developer/956a4b661a784e0fbc146251a06efdee)

##実行・結果
###前回の結果（保有日数制限無し）
<img width="855" alt="スクリーンショット 2019-04-01 6.36.18.png" src="https://qiita-image-store.s3.amazonaws.com/0/318424/034fbabd-0fd5-1c2b-c4b9-a889a2403b86.png">


###今回の結果（保有日数制限有り）
<img width="886" alt="スクリーンショット 2019-04-01 6.35.55.png" src="https://qiita-image-store.s3.amazonaws.com/0/318424/8caea8f5-172d-eadc-89f8-7cfe0f746880.png">

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

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

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

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

##宣伝

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

###詳細はこちらだよ
Pythonアルゴリズム勉強会HP：[https://python-algo.connpass.com/](https://python-algo.connpass.com/)
(connpassって言うイベントサイトに飛びます)

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

## materials
bull-bear:<a href='https://jp.freepik.com/free-vector/ベクター画像_2461918.htm'>Designed by Rawpixel.com</a>