LoginSignup
2
4

More than 5 years have passed since last update.

QuantXでトレイリングストップを実装してみた!!

Last updated at Posted at 2019-03-15

高値、安値に合わせて自動的に利確損切りポジションを変えてくれる夢のトレーリングストップを実装しました。

と思っていたのですが結果は微妙ですね。泣

自己紹介

この度Smart Tradeでインターンすることになったゴンです。

ゴンについて、知りたい方はこちら。
ブログ読んでねー

ツイッターはこちら
https://twitter.com/gon_gon_tarou

初めての記事はこちら(QuantXを初めてみて)
https://qiita.com/investor-gon/items/2b1952ae5c2c88334c74

何を作るのか(完成目標)

日経225平均寄与率上位10位の銘柄(2018年12月25日)を用いて、
トレイリングストップロジックで利確損切りをする、MACDを用いたアルゴリズムを作る!!!

トレイリングストップとは、なんですかって方は簡単にこちら。

高値安値に合わせて、逆指値注文をリアルタイムに自動修正する自動売買です。

同じ売るなら少しでも高いところで売りたい、高値から下落してきたところで売りたい、という場合等で活用できます。

ただQuantXの関係で空売りは対応していないので、今回は高値に合わせて売買する方式にしました。

イメージこんな感じ。
雑な画像ですいません。

无标题1.png

気になる条件はこんな感じ

  • 基本的にはMACDの買いシグナル売りシグナルで売買するようになっている。

  • 売り(ポジションクローズ)に関しては、

    • 3%下がったら損切り。
    • 過去30日間の高値より3%下がったら利確。

まずは完成コード(コピペ、どうぞ)

############################################################################
# トレイリングストップ MACD売買

  #ライブラリーの設定
import pandas as pd
import talib as ta
import numpy as np


def initialize(ctx):
    # 設定
    ctx.logger.debug("initialize() called")

    # ポートフォリオの何%をオーダーするか
    ctx.target = 0.1

    # 損切りと益出しの水準
    ctx.loss_cut = -0.03 
    ctx.profit = 0.06

    #トレイリングストップの日にち
    ctx.high_term = 30

    ctx.configure(
      target="jp.stock.daily",
      channels={          # 利用チャンネル
        "jp.stock": {
          "symbols": [
            "jp.stock.9983", #ファーストリテイリング
            "jp.stock.9984", #ソフトバンク
            "jp.stock.6954", #ファナック
            "jp.stock.9433", #KDDI
            "jp.stock.8028", #ファミリーマート
            "jp.stock.8035", #東京エレクトロン
            "jp.stock.4543", #テルモ
            "jp.stock.6367", #ダイキン
            "jp.stock.6971", #京セラ
            "jp.stock.9735", #セコム

          ],
          "columns": [
            #"open_price_adj",    # 始値(株式分割調整後)
            "high_price_adj",    # 高値(株式分割調整後)
            #"low_price_adj",     # 安値(株式分割調整後)
            #"close_price",        # 終値
            "close_price_adj",    # 終値(株式分割調整後) 
            #"volume_adj",         # 出来高
            #"txn_volume",         # 売買代金
          ]
        }
      }
    )

 #売買シグナル生成部分
    def _my_MACD(data):

      #各銘柄の終値(株式分割調整後)を取得、欠損データの補完 
      df_close = data["close_price_adj"].fillna(method='ffill')

      #MACDの設定
      d_macd = dict() 
      d_macdsignal = dict()
      d_macdhist = dict() 

      for symbol in data.minor_axis:
        macd, macdsignal, macdhist = ta.MACD(df_close[symbol].values.astype(np.double), 
                                          fastperiod=12 , slowperiod=26, signalperiod=9)
        d_macd[symbol] = macd
        d_macdsignal[symbol] = macdsignal
        d_macdhist[symbol] = macdhist

      df_macd = pd.DataFrame(d_macd, index=data.major_axis)        
      df_macdsignal = pd.DataFrame(d_macdsignal, index=data.major_axis)        
      df_macdhist = pd.DataFrame(d_macdhist, index=data.major_axis)

      #macdの売り買いのサインを定義
      df_goldencross = (df_macd > df_macdsignal) &  (df_macd.shift(1) < df_macdsignal.shift(1))
      df_deadcross = (df_macd < df_macdsignal) &  (df_macd.shift(1) > df_macdsignal.shift(1))

      # 欠損値を埋める
      hp = data["high_price_adj"].fillna(method="ffill")
      # 過去30日間における最高値を取得
      new_max = hp.rolling(window=ctx.high_term, center=False).max()
      #ctx.logger.debug(new_max)

      # 過去30日間における最高値の0.97倍と現在地の比較
      df_trailing_stop = (new_max* 0.97 >= df_close)
      #ctx.logger.debug(df_trailing_stop)

      # 売買シグナルの設定
      buy_sig = df_goldencross 
      sell_sig = df_deadcross | df_trailing_stop

      return{
          "MACD": df_macd, 
          "MACDSignal": df_macdsignal, 
          "MACDHist": df_macdhist,
          "df_trailing_stop": df_trailing_stop,
          "new_max": new_max,
          "buy:sig": buy_sig,
          "sell:sig": sell_sig,
        }


    # シグナル登録
    ctx.regist_signal("my_MACD", _my_MACD)

