さて、遂に発注まで来ました。多くのサイトの紹介では「成行、ないし指値オーダーを発注」までで終わっており、その後のオーダー管理や約定後の建玉管理はどうすんねん、という話だったので、今回はそこまで踏み込みました。
これまた注意事項ですが、筆者が使っているのはIB証券公式Python APIではなく、有志作製のIBPyです。地味にこっちの方がネットの情報も多いような…?IBPyの紹介等は過去記事をどうぞ。
【2018/12/20追記】
オーダーの修正、キャンセル方法を追加
事前に確認しておくこと
今まではマーケットデータの取得だったので、本番環境でやろうがテスト環境(ペーパートレーディング口座)でやろうが大した差は無かったのですが、今回は発注関連なので環境の取り違えをすると即爆死です。ですので開発段階では事前に万全の備えを行いましょう。
本番環境側はAPIを「読み込みのみ」に限定する
TWSを起動し、「グローバルコンフィグ」から「API」のページに移り、下の赤い四角で囲った「API(読み込みのみ)」にチェックを入れます。

日本語がいまいち分かり難いですが、これでAPIによる「書き込み」が出来なくなるので、発注が不可能になります。最終的に実弾を撃ち始める時はここのチェックを外します。
テスト環境側はAPIの書き込みを許可する上、ソケットポートの番号を変える
逆にテスト環境側では上記のチェックを外して発注を可能にします。

更に万全を期すために、「ソケットポート」の番号を本番環境と別の数字にしておきます(多分最初からなってますが)。これにより、コード内に入れたポート番号と一致しないことで本番環境で動かなくなります。
また、地味な話ですが「プリセット」からワンショットの発注金額上限を上げておかないと、債券先物のように時価総額だけやたら大きい商品を扱おうとすると、いちいち発注上限に引っかかって注文が失効してしまいます。手動の時は簡素なアラートメッセージが出るだけなので、上限の変更をしなくてもスキャルでなければ大して問題無いですが、APIの場合は調整しておきましょう。
指値、成行の発注
概要
オーダーに必要な情報をまとめて送り付けるだけなので、大して複雑ではないですが、APIで発注する場合、こちら側でオーダーIDを指定してあげる必要があります。このID、重複すると発注できない(厳密にはオーダー変更扱いになる)上に、TWS上で表示されない厄介な仕様(TWS上では手動発注とAPI発注で同一規格の注文IDが付与され、それだけが表示される。どこかにあるのかな…?)になっており、自作システム側でもきちんと管理しましょう。
オーダーIDの管理がカオス過ぎて一旦初期化したい、という場合は上図にあった「API注文のシーケンスIDをリセットする」をクリックすれば初期化出来るようです。その時点でオーダーが残っていた場合に、そのオーダーがどうなるのかは分かりません(おい)
また、IB証券には大量の注文種類が存在しますが今回は指値と成行のみとします。他は気が向いたら…
次のオーダーIDの確認方法
いきなり発注から逸れますが、普通に考えるとオーダーIDは連番が良い訳です。で、連番だとすると「今ここまで使ったから、次はこれが使えるよ」というのは知りたい情報。実はAPI側でIB証券のサーバに接続したタイミングで勝手に親切にその情報を送り付けてきているので、まずはそれを表示する方法から。
下は何でもかんでもログを取得するハンドラーです。最終的に取得する側のコードで取捨選択出来ますが、手を加えればエラーだけを拾えます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
from ib.opt import Connection, message from ib.ext.Contract import Contract from ib.ext.Order import Order import time def reply_handler(msg): print("Server Response: %s, %s" % (msg.typeName, msg)) if __name__ == "__main__": # 接続情報 conn = Connection.create(port = 7497, clientId = 999) # 接続 conn.connect() # 全データハンドラー呼び出し conn.registerAll(reply_handler) # 待機 time.sleep(15) # 切断 conn.disconnect() |
で、これだけで動かして15秒程待機します。本来1秒程度でOKですが、待機しないで即切断すると何も出てこないケースがあるので、多少待ちます。

