Backtrader カスタムインジケーターの作り方


__どうやったらインジケーターを自分で作成することができますか？__

インジケーター作成は
・bt.indicatorクラスを継承したクラスを作成
・プロットするLinesオブジェクトを設定
・移動平均線の期間などパラメーターを設定
・必要であればプロット表示の設定を追加
・式を記述する
という流れで作成します。

例としてストキャスティクスを作って中身を見てみましょう。

```python:sto1.py

import backtrader as bt


class MyStochastic1(bt.Indicator):  #bt.Indicatorを継承
    
    lines = ('k', 'd', )  # プロットに表示するlinesオブジェクト
    params = (
        ('k_period', 14), # パラメーターをタプルのタプルで指定する
        ('d_period', 3),  # タプルの最後にもコンマ(、)をいれる
    )
    plotinfo = dict(plot   =True,
                    subplot=True,
                    plotname='',
               )   

    def __init__(self):
        
        highest = bt.indicators.Highest(self.datas[0], period=self.params.k_period)
        lowest = bt.indicators.Lowest(self.datas[0], period=self.params.k_period)        
        self.lines.k = k = (self.datas[0] - lowest) / (highest - lowest)    
        self.lines.d = bt.ind.SimpleMovingAverage(k, period=self.params.d_period)

    def next(self):
        pass
```



これがインジケータークラスの全体像です。
ストキャスティクスの計算には14日間の高値・安値が必要です。それらと終値を組み合わせて変化量を計算して3日単純移動平均で平滑化します。

#クラスの宣言
`class MyStochastic1(bt.Indicator):`
まずbacktraderのIndicatorクラスを継承した「MyStochastic1」クラスを宣言します。

#Linesオブジェクトの設定
`lines=('k','d')`

ストキャスティクスにはKとDという2つのラインがあります。なのでプロットに表示する2つのLinesオブジェクトを設定しています。
このLinesオブジェクトとはMQL4でいうところの「Indexbuffer」みたいなもので数値と時間軸が結びついた**イテラブルな配列**です。CerebroはこのLinesオブジェクトをチャートにプロットしてくれます。
>>
##Linesオブジェクトのインデックスとスライス
MQL4では最新のbarを[0]で表します。最新のBarから数えて1,2,3,・・・と古いBarになるにつれて数字が大きくなっていきます。
backtraderでも最新のBar・Linesオブジェクトを[0]で表します。これはMQL4と同じです。しかし0を基準にして古いbarになるにつれて-1,-2,-3・・・とマイナス記号が付きます。
>>
これに関連してLinesオブジェクトはPython本来のスライス操作を行うことはできません。
スライスを使いたい場合はget()メソッドを使います。
>>
`myslice = self.lines.my_sma.get(ago=0, size=1)`  [0]を除いて[-1]ひとつだけの配列
`myslice = self.lines.my_sma.get(ago=0, size=10)`  [0]を除いて[-1]から[-10]まで10個の配列
`myslice = self.lines.my_sma.get(ago=-1, size=5)` [-1]を除いて[-2]から[-6]までの配列
>>
**※Linesオブジェクトに対して[ ]オペレータを使えるのはnextメソッドの中だけです。nextメソッドに関してはまた後日まとめたいと思います。**



#パラメーターの設定（params）
`params = (`
        `('k_period', 14), `
        `('d_period', 3),  `  *#最後にもコンマ入れる*
   ` )`
ストキャスティクスは14日期間で計算を行い、その結果を3日単純移動平均で平滑化します。（k_period, d_period）その設定をしているのが、このパラメーターです。タプルのタプル、またはdict（）によって指定します。（参照1）
self.params.k_periodのように「self.params.名前」 という書き方で表します。タプルの場合最後のコンマを入れ忘れないように気をつけてください。


#プロットの設定（plotinfo）
`plotinfo = 
dict(plot   =True,
                    subplot=True,
                    plotname='',
               )   `
プロットする際の設定を変えることができます。

plot : プロット表示On・Off 
subplot : サブウィンドウ表示切り替え 
plotname : 脚注表示名（空欄の場合クラス名が表示）

他にも設定項目があります。





#Initメソッドに式を記述する
##式の作成その1　計算用の組み込みインジケーター呼び出し
`highest = bt.indicators.Highest(self.datas[0],period=self.params.k_period)`
`lowest = bt.indicators.Lowest(self.datas[0],period=self.params.k_period)        `
initメソッド内にストキャスティクスの計算を記述します。
まず14日間高値と安値を算出します。幸いなことにbacktraderでは特定期間の高値・安値を計算してくれるインジケーターがあります。
組み込みインジケーターはbt.indicators.○○○○() で呼び出せます。（最後にsに注意！）ここではHighestおよびLowestを呼び出してk_period期間中の最高値と最低値を検索しています。その結果をLinesオブジェクトhighestとlowestに入れています。




##式の作成その2　Linesオブジェクトに値をいれる
`self.lines.k = k = (self.datas[0] - lowest) / (highest - lowest)`
`self.lines.d = bt.ind.SimpleMovingAverage(k,period=self.params.d_period)`

プロットするLinesオブジェクトは「self.lines.名前」で表します。kは `self.lines.k` でdは`self.lines.d`です。結果として得られたLinesオブジェクトは計算にも使うことができます。ここでは次の行の計算の表記を簡略化するために「k」に代入して単純移動平均インジケータに渡しています。


