LoginSignup
11
13

More than 5 years have passed since last update.

IB API (2) 仕組み~呼び出しの共通化

Last updated at Posted at 2016-02-14

前回の記事の続きです。
今回は、IB APIの仕組みを見て、呼び出しの共通化を考えます。
なお、サンプルとして資産情報の取得を行います。

相変わらず長くなるので、オプション価格の取得やオーダーは次回以降に回したいと思います。

対象者

  • 株式/先物/オプションなどを自動売買したい
  • 売買アルゴリズムのカスタマイズを自由にしたい
  • Java/C++/C#など、使い慣れた言語で書きたい
  • IB証券(LLC)の口座を持っている

前回と同様です。

APIの仕組み

IB APIはほとんどのリクエストが非同期です。
つまり、こちらがリクエストを投げてもすぐに結果は返ってきません。
コールバックをうまく処理して、目的のデータを取得する必要があります。

サンプル

例えば、アカウントの資産情報を取得する流れを見てみます。
まず、IBクライアントに「資産情報ください」とリクエストを投げます。
マニュアル

val requestTags = List(
  "NetLiquidation",
  "InitMarginReq",
  "SMA"
)
socket.reqAccountSummary( client.getNextValidId(), "All", requestTags.mkString(",") )

getNextValidIdは、まだリクエストに使っていない整数値を返すメソッドです。お好きな方法で自作してください。
tagには欲しい情報を指定します。

このリクエストを実行すると、少し時間が経過した後、対応するメソッドが呼ばれます。
どれが呼び出されるか分からない場合は、マニュアルを読むか、実際に読んでみて手元で確認してみてください。

今回対応しているメソッドは
accountSummaryです。
これがtagの個数だけ呼びだされます。
また、全て列挙が終わったら、accountSummaryEndが呼び出されます。
なお、マニュアルに明示的に書かれていないので、実際に呼んでみて試すのが楽です。

コールバック関数は、前回同様以下のように実装しました。
data.accountInfoなるクラスにデータを詰め込んでいます。
設計に応じて置き換えて読んでください。

override def accountSummary(reqId: Int, account: String, tag: String, value: String, currency: String): Unit = {
  tag match {
    case "InitMarginReq" =>
      data.accountInfo.initMarginReq = Some(value.toDouble)
    case "NetLiquidation" =>
      data.accountInfo.netLiquidation = Some(value.toDouble)
    case "SMA" =>
      data.accountInfo.SMA = Some(value.toDouble)
  }
}
override def accountSummaryEnd(reqId: Int): Unit =
  data.accountInfo.complete = true

API呼び出しの共通化

上記で見てきたように、req~~メソッドと~~Endコールバックが開始と終了で、その間に実際にデータが送信されるコールバックがあるという仕組みになっていました(例外もありますが、おおよそこの形です)。

そのため、このようなインターフェースを用意して、リクエストごとにサブクラスを用意し実装すれば上手くいきます。

abstract class Request {
  protected def preRequest()
  protected def request()
  protected def postRequest()

  final def run() = {
    preRequest()
    request()
    // 待つ
    postRequest()
  }
}

もともと非同期なので、ScalaなどではFutureやActorなどで簡単に並列化ができます。

本記事ではわかりやすさのため、ブロッキングして結果が返ってくるまで待つパターンで実装してみます。
単純な自動売買であれば、のんびり同期通信をしても大抵問題ないかと思います。

とはいえ、オプション価格の取得はとりわけ遅いので、複数銘柄を扱う際は並列化した方が良いです。

実装サンプル

以下は実装のサンプルです。
お使いの言語や設計に合わせて変更してください。

まずは、所定のメソッドがtrueを返すまで待つメソッドを適用に用意します。
数秒に一度読込中メッセージを流したかったため、以下のような形式んしています。

object Util {
  val GIVE_UP_TIME = 20 * 1000 // millisecond
  val INTERVAL_TIME = 100 // millisecond

  def wait(condition: () => Boolean)() = {
    (1 to GIVE_UP_TIME / INTERVAL_TIME).toStream
      .takeWhile(_ => !condition()).foreach(i => {
      if (i % 30 == 0)
        println("Waiting...")

      Thread.sleep(INTERVAL_TIME)
      if (i * INTERVAL_TIME >= GIVE_UP_TIME)
        throw new TimeoutException("give up...")
    })
  }
}

次に、リクエスト全般のインターフェースを用意します。
簡単に、complete変数がtrueになったら待機を終了、という挙動をデフォルトにしておきます。
varなのはご容赦ください……。

abstract class Request {
  var complete = false
  protected def isComplete = complete

  protected def preRequest()
  protected def request()
  protected def postRequest()

  final def run() = {
    preRequest()
    request()
    Util.wait(isComplete _)()
    postRequest()
  }
}

次に、リクエストごとの実装を行います。
今回はサンプルでも扱ったAccount情報を取得します。
printlnに放り込んだ時にデータを出力して欲しいので、toStringもオーバーライドしておきます。
varだらけなのは(ry

class Account(client: IBClient) extends Request {
  val requestTags = List(
    "NetLiquidation",
    "InitMarginReq",
    "SMA"
  )
  var netLiquidation: Option[Double] = None
  var initMarginReq: Option[Double] = None
  var SMA: Option[Double] = None

  override protected def preRequest(): Unit = {}
  override protected def postRequest(): Unit = {}
  override protected def request(): Unit =
    client.socket.reqAccountSummary(client.getNextValidId(), "All", requestTags.mkString(","))
  override def toString =
    s"netLiquidationValue: ${netLiquidation}, initMarginReq: ${initMarginReq}, SMA: ${SMA}"
}

さて、あとはコールバックが実装されていれば、以下のように呼び出せば情報が取得できるまで待機し結果を出力してくれます。

val accountInfo = new Account(client)
accountInfo.run()
println(accountInfo)

実行結果は、以下のように出力されます。
ペーパートレーディング口座なので、仮想通貨がたくさん入ってますね。

netLiquidationValue: Some(1.1247504391E8), initMarginReq: Some(1575432.69), SMA: Some(2.423691458E8)

まとめ

APIの仕組み、リクエストの共通化ができました。
様々な情報を取得できるようになったので、ポートフォリオ情報などを取得してみると良いかと思います。
次回はオプションの価格情報を取得したいと思います。

続き IB API (3) オプション情報の取得

11
13
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
11
13