はまったのでメモ。
プロジェクトを作成し、リポジトリを登録しようとするとエラーが出る。
対処は単にリスタートすればなおる場合が有る
ブラウザで以下をたたけばOK
http://localhost/safeRestart
はまったのでメモ。
プロジェクトを作成し、リポジトリを登録しようとするとエラーが出る。
対処は単にリスタートすればなおる場合が有る
ブラウザで以下をたたけばOK
http://localhost/safeRestart
前回、株価のヒストリカルデータを銘柄コード別にファイルに落とすところまでできました。このままでは使いづらいのでリレーショナルDBに入れることにします。
ただ、この動作しているマシンがRaspberryPIなので、データベースを入れるというような暴挙はできません。そこで、組み込みきらしくSQLiteを使うことにします。
$ sqlite3 stock.db SQLite version 3.7.13 2012-07-17 17:46:21 Enter ".help" for instructions Enter SQL statements terminated with a ";" sqlite> .schema CREATE TABLE histDaily( date text, open real, high real, low real, close real, volume real, adj_close real, code text ); CREATE UNIQUE INDEX idx_histDaily on histDaily(code,date); sqlite>
#!/bin/env python # coding:utf-8 import sqlite3 import sys def import_data(file): con=sqlite3.connect("stock.db",isolation_level=None) sql=u"insert into histDaily values(?,?,?,?,?,?,?,?)" for line in open(file,'r'): ay=line.split('\t') con.execute(sql,(ay[0],ay[1],ay[2],ay[3],ay[4],ay[5],ay[6],ay[7])) con.close() if __name__ == '__main__': print sys.argv[1] import_data(sys.argv[1])
#!/bin/sh codes=`ls data/*.txt|grep -v master` for code in $codes;do echo $code python hist_import.py $code done
これで動かしてみたのですがちょっとおそいです。というか丸一日動かして1000銘柄も取り込めていない。。。
後ほどチューニングすることにします
RでYahooファイナンスを取得するモジュール、RFinanceYJを改造し使っていたのですが、あまりの遅さに閉口してしまいました。丸一日掛けても14年分の日次株価は1000銘柄程度しか撮れていません。やり方はこの辺りにまとめています。
http://d.hatena.ne.jp/anagotan/searchdiary?word=%2A%5BR%5D
RaspberryPiを手に入れたこともあり、pythonで書き直してみました。pythonは2.7です
マスターを取得します
#!/bin/env python # coding:utf-8 import urllib import urllib2 from lxml import etree import time def get_stockmaster(hira): p=1 ret="dummy" ss="" while(ret!=""): ret,plen=get_stockmaster_page(hira,p) #print ret if ret != "": ss=ss+ret p=p+1 return ss def get_stockmaster_page(hira,p): hira=hira.encode('utf-8') url="http://stocks.finance.yahoo.co.jp/stocks/qi/?%s" params={"js":hira,"p":p} data=urllib.urlencode(params) #print data res=urllib.urlopen(url % data) page=res.read() root=etree.fromstring(page,etree.HTMLParser()) elem=root.xpath("//a[contains(@href,'/stocks/detail')]") i=0 ret="" for ele in elem: if ele.text != None: if i%3==0: ret=ret+ele.text+"\t" if i%3==1: ret=ret+ele.text.encode('utf-8')+"\n" i=i+1 time.sleep(1) return ret,len(elem) def get_stockmasterall(): hira=u"あいうえおかきくけこさしすせそたちつてとなにぬねのはひふへほまみむめもやゆよわ" size=len(hira) ss="" for i in range(0,size): print hira[i:i+1] #print i ss=ss+get_stockmaster(hira[i:i+1]) f=open("data/master.txt","w") f.write(ss) f.close() if __name__ == '__main__': get_stockmasterall()
時系列データを取得します
#!/bin/env python # coding:utf-8 import urllib import urllib2 from lxml import etree import time def get_stockdata(code,sy,sm,sd,ey,em,ed,tm): p=1 plen=53 ss="" while(plen>=53): ret,plen=get_stockdata_page(code,sy,sm,sd,ey,em,ed,tm,p) ss=ss+ret p=p+1 f=open("data/"+code+".txt","w") f.write(ss) f.close() def get_stockdata_page(code,sy,sm,sd,ey,em,ed,tm,p): url="http://info.finance.yahoo.co.jp/history/?%s" params={"code":code,"sy":sy,"sm":sm,"sd":sd,"ey":ey,"em":em,"ed":ed,"tm":tm,"p":p} data=urllib.urlencode(params) #print data res=urllib.urlopen(url % data) page=res.read() root=etree.fromstring(page,etree.HTMLParser()) elem=root.xpath("//table") ret="" plen=len(elem[1].xpath("//tr")) for tr in elem[1].xpath("//tr"): if len(tr.findall("td"))==7: str="" for td in tr.findall("td"): s=td.text.encode('utf-8').replace(',','').replace('年','-').replace('月','-').replace('日','') str=str+s+"\t" str=str+code ret=ret+str+"\n" time.sleep(1) return ret,plen def get_stockdata_all(sy,sm,sd,ey,em,ed,tm): f=open("data/master.txt") lines=f.readlines() f.close() for line in lines: ay=line.split("\t") print ay[0] get_stockdata(ay[0],sy,sm,sd,ey,em,ed,tm) if __name__ == '__main__': get_stockdata_all("2000","01","01","2014","08","27","d")
やっつけで作成しましたがとりあえずファイルに落とすところまでは確認できました。
ちなみに動作環境はRapberryPITypeB+のRASPBIANVersion:June 2014で動いています
昨日ポチッたRaspberryPi、早速届きました。RSコンポーネンツさん仕事が早いです。
早速セットアップ。
上海問屋で以前入手したトラセンドの32G
ソニーエリクソンのMW1付属のものを流用。
ソニーの液晶テレビ
要は家にある物でなんとかしてみました。セットアップはMacOSXで行います
通常のフォーマットでFAT32にしておけばOK。
http://www.raspberrypi.org/downloads/
こちらからRASPBIANのZIPファイルをダウンロード。解凍しておきます
ディスクをマウントしコマンドプロンプトのdfで確認します
$ df Filesystem 512-blocks Used Available Capacity iused ifree %iused Mounted on /dev/disk0s2 1951845952 408391536 1542942416 21% 51112940 192867802 21% / devfs 380 380 0 100% 659 0 100% /dev map -hosts 0 0 0 100% 0 0 100% /net map auto_home 0 0 0 100% 0 0 100% /home /dev/disk1s2 1952851232 1666536368 286314864 86% 208317044 35789358 85% /Volumes/WININSTALL /dev/disk2s1 61800928 3648 61797280 1% 0 0 100% /Volumes/UNTITLED
/dev/disk2s1がSDのデバイスです。マウントを外し書き込んでいきます。
$ sudo diskutil umountDisk /dev/disk2 # /dev/disk2s1じゃないよ $ sudo dd bs=1m if=2014-06-20-wheezy-raspbian.img of=/dev/disk2 2825+0 records in 2825+0 records out 2962227200 bytes transferred in 382.700174 secs (7740334 bytes/sec)
4、5分でイメージが書き終わります。
HDMIケーブルをテレビと接続し、SDカードをセットしたら電源コネクタにマイクロUSBの電源を差し込みます。テレビに起動コンソールが映り始めればOK
だめな場合には、LEDランプの緑がちかちかしているかどうか確認します。こちらがどうやらディスクアクセスのランプのようです。うまく起動画面が出ない場合にはSDにOSがうまく書き込めていないと思いますので、ディスクを変更するなどし再度チャレンジしてください。
起動したらディスクやLocaleの設定などを行います
$ sudo raspi-config
1.Expand Filesystem
そのままの設定だと2GしかSDカードを使ってくれないのでこちらのメニューからすべての領域を使うようにします
4.Internationalisation Options
I1 Change Locale
ロケールをja_JP.UTF-8へと変更
I2 Change Timezone
Tokyoへ変更
I3 Change Keyboard Layout
General Keyboard 105 あたりからJapaneseを選択
8. Advanced Options
A4 SSH
これをONにしておくと外部からSSHでログイン可能
ここまででだいぶ使いやすくなるかと思います。あと固定IPの方が使いやすいのでネットワークの設定を行います。
$ sudo vi /etc/network/interfaces #iface eth0 inet dhcp # ここをコメントアウトし iface eth0 inet static address 192.168.11.101 # この辺りの設定をする netmask 255.255.255.0 gateway 192.168.11.254
自宅のネットワーク構成に合わせてaddressとgatewayを設定します。
これで一通りの設定ができたかと思います。必要なパッケージはapt-getが使えるので簡単にインストールできるのが楽ですね
前回までのスクリプトをぐるぐる回すようにしてみました。
一銘柄ずつとると時間がかかるので並列で動くようにしています
quoteStockMasterTsData <- function(){ financial.data <- data.frame(NULL) function.stockMasterData<-function(hira){ r <- NULL result.num <- 20 master.data <- data.frame(NULL) start.num<-0 while( result.num >= 20 ){ start.num <- start.num + 1 quote.table <- NULL quote.url <- paste('http://stocks.finance.yahoo.co.jp/stocks/qi/?js=',hira,'&p=',start.num,sep="") try( r <- xmlRoot(htmlTreeParse(quote.url,error=xmlErrorCumulator(immediate=F))), TRUE) if( is.null(r) ) stop(paste("Can not access :", quote.url)) try( quote.table <- xpathApply(r,"//a[contains(@href,'/stocks/detail')]"), TRUE ) if( is.null(quote.table) ){ if( is.null(master.data) ){ stop(paste("Can not quote :", x)) }else{ return(master.data) } } size <- xmlSize(quote.table)/3 if(size==0){ return(master.data) } for(i in 1:size){ mtmp<-data.frame(code=xmlValue(quote.table[[i*3-2]]),name=xmlValue(quote.table[[i*3-1]])) mtmp$code<-as.character(mtmp$code) mtmp$name<-as.character(mtmp$name) master.data <- rbind(master.data,mtmp) } result.num <- xmlSize(quote.table)/3 Sys.sleep(1) } return(master.data) } hiraList<-"あいうえおかきくけこさしすせそたちつてとなにぬねのはひふへほまみむめもやゆよわ" for(i in 1:nchar(hiraList)){ hira<-substring(hiraList,i,i) master.data<-function.stockMasterData(hira) financial.data<-rbind(financial.data,master.data) } financial.data <- financial.data[order(financial.data$code),] return(financial.data) } #API quoteStockTsData <- function(x, since=NULL,start.num=0,date.end=NULL,time.interval='daily') { time.interval <- substr(time.interval,1,1) function.stock <- function(quote.table.item){ if( xmlSize(quote.table.item) < 5) return(NULL) d <- convertToDate(xmlValue(quote.table.item[[1]]),time.interval) o <- as.number(xmlValue(quote.table.item[[2]])) h <- as.number(xmlValue(quote.table.item[[3]])) l <- as.number(xmlValue(quote.table.item[[4]])) c <- as.number(xmlValue(quote.table.item[[5]])) v <- ifelse(xmlSize(quote.table.item) >= 6,as.number(xmlValue(quote.table.item[[6]])),0) a <- ifelse(xmlSize(quote.table.item) >= 7,as.number(xmlValue(quote.table.item[[7]])),0) return(data.frame(date=d,open=o,high=h,low=l,close=c,volume=v, adj_close=a)) } return(quoteTsData(x,function.stock,since,start.num,date.end,time.interval,type="stock")) } quoteFundTsData <- function(x, since=NULL,start.num=0,date.end=NULL,time.interval='daily') { time.interval <- substr(time.interval,1,1) function.fund <- function(quote.table.item){ d <- convertToDate(xmlValue(quote.table.item[[1]]),time.interval) if(time.interval=='monthly'){ d <- endOfMonth(d) } c <- as.number(xmlValue(quote.table.item[[2]])) v <- as.number(xmlValue(quote.table.item[[3]])) return(data.frame(date=d,constant.value=c,NAV=v)) } return(quoteTsData(x,function.fund,since,start.num,date.end,time.interval,type="fund")) } quoteFXTsData <- function(x, since=NULL,start.num=0,date.end=NULL,time.interval='daily') { time.interval <- substr(time.interval,1,1) function.fx <- function(quote.table.item){ d <- convertToDate(xmlValue(quote.table.item[[1]]),time.interval) o <- as.number(xmlValue(quote.table.item[[2]])) h <- as.number(xmlValue(quote.table.item[[3]])) l <- as.number(xmlValue(quote.table.item[[4]])) c <- as.number(xmlValue(quote.table.item[[5]])) return(data.frame(date=d,open=o,high=h,low=l,close=c)) } return(quoteTsData(x,function.fx,since,start.num,date.end,time.interval,type="fx")) } ###### private functions ##### #get time series data from Yahoo! Finance. quoteTsData <- function(x,function.financialproduct,since,start.num,date.end,time.interval,type="stock"){ r <- NULL result.num <- 51 financial.data <- data.frame(NULL) #start <- (gsub("([0-9]{4,4})-([0-9]{2,2})-([0-9]{2,2})","&c=\\1&a=\\2&b=\\3",since)) #end <- (gsub("([0-9]{4,4})-([0-9]{2,2})-([0-9]{2,2})","&f=\\1&d=\\2&e=\\3",date.end)) start <- (gsub("([0-9]{4,4})-([0-9]{2,2})-([0-9]{2,2})","&sy=\\1&sm=\\2&sd=\\3",since)) end <- (gsub("([0-9]{4,4})-([0-9]{2,2})-([0-9]{2,2})","&ey=\\1&em=\\2&ed=\\3",date.end)) if(!any(time.interval==c('d','w','m'))) stop("Invalid time.interval value") extractQuoteTable <- function(r,type){ if(type %in% c("fund","fx")){ tbl <- r[[2]][[2]][[7]][[3]][[3]][[9]][[2]] } else{ tbl <- r[[2]][[2]][[7]][[3]][[3]][[10]][[2]] } return(tbl) } while( result.num >= 51 ){ start.num <- start.num + 1 quote.table <- NULL quote.url <- paste('http://info.finance.yahoo.co.jp/history/?code=',x,start,end,'&p=',start.num,'&tm=',substr(time.interval,1,1),sep="") try( r <- xmlRoot(htmlTreeParse(quote.url,error=xmlErrorCumulator(immediate=F))), TRUE) if( is.null(r) ) stop(paste("Can not access :", quote.url)) #try( quote.table <- r[[2]][[1]][[1]][[16]][[1]][[1]][[1]][[4]][[1]][[1]][[1]], TRUE ) #try( quote.table <- extractQuoteTable(r,type), TRUE ) try( quote.table <- xpathApply(r,"//table")[[2]], TRUE ) if( is.null(quote.table) ){ if( is.null(financial.data) ){ stop(paste("Can not quote :", x)) }else{ financial.data <- financial.data[order(financial.data$date),] return(financial.data) } } size <- xmlSize(quote.table) for(i in 2:size){ financial.data <- rbind(financial.data,function.financialproduct(quote.table[[i]])) } result.num <- xmlSize(quote.table) Sys.sleep(1) } financial.data <- financial.data[order(financial.data$date),] return(financial.data) } #convert string formart date to POSIXct object convertToDate <- function(date.string,time.interval) { #data format is different between monthly and dialy or weekly if(any(time.interval==c('d','w'))){ result <- gsub("^([0-9]{4})([^0-9]+)([0-9]{1,2})([^0-9]+)([0-9]{1,2})([^0-9]+)","\\1-\\3-\\5",date.string) }else if(time.interval=='m'){ result <- gsub("^([0-9]{4})([^0-9]+)([0-9]{1,2})([^0-9]+)","\\1-\\3-01",date.string) } return(as.POSIXct(result)) } #convert string to number. as.number <- function(string) { return(as.double(as.character(gsub("[^0-9.]", "",string)))) } #return end of month date. endOfMonth <- function(date.obj) { startOfMonth <- as.Date(format(date.obj,"%Y%m01"),"%Y%m%d") startOfNextMonth <- as.Date(format(startOfMonth+31,"%Y%m01"),"%Y%m%d") return(startOfNextMonth-1) } library(RSQLite) # fromDate: yyyy-MM-dd getStockHistoricalData<-function(sqliteFile,codes,fromDate){ drv<-dbDriver("SQLite") con<-dbConnect(drv,dbname=sqliteFile) try(rs<-dbSendQuery(con,"drop table historicalData")) rs<-dbSendQuery(con,"create table historicalData (date text,open real,high real, low real,close real,volume real,adj_close real,code text)") rs<-dbSendQuery(con,"create unique index idx_historicalData on historicalData(code,date)") #stockMaster<-quoteStockMasterTsData() for(code in codes){ print(paste("code=",code,sep="")) tryCatch({ data<-quoteStockTsData(code,fromDate) data<-transform(data,code=code) data$code<-as.character(data$code) data$date<-as.character(data$date) dbBeginTransaction(con) dbSendQuery(con,paste("delete from historicalData where code='",code,"'",sep="")) dbSendPreparedQuery(con,"insert into historicalData (date,open,high,low,close,volume,adj_close,code) values(:date, :open, :high, :low, :close, :volume, :adj_close, :code)",bind.data=data) dbCommit(con) }, error = function(e){ message(paste("ERROR:",code)) message(e) }) } }
#!/bin/sh getStockData(){ fromNo=$1 toNo=$2 fileNo=$3 /usr/bin/R --vanilla << Eof library(RFinanceYJ) source("/Users/utsuboka/Documents/prog/RFinanceYJPatch.R") stockMaster<-quoteStockMasterTsData() getStockHistoricalData("stock$fileNo.db",stockMaster\$code[$fromNo:$toNo],"2000-01-01") Eof } getStockData 1 500 1 & getStockData 501 1000 2 & getStockData 1001 1500 3 & getStockData 1501 2000 4 & getStockData 2001 2500 5 & getStockData 2501 3000 6 & getStockData 3001 3500 7 & getStockData 3501 4000 8 &
現在4000銘柄弱ですのでこれでOKだと思います。ただこれでもまだまだ時間がかかりますが全部取得できるかと思います
前回でマスターまで取得ができたので、このマスターを用い、全銘柄分のデータを取得することにします。
取得したデータはリレーショナルデータベースへ格納するのが一般的かと思いますので、ここは簡単に扱えるSQLiteに入れることにします。
SQLiteをインストール
MacOSXの場合には以下のコマンドで一発です
brew install sqlite
つぎにRSQLiteパッケージをインストールします
install.packages("RSQLite")
準備が整ったらスクリプトを用意しこれを実行します
(前回までのRFinaiceYJの拡張関数を実行しておくこと)
library(RSQLite) drv<-dbDriver("SQLite") con<-dbConnect(drv,dbname="stock.db") rs<-dbSendQuery(con,"drop table historicalData") rs<-dbSendQuery(con,"create table historicalData (date text,open real,high real, low real,close real,volume real,adj_close real,code text)") rs<-dbSendQuery(con,"create unique index idx_historicalData on historicalData(code,date)") stockMaster<-quoteStockMasterTsData() for(code in stockMaster$code){ #cat(paste("code=",code,"\n",sep="")) data<-quoteStockTsData(code,"2010-01-01") data<-transform(data,code=code) data$code<-as.character(data$code) data$date<-as.character(data$date) dbBeginTransaction(con) dbSendQuery(con,paste("delete from historicalData where code='",code,"'",sep="")) dbSendPreparedQuery(con,"insert into historicalData (date,open,high,low,close,volume,adj_close,code) values(:date, :open, :high, :low, :close, :volume, :adj_close, :code)",bind.data=data) dbCommit(con) }
かなり時間がかかると思いますが無事SQLiteにはいっていることが確認できます
sqlite> select * from historicalData; 2010-01-04|293.0|294.0|290.0|291.0|28100.0|291.0|3076 2010-01-05|293.0|293.0|288.0|288.0|81500.0|288.0|3076 .. 2014-08-19|1534.0|1536.0|1505.0|1505.0|22900.0|1505.0|3791 2014-08-20|1505.0|1520.0|1505.0|1516.0|7100.0|1516.0|3791
もう少し使い勝手よくしてみました
library(RSQLite) # fromDate: yyyy-MM-dd getStockHistricalData<-function(stockMaster,fromDate){ drv<-dbDriver("SQLite") con<-dbConnect(drv,dbname="stock.db") rs<-dbSendQuery(con,"drop table historicalData") rs<-dbSendQuery(con,"create table historicalData (date text,open real,high real, low real,close real,volume real,adj_close real,code text)") rs<-dbSendQuery(con,"create unique index idx_historicalData on historicalData(code,date)") #stockMaster<-quoteStockMasterTsData() for(code in stockMaster$code){ print(paste("code=",code,sep="")) tryCatch({ data<-quoteStockTsData(code,fromDate) data<-transform(data,code=code) data$code<-as.character(data$code) data$date<-as.character(data$date) dbBeginTransaction(con) dbSendQuery(con,paste("delete from historicalData where code='",code,"'",sep="")) dbSendPreparedQuery(con,"insert into historicalData (date,open,high,low,close,volume,adj_close,code) values(:date, :open, :high, :low, :close, :volume, :adj_close, :code)",bind.data=data) dbCommit(con) }, error = function(e){ message(paste("ERROR:",code)) message(e) }) } }
株価を利用する際には株価コードの一覧が必要となってきます。そこで調子に乗って株価コードの一覧を取得してみました
例によってRFinanceYJに追加する関数です
quoteStockMasterTsData <- function(){ financial.data <- data.frame(NULL) function.stockMasterData<-function(hira){ r <- NULL result.num <- 20 master.data <- data.frame(NULL) start.num<-0 while( result.num >= 20 ){ start.num <- start.num + 1 quote.table <- NULL quote.url <- paste('http://stocks.finance.yahoo.co.jp/stocks/qi/?js=',hira,'&p=',start.num,sep="") try( r <- xmlRoot(htmlTreeParse(quote.url,error=xmlErrorCumulator(immediate=F))), TRUE) if( is.null(r) ) stop(paste("Can not access :", quote.url)) try( quote.table <- xpathApply(r,"//a[contains(@href,'/stocks/detail')]"), TRUE ) if( is.null(quote.table) ){ if( is.null(master.data) ){ stop(paste("Can not quote :", x)) }else{ return(master.data) } } size <- xmlSize(quote.table)/3 if(size==0){ return(master.data) } for(i in 1:size){ mtmp<-data.frame(code=xmlValue(quote.table[[i*3-2]]),name=xmlValue(quote.table[[i*3-1]])) mtmp$code<-as.character(mtmp$code) mtmp$name<-as.character(mtmp$name) master.data <- rbind(master.data,mtmp) } result.num <- xmlSize(quote.table)/3 Sys.sleep(1) } return(master.data) } hiraList<-"あいうえおかきくけこさしすせそたちつてとなにぬねのはひふへほまみむめもやゆよわ" for(i in 1:nchar(hiraList)){ hira<-substring(hiraList,i,i) master.data<-function.stockMasterData(hira) financial.data<-rbind(financial.data,master.data) } financial.data <- financial.data[order(financial.data$code),] return(financial.data) }
YahooFinanceの銘柄名の頭文字から一覧を得るページがあるのでこちらからダウンロードするようにしました。
結果ですが、ページ数が多いので若干時間がかかります。
> stockMaster<-quoteStockMasterTsData() > stockMaster code name 1 3076 あい ホールディングス(株) 2 7013 (株)IHI 3 4812 ISID 4 9753 アイエックス・ナレッジ(株) .. 3451 9927 (株)ワットマン 3452 2918 わらべや日洋(株) 3453 3344 (株)ワンダーコーポレーション 3454 7564 (株)ワークマン 3455 2429 (株)ワールドホールディングス
全銘柄とれるようになりました!
銘柄コード純になるように修正してみました
いつの間にかQiitaに引用されていました。(^^!
http://qiita.com/HirofumiYashima/items/4b3a3a3e49be59d81e4f
うまく動かないとのことなので修正をしてみました。
RFinanceYJはYahooのHTML変更に振り回されているようで、うまく動かない場合が多いです。
> library(RFinanceYJ) > quoteStockTsData("6758.t") 以下にエラー order(financial.data$date) : 引数 1 がベクトルではありません
どうもStockTsDataの↓の部分でテーブルの形を決まりきった物として扱っているので、Yahoo側のちょっとした変更(たとえば上の方になんか広告などを追加するとか)に負けてしまっています。
extractQuoteTable <- function(r,type){ if(type %in% c("fund","fx")){ tbl <- r[[2]][[2]][[7]][[3]][[3]][[9]][[2]] } else{ tbl <- r[[2]][[2]][[7]][[3]][[3]][[10]][[2]] } return(tbl) } while( result.num >= 51 ){ start.num <- start.num + 1 quote.table <- NULL quote.url <- paste('http://info.finance.yahoo.co.jp/history/?code=',x,start,end,'&p=',start.num,'&tm=',substr(time.interval,1,1),sep="") .. #try( quote.table <- r[[2]][[1]][[1]][[16]][[1]][[1]][[1]][[4]][[1]][[1]][[1]], TRUE ) try( quote.table <- extractQuoteTable(r,type), TRUE )
Xpathで解析するようにし、若干の変更には耐えられるように修正してみました。この株価ページにはテーブルが3個あり、その2番目が株価のテーブルですので、最新版の0.3.1にまたまたパッチを当ててみました。こう書き換えます。
#try( quote.table <- extractQuoteTable(r,type), TRUE ) try( quote.table <- xpathApply(r,"//table")[[2]], TRUE )
この関数だけを読み込んでもだめなので全体を記述しそれをlibrary(RFinanceYJ)に続けて読んでやります
library(RFinanceYJ) #API quoteStockTsData <- function(x, since=NULL,start.num=0,date.end=NULL,time.interval='daily') { time.interval <- substr(time.interval,1,1) function.stock <- function(quote.table.item){ if( xmlSize(quote.table.item) < 5) return(NULL) d <- convertToDate(xmlValue(quote.table.item[[1]]),time.interval) o <- as.number(xmlValue(quote.table.item[[2]])) h <- as.number(xmlValue(quote.table.item[[3]])) l <- as.number(xmlValue(quote.table.item[[4]])) c <- as.number(xmlValue(quote.table.item[[5]])) v <- ifelse(xmlSize(quote.table.item) >= 6,as.number(xmlValue(quote.table.item[[6]])),0) a <- ifelse(xmlSize(quote.table.item) >= 7,as.number(xmlValue(quote.table.item[[7]])),0) return(data.frame(date=d,open=o,high=h,low=l,close=c,volume=v, adj_close=a)) } return(quoteTsData(x,function.stock,since,start.num,date.end,time.interval,type="stock")) } quoteFundTsData <- function(x, since=NULL,start.num=0,date.end=NULL,time.interval='daily') { time.interval <- substr(time.interval,1,1) function.fund <- function(quote.table.item){ d <- convertToDate(xmlValue(quote.table.item[[1]]),time.interval) if(time.interval=='monthly'){ d <- endOfMonth(d) } c <- as.number(xmlValue(quote.table.item[[2]])) v <- as.number(xmlValue(quote.table.item[[3]])) return(data.frame(date=d,constant.value=c,NAV=v)) } return(quoteTsData(x,function.fund,since,start.num,date.end,time.interval,type="fund")) } quoteFXTsData <- function(x, since=NULL,start.num=0,date.end=NULL,time.interval='daily') { time.interval <- substr(time.interval,1,1) function.fx <- function(quote.table.item){ d <- convertToDate(xmlValue(quote.table.item[[1]]),time.interval) o <- as.number(xmlValue(quote.table.item[[2]])) h <- as.number(xmlValue(quote.table.item[[3]])) l <- as.number(xmlValue(quote.table.item[[4]])) c <- as.number(xmlValue(quote.table.item[[5]])) return(data.frame(date=d,open=o,high=h,low=l,close=c)) } return(quoteTsData(x,function.fx,since,start.num,date.end,time.interval,type="fx")) } ###### private functions ##### #get time series data from Yahoo! Finance. quoteTsData <- function(x,function.financialproduct,since,start.num,date.end,time.interval,type="stock"){ r <- NULL result.num <- 51 financial.data <- data.frame(NULL) #start <- (gsub("([0-9]{4,4})-([0-9]{2,2})-([0-9]{2,2})","&c=\\1&a=\\2&b=\\3",since)) #end <- (gsub("([0-9]{4,4})-([0-9]{2,2})-([0-9]{2,2})","&f=\\1&d=\\2&e=\\3",date.end)) start <- (gsub("([0-9]{4,4})-([0-9]{2,2})-([0-9]{2,2})","&sy=\\1&sm=\\2&sd=\\3",since)) end <- (gsub("([0-9]{4,4})-([0-9]{2,2})-([0-9]{2,2})","&ey=\\1&em=\\2&ed=\\3",date.end)) if(!any(time.interval==c('d','w','m'))) stop("Invalid time.interval value") extractQuoteTable <- function(r,type){ if(type %in% c("fund","fx")){ tbl <- r[[2]][[2]][[7]][[3]][[3]][[9]][[2]] } else{ tbl <- r[[2]][[2]][[7]][[3]][[3]][[10]][[2]] } return(tbl) } while( result.num >= 51 ){ start.num <- start.num + 1 quote.table <- NULL quote.url <- paste('http://info.finance.yahoo.co.jp/history/?code=',x,start,end,'&p=',start.num,'&tm=',substr(time.interval,1,1),sep="") #try( r <- xmlRoot(htmlTreeParse(quote.url,error=xmlErrorCumulator(immediate=F))), TRUE) # これだと取得時にエラーが出た。。 try(r<-htmlParse(quote.url)) if( is.null(r) ) stop(paste("Can not access :", quote.url)) #try( quote.table <- r[[2]][[1]][[1]][[16]][[1]][[1]][[1]][[4]][[1]][[1]][[1]], TRUE ) #try( quote.table <- extractQuoteTable(r,type), TRUE ) try( quote.table <- xpathApply(r,"//table")[[2]], TRUE ) if( is.null(quote.table) ){ if( is.null(financial.data) ){ stop(paste("Can not quote :", x)) }else{ financial.data <- financial.data[order(financial.data$date),] return(financial.data) } } size <- xmlSize(quote.table) for(i in 2:size){ financial.data <- rbind(financial.data,function.financialproduct(quote.table[[i]])) } result.num <- xmlSize(quote.table) Sys.sleep(1) } financial.data <- financial.data[order(financial.data$date),] return(financial.data) } #convert string formart date to POSIXct object convertToDate <- function(date.string,time.interval) { #data format is different between monthly and dialy or weekly if(any(time.interval==c('d','w'))){ result <- gsub("^([0-9]{4})([^0-9]+)([0-9]{1,2})([^0-9]+)([0-9]{1,2})([^0-9]+)","\\1-\\3-\\5",date.string) }else if(time.interval=='m'){ result <- gsub("^([0-9]{4})([^0-9]+)([0-9]{1,2})([^0-9]+)","\\1-\\3-01",date.string) } return(as.POSIXct(result)) } #convert string to number. as.number <- function(string) { return(as.double(as.character(gsub("[^0-9.]", "",string)))) } #return end of month date. endOfMonth <- function(date.obj) { startOfMonth <- as.Date(format(date.obj,"%Y%m01"),"%Y%m%d") startOfNextMonth <- as.Date(format(startOfMonth+31,"%Y%m01"),"%Y%m%d") return(startOfNextMonth-1) }
実行結果
> quoteStockTsData("6758.t",since="2010-01-01") date open high low close volume adj_close 1136 2010-01-04 2700.0 2744.0 2694.0 2731.0 4004500 2731.0 1135 2010-01-05 2781.0 2782.0 2704.0 2719.0 4894300 2719.0 1134 2010-01-06 2719.0 2757.0 2699.0 2744.0 4270900 2744.0 1133 2010-01-07 2750.0 2764.0 2723.0 2743.0 2899600 2743.0 1132 2010-01-08 2785.0 2809.0 2769.0 2809.0 6981100 2809.0 ..
長いヒストリカルデータも何のその!!
HirofumiYashima様の指摘により修正してみました
なお、実行環境はMacOS10.9のR version 3.0.3 です
こちらの記事はYahooの仕様変更によりとれなくなってしまいました
http://d.hatena.ne.jp/anagotan/20150120
こちらを参照ください
昨日のサンプルは、クリックイベントの処理がモデルの中に入っているので、ちょっとイマイチだったので、コントローラ側でその辺りの処理をするように再度修正してみました。また、ArrayなどのUndoに対応できる用にコントローラ側でデータ修正する際に、深いコピーでモデルに入れるようにしないといけないのでその辺りも合わせて修正しました。
////// /// /// module sample { var undoButton = $("#undo"); var redoButton = $("#redo"); var addButton = $("#add"); var updateNameButton = $("#update_name"); var updateValueButton = $("#update_value"); var itemButton = $("#item_button"); var attrButton = $("#attr_button"); export class App { private listview: ItemListView; private appModel: AppModel; constructor() { var self = this; self.appModel = new AppModel(); self.listview = new ItemListView({ "appModel": self.appModel // collectionのエイリアスを持つのではダメ、ポインタが書き換わるため }); undoButton.click(function () { console.log("undoButton.click"); self.appModel.undoHandler.undo(); self.draw(); }); redoButton.click(function () { console.log("redoButton.click"); self.appModel.undoHandler.redo(); self.draw(); }); addButton.click(function () { console.log("addButton.click"); var ary = >self.appModel.get("collection"); var newary = new Array ();// 新しくオブジェクトを作らないとstack.onしない ary.forEach(function (item) { newary.push(item); }); newary.push({ "name": "0", "value": "0" }); self.appModel.set({ "collection": newary }); //self.listview.list = self.appModel.get("collection"); self.draw(); }); updateNameButton.click(function () { console.log("updateNameButton.click"); var newary = new Array (); self.appModel.get("collection").forEach(function (item) { newary.push({"value":item.value,"name":_.random(100).toString() }); // 新しくオブジェクトを作らないとstack.onしない }); self.appModel.set({ "collection": newary }); self.draw(); }); updateValueButton.click(function () { console.log("updateValueButton.click"); var newary = new Array (); self.appModel.get("collection").forEach(function (item) { newary.push({ "name": item.name, "value": _.random(100).toString() }); }); self.appModel.set({ "collection": newary }); self.draw(); }); itemButton.click(function () { console.log("itemButton.click"); self.appModel.set({ "item_item": { "item_name": _.random(100).toString(), "item_value": _.random(100).toString() } }); self.draw(); }); attrButton.click(function () { console.log("attrButton.click"); self.appModel.set({ "attr": _.random(100).toString() }); self.draw(); }); } draw(): void { console.log("draw"); this.listview.render(); $("#item_name").text(this.appModel.get("item_item").item_name); $("#item_value").text(this.appModel.get("item_item").item_value); $("#attr").text(this.appModel.get("attr")); } } export class AppModel extends Backbone.Model { undoHandler: UndoHandler; constructor(options?) { super(options); this.undoHandler = new UndoHandler(this); this.bind("change", this.change); } defaults() { return { collection:[ { "name": "0", "value": "0" } ], item_item: { "item_name": "", "item_value":"" }, attr:"", } } change(): void { console.log("AppModel.change"); } } export class UndoHandler { private manager: Backbone.UndoManager; constructor(collections:any) { var self = this; self.manager = new Backbone.UndoManager({ register: collections, track: true }); self.manager.removeUndoType("change:isChanging"); self.manager.addUndoType("change:isChanging", { "on": function (model, isChanging, options) { console.log("manager.addUndoType.on"); }, "undo": function (model, before, after, options) { console.log("manager.addUndoType.undo"); model.set(before) }, "redo": function (model, before, after, options) { console.log("manager.addUndoType.redo"); model.set(after) } }); self.manager.on("all", function (type) { console.log("all.type:" + type); switch (type) { case "undo": { console.log("undo"); break; } case "redo": { console.log("redo"); break; } } }); self.manager.trigger("undo redo"); self.manager.stack.on("add", function () { console.log("stack.on.add"); }); } undo(): void{ this.manager.undo(true); } redo(): void { this.manager.redo(true); } } class ItemModel extends Backbone.Model { constructor(options?) { if (options) { if (options.name) this.set({ "name": options.name }, { "validate": true }); if (options.value) this.set({ "value": options.value }, { "validate": true }); } super(options); this.bind("change", this.change); } change(): void { console.log("ItemModel.change:name="+this.get("name")+",value="+this.get("value")); } defaults() { return { "id": _.uniqueId(), "name": "", "value":"" } } validate(attrs) { if (!attrs.name || _.isEmpty(attrs.name)) { return "name must not be empty"; } if (!attrs.value || _.isEmpty(attrs.value)) { return "value must not be empty"; } } } class ItemCollection extends Backbone.Collection { constructor(options) { super(options); } } class ItemView extends Backbone.View { template: (data: any) => string; constructor(options?) { var html = $("#template-item").html(); this.template = _.template(html); this.events = { "click": "onclick", "change":"onchange" }; super(options); this.model.bind("change", this.render, this); } private onclick() { console.log("ItemView.onclick"); } private change() { console.log("ItemView.onchange"); this.render(); } render(): ItemView { var html = this.template(this.model.toJSON()); $(this.el).html(html); return this; } } class ItemListView extends Backbone.View { appModel: any; // backbone.model constructor(options?) { var self = this; this.el = "#item-list"; if (options) { if (options.appModel) this.appModel = options.appModel; } this.collection = new Backbone.Collection (); this.appModel.get("collection").forEach(function (item) { self.collection.add(new ItemModel(item)); }); super(options); this.listenTo(this.collection, 'add', this.render); this.listenTo(this.collection, 'remove', this.render); this.listenTo(this.collection, 'reset', this.render); } render(): ItemListView { var el = $(this.el); el.empty(); var self = this; self.collection = new Backbone.Collection (); this.appModel.get("collection").forEach(function (item) { self.collection.add(new ItemModel(item)); }); this.collection.forEach(item=> { console.log("ItemListView.render"); var view=new ItemView({ "model": item }).render(); el.append(view.el); }); return this; } } } new sample.App().draw();
item_name:,item_value:
attr:
次に単体テストをmochaを使用して記述してみます。
予めmocha.js,mocha.d.ts,mocha.css,asert.js,assert.d.tsを用意しておく必要があります。
書いていてわかったのですが、実はBackbone.undo.jsはデータをスタックする際にタイムスタンプを用いているらしいので、テストユニットでババっと流すと、同じタイムスタンプでスタックされてしまい、思い通りの動きになりません。
そこで、テストユニットにはsettimeoutで少し実行間隔を開けてやることにします。
////// module sample { describe("テスト", () => { it("UndoRedoTest", (done) => {// done:これで同期でテストできる var model = new AppModel(); // どうやらBackboneUndoはタイムスタンプを用いているらしいので同じ時刻にstack.onした場合には認識できない setTimeout(() => { model.set({ "attr": "attrvalue" }); assert(model.get("attr") == "attrvalue"); console.log("attr:" + model.get("attr")); setTimeout(() => { model.set({ "item_item": { "item_name": "item_name1", "item_value": "item_value1" } }); assert(model.get("item_item").item_name == "item_name1"); console.log("item_item.item_name:" + model.get("item_item").item_name); setTimeout(() => { // undo model.undoHandler.undo(); assert(model.get("item_item").item_name == ""); console.log("item_item.item_name:" + model.get("item_item").item_name); setTimeout(() => { // redo model.undoHandler.redo(); assert(model.get("item_item").item_value == "item_value1"); console.log("item_item.item_value:" + model.get("item_item").item_value); done(); // これで同期でテストできる }, 100); }, 100); }, 100); }, 100); }); }); }
done()を入れることにより、同期をとったテストを実行できます
drawing test
本来ならば、gruntとphantom.jsを用いてコマンドラインから実行するのが普通でしょうが、このapp-test.htmlをブラウザで開くだけでもテストを実行できます。エラーなどはchromeのデバッグツールで確認して下さい
サンプルはこちらにアップしておきました
https://github.com/anagotan/typescript_sample/tree/master/undo
前回のサンプルではModelとView辺がうまく分離できていなかったので修正してみた
Backbone.Modelに状態を保存し、イベントを拾って、Undoなどの処理を行うようにしてみた
////// /// /// module sample { var undoButton = $("#undo"); var redoButton = $("#redo"); var addButton = $("#add"); var updateNameButton = $("#update_name"); var updateValueButton = $("#update_value"); export class App { private listview: ItemListView; private appModel: AppModel; constructor() { this.appModel = new AppModel(); this.listview = new ItemListView({ "collection":this.appModel.get("collection") }); var self = this; undoButton.click(function () { console.log("undoButton.click"); self.appModel.set({ "undoButtonClicked": true }); }); redoButton.click(function () { console.log("redoButton.click"); self.appModel.set({ "redoButtonClicked": true }); }); addButton.click(function () { console.log("addButton.click"); self.appModel.set({ "addButtonClicked": true }); }); updateNameButton.click(function () { console.log("updateNameButton.click"); self.appModel.set({ "updateNameButtonClicked": true }); }); updateValueButton.click(function () { console.log("updateValueButton.click"); self.appModel.set({ "updateValueButtonClicked": true }); }); } draw(): void { this.listview.render(); } } class AppModel extends Backbone.Model { private undoHandler: UndoHandler; constructor(options?) { super(options); this.undoHandler = new UndoHandler(this.get("collection")); this.bind("change", this.change); } defaults() { return { collection: new ItemCollection([ new ItemModel({ "name": "0", "value": "0" }), ]), undoButtonClicked: false, redoButtonClicked: false, addButtonClicked: false, updateNameButtonClicked: false, updateValueButtonClicked:false } } change(): void { if (this.get("undoButtonClicked")) { this.undoHandler.undo(); this.set({ "undoButtonClicked": false }); } if (this.get("redoButtonClicked")) { this.undoHandler.redo(); this.set({ "redoButtonClicked": false }); } if (this.get("addButtonClicked")) { this.get("collection").add(new ItemModel({ "name": "0", "value": "0" })); this.set({ "addButtonClicked": false }); } if (this.get("updateNameButtonClicked")) { this.get("collection").forEach(function (item) { item.set("name", _.random(100).toString()); }); this.set({ "updateNameButtonClicked": false }); } if (this.get("updateValueButtonClicked")) { this.get("collection").forEach(function (item) { item.set("value", _.random(100).toString()); }); this.set({ "updateValueButtonClicked": false }); } console.log("AppModel.change"); } } class UndoHandler { private manager: Backbone.UndoManager; constructor(collections:Backbone.Collection ) { var self = this; self.manager = new Backbone.UndoManager({ register: collections, track: true }); self.manager.removeUndoType("change:isChanging"); self.manager.addUndoType("change:isChanging", { "on": function (model, isChanging, options) { console.log("manager.addUndoType.on"); }, "undo": function (model, before, after, options) { console.log("manager.addUndoType.undo"); model.set(before) }, "redo": function (model, before, after, options) { console.log("manager.addUndoType.redo"); model.set(after) } }); self.manager.on("all", function (type) { console.log("all.type:" + type); switch (type) { case "undo": { console.log("undo"); break; } case "redo": { console.log("redo"); break; } } }); self.manager.trigger("undo redo"); self.manager.stack.on("add", function () { console.log("stack.on.add"); }); } undo(): void{ this.manager.undo(true); } redo(): void { this.manager.redo(true); } } class ItemModel extends Backbone.Model { constructor(options?) { if (options) { if (options.name) this.set({ "name": options.name }, { "validate": true }); if (options.value) this.set({ "value": options.value }, { "validate": true }); } super(options); this.bind("change", this.change); } change(): void { console.log("ItemModel.change:name="+this.get("name")+",value="+this.get("value")); } defaults() { return { "id": _.uniqueId(), "name": "", "value":"" } } validate(attrs) { if (!attrs.name || _.isEmpty(attrs.name)) { return "name must not be empty"; } if (!attrs.value || _.isEmpty(attrs.value)) { return "value must not be empty"; } } } class ItemCollection extends Backbone.Collection { constructor(options) { super(options); } } class ItemView extends Backbone.View { template: (data: any) => string; constructor(options?) { var html = $("#template-item").html(); this.template = _.template(html); this.events = { "click": "onclick", "change":"onchange" }; super(options); this.model.bind("change", this.render, this); } private onclick() { console.log("ItemView.onclick"); } private change() { console.log("ItemView.onchange"); this.render(); } render(): ItemView { var html = this.template(this.model.toJSON()); $(this.el).html(html); return this; } } class ItemListView extends Backbone.View { constructor(options?) { this.el = "#item-list"; if (options) { if (options.collection) this.collection = options.collection; } super(options); this.listenTo(this.collection, 'add', this.render); this.listenTo(this.collection, 'remove', this.render); this.listenTo(this.collection, 'reset', this.render); } render(): ItemListView { var el = $(this.el); el.empty(); this.collection.forEach(item=> { console.log("ItemListView.render"); var view=new ItemView({ "model": item }).render(); el.append(view.el); }); return this; } } } new sample.App().draw();
AppModelクラスが状態を保存している。このBackboneModelに対し、各マウスイベントで発生したClickイベントの関数からModelに対し値をセット。Backboneのchangeイベントが発生するのでそれを拾って処理を行う感じ
Backboneをこのように使うのかどうかは不明。。自己流です