PyAlgoTradeの日本語解説ブログ

PyAlgoTrade の勝手に日本語解説ブログ。日本語の内容に関して保証はいたしておりません。必ず本家のサイトをご確認ください。

2015年11月30日月曜日

PyAlgoTrade  処理を分散してシミュレーション

 いままで、細かいところを見ているだけで一通り動くところはチュートリアルで試しただけなので、改めて一式動くようにしてみた。
 まず、パラメータの組み合わせを変えて試すと試行回数が莫大になる。 例えば、移動平均のパラメータだけで考えた場合、1本の移動平均線だと平均期間が2本~100本なら98回試す、エントリーとクローズに別の期間を使うのであれば98x98=9604 回 になる。さらに長期移動平均と短期移動平均を組み合わせると920億回になる。組み合わせを掛け算すると爆発的にテスト回数は増えるのだ。 それを、少しでも早く計算するために、複数のコアを持つマシンで処理を分ける方法と、複数のマシンを使って大規模にテストする方法が用意されている。
 複数のマシンは簡単に用意できないので、まずはコアがたくさんあるマシンを使おう


用意するファイルは3つ
・トレードアルゴリズムを書いた mytrade.py
・並列実行させるためのlocalworker.py
・テストに使うデータ fxdata.csv

まず、トレードアルゴリズムを書いたmytrade.pyが以下になる。ポジションを持っていないとき、単純移動平均より上ならロング、下ならショートでポジションを持つ。
ポジションを追っているときは、別の期間での単純移動平均で判定して手放す。
手持ち資金の90%で買う

__main__の下にメインプログラムを書いておくことでこのプログラム単体でも実行できる。こうしておけばデバッグが簡単になる。

#!/usr/bin/env python
# -*- coding: utf-8 -*-
import matplotlib
matplotlib.use('Agg')
from pyalgotrade import strategy
from pyalgotrade.technical import ma
from pyalgotrade.technical import cross
from pyalgotrade import plotter
from pyalgotrade.tools import yahoofinance
from pyalgotrade.stratanalyzer import sharpe
from pyalgotrade import bar
from pyalgotrade.barfeed import csvfeed

class MyTrade(strategy.BacktestingStrategy):
    def __init__(self, feed, instrument, entrySMA, exitSMA):
        strategy.BacktestingStrategy.__init__(self, feed)
        self.__instrument = instrument
        # We'll use adjusted close values, if available, instead of regular close values.
        if feed.barsHaveAdjClose():
            self.setUseAdjustedValues(True)
        self.__priceDS = feed[instrument].getPriceDataSeries()
        self.__entrySMA = ma.SMA(self.__priceDS, entrySMA)
        self.__exitSMA = ma.SMA(self.__priceDS, exitSMA)
        self.__longPos = None
        self.__shortPos = None
    #マーケットインするときに使用する単純移動平均
    def getEntrySMA(self):
        return self.__entrySMA

    #マーケットアウトするときに使用する単純移動平均
    def getExitSMA(self):
        return self.__exitSMA

    def onEnterCanceled(self, position): #ポジションを持っていたら売り、買いともに清算
        if self.__longPos == position:
            self.__longPos = None
        elif self.__shortPos == position:
            self.__shortPos = None
        else:
            assert(False)

    def onExitOk(self, position): #上と同内容
        if self.__longPos == position:
            self.__longPos = None
        elif self.__shortPos == position:
            self.__shortPos = None
        else:
            assert(False)

    def onExitCanceled(self, position): #全決済できなければ決済関数を呼ぶ
        # If the exit was canceled, re-submit it.
        position.exitMarket()

    def onBars(self, bars):
        # Wait for enough bars to be available to calculate SMA
        # 各種指標が出るまで待つ
        if self.__exitSMA[-1] is None or self.__entrySMA[-1] is None:
            return

        bar = bars[self.__instrument]
        if self.__longPos is not None:#買いポジがないとき
            if self.exitLongSignal():#終了シグナルあり
                self.__longPos.exitMarket()#買いポジ全決済
        elif self.__shortPos is not None:#売りポジないとき
            if self.exitShortSignal():#終了シグナルあり
                self.__shortPos.exitMarket()#売りポジ全決済strategy
        else:#ノーポジのとき
            if self.enterLongSignal(bar):#買い条件成立時
                #全予算の90%で買う
                shares = int(self.getBroker().getCash() * 0.9 / bars[self.__instrument].getPrice())#
                self.__longPos = self.enterLong(self.__instrument, shares, True)
            elif self.enterShortSignal(bar):#売り条件成立時
                #全予算の90%を使って売る
                shares = int(self.getBroker().getCash() * 0.9 / bars[self.__instrument].getPrice())
                self.__shortPos = self.enterShort(self.__instrument, shares, True)

    def enterLongSignal(self, bar):#買いから入る条件
        #単純移動平均がプラス
        return bar.getPrice() > self.__entrySMA[-1]

    def exitLongSignal(self):#ロングポジションを解消する条件
        #現在価格が単純移動平均とクロスした
        return cross.cross_above(self.__priceDS, self.__exitSMA) and not self.__longPos.exitActive()

    def enterShortSignal(self, bar):
        return bar.getPrice() < self.__entrySMA[-1]

    def exitShortSignal(self):
        return cross.cross_below(self.__priceDS, self.__exitSMA) and not self.__shortPos.exitActive()

if __name__ == "__main__":
    instrument = "USDJPY"
    entrySMA = 59
    exitSMA = 17

    feed = csvfeed.GenericBarFeed(bar.Frequency.DAY)
    feed.addBarsFromCSV("USDJPY","fxdata.csv")

    strat = MyTrade(feed, instrument, entrySMA, exitSMA)

    plt = plotter.StrategyPlotter(strat, True, True, True)
    plt.getInstrumentSubplot(instrument).addDataSeries("Entry SMA", strat.getEntrySMA())
    plt.getInstrumentSubplot(instrument).addDataSeries("Exit SMA", strat.getExitSMA())

    strat.run()

    fig=plt.buildFigure(None,None)
    fig.savefig("output.png")


並列実行させるためのlocalworker.pyは以下のソースになる。
アルゴリズムが変わったときは、local.run の実行プログラムを書き換える。パラメータが変わったときはparameters_generater()関数を変更する。
準備ができたらpython localworker.py で実行する。このトレードアルゴリズムでは調整パラメータが2個しかなく55x55回の試行なのですぐに終わる。

#!/usr/bin/python

import itertools
from pyalgotrade.optimizer import local
from pyalgotrade.barfeed import yahoofeed
from pyalgotrade import bar
from pyalgotrade.barfeed import csvfeed
import mytrade


def parameters_generator():
    instrument = ["USDJPY"]
    entrySMA = range(5, 60)
    exitSMA = range(5, 60)
    return itertools.product(instrument, entrySMA, exitSMA)


# The if __name__ == '__main__' part is necessary if running on Windows.
if __name__ == '__main__':
    # Load the feed from the CSV files.
    feed = csvfeed.GenericBarFeed(bar.Frequency.DAY)
    feed.addBarsFromCSV("USDJPY","fxdata.csv")

    local.run(mytrade.MyTrade, feed, parameters_generator())


データはGMOクリック証券から取得したドル円の1日分のデータを実行可能なCSVに変換したもの。出来高は10000000固定

Date Time,Open,High,Low,Close,Volume
2015-10-12 07:00:00,120.191,120.220,120.186,120.206,10000000
2015-10-12 07:01:00,120.207,120.234,120.204,120.221,10000000
2015-10-12 07:02:00,120.221,120.222,120.187,120.190,10000000
2015-10-12 07:03:00,120.189,120.198,120.176,120.176,10000000
2015-10-12 07:04:00,120.176,120.189,120.174,120.179,10000000
2015-10-12 07:05:00,120.179,120.184,120.176,120.184,10000000

実行結果ではentryが59本、exitが17本がベストであった。
これらのパラメータをmytrade.py に設定して単独実行、python mytrade.py を実行すると結果がoutput.pngに吐き出される。


さすが往復ビンタ恐ろしい数の売買をしていて笑える。


2015年11月24日火曜日

PyAlgoTrade トレードシミュレーションのやり方をチュートリアルから復習

以前にチュートリアルの直訳を行ってトレードシミュレーションを行った。



うまくいったように見えるが、正直、英文を丸写ししただけの訳なので意味がよくわかっていない。

そこで、自分なりのシミュレーションができるように細かく調べていくことにする。
まず、1日ごとに呼ばれるonBarsにコメントを入れてみた

    def onBars(self, bars):
        # Wait for enough bars to be available to calculate SMA and RSI.
        # 各種指標が出るまで待つ
        if self.__exitSMA[-1] is None or self.__entrySMA[-1] is None or self.__rsi[-1] is None:
            return

        bar = bars[self.__instrument]
        if self.__longPos is not None:#買いポジがないとき
            if self.exitLongSignal():#終了シグナルあり
                self.__longPos.exitMarket()#買いポジ全決済
        elif self.__shortPos is not None:#売りポジないとき
            if self.exitShortSignal():#終了シグナルあり
                self.__shortPos.exitMarket()#売りポジ全決済
        else:#ノーポジのとき
            if self.enterLongSignal(bar):#買い条件成立時
                #全予算の90%で買う
                shares = int(self.getBroker().getCash() * 0.9 / bars[self.__instrument].getPrice())#
                self.__longPos = self.enterLong(self.__instrument, shares, True)
            elif self.enterShortSignal(bar):#売り条件成立時
                #全予算の90%を使って売る
                shares = int(self.getBroker().getCash() * 0.9 / bars[self.__instrument].getPrice())
                self.__shortPos = self.enterShort(self.__instrument, shares, True)


このケースでは、売り買いどちらからでも入ることができるが両建てはしない。持っている現金の90%で買えるだけ買って、決済注文も全決済と潔いケースである。
マーケットインおよびマーケットアウトの指標、購入売却のところは関数になっているので、トリガーと資金マネジメントは別で考えることができる。

 何が用意された関数で、どれが自分で用意した関数なのかよくわからなくなるので整理する。

まず Brokerさんは取引に必要なお金を管理しており、getBrokerで呼び出すことができる。
Brokerクラスにはたくさんの関数があるが、getCashしか使っていない

enterLongとenerShortはstrategyクラスの関数だ。価格はいらない、数量だけでいい。後ろのTrueはセッションクローズ時に自動的にポジションクローズしない設定らしい。セッションが何を指すのかよくわからないのでTrueにしておけばよさそう。
それにしてもたくさんの機能があるが、深入りするよりはサンプルの戦略を言調べるほうがよさそう。


2015年11月15日日曜日

PyAlgoTrade で GMOクリック証券からダウンロードした時系列データーを読ませる

 最近FXを始めた、値動きが激しくて面白い。やってみてわかったことの一つに値段がブローカーによって微妙に異なるということ、つまりは取引している会社のデーターをもってきて分析しないといけない。

 私は、GMOクリック証券を使っているのだがこの会社は1分単位の時系列データを過去まで公開してくれているのでバックテストには助かる。データーをくれない会社は何か怪しい。

データーのダウンロードは マイページ→ヒストリカルデータ をたどっていけば下のページになる。
1か月分のアーカイブの中には1日単位でファイルがあり、1分足となっている。



ダウンロードしたデータを見ると、項目のヘッダが日本語になっているのと日付が数字の連続になっているためそのままでは使えない。
変換プログラムを書いた。取引量(volume)のところは一律で10000000をいれている。適当な数字でも入れておかないと、売買シミューレーション時に売買してくれないのだ。
適宜使ってほしい。

#/usr/bin/python
import sys
import csv


def GMOdataConvert(fname):
    try:
        f=open(fname,'r')
        f.readline()    #ignore japanese shift jis column define
        reader = csv.reader(f)
        for row in reader:
            dt=row[0]
            y=dt[0:4]
            m=dt[4:6]
            d=dt[6:8]
            H=dt[8:10]
            i=dt[10:12]
            s=dt[12:14]
            print("%s-%s-%s %s:%s:%s,%s,%s,%s,%s,10000000" % (y,m,d,H,i,s,row[1],row[2],row[3],row[4]))
    except IOError:
        print ("File ["+fname+"] open error \n")
        return
    else:
        f.close()

