ストックドッグ

KatoTakahiro。金融系の会社で働くSEが株やPython、その他諸々について書いています。サービスも運営してます→http://fmbrain.work

cronで定期的にフォルダを作成してみる(Ubuntu 16.04)

色々とcronについて勉強したので、今回はcronで一分毎にTestという名のフォルダを作成するまでやってみようと思います。

cronとは

設定したファイルを定期的に実行させるためのツール

crontabに登録されているcronを確認

crontab -e

編集等はvimで行うことになります。
もし、まだcronがひとつも登録されていなければ、no crontab for <ユーザー名> が表示されます。

crontabは /etc の中にあるのですが、私はvimの使い方がわからなかったので、sublimetext(テキストエディタ)に移してから編集していましたが、これではうまくいきませんでした。(編集しても内容が反映されない)

おそらく、編集・実行の権限が与えられていなかったからだと思います。

諦めてvimの使い方をドットインストールで学んできて、ようやく編集することができました。

vimは癖が強すぎるが、慣れれば最高に使いやすいツールになることの片鱗をつかめた!)

そもそもcronが動作しているのかをチェック

sudo service cron status

active(running)が表示されていれば正常に動作しています。

また、cronの動作ログもこれで確認可能です。

基本的にcronの動作ログは /var/log のsyslogファイルに書かれています。

なので。

cd /var/log
cat syslog

で、確認可能ですが、いちいちsyslogを見に行くのも面倒なので、sudo service cron status で確認したほうが楽です。


定期的に実行させるファイルを作成

/home にtest.shというファイルを作成します。

#!/bin/shシェルスクリプトであるということを明示するためのものです。

mkdir Test にしているので、cronが上手く実行されれば/home にTestという名のフォルダが作成されるはずです。

(mkdirでもなくても大丈夫です、今回はcronが動作しているかどうかを確認したいため、適当にmkdirにしました)

#!/bin/sh
mkdir Test

crontabからcronを設定

crontab -e
* * * * * /bin/sh /home/ubuntu/test.sh > /dev/null 2>&1

時間は1毎分に設定しています。

エラーが発生していた場合、すぐに確認できるので。

また、cronによって実行するファイルがシェルスクリプトであることを明示するために /bin/sh を記述しています。

cronでの時間設定の書式は以下のサイトがわかりやすかったです。

qiita.com


これでcronの設定は終わったので、ログを見てみます。

3月 30 23:09:01 tk CRON[4148]: (ubuntu) CMD (/bin/sh /home/test.sh)

CMDとはcronが実行したコードを示しています。

ログを見る限り、cronは正常に実行されています。

ディレクトリを見てみると...

これが...

テンプレート
ピクチャ
test.sh
デスクトップ
ミュージック
ドキュメント
公開
ダウンロード
ビデオ

1分後...

Testフォルダが作成されたので、無事にcronが動作したようです。

Test
ダウンロード
ビデオ
テンプレート
ピクチャ
test.sh
デスクトップ
ミュージック
ドキュメント
公開

MTAというエラーが出た場合

cronはエラーが出た際、メールで通知しようとするのですが、メールの設定を行っていない場合にMTAというエラーが出ます。

postfixなどでメールの設定を行うことでこのエラーは解決できます。

以下のサイトがわかりやすかったです。

ryoichi0102.hatenablog.com

まとめ

今回はcronを動かすまでを最短距離で行ったので、至らない点が多数あります。

cronにはまだまだ工夫の余地があり、以下のサイトがとても参考になります。

dqn.sakusakutto.jp

bootの容量がいっぱいなのに、古いカーネルが消せない問題(ubuntu 16.04)

bootの容量がいっぱいすぎて、新しいカーネルに更新できず、まわりまわって他のソフトウェアもアップデートできないという問題が発生しました。

試しにbootの容量を確認してみると...

df /boot
Filesystem     1K-blocks   Used Available Use% Mounted on
/dev/sda7         236876 224404         0 100% /boot

見事に100%!!
100%って何か気持ち良いですね!元気がでました。

だがしかしだけれども、何とかbootの容量を空けないと身動きができないので、古いカーネルを消そうとしました。

とりあえず、現在使用中のカーネルは...

uname -r
4.4.0-63-generic


そして、boot内にあるカーネルは...

