1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

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. ブル・ベアファンド自動裁定 〜利確損切り機能実装〜(前回)

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

前回の完成コードに買い増し機能を追加
前回のコードはこちら

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

  • 利確損切りが発生しておらず、シグナルが連続した時10単元株を買い増しする
  • 何かしらで売りが出たら買い増しをリセット、総資産買いからスタートする

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

initialize
my_signals
handle_signals

の部分をイジっていく形になります。

コードの全体像
#必要なライブラリの読み込み
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
    '''
    done_syms = set([])
    # ctx.logger.debug(current)
    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.05:
          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:
      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")
      
    # 売りシグナル
    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.50, comment="BEAR BUY")
        bull.order_target_percent(0, comment="BULL SELL") 
        done_syms.add("jp.stock.1357")
    

####リセット用のフラグ機能を追加
買い増し機能と言っても

総資産割合買い→シグナル連続したら買い増し→売りがでたらリセット→シグナルが出たら総資産割合買い→…

以上の繰り返しと言う形にしたいので、買い増ししたいのか総資産割合買いしたいのかをフラグで管理します。
そのためにinitializeにAPI機能のlocalStorageを用いて以下のコードを追加してブルベアそれぞれのフラグを作ります。

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

ちなみに
0 : 総資産割合買いをしたいとき
1 : 買い増しを行いたいとき
となります、APIの詳細についてはこちらを参照ください。

以上のコードを追加したinitializeが以下の様になります。

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": [
            #"open_price_adj",    # 始値(株式分割調整後)
            "high_price_adj",    # 高値(株式分割調整後)
            "low_price_adj",     # 安値(株式分割調整後)
            #"volume_adj",         # 出来高
            #"txn_volume",         # 売買代金
            "close_price",        # 終値
            "close_price_adj",    # 終値(株式分割調整後) 
          ]
        }
      }
    )
    
    ctx.localStorage = {
      "jp.stock.1570":0,
      "jp.stock.1357":0
    }

    def _my_signal(data):
#中略...

これを後ほどhandle_signalで使い買い増しを行います。

####1日前のシグナルを使えるようにする

ここでは以下の_my_signals関数をいじって行きます

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)

このコードでは日毎のシグナルが出ているかをbuy_signal,sell_signalに格納しています。
これを以下のコードによって日にちをズラします。

buy_sig.shift(1)
sell_sig.shift(1)

実際にずれているか確認してみましょう。
元の日にちのシグナルが以下のようになっています。

signa.png

また.shift(1)によってズラしたものがこちらになります。

signa_shift.png

2018/01/09のシグナルTrueが2018/01/10のシグナルにずれている事が確認できます。

これをreturnしてhandle_signalで使えるようにします。
これらのコードを追加すると_my_signalsは以下のようになります。

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)

####1日前のシグナル用いて買い増しを行う
ここでhandle_signalで1日前のシグナルが使える様になったのでいよいよ実際に買い増しの実装を行いたいと思います。

ここは少し長いので最初に完成のコードを載せ、前回のコードとの差分を説明していきたいと思います。

def handle_signals(ctx, date, current):
    '''
    current: pd.DataFrame
    '''
    done_syms = set([])
    
    # ctx.logger.debug(current)
    # 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 (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.localStorage[sym] = 0
          done_syms.add(sym)
          # ctx.logger.debug(ctx.localStorage[sym])
        elif returns < -0.01:
          sec = ctx.getSecurity(sym)
          sec.order(-val["amount"], comment="損切り(%f)" % returns)
          ctx.localStorage[sym] = 0
          done_syms.add(sym)
    # 買いシグナル
    # ctx.logger.debug(df_buy)
    # ctx.logger.debug(ctx.localStorage["jp.stock.1570"])
    if df_buy & df_inc_bull & (ctx.localStorage["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.localStorage["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.localStorage["jp.stock.1570"] = 1
        ctx.localStorage["jp.stock.1357"] = 0
    
    # 売りシグナル
    if df_sell & df_inc_bear & (ctx.localStorage["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.localStorage["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.localStorage["jp.stock.1357"] = 1
        ctx.localStorage["jp.stock.1570"] = 0

まずは_my_signalsでreturn、登録した1日前のシグナルを以下のコードで取得します

df_inc_bull = current["incbull:sig"][0]
df_inc_bear = current["incbear:sig"][0]

次に売りが出された銘柄に関しては以下のコードによってフラグを0とします。

ctx.localStorage[sym] = 0  #symには"jp.stock.1357"などの銘柄が入ります

また、以下のコードでそれぞれの銘柄においてctx.localStorage[sym]が1の時に買い増しを行う様にします。

      if df_buy & df_inc_bull & (ctx.localStorage["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_sell & df_inc_bear & (ctx.localStorage["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")

最後にそれぞれの銘柄の総資産割合注文の部分の条件式などを以下の様にする事で
ctx.localStorage[sym]が0の時に総資産割合買いをする様にします。

    if df_buy & (ctx.localStorage["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.localStorage["jp.stock.1570"] = 1 #総資産割合買いしたので1にする(今後連続でシグナル出たら買い増し)
        ctx.localStorage["jp.stock.1357"] = 0 #売ったので0にする(今後シグナルが出たら総資産割合買いする)

    if df_sell & (ctx.localStorage["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.localStorage["jp.stock.1357"] = 1 #総資産割合買いしたので1にする(今後連続でシグナル出たら買い増し)
        ctx.localStorage["jp.stock.1570"] = 0 #売ったので0にする(今後シグナルが出たら総資産割合買いする)

これで完成です!

完成のコードはこちら

##実行・結果
###前回の結果(買い増し機能無し)
hutuuu.png

###今回の結果(買い増し機能あり)
kaimasi.png

ちなみにシグナルの出方は以下の様であり色の濃いところが買い増しの期間になっています。

スクリーンショット 2019-03-31 2.24.37.png

###考察
買い増し機能を入れたことにより今まで単純な総資産割合指定だった為に買いシグナルが続いても大した数注文できていなかった。これが数量指定で買い増しすることが可能になった事で収益とシグナル数が増加したと考えられる。

その一方保有割合は全期間において大きくなっているのでMaxDrawDown(最大損失)が大きくなっていると推定できる。

###今後の展望
ただ単純に買い増しを行うだけでなく保有日数や保有割合によって保有率を調整しリスク管理・MaxDrawDown抑制を狙う。

連続日数を2日だけでなく3日なども試してみる?

##宣伝

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

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?