無料で使えるシステムトレードフレームワーク「Jiji」 をリリースしました!

・OANDA Trade APIを利用した、オープンソースのシステムトレードフレームワークです。
・自分だけの取引アルゴリズムで、誰でも、いますぐ、かんたんに、自動取引を開始できます。

セッションの開始とノード情報の取得

Adobe Share APIにアクセスして、認証→セッションを開始→ノード情報を取得するところまで作った。

呼び出し例。

# クライアントを作る
cl = Share::Client.new( <APIキー>, <shared secret>, <ユーザーID>, <パスワード> )

# セッションを開始
cl.session { |session|
  
  # ルートフォルダの情報を得る。
  info = session.get_node_info
  
  # ルートフォルダのid
  puts info[:node][:nodeid]
  # 子要素の情報表示
  info[:children].each { |item|
    puts item[:name] + " : " + item[:nodeid]  
  }
}

クライアントライブラリ(share-client.rb)

require 'http-requestor'
require 'digest/md5'
require "rexml/document"

# Adobe Share クライアント
module Share
  
  class Base
    def initialize( api_key, shared_secret )
      @api_key = api_key
      @shared_secret = shared_secret
    end
    
    # 認証ヘッダを挿入する。
    def set_authorization_header( req, shared_secret, session_id=nil  )
        h = req["headers"]
        data = req["method"] + " " + "https://" + req["host"] + req["path"] + " " + (Time.new.to_f * 1000).round.to_s
        auth = "AdobeAuth "
        auth << "sessionid=\"" << session_id << "\"," if session_id != nil
        auth << "apikey=\"" << @api_key << "\","
        auth << "data=\"" << data << "\","
        auth << "sig=\"" << Digest::MD5.hexdigest( data + shared_secret ) << "\""
        h["Authorization"] = auth      
    end
    # レスポンスを解析する
    def parse_response( body )
      raise body unless body =~ /^\s*<response/
      doc = REXML::Document.new(body)
      res = {
        :status => doc.attributes["status"],
        :property  => {}
      }
      doc.elements.each("./response/*") { |item|
        res[:property][item.name.to_sym] = item.text
      }
      return res
    end
  end
  
  class Client < Base
  
    def initialize( api_key, shared_secret, user_name, password )
      super( api_key, shared_secret )
      @user_name = user_name
      @password = password
      @hr = HttpRequestor.new( File.dirname(__FILE__) + "/share-requests.yaml")
      @authtoken = login  # ログイン
    end
    
    # セッションを開始する
    def session( &block )
        # セッションを開始
        res = @hr.new_session() { |req|
          set_authorization_header( req, @shared_secret )
          req["body"] =<<-POST
            <request>
              <authtoken>#{@authtoken}</authtoken>
            </request>
          POST
          req["body"].gsub!( /\n/, "" ) # 改行を取る。
        }
        res = parse_response( res.body )
        session_id = res[:property][:sessionid]
        secret = res[:property][:secret]
        begin
          session = Session.new( @api_key, secret, session_id, @hr )
          block.call( session ) if block_given?
        ensure
          # セッション終了
          if ( session_id != nil )
            @hr.end_session() { |req|
              req["path"] = req["path"] + session_id + "/"
              set_authorization_header( req, secret, session_id )          
              req["path"] = req["path"] + "?method=DELETE"
            }      
          end
        end
    end
  
  private
  
    # ログインする。
    def login()
      res = @hr.auth() { |req|
        set_authorization_header( req, @shared_secret )
        req["body"] =<<-POST
          <request>
            <username>#{@user_name}</username>
            <password>#{@password}</password>
          </request>
        POST
        req["body"].gsub!( /\n/, "" ) # 改行を取る。
      }
      res = parse_response( res.body )
      return res[:property][:authtoken]
    end
  end
 
  class Session < Base
    def initialize( api_key, shared_secret, session_id, hr )
      super( api_key, shared_secret )
      @hr = hr
      @session_id = session_id
    end
    
    # フォルダの情報を取得する。
    # @param node_id 情報を取得するノードのID。nilの場合ルートノードの情報を返す
    def get_node_info( node_id=nil )
      res = @hr.get_node_info() { |req|
        req["path"] = req["path"] + node_id + "/" if node_id != nil
        set_authorization_header( req, @shared_secret, @session_id )
      }
      return parse_get_nodeinfo_response( res.body )
    end
    
  private
    # ノード情報取得のレスポンスを解析する
    def parse_get_nodeinfo_response( body )
      raise body unless body =~ /^\s*<response/
      doc = REXML::Document.new(body)
      res = {
        :status => doc.attributes["status"],
        :node  => {},
        :children=> []
      }
      doc.elements.each("./response/node") { |item|
        item.attributes.each { |k, v|
          res[:node][k.to_sym] = v
        }
      }
      doc.elements.each("./response/children/node") { |item|
        attrs = {}
        item.attributes.each { |k, v|
          attrs[k.to_sym] = v
        }
        res[:children] << attrs
      }      
      return res
    end  
  end
