グラフの設定が保存されない不具合を修正したjiji-1.2.5をリリース
グラフの設定が保存されない不具合にいまさら気がついたので、修正版をリリースします。
→Github - unageanu/jiji
→Gemcutter.org - jiji
GitHubのgemビルド機能が廃止になったようなので、Gemcutterにアップロードしています。gemの名前も「unagenau-jiji」→「jiji」に修正。初めて使う場合は
$ gem install gemcutter $ gem tumble
してから、以下のコマンドを実行してください。
$ jiji stop $ gem uninstall unageanu-jiji $ gem install jiji $ jiji start
クリック証券で提供されている為替レートのヒストリカルデータをjijiに取り込むスクリプト
クリック証券で提供されているヒストリカルデータをダウンロードして、jijiで使えるCSV形式にコンバートするツールを作ってみました。
使い方
$ ruby rate_data_importer.rb <クリック証券のユーザーID> <パスワード> <取り込む年> <開始月> <終了月>
具体例。
$ ruby rate_data_importer.rb foo var 2007 1 2
- 実行すると、「./tmp」にヒストリカルデータが展開され、「./converted」に変換済みデータが作成されます。
- あとは、「./converted」以下のフォルダをjijiのレートデータ置き場(デフォルトでは~/.jiji/rate_datas)に配置すれば、チャートやバックテストで利用可能となります。
注意事項
ツールのコード
以下です。ちょい長いのでご注意。
require 'kconv' require 'rubygems' require 'mechanize' require 'zip/zip' require 'tmpdir' require 'jiji/registry' module JIJI # #==クリック証券のヒストリカルデータダウンロードサービスから為替レートデータを取得するユーティリティ # module Download #===ダウンロードを行うためのセッションを開始する #userId:: クリック証券のユーザーID #password:: ログインパスワード #proxy:: プロキシ def self.session( userid, password, proxy=nil ) client = WWW::Mechanize.new {|c| # プロキシ if proxy uri = URI.parse( proxy ) c.set_proxy( uri.host, uri.port ) end } client.keep_alive = false client.max_history=0 client.user_agent_alias = 'Windows IE 7' # ログイン page = client.get("https://sec-sso.click-sec.com/loginweb/") raise "Unexpected Error" if page.forms.length <= 0 form = page.forms.first form.j_username = userid form.j_password = password client.submit(form, form.buttons.first) session = Session.new( client ) if block_given? begin return yield( session ) ensure session.logout end else return session end end class Session def initialize( client ) @client = client end #===CSVデータをダウンロードする #yesr:: 年 #month:: 月 #pair:: 通貨ペア #to:: ダウンロード先ディレクトリ def download( year, month, pair, to="./" ) FileUtils.makedirs(to) file = "#{to}/#{pair}_#{year}_#{month}.zip" result = @client.get("https://tb.click-sec.com/fx/historical/historicalDataDownload.do?" + "y=#{year}&m=#{sprintf("%02d", month)}&c=#{C_MAP[pair]}&n=#{pair}" ) open( file, "w" ) {|w| w << result.body } extract( file, "#{to}" ) FileUtils.rm(file) end #===ログアウトする def logout @client.get("https://sec-sso.click-sec.com/loginweb/sso-logout") end #===zipファイルを展開する。 #zip:: zipファイル #dest:: 展開先ディレクトリ def extract( zip, dest ) FileUtils.makedirs(dest) Zip::ZipFile.foreach(zip) {|entry| if entry.file? FileUtils.makedirs("#{dest}/#{File.dirname(entry.name)}") entry.get_input_stream {|io| open( "#{dest}/#{entry.name}", "w" ) {|w| while ( bytes = io.read(1024)) w.write bytes end } } else FileUtils.makedirs("#{dest}/#{entry.name}") end } end C_MAP = { :USDJPY=>"01", :EURJPY=>"02", :GBPJPY=>"03", :AUDJPY=>"04", :NZDJPY=>"05", :CADJPY=>"06", :CHFJPY=>"07", :ZARJPY=>"08", :EURUSD=>"09", :GBPUSD=>"10", :AUDUSD=>"11",:EURCHF=>"12", :GBPCHF=>"13", :USDCHF=>"14" } end PAIRS = [ :USDJPY, :EURJPY, :GBPJPY, :AUDJPY, :NZDJPY, :CADJPY, :CHFJPY, :ZARJPY, :EURUSD, :GBPUSD, :AUDUSD,:EURCHF, :GBPCHF, :USDCHF ] end class Converter def initialize( ) @registry = JIJI::Registry.new( "./" ) end #===展開したCSVデータをjijiの形式にフォーマットする。 #csv_dir:: csvデータ置き場 #to:: CSVデータ def convert( dir, to ) FileUtils.mkdir_p to dao = @registry.rate_dao dao.instance_variable_set(:@data_dir, to) # CSVを読みつつデータを作成 each_rate(dir) {|rates| dao.next_rates( rates ) } end def each_rate(dir, &block) yyyymm = Dir.entries( dir ).reject{|d| !(d=~ /\d{6}/) }.sort yyyymm.each {|ym| 1.upto(31).each {|d| readers = {} begin JIJI::Download::Session::C_MAP.each {|p| file = "#{dir}/#{ym}/#{p[0]}_#{ym}#{sprintf("%02d", d)}.csv" next unless File.exist? file readers[p[0]] = PushBackReader.new(CSV.open( file, 'r' )) readers[p[0]].shift # 最初のデータはヘッダーなので除外 } next if readers.empty? read( readers, &block ) ensure readers.each {|p| begin p[0].close rescue; end } end } } end def read( readers ) while( true ) first = nil readers.each {|p| line = p[1].shift p[1].unshift line next if !line || line.length < 5 next unless line[0] =~ /(\d{4})(\d{2})(\d{2})(\d{2})(\d{2})(\d{2})/ first = !first || first.to_i > line[0].to_i ? line[0] : first } return unless first buff = readers.inject([]) {|r,p| line = p[1].shift next r if !line || line.length < 5 if ( line[0] != first) p[1].unshift line next r end 0.upto(3) {|i| time = Time.local( $1.to_i, $2.to_i, $3.to_i, $4.to_i, $5.to_i, i*10 ) map = r[i] || r[i] = {} bid = line[i+1].to_f map[p[0].to_sym] = Rate.new( bid, bid+SPREAD[p[0].to_sym], 0, 0, time ) } r } return if !buff || buff.empty? buff.each {|e| yield Rates.new( {}, e, e[:USDJPY].time ) } end end SPREAD = { :AUDJPY => 0.02, :GBPUSD => 0.0003, :NZDJPY => 0.024, :AUDUSD => 0.0003, :CADJPY => 0.03, :EURCHF => 0.0003, :USDJPY => 0.008, :CHFJPY => 0.03,:GBPCHF => 0.0004, :EURJPY => 0.014,:ZARJPY => 0.04,:USDCHF => 0.0004, :GBPJPY => 0.024,:EURUSD => 0.00016 } end class PushBackReader def initialize( reader ) @reader = reader @buff = [] end def shift @buff.empty? ? @reader.shift : @buff.shift end def unshift(v) @buff.unshift v end def close @reader.close end end end puts "start download. #{Time.now}" JIJI::Download.session( ARGV[0], ARGV[1] ) {|s| ARGV[3].to_i.upto( ARGV[4].to_i ) {|month| JIJI::Download::PAIRS.each {|pair| s.download( ARGV[2], month, pair, "./tmp" ) puts "downloaded. #{ARGV[2]}/#{sprintf("%02d", month)} #{pair}" } } } puts "end download. #{Time.now}" puts "start convert. #{Time.now}" converter = JIJI::Converter.new converter.convert( "./tmp", "./converted" ) puts "end convert. #{Time.now}"
細かい機能追加を行ったjiji-1.2.4をリリース
細かい機能追加を行ったjiji-1.2.4をリリースしました。
→Github - unageanu/jiji
前バージョンからの変更点は以下です。
- 取引結果の約定日時/決済日時から、その時点のチャートにジャンプする機能を追加しました。
- チャート上で取引の詳細を表示する際に、表示中の取引を強調表示するように修正しました。
あと、JSON-RPC APIを利用してバックテストの実行と結果の取得を行うユーティリティもリリース物に追加しています。
更新手順
以下の操作を実行してください。
$ gem update unageanu-jiji
バックテストを実行して結果を取得するサンプル
jijiにバックテストを登録&実行して、結果を取得するスクリプトのサンプルです。
- デモサイトに接続して、
- パラメータの違う移動平均エージェントを2つ実行するバックテストを作成。
- 実行終了を待ち、結果を出力します。
いろいろいけてないところを隠蔽するため、ユーティリティクラスを作ってみました。(ユーティリティのソースは末尾)
require 'backtester' # テストを実行するためのユーティリティクラス #テスターを作成 tester = JIJI::BackTester.new( "http://unageanu.homeip.net/jiji-demo" ) #エージェントを作成 agents = [] agents << tester.create_agent( "MovingAverageAgent", "moving_average_agent.rb", "移動平均_12-36", {"long"=>36,"period"=>10,"short"=>12}) agents << tester.create_agent( "MovingAverageAgent", "moving_average_agent.rb", "移動平均_25-75", {"long"=>75,"period"=>10,"short"=>25}) #テストを実行 time = Time.local(2009, 8, 20).to_i process_id = tester.regist_test( "移動平均テスト2", "", time, time+24*60*60*3, agents ) #テストの実行完了を待つ tester.wait( process_id ) #結果を確認 result = tester.get_result( process_id ) result.each_pair {|k,v| puts "\n---#{k}" puts "profit or ross : #{v.profit_or_loss}" puts "positions : " v.positions.each {|p| puts " #{p["pair"]} #{p["sell_or_buy"]} #{p["profit_or_loss"]}" } }
実行結果です。
---移動平均_25-75 profit or ross : -4513 positions : EURJPY sell -4900 EURJPY sell -100 EURJPY sell -3800 EURJPY buy 10094 EURJPY buy -5807 ---移動平均_12-36 profit or ross : -5711 positions : EURJPY buy -100 EURJPY buy 5594 EURJPY sell -6300 EURJPY sell 900 EURJPY sell -1599 EURJPY sell -100 EURJPY buy -1699 EURJPY buy -1807 EURJPY sell -500 EURJPY buy -100
ユーティリティのソース
次のとおり。
require "rubygems" require "jiji/util/json_rpc_requestor" require 'uuidtools' module JIJI #==jijiに接続してバックテストの実行と結果の取得を行うためのユーティリティ。 class BackTester #===コンストラクタ #endpoint:: 接続先サーバーを示すエンドポイント 例) "http://unageanu.homeip.net/jiji-demo" def initialize( endpoint ) @agent_service = JSONBroker::JsonRpcRequestor.new("agent", endpoint) @process_service = JSONBroker::JsonRpcRequestor.new("process", endpoint) @trade_result_service = JSONBroker::JsonRpcRequestor.new("trade_result", endpoint) @agents = @agent_service.list_agent_class end #===エージェントの情報を取得する。 #class_name:: クラス名 #file:: クラスが定義されているファイル名 #return:: エージェントの情報/対応するエージェントがなければnil def get_agent_info( class_name, file ) return @agents.find{|i| i["class_name"]==class_name && i["file_name"]==file } end #===バックテストに登録するためのエージェント情報を作成する。 #class_name:: クラス名 #file:: クラスが定義されているファイル名 #name:: エージェント名 #properties:: エージェントのプロパティ #return:: バックテストに登録するためのエージェント情報 def create_agent( class_name, file, name, properties ) info = get_agent_info( class_name, file ) raise "agent not found." unless info agent = info.dup agent["id"] = UUIDTools::UUID.random_create().to_s agent["class"] = "#{info["class_name"]}@#{info["file_name"]}" agent["name"] = name agent["property_def"] = agent["properties"].inject({}){|r,i| r[i["id"]] = i r } agent["properties"] = properties return agent end #===バックテストを実行する。 #title:: テストの名前 #memo:: メモ #start_time:: 開始日時(UNIXタイム/1970-01-01からの秒数) #end_time:: 終了日時(UNIXタイム/1970-01-01からの秒数) #agents:: 実行するエージェントの配列 #return:: バックテストの識別ID(process_id) def regist_test( title, memo, start_time, end_time, agents ) return @process_service.new_test( title, memo, start_time, end_time, agents )["id"] end #===バックテストの完了を待つ #process_id:: バックテストの識別ID def wait( process_id ) while ( true ) status = @process_service.status( [process_id] )[0]["state"] return if status == "ERROR_END" || status == "FINISHED" || status == "CANCELED" sleep 5 end end #===バックテストの実行結果を取得する。 #process_id:: バックテストの識別ID #return:: エージェント名をキーとする実行結果のハッシュ def get_result( process_id ) result = @trade_result_service.list( process_id, "5d", nil, nil ) return result.inject({}) {|r, item| info = r[item["trader"]] ||= Result.new( 0, [] ) info.profit_or_loss += item["profit_or_loss"] info.positions << item r } end #実行結果 Result = Struct.new( :profit_or_loss, :positions ) end end
バックテストを登録するスクリプトのサンプル
エージェントのバックテストでは、各種パラメータの組み合わせをいろいろと変えて動作を調整したい場合があります。(カーブフィティングとかいうやつてずね。)jijiでは複数のエージェントを一括でテスト可能ですが、UIからちまちま登録するのはなかなかメンドイ。スクリプトで、for文とか使ってさくっと一括登録したい。
→ならJSONインターフェイスを使って登録すればいいんじゃね?ということで、jijiにバックテストを登録するRubyスクリプトのサンプルを書いてみました。
- 短期移動平均(10,20,30)x長期移動平均(30,40,50)のすべての組み合わせの移動平均エージェントを実行するバックテストを、デモサイトに登録するサンプルです。
- 処理の流れは以下のとおりです。
- jiji添付のクライアントライブラリを使用して、jijiに接続するためのスタブクラスを作成。
- スタブを利用して、エージェント一覧を取得
- 一覧から実行するエージェントを探し、実行時の引数として渡すエージェント一覧を作成。
- テスト名、メモ、開始日時、終了日時、↑で作成したエージェントの一覧を指定してバックテストを実行します。
require "rubygems" require "jiji/util/json_rpc_requestor" require 'uuidtools' END_POINT = "http://unageanu.homeip.net/jiji-demo" #1.jiji添付のクライアントライブラリを使用して、jijiに接続するためのスタブクラスを作成。 agent_service = JSONBroker::JsonRpcRequestor.new("agent", END_POINT) process_service = JSONBroker::JsonRpcRequestor.new("process", END_POINT) #2.エージェント一覧を取得し、登録対象とするエージェントを探索 ma = agent_service.list_agent_class.find{|i| i["class_name"]=="MovingAverageAgent" && i["file_name"]=="moving_average_agent.rb" } #3.実行するエージェント情報を作成。 # 短期移動平均→10,20,30 # 長期移動平均→40,50,60 #で、3x3の組み合わせをすべて試す。 agents = [] [10,20,30].each {|short| # 短期移動平均のバリエーション [40,50,60].each {|long| # 長期移動平均のバリエーション #登録情報を作成。 agent = ma.dup agent["id"] = UUIDTools::UUID.random_create().to_s agent["class"] = "#{agent["class_name"]}@#{agent["file_name"]}" #いけてない・・。 agent["name"] = "移動平均_#{short}-#{long}" #コンバートしないと実行結果からエージェントの詳細が見えなかったり・・いけてない。 agent["property_def"] = agent["properties"].inject({}){|r,i| r[i["id"]] = i r } agent["properties"] = {"long"=>long,"period"=>10,"short"=>short} agents << agent } } #4.テスト名、メモ、開始日時、終了日時、エージェントを指定してバックテストを実行。 time = Time.local(2009, 8, 20).to_i process_service.new_test( "バックテスト登録のテスト", "メモ", time, time+12*60*60, agents )
もともと公開APIとして考えていなかったのでいろいろといけてない部分もありますが・・。ご容赦。
エージェントエディタを一新したjiji-1.2.0をリリース
エージェントエディタの一新など、いくつかの機能強化、バグフィクスを行ったjiji-1.2.0をリリースしました。
→Github - unageanu/jiji
更新内容、更新手順については、こちらを参照ください。
さて、昨日嵌っていたgemがビルドされない問題は、どうもリポジトリが壊れてしまっていたことが原因だったようです。pushはできるがcloneはできない状態になっていて、エラーメッセージを頼りにgoogleで検索して修復を試みるもうまくいかず、結局新しいリポジトリを新規に作成することにしました。
コミットログはすべて失われてしまいましたが、まぁ、やむなしと判断。新規のリポジトリでは無事にgemが作成されました。
エージェントエディタを一新したjiji-1.2.0をリリース、、ならず。
エージェントエディタの一新など、いくつかの機能強化、バグフィクスを行ったjiji-1.2.0をリリース、、、といいたいところですが、GitHubにコミットしたものの1時間待ってもgemがビルドされず・・・・。
→オープン・フリーのFX自動取引システム「jiji」
→Github - unageanu/jiji
んー、メンテナンス中とかなのかな?しばらく様子をみて、配布が確認できたら別途報告します。
変更点
- エージェントエディタの一新
- 複数のエージェントや共有ライブラリをタブで同時に編集できるようになりました。
- エージェントや共有ライブラリをディレクトリで分類できるようになりました。
- 標準ライブラリの追加
- バックテストの再実行機能を追加
- バックテストを1クリックで再実行できるようになりました。
- グラフ出力の不具合修正
- エージェントを削除するとそのグラフも表示できなくなる問題を改修しました。
- この変更でリアルトレードでは古いグラフが蓄積されていくようになったため、不要なグラフを破棄する機能も追加しました。
エージェントエディタはCodePressを捨て、EditAreaに変更。タブでの複数ファイル同時編集やフルスクリーンモード、検索機能がついてちょっと高機能になっています。動作もCodePressよりは早い印象(それでもやや野暮ったいですが)。どうしても不満という場合は、「Ctrl+h」でシンタックスハイライトをoffにすると、大分改善されます。
更新手順
以下の操作を実行してください。
$ gem update unageanu-jiji $ jiji setting
※新たに追加された共有ライブラリをコピーするため、今回は「jiji setting」の再実行も必要です。「jiji setting」での入力内容はインストール手順を参照ください。