dpkg --get-selections | grep linux-
linux-base					install
linux-firmware					install
linux-generic					install
linux-headers-4.4.0-57				install
linux-headers-4.4.0-57-generic			install
linux-headers-4.4.0-59				install
linux-headers-4.4.0-59-generic			install
linux-headers-4.4.0-62				install
linux-headers-4.4.0-62-generic			install
linux-headers-4.4.0-63				install
linux-headers-4.4.0-63-generic			install
linux-headers-4.4.0-64				install
linux-headers-4.4.0-64-generic			install
linux-headers-4.4.0-66				install
linux-headers-4.4.0-66-generic			install
linux-headers-4.4.0-70				install
linux-headers-4.4.0-70-generic			install
linux-headers-generic				install
linux-image-4.4.0-21-generic			deinstall
linux-image-4.4.0-53-generic			deinstall
linux-image-4.4.0-57-generic			install
linux-image-4.4.0-59-generic			install
linux-image-4.4.0-62-generic			install
linux-image-4.4.0-63-generic			install
linux-image-4.4.0-64-generic			install
linux-image-4.4.0-66-generic			install
linux-image-4.4.0-70-generic			install
linux-image-extra-4.4.0-21-generic		deinstall
linux-image-extra-4.4.0-53-generic		deinstall
linux-image-extra-4.4.0-57-generic		install
linux-image-extra-4.4.0-59-generic		install
linux-image-extra-4.4.0-62-generic		install
linux-image-extra-4.4.0-63-generic		install
linux-image-extra-4.4.0-64-generic		install
linux-image-extra-4.4.0-66-generic		install
linux-image-extra-4.4.0-70-generic		install
linux-image-generic				install
linux-libc-dev:amd64				install
linux-sound-base				install
syslinux-common					install
syslinux-legacy					install

放置っぷりが明らかに...
古いカーネルはたくさんあるし、最新のカーネルにアップデートもしてないし...

現在使用しているのが、4.4.0-63なので、それ以前のカーネルは削除しようと思います。

sudo apt-get autoremove --purge linux-image-4.4.0-57-generic

すると未解決の依存関係があるようで、削除できません。

パッケージリストを読み込んでいます... 完了
依存関係ツリーを作成しています                
状態情報を読み取っています... 完了
以下の問題を解決するために 'apt-get -f install' を実行する必要があるかもしれません:
以下のパッケージには満たせない依存関係があります:
 linux-image-extra-4.4.0-57-generic : 依存: linux-image-4.4.0-57-generic しかし、インストールされようとしていません
 linux-image-extra-4.4.0-70-generic : 依存: linux-image-4.4.0-70-generic しかし、インストールされようとしていません
 linux-image-generic : 依存: linux-image-4.4.0-70-generic しかし、インストールされようとしていません
E: 未解決の依存関係です。'apt-get -f install' を実行してみてください (または解法を明示してください)

'apt-get -f install'を実行したとしても、bootの容量がいっぱいなのでどのみちインストールはできません。

sudo apt-get -f install linux-image-4.4.0-70-generic


けっこうな詰みです。
古いカーネルを消そうとしても、未解決の依存関係が原因で消せない、にもかかわらず、その未解決の依存関係を修復するために最新のカーネルをインストールしようとしてもbootの容量がいっぱい...


ということで最終手段、依存関係を無視して古いカーネルを消すことにしました。

cd /boot
ls boot

一番古い57系のものを色々と削除。

sudo rm linux-image-4.4.0-57-generic


ここでもう一度容量を確認。

df /boot

Filesystem     1K-blocks   Used Available Use% Mounted on
/dev/sda7         236876 184731     35608  84% /boot

無事にbootを整理できました。

以上終わり!

SQLite3をPandasから操作する

SQL文の操作を覚えるのが面倒...

select * from tbl_nameまでが覚えれる限界でした。

データの検索・変更などのwhere文等を覚えるのが面倒かつ、逃げ道を見つけてしまったので、逃げ道のほうに行くほかない。

ということで、SQLite3のデータをPandasのDataFrame型に出力してから、DataFrame型でデータを加工するという技を身につけたので、ここにメモとして保存します。

これだと、Pandasでデータをいじくり回して、最後にSQLite3に保存するだけなので、楽ちん。


Pandasの万能さを実感し、かつ、SQL文を覚える機会をまたまた失ったわけであります。

#SQLite3をインポート
import sqlite3
import pandas as pd


#db
dbname = "code.db"


#dbコネクト
conn = sqlite3.connect(dbname)
c = conn.cursor()


#dbを見てみる
pd.read_sql_query("select * from code_7776", conn)


#dbをpandasのDataFrame型で読み込む
df = pd.read_sql("select * from code_7776", conn)

#dfの中身はすでにDataFrame形式
#データの変更等はPandasでのデータいじりの操作方法でいける
df["Altering"] = None
print(df)


#データをいじり終わったら、SQLite3のdbに書き込む
df.to_sql("code_7776", conn, if_exists="replace")


【参考】

www.dataquest.io

PandasのデータをSQLite3で保存する

スクレイピングしたデータをどうすればいいかわからない問題発生