if __name__ == '__main__':
    if(len(sys.argv)<2):
        print("Usage:\nGmoFxdta.py inputfile1 inputfile2 ...\n")
        exit(1)
    print "Date Time,Open,High,Low,Close,Volume"
    for fname in sys.argv[1:]:
        GMOdataConvert(fname)
ついでと言っては何だが、csvだと簡単にmysqlに読み込ませることができる。いったん読み込ませれば任意の期間をcsvで書き出すことができるので便利だ ついでに分足から、時足、日足、月足、週足も作ってしまおう
解説はしないので、読み込むファイル名等適宜変更して使ってほしい
CREATE TABLE `minute_data` (
  `dt` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
  `st` float DEFAULT NULL,
  `hi` float DEFAULT NULL,
  `lo` float DEFAULT NULL,
  `en` float DEFAULT NULL,
  PRIMARY KEY (`dt`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

CREATE TABLE `hourly_data` (
  `dt` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
  `st` float DEFAULT NULL,
  `hi` float DEFAULT NULL,
  `lo` float DEFAULT NULL,
  `en` float DEFAULT NULL,
  PRIMARY KEY (`dt`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

CREATE TABLE `daily_data` (
  `dt` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
  `st` float DEFAULT NULL,
  `hi` float DEFAULT NULL,
  `lo` float DEFAULT NULL,
  `en` float DEFAULT NULL,
  PRIMARY KEY (`dt`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

CREATE TABLE `monthly_data` (
  `dt` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
  `st` float DEFAULT NULL,
  `hi` float DEFAULT NULL,
  `lo` float DEFAULT NULL,
  `en` float DEFAULT NULL,
  PRIMARY KEY (`dt`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;


CREATE TABLE `weekly_data` (
  `dt` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
  `st` float DEFAULT NULL,
  `hi` float DEFAULT NULL,
  `lo` float DEFAULT NULL,
  `en` float DEFAULT NULL,
  PRIMARY KEY (`dt`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

load data infile '/home/GMO/USDJPY2015data.csv' into table minute_data fields terminated by ',' ignore 1 lines;


insert into hourly_data 
select date_format(dt,'%Y-%m-%d %H:%i:%s') dt 
 ,substring_index(group_concat(st),',',1) st
 ,max(hi) hi
 ,min(lo) lo
 ,substring_index(group_concat(en),',',-1) en
 from minute_data group by date_format(dt,'%Y-%m-%d %H');

insert into daily_data
 select date_format(dt,'%Y-%m-%d %H:%i:%s') dt 
 ,substring_index(group_concat(st),',',1) st
 ,max(hi) hi
 ,min(lo) lo
 ,substring_index(group_concat(en),',',-1) en
 from hourly_data group by date_format(dt,'%Y-%m-%d');

insert into weekly_data
 select date_format(dt,'%Y-%m-%d %H:%i:%s') dt 
 ,substring_index(group_concat(st),',',1) st
 ,max(hi) hi
 ,min(lo) lo
 ,substring_index(group_concat(en),',',-1) en
 from daily_data group by date_format(dt,'%U');

insert into monthly_data
 select date_format(dt,'%Y-%m-%d %H:%i:%s') dt 
 ,substring_index(group_concat(st),',',1) st
 ,max(hi) hi
 ,min(lo) lo
 ,substring_index(group_concat(en),',',-1) en
 from daily_data group by date_format(dt,'%Y-%m');

2015年11月3日火曜日

PyAlgoTrade に付属しているデーター取得tools

チュートリアルではいつもオラクルの株価をyahooファイナンスからダウンロードしていた。他にも、googlefinanceとquandlからダウンロードするライブラリがあったので動作確認

googolefinanceはyahoofinanceと使い方は全く同じ。

例)

yahoo finance から取得

python -c "from pyalgotrade.tools import yahoofinance; yahoofinance.download_daily_bars('orcl', 2000, 'orcl-2000.csv')"

google financeから取得
python -c "from pyalgotrade.tools import googlefinance; googlefinance.download_daily_bars('orcl', 2000, 'orcl-2000.csv')"

しかしながら、googlefinanceから取得したものは日付が以下のようなフォーマットになっている

29-Dec-00,30.88,31.31,28.62,29.06,29118900
28-Dec-00,30.56,31.62,30.38,31.06,24503200

Dec-00 を2000-12に変換しないと処理できそうにない。

quandlは 様々なデータを提供しているサイト。登録なしでも使えるがアクセス回数に制限がある。登録すればAPI-KEYをもらえる。

しかしながら付属のライブラリでは404 Not Foundになる。どうやらAPIがv3になっているのでv1の機能はもう使えないらしい。残念

Quandl は自前でPythonのモジュールも提供しているので、そちらを使ったほうがよさそう。
pip install Quandl とすればインストールできる。

[参考]
PythonでQuandlからデータを取得する

2015年9月30日水曜日

PyAlgoTrade グラフを保存するためのもっとスマートな方法

 今まで、linuxコンソール以外で実行するためにplotter.pyを改造してきたが、その必要がないことがplotter.pyのソースを読んでいて分かった。
 呼び出すほうを工夫すれば無改造でかまわない。

今まで

オリジナルでは
plt.plot(None,None)
改造したライブラリでは
plt.plot(None,None,"output.png")


としていたところを、以下の2行でいい

fig=plt.buildFigure(None,None)
fig.savefig("output.png")

いい加減な情報を提供して申し訳ない。

2015年9月23日水曜日

PyAlgoTrade を使える仲間を探している

投資は自分との闘いとはいえ、一人で黙々とやっているのはうんざりしますよね。
PyAlgoTradeに関するつながりを求めて検索してみました。


Google Groups

PyAlgoTrade
https://groups.google.com/forum/#!forum/pyalgotrade

twitter
全然活発ではない
https://twitter.com/pyalgotrade

freelancer
お金を払えばpythonのプログラムを作ってくれます。
https://www.freelancer.com.au/job-search/pyalgotrade/1/

英語ばかりです。


PyAlgoTrade データ取得方法を見直して任意のデータを扱えるようにする

以前に米Yahoo!に頼らない方法としてjsmを使って日本のYahoo!から取得する方法を紹介した。


日本の時系列株価データーがほしい時にはjsmを使う
http://pyalgotradejp.blogspot.jp/2015/09/jsm.html

手元に蓄積したデータがあってそれを使いたい場合は、feedにCSVファイルを読み込むことができる。

まずはフィードにCSVから読み込む改造
9997は証券コードだが、別に何でもいい。test.csvからデータをフィードに取り込んでいる。

ポイントは3つ from pyalgotrade.barfeed import csvfeed
feed = csvfeed.GenericBarFeed(bar.Frequency.DAY)
feed.addBarsFromCSV("9997","test.csv")
あとはいつもと変わらない。
#!/usr/bin/python
# -*- coding: utf-8 -*-
import matplotlib 
matplotlib.use('Agg')
from pyalgotrade import plotter
from pyalgotrade.technical import highlow
from pyalgotrade import strategy
from pyalgotrade import bar
from pyalgotrade.barfeed import csvfeed
from pyalgotrade.technical import cross


class MyStrategy(strategy.BacktestingStrategy):
    def __init__(self, feed, instrument):
        strategy.BacktestingStrategy.__init__(self, feed)
        self.__prices = feed[instrument].getPriceDataSeries()
        self.__high = highlow.High(feed[instrument].getCloseDataSeries(),10)
        self.__low = highlow.Low(feed[instrument].getCloseDataSeries(),10)
        self.__instrument = instrument

    def onBars(self, bars):
        bar = bars[self.__instrument]
        self.info("%f Hi:%s Lo:%s" % (self.__prices[-1],self.__high[-1],self.__low[-1]))


# Load the Local feed from the CSV file
feed = csvfeed.GenericBarFeed(bar.Frequency.DAY)
feed.addBarsFromCSV("9997","test.csv")

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

plt = plotter.StrategyPlotter(myStrategy, True, True, True)
myStrategy.run()
plt.plot(None,None,"output.png")



データベースから直接読み取りたいところであるが、サポートしていないようだ。
仕方ないので、データベースからcsvで書き出すプログラムを書いた。「それSQLでできるよ」なのだが、指定のヘッダーが必ず必要なのでどうせならとpythonで書いてしまった。


#/usr/bin/python

import MySQLdb
from MySQLdb.cursors import DictCursor

import sys

argvs = sys.argv
argc = len(argvs)
if(argc != 2):
 print "usage: # python %s code" % argvs[0]
 quit()
code=argvs[1]

connection = MySQLdb.connect(db="stock",user="stock")

cursor = connection.cursor()

cursor.execute("select * from bulk_data where code=%s order by reg_date",(code,))
result=cursor.fetchall()

print "Date Time,Open,High,Low,Close,Volume"
for row in result:
 print("%s 15:00:00,%d,%d,%d,%d,%d" % (row[0],row[2],row[3],row[4],row[5],row[6]))

cursor.close()
connection.close()

ぶっちゃけシェルスクリプトで書くこともできるが、あえてpythonで書く。というのも、調整終値の欄がないからだ。
 分割などでいきなり株価が半分になっているのを権利落ち修正しないとデーターの連続性がなくなってしまう。データーがないのであればいっそ切り捨てたほうがいい。というような処理をしたいときに困るだろうという配慮だ。
 ちなみに、この銘柄は2014年1月に分割している


PyAlgoTrade の plot機能でグラフを書く

今までガン無視してきたplotについて、まとめて取り上げます。

グラフを書くにはplt クラスを定義して、strategyをrunした後にplotします。
stategyを実行した後にまとめてグラフ関係を処理したいのですが、必ず間に挟まないとダメみたいです。その割にはOnBarsで計算した値を先に指定するとか、よくよく考えれば順番がおかしい気がしますが、動いているからいいのです。

この間のvwapを使ったサンプルを改造してテストします。
出力するグラフは4枚、

まず
plotter.StrategyPlotter(strat, arg1, arg2, arg3)

arg1 値動きと取引に関するグラフのON/OFF
arg2 Buy-Sellマークを付ける
arg3 ポートフォリオのグラフのON/OFF

ある程度を組み合わせたものを下のプログラムに書きました。


import matplotlib 
matplotlib.use('Agg')
from pyalgotrade import strategy
from pyalgotrade import plotter
from pyalgotrade.tools import yahoofinance
from pyalgotrade.technical import vwap
from pyalgotrade.stratanalyzer import sharpe
import numpy as np

class VWAPMomentum(strategy.BacktestingStrategy):
    def __init__(self, feed, instrument, vwapWindowSize, threshold):
        strategy.BacktestingStrategy.__init__(self, feed)
        self.__instrument = instrument
        self.__vwap = vwap.VWAP(feed[instrument], vwapWindowSize)
        self.__threshold = threshold
        self.__price= feed[instrument].getPriceDataSeries()
        self.__delta= []
        self.__notional=[]

    def getVWAP(self):
        return self.__vwap

    def getDelta(self):
        return self.__delta

    def getNotional(self):
        return self.__notional

    def onBars(self, bars):
        vwap = self.__vwap[-1]
        if vwap is None:
            return
        price = self.__price[-1]
        shares = self.getBroker().getShares(self.__instrument)
        #price = bars[self.__instrument].getClose()
        notional = shares * price
        self.__delta.append(price-vwap)
        self.__notional.append(notional)
        if price > vwap * (1 + self.__threshold) and notional < 1000000:
            self.marketOrder(self.__instrument, 100)
        elif price < vwap * (1 - self.__threshold) and notional > 0:
            self.marketOrder(self.__instrument, -100)


def main():
    instrument = "aapl"
    vwapWindowSize = 25
    threshold = 0.01

    # Download the bars.
    feed = yahoofinance.build_feed([instrument], 2011, 2012, ".")

    strat = VWAPMomentum(feed, instrument, vwapWindowSize, threshold)
    sharpeRatioAnalyzer = sharpe.SharpeRatio()
    strat.attachAnalyzer(sharpeRatioAnalyzer)

    plt = plotter.StrategyPlotter(strat, True, True, True) #standard use
    plt.getInstrumentSubplot(instrument).addDataSeries("vwap", strat.getVWAP()) #Add VWAP Line

    plt2 = plotter.StrategyPlotter(strat, True, True, False) # without portfolio
    plt3 = plotter.StrategyPlotter(strat, False, True, True) #portfolio only

    plt4 = plotter.StrategyPlotter(strat, True, True, False) #Optional Graph
    plt4.getOrCreateSubplot("delta").addDataSeries("price-vwap", strat.getDelta())
    plt4.getOrCreateSubplot("notional").addDataSeries("notional", strat.getNotional())
    plt4.getOrCreateSubplot("notional").addLine("limit", 1000000)
    
    strat.run()
 
    plt.plot(None,None,"output.png")
    plt2.plot(None,None,"output2.png")
    plt3.plot(None,None,"output3.png")
    plt4.plot(None,None,"output4.png")

if __name__ == "__main__":
    main()


出力されるのは以下のグラフです。
output.png

output2.png

output3.png

output4.png
出力サイズは決まっているみたいなので、拡大したければ不要なグラフを削るしかなさそうです。
4番目のグラフは指標ではなくデバッグで作ってみました。
価格とVWAPを引いた値、価格と出来高をかけたnotionalの値をプロットして、意図したタイミングで売買がされているかを確認するためのものです。

2015年9月22日火曜日

PyAlgoTrade サンプルの動作を調べる RSI2を使った売買

 RSI2という戦略があるらしい。

http://stockcharts.com/school/doku.php?id=chart_school:trading_strategies:rsi2

 ざっくり説明を読んだところ、200日移動平均のような長いスパンの線に対して、長期の上昇を続けているまたはその逆に下がり続けているような場合に押し目を待って市場に参入する作戦だ。押し目の判定はRSIを使う
 「押し目待ちに押し目なし」という格言に逆らっているようだ。
 ストップロスをセットしておかないと押し目ではなく本当にブレイクしてしまうことがあるので注意。


サンプルとして掲載されていたオリジナルは2ファイルに分かれていたけど、一つにした。
でもって、ストップロスを入れていない無謀な取引


import matplotlib 
matplotlib.use('Agg')
from pyalgotrade import strategy
from pyalgotrade.technical import ma
from pyalgotrade.technical import rsi
from pyalgotrade.technical import cross
from pyalgotrade import plotter
from pyalgotrade.tools import yahoofinance
from pyalgotrade.stratanalyzer import sharpe


class RSI2(strategy.BacktestingStrategy):
    def __init__(self, feed, instrument, entrySMA, exitSMA, rsiPeriod, overBoughtThreshold, overSoldThreshold):
        strategy.BacktestingStrategy.__init__(self, feed)
        self.__instrument = instrument
        # We'll use adjusted close values, if available, instead of regular close values.
        if feed.barsHaveAdjClose():
            self.setUseAdjustedValues(True)
        self.__priceDS = feed[instrument].getPriceDataSeries()
        self.__entrySMA = ma.SMA(self.__priceDS, entrySMA)
        self.__exitSMA = ma.SMA(self.__priceDS, exitSMA)
        self.__rsi = rsi.RSI(self.__priceDS, rsiPeriod)
        self.__overBoughtThreshold = overBoughtThreshold
        self.__overSoldThreshold = overSoldThreshold
        self.__longPos = None
        self.__shortPos = None

    def getEntrySMA(self):
        return self.__entrySMA

    def getExitSMA(self):
        return self.__exitSMA

    def getRSI(self):
        return self.__rsi

    def onEnterCanceled(self, position):
        if self.__longPos == position:
            self.__longPos = None
        elif self.__shortPos == position:
            self.__shortPos = None
        else:
            assert(False)

    def onExitOk(self, position):
        if self.__longPos == position:
            self.__longPos = None
        elif self.__shortPos == position:
            self.__shortPos = None
        else:
            assert(False)

    def onExitCanceled(self, position):
        # If the exit was canceled, re-submit it.
        position.exitMarket()

    def onBars(self, bars):
        # Wait for enough bars to be available to calculate SMA and RSI.
        if self.__exitSMA[-1] is None or self.__entrySMA[-1] is None or self.__rsi[-1] is None:
            return

        bar = bars[self.__instrument]
        if self.__longPos is not None:
            if self.exitLongSignal():
                self.__longPos.exitMarket()
        elif self.__shortPos is not None:
            if self.exitShortSignal():
                self.__shortPos.exitMarket()
        else:
            if self.enterLongSignal(bar):
                shares = int(self.getBroker().getCash() * 0.9 / bars[self.__instrument].getPrice())
                self.__longPos = self.enterLong(self.__instrument, shares, True)
            elif self.enterShortSignal(bar):
                shares = int(self.getBroker().getCash() * 0.9 / bars[self.__instrument].getPrice())
                self.__shortPos = self.enterShort(self.__instrument, shares, True)

    def enterLongSignal(self, bar):
        return bar.getPrice() > self.__entrySMA[-1] and self.__rsi[-1] <= self.__overSoldThreshold

    def exitLongSignal(self):
        return cross.cross_above(self.__priceDS, self.__exitSMA) and not self.__longPos.exitActive()

    def enterShortSignal(self, bar):
        return bar.getPrice() < self.__entrySMA[-1] and self.__rsi[-1] >= self.__overBoughtThreshold

    def exitShortSignal(self):
        return cross.cross_below(self.__priceDS, self.__exitSMA) and not self.__shortPos.exitActive()



def main(plot):
    instrument = "DIA"
    entrySMA = 200
    exitSMA = 5
    rsiPeriod = 2
    overBoughtThreshold = 90
    overSoldThreshold = 10

    # Download the bars.
    feed = yahoofinance.build_feed([instrument], 2009, 2012, ".")

    strat = RSI2(feed, instrument, entrySMA, exitSMA, rsiPeriod, overBoughtThreshold, overSoldThreshold)
    sharpeRatioAnalyzer = sharpe.SharpeRatio()
    strat.attachAnalyzer(sharpeRatioAnalyzer)

    if plot:
        plt = plotter.StrategyPlotter(strat, True, True, True)
        plt.getInstrumentSubplot(instrument).addDataSeries("Entry SMA", strat.getEntrySMA())
        plt.getInstrumentSubplot(instrument).addDataSeries("Exit SMA", strat.getExitSMA())
        plt.getOrCreateSubplot("rsi").addDataSeries("RSI", strat.getRSI())
        plt.getOrCreateSubplot("rsi").addLine("Overbought", overBoughtThreshold)
        plt.getOrCreateSubplot("rsi").addLine("Oversold", overSoldThreshold)

    strat.run()
    print "Sharpe ratio: %.2f" % sharpeRatioAnalyzer.getSharpeRatio(0.05)

    if plot:
        plt.plot(None,None,"output.png")


if __name__ == "__main__":
    main(True)


結果は以下の通り。
シャープレシオは-0.68です。残念。
でも、これは実装に問題があるような気がする。移動平均と株価がクロスするような場所で使ってはいけない戦略なのに単純に平均線の上か下しか見ていないから。
 案の定クロス付近で財産を失う。




StockChartsは様々な戦略を紹介している。RSI2はこの中の戦略の一つ。


StockCharts.com - Articles
Trading Strategies and Models
http://stockcharts.com/school/doku.php?id=chart_school:trading_strategies

PyAlgoTrade サンプルの動作を調べる ボリンジャーバンドを使った売買

また、シンプルな売買プログラムに戻ります。
移動平均をボリンジャーバンドに置き換えただけの簡単なプログラムです。
2σの線にタッチしたら売りまたは買いを行います。

ボリンジャーバンドの期間は40日対象は米Yahooとmainの最初で定義しています。

import matplotlib 
matplotlib.use('Agg')
from pyalgotrade import strategy
from pyalgotrade import plotter
from pyalgotrade.tools import yahoofinance
from pyalgotrade.technical import bollinger
from pyalgotrade.stratanalyzer import sharpe


class BBands(strategy.BacktestingStrategy):
    def __init__(self, feed, instrument, bBandsPeriod):
        strategy.BacktestingStrategy.__init__(self, feed)
        self.__instrument = instrument
        self.__bbands = bollinger.BollingerBands(feed[instrument].getCloseDataSeries(), bBandsPeriod, 2)

    def getBollingerBands(self):
        return self.__bbands

    def onBars(self, bars):
        lower = self.__bbands.getLowerBand()[-1]
        upper = self.__bbands.getUpperBand()[-1]
        if lower is None:
            return

        shares = self.getBroker().getShares(self.__instrument)
        bar = bars[self.__instrument]
        if shares == 0 and bar.getClose() < lower:
            sharesToBuy = int(self.getBroker().getCash(False) / bar.getClose())
            self.marketOrder(self.__instrument, sharesToBuy)
        elif shares > 0 and bar.getClose() > upper:
            self.marketOrder(self.__instrument, -1*shares)


def main(plot):
    instrument = "yhoo"
    bBandsPeriod = 40

    # Download the bars.
    feed = yahoofinance.build_feed([instrument], 2011, 2012, ".")

    strat = BBands(feed, instrument, bBandsPeriod)
    sharpeRatioAnalyzer = sharpe.SharpeRatio()
    strat.attachAnalyzer(sharpeRatioAnalyzer)

    if plot:
        plt = plotter.StrategyPlotter(strat, True, True, True)
        plt.getInstrumentSubplot(instrument).addDataSeries("upper", strat.getBollingerBands().getUpperBand())
        plt.getInstrumentSubplot(instrument).addDataSeries("middle", strat.getBollingerBands().getMiddleBand())
        plt.getInstrumentSubplot(instrument).addDataSeries("lower", strat.getBollingerBands().getLowerBand())

    strat.run()
    print "Sharpe ratio: %.2f" % sharpeRatioAnalyzer.getSharpeRatio(0.05)

    if plot:
        plt.plot(None,None,"output.png")


if __name__ == "__main__":
    main(True)
    


実行すると現金不足のエラーが出ます。残金を確認せずに発注しているので仕方ないです。


シャープレシオは0.71をたたき出しました。いいですね。
売りと買いを同時に行っているので往復で稼いでいます。


SellとBuyにわかりやすくマークがついています。これを出すのはStrategyPlotterの3番目の引数をTrueにします。
よく見ると買った瞬間から下がってしまい、売った直後から上がってます。頭としっぽはくれてやれといいますが、ほとんど食われているような。

PyAlgoTrade サンプルの動作を調べる マーケットタイミングと移動平均のクロスを使った売買

今回はぐっと難易度が上がります。市況のデーターを合わせて判断します。


import matplotlib 
matplotlib.use('Agg')
from pyalgotrade import strategy
from pyalgotrade import plotter
from pyalgotrade.tools import yahoofinance
from pyalgotrade.technical import ma
from pyalgotrade.technical import cumret
from pyalgotrade.stratanalyzer import sharpe
from pyalgotrade.stratanalyzer import returns


class MarketTiming(strategy.BacktestingStrategy):
    def __init__(self, feed, instrumentsByClass, initialCash):
        strategy.BacktestingStrategy.__init__(self, feed, initialCash)
        self.setUseAdjustedValues(True)
        self.__instrumentsByClass = instrumentsByClass
        self.__rebalanceMonth = None
        self.__sharesToBuy = {}
        # Initialize indicators for each instrument.
        self.__sma = {}
        for assetClass in instrumentsByClass:
            for instrument in instrumentsByClass[assetClass]:
                priceDS = feed[instrument].getPriceDataSeries()
                self.__sma[instrument] = ma.SMA(priceDS, 200)

    def _shouldRebalance(self, dateTime):
        return dateTime.month != self.__rebalanceMonth

    def _getRank(self, instrument):
        # If the price is below the SMA, then this instrument doesn't rank at
        # all.
        smas = self.__sma[instrument]
        price = self.getLastPrice(instrument)
        if len(smas) == 0 or smas[-1] is None or price < smas[-1]:
            return None

        # Rank based on 20 day returns.
        ret = None
        lookBack = 20
        priceDS = self.getFeed()[instrument].getPriceDataSeries()
        if len(priceDS) >= lookBack and smas[-1] is not None and smas[-1*lookBack] is not None:
            ret = (priceDS[-1] - priceDS[-1*lookBack]) / float(priceDS[-1*lookBack])
        return ret

    def _getTopByClass(self, assetClass):
        # Find the instrument with the highest rank.
        ret = None
        highestRank = None
        for instrument in self.__instrumentsByClass[assetClass]:
            rank = self._getRank(instrument)
            if rank is not None and (highestRank is None or rank > highestRank):
                highestRank = rank
                ret = instrument
        return ret

    def _getTop(self):
        ret = {}
        for assetClass in self.__instrumentsByClass:
            ret[assetClass] = self._getTopByClass(assetClass)
        return ret

    def _placePendingOrders(self):
        remainingCash = self.getBroker().getCash() * 0.9  # Use less chash just in case price changes too much.

        for instrument in self.__sharesToBuy:
            orderSize = self.__sharesToBuy[instrument]
            if orderSize > 0:
                # Adjust the order size based on available cash.
                lastPrice = self.getLastPrice(instrument)
                cost = orderSize * lastPrice
                while cost > remainingCash and orderSize > 0:
                    orderSize -= 1
                    cost = orderSize * lastPrice
                if orderSize > 0:
                    remainingCash -= cost
                    assert(remainingCash >= 0)

            if orderSize != 0:
                self.info("Placing market order for %d %s shares" % (orderSize, instrument))
                self.marketOrder(instrument, orderSize, goodTillCanceled=True)
                self.__sharesToBuy[instrument] -= orderSize

    def _logPosSize(self):
        totalEquity = self.getBroker().getEquity()
        positions = self.getBroker().getPositions()
        for instrument in self.getBroker().getPositions():
            posSize = positions[instrument] * self.getLastPrice(instrument) / totalEquity * 100
            self.info("%s - %0.2f %%" % (instrument, posSize))

    def _rebalance(self):
        self.info("Rebalancing")

        # Cancel all active/pending orders.
        for order in self.getBroker().getActiveOrders():
            self.getBroker().cancelOrder(order)

        cashPerAssetClass = self.getBroker().getEquity() / float(len(self.__instrumentsByClass))
        self.__sharesToBuy = {}

        # Calculate which positions should be open during the next period.
        topByClass = self._getTop()
        for assetClass in topByClass:
            instrument = topByClass[assetClass]
            self.info("Best for class %s: %s" % (assetClass, instrument))
            if instrument is not None:
                lastPrice = self.getLastPrice(instrument)
                cashForInstrument = cashPerAssetClass - self.getBroker().getShares(instrument) * lastPrice
                # This may yield a negative value and we have to reduce this
                # position.
                self.__sharesToBuy[instrument] = int(cashForInstrument / lastPrice)

        # Calculate which positions should be closed.
        for instrument in self.getBroker().getPositions():
            if instrument not in topByClass.values():
                currentShares = self.getBroker().getShares(instrument)
                assert(instrument not in self.__sharesToBuy)
                self.__sharesToBuy[instrument] = currentShares * -1

    def getSMA(self, instrument):
        return self.__sma[instrument]

    def onBars(self, bars):
        currentDateTime = bars.getDateTime()

        if self._shouldRebalance(currentDateTime):
            self.__rebalanceMonth = currentDateTime.month
            self._rebalance()

        self._placePendingOrders()


def main(plot):
    initialCash = 10000
    instrumentsByClass = {
        "US Stocks": ["VTI"],
        "Foreign Stocks": ["VEU"],
        "US 10 Year Government Bonds": ["IEF"],
        "Real Estate": ["VNQ"],
        "Commodities": ["DBC"],
    }

    # Download the bars.
    instruments = ["SPY"]
    for assetClass in instrumentsByClass:
        instruments.extend(instrumentsByClass[assetClass])
    feed = yahoofinance.build_feed(instruments, 2007, 2013, "data", skipErrors=True)

    strat = MarketTiming(feed, instrumentsByClass, initialCash)
    sharpeRatioAnalyzer = sharpe.SharpeRatio()
    strat.attachAnalyzer(sharpeRatioAnalyzer)
    returnsAnalyzer = returns.Returns()
    strat.attachAnalyzer(returnsAnalyzer)

    if plot:
        plt = plotter.StrategyPlotter(strat, False, False, True)
        plt.getOrCreateSubplot("cash").addCallback("Cash", lambda x: strat.getBroker().getCash())
        # Plot strategy vs. SPY cumulative returns.
        plt.getOrCreateSubplot("returns").addDataSeries("SPY", cumret.CumulativeReturn(feed["SPY"].getPriceDataSeries()))
        plt.getOrCreateSubplot("returns").addDataSeries("Strategy", returnsAnalyzer.getCumulativeReturns())

    strat.run()
    print "Sharpe ratio: %.2f" % sharpeRatioAnalyzer.getSharpeRatio(0.05)
    print "Returns: %.2f %%" % (returnsAnalyzer.getCumulativeReturns()[-1] * 100)

    if plot:
        plt.plot(None,None,"output.png")


if __name__ == "__main__":
    main(True)
    


いやー、深く考えれば考えるほど泥沼なんでしょうか。ついにシャープレシオはマイナスになりました。

PyAlgoTrade サンプルの動作を調べる 移動平均のクロスを使った売買

今までさんざん取り扱ってきた移動平均であるが、あらためてゴールデンクロスで買ってデッドクロスで売る戦略である。
 今回のサンプルは売買とメインプログラムの2つに分かれている。

売買プログラム

以下のプログラムをsma_crossover.py という名前で保存します。
from pyalgotrade import strategy
from pyalgotrade.technical import ma
from pyalgotrade.technical import cross


class SMACrossOver(strategy.BacktestingStrategy):
    def __init__(self, feed, instrument, smaPeriod):
        strategy.BacktestingStrategy.__init__(self, feed)
        self.__instrument = instrument
        self.__position = None
        # We'll use adjusted close values instead of regular close values.
        self.setUseAdjustedValues(True)
        self.__prices = feed[instrument].getPriceDataSeries()
        self.__sma = ma.SMA(self.__prices, smaPeriod)

    def getSMA(self):
        return self.__sma

    def onEnterCanceled(self, position):
        self.__position = None

    def onExitOk(self, position):
        self.__position = None

    def onExitCanceled(self, position):
        # If the exit was canceled, re-submit it.
        self.__position.exitMarket()

    def onBars(self, bars):
        # If a position was not opened, check if we should enter a long position.
        if self.__position is None:
            if cross.cross_above(self.__prices, self.__sma) > 0:
                shares = int(self.getBroker().getCash() * 0.9 / bars[self.__instrument].getPrice())
                # Enter a buy market order. The order is good till canceled.
                self.__position = self.enterLong(self.__instrument, shares, True)
        # Check if we have to exit the position.
        elif not self.__position.exitActive() and cross.cross_below(self.__prices, self.__sma) > 0:
            self.__position.exitMarket()


新しい関数です。
 self.setUseAdjustedValues(True) 調整終値を使う
getBroker().getCash() ブローカーからお金を引き出す。
 これを使っているので前回のようにキャッシュ不足にはならない。 購入数量を 使えるお金*0.9/価格 としている。 とはいえ,マニュアルにはBrokerクラスは基底クラスなので直接使うなと書いてあるし。

 以下3つのコールバックは必要なんでしょうか。デフォルトじゃダメですか
def onEnterCanceled(self, position):
def onExitOk(self, position):
def onExitCanceled(self, position):

削っても動いたのでなくてもいいみたい。
ただしgetSMAはあとでグラフを書くときに必要なので削っちゃいけない。


メインプログラム
以下のプログラムをmain.py という名前で保存します。
python main.py で実行します。
import matplotlib 
matplotlib.use('Agg')
import sma_crossover
from pyalgotrade import plotter
from pyalgotrade.tools import yahoofinance
from pyalgotrade.stratanalyzer import sharpe


def main(plot):
    instrument = "aapl"
    smaPeriod = 163

    # Download the bars.
    feed = yahoofinance.build_feed([instrument], 2011, 2012, ".")

    strat = sma_crossover.SMACrossOver(feed, instrument, smaPeriod)
    sharpeRatioAnalyzer = sharpe.SharpeRatio()
    strat.attachAnalyzer(sharpeRatioAnalyzer)

    if plot:
        plt = plotter.StrategyPlotter(strat, True, False, True)
        plt.getInstrumentSubplot(instrument).addDataSeries("sma", strat.getSMA())

    strat.run()
    print "Sharpe ratio: %.2f" % sharpeRatioAnalyzer.getSharpeRatio(0.05)

    if plot:
        plt.plot(None,None,"output.png")


if __name__ == "__main__":
    main(True)

なんというかね、ひとつ前のVWAPより成績がいいわけですよ。
シンプル イズ ベストとかいう問題じゃなくて、工夫することが無駄に思えるようで落ち込むのですが、頑張りましょう。

PyAlgoTrade サンプルの動作を調べる VWAPを使った売買

リファレンスマニュアルを淡々と読んでいると眠くなってくるので、一足飛びにサンプルを見てみる。
題材としてはVWAPを使ったApple株の取りひき。Appleなんて適当に買っても利益が出そうだ。
20年前の俺にあったらひとこと言いたい「ゴミくずとか言わないでAppleかっとけ」
いや、後悔先に立たずなので未来に向かって分析をしようか。

もともとのサンプルはXindows環境でプロットするが、SSHでリモートログインしているので画面がテキストしかない。 よって、ファイルに吐き出してみた。 もちろん、事前にplot関巣の改造は必要だ。以下を参照のこと
  PyAlgoTradeが出力するグラフの出力先を変更したい


import matplotlib 
matplotlib.use('Agg')
from pyalgotrade import strategy
from pyalgotrade import plotter
from pyalgotrade.tools import yahoofinance
from pyalgotrade.technical import vwap
from pyalgotrade.stratanalyzer import sharpe


class VWAPMomentum(strategy.BacktestingStrategy):
    def __init__(self, feed, instrument, vwapWindowSize, threshold):
        strategy.BacktestingStrategy.__init__(self, feed)
        self.__instrument = instrument
        self.__vwap = vwap.VWAP(feed[instrument], vwapWindowSize)
        self.__threshold = threshold

    def getVWAP(self):
        return self.__vwap

    def onBars(self, bars):
        vwap = self.__vwap[-1]
        if vwap is None:
            return

        shares = self.getBroker().getShares(self.__instrument)
        price = bars[self.__instrument].getClose()
        notional = shares * price

        if price > vwap * (1 + self.__threshold) and notional < 1000000:
            self.marketOrder(self.__instrument, 100)
        elif price < vwap * (1 - self.__threshold) and notional > 0:
            self.marketOrder(self.__instrument, -100)


def main(plot):
    instrument = "aapl"
    vwapWindowSize = 5
    threshold = 0.01

    # Download the bars.
    feed = yahoofinance.build_feed([instrument], 2011, 2012, ".")

    strat = VWAPMomentum(feed, instrument, vwapWindowSize, threshold)
    sharpeRatioAnalyzer = sharpe.SharpeRatio()
    strat.attachAnalyzer(sharpeRatioAnalyzer)

    if plot:
        plt = plotter.StrategyPlotter(strat, True, False, True)
        plt.getInstrumentSubplot(instrument).addDataSeries("vwap", strat.getVWAP())

    strat.run()
    print "Sharpe ratio: %.2f" % sharpeRatioAnalyzer.getSharpeRatio(0.05)

    if plot:
        plt.plot(None,None,"output.png")

if __name__ == "__main__":
    main(True)

出力結果は以下の通り。はい。儲かりました。
 

さて、お分かりだと思いますがVWAPの復習です。


VWAPの重要な3つの機能
VWAPの重要な機能として、以下の3点があると言えます。
  • 市場参加者の強気・弱気を判断する
  • 抵抗線、支持線として機能する
  • ファンド(正確には証券会社)などが買いを入れる(VWAP取引)基準として使用する
1 市場参加者の強気・弱気を判断する
ちなみに株価がVWAPを上回っている状態では、今日買った人全員の損益を合計したらプラスで、逆に株価がVWAPを下回っている場合はマイナスだ、ということがわかります。つまり、株価がVWAPを上回っている時には、短期的には強含みでその後も買い目線が続くだろうと判断できるわけです。逆に下回る時なんかは弱含みとなり、買いの手は控えようと考える投資家がおおくなるため、軟調に推移することが多くなります。
2 抵抗線、支持線として機能する
また、VWAPには抵抗・支持というその先の付近に株価が近づくと跳ね返されるという機能があります。
当然VWAPを見てトレードをしている人がおおく、VWAPトレーダーと呼ばれる「VWAPに近づくと買いを入れる」トレーダーも多く存在するため(後述)、その付近ではいったん株価は反発することになります。
そのことを知っておけば、移動平均線やトレンドラインにもう1つ抵抗・支持の役割をもつ指標を自分のトレードの手法の一つとして持っておくことができますよね。
3 ファンド(正確には証券会社)などが買いを入れる(VWAP取引)基準として使用する
ファンドや投資家などが市場で買いを入れると、多量の注文の場合はマーケットインパクトが大きくなって市場に混乱をきたします。そのため、そのような大口の注文は証券会社などによって上手に捌いてもらう必要が有るのです。
そしてそのために使用されるのがこれまで説明してきたVWAPであり、このような取引をVWAP取引といいます。
投資の教科書より


改めてプログラムを見直していきます。
 vwap.VWAP(feed[instrument], vwapWindowSize) でvwapを計算します。ウィンドウサイズを5にしています。
 肝心の売買はonBarsです。 価格がvwap*(1+調整分)より高かったら100買います。価格がvwap*(1-調整分)低かったら100売ります。調整分がついているのはわずかな変動は無視するためでしょう。notional という出来高*価格という指標がついているのですが意味は分かりません。
調整分は0.01にしています。
買えるだけ買って売れるだけ売るので、現金不足や株不足が起きます。
その時は以下のようなメッセージが出ます。
2012-01-25 00:00:00 broker.backtesting [DEBUG] Not enough cash to fill aapl order [190] for 100 share/s


売買ロボットの評価については利回りだけでなくシャープレシオというものを使う。 sharpeRatioAnalyzer.getSharpeRatio(0.05)の0.05が無リスクのときの利回り。

そもそもシャープレシオって何だ?これだ

 [AllAbout]シャープレシオは投資の効率性を示す指標

PyAlgoTrade その他の指標がわかりません

その他、計算はできるけど意味がわからなかった指標、いや正確には英語は問題なくて、どのように使うのかがイメージできませんでした。



  • ATR Average True Range
  • ROC Rate Of Change
  • Cumulative Return
  • Hurst Exponent
  • Least Squares Regression 最小二乗法 およびそれを使ったSlope
  • Standard deviation filter 標準偏差
  • Z-Score filter Z検定


たぶん、最小二乗法を使ってトレンドラインを引くんじゃないかと思うんだが


動画も日本語のものがない。

ATR
Hurst Exponent


追記

 ATRわかった。

■リスク管理の基本の指標「ATR」

http://zai.diamond.jp/articles/-/157970?page=3


「今日のATRがいくらなのかは、すぐに計算できます。計算に使う数字は前日の終値と、今日の高値・安値だけです」
 ATRの計算って、意外と簡単。まずは今日のTR(トゥルー・レンジ)を求める。TRは次の3つの数字のうち、最大のものだ。
(1)当日高値-前日終値(2)前日終値-当日安値(3)当日高値-当日安値 この3つの数字のなかで最大のものが「今日のTR」。当日を含め過去20日分のTRを平均化するとATRとなる。エクセルに計算式を入力しておくと、日々の四本値を入れるだけで計算できて便利かも。
動画も日本語に

ROCもわかった。
テクニカル分析入門
http://www.fx-soken.co.jp/tech/roc.html

ROCは前ページで解説したモメンタムの改良版です。モメンタムでは、本日と〇日前の単純な値差をプロットしていきます。そのため、表示する期間や銘柄の違いで値動きの幅が異なれば、目盛りの間隔も異なってきます。また、月足で長期間の動向を見る場合、初めのほうと終わりのほうで相場の水準が大きく異なると、見た目で比較することができません
   そのため、絶対的な値差ではなく変化率をプロットすればよいという考え方が出てきます。これがROC(Rate of Change)です。計算式は次のようになります。
  • 本日のROC = (本日のレート ÷ 〇日前のレート) × 100
こちらも動画も日本語に

PyAlgoTrade 各種指標を判断する 価格で判断、ライン、直近高値、安値

 チャートを見ないで売買するとよくわからないが、チャートを見てしまうと「新高値?ないない」とか言ってチャンスをドブに捨ててしまう根拠のない値段のことです。
 適当なことを言ってすいません。
 でも心理的なものですよ、たぶん。人間が売買する以上は重要ですけどね。


毎日10営業日前までの高値、安値を計算しました。
#!/usr/bin/python
# -*- coding: utf-8 -*-
import logging
from pyalgotrade.technical import highlow
from pyalgotrade import strategy
from pyalgotrade.barfeed import yahoofeed
from pyalgotrade.technical import cross

logging.basicConfig(filename='/tmp/exec.log')

class MyStrategy(strategy.BacktestingStrategy):
    def __init__(self, feed, instrument):
        strategy.BacktestingStrategy.__init__(self, feed)
        self.__prices = feed[instrument].getPriceDataSeries()
        self.__high = highlow.High(feed[instrument].getCloseDataSeries(),10)
        self.__low = highlow.Low(feed[instrument].getCloseDataSeries(),10)
        self.__instrument = instrument

    def onBars(self, bars):
        bar = bars[self.__instrument]
        self.info("%f Hi:%s Lo:%s" % (self.__prices[-1],self.__high[-1],self.__low[-1]))


# Load the yahoo feed from the CSV file
feed = yahoofeed.Feed()
feed.addBarsFromCSV("orcl", "orcl-2000.csv")

# Evaluate the strategy with the feed's bars.
myStrategy = MyStrategy(feed, "orcl")
myStrategy.run()
で、結果がこうなる

2000-11-27 00:00:00 strategy [INFO] 23.125000 Hi:28.875 Lo:22.3125
2000-11-28 00:00:00 strategy [INFO] 22.656250 Hi:28.875 Lo:22.3125
2000-11-29 00:00:00 strategy [INFO] 22.875000 Hi:28.875 Lo:22.3125
2000-11-30 00:00:00 strategy [INFO] 26.500000 Hi:28.8125 Lo:22.3125
2000-12-01 00:00:00 strategy [INFO] 26.437500 Hi:28.8125 Lo:22.3125
2000-12-04 00:00:00 strategy [INFO] 28.187500 Hi:28.1875 Lo:22.3125
2000-12-05 00:00:00 strategy [INFO] 31.500000 Hi:31.5 Lo:22.3125
2000-12-06 00:00:00 strategy [INFO] 30.187500 Hi:31.5 Lo:22.3125
2000-12-07 00:00:00 strategy [INFO] 28.312500 Hi:31.5 Lo:22.65625
2000-12-08 00:00:00 strategy [INFO] 30.062500 Hi:31.5 Lo:22.65625
2000-12-11 00:00:00 strategy [INFO] 31.937500 Hi:31.9375 Lo:22.65625
2000-12-12 00:00:00 strategy [INFO] 30.750000 Hi:31.9375 Lo:22.875
2000-12-13 00:00:00 strategy [INFO] 28.375000 Hi:31.9375 Lo:26.4375
2000-12-14 00:00:00 strategy [INFO] 27.500000 Hi:31.9375 Lo:26.4375
2000-12-15 00:00:00 strategy [INFO] 28.562500 Hi:31.9375 Lo:27.5
2000-12-18 00:00:00 strategy [INFO] 32.000000 Hi:32.0 Lo:27.5
2000-12-19 00:00:00 strategy [INFO] 30.625000 Hi:32.0 Lo:27.5
2000-12-20 00:00:00 strategy [INFO] 28.500000 Hi:32.0 Lo:27.5
2000-12-21 00:00:00 strategy [INFO] 29.500000 Hi:32.0 Lo:27.5
2000-12-22 00:00:00 strategy [INFO] 31.875000 Hi:32.0 Lo:27.5
2000-12-26 00:00:00 strategy [INFO] 30.937500 Hi:32.0 Lo:27.5
何をもって10日なのかと聞かれたら適当ですと答えます。

PyAlgoTrade 各種指標を判断する まずはゴールデンクロスとデットクロス

 トレーディングライブラリについて3回に分けて調べてきた。各種指標が若干違うとはいえ同じように呼び出せるのはありがたい。計算式自体はエクセルでもできるわけだが、ちゃんとデバッグ済みなのが嬉しい。ここでバグ出すとペナルティでかいからね。

さて、指標を使う上でのいくつかの補足。

まず終値を当日だけでなくそれ以前も参照したいときは、あらかじめgetPriceDataSeries()の値をストラテジーのメンバ変数に代入しておく。
2つ目は、仕様がクロスしたときの検出、例えば平均移動線を価格が抜いていったときのゴールデンクロスの検出はcross_aboveを使う
例によってテストしたコードは下の通り

#!/usr/bin/python
# -*- coding: utf-8 -*-
import logging
from pyalgotrade.technical import ma
from pyalgotrade import strategy
from pyalgotrade.barfeed import yahoofeed
from pyalgotrade.technical import cross

logging.basicConfig(filename='/tmp/exec.log')

class MyStrategy(strategy.BacktestingStrategy):
    def __init__(self, feed, instrument):
        strategy.BacktestingStrategy.__init__(self, feed)
        self.__prices = feed[instrument].getPriceDataSeries()
        self.__sma = ma.SMA(feed[instrument].getCloseDataSeries(), 15)
        self.__instrument = instrument

    def onBars(self, bars):
        if self.__sma[-1] is None:
            return
        bar = bars[self.__instrument]
        flag1 = 1 if self.__prices[-1] > self.__sma[-1] else 0
        flag2 = cross.cross_above(self.__prices,self.__sma)
        self.info("%f %f SMA:%f %d %d " % (bar.getClose(),self.__prices[-1],self.__sma[-1],flag1,flag2))


# Load the yahoo feed from the CSV file
feed = yahoofeed.Feed()
feed.addBarsFromCSV("orcl", "orcl-2000.csv")

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

で、結果は以下の通り
2000-11-14 00:00:00 strategy [INFO] 28.375000 28.375000 SMA:29.570833 0 0 
2000-11-15 00:00:00 strategy [INFO] 28.875000 28.875000 SMA:29.204167 0 0 
2000-11-16 00:00:00 strategy [INFO] 27.375000 27.375000 SMA:28.758333 0 0 
2000-11-17 00:00:00 strategy [INFO] 28.812500 28.812500 SMA:28.400000 1 1 
2000-11-20 00:00:00 strategy [INFO] 24.750000 24.750000 SMA:27.941667 0 0 
2000-11-21 00:00:00 strategy [INFO] 23.875000 23.875000 SMA:27.333333 0 0 
2000-11-22 00:00:00 strategy [INFO] 22.312500 22.312500 SMA:26.729167 0 0 
2000-11-24 00:00:00 strategy [INFO] 24.125000 24.125000 SMA:26.366667 0 0 
2000-11-27 00:00:00 strategy [INFO] 23.125000 23.125000 SMA:25.887500 0 0 
2000-11-28 00:00:00 strategy [INFO] 22.656250 22.656250 SMA:25.535417 0 0 
2000-11-29 00:00:00 strategy [INFO] 22.875000 22.875000 SMA:25.289583 0 0 
2000-11-30 00:00:00 strategy [INFO] 26.500000 26.500000 SMA:25.402083 1 1 
2000-12-01 00:00:00 strategy [INFO] 26.437500 26.437500 SMA:25.352083 1 0 
2000-12-04 00:00:00 strategy [INFO] 28.187500 28.187500 SMA:25.535417 1 0 
2000-12-05 00:00:00 strategy [INFO] 31.500000 31.500000 SMA:25.985417 1 0 
2000-12-06 00:00:00 strategy [INFO] 30.187500 30.187500 SMA:26.106250 1 0 
2000-12-07 00:00:00 strategy [INFO] 28.312500 28.312500 SMA:26.068750 1 0 
2000-12-08 00:00:00 strategy [INFO] 30.062500 30.062500 SMA:26.247917 1 0 
2000-12-11 00:00:00 strategy [INFO] 31.937500 31.937500 SMA:26.456250 1 0 
2000-12-12 00:00:00 strategy [INFO] 30.750000 30.750000 SMA:26.856250 1 0 
2000-12-13 00:00:00 strategy [INFO] 28.375000 28.375000 SMA:27.156250 1 0 
2000-12-14 00:00:00 strategy [INFO] 27.500000 27.500000 SMA:27.502083 0 0 
2000-12-15 00:00:00 strategy [INFO] 28.562500 28.562500 SMA:27.797917 1 1 
2000-12-18 00:00:00 strategy [INFO] 32.000000 32.000000 SMA:28.389583 1 0 
2000-12-19 00:00:00 strategy [INFO] 30.625000 30.625000 SMA:28.920833 1 0 
2000-12-20 00:00:00 strategy [INFO] 28.500000 28.500000 SMA:29.295833 0 0 
2000-12-21 00:00:00 strategy [INFO] 29.500000 29.500000 SMA:29.495833 1 1 
2000-12-22 00:00:00 strategy [INFO] 31.875000 31.875000 SMA:29.858333 1 0 
2000-12-26 00:00:00 strategy [INFO] 30.937500 30.937500 SMA:30.041667 1 0 
2000-12-27 00:00:00 strategy [INFO] 30.687500 30.687500 SMA:29.987500 1 0 
2000-12-28 00:00:00 strategy [INFO] 31.062500 31.062500 SMA:30.045833 1 0 
2000-12-29 00:00:00 strategy [INFO] 29.062500 29.062500 SMA:30.095833 0 0 

・getPriceDataSeries()と__pricesは同じ値段になっている。
・単純比較とは違って、下から上にクロスしたときだけcross_aboveが1になっている。
同様にデッドクロスを検出するためのcross_belowもある。
これらは第3引数に何本前の値までさかのぼるかを指定できるが、デフォルトでは-2で2本分だけ。





2015年9月21日月曜日

PyalgTrade で ボリンジャーバンド(BollingerBands)を使う

非常に使っている人が多いボリンジャーバンドについて触れないわけにはいくまい。いや、その他ATRとかCumulativeReturnとかは何を言っているのかわからないのでパス。わかったら書きます。

ボリンジャーバンドは中心を移動平均線、その上と下にσの線を引きます。2σの線を超えることは95%ないということらしいのですが、これで大やけどをする人もいいるそうなので使い方には注意が必要です。

さて、ボリンジャーバンドを使うプログラムは以下のようになります。σ=2を指定してます。



#!/usr/bin/python
# -*- coding: utf-8 -*-
import logging
from pyalgotrade.technical import bollinger
from pyalgotrade import strategy
from pyalgotrade.barfeed import yahoofeed

logging.basicConfig(filename='/tmp/exec.log')

class MyStrategy(strategy.BacktestingStrategy):
    def __init__(self, feed, instrument):
        strategy.BacktestingStrategy.__init__(self, feed)
        sigma=2
        self.__bband = bollinger.BollingerBands(feed[instrument].getCloseDataSeries(),15, sigma)
        self.__bbandLow = self.__bband.getLowerBand()
        self.__bbandMiddle = self.__bband.getMiddleBand()
        self.__bbandUpper = self.__bband.getUpperBand()
        self.__instrument = instrument

    def onBars(self, bars):
        bar = bars[self.__instrument]
        self.info("%f Low:%s Middle:%s Upper:%s" % (bar.getClose(),self.__bbandLow[-1],self.__bbandMiddle[-1],self.__bbandUpper[-1]))


# Load the yahoo feed from the CSV file
feed = yahoofeed.Feed()
feed.addBarsFromCSV("orcl", "orcl-2000.csv")

# Evaluate the strategy with the feed's bars.
myStrategy = MyStrategy(feed, "orcl")
myStrategy.run()
解説は各動画で

kabu.comの解説
 ボリンジャーバンド

PyAlgoTrade MACD RSI StochasticOscillator RateOfChange モメンタム指標を使う



モメンタム(慣性)ってなんだっけ

モメンタム投資とは、現在の株価のトレンドが継続することを想定した投資ストラテジーです

15分でわかるモメンタム投資より

 ということで、昨日まで上がっていたものは上がるし下がっていたものは下がる。毎日変わるような相場は休め。という戦略らしい。

PyAlgoTradeで使えるモメンタムに関する指標は以下の通り

  • MACD
  • RSI
  • StochasticOscillator
  • RateOfChange

早速プログラムで使ってみる。まずはMACD

#!/usr/bin/python
# -*- coding: utf-8 -*-
import logging
from pyalgotrade.technical import macd
from pyalgotrade import strategy
from pyalgotrade.barfeed import yahoofeed

logging.basicConfig(filename='/tmp/exec.log')

class MyStrategy(strategy.BacktestingStrategy):
    def __init__(self, feed, instrument):
        strategy.BacktestingStrategy.__init__(self, feed)
        firstEMA=12
        slowEMA=26
        signalEMA=9
        self.__macd = macd.MACD(feed[instrument].getCloseDataSeries(),firstEMA, slowEMA, signalEMA)
        self.__macdHistgram=self.__macd.getHistogram()
        self.__macdSignal=self.__macd.getSignal()
        self.__instrument = instrument

    def onBars(self, bars):
        bar = bars[self.__instrument]
        self.info("%f MACD:%s Histgram:%s Signal:%s " % (bar.getClose(),self.__macd[-1],self.__macdHistgram[-1],self.__macdSignal[-1]))


# Load the yahoo feed from the CSV file
feed = yahoofeed.Feed()
feed.addBarsFromCSV("orcl", "orcl-2000.csv")

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

MACDの3つの追加パラメーターについて補足説明。 長期の移動平均と短期の移動平均の差を求めて計算するためそれぞれ指定する、またMACDの移動平均となるシグナル線の期間も指定する。標準は26,12,9が使われる。別のソフトだけど以下の動画の説明が詳しい。


次はRSI、買われすぎや売られすぎの指標として補助的に移動平均と一緒に使われることがあ多いが、ライブラリもまた移動平均と同様にデータ列と期間を与えることで使用することができる。

#!/usr/bin/python
# -*- coding: utf-8 -*-
import logging
from pyalgotrade.technical import rsi
from pyalgotrade.technical import ma
from pyalgotrade.technical import vwap
from pyalgotrade import strategy
from pyalgotrade.barfeed import yahoofeed

logging.basicConfig(filename='/tmp/exec.log')

class MyStrategy(strategy.BacktestingStrategy):
    def __init__(self, feed, instrument):
        strategy.BacktestingStrategy.__init__(self, feed)
        self.__rsi = rsi.RSI(feed[instrument].getCloseDataSeries(), 15)
        self.__instrument = instrument

    def onBars(self, bars):
        bar = bars[self.__instrument]
        self.info("%f RSI:%s " % (bar.getClose(),self.__rsi[-1]))


# Load the yahoo feed from the CSV file
feed = yahoofeed.Feed()
feed.addBarsFromCSV("orcl", "orcl-2000.csv")

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



こちらもMACD同様に動画の説明が詳しい
Stochastic oscillator (ストキャスティクス)と ROCもRSI同様に計算できる。
#!/usr/bin/python
# -*- coding: utf-8 -*-
import logging
from pyalgotrade.technical import rsi
from pyalgotrade.technical import stoch
from pyalgotrade.technical import roc
from pyalgotrade.technical import ma
from pyalgotrade.technical import vwap
from pyalgotrade import strategy
from pyalgotrade.barfeed import yahoofeed

logging.basicConfig(filename='/tmp/exec.log')

class MyStrategy(strategy.BacktestingStrategy):
    def __init__(self, feed, instrument):
        strategy.BacktestingStrategy.__init__(self, feed)
        self.__rsi = rsi.RSI(feed[instrument].getCloseDataSeries(), 15)
        self.__so = stoch.StochasticOscillator(feed[instrument], 15)
        self.__roc =roc.RateOfChange(feed[instrument].getCloseDataSeries(), 15)
        self.__instrument = instrument

    def onBars(self, bars):
        bar = bars[self.__instrument]
        self.info("%f RSI:%s StochasticOscillator:%s RateOfChange:%s " % (bar.getClose(),self.__rsi[-1],self.__so[-1],self.__roc[-1]))


# Load the yahoo feed from the CSV file
feed = yahoofeed.Feed()
feed.addBarsFromCSV("orcl", "orcl-2000.csv")

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

動画の説明はわかりやすくていいな。でもROCの説明動画は見つかりませんでした





文章での説明は、証券会社各社で提供されているがカブドッドコム証券のテクニカル分析ABCがわかりやすかった。
 テクニカル分析ABC http://kabu.com/investment/guide/technical/default.html


2015年9月20日日曜日

PyAlgoTradeで使える移動平均いろいろ

仕様を調べていたら、売買まで進みません。まあ、間違った使い方で無駄に一喜一憂するよりはマシ、マーケットは明日もあいている。ということで、


移動平均については以下の4つをサポートしているが、SMAとEMA以外は呼び出し方が違う。



  • SMA(Simple Moving Average)・・・単純移動平均線
  • EMA(Exponential Moving Average)・・・指数移動平均線
  • WMA(Weighted Moving Average)・・・加重移動平均線
  • VWAP (Volume Weighted Average Price) 出来高加重移動平均

SMA->EMA->WMAの順に直近の価格が優先される。このソフトの場合のWMAは比率を自分で決めることができる。
たとえば、昨日:一昨日を3:1で平均を取ったのが以下のサンプル。とはいえほぼ昨日の株価になってしまうので、10日分ぐらいはブレンドするよね。

VMAPは所属しているライブラリが違う。また、引数としては株価データを全部渡す。計算には価格と出来高が必要だから。ちなみに読み方は「ブイワップ」でかっこいい。
株価がVWAPを上回っている時は、今日買った人全員の損益を合計したらプラス、逆に株価がVWAPを下回っている場合はマイナスになる。

それでは、全部計算してみましょう


#!/usr/bin/python
# -*- coding: utf-8 -*-
import logging
from pyalgotrade.technical import ma
from pyalgotrade.technical import vwap
from pyalgotrade import strategy
from pyalgotrade.barfeed import yahoofeed

logging.basicConfig(filename='/tmp/exec.log')

class MyStrategy(strategy.BacktestingStrategy):
    def __init__(self, feed, instrument):
        strategy.BacktestingStrategy.__init__(self, feed)
 self.__sma = ma.SMA(feed[instrument].getCloseDataSeries(), 15)
 self.__ema = ma.EMA(feed[instrument].getCloseDataSeries(), 15)
 self.__wma = ma.WMA(feed[instrument].getCloseDataSeries(), (1,2))
 self.__vwap =vwap.VWAP(feed[instrument], 15)
        self.__instrument = instrument

    def onBars(self, bars):
        bar = bars[self.__instrument]
        self.info("%f sma:%s ema:%s wma:%s vwap:%s " % (bar.getClose(),self.__sma[-1],self.__ema[-1],self.__wma[-1],self.__vwap[-1]))


# Load the yahoo feed from the CSV file
feed = yahoofeed.Feed()
feed.addBarsFromCSV("orcl", "orcl-2000.csv")

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

次はRSIなどのモメンタムかな。

2015年9月16日水曜日

PyAlgoTrade 今までのおさらい

「よーし、OKだなんでも書けるぞ」と思ったら1行もかけない罠 そこで復讐いや復習です。
 基本のプログラムを最小限のものに絞り込みます。
class MyStrategy(strategy.BacktestingStrategy):
    def __init__(self, feed, instrument):
       pass
    def onBars(self, bars):
       pass

feed = yahoofeed.Feed()
myStrategy = MyStrategy(feed, "orcl")
myStrategy.run()



つまり、  プログラム MyStrategyクラスを定義 onBarsを書く 株価をfeedとして取得し feedを引数にしてMyStrategyのクラスを作成 それをrun() 実行結果 feedのすべてのレコード一つ一つに対してMyStrategyのonBarsが呼び出される 唯一の引数はbars、feedで与えられた価格のほかに各種指標の当日分が入っている。

 onBarsの中ではprint関数を使うこともできるが、self.info()関数を使うと、日時をつけて表示してくれる。info 同様に debug,warning,error,criticalなどが用意されているので適宜使い分ける。
import logging で logging.basicConfigを設定するとファイルやシステムログに記録することができる。

 onBars関数の中では、1日分のデーターが取得できる。各値はメソッド関数を呼び出して使用する

  • bar.getOpen()  始値
  • bar.getHigh() 高値
  • bar.getLow() 安値
  • bar.getClose() 終値
  • bar.getAdjClose() 調整終値
  • bar.getPrice() 終値か調整終値のどちらか
これらを表示させると以下のようなプログラムになる。


#!/usr/bin/python
# -*- coding: utf-8 -*-
import logging
from pyalgotrade import strategy
from pyalgotrade.barfeed import yahoofeed

logging.basicConfig(filename='/tmp/exec.log')

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

    def onBars(self, bars):
        bar = bars[self.__instrument]
        self.info("%f %f %f %f %f %d" % (bar.getOpen(),bar.getHigh(),bar.getLow(),bar.getClose(),bar.getAdjClose(),bar.getVolume()))


# Load the yahoo feed from the CSV file
feed = yahoofeed.Feed()
feed.addBarsFromCSV("orcl", "orcl-2000.csv")

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

当日分のデータだけだと、値段と取引量の絶対値と陽線か陰線かしかわからない。
移動平均とか以前のデータから計算する指標は最初に計算してやはりOnBars関数の中で参照する。
例えば15日移動平均を参照するには

  • from pyalgotrade.technical import ma でライブラリをインポート
  • 初期化__init__の中に self.__sma = ma.SMA(feed[instrument].getCloseDataSeries(), 15) で15日移動平均を計算してしまう。
  • onBarsでは self.__sma[-1]で参照する。忘れがちなので注意

また移動平均は終値を指定して計算させているが、、毎日とれているデータであればなんでもいい。

終値と移動平均を表示させてみる


#!/usr/bin/python
# -*- coding: utf-8 -*-
import logging
from pyalgotrade.technical import ma
from pyalgotrade import strategy
from pyalgotrade.barfeed import yahoofeed

logging.basicConfig(filename='/tmp/exec.log')

class MyStrategy(strategy.BacktestingStrategy):
    def __init__(self, feed, instrument):
        strategy.BacktestingStrategy.__init__(self, feed)
        self.__sma = ma.SMA(feed[instrument].getCloseDataSeries(), 15)
        self.__instrument = instrument

    def onBars(self, bars):
        bar = bars[self.__instrument]
        self.info("%f %s " % (bar.getClose(),self.__sma[-1]))


# Load the yahoo feed from the CSV file
feed = yahoofeed.Feed()
feed.addBarsFromCSV("orcl", "orcl-2000.csv")

# Evaluate the strategy with the feed's bars.
myStrategy = MyStrategy(feed, "orcl")
myStrategy.run()
おわかりだろうか、self.infoのフォーマットが %fから%sに変わっている。
なぜかというと計算できないときはnoneになるので実数として表示できないからだ。
この後は指標をいろいろ試してみようかな。

Quantopian が理想的なシステムトレーディングのテスト場かもしれない

折角PyAlgoTradeをインストールして準備したのに恐縮ではあるが、環境を作らなくてもオンラインで試すことができるサービスがある。




 ここにはWEB上でpythonプログラムを作る環境があり、IDEでデバッグもできる
もちろんバックテストも可能だ。

 しかも、過去の相場データさらに財務データも使い放題、同じサーバーにあるので取得に時間もかからない。
 さらに掲示板があって自動売買プログラムについての情報交換がなされていたり、コンテストが開催されていたりする。
 これだけそろって無料。

残念なのは、全部英語でデータもすべて米国マーケット。

このサイトの日本版を作ったらニーズはあるのかな。無料で公開したいので誰かスポンサーになってください。

Quautopian
Quantopian
https://www.quantopian.com/home

2015年9月14日月曜日

日本の時系列株価データーがほしい時にはjsmを使う

もともとpandasで提供されているのは海外のyahoo financeから取得する株価で日本のyahoo financeはapi提供をしていなかったりする。

海外のデータだと今一つ親近感がわかないので、国内用のデータ取得モジュールを入れる。

jsm 0.18
Get the japanese stock market data

インストールはpipで問題なく簡単に入った

pip install jsm




import jsm と書いてテスト実行するとワーニングが出る。たぶん文字コードの問題かと思うけど実害はないので放置。

yahooの番号が4689なので、指定した期間のCSVを取得したいのであれば以下のようなプログラムを書いて実行する

#
import jsm
import datetime

start_date = datetime.date(2013,1,1)
end_date=datetime.date(2013,2,1)
c=jsm.QuotesCsv();
c.save_historical_prices("test.csv",4689,jsm.DAILY,start_date,end_date)

ちなみに日経平均株価は998407を指定すればいい。

2015年9月12日土曜日

PyAlgoTradeをUbuntuにインストールしてみる

 インストールばっかりで先に進んでないと思うけど、今度はCentOSではなくUbuntuにインストール。
いやね、レンタルサーバーだと最低スペックから少しでも上げると急激に値段が高くなる、しょせんメモリ2Gぐらいだったら手元に転がっているX61のほうが性能が高いことに気が付いた。メモリもDDR2なのでゴミみたいに扱われているしね。

 デスクトップはUbuntuのほうが圧倒的に楽。
標準のインストールを行った後、計算に必要なライブラリをapt-getでインストールする。Ubuntuではpipは使わないで、apt-get install python-モジュール名 で書くらしい。


sudo apt-get install python-numpy python-scipy python-matplotlib ipython ipython-notebook python-pandas python-sympy python-nose


計算関係のライブラリを入れたら、先ほどpipは使わないと書いていて恐縮だが、Pyalgotradeをpipで入れる。

pip install pyalgotrade


warningがいっぱい出るけど一応動く、そのうち動かなくなる予感なので、動作する今のバージョンで入れておくべき。

一通りいれたら、チュートリアルを試す。テスト方法を一切忘れていたのでブログを書いていてよかった。

やっぱり結果がその場でポップアップされるのは良い。

ta-libはpython で書かれたものとそれ以外があって

まず、これを実行し本体をコンパイルしてインストールする。
$ wget http://prdownloads.sourceforge.net/ta-lib/ta-lib-0.4.0-src.tar.gz
$ tar xvfz ta-lib-0.4.0-src.tar.gz
$ cd ta-lib-0.4.0
$ ./configure
$ make
$ make install
あとは呼び出すためのpythonライブラリをpipで入れる

pip ta-lib

ライブラリが見つからないといったエラーが出るので環境変数を設定する
$export LD_LIBRARY_PATH="/usr/local/lib:$LD_LIBRARY_PATH" 

2015年9月6日日曜日

PyAlgoTradeではTA-libを売買時に使うことができる

チャートに関しての様々な指標のほとんどは手計算を行っていた時代に考えられたものなので、今どきのコンピュータなら一瞬で答えが出てくる。とはいえ、計算式をプログラムにするときにバグが入り込みやすく、本人の勉強以外ですでにある指標を自作することはあまり得策ではない。
TA-Libはさまざまな指標を計算することができるライブラリで、これを活用することで時間を節約することができる。
TA-Lib
http://ta-lib.org/

組み込みテスト
以下のプログラムが動いたらTA-libが使えるようになっている。 ちなみに、乱数をボリンジャーバンドに当てはめただけなので結果にに意味はない
import numpy
import talib

data=numpy.random.random(100)
upper,middle,lower = talib.BBANDS(data,matype=talib.MA_Type.T3)

print(upper)
print(middle)
print(lower)
TA-Libがサポートしている指標は以下の通り。すいません、半分もわかりません。www
pyalgotrade.talibext.indicator.AD(barDscount)
Chaikin A/D Line
pyalgotrade.talibext.indicator.ADOSC(barDscountfastperiod=-2147483648slowperiod=-2147483648)
Chaikin A/D Oscillator
pyalgotrade.talibext.indicator.ADX(barDscounttimeperiod=-2147483648)
Average Directional Movement Index
pyalgotrade.talibext.indicator.ADXR(barDscounttimeperiod=-2147483648)
Average Directional Movement Index Rating
pyalgotrade.talibext.indicator.APO(dscountfastperiod=-2147483648slowperiod=-2147483648matype=0)
Absolute Price Oscillator
pyalgotrade.talibext.indicator.AROON(barDscounttimeperiod=-2147483648)
Aroon
pyalgotrade.talibext.indicator.AROONOSC(barDscounttimeperiod=-2147483648)
Aroon Oscillator
pyalgotrade.talibext.indicator.ATR(barDscounttimeperiod=-2147483648)
Average True Range
pyalgotrade.talibext.indicator.AVGPRICE(barDscount)
Average Price
pyalgotrade.talibext.indicator.BBANDS(dscounttimeperiod=-2147483648nbdevup=-4e+37nbdevdn=-4e+37matype=0)
Bollinger Bands
pyalgotrade.talibext.indicator.BETA(ds1ds2counttimeperiod=-2147483648)
Beta
pyalgotrade.talibext.indicator.BOP(barDscount)
Balance Of Power
pyalgotrade.talibext.indicator.CCI(barDscounttimeperiod=-2147483648)
Commodity Channel Index
pyalgotrade.talibext.indicator.CDL2CROWS(barDscount)
Two Crows
pyalgotrade.talibext.indicator.CDL3BLACKCROWS(barDscount)
Three Black Crows
pyalgotrade.talibext.indicator.CDL3INSIDE(barDscount)
Three Inside Up/Down
pyalgotrade.talibext.indicator.CDL3LINESTRIKE(barDscount)
Three-Line Strike
pyalgotrade.talibext.indicator.CDL3OUTSIDE(barDscount)
Three Outside Up/Down
pyalgotrade.talibext.indicator.CDL3STARSINSOUTH(barDscount)
Three Stars In The South
pyalgotrade.talibext.indicator.CDL3WHITESOLDIERS(barDscount)
Three Advancing White Soldiers
pyalgotrade.talibext.indicator.CDLABANDONEDBABY(barDscountpenetration=-4e+37)
Abandoned Baby
pyalgotrade.talibext.indicator.CDLADVANCEBLOCK(barDscount)
Advance Block
pyalgotrade.talibext.indicator.CDLBELTHOLD(barDscount)
Belt-hold
pyalgotrade.talibext.indicator.CDLBREAKAWAY(barDscount)
Breakaway
pyalgotrade.talibext.indicator.CDLCLOSINGMARUBOZU(barDscount)
Closing Marubozu
pyalgotrade.talibext.indicator.CDLCONCEALBABYSWALL(barDscount)
Concealing Baby Swallow
pyalgotrade.talibext.indicator.CDLCOUNTERATTACK(barDscount)
Counterattack
pyalgotrade.talibext.indicator.CDLDARKCLOUDCOVER(barDscountpenetration=-4e+37)
Dark Cloud Cover
pyalgotrade.talibext.indicator.CDLDOJI(barDscount)
Doji
pyalgotrade.talibext.indicator.CDLDOJISTAR(barDscount)
Doji Star
pyalgotrade.talibext.indicator.CDLDRAGONFLYDOJI(barDscount)
Dragonfly Doji
pyalgotrade.talibext.indicator.CDLENGULFING(barDscount)
Engulfing Pattern
pyalgotrade.talibext.indicator.CDLEVENINGDOJISTAR(barDscountpenetration=-4e+37)
Evening Doji Star
pyalgotrade.talibext.indicator.CDLEVENINGSTAR(barDscountpenetration=-4e+37)
Evening Star
pyalgotrade.talibext.indicator.CDLGAPSIDESIDEWHITE(barDscount)
Up/Down-gap side-by-side white lines
pyalgotrade.talibext.indicator.CDLGRAVESTONEDOJI(barDscount)
Gravestone Doji
pyalgotrade.talibext.indicator.CDLHAMMER(barDscount)
Hammer
pyalgotrade.talibext.indicator.CDLHANGINGMAN(barDscount)
Hanging Man
pyalgotrade.talibext.indicator.CDLHARAMI(barDscount)
Harami Pattern
pyalgotrade.talibext.indicator.CDLHARAMICROSS(barDscount)
Harami Cross Pattern
pyalgotrade.talibext.indicator.CDLHIGHWAVE(barDscount)
High-Wave Candle
pyalgotrade.talibext.indicator.CDLHIKKAKE(barDscount)
Hikkake Pattern
pyalgotrade.talibext.indicator.CDLHIKKAKEMOD(barDscount)
Modified Hikkake Pattern
pyalgotrade.talibext.indicator.CDLHOMINGPIGEON(barDscount)
Homing Pigeon
pyalgotrade.talibext.indicator.CDLIDENTICAL3CROWS(barDscount)
Identical Three Crows
pyalgotrade.talibext.indicator.CDLINNECK(barDscount)
In-Neck Pattern
pyalgotrade.talibext.indicator.CDLINVERTEDHAMMER(barDscount)
Inverted Hammer
pyalgotrade.talibext.indicator.CDLKICKING(barDscount)
Kicking
pyalgotrade.talibext.indicator.CDLKICKINGBYLENGTH(barDscount)
Kicking - bull/bear determined by the longer marubozu
pyalgotrade.talibext.indicator.CDLLADDERBOTTOM(barDscount)
Ladder Bottom
pyalgotrade.talibext.indicator.CDLLONGLEGGEDDOJI(barDscount)
Long Legged Doji
pyalgotrade.talibext.indicator.CDLLONGLINE(barDscount)
Long Line Candle
pyalgotrade.talibext.indicator.CDLMARUBOZU(barDscount)
Marubozu
pyalgotrade.talibext.indicator.CDLMATCHINGLOW(barDscount)
Matching Low
pyalgotrade.talibext.indicator.CDLMATHOLD(barDscountpenetration=-4e+37)
Mat Hold
pyalgotrade.talibext.indicator.CDLMORNINGDOJISTAR(barDscountpenetration=-4e+37)
Morning Doji Star
pyalgotrade.talibext.indicator.CDLMORNINGSTAR(barDscountpenetration=-4e+37)
Morning Star
pyalgotrade.talibext.indicator.CDLONNECK(barDscount)
On-Neck Pattern
pyalgotrade.talibext.indicator.CDLPIERCING(barDscount)
Piercing Pattern
pyalgotrade.talibext.indicator.CDLRICKSHAWMAN(barDscount)
Rickshaw Man
pyalgotrade.talibext.indicator.CDLRISEFALL3METHODS(barDscount)
Rising/Falling Three Methods
pyalgotrade.talibext.indicator.CDLSEPARATINGLINES(barDscount)
Separating Lines
pyalgotrade.talibext.indicator.CDLSHOOTINGSTAR(barDscount)
Shooting Star
pyalgotrade.talibext.indicator.CDLSHORTLINE(barDscount)
Short Line Candle
pyalgotrade.talibext.indicator.CDLSPINNINGTOP(barDscount)
Spinning Top
pyalgotrade.talibext.indicator.CDLSTALLEDPATTERN(barDscount)
Stalled Pattern
pyalgotrade.talibext.indicator.CDLSTICKSANDWICH(barDscount)
Stick Sandwich
pyalgotrade.talibext.indicator.CDLTAKURI(barDscount)
Takuri (Dragonfly Doji with very long lower shadow)
pyalgotrade.talibext.indicator.CDLTASUKIGAP(barDscount)
Tasuki Gap
pyalgotrade.talibext.indicator.CDLTHRUSTING(barDscount)
Thrusting Pattern
pyalgotrade.talibext.indicator.CDLTRISTAR(barDscount)
Tristar Pattern
pyalgotrade.talibext.indicator.CDLUNIQUE3RIVER(barDscount)
Unique 3 River
pyalgotrade.talibext.indicator.CDLUPSIDEGAP2CROWS(barDscount)
Upside Gap Two Crows
pyalgotrade.talibext.indicator.CDLXSIDEGAP3METHODS(barDscount)
Upside/Downside Gap Three Methods
pyalgotrade.talibext.indicator.CMO(dscounttimeperiod=-2147483648)
Chande Momentum Oscillator
pyalgotrade.talibext.indicator.CORREL(ds1ds2counttimeperiod=-2147483648)
Pearson’s Correlation Coefficient (r)
pyalgotrade.talibext.indicator.DEMA(dscounttimeperiod=-2147483648)
Double Exponential Moving Average
pyalgotrade.talibext.indicator.DX(barDscounttimeperiod=-2147483648)
Directional Movement Index
pyalgotrade.talibext.indicator.EMA(dscounttimeperiod=-2147483648)
Exponential Moving Average
pyalgotrade.talibext.indicator.HT_DCPERIOD(dscount)
Hilbert Transform - Dominant Cycle Period
pyalgotrade.talibext.indicator.HT_DCPHASE(dscount)
Hilbert Transform - Dominant Cycle Phase
pyalgotrade.talibext.indicator.HT_PHASOR(dscount)
Hilbert Transform - Phasor Components
pyalgotrade.talibext.indicator.HT_SINE(dscount)
Hilbert Transform - SineWave
pyalgotrade.talibext.indicator.HT_TRENDLINE(dscount)
Hilbert Transform - Instantaneous Trendline
pyalgotrade.talibext.indicator.HT_TRENDMODE(dscount)
Hilbert Transform - Trend vs Cycle Mode
pyalgotrade.talibext.indicator.KAMA(dscounttimeperiod=-2147483648)
Kaufman Adaptive Moving Average
pyalgotrade.talibext.indicator.LINEARREG(dscounttimeperiod=-2147483648)
Linear Regression
pyalgotrade.talibext.indicator.LINEARREG_ANGLE(dscounttimeperiod=-2147483648)
Linear Regression Angle
pyalgotrade.talibext.indicator.LINEARREG_INTERCEPT(dscounttimeperiod=-2147483648)
Linear Regression Intercept
pyalgotrade.talibext.indicator.LINEARREG_SLOPE(dscounttimeperiod=-2147483648)
Linear Regression Slope
pyalgotrade.talibext.indicator.MA(dscounttimeperiod=-2147483648matype=0)
All Moving Average
pyalgotrade.talibext.indicator.MACD(dscountfastperiod=-2147483648slowperiod=-2147483648,signalperiod=-2147483648)
Moving Average Convergence/Divergence
pyalgotrade.talibext.indicator.MACDEXT(dscountfastperiod=-2147483648fastmatype=0slowperiod=-2147483648,slowmatype=0signalperiod=-2147483648signalmatype=0)
MACD with controllable MA type
pyalgotrade.talibext.indicator.MACDFIX(dscountsignalperiod=-2147483648)
Moving Average Convergence/Divergence Fix 12/26
pyalgotrade.talibext.indicator.MAMA(dscountfastlimit=-4e+37slowlimit=-4e+37)
MESA Adaptive Moving Average
pyalgotrade.talibext.indicator.MAX(dscounttimeperiod=-2147483648)
Highest value over a specified period
pyalgotrade.talibext.indicator.MAXINDEX(dscounttimeperiod=-2147483648)
Index of highest value over a specified period
pyalgotrade.talibext.indicator.MEDPRICE(barDscount)
Median Price
pyalgotrade.talibext.indicator.MFI(barDscounttimeperiod=-2147483648)
Money Flow Index
pyalgotrade.talibext.indicator.MIDPOINT(dscounttimeperiod=-2147483648)
MidPoint over period
pyalgotrade.talibext.indicator.MIDPRICE(barDscounttimeperiod=-2147483648)
Midpoint Price over period
pyalgotrade.talibext.indicator.MIN(dscounttimeperiod=-2147483648)
Lowest value over a specified period
pyalgotrade.talibext.indicator.MININDEX(dscounttimeperiod=-2147483648)
Index of lowest value over a specified period
pyalgotrade.talibext.indicator.MINMAX(dscounttimeperiod=-2147483648)
Lowest and highest values over a specified period
pyalgotrade.talibext.indicator.MINMAXINDEX(dscounttimeperiod=-2147483648)
Indexes of lowest and highest values over a specified period
pyalgotrade.talibext.indicator.MINUS_DI(barDscounttimeperiod=-2147483648)
Minus Directional Indicator
pyalgotrade.talibext.indicator.MINUS_DM(barDscounttimeperiod=-2147483648)
Minus Directional Movement
pyalgotrade.talibext.indicator.MOM(dscounttimeperiod=-2147483648)
Momentum
pyalgotrade.talibext.indicator.NATR(barDscounttimeperiod=-2147483648)
Normalized Average True Range
pyalgotrade.talibext.indicator.OBV(ds1volumeDscount)
On Balance Volume
pyalgotrade.talibext.indicator.PLUS_DI(barDscounttimeperiod=-2147483648)
Plus Directional Indicator
pyalgotrade.talibext.indicator.PLUS_DM(barDscounttimeperiod=-2147483648)
Plus Directional Movement
pyalgotrade.talibext.indicator.PPO(dscountfastperiod=-2147483648slowperiod=-2147483648matype=0)
Percentage Price Oscillator
pyalgotrade.talibext.indicator.ROC(dscounttimeperiod=-2147483648)
Rate of change : ((price/prevPrice)-1)*100
pyalgotrade.talibext.indicator.ROCP(dscounttimeperiod=-2147483648)
Rate of change Percentage: (price-prevPrice)/prevPrice
pyalgotrade.talibext.indicator.ROCR(dscounttimeperiod=-2147483648)
Rate of change ratio: (price/prevPrice)
pyalgotrade.talibext.indicator.ROCR100(dscounttimeperiod=-2147483648)
Rate of change ratio 100 scale: (price/prevPrice)*100
pyalgotrade.talibext.indicator.RSI(dscounttimeperiod=-2147483648)
Relative Strength Index
pyalgotrade.talibext.indicator.SAR(barDscountacceleration=-4e+37maximum=-4e+37)
Parabolic SAR
pyalgotrade.talibext.indicator.SAREXT(barDscountstartvalue=-4e+37offsetonreverse=-4e+37,accelerationinitlong=-4e+37accelerationlong=-4e+37accelerationmaxlong=-4e+37accelerationinitshort=-4e+37,accelerationshort=-4e+37accelerationmaxshort=-4e+37)
Parabolic SAR - Extended
pyalgotrade.talibext.indicator.SMA(dscounttimeperiod=-2147483648)
Simple Moving Average
pyalgotrade.talibext.indicator.STDDEV(dscounttimeperiod=-2147483648nbdev=-4e+37)
Standard Deviation
pyalgotrade.talibext.indicator.STOCH(barDscountfastk_period=-2147483648slowk_period=-2147483648slowk_matype=0,slowd_period=-2147483648slowd_matype=0)
Stochastic
pyalgotrade.talibext.indicator.STOCHF(barDscountfastk_period=-2147483648fastd_period=-2147483648,fastd_matype=0)
Stochastic Fast
pyalgotrade.talibext.indicator.STOCHRSI(dscounttimeperiod=-2147483648fastk_period=-2147483648,fastd_period=-2147483648fastd_matype=0)
Stochastic Relative Strength Index
pyalgotrade.talibext.indicator.SUM(dscounttimeperiod=-2147483648)
Summation
pyalgotrade.talibext.indicator.T3(dscounttimeperiod=-2147483648vfactor=-4e+37)
Triple Exponential Moving Average (T3)
pyalgotrade.talibext.indicator.TEMA(dscounttimeperiod=-2147483648)
Triple Exponential Moving Average
pyalgotrade.talibext.indicator.TRANGE(barDscount)
True Range
pyalgotrade.talibext.indicator.TRIMA(dscounttimeperiod=-2147483648)
Triangular Moving Average
pyalgotrade.talibext.indicator.TRIX(dscounttimeperiod=-2147483648)
1-day Rate-Of-Change (ROC) of a Triple Smooth EMA
pyalgotrade.talibext.indicator.TSF(dscounttimeperiod=-2147483648)
Time Series Forecast
pyalgotrade.talibext.indicator.TYPPRICE(barDscount)
Typical Price
pyalgotrade.talibext.indicator.ULTOSC(barDscounttimeperiod1=-2147483648timeperiod2=-2147483648,timeperiod3=-2147483648)
Ultimate Oscillator
pyalgotrade.talibext.indicator.VAR(dscounttimeperiod=-2147483648nbdev=-4e+37)
Variance
pyalgotrade.talibext.indicator.WCLPRICE(barDscount)
Weighted Close Price
pyalgotrade.talibext.indicator.WILLR(barDscounttimeperiod=-2147483648)
Williams’ %R
pyalgotrade.talibext.indicator.WMA(dscounttimeperiod=-2147483648)
Weighted Moving Average