株のシステムトレードをしよう - 1から始める株自動取引システムの作り方

株式をコンピュータに売買させる仕組みを少しずつ作っていきます。できあがってから公開ではなく、書いたら途中でも記事として即掲載して、後から固定ページにして体裁を整える方式で進めていきます。

logger と handler を使用して、ログレベルに応じたログ出力 ◆ Backtrader 最初の戦略その2

f:id:dogwood008:20201029012608j:plain
Photo by Arget on Unsplash

前回の振り返り

前回は基礎的な戦略 TestStrategy を作成した。

how-to-make-stock-trading-system.dogwood008.com

今回の内容

今回は昨日作成した戦略を使用した、バックテストを実行してみる。

バックテストの実行

実行すると、下記のような出力を得られるだろう。

$ docker-compose up
Starting stock-trading-system_app_1 ... done
Attaching to stock-trading-system_app_1
app_1  | Starting Portfolio Value: 100000.00
app_1  | 2000-01-03, Close, 26.27
app_1  | 2000-01-04, Close, 23.95
(中略)
app_1  | 2000-12-28, Close, 27.63
app_1  | 2000-12-29, Close, 25.85
app_1  | Final Portfolio Value: 100000.00
stock-trading-system_app_1 exited with code 0

これと、 TestStrategy を見比べて、動作の流れを説明する。

# 以下引用: mementum, "Quickstart Guide - Backtrader", 
# "https://www.backtrader.com/docu/quickstart/quickstart/#our-first-strategy", 
# アクセス日:2020年10月29日, 日本語のコメント部は著者による

# Create a Stratey ここから売買戦略だが、まだ基本的には何かを参照して売買を決めるような戦略は作らない
class TestStrategy(bt.Strategy):

    def log(self, txt, dt=None):
        ''' Logging function for this strategy'''
        dt = dt or self.datas[0].datetime.date(0)
        print('%s, %s' % (dt.isoformat(), txt))

    def __init__(self):
        # Keep a reference to the "close" line in the data[0] dataseries
        self.dataclose = self.datas[0].close  # `self.dataclose` に今着目しているローソク足の終値を代入する
        # 「今」とは、バックテスト中では1営業日ずつ進むその年月日を指す

    def next(self):
        # Simply log the closing price of the series from the reference
        self.log('Close, %.2f' % self.dataclose[0])

ローソク足(ティック)が1進む、つまり着目している年月日が1営業日進むと、そのたびに next(self) が呼ばれる。 next(self) からは self.log(~) を呼んでいるので、ログが記録される。

ここで、前回書いたとおり、 print() によるログよりも logger を使ったログ出力の方が望ましいので、その部分を変えていこう。修正には次の記事を参考にする。

qiita.com

ついでに、 private 扱いにしたいメソッドや変数は、プレフィクスとして _ を付けておく1

# main.py を修正

(前略)
# ログ用
from logging import getLogger, StreamHandler, Formatter, DEBUG

# Create a Stratey
class TestStrategyWithLogger(bt.Strategy):
    def _log(self, txt, dt=None):
        ''' Logging function for this strategy '''
        dt = dt or self.datas[0].datetime.date(0)
        self._logger.debug('%s, %s' % (dt.isoformat(), txt))

    def __init__(self):
        # Keep a reference to the "close" line in the data[0] dataseries
        self._dataclose = self.datas[0].close
        self._logger = getLogger(__name__)
        self.handler = StreamHandler()
        self.handler.setLevel(DEBUG)
        self._logger.setLevel(DEBUG)
        self._logger.addHandler(self.handler)
        self._logger.propagate = False
        self.handler.setFormatter(
                Formatter('[%(levelname)s] %(message)s'))

    def next(self):
        # Simply log the closing price of the series from the reference
        self._log('Close, %.2f' % self._dataclose[0])

if __name__ == '__main__':
(中略)
    # Add a strategy
    IN_DEVELOPMENT = True  # このフラグにより、ログレベルを切り替えることで、本番ではWARN以上のみをログに出すようにする。
    # フラグの切り替えは、環境変数で行う事が望ましいが今は一旦先送りする。
    loglevel = DEBUG if IN_DEVELOPMENT else WARN
    cerebro.addstrategy(TestStrategyWithLogger, loglevel)
(後略)

これにより、 IN_DEVELOPMENT 定数の状態により、ログレベルを変更することができるようになった。つまり、本番はより重要なログのみを出力するようにし、開発中はデバッグ用のログを出力することができるようになったということである。

実際に実行してみよう。

# IN_DEVELOPMENT = True のとき
$ docker-compose up
Starting stock-trading-system_app_1 ... done
Attaching to stock-trading-system_app_1
app_1  | [DEBUG] 2000-01-03, Close, 26.27
app_1  | [DEBUG] 2000-01-04, Close, 23.95
app_1  | [DEBUG] 2000-01-05, Close, 22.68
(中略)
app_1  | [DEBUG] 2000-12-27, Close, 27.30
app_1  | [DEBUG] 2000-12-28, Close, 27.63
app_1  | [DEBUG] 2000-12-29, Close, 25.85
app_1  | Starting Portfolio Value: 100000.00
app_1  | Final Portfolio Value: 100000.00
stock-trading-system_app_1 exited with code 0
# IN_DEVELOPMENT = False のとき
$ docker-compose up
Starting stock-trading-system_app_1 ... done
Attaching to stock-trading-system_app_1
app_1  | Starting Portfolio Value: 100000.00
app_1  | Final Portfolio Value: 100000.00
stock-trading-system_app_1 exited with code 0

どうだろうか。下の「 IN_DEVELOPMENT = False のとき」には、WARN以上のログのみが出力されている(=DEBUGはレベルが低いため、出力がスキップされる)ため、出力がシンプルになっているのがわかる。

次回は実際に前日とのローソク足を比較するロジックを実装する。


  1. Pythonにはprivate変数・メソッドという概念が無いようである。プレフィクスは慣習として付けるに留まるので、インスタンスの外から見える。

(C) 2020 dogwood008 禁無断転載 不許複製 Reprinting, reproducing are prohibited.