株価データ等をYa〇〇〇ファ〇〇〇〇などからスクレイピングして、今まではCSVファイルでローカルのディレクトリに置いていました。

言ってしまえば、スクレイピングして放置状態でした。

データを使用するときはCSV形式なので、今まではそれが都合良かったのですが...

毎回毎回スクレイピングするのは相手方のサーバーに申し訳ないので、そろそろデータベースで管理したほうがいいかなぁ、と思うようになりまして。

データベースで管理すれば、サイトが更新されたデータ分だけを拾って、データベースを更新するだけで済むので、かなり時間も節約できますし。

そこでSQLite3を勉強していたのですが、はてさてpandasのDataFrame形式はいかにしてDBに放り込むのか。


少し調べるとpandasでできるらしいぞ、と。

SQLiteに放り込むCSVファイルはこんな形式。

f:id:doz13189:20170322005408p:plain


コードを以下に貼ります。

import sqlite3
import pandas as pd


#pandasでcsvファイルを読み込む
df = pd.read_csv("code_1332.csv")
df.columns= ["Date", "Open", "High", "Low", "Close", "Volume", "Adj Close", "Number"]

#db作成
dbname = "code.db"

#dbコネクト
conn = sqlite3.connect(dbname)
c = conn.cursor()

#dbのnameをcode_1332とし、読み込んだcsvファイルをsqlに書き込む
#if_existsでもしすでにcode_1332が存在していても、置き換えるように指示
df.to_sql("code_1332", conn, if_exists="replace")

#作成したdbを見てみる
select_sql = 'select * from code_1332'
for row in c.execute(select_sql):
    print(row)

conn.close()

出力を見てみる。

f:id:doz13189:20170322004401p:plain

SQLite3に書き込まれ、DBが作成されています。

pandasって万能やなぁと今日も感じた次第であります。

以上報告終わり!なのですが...


他のシストレやる方たちはどのようにデータを集めて保存しているのですか?

もっと効率良い方法があるなら教えて欲しいです。

もしよければコメント下さい、お願いしますm(_ _)m

Yahooファイナンスの株価予想ページをScrapyでクローリング・スクレイピング

目次

本日やること

Yahooファイナンスでは、毎日アナリスト的な人たちが株価予想を行っています。
今回は、そのページをScrapyでクローリングしながら、各アナリストたちの予想をスクレイピングして、ファイルに保存します。

info.finance.yahoo.co.jp


完成予定ファイル

こんな感じのページを…

f:id:doz13189:20170321184350p:plain

こんな感じのファイルにまとめます!

f:id:doz13189:20170321184425p:plain

必要な環境

python3
scrapy
jq
ubuntu(OSは自由)

なぜscrapy?

scrapyはクローリング・スクレイピングを行うためのライブラリです。
ライブラリの記述ルールを覚えてしまえば、とてもシンプルにクローリング・スクレイピングを行うことができます。

分析等を行うには大前提としてデータが必要です。
データは様々なサイトから集めてくると思うのですが、個々のサイトごとにコードを書いていては大変ですし、また管理の手間も増えます。

scrapyでスクレイピングを行えば、トータル的に無駄が省けてハッピーということです。

Python以外にはあまりスクレイピングのためのライブラリはないようなので、scrapyをバンバン使って、無駄という無駄を省きパイソニストのメリットを最大限に活かしましょう!笑

スクレイピングの流れ

スクレイピング対象のサイトのHTML構成を確認。
scrapyでサイトからスクレイピング
以上!!!(めっちゃ簡単)

Scrapyのインストール

pip install scrapy

Scrapyプロジェクトの作成

scrapy startproject mypredict

Ubuntuで言う端末、Macで言うターミナル、Windowsで言うコマンドプロンプトに上記のコードを打ちましょう。

これでScrapyのプロジェクトが作成されます。
このコマンドを打つと、mypredictというファイルが作成されます。
以降、このフォルダの中で作業を行います。

念の為、ファイル構成を確認しておきましょう。

f:id:doz13189:20170321185616p:plain

中には、Scrapyの設定を記述してあるファイルやスクレイピングした内容を検証するためのファイルがありますが、今回は使用しません。

では、mypredictに移動します

cd mypredict

基本的には、作業はこの階層で行います。

Spiderの作成

scrapyでは、スクレイピングをするためのルールをこのspiderに記述します。
そのため、スクレイピングを行うサイトごとにspiderを作成するイメージです。

スクレイピングをするにはサイトごとに異なるページ構成を考慮する必要があります。
class名やid名などサイトによって使用しているものが異なるので、そういったサイトの個別のルールをこのspiderに記述します。
※ページ内容のダウンロード等は他のファイル(すでに用意されている)

