昨日は元の呼び出し部分をそのままコピーしてきて、 KabuPlusJPCSVData
クラスに再実装することで、確かにそのメソッド内で失敗することを確認した。
how-to-make-stock-trading-system.dogwood008.com
今日以降はそれを受けて、実際に呼び出し部で失敗しないように書き換えていく。まず完成形を共有する。
!pip install backtrader
import pandas as pd import backtrader as bt path_to_csv = '/content/drive/MyDrive/Project/kabu-plus/japan-stock-prices-2_2020_9143_adjc.csv' csv = pd.read_csv(path_to_csv)
############################################################# # Copyright (C) 2020 dogwood008 (original author: Daniel Rodriguez; https://github.com/mementum/backtrader) # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see <https://www.gnu.org/licenses/>. ############################################################# import csv import itertools import io from datetime import date, datetime from backtrader.utils import date2num from typing import Any class KabuPlusJPCSVData(bt.feeds.YahooFinanceCSVData): ''' Parses pre-downloaded KABU+ CSV Data Feeds (or locally generated if they comply to the Yahoo formatg) Specific parameters: - ``dataname``: The filename to parse or a file-like object - ``reverse`` (default: ``True``) It is assumed that locally stored files have already been reversed during the download process - ``round`` (default: ``True``) Whether to round the values to a specific number of decimals after having adjusted the close - ``roundvolume`` (default: ``0``) Round the resulting volume to the given number of decimals after having adjusted it - ``decimals`` (default: ``2``) Number of decimals to round to - ``swapcloses`` (default: ``False``) [2018-11-16] It would seem that the order of *close* and *adjusted close* is now fixed. The parameter is retained, in case the need to swap the columns again arose. ''' DATE = 'date' OPEN = 'open' HIGH = 'high' LOW = 'low' CLOSE = 'close' VOLUME = 'volume' ADJUSTED_CLOSE = 'adjusted_close' params = ( ('reverse', True), ('round', True), ('decimals', 2), ('roundvolume', False), ('swapcloses', False), ('headers', True), ('header_names', { # CSVのカラム名と内部的なキーを変換する辞書 DATE: 'date', OPEN: 'open', HIGH: 'high', LOW: 'low', CLOSE: 'close', VOLUME: 'volumes', ADJUSTED_CLOSE: 'adj_close', }) ) def _fetch_value(self, values: dict, column_name: str) -> Any: ''' パラメタで指定された変換辞書を使用して、 CSVで定義されたカラム名に沿って値を取得する。 ''' index = self._column_index(self.p.header_names[column_name]) return values[index] def _column_index(self, column_name: str) -> int: ''' 与えたカラム名に対するインデックス番号を返す。 見つからなければ ValueError を投げる。 ''' return self._csv_headers.index(column_name) # copied from https://github.com/mementum/backtrader/blob/0426c777b0abdfafbb0988f5c31347553256a2de/backtrader/feed.py#L666-L679 def start(self): super(bt.feed.CSVDataBase, self).start() if self.f is None: if hasattr(self.p.dataname, 'readline'): self.f = self.p.dataname else: # Let an exception propagate to let the caller know self.f = io.open(self.p.dataname, 'r') if self.p.headers and self.p.header_names: _csv_reader = csv.reader([self.f.readline()]) self._csv_headers = next(_csv_reader) self.separator = self.p.separator def _loadline(self, linetokens): while True: nullseen = False for tok in linetokens[1:]: if tok == 'null': nullseen = True linetokens = self._getnextline() # refetch tokens if not linetokens: return False # cannot fetch, go away # out of for to carry on wiwth while True logic break if not nullseen: break # can proceed dttxt = self._fetch_value(linetokens, self.DATE) dt = date(int(dttxt[0:4]), int(dttxt[5:7]), int(dttxt[8:10])) dtnum = date2num(datetime.combine(dt, self.p.sessionend)) self.lines.datetime[0] = dtnum o = float(self._fetch_value(linetokens, self.OPEN)) h = float(self._fetch_value(linetokens, self.HIGH)) l = float(self._fetch_value(linetokens, self.LOW)) rawc = float(self._fetch_value(linetokens, self.CLOSE)) self.lines.openinterest[0] = 0.0 adjustedclose = float(self._fetch_value(linetokens, self.ADJUSTED_CLOSE)) v = float(self._fetch_value(linetokens, self.VOLUME)) if self.p.swapcloses: # swap closing prices if requested rawc, adjustedclose = adjustedclose, rawc adjfactor = rawc / adjustedclose o /= adjfactor h /= adjfactor l /= adjfactor v *= adjfactor if self.p.round: decimals = self.p.decimals o = round(o, decimals) h = round(h, decimals) l = round(l, decimals) rawc = round(rawc, decimals) v = round(v, self.p.roundvolume) self.lines.open[0] = o self.lines.high[0] = h self.lines.low[0] = l self.lines.close[0] = rawc self.lines.volume[0] = v self.lines.adjclose[0] = adjustedclose return True
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, loglevel): # Keep a reference to the "close" line in the data[0] dataseries self._dataclose = self.datas[0].close self._dataadjclose = self.datas[0].adjclose self._datavolume = self.datas[0].volume self._logger = getLogger(__name__) self.handler = StreamHandler() self.handler.setLevel(loglevel) self._logger.setLevel(loglevel) 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, Adj. Close, Volume) = ({close:>5.2f}, {adjc:>5.2f}, {vol:>010.2f})'.format( close=self._dataclose[0], adjc=self._dataadjclose[0], vol=self._datavolume[0]))
if __name__ == '__main__': cerebro = bt.Cerebro() data = KabuPlusJPCSVData( dataname=path_to_csv, fromdate=datetime(2020, 1, 1), todate=datetime(2020, 11, 30), reverse=False) cerebro.adddata(data) # Add a strategy IN_DEVELOPMENT = True # このフラグにより、ログレベルを切り替えることで、本番ではWARN以上のみをログに出すようにする。 # フラグの切り替えは、環境変数で行う事が望ましいが今は一旦先送りする。 loglevel = DEBUG if IN_DEVELOPMENT else WARN cerebro.addstrategy(TestStrategyWithLogger, loglevel) cerebro.run()
詳細な数値は掲載できないが、株式分割があった10月28日から29日にかけて、終値は半分近くになっているが、調整後終値は同程度の水準である事を確認できる。
明日以降はこの変更内容を説明する。
現在技術書典10で電子書籍発売中です!是非ご覧ください。