Quantxでブレイクアウト（騙し回避型）を実装してみた

# はじめに

今回は株式会社 Smart Tradeが提供している[Quantx Factory](https://factory.quantx.io/)にて、ブレイクアウト手法のアルゴリズムを組んでみました。

# 自己紹介
Qiita初投稿です。

つい先日からSmart Tradeでインターンを始めたので、学んだことをアウトプットも兼ねて更新していきたいと思います。

#ブレイクアウトとは

![画像](https://toushi-kyokasho.com/wp-content/uploads/2014/11/break.jpg)

ブレイクアウトとはレジスタンスラインやサポートライン、最高値など**多くの市場参加者が意識しているポイント**をブレイク（突破）していく事です。

基本的にブレイクアウト後は、更に一段高（安）を付けにいくという相場の習性があります。

#ブレイクアウト手法のメリット・デメリット

###・メリット
力強い相場において、出遅れる事なく利益を挙げられる。（トレンドに強い）
###・デメリット
高値圏でポジションを取る為ブレイクに失敗した際（騙し）、損切りが大きくなりやすい。

#デメリットを考慮したシグナル
直近10日間の高値をブレイクした後、その10日間の値幅に対して50%以上押した所を拾います。
また、だましを避けるためにテクニカル指標のADXを用いてトレンドの強弱の判定をしています。
（終値に関しては株式分割調整後の値を用いています）

#決済方法
直近安値を更新したポイントで決済
また資金管理の為、騰落率に合わせた決済もしています。

#コード
[コピー](https://factory.quantx.io/developer/30aa920f14da4e29a27056cd266c0b78/coding)
↑共有どうぞ。


```python
import maron
import pandas as pd
import numpy as np
import talib as ta

ot = maron.OrderType.MARKET_OPEN   # シグナルがでた翌日の始値のタイミングでオーダー

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

    # 過去何日間を観察するか
    ctx.high_term = 10
    ctx.low_term = 10
    ctx.vol_term = 10
    # ポジションに対して何％持つか 
    ctx.target = 0.5 
    
    ctx.configure(
      channels={          # 利用チャンネル
        "jp.stock": {
          "symbols": [
            "jp.stock.7203",
            "jp.stock.9984",
            "jp.stock.8306",
            "jp.stock.4502",
            "jp.stock.6758",
            "jp.stock.6861",
            "jp.stock.9432",
            "jp.stock.8316",
            "jp.stock.7267",
            "jp.stock.8411",
            "jp.stock.6098",
            "jp.stock.7974",
            "jp.stock.9433",
            "jp.stock.9022",
            "jp.stock.8058",
            "jp.stock.4452",
            "jp.stock.8766",
            "jp.stock.6954",
            "jp.stock.6981",
            "jp.stock.9020",
            "jp.stock.4063",
            "jp.stock.7751",
            "jp.stock.6501",
            "jp.stock.3382",
            "jp.stock.9437",
            "jp.stock.8031",
            "jp.stock.4503",
            "jp.stock.8802",
            "jp.stock.6752",
          ],
          "columns": [
            "high_price_adj",
            "low_price_adj",
            "close_price_adj"
            "volume_adj",
            ]}})  

    def _BREAK_NEW_HIGH(data):

      # 欠損値を埋める
      hp = data["high_price_adj"].fillna(method="ffill")
      lp = data["low_price_adj"].fillna(method="ffill")
      cp = data["close_price_adj"].fillna(method="ffill")
      vol = data["volume_adj"].fillna(method="ffill")
      # 過去10日間における最高値を取得
      new_max = hp.rolling(window=ctx.high_term, center=False).max()
      new_min = lp.rolling(window=ctx.low_term, center=False).min()
      vol_max = vol.rolling(window=ctx.vol_term, center=False).max()
      # その最高値が今日の高値と同じかどうかの真偽値
      today_is_new_high = new_max == hp
      today_is_new_low = new_min == lp
      today_is_vol_max = vol_max == vol

      price_range = new_max - new_min   #値幅
      
      ADX = pd.DataFrame(data=0,columns=cp.columns, index=cp.index)
      
      for (sym,val) in cp.items():
        ADX[sym] = ta.ADX(hp[sym], lp[sym], cp[sym], timeperiod=14)
      
      buy_sig1 = (new_max - lp) > price_range*0.5
      buy_sig2 = ADX > 32
      buy_sig = (buy_sig1) & (buy_sig2)
      sell_sig = lp < new_min
      
      # market_sigという全て0が格納されているデータフレームを作成
      market_sig = pd.DataFrame(data=0.0, columns=hp.columns, index=hp.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
    
      return {
        "ADX":ADX, 
        "New High":new_max, 
        "New Low":new_min,
        "price range":price_range,
        "market:sig": market_sig,
        }

    # シグナル登録
    ctx.regist_signal("BREAK_NEW_HIGH", _BREAK_NEW_HIGH)
    
def handle_signals(ctx, date, current):    
   
    market_sig = current["market:sig"]

    done_syms = set([])
  
    # 損切り、利確の設定
    for (sym, val) in ctx.portfolio.positions.items():
      returns = val["returns"]
      if returns < -0.025:
        sec = ctx.getSecurity(sym)
        sec.order(-val["amount"], comment="損切り(%f)" % returns)
        done_syms.add(sym)
      elif returns > 0.07:
        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(sec.unit() * 1.5,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(sec.unit() * -1.5,orderType=ot, comment="SIGNAL SELL")
      #ctx.logger.debug("SELL: %s,  %f" % (sec.code(), val))
      pass
```

#実行結果
![結果ブレイクアウト　.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/587560/e5734c8a-b221-512f-7362-e2c0cce98b2f.png)

過去3年間の相場をバックテストしてみました。

損益率 : 77.71%
最大ドローダウン : 10.1%
シャープレシオ : 1.682
ボラティリティ : 0.123
α : 0.162
β : 0.525

#感想・考察
結果自体は悪くないんですが、想定していたより大分取引回数が多かったです。
恐らくピラミッティング（買い増し）が多い為だと思われます。（もう少し取引回数少なくてもいいかも）

改善点としましては、
・もう少しDDを抑える
・エントリーポイントの精査
・ポートフォリオ管理の実装
・銘柄に大きく依存しない

ってところですかね、、、


#参考サイト
・シャープレシオ・最大DD・ボラティリティとは【アルゴリズムの指標はこう見る！】
https://blog.quantx.io/2019/03/25/yenzo-algo-erabikata/

・新高値更新のアルゴリズム #QuantX
https://qiita.com/shinseitaro/items/a098d99f15e99ac3e0c3

#免責注意事項
このコードや知識を使った実際の取引で生じた損益に関しては一切の責任を負いかねますので御了承下さい.