まず、spidersフォルダの中にpredict.pyというファイルを作成します。
このファイルがクローリング・スクレイピングするファイルとなります。

今回作成するファイルはこれだけです。
このファイルで全ての処理を行います。
記述するコードも30行にもいきません。

※注意、ファイルを作成するのはspidersの中ですが、作業をするのはあくまでもmypredict直下です。scrapy.cfgがある階層です。

とりあえず、predict.pyというファイルだけ作成して、そのままにしておきます。

スクレイピングするサイトを確認

先ほど貼った画像と同じです。

f:id:doz13189:20170321184350p:plain


この予想一覧を上から順にクローリングしていき、URLをたどっていきます。

最終的には、このページ内にある全ての個別の予想ページの中身の文章をスクレイピングしていきます。

f:id:doz13189:20170321190701p:plain


ここでクローリングとスクレイピングの意味を確認しておきます。

私もそこまではっきりと認識しているわけではなくて、ニュアンスで使っていますが...

クローリングはサイトの情報を閲覧する

スクレイピングは、閲覧している情報をダウンロードしてくること

こんな感じだと思います、以降このような意味でクローリング・スクレイピングという言葉を使っていきます。

スクレイピングはじまるよー

ではまず、予想トップページをスクレイピングしてみましょう。
この予想トップページから個別ページに繋がるURLを拾ってくるのが目的です。
以下のコードを、ちょっと前に作成してほったらかしにしているpredict.pyに記述します。

import scrapy


class PredictSpider(scrapy.Spider):
	#predictはSpiderの名前。spider実行時に入力する。
	name = "predict"

        #必ず必要なコードです
     #相手サーバーに配慮するために、スクレイピングの間隔に1秒入れます
        custom_settings = {
            "DOWNLOAD_DELAY": 1,
        }

	#スクレイピングを行う際に一番最初にクローリングするページ
	start_urls = ["http://info.finance.yahoo.co.jp/kabuyoso/article/"]
	
	def parse(self, response):

		#Google Chromeの検証等でページの構成を確認すると。
		#URLはHTMLのh3タグのクラス st02の下に格納されていることがわかる。
		for href in response.css("h3.st02 a::attr('href')"):
			
			#相対URLから絶対URLに変換
			url = response.urljoin(href.extract())

			#スクレイピングした内容を確認
			print(url)

ちなみに、ページ構成のスクショは以下。

こればっかりはページのHTMLを確認しながら自分で地道にたどっていくしかないです。

f:id:doz13189:20170321190913p:plain


先ほどのコードを実行してみます。

mypredict直下で以下のコードを打ちます。

scrapy crawl predict

こんな感じでURLがリスト形式で表示されれば成功です。

f:id:doz13189:20170321191206p:plain


さて、これでたどるURLを入手することができました。
次にこのURLを辿って、ページの中身をスクレイピングします。
先ほどのコードを少し変更し、さらに個別ページをたどるコードを追加しています。

import scrapy


class PredictSpider(scrapy.Spider):
	name = "predict"

        custom_settings = {
            "DOWNLOAD_DELAY": 1,
        }

	start_urls = ["http://info.finance.yahoo.co.jp/kabuyoso/article/"]

	def parse(self, response):
		for href in response.css("h3.st02 a::attr('href')"):
			url = response.urljoin(href.extract())
			
			#ここから下が追加分
			#スクレイピングしたURLをたどるためにparse_topicsに投げる
			yield scrapy.Request(url, self.parse_topics)


	def parse_topics(self, response):
		#個別ページのHTML構造を確認する
		#h3タグのクラスst02にタイトルが格納されていることがわかる
		title = response.css("h3.st02 ::text").extract_first()

		#本文はdlタグのクラスmarB20に格納されていることがわかる
		#これを::textで抽出するとbrタグも含めまれて上手くスクレイピングできない
		#xpathのstring()を使用すると、うまい具合にスクレイピングすることができる
		body = response.css("dl.marB20").xpath("string()").extract()

		#辞書形式で格納する
		yield {
			"title" : title,
			"body" : body
		}

先ほどと同じように、たどるページ先のHTML構成を確認しておきます。

f:id:doz13189:20170321191511p:plain


さて実行してみます。

Scrapy crawl predict

実行結果はこちらです。

f:id:doz13189:20170321191620p:plain

スクレイピングが成功していたら、何かしらの文字列が表示されると思います。

スクレイピング内容をファイルに保存

とても簡単です。

#-oはファイルに保存する
#predict.json、ここは自由
#保存したいファイル名とファイル形式を指定するだけです
scrapy crawl predict -o predict.json

これだけです。

中身を見てみましょう。

