対象者
- 株式/先物/オプションなどを自動売買したい
- 売買アルゴリズムのカスタマイズを自由にしたい
- Java/C++/C#など、使い慣れた言語で書きたい
- IB証券(LLC)の口座を持っている
Forexの自動売買であれば、Meta Trader(MT)という非常に優れたソフトがあり、MQL言語さえ書ければ比較的簡単に実装できます。
しかし、株式はMTのように統一されたクライアントはありません。
特に日本の証券会社では絶望的です。
というわけで、本記事ではInteractive Brokers証券のAPIを使って価格データを取るまでの手順を記載します。
長くなるので、それ以降は別記事としたいと思います。
API利用のための準備
ライブラリのインストール
IB証券の紹介をするのが目的では無いので詳細は省きますが、現時点で世界一のネット証券会社(個人の感想)です。
IB証券は世界中の投資商品を取り扱っており、株からオプション、FXまでを格安手数料で扱うことができます。
全商品をAPIで共通して扱えるようになっており、比較的簡単に株式やオプションの自動売買プログラムが書けるようになっています。
APIを叩くためのライブラリは、以下のページの「GET API SOFTWARE」から入手できます。
IB API
OSにあわせたインストーラーが落とせます。
なお、APIのソースはgithubにて管理されており、プルリクエストを投げられるようです。
担当者にメールを送らないと閲覧権限が貰えないため、確認はできていませんが。
よく見ると、API Softwareをダウンロードするページのアドレスがgithub.io……。
ますます何の会社か分からなくなってきましたが、さすが世界一(個人の感想)なだけあります。
インストールできたら、インストール先フォルダにライブラリのソースがあるので、それをクラスパスに通しておいてください。
言語選択
APIのページを見ると分かりますが、Java / C# / C++ / Excelなど、様々な言語をサポートしているようです。
Javaが最初に列挙されており、恐らくライブラリも充実しているだろうと想定されます。
というわけで、以下はScalaを使った例を記載します。
ペーパートレーディングアカウントの作成
当然ですが、いきなり本番アカウントで動かすとバグが有った場合に即破産なので、まずテスト用アカウントを作る必要があります。
IBのWebページからログインし、「口座管理」->「設定」->「ペーパートレーディング」から申請します。
なお、ペーパートレーディングアカウントは有効化済みのアカウントからでないと作成できません。
1営業日くらいするとIBの人が処理をしてくれて、作成完了します。
必要に応じて、設定画面で「マーケットデータをペーパートレーディングアカウントで利用する」にチェックを入れましょう。
これにチェックを入れると、本番アカウントと同時に取得はできなくなりますが、購入したマーケットデータが使えます。
TWSの設定
APIを利用するには、TWSを経由する方法と、IB Gatewayを経由する方法があります。
TWSを使うと、ポジションの状況等がわかり、デバッグしやすいです。
IB Gatewayは、軽量ですが情報は得られません。
そのため、開発中はTWSを使います。
お手持ちのTWSを起動し、先ほど作ったアカウントでログインします。
なお、APIを使うためには、いくつか設定が必要です。
「Global Configs」->「API」->「Settings」から、「Enable ActiveX and Socket Client」にチェックを入れ、ポート番号も確認します。
オーダーを出す場合は、「Read-only API」のチェックを外しておきましょう。
APIの利用
ようやくAPIを叩く準備が出来ました。
APIの概要やチュートリアルは以下のページに記載されています。
APIマニュアル TOP
……が、C++/C#にはチュートリアルがあるのに、Javaはサンプルプログラムの起動方法が書いてあるだけ!
そのサンプルも、サンプルにしては何でも出来過ぎて分かりにくい。
というわけで、C#版のチュートリアルを参考にして以下記事は記載しています。
クライアントへの接続
クライアントへの接続は、EWrapperなるインターフェースを継承したクラスを作り、それを使ってEClientSocketのインスタンスを作り接続します。
class IBClient extends EWrapper {
val socket = new EClientSocket(this)
}
val LAST_CLIENT_ID = 0
val HOST = "localhost"
val PORT = 7497
val client = new IBClient
client.socket.eConnect(HOST, PORT, LAST_CLIENT_ID)
とまぁこれだけなはずなのですが、EWrapperには大量のabstractなメソッドがあるので、それを実装しないといけません。
とはいえ、必要なメソッドは一部なので、以下のように最低限のメソッドを実装し、必要に応じて増やすと良いかと思います。
override def tickPrice(tickerId: Int, field: Int, price: Double, canAutoExecute: Int): Unit =
println(s"tickerId:${tickerId}, field:${field}, price:${price}, canAutoExecute:${canAutoExecute}")
override def managedAccounts(accountsList: String): Unit =
println(s"Account List: ${accountsList}")
override def tickSize(tickerId: Int, field: Int, size: Int): Unit =
println(s"tickerId:${tickerId}, field:${field}, size:${size}")
override def currentTime(time: Long): Unit =
println(s"Current Time: ${time}")
override def tickGeneric(tickerId: Int, tickType: Int, value: Double): Unit =
println(s"tickerId:${tickerId}, tickType:${tickType}, value:${value}")
override def nextValidId(orderId: Int): Unit =
println(s"Next Valid Id: ${orderId}")
override def error(id: Int, errorCode: Int, errorMsg: String): Unit =
println(s"id:${id}, errorCode:${errorCode}, errorMsg:${errorMsg}")
上記を実装し、プログラムを走らせると以下の出力が得られます。
Server Version:76
TWS Time at connection:20151213 15:00:59 JST
Account List: XXXXXXX
Next Valid Id: 1
id:-1, errorCode:2104, errorMsg:Market data farm connection is OK:hfarm
id:-1, errorCode:2104, errorMsg:Market data farm connection is OK:cashfarm
id:-1, errorCode:2104, errorMsg:Market data farm connection is OK:usfarm
id:-1, errorCode:2106, errorMsg:HMDS data farm connection is OK:ilhmds
id:-1, errorCode:2106, errorMsg:HMDS data farm connection is OK:hthmds
id:-1, errorCode:2106, errorMsg:HMDS data farm connection is OK:fundfarm
id:-1, errorCode:2106, errorMsg:HMDS data farm connection is OK:ethmds
id:-1, errorCode:2106, errorMsg:HMDS data farm connection is OK:ushmds
errorのような出力が出ていますが、API Message Codesを見ると、正しく接続されたというエラーのようです。
E_SUCCESSってやつですか。
とりあえずスルーです。
各種リクエストの仕組み
市場データの取得、注文の発注などは、EClientSocketクラスを経由してクライアントに送信します。
クライアントが処理をし、メッセージを返すと、EWrapperクラスの対応するメソッドが呼び出されます。
そのため、対応するメソッドを実装するだけで問題ありません。
市場データのリクエスト
Contractクラスに必要な情報を与え、リクエスト関数に渡せば良いです。
株式等はマーケットデータが必要なため、まずはForexデータで試すと良いでしょう。
以下の例では、USD/JPYのデータをリクエストしています。
// create Contract to fetch data
val contract = new Contract()
contract.m_symbol = "USD"
contract.m_secType = "CASH"
contract.m_currency = "JPY"
contract.m_exchange = "IDEALPRO"
// request
client.socket.reqMktData(1, contract, "", false, null)
リクエストが正常に行われると、画面に以下のようなメッセージが出力されます。
tickerId:1, field:1, price:-1.0, canAutoExecute:1
tickerId:1, field:0, size:0
tickerId:1, field:2, price:-1.0, canAutoExecute:1
tickerId:1, field:3, size:0
tickerId:1, field:9, price:120.86, canAutoExecute:0
field:9が現在値を表しているようです。
詳細はマニュアルを確認してください。
Scalaでのサンプル
市場データのリクエストまでを1ファイルにまとめて以下に記載します。
package esplo
import com.ib.client._
class IBClient extends EWrapper {
val socket = new EClientSocket(this)
override def tickPrice(tickerId: Int, field: Int, price: Double, canAutoExecute: Int): Unit =
println(s"tickerId:${tickerId}, field:${field}, price:${price}, canAutoExecute:${canAutoExecute}")
override def managedAccounts(accountsList: String): Unit =
println(s"Account List: ${accountsList}")
override def tickSize(tickerId: Int, field: Int, size: Int): Unit =
println(s"tickerId:${tickerId}, field:${field}, size:${size}")
override def currentTime(time: Long): Unit =
println(s"Current Time: ${time}")
override def tickGeneric(tickerId: Int, tickType: Int, value: Double): Unit =
println(s"tickerId:${tickerId}, tickType:${tickType}, value:${value}")
override def nextValidId(orderId: Int): Unit =
println(s"Next Valid Id: ${orderId}")
override def error(id: Int, errorCode: Int, errorMsg: String): Unit =
println(s"id:${id}, errorCode:${errorCode}, errorMsg:${errorMsg}")
override def bondContractDetails(reqId: Int, contractDetails: ContractDetails): Unit = ???
override def displayGroupList(reqId: Int, groups: String): Unit = ???
override def receiveFA(faDataType: Int, xml: String): Unit = ???
override def tickSnapshotEnd(reqId: Int): Unit = ???
override def marketDataType(reqId: Int, marketDataType: Int): Unit = ???
override def updateMktDepthL2(tickerId: Int, position: Int, marketMaker: String, operation: Int, side: Int, price: Double, size: Int): Unit = ???
override def position(account: String, contract: Contract, pos: Int, avgCost: Double): Unit = ???
override def verifyCompleted(isSuccessful: Boolean, errorText: String): Unit = ???
override def scannerDataEnd(reqId: Int): Unit = ???
override def deltaNeutralValidation(reqId: Int, underComp: UnderComp): Unit = ???
override def execDetailsEnd(reqId: Int): Unit = ???
override def orderStatus(orderId: Int, status: String, filled: Int, remaining: Int, avgFillPrice: Double, permId: Int, parentId: Int, lastFillPrice: Double, clientId: Int, whyHeld: String): Unit = ???
override def tickString(tickerId: Int, tickType: Int, value: String): Unit = ???
override def accountSummaryEnd(reqId: Int): Unit = ???
override def tickOptionComputation(tickerId: Int, field: Int, impliedVol: Double, delta: Double, optPrice: Double, pvDividend: Double, gamma: Double, vega: Double, theta: Double, undPrice: Double): Unit = ???
override def updateNewsBulletin(msgId: Int, msgType: Int, message: String, origExchange: String): Unit = ???
override def openOrder(orderId: Int, contract: Contract, order: Order, orderState: OrderState): Unit = ???
override def contractDetailsEnd(reqId: Int): Unit = ???
override def contractDetails(reqId: Int, contractDetails: ContractDetails): Unit = ???
override def scannerParameters(xml: String): Unit = ???
override def updateMktDepth(tickerId: Int, position: Int, operation: Int, side: Int, price: Double, size: Int): Unit = ???
override def updateAccountTime(timeStamp: String): Unit = ???
override def realtimeBar(reqId: Int, time: Long, open: Double, high: Double, low: Double, close: Double, volume: Long, wap: Double, count: Int): Unit = ???
override def updatePortfolio(contract: Contract, position: Int, marketPrice: Double, marketValue: Double, averageCost: Double, unrealizedPNL: Double, realizedPNL: Double, accountName: String): Unit = ???
override def updateAccountValue(key: String, value: String, currency: String, accountName: String): Unit = ???
override def positionEnd(): Unit = ???
override def tickEFP(tickerId: Int, tickType: Int, basisPoints: Double, formattedBasisPoints: String, impliedFuture: Double, holdDays: Int, futureExpiry: String, dividendImpact: Double, dividendsToExpiry: Double): Unit = ???
override def historicalData(reqId: Int, date: String, open: Double, high: Double, low: Double, close: Double, volume: Int, count: Int, WAP: Double, hasGaps: Boolean): Unit = ???
override def verifyMessageAPI(apiData: String): Unit = ???
override def openOrderEnd(): Unit = ???
override def fundamentalData(reqId: Int, data: String): Unit = ???
override def execDetails(reqId: Int, contract: Contract, execution: Execution): Unit = ???
override def accountDownloadEnd(accountName: String): Unit = ???
override def accountSummary(reqId: Int, account: String, tag: String, value: String, currency: String): Unit = ???
override def commissionReport(commissionReport: CommissionReport): Unit = ???
override def scannerData(reqId: Int, rank: Int, contractDetails: ContractDetails, distance: String, benchmark: String, projection: String, legsStr: String): Unit = ???
override def displayGroupUpdated(reqId: Int, contractInfo: String): Unit = ???
override def error(e: Exception): Unit = ???
override def error(str: String): Unit = ???
override def connectionClosed(): Unit = ???
}
object Main {
val LAST_CLIENT_ID = 0
val HOST = "localhost"
val PORT = 7497
def main(args: Array[String]): Unit = {
val client = new IBClient
client.socket.eConnect(HOST, PORT, LAST_CLIENT_ID)
// create Contract to fetch data
val contract = new Contract()
contract.m_symbol = "USD"
contract.m_secType = "CASH"
contract.m_currency = "JPY"
contract.m_exchange = "IDEALPRO"
// request
client.socket.reqMktData(1, contract, "", false, null)
Thread.sleep(100)
}
}
まとめ
ひとまず市場データを取得することが出来るようになりました。
既に長いので、続きはまた別の記事として記載したいと思います。