Skip to content

Ibkr Client

voyz edited this page May 1, 2024 · 14 revisions

IbkrClient

The IbkrClient class provides an interface to the IBKR REST API.

It shares some basic configuration with the IbkrWsClient. See IBind Configuration - Construction Parameters section for more.

Basic usage of IbkrClient:

from ibind import IbkrClient

ibkr_client = IbkrClient()
ibkr_client.tickle()

The IbkrClient class is a concrete implementation of the abstract RestClient class.

The RestClient provides a boilerplate support for common REST methods: GET, POST and DELETE, and handles the request/response processing of all API methods declared in the IbkrClient.

Mapped endpoints

Almost all endpoints defined in the IBKR REST API are mapped to IbkrClient methods. Currently, the endpoint sections that are still NOT mapped are:

  • Alerts
  • FA Allocation Management
  • FYIs and Notifications

Note:

  • IBKR endpoints are not documented in this documentation. Please refer to the official IBKR REST API reference for full documentation.
  • Endpoints' API implementation is categorised into Python class mixins. See ibkr_client_mixins directory for implementation details.

REST Results

Almost all methods in the IbkrClient return an instance of Result object. It is a dataclass containing two fields:

  • data - the data returned from the operation
  • request - details of the request that resulted in this data, usually containing the URL and arguments used in the REST request to IBKR

This interface gives you additional information as to how IbkrClient communicated with the IBKR API. The request field facilitates debugging and ensuring the client library operates according to our expectations.

What this implies, is that to access the returned data of the REST API calls, you need to use the .data accessor:

authentication_data = IbkrClient.authentication_status().data

There are very few methods that do not return Result. In all such cases it is explicitly stated in the documentation.

Advanced API

Majority of the IBKR endpoints' mapping is implemented by performing a simple REST request. For example:

class SessionMixin():
    def authentication_status(self: 'IbkrClient') -> Result:
        return self.post('iserver/auth/status')

    ...

In several cases, IbkrClient implements additional logic to allow more sophisticated interaction with the IBKR endpoints.

The advanced API methods are:

security_stocks_by_symbol

IBKR provides access to multiple exchanges, markets and instruments for most symbols. As a result, when searching for a stock by symbol using the trsrv/stocks endpoint, the API will frequently return more than one contract.

{
    "AAPL": [
        {
            "assetClass": "STK",
            "chineseName": "苹果公司",
            "contracts": [
                {"conid": 265598, "exchange": "NASDAQ", "isUS": True},
                {"conid": 38708077, "exchange": "MEXI", "isUS": False},
                {"conid": 273982664, "exchange": "EBS", "isUS": False},
            ],
            "name": "APPLE INC",
        },
        {
            "assetClass": "STK",
            "chineseName": None,
            "contracts": [{"conid": 493546048, "exchange": "LSEETF", "isUS": False}],
            "name": "LS 1X AAPL",
        },
        {
            "assetClass": "STK",
            "chineseName": "苹果公司",
            "contracts": [{"conid": 532640894, "exchange": "AEQLIT", "isUS": False}],
            "name": "APPLE INC-CDR",
        },
    ]
}

To facilitate specifying which security we want to operate on, the security_stocks_by_symbol provides a filtering mechanism. To use it, each stock that you'd want to search for can be expressed as an instance of the StockQuery dataclass:

@dataclass
class StockQuery():
    symbol: str
    name_match: Optional[str] = field(default=None) # ie. filter for instrument name
    instrument_conditions: Optional[dict] = field(default=None) # ie. filters for instrument fields
    contract_conditions: Optional[dict] = field(default=None) # ie. filters for contract fields

When using the StockQuery, apart from specifying the symbol to search for, we can provide additional filters that will be used to narrow down our stock search query, eg.:

from ibind import IbkrClient, StockQuery

ibkr_client = IbkrClient()
queries = [StockQuery('AAPL', contract_conditions={'exchange': 'MEXI'})]
stocks = ibkr_client.security_stocks_by_symbol(queries, default_filtering=False)

This will call the trsrv/stocks endpoint and filter the result to contracts whose exchange is equal to MEXI:

{
    "AAPL": [
        {
            "assetClass": "STK",
            "chineseName": "苹果公司",
            "contracts": [{"conid": 38708077, "exchange": "MEXI", "isUS": False}],
            "name": "APPLE INC",
        }
    ]
}

Note:

  • A isUS=True contract condition is applied to all calls of this method by default. Disable by setting security_stocks_by_symbol(..., default_filtering=False).
  • You can call this method with str arguments instead of StockQuery. These will be interpreted as StockQuery arguments with no filtering.
  • You can call this method with one or many arguments, eg. security_stocks_by_symbol('AAPL') or security_stocks_by_symbol(['AAPL', 'GOOG'])
  • You can mix str and StockQuery arguments, eg.: security_stocks_by_symbol(['AAPL', StockQuery('GOOG')])
  • Same rules apply to all other methods using StockQuery parameters.

stock_conid_by_symbol

Most of the IBKR endpoints require us to specify securities' numerical contract IDs (usually shortened to conid) rather than symbols.

ibkr_client.contract_information_by_conid('AAPL') # INVALID
ibkr_client.contract_information_by_conid(265598) # VALID