#jqを事前にインストールする必要あり
cat predict.json | jq


実行結果です。

f:id:doz13189:20170321192042p:plain


まとめ
上手くできましたか?
思いの外簡単にスクレイピング出来たと思います。
なんせ、書いたコードは30行に満たない程度ですから。

今回はファイルに保存しましたが、これをデータベースに保存してもOKですし、実際には管理のしやすさ等を考えるとそちらのほうがベターかと思います。

私もまだまだscrapyを触り始めたばかりなので、これから色々と使っていきたいと思います。

株価の予測精度が60%を超えた

【更新 2017-08-15】
未だによくアクセスがあるので、書いておきます。
この結果が検証したわけではないですが、きっと間違いです。
シミュレーション期間がとても短いこともあって、運が良かっただけのように思います。
検証するのも面倒なので、しませんが。
ただ、記事としては残しておこうと思います。
若気の至り的な自戒もこめて...

目次

予測精度が60%超えた!?

2014年8月15日〜2017年2月28日までの期間で、株価を予測するシミュレーションを行ったところ...

66.19%

となりました。


2値分類モデルで、n日後の株価が上昇しているか下落しているかを予測する、というものです。


予測対象はETFで、2013年1月時点で運用をしているという条件で絞り込んでいます。

[ 1305, 1306, 1308, 1309, 1310, 1311, 1320, 1321, 1324, 1325, 1326, 1328, 1329, 1330, 1343, 1344, 1345, 1346, 1348, 1540, 1541, 1542, 1545, 1546, 1547, 1550, 1552, 1554, 1555, 1563, 1566, 1569, 1570, 1571, 1572, 1615, 1671, 1678, 1680, 1681, 1698, 1699 ]

ETFだから予測精度が高いとかそういうことではなくて、個別銘柄でもおそらくあまり変わらない結果が出ると思います。

ETFにしているのは、購入単位が安いので売買シミュレーションがしやすいからです。

以下は平均予測精度を時系列順にプロットしたものです。

f:id:doz13189:20170303184448p:plain

これらの平均が66.19%ということになります。

定期的に大きく予測が外れるという特徴は以前から変わらずですね。

本当に予測できてるのか?

わかりません。

もう実践に投入して確かめるしかないと思ってます。


シミュレーションというシミュレーションはし尽くしました。

使用しているプログラムにも今の所ミスは見つかっていません。

ただ、今までの経験上予測精度が60%を超えると確実にミスが発見されます。

引き続きプログラムミスの可能性を疑ってかかることにします。


ということもありますが、今後はこれを活かすためにシストレやりたいなぁという方向性で頑張っています。

ただ、それらの知識が皆無なので大変です。

なんとか4月の入社までにある程度の形にもっていきたいですが、厳しいかも。

中身のアルゴリズムはあまり詳しく話しませんが...

私が株価予測を行う上で、ここを気にしたというポイントをまとめます。

2ヶ月前の自分に口酸っぱく言いたいな!という内容を書いています。

  • 株価の価格を予測しない
  • 時系列を意識する
  • 手法を組み合わせる


この3つに集約されます。

この3つを意識したあたりから、私は上手く行き出しました。

株価の価格を予測しない、というのはちょっとわかりにくいですね。

言い換えると、株価の価格を予測しても精度は上がらないよ、ということです。

じゃあ何予測すんねんって感じですね笑

これは私の経験則であり、組むアルゴリズムによってそこらへんの事情は変わると思いますが、私は価格を予測しようとしても精度は50%程度にいつも収束していきました。

だから、予測する対象を変えました。

予測する対象は、市場の方向性です。

市場の方向性とはつまり、市場が上昇傾向にあるのか、あるいは下落傾向にあるのか、です。

方向性を予測した結果を銘柄に適用しているだけです。


2つめの時系列を意識するというのは、当たり前かもしれません。

例えば、株価は自己相関しますね。(自己相関とは、上がったら下がってきて、下がったら上がってくる、という株価の性質をカッコよく言っているだけです。)

その性質を利用したアルゴリズムは数多く提唱されており、例えば、anticorなどが有名です。

anticorは時系列を強く意識したアルゴリズムと言えます。

株価の1回1回の価格形成は独立しているわけではありません。(前日の株価が次の日の株価に影響を与えている、ということ)

私は自己相関を利用しているわけではありませんが、時系列というのは強く意識しています。

株価が時間と共に変動している以上、時間という要素を抜いてはあまり上手くはいかないのじゃあないかな、というのが私の意見です。


はい、3つ目。

単発の手法ではなかなか精度は上がらないかな、というこれも私の経験則に基づいたものです。

株価に限らず、予測という分野は手法を組み合わせることで精度が劇的に上がるケースは多いと思います。

