ブル・ベアファンド自動裁定 〜利確損切り機能実装〜

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

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

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

1. [ブル・ベアファンド自動裁定 〜はじめの一歩〜(前回)](https://qiita.com/Ric418/items/ac539b72f6368079b400)


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

**前回の完成コードに損切り機能を追加**
[前回のコードはこちら](https://factory.quantx.io/developer/0c42e5884489491d8cad673c95d0b9fd)

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

- 利確損切りを優先する
- 指定した終値の上昇率で利確売り
- 指定した終値の下落率で損切り売り
- 利確損切り売りした後は一切取引しない


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

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"
        		]
        	}
        
      }
    )

    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)
      return {
        "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
    '''
    # ctx.logger.debug(current)
    bear = ctx.getSecurity("jp.stock.1357")
    bull = ctx.getSecurity("jp.stock.1570")
    # 買いシグナル
    df_buy = current["buy:sig"][0]
    if df_buy:
      bull.order_target_percent(0.10, comment="BULL BUY")
      bear.order_target_percent(0, comment="BEAR SELL")
      
    # 売りシグナル
    df_sell = current["sell:sig"][0]
    if df_sell:
      bear.order_target_percent(0.10, comment="BEAR BUY")
      bull.order_target_percent(0, comment="BULL SELL")
```
</div></details>

####取引したかの判断のフラグを追加
利確損切り売りした後に買いシグナルが出て買ってしまっては
利確損切りの意味がありません。

それを回避するために利確損切り売りした時はフラグを立て
一切取引しないという形にします。

今回は利確損切り機能の実装ですが他の機能？にも用います。

例えば、
条件によってはある一日で買い売りシグナルが同時にでるという事もありどちらを優先するかなどでしょうか。

フラグ機能をつけるために追加する一文のコードが以下

```
done_syms = set([])
```

となります、`done_syms`という空集合を作り利確損切りした銘柄を入れて
一切の取引を行わないように処理します。

以下の場所に追加しましょう。

```.py
def handle_signals(ctx, date, current):
    '''
    current: pd.DataFrame
    '''
    done_syms = set([])
    # ctx.logger.debug(current)
    bear = ctx.getSecurity("jp.stock.1357")
    bull = ctx.getSecurity("jp.stock.1570")
```

####利確損切り注文を行う
ここが一番の肝になります。以下のコードを追加します。

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

今回は３銘柄だけなのでfor文で一気にやる必要はそれほどないのですが、
ブルベア以外の自動裁定を作る際に便利、コード量削減のためこの形で書きます。

具体的に何をしているか順を追って説明します。

```.py
if sym == 'jp.stock.1321':
          continue
```
今回実際取引に用いる銘柄はブル型とベア型のETF(1570と1357)のみなので、
扱っている銘柄が1321(日経平均225)の場合は`continue` 以下の処理を無視(利確損切りの注文はしない)という意味です。

```.py
returns = val["returns"]
```
QuantX上のAPIを用いて扱っている銘柄の損益率をreturnsに保存します。

`ctx.portfolio.positions.items()`のAPIに関しては
[こちら](https://factory.quantx.io/handbook/ja/api.html#Portfolio)のドキュメントを参考にしてください。


```.py
        if sym == 'jp.stock.1321':
          continue
        returns = val["returns"]
        if returns > 0.05:
          sec = ctx.getSecurity(sym)
          sec.order(-val["amount"], comment="利益確定売(%f)" % returns)
          done_syms.add(sym)
        elif returns < -0.03:
          sec = ctx.getSecurity(sym)
          sec.order(-val["amount"], comment="損切り(%f)" % returns)
          done_syms.add(sym)
```
ここで`returns`、つまり損益率が

- 0.05(5%値上がり)より大きい場合 -> <font color=Red>利益確定</font>として全売り
- -0.03(3%値下がり)より小さい場合 -> <font color=Blue>損失確定</font>として全売り

ということになります。

ちなみに以下の部分のコードは…

```.py
sec = ctx.getSecurity(sym) #銘柄のオブジェクトを取得
sec.order(-val["amount"], comment="利益確定売(%f)" % returns) #対象の銘柄に対して保有している数(val["amount"])だけ売る
done_syms.add(sym) #取引したのでこの銘柄を集合done_symsに追加(.add(sym))
```

という意味になります。

ではこれらのコード群を追加しましょう。

```.py
#...(中略)
bear = ctx.getSecurity("jp.stock.1357")
    bull = ctx.getSecurity("jp.stock.1570")
    #利確損切り
    for (sym,val) in ctx.portfolio.positions.items():
        if sym == 'jp.stock.1321':
          continue
        returns = val["returns"]
        if returns > 0.048:
          sec = ctx.getSecurity(sym)
          sec.order(-val["amount"], comment="利益確定売(%f)" % returns)
          done_syms.add(sym)
        elif returns < -0.01:
          sec = ctx.getSecurity(sym)
          sec.order(-val["amount"], comment="損切り(%f)" % returns)
          done_syms.add(sym)
    # 買いシグナル
    df_buy = current["buy:sig"][0]
    if df_buy:
      bull.order_target_percent(0.10, comment="BULL BUY")
      bear.order_target_percent(0, comment="BEAR SELL")
#...(中略)
```

ここで追加する位置をミスると利確損切りが優先されなくなるので注意してください。
今回は優先順位として

利益確定売り -> 損失確定売り -> 買いシグナル -> 売りシグナル

という感じになるかと思います。

####利確損切り後に取引しないようにする
この部分を改良していきます。
今のコードの状態では`df_buy`, `df_sell`がTrueであれば問答無用で取引するものなのでこれを利確損切り、もしくは既に取引されている、

つまり、

done_symsに入っている銘柄は取引をしない形にしましょう。


以下のコードを追加します。
ブル型もベア型もdone_symsに入っていなければ下のコードを実行するというif文になります。

```.py
if not ('jp.stock.1357' in done_syms) | ('jp.stock.1570' in done_syms):
```

これはブル型ベア型どちらかで利確損切りが起きた場合取引を行わない形になるので
書き方を変えればブルで利確損切り起きたけどベアは買っていいよ、という形にもできます。

せっかくシグナルで買った銘柄を他のシグナルで売りたくないので
`done_syms.add()`も追加しておきます。

実際に追加すると以下のようになるかと思います。

```.py
#...(中略)
    # 買いシグナル
    df_buy = current["buy:sig"][0]
    if df_buy:
      if not ('jp.stock.1357' in done_syms) | ('jp.stock.1570' in done_syms):
        bull.order_target_percent(0.10, comment="BULL BUY")
        bear.order_target_percent(0, comment="BEAR SELL")
        done_syms.add("jp.stock.1570")
      
    # 売りシグナル
    df_sell = current["sell:sig"][0]
    if df_sell:
      if not ('jp.stock.1357' in done_syms) | ('jp.stock.1570' in done_syms):
        bear.order_target_percent(0.10, comment="BEAR BUY")
        bull.order_target_percent(0, comment="BULL SELL") 
        done_syms.add("jp.stock.1357")  
#...(中略)
```

ここでやっと完成です。
完成のコードは[こちら](https://factory.quantx.io/developer/d9c9192d1e9e4cf4b64a740ca4721fac)

##実行・結果
注文の量が総資産割合0.10となっており、取引する銘柄が二つだけにしては少ないので
ブルベアそれぞれ以下のように0.50として実行してみましょう。

```.py
#...(中略)
bull.order_target_percent(0.50, comment="BULL BUY")
#...(中略)
bear.order_target_percent(0.50, comment="BULL BUY")
#...(中略)
```
###前回の結果（利確損切りなし）
<img width="821" alt="スクリーンショット 2019-03-26 16.52.12.png" src="https://qiita-image-store.s3.amazonaws.com/0/318424/3aee0ef7-8783-0926-120b-cc4a6051ceaa.png">


###今回の結果（利確損切りあり）
<img width="818" alt="スクリーンショット 2019-03-26 16.52.48.png" src="https://qiita-image-store.s3.amazonaws.com/0/318424/a31130e6-73dd-4be6-5bd8-d9821ef21e14.png">

###考察
利確損切り機能を入れたことによって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>