Login

The smallest program that opens a session: connect, read the order id, disconnect.

Two flavours are shown. Paper for everyday testing, live for read-only checks against the real account.

What this shows

  • Reading credentials from environment variables.
  • EClient.connect(username=, password=, host=, paper=).
  • Starting EClient.run on a daemon thread.
  • Reading the order id off the next_valid_id callback.

What comes back

connect() blocks until the session is up, and fires three callbacks on your wrapper before it returns, on the calling thread: connect_ack, managed_accounts, then next_valid_id.

next_valid_id is fired last on purpose. The venue names what the account has already used just after the connection is made, and an id announced before that lands is one a fill spent long ago. A program that trusted it would have its first order refused as a duplicate.

So when connect() returns, the session is established and the id has already been delivered. The wait() in the example is belt and braces, not a gate.

The daemon run() thread is what carries every callback after connect: quotes, order status, bars, errors. Start it, or nothing further arrives.

Limits

A live login may push a second factor. Approve it on your authenticator; connect() blocks until the gate clears, so give it a longer wall clock than a paper login needs.

The live example is read-only: it logs in, prints, and disconnects. Send orders from the paper account.

Paper

Run it

IB_USERNAME=... IB_PASSWORD=... python examples/hello_login.py

Source

"""Hello-world recipe: connect, wait for next_valid_id, disconnect.

Usage:
    IB_USERNAME=... IB_PASSWORD=... python examples/hello_login.py
"""

import os
import threading

from ib_dx import EClient, EWrapper


class LoginWrapper(EWrapper):
    def __init__(self):
        self.ready = threading.Event()
        self.order_id = None

    def next_valid_id(self, order_id):
        self.order_id = order_id
        self.ready.set()


w = LoginWrapper()
c = EClient(w)
c.connect(
    username=os.environ["IB_USERNAME"],
    password=os.environ["IB_PASSWORD"],
    paper=True,
)
threading.Thread(target=c.run, daemon=True).start()

if not w.ready.wait(timeout=15):
    raise RuntimeError("did not receive next_valid_id")

print(f"logged in. next_valid_id = {w.order_id}")

c.disconnect()

Live

Run it

IB_LIVE_USERNAME=... IB_LIVE_PASSWORD=... python examples/hello_login_live.py

Source

"""Live-account login recipe: connect with paper=False, wait for next_valid_id,
disconnect. Read-only — no orders, no market data.

When the live login triggers a second-factor push, approve it on your mobile
authenticator. The connect call blocks until the gate clears.

Usage:
    IB_LIVE_USERNAME=... IB_LIVE_PASSWORD=... python examples/hello_login_live.py
"""

import os
import threading

from ib_dx import EClient, EWrapper


class LoginWrapper(EWrapper):
    def __init__(self):
        self.ready = threading.Event()
        self.order_id = None

    def next_valid_id(self, order_id):
        self.order_id = order_id
        self.ready.set()


w = LoginWrapper()
c = EClient(w)
c.connect(
    username=os.environ["IB_LIVE_USERNAME"],
    password=os.environ["IB_LIVE_PASSWORD"],
    host=os.environ.get("IB_HOST", ""),
    paper=False,
)
threading.Thread(target=c.run, daemon=True).start()

if not w.ready.wait(timeout=60):
    raise RuntimeError("did not receive next_valid_id")

print(f"logged in LIVE. next_valid_id = {w.order_id}")

c.disconnect()