となると、ディープラーニングと何を組み合わせようかな〜、と考えてしまう人がいると思いますが、これはきっと上手く行きません。

ディープラーニングを使わないと予測できないとか、機械学習を使わないと予測できないとか、手法の問題ではないと思います。

手法を組み合わせるって言っても、考え方の問題だと私は思っています。

私は、アルゴリズムの中に強化学習を使用していますが、強化学習のどのアルゴリズムを使用しているというわけではありません。

強化学習の「報酬を最大化」する、という考え方をアルゴリズムに組み込んでいるだけです。

どうしても手法ありきで考えてしまうと、この手法は株価予測に使えるor使えないで判断してしまいます。

株価予測に最適なアルゴリズムなんてものはないので、ピッタリくるものはなかなか出会えないですよね。

色々なアルゴリズムの考え方を組み合わせながら、株価予測のアルゴリズムを構築していくのが大事かなって思います。

以上、ゆるーくポイントをまとめてみました。

ゆくゆくはアルゴリズムもフルオープンにしたいなぁと考えるもそう甘くはない

アルゴリズム取引の世界ってすごくクローズドですよね。

こんなにも様々な分野の様々な記事がネット上にアップされているにも関わらず、アルゴリズムトレードに関するものは極端に数が少なく、あったとしてもその記事の質は低かったりします。

もちろん、アルゴリズムを公開していて生き残って行ける業界ではないのですが。

ただ、ノウハウを個人個人で溜め込んで、それを墓まで持っていくスタイルなので、ノウハウの蓄積が少なく、若い人材が確実に育ちにくい形態をしています。

業界の体質上仕方がない気もしますが、なんとも歯がゆい...


今後10年でアルゴリズム取引という業界は今よりもっともっと大きくなります。

今でさえ米国では80〜90%の取引はアルゴリズムによるものであるというデータがありますし、フラッシュクラッシュなんて楽しいことも起きています。

現状、日本市場で動いているアルゴリズム取引も米国産のものが多いと言われていますし、日本のシストレ勢はまだまだ少ないように思います。

(少ないと言っても私のフォロー・フォロワーさんは優秀なシストレ勢がたくさんいますが...)


この現状をどうにかしたいなぁ、なんて考えているのでよく株価分析系の記事をできるだけわかりやすい内容にしてアップしたりはしているのですが、もっと根本の部分から変えないと現状は変わらないように感じています。

どうにかその仕組みづくりをできればなぁなんてのが最近の野望です。

最後に予測モデルで売買シミュレーションを行ったので、それを貼って記事を締めます

予測モデルと言っても、以前から使用してる予測精度が54〜55%くらいのモデルです。

(60%を超えたモデルではまだ売買シミュレーションをやっていません。)

予測モデルに従い、100万円を元手に2014年から2016年の3年間に取引を行うとどうなるかっていうシミュレーションです。

手数料はSBI証券のものを適用しています。

対象銘柄は、先ほどと同じETFであり、予測結果の中からランダムで銘柄選択をしているので、シミュレーションを20回行っています。

f:id:doz13189:20170303202833p:plain

3年間運用して、元手の100万円を割ったのは、20回中3回。

厳しい結果ですね。

予測精度と言っても、結局当たるか外れるかは確率なので、予測精度が利益に繋がるとは限りませんね。

これだとまだまだ市場投入するには早いかなって思います。

60%を超えたモデルでのシミュレーションはまだ行っていないので近々やると思います。

結果が楽しみです。(~o~)


今回は目的もなくダラダラと書いてしまって、収集がつきません。

強制的にここらで終わることにします。

終わり!

強化学習で銘柄を選んでみた(検証)

目次

今日やること

銘柄選択を強化学習に任せてみよう、ということをお題にシンプルな検証をやってみたいと思います。

強化学習

強化学習の中でも、基礎とされるn本腕バンディット問題について扱います。

相場をn本腕バンディット問題に見立てて、検証したいと思います。

そもそもn本腕バンディット問題って何?

見識高き読者は華麗に読み飛ばして下さい。

今回使用するバンディットアルゴリズムのε-グリーディ手法を私なりに説明してるだけです(数式は一切使いません)。


n本腕バンディット問題は例えで説明すると、とてもわかりやすいです。

例えスタート!!

f:id:doz13189:20170224221859j:plain

  1. ここに5つのコインがあります(画像は6枚ですが、気にしないで)
  2. 5つのコインは表がでる確率がそれぞれ異なります
  3. コインAは30%、コインBは40%、コインCは50%、コインDは60%、コインEは70%、の確率で表がでます
  4. どのコインがどの確率なのかはわかりません
  5. 5つのコインから1つだけを選んで投げる、という行動を10000回繰り返します
  6. 目的は、この10000回の中でできるだけ表を多く出したい
  7. さて、どういった戦略をとれば表を多く出せるでしょう?