stock_conid_by_symbol method facilitates acquiring conids for stock symbols using the same filtering functionality as the security_stocks_by_symbol.

  • Importantly, this method will raise a RuntimeError unless all of the filtered stocks return exactly one instrument and one contract.
  • As a result, exactly one conid can be acquired for each symbol, prompting the users to tweak the StockQuery arguments until this is true.
  • This ensures that there is no ambiguity when searching for conids.

Note:

  • This one instrument and one contract limitation is not present in the security_stocks_by_symbol method.

Eg.:

from ibind import IbkrClient, StockQuery

stock_queries = [
    StockQuery('AAPL', contract_conditions={'exchange': 'MEXI'}),
    'HUBS',
    StockQuery('GOOG', name_match='ALPHABET INC - CDR')
]
conids = ibkr_client.stock_conid_by_symbol(stock_queries, default_filtering=False).data
print(conids)

# outputs:
{'AAPL': 38708077, 'GOOG': 532638805, 'HUBS': 169544810}

marketdata_history_by_symbol

marketdata_history_by_symbol is a small wrapper around the simple marketdata_history_by_conid method.

It utilises the stock_conid_by_symbol to query stock conids automatically before calling the marketdata_history_by_conid.

marketdata_history_by_symbols

marketdata_history_by_symbols is an extended version of the marketdata_history_by_symbol method.

For each StockQuery provided, it queries the marketdata history for the specified symbols in parallel. The results are then cleaned up and unified. Due to this grouping and post-processing, this method returns data directly without the Result dataclass.

ibkr_client.marketdata_history_by_symbols('AAPL', period='1min', bar='1min', outside_rth=True)

# outputs:
{
    "AAPL": [
        {
            "open": 169.15,
            "high": 169.15,
            "low": 169.15,
            "close": 169.15,
            "volume": 0,
            "date": datetime.datetime(2024, 4, 25, 19, 56),
        },
    ]
}
ibkr_client.marketdata_history_by_symbols(['AAPL', 'MSFT'], period='1min', bar='1min', outside_rth=True)

# outputs:
{
    "AAPL": [
        {
            "open": 169.15,
            "high": 169.15,
            "low": 169.15,
            "close": 169.15,
            "volume": 0,
            "date": datetime.datetime(2024, 4, 25, 19, 56),
        },
    ],
    "MSFT": [
        {
            "open": 400.75,
            "high": 400.75,
            "low": 400.75,
            "close": 400.75,
            "volume": 0,
            "date": datetime.datetime(2024, 4, 25, 19, 56),
        },
    ]
}

Both of these requests took approximately the same amount of time to execute, due to parallel execution.

This method imposes a limit of 5 parallel requests per second due to IBKR rate limiting.

marketdata_unsubscribe

The /iserver/marketdata/unsubscribe endpoint allows for only one conid to be unsubscribed from at a time.

marketdata_unsubscribe method utilises parallel execution to unsubscribe from multiple marketdata history conids at the same time.

place_order

From placing order docs:

In some cases the response to an order submission request might not deliver an acknowledgment. Instead, it might contain an "order reply message" - essentially a notice — which must be confirmed via a second request before our order ticket can go to work.

When placing an order using place_order method, you are to provide a dictionary of question-answer pairs. The method will then perform a back-and-forth message exchange with IBKR servers to reply to the questions and complete the order submission process automatically.

from ibind import IbkrClient, QuestionType

answers = {
    QuestionType.PRICE_PERCENTAGE_CONSTRAINT: True,
    QuestionType.ORDER_VALUE_LIMIT: True,
    "Unforeseen new question": True,
}

ibkr_client = IbkrClient()
ibkr_client.place_order(..., answers, ...)

Observe that:

  • Questions-answer pairs are expected to be made of QuestionType enum and a boolean.
  • You can provide a str instead of QuestionType as key in order to answer questions that have not been previously observed.
  • You may chose to use str in every case, although QuestionType enum is preferred.

If a question is found without an answer or the answer to the question is negative, an exception is raised.


Additionally, make_order_request utility function is provided to facilitate creating the order_request dictionary. It ensures all necessary fields are specified when placing an order using place_order method.

import datetime
from ibind import IbkrClient, make_order_request

conid = '265598' # AAPL
side = 'BUY'
size = 1
order_type = 'MARKET'
price = 100
order_tag = f'my_order-{datetime.datetime.now().strftime("%Y%m%d%H%M%S")}'

order_request = make_order_request(
    conid=conid,
    side=side,
    quantity=size,
    order_type=order_type,
    price=price,
    acct_id=account_id,
    coid=order_tag
)

ibkr_client = IbkrClient()
ibkr_client.place_order(order_request, ...)

modify_order

modify_order method follows the same question-handling logic as place_order.

check_health

check_health method is a wrapper around the tickle method, providing additional interpretation to its response.

The outcomes of check_health are:

  • True - all good. We can call the Gateway and verify that it has a non-competing, authenticated session.
  • False - something is wrong. We either cannot call the Gateway (in which case an additional log is produced) or the session is either competing, disconnected or unauthenticated.
  • AttributeError - tickle endpoint returned unexpected data.