def handle_signals(ctx, date, current):

   #ctx.logger.debug(current)

  # ポジションクローズ
   for (sym,val) in ctx.portfolio.positions.items():
     rtn = val["returns"]

     if rtn < ctx.loss_cut:
        sec = ctx.getSecurity(sym)
        # order_target_percent (memo 参照)
        sec.order_target_percent(0, comment="損切り {:.2%}".format(rtn))

   sell = current["sell:sig"].dropna()
   df_sell = sell[sell > 0]

   for (sym,val) in df_sell.items():
     #retn = val["returns"]
     sec = ctx.getSecurity(sym)
     sec.order_target_percent(0, comment="利確")   
     #.format(retn)

   # 買いシグナル
   df = current.copy()  
    # シグナル["buy:sig"]がTrueだけ取得
   df = df[df["buy:sig"]]

   if not df.empty:
      for (sym, val) in df.iterrows(): 
        sec = ctx.getSecurity(sym)
        msg = "買いシグナル"
        sec.order_target_percent(ctx.target, comment=msg)

結果はこちら

スクリーンショット 2019-03-15 10.55.24.png

全然利益でないですね、ツライ〜

詳しくみてみると、
スクリーンショット 2019-03-15 10.56.26.png

買いシグナルはいいにしても、売りシグナル大量に出ていますね。

買ってもすぐ、損切りか利確されてしまうので、これは困ったものです。
利確損切りの数値がかなり重要な気がしました。

解説

def initialize(ctx)編 (株選択&変数入力)

# ポートフォリオの何%をオーダーするか
    ctx.target = 0.1

    # 損切りと益出しの水準
    ctx.loss_cut = -0.03 
    ctx.profit = 0.06

    #トレイリングストップの日にち
    ctx.high_term = 30

こんな感じにコード書くと変数を作れます!!

def _my_signal(data)編 (売買シグナル生成)

# 欠損値を埋める
      hp = data["high_price_adj"].fillna(method="ffill")
      # 過去30日間における最高値を取得
      new_max = hp.rolling(window=ctx.high_term, center=False).max()
      #ctx.logger.debug(new_max)

      # 過去30日間における最高値の0.97倍と現在地の比較
      df_trailing_stop = (new_max* 0.97 >= df_close)
      #ctx.logger.debug(df_trailing_stop)

      # 売買シグナルの設定
      buy_sig = df_goldencross 
      sell_sig = df_deadcross | df_trailing_stop

基本MACDのコードですが、トレイリングストップのロジックを売りシグナルに混ぜました。

MACDの説明はこちら。
https://qiita.com/investor-gon/items/df1c7368f5af20ee9499

高値の値を持ってきて、過去30日間の高値を取得して、3%下がったときと、現在値と比較するロジックです。

def handle_signals(ctx, date, current)編 (損切り、利確の設定)

# ポジションクローズ

#損切り 
   for (sym,val) in ctx.portfolio.positions.items():
     rtn = val["returns"]

     if rtn < ctx.loss_cut:
        sec = ctx.getSecurity(sym)
        # order_target_percent (memo 参照)
        sec.order_target_percent(0, comment="損切り {:.2%}".format(rtn))
#利確 
   sell = current["sell:sig"].dropna()
   df_sell = sell[sell > 0]

   for (sym,val) in df_sell.items():
     #retn = val["returns"]
     sec = ctx.getSecurity(sym)
     sec.order_target_percent(0, comment="利確")   
     #.format(retn)

ポジションクローズだけいつもと違うコードです。

損切りの部分は、portfolioからリターンを取ってきて、比較して、損切りライン(3%下落)にかかったら損切りします。

なお、ここでの{:.2%}はリターンを小数点2けたで表示するものであって、損切りラインではないです。
ちなみにこの一行
sec.order_target_percent(0, comment="損切り {:.2%}".format(rtn))

利確は、売りシグが発生していたら、利確します。

それでは今回はこの辺で。

2
4
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
2
4