そうすると上記のように色々吐いてくる訳ですが、赤い四角で囲った箇所で次の有効なオーダーIDを教えてくれています。ちなみにこのAPI、要求していなくても接続時に発注中の指値オーダーの情報まで送り付けてきます(説明の順番上省いていますが、この上に実は指値の情報が入ってきていました)。また、早速エラーを大量に吐いているようで、実はどれも正常稼働の通知です。
因みにエラーを狙って取得したい場合は以下のように。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
from ib.opt import Connection, message from ib.ext.Contract import Contract from ib.ext.Order import Order import time def error_handler(msg): print("Server Error: %s" % msg) if __name__ == "__main__": # 接続情報 conn = Connection.create(port = 7497, clientId = 999) # 接続 conn.connect() # エラーハンドラー呼び出し conn.register(error_handler, "Error") # 待機 time.sleep(15) # 切断 conn.disconnect() |
発注作業
今回の例ではE-mini S&P500先物を2,685で1枚買う、という指値注文を出すものにしました。銘柄情報の定義方法はヒストリカルデータ取得編をご参照下さい。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 |
from ib.opt import Connection, message from ib.ext.Contract import Contract from ib.ext.Order import Order # 銘柄情報の定期 def make_contract(symbol, sec_type, exch, curr): Contract.m_localSymbol = symbol Contract.m_secType = sec_type Contract.m_exchange = exch Contract.m_currency = curr return Contract # 全データハンドラー def reply_handler(msg): print("Server Response: %s, %s" % (msg.typeName, msg)) # オーダー作成 def make_order(action, quantity, price = None): order = Order() order.m_action = action order.m_totalQuantity = quantity # オーダー水準が指定されていれば指値 if price is not None: order.m_orderType = "LMT" order.m_lmtPrice = price # 指定されていなければ成行 else: order.m_orderType = "MKT" return order if __name__ == "__main__": # オーダーIDの設定 order_id = 626 conn = Connection.create(port = 7497, clientId = 999) conn.connect() conn.registerAll(reply_handler) # 銘柄情報を定義 cont = make_contract("ESZ8", "FUT", "GLOBEX", "USD") # 注文情報の定義 offer = make_order("BUY", 1, 2685) # 発注 conn.placeOrder(order_id, cont, offer) # 切断 conn.disconnect() |
先に注文情報を「make_order」という関数で定義してあげます。ここでは指値か成行かは「指値水準があるか否か」というところで判断していますが、きちんと「指値か成行か」というフラグを渡して分岐させた方が安定性が高い気がします。そこはお好みで。
最終的な発注作業は「placeOrder」で行いますが、この時オーダーIDが必要になります。
マニアックな注意事項
このブログの読者の方のうち何%に関係があるか分かりませんが、個人的に詰まったのは米債先物の指値注文。
元々、「120’065」というような複雑怪奇な価格の表示方法になっているので、結局どんな値を指値水準として渡せば良いのか格闘した結果、「デシマル表示にしてからテキスト形式で渡す」とOKのようです。知るかいな。
1 2 |
fut_price = 120 + 6.5 / 32 offer = make_order("BUY", 1, str(fut_price)) |
上記のように「120’065」を120 + 6.5 / 32でデシマル表示に変換した後、テキストとして渡します。
オーダーの変更
発注時に指定したオーダーIDを再度指定して再度オーダーを出せば、それで変更扱いになります。ただ、これだと銘柄から数量から何でもかんでも変更出来ますが、公式マニュアル的には数量、価格、キャンセル条件以外の大幅な変更は推奨していないようです。
オーダーのキャンセル
オーダーIDを指定して、特定のオーダーのみキャンセルする場合です。
1 2 3 4 |
conn = Connection.create(port = 7497, clientId = 999) conn.connect() conn.cancelOrder(order_id) |
全オーダーのキャンセル
一括で全オーダーをキャンセル出来ます。
1 2 3 4 |
conn = Connection.create(port = 7497, clientId = 999) conn.connect() conn.reqGlobalCancel() |
オーダー管理
さて、次に指値オーダーがまだ生きているのか、約定したのか判別するためにオーダー管理として「オープンオーダーを全て取得する」という処理を行っていきます。
上述のように、サーバ接続時に勝手に流れて来る訳ですが、勿論その後こちらから要求することも可能です。
APIではサーバ側から色々とデータが送られてくるので、欲しい情報は自分で絞り込むなりする必要があります。手法は簡単に二つあり、
- 「register」側でハンドラーに渡すデータを絞る
- ハンドラー側でif文等でデータを絞る
筆者はエンジニアではないので、どちらがコーディング的に、ないしシステム的に良いのか分かりませんが、とりあえず二つ共紹介していきます。因みにこれはオーダー管理以外に、この先で建玉の管理をするのにも使えます。
register側制御
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
def reply_handler(msg): print("Server Response: %s, %s" % (msg.typeName, msg)) if __name__ == "__main__": conn = Connection.create(port = 7497, clientId = 999) conn.connect() # registerで引数に「orderStatus」を指定 conn.register(reply_handler, message.orderStatus) time.sleep(15) conn.disconnect() |
まあコード内にコメントしてある通りなんですが…今までは「registerAll」だったのが地味に「register」に変わっています。こうすると絞れる模様?
ハンドラー側if文制御
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
def reply_handler(msg): # if文でorderStatus関連のデータに絞る if msg.typeName == "orderStatus": print("Server Response: %s, %s" % (msg.typeName, msg)) if __name__ == "__main__": conn = Connection.create(port = 7497, clientId = 999) conn.connect() conn.registerAll(reply_handler) time.sleep(15) conn.disconnect() |
そのまんまですが、「msg」に要素が色々とあり、その中でも「msg.typeName」でデータ種類を判別できます。これを使って、orderStatus関連のデータに絞る方法です。この場合だと「registerAll」を採用する辺りは変更がありません。
取得したデータの使い方

