Quant X トレーリングストップ

@kei-tenniacoさんと組んでアルゴリズムを作りました。

#トレーリングストップの説明


トレーリングストップとは、株価の上昇に合わせて損切りラインを更新する売買方法です。QuantX Factoryで現在使用されているアルゴリズムの多くでは、損切りと利確ラインを株価と独立に一定に設定しています。

トレーリングストップを使っていない例：

![Screen Shot 2019-07-16 at 17.34.27.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/459110/ffce9e6e-7db3-f718-5392-9cdaa9af28e5.png)


このように、株価が急増するときは利確ラインで売ってしまい、利益を失います。


トレーリングストップを使っている例：

![Screen Shot 2019-07-16 at 15.27.04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/459110/d47d056d-94c5-39cb-6f2b-7ca43596d7d0.png)



トレーリングストップを使えば、損切りラインを株価とともに動かせて、急増している株価から最大の利益を得ることができます。

株価が下がったら損切りラインでSELLをして、株価が上がったらトレーリングストップを使って高い株価で売ります。

損切りラインが上下に動いているため、利確ラインはいりません。

#ゴンさんのトレーリングストップの間違え

２０１９年の3月にゴンさんがトレーリングストップのプログラムを作りました。それは[こちら（１）](https://qiita.com/investor-gon/items/e6c0a9f6982f8aea4e28)と[こちら(２）](https://qiita.com/investor-gon/items/41dbf925d2fa86c40c19)にあります。

彼の最初のコードには欠点が三つあります。

１. 利確がまだ残っている。
２. トレーリングストップポジションを保つため、３０日間のmaxを使用している。これにより、実際にはポジションを保持していなかった時期のmax値を使用してしまったり、本来使用すべきmax値を逃してしまったりと、関係のないデータを使ってしまう可能性がある。一定の日にちを入力するよりもポジションを保持した時からのMAX値を使った方がいい。
３. 損切り時での使用でなく、SELL＿SIGにトレーリングストップを導入してしまっている。これにより、ポジションを持っていない際もSELL_SIGがTrueとなり、0.0.5エンジンではBUYとSELLのシグナルが打ち消し合い、買いが入れれなくなってしまう。（0.0.1ではBUYのあと一瞬でSELLをしてしまう。）よって、これはどのバージョンでも効率的ではない。

また、ゴンさんの二つ目の記事は１番目の欠点しか直していません。これを解決してより良いトレーリングストップを作ります。


#完成したコード

一般的なWMAモデル（対象銘柄：アンジェス）のコードにトレーリングストップを実装しました。
（0.0.5エンジンを使っています。）

```
# Sample Algorithm
# ライブラリーのimport
# 必要ライブラリー
import maron
import maron.signalfunc as sf
import maron.execfunc as ef
# 追加ライブラリー
# 使用可能なライブラリに関しましては右画面のノートをご覧ください①
import pandas as pd
import talib as ta
import numpy as np

# オーダ方法(目的の注文方法に合わせて以下の2つの中から一つだけコメントアウトを外してください)
# オーダー方法に関しましては右画面のノートをご覧ください②
#ot = maron.OrderType.MARKET_CLOSE # シグナルがでた翌日の終値のタイミングでオーダー
ot = maron.OrderType.MARKET_OPEN   # シグナルがでた翌日の始値のタイミングでオーダー
#ot = maron.OrderType.LIMIT        # 指値によるオーダー

# 銘柄、columnsの取得
# 銘柄の指定に関しては右画面のノートをご覧ください③
# columnsの取得に関しては右画面のノートをご覧ください④
def initialize(ctx):
    # 設定, 6行目から20行目は変えないようにお願いします。
    ctx.flag_profit = False #利益確定売りを用いるかTrueなら用いるFalseなら用いない
    ctx.flag_loss = True #損切りを用いるかTrueなら用いるFalseなら用いない
    ctx.flag_trailing_stop = True #トレーリングストップを用いるかTrueなら用いるFalseなら用いない
    ctx.loss_cut = -0.03 #損切りのボーダーマイナス％
    ctx.profit_taking = 0.05 #利益確定売りのボーダープラス％
    ctx.trailing_stop = -0.03 #トレーリングストップのmax値比較ボーダーマイナス％
    ctx.codes = [4563]
    ctx.symbol_list  = ["jp.stock.{}".format(code) for code in ctx.codes]
    ctx.configure(
      channels={          # 利用チャンネル
        "jp.stock": {
          "symbols":ctx.symbol_list,
          "columns": [
            #"open_price_adj",    # 始値(株式分割調整後)
            #"high_price_adj",    # 高値(株式分割調整後)
            #"low_price_adj",     # 安値(株式分割調整後)
            "close_price",        # 終値
            "close_price_adj",    # 終値(株式分割調整後)
            # "volume_adj",         # 出来高
            # "txn_volume",         # 売買代金
                     ]}})
    # シグナル定義
    def _my_signal(data):
      # この部分に作成するアルゴの指標を書き込んで下さい。
       #各銘柄の終値(株式分割調整後)を取得、欠損データの補完
      cp = data["close_price_adj"].fillna(method="ffill")
      for (sym, val) in cp.items():
        # valがすべてNaNだったときはtalibで計算しない
        isna = val.isnull().all()
        if isna:
          continue
  
      #単純移動平均線(SMA)の設定
      #データの入れ物を用意
      s_wma = pd.DataFrame(data=0,columns=[], index=cp.index)
      m_wma = pd.DataFrame(data=0,columns=[], index=cp.index)
      l_wma = pd.DataFrame(data=0,columns=[], index=cp.index)
      #TA-Libによる計算
      for (sym,val) in cp.items():
        s_wma[sym] = ta.WMA(cp[sym].values.astype(np.double), timeperiod=5)
        m_wma[sym] = ta.WMA(cp[sym].values.astype(np.double), timeperiod=25)
        l_wma[sym] = ta.WMA(cp[sym].values.astype(np.double), timeperiod=75)
      #SMAの売買シグナルの定義
      s_wma_ratio = s_wma/m_wma
      l_wma_ratio = m_wma/l_wma
      
      buy_sig = (s_wma_ratio > 1.00) & (s_wma_ratio < 1.10) & (l_wma_ratio > 1.00) & (l_wma_ratio < 1.10)
      sell_sig = (((s_wma_ratio < 0.99) & (l_wma_ratio < 0.99)))
  
      # market_sigという全て0が格納されているデータフレームを作成
      # market_sigに関しては右画面のノートをご覧ください⑥
      market_sig = pd.DataFrame(data=0.0, columns=cp.columns, index=cp.index)
      
      # buy_sigがTrueのとき1.0、sell_sigがTrueのとき-1.0とおく
      market_sig[buy_sig == True] = 1.0
      market_sig[sell_sig == True] = -1.0
      market_sig[(buy_sig == True) & (sell_sig == True)] = 0.0
  
      # loggerに関しましては右画面のノートをご覧ください⑦
      # ctx.logger.debug(market_sig)

      return {
        "wma5":s_wma,
        "wma25":m_wma,
        "wma75":l_wma,
        "market:sig": market_sig,
      }
      
    # シグナル登録
    ctx.regist_signal("my_signal", _my_signal)

def handle_signals(ctx, date, current):
    '''
    current: pd.DataFrame
    '''
    market_sig = current["market:sig"]
    done_syms = set([])
      
    for (sym,val) in ctx.portfolio.positions.items():
        returns = val["returns"]
        trailing = val["max_returns"] - returns
        if (ctx.flag_loss) & (returns < ctx.loss_cut):
          sec = ctx.getSecurity(sym)
          sec.order(-val["amount"], comment="損切り(%f)" % returns)
          done_syms.add(sym)
        elif (ctx.flag_profit) & (returns > ctx.profit_taking):
          sec = ctx.getSecurity(sym)
          sec.order(-val["amount"], comment="利益確定売(%f)" % returns)
          done_syms.add(sym)
        elif (ctx.flag_trailing_stop) & (returns > 0.10) & (ctx.trailing_stop > 0.01):
          sec = ctx.getSecurity(sym)
          sec.order(-val["amount"], comment="トレーリング１０％利確(%f)" % returns)
          done_syms.add(sym)
        elif (ctx.flag_trailing_stop) & (returns > 0.05) & (ctx.trailing_stop > 0.02):
          sec = ctx.getSecurity(sym)
          sec.order(-val["amount"], comment="トレーリング５％利確(%f)" % returns)
          done_syms.add(sym)
        elif (ctx.flag_trailing_stop) & (returns - val["max_returns"] < ctx.trailing_stop):
          sec = ctx.getSecurity(sym)
          sec.order(-val["amount"], comment="トレーリング利確(%f)" % returns)
          done_syms.add(sym)
          
      # 買いシグナル
    buy = market_sig[market_sig > 0.0] 
    for (sym, val) in buy.items():
      if sym in done_syms:
        continue
      
      sec = ctx.getSecurity(sym)
      sec.order_target_percent(0.8, orderType=ot, comment="SIGNAL BUY")
      #ctx.logger.debug("BUY: %s,  %f" % (sec.code(), val))
      pass
      
    # 売りシグナル
    sell = market_sig[market_sig < 0.0]
    for (sym, val) in sell.items():
      if sym in done_syms:
        continue
      sec = ctx.getSecurity(sym)
      sec.order_target_percent(0, orderType=ot, comment="SIGNAL SELL")
      #ctx.logger.debug("SELL: %s,  %f" % (sec.code(), val))
```


#コードの説明

###def initialize:

```
    ctx.flag_profit = False #利益確定売りを用いるかTrueなら用いるFalseなら用いない
    ctx.flag_loss = True #損切りを用いるかTrueなら用いるFalseなら用いない
    ctx.flag_trailing_stop = True #トレーリングストップを用いるかをTrue/Falseで決める
    ctx.loss_cut = -0.03 #損切りのボーダーマイナス％
    ctx.profit_taking = 0.05 #利益確定売りのボーダープラス％
    ctx.trailing_stop = -0.03 #トレーリングストップのmax値比較ボーダーマイナス％
```


ここでは損切りと利確ラインのボーダー％を決めます。トレーリングストップを使うので、利確はいりません。そのため、ctx.flag_profitをFalseになっています。

ctx.trailing_stop　はトレーリングストップを株価の何パーセント下にするかを決めています。

トレーリングストップの弱点は変動が大きすぎて急増する前に売ってしまうことがあります。そのため、変動が激しいグラフでしたら、ctx.trailing_stopの設定幅を大きくした方が効果的です。


###def handle_signals:

```

        elif (ctx.flag_trailing_stop) & (returns > 0.10) & (ctx.trailing_stop > 0.01):
          sec = ctx.getSecurity(sym)
          sec.order(-val["amount"], comment="トレーリング１％利確(%f)" % returns)
          done_syms.add(sym)
        elif (ctx.flag_trailing_stop) & (returns > 0.05) & (ctx.trailing_stop > 0.02):
          sec = ctx.getSecurity(sym)
          sec.order(-val["amount"], comment="トレーリング２％利確(%f)" % returns)
          done_syms.add(sym)
        elif (ctx.flag_trailing_stop) & (returns - val["max_returns"] < ctx.trailing_stop):
          sec = ctx.getSecurity(sym)
          sec.order(-val["amount"], comment="トレーリング利確(%f)" % returns)
          done_syms.add(sym)
```

ここでは、トレーリングストップを実行しています。ゴンさんと同じく株価の利益率が１０％を超えたらctx.trailing_stop　を３％から１％に変えて。５％を超えたら２％に変えます。

#比べた結果

トレーリングストップを使っていないWMAモデル：

![Screen Shot 2019-07-16 at 17.41.36.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/459110/756f66d4-84eb-b05c-19a1-5f11da736ca5.png)


トレーリングストップを使ったWMAモデル：

![Screen Shot 2019-07-16 at 15.30.26.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/459110/8756a4c8-fbb0-6f0d-7fbb-77c2b00b1138.png)



トレーリングストップを使わずに利確ラインを使うと、急増で利益を失います。
（青はSELLです。）
　　　　　![Screen Shot 2019-07-16 at 15.39.22.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/459110/d32eae0d-16b4-aace-0911-5e9fd424879b.png)


ですが、利確ラインの代わりにトレーリングストップを使えば、効果的なSELLができます。　
　　　　　![Screen Shot 2019-07-16 at 15.37.41.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/459110/aa76b80b-0c22-eb9c-761c-6003b1e6ae9d.png)