10000回の試行回数の中でどれが一番表が出やすいコインを手探りで確かめなければならない、かつ、できるだけ早く見つける。

こういった状況をバンディット問題と呼びます。

(n本腕というのは選択肢の数で、今回は5つのコイン、つまり、5つの選択肢があるので5本腕バンディット問題ということになります)

そして、この手探りの効率の良さをより求めたものがバンディットアルゴリズムとして提唱されています。


さて、そろそろ戦略について考えてみましょう。


では、まずギャンブラー的戦略をとってみましょう。

ギャンブラー的戦略:5つのうちどれかを選び、それを10000回投げ続ける。コインEを偶然選ぶことができればラッキー、コインAに当たれば悲劇。


次にみんな平等、ゆとり教育的戦略をとりましょう。

ゆとり教育的戦略:みんな平等、5つとも2000回づつ投げましょう。表が出る期待値は、当然5枚のコインの期待値の平均。


次は、ちょっと頭をひねった戦略をとります。

最初の5000回は5枚とも均等にコインを投げます。

5000回投げ終わった時点で、それぞれのコインの表が出た回数を計算し、その中で一番表が出た回数が多かったものを残りの5000回投げましょう、という戦略。

実はこれはグリーディ手法と呼ばれる戦略です。

最初の5000回の中から期待値の高いものを貪欲に(Greedy)選ぶことからグリーディ手法と呼ばれています。


さて、先ほどのグリーディ手法ですが、最初に5000回は投げ過ぎじゃないですか?

もうちょっと減らしても、コインEは見つけれるんじゃないか?

1000回、いいや、100回、いやいや、20回!


20回まで減らして、もし20回の中でコインEを見つけることができれば、残りの9880回はコインEを投げれるわけです。

しかし、心配な点もあります。

それは20回の中で、たまたまコインDの表が出る回数が多くて、残りの9880回にコインDを選んでしまうケース。

悲劇ですね。

しかし、あくまでもコインの表が出るのは確率なので、十二分に考えられます。

コインEを見つけるための試行回数を減らしすぎると、こういった悲劇(不確実性)が起こりやすくなります。


ちなみにバンディット問題では。

このコインEを見つけるための行動を探索と言います。

そして、コインEの表が出る確率が高いということを見出し、コインEを投げることを知識の利用と言います。(試行回数をこなすことで得た知識を利用)


探索と知識の利用はトレードオフの関係にあり、探索をしすぎても最終的な報酬(表が出る回数)は減り、知識の利用を早い段階からしすぎても不確実性が増すことになります。

バンディットアルゴリズムでは、探索と知識の利用のバランシングの効率の良さを追求しています。

このバランスは、どこが最適かとかは扱う問題によって異なるので、最適を求めるのは難易度高めです。

今回のようなコイン投げだと、確率論的に求めることはできますが、こんな単純な問題は現実には存在しません。

この記事で扱う内容も、バランシングの最適化はまったく行っていません。(無理なので)


と、n本腕バンディット問題についてざっと説明が終わったところで、最後にもうひとつだけ戦略を上げておきます。(本命)

今回、検証で使用するものです。

ε-グリーディ手法と言って、グリーディ手法のデメリットをちょっと改善したものです。

コインEを見つけるために、最初の20回はどのコインも均等に投げるとします。

ここまではグリーディ手法と同じです。

グリーディ手法だと、残りの9880回は20回の中での表が出た回数が多かったコインをひたすら投げ続けますが、ε-グリーディ手法は、残りの9880回の中でもたまに他のコインを投げます。

もし、誤った選択をしていても、たまに他のコインを投げ、その都度表が出る期待値を計算し直すので、どこかでコインEにたどり着くことができるでしょう。

探索をある一定の確率で行い続けるのが、ε-グリーディ手法です。

ε-グリーディ手法では、このように不確実性を減らしていき、かつ最終的な報酬も求めていきます。

検証方法

相場をn本腕バンディット問題に見立てながら、検証を行っていきます。

先ほどは5つのコインでしたが、相場なので5つの銘柄です。


対象銘柄

証券コード ETF リターン
1545 (NEXT FUNDS)NASDAQ-100(R)連動型上場投信 -0.0061
1525 国際のETF VIX短期先物指数 0.035
1671 WTI原油価格連動型上場投信 0.010
1681 上場インデックスファンド海外新興国株式 0.0004
1698 上場インデックスファンド日本高配当 -0.0035

リターンは、2013年から2016年まで、10営業日保有して売却するという行動を行い、それらの平均リターンを示しています。