end

HTTPアクセスユーティリティもちょっと変更。

require "net/http"
require "net/https"
require 'yaml'
require 'cgi'

class HttpRequestor

  # 初期化する。
  def initialize( request_file="./request.yaml" )
    File.open( request_file ) {|f|
      @request = YAML.load( f )
    }
  end

  # メソッドが定義されていない場合に呼ばれる。
  def method_missing( name, *args, &block )
    return do_request( name.to_s, &block )
  end

  # リクエストを実行する。
  # ブロックが与えられた場合は、ブロックにリクエスト情報を渡し編集する。
  def do_request ( method, &block )
    fail "unknown method " << method unless @request["requests"].key?( method )
    request = merge( {"params"=>{}, "headers"=>{}}, @request["requests"][method] )
    request = merge( request, @request["default"] )
    block.call( request ) if block_given?

    response = nil
    Net::HTTP.version_1_2

    requestor = nil
    if ( @request["conf"] != nil &&  @request["conf"]["proxy_host"] != nil && @request["conf"]["proxy_port"] != nil )
      requestor = Net::HTTP::Proxy(
        @request["conf"]["proxy_host"],
        @request["conf"]["proxy_port"]
      ).new( request["host"], request["port"] )
    else
      requestor = Net::HTTP.new( request["host"], request["port"] )
    end

    # for ssl
    if ( request["ssl"] )
        requestor.use_ssl = true
        requestor.ca_file = request["ca_file"]
        requestor.verify_mode = OpenSSL::SSL::VERIFY_PEER
        requestor.verify_depth = 5
    end

    requestor.start { |http|

      param = map_to_param( request["params"] )
      param += "&" + list_to_param(request["param-list"])
      if request["method"] == "GET"
        request["path"]  = request["path"] + "?" + param if param.length > 1
        response = http.get( request["path"], request["headers"] )
      elsif request["method"] == "POST"
        # POSTの場合、bodyの指定があればそちらを優先する。
        param = request["body"] != nil ?  request["body"] : param
        response = http.post( request["path"], param, request["headers"] )
      end
    }
    return response
  end

private
  def map_to_param( map )
   return "" if map == nil
    str = ""
    map.each { | key, value |
      str << key << '=' << CGI.escape(value.to_s) << '&' if value != nil
    }
    return str
  end

  def list_to_param( list )
   return "" if list == nil
    str = ""
    list.each { | item |
      if item["key"] != nil && item["value"] != nil
        str << item["key"] << '=' << CGI.escape(item["value"].to_s) << '&'
      end
    }
    return str
  end

  # リクエストの設定をマージする。
  def merge (request, parent)
    return request if ( parent == nil )
    request.merge!(parent) { |key, self_val, other_val|
      if ( key == "headers" || key == "params" )
        merge(self_val != nil ? self_val : {}, other_val)
      else
        self_val != nil ? self_val : other_val
      end
    }
    return request
  end

end

HTTPアクセスユーティリティの接続設定ファイル

---
conf:
  #proxy_host: <プロキシホスト>
  #proxy_port: <プロキシポート>

default:
  host: api.share.adobe.com
  path: /webservices/api/v1/
  port: 443
  ssl: true
  ca_file: "./v.cer" # サーバーの公開鍵証明書
  headers:

requests:
  auth:
      path: /webservices/api/v1/auth/
      method: POST

  new_session:
      path: /webservices/api/v1/sessions/
      method: POST

  end_session:
      path: /webservices/api/v1/auth/sessions/
      method: POST
      
  get_node_info:
      path: /webservices/api/v1/dc/
      method: GET
      

修正(2007-10-09)

かとうさんからのご指摘を受けて、認証ヘッダ作成時のタイムスタンプ作成部分を修正。ミリ秒でなく秒になっていた・・。

# ○
req["method"] + " " + "https://" + req["host"] + req["path"] + " " + (Time.new.to_f * 1000).round.to_s
# ×
req["method"] + " " + "https://" + req["host"] + req["path"] + " " + Time.new.to_i.to_s