Pythonのバックテストフレームワーク「PyAlgoTrade」を使ってみる2 〜 チュートリアル編

Pythonのバックテストフレームワーク「PyAlgoTrade」を使ってみる1 〜 環境構築」の続き。

前回はQiitaの記事「pythonのアルゴリズムトレードライブラリ」からのコピペを使って稼働確認しただけだったので、今回は少し調べてみた。
といっても少しだけ。もう少し色々調べてから纏めたいと思っていたが、実施したら直ぐにメモしておかないと忙しいので忘れてしまう・・・。間隔もあけてしまうし・・・。

さて、本家サイトの「Tutorial」を元に少し試してみた。

#coding:utf-8
from pyalgotrade import strategy
from pyalgotrade.tools import yahoofinance
from pyalgotrade.barfeed import csvfeed
from pyalgotrade.bar import Frequency

class MyStrategy(strategy.BacktestingStrategy):
    def __init__(self, feed, instrument):
        super(MyStrategy, self).__init__(feed)
        self.__instrument = instrument

    def onBars(self, bars):
        bar = bars[self.__instrument]
        self.info(bar.getClose())

# Load the csv feed from the CSV file
csvFeed = csvfeed.GenericBarFeed(frequency=Frequency.MINUTE)
csvFeed.addBarsFromCSV("orcl5min","input/orcl5min.csv")
feedElementList = csvFeed._BarFeed__bars['orcl5min']
for elem in feedElementList:
    print elem._BasicBar__close

# Download the bars.
yahoofinanceFeed = yahoofinance.build_feed(['orcl'], 2011, 2012, ".")
yahoofinanceFeed.addBarsFromCSV("orcl","orcl-2011-yahoofinance.csv") #汎用性が無い
feedElementList = yahoofinanceFeed._BarFeed__bars['orcl']
for elem in feedElementList:
    print elem._BasicBar__close


# Evaluate the strategy with the feed's bars.
myStrategy = MyStrategy(csvFeed, "orcl5min")
myStrategy.run()

自分のStrategyクラスを作成して、そのクラスはpyalgotrade.strategyパッケージのBacktestingStrategyクラスを継承する。
Strategyの実行には、myStrategy.run()のようにする。

runを実行すると具体的に何が実行されるのかはまだ調べられていない。

とりあえず今回は株価データ取得方法を調べてみた。
今回調べた感じだと、CSV形式にしたデータを扱うのが基本のようだ。

1.CSVファイルに格納した株価データを読み込む
上記サンプルでの該当ソースは下記。

# Load the csv feed from the CSV file
csvFeed = csvfeed.GenericBarFeed(frequency=Frequency.MINUTE)
csvFeed.addBarsFromCSV("orcl5min","input/orcl5min.csv")

feedElementList = csvFeed._BarFeed__bars['orcl5min']
for elem in feedElementList:
    print elem._BasicBar__close

パッケージ構造は次図のようにしてある。

今回のサンプルソースは「pyalgoTradeSample1.py」という名前。
同じ階層にinputフォルダを作成して、orcl5min.csvというCSVファイルを配置。
それをpyalgotrade.barfeed.csvfeed.GenericBarFeedを使用して読み込む手段を用意する。
※)「pyalgotrade.barfeed.membf.BarFeed」は基底クラスのため直接使用できない。
※)ドキュメントには「A BarFeed that loads bars from CSV files」と書いてあり、BarFeedがデータを読み込んでくれるかと思ったが、この後出てくるaddBarsFromCSVメソッドを実行して読み取るのだと理解した。

BarFeedがCSVファイルから4本値を読み込むための手段を提供してくれるが、CSVフォーマットが指定されており、次のフォーマットで無ければならないことがドキュメントに書かれている。
例えば、第1列「Date Time」というカラム名を「Date」にしたらエラーとなった。Yahoo Financeからダウンロードしたデータを使用する場合は問題ないだろうが、他ベンダーからダウンロードしたデータを使う際は忘れやすいかも。
(追記ここから)
特に、「frequency=Frequency.MINUTE」を指定しているこの例の場合、「Date Time」カラムの書式は「YYYY-MM-DD hh:mm:ss」の形式で無ければエラーとなる。「YYYY/MM/DD hh:mm:ss」でも駄目だし、「YYYY-MM-DD hh:mm」でも駄目。
(追記ここまで)

Date Time,Open,High,Low,Close,Volume,Adj Close
2013-01-01 13:59:00,13.51001,13.56,13.51,13.56,273.88014126,13.51001

CSVから実際に4本値データを読み取るにはaddBarsFromCSVメソッドを使用する。

csvFeed.addBarsFromCSV("orcl5min","input/orcl5min.csv")

取得したfeedデータから試しに終値を確認するコードが下記。

feedElementList = csvFeed._BarFeed__bars['orcl5min']
for elem in feedElementList:
    print elem._BasicBar__close

実行すると次のように出力された。

38.71
38.68
38.65
38.64
38.75
38.82
38.815
38.855
38.75
・・・・

ここまでが、CSVファイルから4本値を取得する方法。
次はインターネット経由で取得する方法を試す。

2.Yahoo Financeから株価データをダウンロードして読み込む
上記サンプルで、次の部分が目的を実現するコード。
下記コードをさらっと書いたが、ここまで調べてみて、色々使い勝手が悪いことが分かった。

# Download the bars.
yahoofinanceFeed = yahoofinance.build_feed(['orcl'], 2011, 2012, ".")
yahoofinanceFeed.addBarsFromCSV("orcl","orcl-2011-yahoofinance.csv") #汎用性が無い
feedElementList = yahoofinanceFeed._BarFeed__bars['orcl']
for elem in feedElementList:
    print elem._BasicBar__close

