BBANDSを利用したサンプル

#自己紹介
5ヶ月ほど前から趣味でPythonを触っている、プログラミング初心者です。
そんな初心者の僕がSmartTrade社でインターンをさせていただくことになったので、プログラミングも投資も分からなくても理解出来るように、学んだ内容を随時更新していきたいと思います。
#QuantXとは
QuantXとはSmart Trade社が提供しているプラットホームです。QuantXを用いることで、無料で株価データを分析し、アルゴリズムを開発することができます。この記事では、[QuantXの公式サンプルコード](https://determined-feynman-fb5a05.netlify.com/ja/)を読んでいくだけなので、登録は必須ではありませんが、無料で登録することができるので登録してみてください。登録は[QuantXに入門してみる](https://qiita.com/tetymd/items/e9bda1efce0bd33f4667)を参考にするといいかもしれません。

#[DEMO] BBANDSを利用したサンプル
画面右上のヘルプから「BBANDを利用したサンプル」を選択すると、

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

def initialize(ctx):

    # 設定
    ctx.configure(
        target="jp.stock.daily",
        channels={          # 利用チャンネル
            "jp.stock": {
              "symbols": [
                "jp.stock.7203",  "jp.stock.7267",
                "jp.stock.7270",  "jp.stock.7269",
                "jp.stock.7259",  "jp.stock.7202",
                "jp.stock.7203",  "jp.stock.7205",
                "jp.stock.7208",  "jp.stock.7211",
                "jp.stock.7211",  "jp.stock.7276",
                "jp.stock.7272",  "jp.stock.7240",
                "jp.stock.3116",  "jp.stock.7282",
                "jp.stock.7248",  "jp.stock.7313",
                "jp.stock.6995",  "jp.stock.7222",
                "jp.stock.7278",  "jp.stock.7242",
                "jp.stock.7287",  "jp.stock.7251",
                "jp.stock.7230",  "jp.stock.5949",
                "jp.stock.7296",  "jp.stock.7279",
                "jp.stock.7250",  "jp.stock.5970",
                "jp.stock.7220",
              ],
              "columns": [ "close_price_adj",]
            }
        }
    )
    
    def _TALIB_CALL(data):

      cp = data["close_price_adj"].fillna(method="ffill")

      upperband = {}
      middleband = {}
      lowerband = {}
      
      buy_sig = pd.DataFrame(data=0,columns=[], index=cp.index)
      sell_sig = pd.DataFrame(data=0,columns=[], index=cp.index)
      uband = pd.DataFrame(data=0,columns=[], index=cp.index)
      lband = pd.DataFrame(data=0,columns=[], index=cp.index)

      for (sym,val) in cp.items():
        upperband[sym], middleband[sym], lowerband[sym] = ta.BBANDS(cp[sym].values.astype(np.double),
          timeperiod=10,
          nbdevup=2,
          nbdevdn=2,
          matype=0)
          
        lband[sym] = lowerband[sym]
        uband[sym] = upperband[sym]
        buy_sig[sym] = lowerband[sym] / cp[sym]
        sell_sig[sym] = cp[sym] / upperband[sym]


      return {
        "upperband:price": uband,
        "lowerband:price": lband,
        "bb_buy:sig": (buy_sig >= 1.002),
        "bb_sell:sig": (sell_sig >= 0.99),
      }

    
    # シグナル登録
    ctx.regist_signal("TALIB", _TALIB_CALL)

def handle_signals(ctx, date, current):

    done_syms = set([])

    ratio = current["bb_buy:sig"]
    df = ratio[ratio == 1.0]
    for (sym,val) in df.items():
        if sym in done_syms:
          continue

        sec = ctx.getSecurity(sym)
        sec.order_target_percent(0.15, comment="シグナル買(%f)" % val)
        pass

    ratio = current["bb_sell:sig"]
    df = ratio[ratio == 1.0]
    for (sym,val) in df.items():
        if sym in done_syms:
          continue
          
        sec = ctx.getSecurity(sym)
        sec.order_target_percent(0, comment="シグナル売(%f)" % val)
        pass

    pass
```
というコードが表示されます。
初期化部分は今まで[QuantXの標準テンプレートを理解する](https://qiita.com/RS4322/items/bef8bdfb49fdffeb85d2)と同じなので省略し、def _TALIB_CALL(data):の行から解説して行きたいと思います。
###def _TALIB_CALL(data):
ここはQuantXの標準テンプレートにもあった通り売買シグナルを指定する関数を定義しています。
###cp = data["close_price_adj"].fillna(method="ffill")
ボリンジャー・バンドは終値を使うのでその定義をします。
data["close_price_adj"]は以下のようなデータフレームになっています。

|  |jp.stock.3116|jp.stock.5949|jp.stock.5970|jp.stock.6995|jp.stock.7202|jp.stock.7203| 指定した銘柄|
|---|---|---|---|---|---|---|---|
|⋮ |⋮  |⋮  |⋮  |⋮  |⋮  |⋮  |⋮  |
|2015/10/3  |終値  |終値|終値  |終値  |終値  |終値|終値  |
|2015/10/4  |終値  |終値  |終値  |終値  |終値  |終値  |終値  |
|2015/10/5 |終値  |終値  |終値  |終値  |終値  |終値  |終値  |
|2015/10/6  |終値  |終値  |終値  |終値  |終値  |終値  |終値  |
|2015/10/7  |終値  |終値  |終値  |終値  |終値  |終値  |終値  |
|2015/10/8  |終値  |終値  |終値  |終値  |終値  |終値  |終値  |
|2015/10/9 |終値  |終値  |終値  |終値  |終値  |終値  |終値  |
|⋮ |⋮  |⋮  |⋮  |⋮  |⋮  |⋮  |⋮  |

これに対し、.fillna(method="ffill")で欠損値がある箇所を前の値で補完して行きます。
証券データの中にはいろいろな理由で値の無いものがありますのでその際に誤動作しないようにするためです。
###upperband = {},middleband = {},lowerband = {}
空の辞書型を用意しておきます。後ほどボリンジャーバンドを定義した時(<b>ta.BBANDS()</b>)に、この辞書内にデータを格納していきます。
###buy_sig = pd.DataFrame(data=0,columns=[], index=cp.index)
以下のようなデータフレームが出力されます。
dataは0、columnsは無しで、indexはcp.icdex=「cpのインデックス」のデータフレームになっています。

|  |
|---|
|⋮|
|2015/10/3  |
|2015/10/4  |
|2015/10/5 |
|2015/10/6  |
|2015/10/7  |
|2015/10/8  |
|2015/10/9 |
|⋮|
同じようにsell_sig、uband、lband も空のデータフレームを作っていきます。
####sell_sig = pd.DataFrame(data=0,columns=[], index=cp.index)
####uband = pd.DataFrame(data=0,columns=[], index=cp.index)
####lband = pd.DataFrame(data=0,columns=[], index=cp.index)
###for (sym,val) in cp.items():
for文の中身がcp.itemの数だけ(指定した期間の日数)実行されます。
このfor文のsymはcpのカラム、つまり指定した証券コードが指定されます。
確認ですがcpは以下のようなデータフレームになっています

|  |jp.stock.3116|jp.stock.5949|jp.stock.5970|jp.stock.6995|jp.stock.7202|jp.stock.7203| 指定した銘柄|
|---|---|---|---|---|---|---|---|
|2015/10/3  |終値  |終値|終値  |終値  |終値  |終値|終値  |
|2015/10/4  |終値  |終値  |終値  |終値  |終値  |終値  |終値  |
|2015/10/5 |終値  |終値  |終値  |終値  |終値  |終値  |終値  |
|2015/10/6  |終値  |終値  |終値  |終値  |終値  |終値  |終値  |
|2015/10/7  |終値  |終値  |終値  |終値  |終値  |終値  |終値  |
|2015/10/8  |終値  |終値  |終値  |終値  |終値  |終値  |終値  |
|2015/10/9 |終値  |終値  |終値  |終値  |終値  |終値  |終値  |
|⋮ |⋮  |⋮  |⋮  |⋮  |⋮  |⋮  |⋮  |

###upperband[sym],middleband[sym],lowerband[sym]=ta.BBANDS()
ここではボリンジャーバンドに必要なデータを作っていきます。upperband,middleband,lowerbandはそれぞれ+2α,10日移動平均線,-2αのデータが入ります。
###=ta.BBANDS(cp[sym].values.astype(np.double),timeperiod=10,nbdevup=2,nbdevdn=2,matype=0)
<b>第1引数</b>：該当の銘柄の終値の配列
<b>timeperiod</b>：何日の移動平均線を中心にするか
<b>nbdevup</b>：上側ボリンジャーバンドの標準偏差
<b>nbdevdn</b>：下側ボリンジャーバンドの標準偏差
<b>matype</b>：moving averageのタイプ。 matype=0はSMA(Simple Moving Average)となります。
返り値はdict型の３つの配列、upperband, middleband, lowerbandにそれぞれ格納されます。

###lband[sym] = lowerband[sym]  

pd.DataFrame(data=0,columns=[], index=cp.index) で作った、空のデータフレームにデータを入れて行きます。symは指定した証券コードのことなので、
<b>lband[sym]</b> = lowerband[sym]：下側ボリンジャーバンド(-2α)
<b>uband[sym]</b> = upperband[sym]：上側ボリンジャーバンド(+2α)
<b>buy_sig[sym]</b> = lowerband[sym] / cp[sym]：買いシグナル = 下側ボリンジャーバンド(-2α)/指定した証券コードの終値
<b>sell_sig[sym]</b> = cp[sym] / upperband[sym]：売りシグナル = 上側ボリンジャーバンド(+2α)/指定した証券コードの終値
と指定しています。
###return {}

```python
return {
        "upperband:price": uband,
        "lowerband:price": lband,
        "bb_buy:sig": (buy_sig >= 1.002),
        "bb_sell:sig": (sell_sig >= 0.99),
      }
```
def  _TALIB_CALL(data)関数の戻り値を返しています。
<b>"upperband:price","lowerband:price"</b>:ボリンジャー・バンドの上端と下端のチャートを引けるように指定します。
<b>"bb buy:sig","bb sell:sig"</b>:バックテストのチャートに表示される買いシグナル、売りシグナルの条件を設定します。

###ctx.regist_signal("TALIB", _TALIB_CALL)
シグナルを登録します
第１引数にシグナルを登録したい名前(なんでもいい)、第２引数に先ほどの関数 _TALIB_CALLを入れます。
この辺りは以前書いた記事([QuantXの標準テンプレートを理解する](https://qiita.com/RS4322/items/bef8bdfb49fdffeb85d2)／[移動平均を使ったサンプル](https://qiita.com/RS4322/items/fd7f7616e0389b63b4db))でも説明しているのでそちらも参照してみてください。
###def handle_signals(ctx, date, current):
売買の処理をする関数の定義を行っています
dateにはdatetime.datetime型で対応する日付が入っています。
currentは、date(期間内のある日)のデータとシグナルを含んだ pandas.DataFrame オブジェクトで、以下のようなpandas.DataFrame オブジェクトになっています。

|  |split_ratio|close_price_adj|close_price|bb_buy:sig|bb_sell:sig|lowerband:price|upperband:price  
|---|---|---|----|----|---|---|---|
|jp.stock.3116  |値  |値  |値  |True  |False  |値  |値  |
|jp.stock.5949  |値  |値  |値  |False|True  |値  |値  |
|jp.stock.5970  |値  |値  |値  | False  | True  |値  |値  |
|jp.stock.6995  |値  |値  |値  | False  |Flase  |値  |値  |
|jp.stock.7202  |値  |値  |値  | False  | False  |値  |値  |
|jp.stock.7203  |値  |値  |値  |True  |False  |値  |値  |
|jp.stock.7205  |値  |値  |値  | False  | False  |値  |値  |
|⋮ |⋮  |⋮  |⋮  |⋮  |⋮  |⋮  |⋮  |

###done_syms = set([])
後に必要になる(今回のコードでは<b>損切り</b>、<b>利益確定売</b>をしないので必要ないですが)、空のsetオブジェクトを用意しておきます。
###ratio = current["bb_buy:sig"]
currentの"bb_buy:sig"のみを取り出しているので、ratioは以下のようになっています。(例えば2015/1/1と2015/1/7は)

<b>2015/1/1</b>

| 銘柄 | 売りシグナル(TrueまたはFalse) | 
|:-----------|------------:|
| jp.stock.3116|False|
| jp.stock.5949|False|
| jp.stock.5970|False|
| jp.stock.6995|False|
| jp.stock.7202|False|
| ⋮ |⋮  |

<b>2015/1/7</b>

| 銘柄 | 売りシグナル(TrueまたはFalse) | 
|:-----------|------------:|
| jp.stock.3116|False|
| jp.stock.5949|False|
| jp.stock.5970|True|
| jp.stock.6995|True|
| jp.stock.7202|False|
| ⋮ |⋮  |
###df = ratio[ratio == 1.0]
シグナルがTrueとなっているratioデータを抜き出します。True=1.0, False=0なので、ratio==1.0はratio==Trueを指定しているのと同じです。
例えば、2015/1/1と2015/1/7の場合は以下のようになっています。

<b>2015/1/1</b>
ratioにTrueがないのでデータなし
<b>2015/1/7</b>
jp.stock.5970とjp.stock.6995のみがTrueなので、この２つの銘柄のPandas.Seriesになる。

| 銘柄 | 売りシグナル | 
|:-----------|------------:|
| jp.stock.5970|True|
| jp.stock.6995|True|

###for (sym,val) in df.items():
for文で
<b>sym</b> : 銘柄
<b>val</b> : 1(True)
を取得しています。
###if sym in done_syms:
もしdone_symsにすでにsymが入っていたらシグナルによる売買と「損切り、利益確定売り」が被ってしまうのでdone_symsにすでにsymが入っていないかを確認します。(今回のコードでは<b>損切り</b>、<b>利益確定売</b>をしないので必要ないです)
###continue
もしdone_symsにすでにsymが入っていたら、それ以降の処理が実行されずにスキップされ、次のループに進みます。done_symsにすでにsymが入っていない場合、つまりシグナルによる売買と「損切り、利益確定売り」の被りがない場合のみ次の処理に進みます。
###sec = ctx.getSecurity(sym)
Securityとは銘柄を表すオブジェクトであり、ctx.getSecurity() で取得できます。このオブジェクトを使用して注文を行ないます。symには前述した通りctx.configureで指定した証券コードが入っています。
###sec.order_target_percent(0, comment="シグナル売(%f)" % val)
再びorder()メソッドです。この銘柄の総保有額が総資産評価額(現金+保有ポジション評価額)に対して指定の割合となるように注文を行ないます。 
第一引数にはこの銘柄の総保有額の目標割合を指定します。
第二引数にはバックテスト結果に表示されるコメントを指定します。ここではcomment="シグナル売(%f)" % val"で"<b>シグナル売(1.000000)</b>"というコメントを出力します。
###pass
pass文は、「何も行わない」という動作の命令です。
###ratio = current["bb_sell:sig"] 
以降
同様に売り注文も実行します。
#最後に
何か誤り等ございましたらご指摘お願いします