上記のように「msg」全体を表示すると、上図のような結果になります(例では指値4本を置いておきました)。
ここから要素を絞るのは簡単で、「msg.orderId」と限定すればオーダーIDのみ取得出来ます。これと手元のオーダーIDを突合すればOK。他の主要な要素は以下。
- filled:約定済み枚数
- remaining:未約定枚数
- permId:TWS上に表示される手動発注オーダーと混在する場合の注文ID
こちらからオープンオーダーの情報を要求する場合
接続直後以外に要求するのは簡単。
1 2 3 4 5 6 7 8 9 10 11 12 |
if __name__ == "__main__": conn = Connection.create(port = 7497, clientId = 999) conn.connect() conn.registerAll(reply_handler) time.sleep(15) # オープンオーダー一覧要求 conn.reqAllOpenOrders() time.sleep(15) conn.disconnect() |
ポジション管理
今度は約定した建玉の一覧を取得していきます。
1 |
conn.reqPositions() |
要求自体は極めて簡単ですが、返ってくるデータの処理にひと手間が必要。

とりあえずmsgを全て表示させると上のように。ポジションの数量、サイド、平均持ち値は分かりますが、銘柄情報(contract)が謎の32進数?になっています。
1 2 3 4 |
def reply_handler(msg): if msg.typeName == "position": Contact = msg.contract print(Contact.m_symbol + " / " + str(msg.pos)) |
一旦「msg.contract」で銘柄だけ抜き取った後、「m_symbol」でシンボルに変換します。

銘柄名称とポジション量に変換されました。細かい話ですが、ポジション量は数字形式ですが、「8604」のような証券コードはテキスト形式扱いらしく、そのままprintで表示できるようです。何で8604ロングしてるのかって?テスト環境だからです(即座に宙を舞う四季報)。
口座情報取得
最後に口座情報の取得方法も記載しておきます。
1 |
conn.reqAccountSummary(999, "All", "$LEDGER:JPY") |
構造はシンプルですが、左から順に引数を説明していきます。
- データ要求ID:指定が必要だが、重複しても特に問題無く普通に動いている?謎の数値。オーダーIDとは別に要求したデータと受信したデータの紐づけに内部的に使っているのかも?
- 口座グループ:これまたよく分からないんですが、アカウントに対して複数の口座を持っている場合等で仕訳に使う…ような印象。普通は1アカウントに1口座なので、「All」にしておけばOK。
- 通貨特定用:「$LEDGER」と入れるだけで口座の基準通貨ベースのデータだけに限定される。で、「$LEDGER:JPY」のように通貨を指定すれば、その通貨だけのデータに限定される。まあこの場合、日本人が使う口座なので、「:JPY」を付けても付けなくても結果は同じ。ただ、海外先物の決済損益や、FXのポジションは外貨ベースの現金で計上されるので、通貨を限定する意味はあまりないかも。
このsummaryですが、結構な量のデータが送られてきます。現金残高(TotalCashBalance)以外にも、評価損益、実現損益(おそらく当日中?)、円転レートや、オプション、債券(多分現物)、ワラントの現在価値等、自分で使いたいデータに絞らないと安定のカオスです。
1 2 3 |
def reply_handler(msg): if msg.typeName == "accountSummary" and msg.tag == "TotalCashBalance": print(msg.value) |
ハンドラーの方で「accountSummary」でかつ、その中でも「tag」が「TotalCashBalance」になるものに絞り、最終的にそのデータの中から「Value」を指定すると、円の残高が表示されます。
おわりに
長くなりましたが、次回はオーダーのキャンセルや変更のところをやりたいなと思います。というか今回建玉の取得やらずにそっちを先にやれば良かった…