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

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

ブロック式ログインを非ブロック式ログインに変換する

↓みたいな、ブロックで処理を受け取って、ログインして実行するAPIがあって、

# ログインして何かする
def do_as( user ) 
  begin
    # ログインして..
    puts "login as #{user}" 
    # ブロックの処理を実行 
    # 引数としてセッションIDを渡す
    yield "sid-#{user}"
  ensure
    # ログアウト
    puts "logout"
  end
end

これは、

do_as( "unageanu" ) {|sid|
  puts "run:#{sid}"
}

こんな感じで呼び出せて、まぁ、特に問題なく使えるわけだけど、諸般の事情によりこれを

session = nil
begin
  session = new Session
  session.login( "unageanu" ) # ログイン
  session.request {|sid|
    # 処理
    puts "run:#{sid}"
  }
ensure
  session.logout if session # ログアウト
end

みたいな感じで呼び出せるようにしたい。begin,ensureとか書くがいかにも面倒だけど、そこは大人の事情があるのです。

ということで

次のようなセッションクラスを書いてみた。戦略は以下

  • セッション作成時にリクエスト実行スレッドを開始し、ログインまで進めてwaitさせておく。
  • セッションにリクエストが送付されたら、キューを使ってスレッドに処理を送り、実行させる。
    • リクエスト送付側のスレッドは、リクエストが完了するまでwait
    • リクエストを実行するスレッドは、リクエスト実行完了後、結果を渡し送付側スレッドを起こす
  • セッションが不要になったら、closeで破棄する。(このとき、ログアウトが実行される。)
# セッション
class Session
  def initialize
    @alive = true
    @alive_mutex = Mutex.new
    @q = Queue.new
    @t = Thread.fork {
      yield proc {|*args|
        while( @alive_mutex.synchronize { @alive } )
          req = @q.pop
          req.call( *args ) if req
        end
      }
    }
  end
  # リクエストを送る
  def request( &block )
    return unless block_given?
    req = Request.new(block)
    @q.push req
    req.wait
  end
  # セッションを破棄する
  def close
    @alive_mutex.synchronize { 
      @alive = false
    }
    @q.push nil
    @t.join
  end
  
  # リクエスト
  class Request
    def initialize( block )
      @mutex = Mutex.new
      @cv = ConditionVariable.new
      @finished = false
      @value = nil
      @proc = proc {|*args|
        begin
         @value = block.call(*args)
        ensure
         @mutex.synchronize{
           @finished = true
           @cv.signal
          }
        end
      }
    end
    # リクエストの完了を待ち、結果を返す。
    def wait
      @mutex.synchronize{
        @cv.wait( @mutex ) until @finished
      }
      @value
    end
    # リクエストを実行する。
    def call(*args)
      @proc.call(*args)
    end
  end
end

こんな感じで使います。

# セッションを開始
session = nil 
begin 
  session = Session.new {|wait|
    # ログインを実行して
    do_as( "unageanu" ) { |sid|
      wait.call( sid ) # ここで待つ。
    }
  }
  
  # リクエストを発行。
  # リクエスト(=ブロックで指定した処理)が完了するまで、スレッドは待ち状態になり、
  # ブロックの実行結果が戻り値として返される。
  puts session.request {|sid|
    "aaa:#{sid}"
  }
  puts session.request {|sid|
    "bbb:#{sid}"
  }
  puts session.request {|sid|
    "ccc:#{sid}"
  }
ensure
  # いらなくなったら破棄すること。
  session.close if session
end

実行結果です。

login as unageanu
aaa:sid-unageanu
bbb:sid-unageanu
ccc:sid-unageanu
logout

openみたいに、API側で両方の使い方に対応してくれていればいいんだけどね。