ブル・ベアファンド自動裁定 〜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)
4. [ブル・ベアファンド自動裁定 〜QuantX APIでリスク回避・保有日数編〜](https://qiita.com/Ric418/items/11c0607aafd47a0d83d8)
5. [ブル・ベアファンド自動裁定 〜買い増し機能を柔軟に〜（前回）](https://qiita.com/Ric418/items/af6c2af5a1f07286820c)

##今回の目標
保有日数によってリスク管理を試みたが目見える効果は見られなかったため今回は

**各銘柄の<font color = Red>保有率</font>を元にリスクを管理する**
[前回のコードはこちら](https://factory.quantx.io/developer/a279f6509d3c44058d0fc1a63e49011c)

という機能を実装します。

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

- 各銘柄の保有数をAPIにより取得しそれに割合(0.08)をかける事で買い増しを行う
- 総資産に対して各銘柄が指定した保有率を超えたら利確損切りラインを調整する（下げる）

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

`handle_signals`

の部分をいじって行く形になります。

<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
    }
    
    ctx.counts = {
      "jp.stock.1570":0,
      "jp.stock.1357":0
    }
    
    ctx.count_border = 4

    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(ctx.localStorage["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 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
        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.counts[sym] = 0
          # ctx.logger.debug(ctx.localStorage[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.counts[sym] = 0
    # 買いシグナル
    # ctx.logger.debug(df_buy)
    # ctx.logger.debug(ctx.localStorage["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):
        if 'jp.stock.1570' in ctx.portfolio.positions:
          bull.order(ctx.portfolio.positions['jp.stock.1570']['amount']*0.08, 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
        ctx.counts["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):
         if 'jp.stock.1357' in ctx.portfolio.positions:
          bear.order(ctx.portfolio.positions['jp.stock.1357']['amount']*0.08, 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
        ctx.counts["jp.stock.1570"] = 0
        
    for (sym,val) in ctx.portfolio.positions.items():
      if val["amount"] > 0:
        ctx.counts[sym] = ctx.counts[sym] + 1
    # ctx.logger.debug(ctx.counts["jp.stock.1357"])
    # ctx.logger.debug(ctx.counts["jp.stock.1570"])
```
</div></details>

####利確損切りラインの変数を設定する
利確損切りラインを定義する変数を作成します。

利確損切りそれぞれ`ctx.profit_taking`, `ctx.losscut` という変数とします。
これを利確損切りの注文をおこなうかどうかの以下の条件式で用います。

```.py
 if returns > ctx.profit_taking: #ココ
          sec = ctx.getSecurity(sym)
          sec.order(-val["amount"], comment="利益確定売(%f)" % returns)
          ctx.localStorage[sym] = 0
          done_syms.add(sym)
          # ctx.logger.debug(ctx.localStorage[sym])
        elif returns < ctx.losscut: #ココ
          sec = ctx.getSecurity(sym)
          sec.order(-val["amount"], comment="損切り(%f)" % returns)
          ctx.localStorage[sym] = 0
          done_syms.add(sym)
```

####総資産に対する割合を取得し、利確損切りラインを調整する
以下のコードを92行目あたりからのfor文内に追加します。

```.py
if val["portfolio_ratio"] > 0.65:
          ctx.profit_taking = 0.02
          ctx.losscut = -0.01
        else:
          ctx.profit_taking = 0.048
          ctx.losscut = -0.02
```

ちなみにこれは各銘柄の総資産に対する保有割合が65%を超える場合利確損切りラインをそれぞれ2%,-1%（慎重）、超えない場合はそれぞれ4.8%,-2%(強気)という物にします。

####保有日数に関連する物を削除
以下のコードは使わないので削除もしくはコメントアウトします。

```.py
#93行目あたり
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

#148行目あたり
for (sym,val) in ctx.portfolio.positions.items():
      if val["amount"] > 0:
        ctx.counts[sym] = ctx.counts[sym] + 1

#（それ以外のカウントリセット系のところも消す、正直なところ93行目あたりを消せば十分な気がする）
```

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

##実行・結果
###前回の結果（保有日数によるリスク回避）
![スクリーンショット 2019-04-15 5.08.10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/318424/b220828e-0e9b-b9cf-c22c-a4226c7a3f6e.png)

###今回の結果（保有率によるリスク回避）
![スクリーンショット 2019-04-15 5.04.29.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/318424/aaef8997-689b-9060-d64b-5e49aa8be952.png)

###結果・考察
MaxDrawDownだけ見ると保有率によるリスク回避に関してはあまり芳しく無い結果である。
しかしシグナル回数がへり損益率が上がっているので無駄な（損をする）取引を回避している？と考えられる。

また保有日数と保有率でのリスク管理を比べると保有率の段階に合わせて利確損切りラインを段階的に調整できるなどを考えると拡張性や今後の可能性は保有率による調整の方に将来性があるように考えられる。

###今後の展望
買いシグナル売りシグナルが被った時の対応（優先順位）を考える。
そろそろ`handle_signal`の調整による改善にも限界が見えてきているのでシグナルの出し方を工夫していく。

##宣伝

###もくもく会もやってるよ
**日時**：毎週水曜日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>