どの銘柄がどの平均リターンかはわからないものとします。

だいたい、リターンがバラけるように選びました。


2013年から2016年までの合計取引回数は全部で89回です。

目的は、この89回の試行回数でより平均リターン高めることです。

つまり、ε-グリーディ手法によってできるだけ少ない試行回数で、一番リターンの高い国際のETF VIX短期先物指数(1525)を見つけることができれば良いということになります。

結果

f:id:doz13189:20170224204715p:plain

平均リターンは、0.0129となりました。

国際のETF VIX短期先物指数(1525)の0.035には及ばないものの、ちゃんと1525を選べているようです。

探索と知識利用のバランスは、はじめの20回をランダムに取引し(探索)、その後は知識の利用にしています。

f:id:doz13189:20170224204933p:plain

これは試行回数ごとに選択した銘柄をプロットしています。

1525が一番多く選択されていることがわかります。

たまに他の銘柄も選択しているのがε-グリーディ手法の特徴とも言えます。


ちなみにグリーディ手法で行った際のシミュレーションも見てみましょう。

f:id:doz13189:20170225054809p:plain

平均リターンは0.020でした。

今回に限っては、ε-グリーディ手法より上を行きました。

それだけε-グリーディ手法に無駄な探索が多かったと言えます。

f:id:doz13189:20170225054835p:plain

試行回数ごとに選択した銘柄をプロットしたものを見ると、1525を選んでいることがわかります。


ここまでは、ε-グリーディ手法orグリーディ手法が上手くいったパターンでした。

次に上手く探索できず国際のETF VIX短期先物指数(1525)を見つけれなかったパターンを見てみます。

f:id:doz13189:20170224205357p:plain

平均リターンは、0.0049となりました。

国際のETF VIX短期先物指数(1525)の0.035には遠く及びません。

それもそのはず選択した銘柄を見てみると...

f:id:doz13189:20170224205512p:plain

最初はWTI原油価格連動型上場投信(1671)のリターンが高いと評価しており、途中から上場インデックスファンド海外新興国株式(1681)に乗り換えています。

残念、どっちもはずれです。


こういったこともあるので、探索と知識の利用のバランシングは慎重に行わなければいけません。

強化学習のアルゴリズムは全般的に学習までの期間にかなりの時間を要します。

試行回数89回程度ではなかなか最適な解は見つかりにくく、ある程度の不確実性は許容するしかありません。

しかし、相場の歴史は長いです。

いくらでもさかのぼって探索の期間をたっぷり取ればいいか、というとまたそうでもありません。

今回のシミュレーションの問題点

一言で言うと、相場は非定常環境であるにも関わらず、定常環境を想定したシミュレーションを行っている、ということです。

コインの表は出る確率は一定ですが(定常環境)、リターンは時間と共に平均が変化していきます(非定常環境)。

これの何がまずいかというと...

2013年から2016年の平均リターンをとったら-0.01でも、2013年から2014年というある期間をとったら+0.05かもしれない。

ということは、ε-グリーディ手法は2013年から2014年の間を探索期間として設けていると高確率で誤った知識を得ることになります。

いや、そもそも誤った知識でもありませんね。

ただ、その知識を2014年、2015年と利用していても良い結果は得ることはできません。

今回のシミュレーションで言うと、2013年〜2016年は高いリターンを示していた国際のETF VIX短期先物指数(1525)を2017年以降も買い続けていれば高いリターンを期待できるかというとそうではない、ということです、当たり前ですね笑


これを解決するには、古い知識の価値をあまり評価せず、新しい知識を評価するということが上がられます。

これを今回のシミュレーションに置き換えると、時間軸をもとにリターンを加重平均すると非定常環境に対応することができるということになります。


このように相場に合わせていくと、強化学習を使ったトレンドフォロー型のアルゴリズムができます。

(今回のシミュレーションもリターンの高いものを積極的に選ぶトレンドフォロー型)

ちなみに強化学習を使った株取引で15%の超過リターンを達成したで〜っていう論文もあります。

http://sigfin.org/?plugin=attach&refer=SIG-FIN-011-04&openfile=SIG-FIN-011-04.pdf


工夫次第で取引戦略に組み込めそうですね!!!


〜更新〜

非定常環境に対応させたバージョン

直近のリターンが高いものを高く評価した場合のシミュレーション

f:id:doz13189:20170225080339p:plain

平均リターンは0.0260となかなかの数値。

何回かシミュレーションを繰り返しましたが、比較的高い数値を出しました。


f:id:doz13189:20170225080500p:plain

試行回数ごとに選択した銘柄をプロットしたものを見ても、適度に他の銘柄を選んでいることがわかります。

以上でした。