まず、「yahoofinance.build_feed(['orcl'], 2011, 2012, ".")」によって、2011年と2012年のオラクル(Yahoo Financeでのコードは「orcl」)の4本値を取得するための手段が得られる。
しかし、yahoofinance.build_feedデータを一度CSVファイルへダウンロードする仕様となっている。
そのため、addBarsFromCSVを使用してCSVファイルからデータを読み込むにもCSVファイル名が分からないと使えない。
そこでyahoofinance.build_feedのソースを読むとbuild_feedの定義は次のようになっている。

def build_feed(instruments, fromYear, toYear, storage, frequency=bar.Frequency.DAY, timezone=None, skipErrors=False):
    """Build and load a :class:`pyalgotrade.barfeed.yahoofeed.Feed` using CSV files downloaded from Yahoo! Finance.
    CSV files are downloaded if they haven't been downloaded before.

    :param instruments: Instrument identifiers.
    :type instruments: list.
    :param fromYear: The first year.
    :type fromYear: int.
    :param toYear: The last year.
    :type toYear: int.
    :param storage: The path were the files will be loaded from, or downloaded to.
    :type storage: string.
    :param frequency: The frequency of the bars. Only **pyalgotrade.bar.Frequency.DAY** or **pyalgotrade.bar.Frequency.WEEK**
        are supported.
    :param timezone: The default timezone to use to localize bars. Check :mod:`pyalgotrade.marketsession`.
    :type timezone: A pytz timezone.
    :param skipErrors: True to keep on loading/downloading files in case of errors.
    :type skipErrors: boolean.
    :rtype: :class:`pyalgotrade.barfeed.yahoofeed.Feed`.
    """

    logger = pyalgotrade.logger.getLogger("yahoofinance")
    ret = yahoofeed.Feed(frequency, timezone)

    if not os.path.exists(storage):
        logger.info("Creating %s directory" % (storage))
        os.mkdir(storage)

    for year in range(fromYear, toYear+1):
        for instrument in instruments:
            fileName = os.path.join(storage, "%s-%d-yahoofinance.csv" % (instrument, year))
            if not os.path.exists(fileName):
                logger.info("Downloading %s %d to %s" % (instrument, year, fileName))
                try:
                    if frequency == bar.Frequency.DAY:
                        download_daily_bars(instrument, year, fileName)
                    elif frequency == bar.Frequency.WEEK:
                        download_weekly_bars(instrument, year, fileName)
                    else:
                        raise Exception("Invalid frequency")
                except Exception, e:
                    if skipErrors:
                        logger.error(str(e))
                        continue
                    else:
                        raise e
            ret.addBarsFromCSV(instrument, fileName)
    return ret

ファイル名は「fileName = os.path.join(storage, "%s-%d-yahoofinance.csv" % (instrument, year))」によって作成していることが分かるので、サンプルでは具体的に指定して動かしているが汎用性は無い。
実際、CSVファイルがダウンロードされている。

まだ調査も始めたばかりなので、自分が知らないだけかもしれないが、データは一度CSVデータに変換するしか無いとしたら非常に使いづらそう。

なお、最後のmyStrategy.run()を実行すると次のような出力がされた。
目的はpyalgoTradeをそのまま使うことではなく、設計思想を知ることなので、まだまだ先は長い。
分からんけど、複数銘柄でポートフォリオを構成したトレードも出来なさそうだし。

2017-01-04 09:35:00 strategy [INFO] 38.71
2017-01-04 09:40:00 strategy [INFO] 38.68
2017-01-04 09:45:00 strategy [INFO] 38.65
2017-01-04 09:50:00 strategy [INFO] 38.64
2017-01-04 09:55:00 strategy [INFO] 38.75
2017-01-04 10:00:00 strategy [INFO] 38.82
2017-01-04 10:05:00 strategy [INFO] 38.815
2017-01-04 10:10:00 strategy [INFO] 38.855
2017-01-04 10:15:00 strategy [INFO] 38.75
2017-01-04 10:20:00 strategy [INFO] 38.725
2017-01-04 10:25:00 strategy [INFO] 38.795
2017-01-04 10:30:00 strategy [INFO] 38.75
2017-01-04 10:35:00 strategy [INFO] 38.78
2017-01-04 10:40:00 strategy [INFO] 38.81
2017-01-04 10:45:00 strategy [INFO] 38.81
2017-01-04 10:50:00 strategy [INFO] 38.85
2017-01-04 10:55:00 strategy [INFO] 38.84
2017-01-04 11:00:00 strategy [INFO] 38.85
2017-01-04 11:05:00 strategy [INFO] 38.8499
2017-01-04 11:10:00 strategy [INFO] 38.875
2017-01-04 11:15:00 strategy [INFO] 38.9
2017-01-04 11:20:00 strategy [INFO] 38.845
2017-01-04 11:25:00 strategy [INFO] 38.855
2017-01-04 11:30:00 strategy [INFO] 38.865
2017-01-04 11:35:00 strategy [INFO] 38.855
2017-01-04 11:40:00 strategy [INFO] 38.85
2017-01-04 11:45:00 strategy [INFO] 38.84
2017-01-04 11:50:00 strategy [INFO] 38.81
2017-01-04 11:55:00 strategy [INFO] 38.815
2017-01-04 12:00:00 strategy [INFO] 38.82
・・・・・・・・・・