HLバンドを順張りで使ってみる

#はじめに

今回はHL (HighLow) バンドを用いたアルゴリズムを作ってみました. 
HLバンドの詳細については以下の記事を参考にしたのでご覧ください.

[HLバンドの見方・使い方【テクニカル指標・トレンド系】](https://fx-quicknavi.com/chart/hlbands/)

簡単に説明すると, HLバンドではある期間の高値の最大値を結んだ線（Highバンド）と, 安値の最小値を結んだ線（Lowバンド）, 2本の線の平均線（Middleバンド）を用いています. 

では早速本題に移ります.

#HLバンドアルゴリズム
##シグナル

買いシグナル：Middleバンドを終値（cp）が突き抜けたとき
売りシグナル：Middleバンドを終値（cp）が下回ったとき or 終値がHighバンドを突き抜けたとき

シグナルの決定は[こちら](https://fx-quicknavi.com/chart/hlbands/)を参考にしました.


##コード

今回のコードは[こちら](https://factory.quantx.io/developer/f7ad05123863478faa91a248bba0a1c8/coding)

```c
#HLバンド
import maron
import maron.signalfunc as sf
import maron.execfunc as ef
import pandas as pd
import talib as ta 
import numpy as np 

#ot = maron.OrderType.MARKET_CLOSE # シグナルがでた翌日の終値のタイミングでオーダー
ot = maron.OrderType.MARKET_OPEN   # シグナルがでた翌日の始値のタイミングでオーダー
#ot = maron.OrderType.LIMIT        # 指値によるオーダー

def initialize(ctx):
  # 設定
  ctx.logger.debug("initialize() called")
  ctx.configure(
    channels={               # 利用チャンネル
      "jp.stock": {
        "symbols": [
          "jp.stock.2914",
          "jp.stock.3382", 
          "jp.stock.4063", 
          "jp.stock.4452",
          "jp.stock.4502",
          "jp.stock.4503",
          "jp.stock.4568",
          "jp.stock.6094",
          "jp.stock.6501",
          "jp.stock.6758",
          "jp.stock.6861",
          "jp.stock.6954",
          "jp.stock.6981",
          "jp.stock.7203",
          "jp.stock.7267",
          "jp.stock.7751",
          "jp.stock.7974",
          "jp.stock.8031",
          "jp.stock.8058",
          "jp.stock.8306",
          "jp.stock.8316",
          "jp.stock.8411",
          "jp.stock.8766",
          "jp.stock.8802",
          "jp.stock.9020",
          "jp.stock.9022",
          "jp.stock.9432",
          "jp.stock.9433",
          "jp.stock.9437",
          "jp.stock.9984",
          
        ],
        "columns": [
          "close_price",     # 終値
          "close_price_adj", # 終値(株式分割調整後)
          "high_price_adj",    # 高値(株式分割調整後)
          "low_price_adj",     # 安値(株式分割調整後)
          "volume_adj",     # 出来高
          "txn_volume",     # 売買代金
        ]
      }
    }
  )
  
  # シグナル定義
  def _my_signal(data):
    cp =  data["close_price_adj"].fillna(method='ffill')  #終値取得
    hp = data["high_price_adj"].fillna(method='ffill')    #高値取得
    lp = data["low_price_adj"].fillna(method='ffill')     #安値取得
    hband = hp.rolling(20).max()                          #Highバンド設定
    lband = lp.rolling(20).min()                          #Lowバンド設定
    mband = (hband - lband)/2 + lband                     #Middleバンド設定
    
    market_sig = pd.DataFrame(data=0.0, columns=cp.columns, index=cp.index)
    buy_sig = ((mband.shift(1) > cp.shift(1)) & (mband < cp))
    sell_sig = ((mband > cp) | (cp > hband))
    # 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
    #ctx.logger.debug(market_sig)

    return {
      # "hband:g2": hband,
      # "lband:g2": lband,
      # "mband:g2": mband,
      "hband": hband,
      "lband": lband,
      "mband": mband,
      "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"]
    if returns < -0.02:
      sec = ctx.getSecurity(sym)
      sec.order(-val["amount"], comment="損切り(%f)" % returns)
      done_syms.add(sym)
    elif returns > 0.04:
      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, 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, orderType=ot, comment="SIGNAL SELL")
    #ctx.logger.debug("SELL: %s,  %f" % (sec.code(), val))
    pass

```


##コード解説

基本的なコードの解説は, 勉強会用に作成した資料が下記のリンク先にあるので[こちら](https://python-algo.connpass.com/event/161848/)をご覧ください.

今回重要なところを解説していきます.

**69~71行目**

```c
    hband = hp.rolling(20).max()                          #Highバンド設定
    lband = lp.rolling(20).min()                          #Lowバンド設定
    mband = (hband - lband)/2 + lband                     #Middleバンド設定
```

ここでは, 各バンドの設定をしています. hbandは過去20日間の高値（cp）の最大値, lbandは過去20日間の安値（lp）の最小値, mbandはhbandとlbandの平均をとっています.

rollingについての詳しいことは[こちら](https://pandas.pydata.org/pandas-docs/version/0.25.3/reference/api/pandas.core.window.Rolling.min.html)



**74~75行目**

```c
    buy_sig = ((mband.shift(1) > cp.shift(1)) & (mband < cp))
    sell_sig = ((mband > cp) | (cp > hband))
```

買いシグナル：mbandを終値（cp）が突き抜けたとき
売りシグナル：mbandを終値（cp）が下回ったとき or 終値がhbandを突き抜けたとき

##バックテスト結果

<img width="542" alt="hlband2.PNG" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/538875/fc8892e9-1bb3-8e3c-f467-26187e45bad7.png">


3年で損益率が17%,,,,

あまり儲かっていませんね.

MaxDrawdown, SharpRatio, αの値を見ても, かなり改善の余地がありそうです.



##感想

HLバンドは一般的には, ブレイクアウト手法で使われることが多く, 大相場（普段と比べて, 株式相場全体が活況に満ちて高騰し, 出来高も大きく膨らんだ相場のこと）でないと失速することが多いため, すべての状況でHLバンドを用いてもあまり良いアルゴリズムはできないことが結果からも言えます.

#免責注意事項
本コードを用いた実際のトレードで生じる損害は一切保証しかねますので、ご注意ください。