>>
##データフィード（datas）
`self.datas[0]`
式の中のself.datas[0]は「1つめのCSVデータ」を表しています。特に指定しない場合は終値が使用されます。データは複数渡すことができるので例えば、日足と週足のような2つのCSVデータを渡した場合には小さい時間枠が先で大きい時間枠を後に指定しなくてはいけません。つまりdatas[0]が日足データ、datas[1]を週足データにしなくてはいけません。今回は日足データのみ渡しています。
>>
*同一市場の異なる時間枠データを使用して分析するだけでなく、異なる市場、例えば金（Gold）と為替の連動性を分析したりすることもできます。また3つ以上の市場データ・異なる時間枠データを使用して分析することもできます。参照2*
>>
##省略表記
|--- |正式|省略|
|:---:|:---:|:---:|
|lines|self.lines.aaa|self.l.aaa
|params|self.params.aaa|self.p.aaa|
|datas|self.datas[0].close|self.data.close または self.data
|indicators|bt.indicators.aaa|bt.ind.aaa|
|Alias|SimpleMovingAverage|SMA|
>>
backtraderでは省略表記ができます。便利ですが慣れるまで混乱しやすいかもしれません。
そして実は今回のplotinfoはまるごと全部省略できます。plot,subplot,plotnameは省略した場合デフォルト設定が使われます。
>>
##nextメソッド
今回は使っていませんが、Bar1本ごとに条件判断したり処理を行いたいときにはnextメソッドに内容を記述します。
>>
※init と nextの違いなども後日まとめたいと思います。


#完成　ストラテジークラスで呼び出し
__インジケーターを表示するにはどうしたらいいでしょうか？__

すべての設定が終わりました。
このインジケータークラスをストラテジークラスのinitメソッドの中でインスタンス化すると、Cerebroがチャートにプロットしてくれます。インスタンス名は自分で自由につけれます。

`self.myind1 = MyStochastic1(self.data) `


（各所に説明コメント追加・なるべく省略表記しています。）


```python:sto12.py
%matplotlib notebook
from __future__ import (absolute_import, division, print_function,
                        unicode_literals)

import datetime  
import os.path  
import sys  


import backtrader as bt


class MyStochastic1(bt.Indicator):
    
    lines = ('k', 'd', )  # プロットに表示するlinesオブジェクト
    params = (
        ('k_period', 14), # パラメーターをタプルのタプルで指定する
        ('d_period', 3),  # タプルの最後にもコンマ(、)をいれる
    )
    
    #省略
    #plotinfo = dict()

    def __init__(self):
        # 正確にはself.datas[0]と書くが省略可能 
        # self.params.k_periodを省略してself.p.k_period 
        highest = bt.ind.Highest(self.data, period=self.p.k_period)
        lowest = bt.ind.Lowest(self.data, period=self.p.k_period)        
       
        self.lines.k = k = (self.data - lowest) / (highest - lowest)    
        self.lines.d = bt.ind.SMA(k, period=self.p.d_period)

        

class TestStrategy(bt.Strategy):
    def __init__(self):
        #インスタンス名myind1の部分は任意の名前でいい
        self.myind1 = MyStochastic1(self.data)    
        
        
if __name__ == '__main__':
    cerebro = bt.Cerebro()
    cerebro.addstrategy(TestStrategy)

    datapath = 'C:\\Users\\aaaa\\orcl-1995-2014.txt'

    # Create a Data Feed
    data = bt.feeds.YahooFinanceCSVData(
        dataname=datapath,        
        fromdate=datetime.datetime(2000, 1, 1),
        todate=datetime.datetime(2000, 12, 31),
        reverse=False)

    cerebro.adddata(data)
    cerebro.run(stdstats=False)
    cerebro.plot(style='candle')
```


![1sto.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/579602/bdac46b0-cabe-2b3f-6daa-0627477f3066.png)

無事ストキャスティクスが表示されました。

*※CSVデータの入手先・準備に関してはこちらの記事に書きました。*https://qiita.com/xxssxxx/items/e915b8afe51facc47a55

#Cerebro起動までのおおまかな流れ
__Cerebroとは？__

Backtraderの中心的な機能です。各種の設定をした上でこのCerebroを起動すると自動売買や分析・最適化、結果のプロットを自動的に行ってくれます。先程のスクリプトのCerebro起動までの流れをおさらいします。

1. 自動売買の内容とそのために呼び出すインジケーターをストラテジークラス（TestStrategy）に記述する
2. Cerebroをインスタンス化する `cerebro = bt.Cerebro()`
3. cerebroにCSVデータを渡す  `cerebro.adddata(data)`
4. ストラテジーをcerebroに読み込ませる `cerebro.addstrategy(TestStrategy)`
5. cerebroを起動する  `cerebro.run()`
6. チャートを表示する `cerebro.plot()`

自分で考えたストラテジークラス「TestStrategy」を作成し、CSVデータを指定した上でCerebroを実行すると、自動的にこの「TestStrategy」クラスの内容を実行してくれます。
今回は自動売買を行っていませんが「TestStrategy」の中でストキャスティクスが使われているのでCerebroがプロットしてくれます。

#まとめ
いかがでしたでしょうか？
Backtraderでカスタムインジケーターを使うには、カスタムインジケーターのクラスを自分で作成し、それをストラテジークラスの中でインスタンス化します。



#参照文献
参照1
*当初はパラメータの順番を守りたいときにはタプル、そうでないときは辞書を使うという違いがあったようです。しかしPython3.7からは関係がなくなったのだそうです。強いて2つの違いをあげるとすればタイプする量が若干減って楽なのがdict()であとは好みによるそうです。*
1.MomentumStrategy (Dict vs Tuple of tuple)
https://www.backtrader.com/blog/2019-05-20-momentum-strategy/momentum-strategy/

参照2
2.Gold vs SP500
https://www.backtrader.com/blog/posts/2016-12-13-gold-vs-sp500/gold-vs-sp500/