11 Processing IB events
11.1 Processing events in IB-Matlab
IB uses an asynchronous event-based mechanism for sending information to clients. This means that we do not simply send a request to IB and wait for the answer. Instead, we send a request, and when IB is ready it will send us one or more (or zero) events in response. These events carry data, and by analyzing the stored event data we (hopefully) receive the answer that we were waiting for.
These callbacks are constantly being “fired” (i.e., invoked) by asynchronous messages from IB, ranging from temporary market connection losses/reconnections, to error messages and responses to market queries. Some of the events are triggered by user actions (market or portfolio queries, for example), while others are triggered by IB (e.g., disconnection notifications). The full list of IB events (and their data) is documented in the online API documentation.127
Matlab has built-in support for asynchronous events, called callbacks in Matlab jargon.128 Whereas Matlab callbacks are normally used in conjunction with Graphical User Interfaces (GUI), they can also be used with IB-Matlab, which automatically converts all the Java events received from IB into Matlab callbacks.
There are two types of callbacks that you can use in IB-Matlab:
Generic callback – this is a catch-all callback function that is triggered upon any IB event. Within this callback, you would need to write some code to distinguish between the different event types in order to process the events’ data. A skeleton for this is given below. The parameter controlling this callback in IBMatlab is called CallbackFunction.
Specific callback – this is a callback function that is only triggered when the specific event type is received from IB. Since the event type is known, you can process its event data more easily than in the generic callback case. However, you would need to specify a different specific callback for each of the event types that you wish to process.
The parameters controlling the specific callbacks in IBMatlab are called CallbackXXX, where XXX is the name of the IB event (the only exception to this rule is CallbackMessage, which handles the IB error event – the reason is that this event sends informational messages in addition to errors,129 so IB’s event name is misleading in this specific case).
When you specify any callback function to IBMatlab, either the generic kind (CallbackFunction) or a specific kind (CallbackXXX), the command action does not even need to be related to the callback (for example, you can set CallbackExecDetails together with Action='query').
data = IBMatlab('action','query', ..., ...
'CallbackExecDetails',@IBMatlab_CallbackExecDetails);
where IBMatlab_CallbackExecDetails() is a Matlab function created by you that accepts two input arguments (which are automatically populated in run-time):
ibConnector – the Java connector object that is described in §15 below
eventData – a Matlab struct that contains the event’s data in separate fields130
An example for specifying a Matlab callback function is:
function IBMatlab_CallbackExecDetails(ibConnector, eventData)
% do the callback processing here
end
You can pass external data to your callback functions using the callback cell-array format. For example, to pass two extra data values:131
callbackDetails = {@IBMatlab_CallbackExecDetails, 123, 'abc'};
IBMatlab('action','query',..., 'CallbackExecDetails',callbackDetails);
function IBMatlab_CallbackExecDetails(ibConn,eventData,extra1,extra2)
% do the callback processing here
end
When you specify any callback function to IBMatlab, you only need to set it once, in any IBMatlab command. Unlike most IBMatlab parameters, which are not remembered across IBMatlab commands and need to be re-specified, callbacks do not need to be re-specified. They are remembered from the moment they are first set, until such time as Matlab exits or the callback parameter is changed.132
To reset a callback (i.e., remove the callback invocation), simply set the callback parameter value to [] (empty square brackets) or '' (empty string):
data = IBMatlab('action','query', ..., 'CallbackExecDetails','');
Matlab callbacks are invoked even if you use the Java connector object (see §15) for requesting data from IB. This is actually very useful: we can use the connector object to send a request to IB, and then process the results in a Matlab callback function.
Using Matlab callbacks with the Java connector object can be used, for example, to implement combo trades,133 as an alternative to the built-in mechanism described in §9.5 above. In this case, separate contracts are created for the separate combo legs, then submitted to IB via the Java connector’s reqContractDetails() method, awaiting the returned IDs via the Matlab callback to the ContractDetails event (see CallbackContractDetails in the table below). Once the IDs for all the legs are received, com.ib.client.ComboLeg objects134 are created. The completed order can then be submitted to IB for trading via the Java connector’s placeOrder() method. All this may appear a bit difficult to implement, but in fact can be achieved in only a few dozen lines of code. This example illustrates how Matlab callbacks can seamlessly interact with the underlying Java connector’s methods.
Here is the list of callback events in IBMatlab (for information about any IB event, see https://interactivebrokers.github.io/tws-api/interfaceIBApi_1_1EWrapper.html):
IBMatlab parameter | IB Event | Triggered by IBMatlab? | Called when? |
CallbackAccountDownloadEnd | accountDownloadEnd | Yes | in response to account queries, after all UpdateAccount events were sent, to indicate end of data |
CallbackAccountSummary | accountSummary | Yes | for every single field in the summary data when the account data is requested (see §4.1) |
CallbackAccountSummaryEnd | accountSummaryEnd | Yes | indicates end of data after all AccountSummary events are sent |
CallbackBondContractDetails | bondContractDetails | Yes | in response to market queries; not really used in IBMatlab |
CallbackCommissionReport | commissionReport | Yes | immediately after a trade execution, or when requesting executions (see §12.1 below) |
CallbackConnectionClosed | connectionClosed | Yes | when IB-Matlab loses its connection (or reconnects) to TWS/Gateway |
CallbackContractDetails | contractDetails | Yes | in response to market queries; used in IBMatlab only to get the tick value |
CallbackContractDetailsEnd | contractDetailsEnd | Yes | indicates end of data after all ContractDetails events were sent |
CallbackCurrentTime | currentTime | Yes | numerous times during regular work; returns the current server system time |
CallbackDeltaNeutralValidation | deltaNeutralValidation | No | in response to a Delta-Neutral (DN) RFQ |
CallbackExecDetails | execDetails | Yes | whenever an order is partially or fully filled, or in response to the Java connector’s reqExecutions() |
CallbackExecDetailsEnd | execDetailsEnd | Yes | indicates end of data after all the ExecDetails events were sent |
CallbackFundamentalData | fundamentalData | Yes | in response to requesting fundamental data for a security |
CallbackHistoricalData | historicalData | Yes | in response to a historical data request, for each of the result bars separately |
CallbackManagedAccounts | managedAccounts | Yes | when a successful connection is made to a Financial Advisor account, or in response to calling the Java connector’s reqManagedAccts() |
CallbackMarketDataType | marketDataType | No | when the market type is set to Frozen or RealTime, to announce the switch, or in response to calling the Java connector’s reqMarketDataType() |
CallbackMessage | error | Yes | whenever IB wishes to send the user an error or informational message. See §14.1 below. |
CallbackNextValidId | nextValidId | No | after connecting to IB |
CallbackOpenOrder | openOrder | Yes | in response to a user query for open orders, for each open order |
CallbackOpenOrderEnd | openOrderEnd | Yes | after all OpenOrder events have been sent for a request, to indicate end of data |
CallbackOrderStatus | orderStatus | Yes | in response to a user query for open orders (for each open order), or when an order’s status changes |
CallbackPosition | position | Yes | in response to a user query for portfolio positions (for each position in the portfolio) |
CallbackPositionEnd | positionEnd | Yes | indicates end of data after all Position events have been sent |
CallbackTickPrice | tickPrice | Yes | in response to a market query, for price fields (e.g., bid) |
CallbackTickSize | tickSize | Yes | in response to a market query, for size fields (e.g., bidSize) |
CallbackTickString | tickString | Yes | in response to a market query, for string fields (e.g., lastTimestamp) |
CallbackTickGeneric | tickGeneric | Yes | in response to a query with a GenericTickList param |
CallbackTickEFP | tickEFP | No | when the market data changes. Values are updated immediately with no delay |
CallbackTickOptionComputation | tickOptionComputation | No | when the market of an option or its underlier moves. TWS’s option model volatilities, prices, and deltas, along with the present value of dividends expected on that underlier are received |
CallbackTickSnapshotEnd | tickSnapshotEnd | Yes | when all events in response to a snapshot query request have been sent, to indicate end of data |
CallbackRealtimeBar | realtimeBar | Yes | in response to a realtime bars request, for each bar separately |
CallbackReceiveFA | receiveFA | No | in response to calling the Java connector’s requestFA() |
CallbackScannerData | scannerData | Yes | in response to a user query for scanner data, for each result row |
CallbackScannerDataEnd | scannerDataEnd | Yes | indicates end of data after the last scannerData event was sent |
CallbackScannerParameters | scannerParameters | Yes | in response to a user query for scanner parameters XML |
CallbackUpdateAccountTime | updateAccountTime | Yes | together with the Update-AccountValue callbacks, to report on the event time |
CallbackUpdateAccountValue | updateAccountValue | Yes | for every single property in the list of account properties, when the account data is requested (see §4) or updated |
CallbackUpdateMktDepth | updateMktDepth | Yes | when market depth has changed |
CallbackUpdateMktDepthL2 | updateMktDepthL2 | Yes | when the Level II market depth has changed |
CallbackUpdateNewsBulletin | updateNewsBulletin | No | for each new bulletin if the client has subscribed by calling the Java connector’s reqNewsBulletins() |
CallbackUpdatePortfolio | updatePortfolio | Yes | when account updates are requested or occur |
11.2 Example – using CallbackExecDetails to track executions
The execDetails event is triggered whenever an order is fully or partially executed. Let us trap this event and send the execution information into a CSV file for later use in Excel (also see §12 below):
orderId = IBMatlab('action','BUY', 'symbol','GOOG', 'quantity',1, ...
'limitPrice',600, ...
'CallbackExecDetails',@IBMatlab_CallbackExecDetails);
Where the function IBMatlab_CallbackExecDetails is defined as follows (for example, in a file called IBMatlab_CallbackExecDetails.m):135
function IBMatlab_CallbackExecDetails(ibConnector, eventData, varargin)
% Extract the basic event data components 136
contractData = eventData.contract;
executionData = eventData.execution;
% Example of extracting data from the contract object:
symbol = eventData.contract.symbol;
secType = eventData.contract.secType;
% ... several other contract data field available – see above webpage
% Example of extracting data from the execution object:
orderId = eventData.execution.orderId;
execId = eventData.execution.execId;
time = eventData.execution.time;
exchange = eventData.execution.exchange;
side = eventData.execution.side;
shares = eventData.execution.shares;
price = eventData.execution.price;
permId = eventData.execution.permId;
liquidation = eventData.execution.liquidation;
cumQty = eventData.execution.cumQty;
avgPrice = eventData.execution.avgPrice;
% ... several other execution data field available – see above webpage
% Convert the data elements into a comma-separated string
csvline = sprintf('%s,%d,%s,%d,%d,%f\n', time, orderId, symbol, ...
shares, cumQty, price);
% Now append this comma-separated string to the CSV file
fid = fopen('executions.csv', 'at'); % 'at' = append text
fprintf(fid, csvline);
fclose(fid);
end % IBMatlab_CallbackExecDetails
11.3 Example – using CallbackTickGeneric to check if a security is shortable
In this example, we attach a user callback function to tickGeneric events in order to check whether a security is shortable137 (also see §7.1 above).
Note: according to IB,138 “Generic Tick Tags cannot be specified if you elect to use the Snapshot market data subscription”, and therefore we need to use the streaming-quotes mechanism, so QuotesNumber>1:
orderId = IBMatlab('action','Query', 'symbol','GOOG', ...
'GenericTicklist','236', 'QuotesNumber',2, ...
'CallbackTickGeneric',@IBMatlab_CallbackTickGeneric);
where the function IBMatlab_CallbackTickGeneric is defined as follows:139
function IBMatlab_CallbackTickGeneric(ibConnector, eventData, varargin)
% Only check the shortable tick type =46, according to
% https://interactivebrokers.github.io/tws-api/tick_types.html#shortable
if eventData.field == 46 % 46=Shortable (see footnote below)
% Get this event's tickerId (=orderId as returned from the
% original IBMatlab command)
tickerId = eventData.tickerId;
% Get the corresponding shortable value
shortableValue = eventData.generic; % (see footnote below)
% Now check whether the security is shortable or not
title = sprintf('Shortable info for request %d', tickerId);
if (shortableValue > 2.5) % 3.0
msgbox('>1000 shares available for a short',title,'help');
elseif (shortableValue > 1.5) % 2.0
msgbox('This contract will be available for short sale if shares can be located', title, 'warn');
elseif (shortableValue > 0.5) % 1.0
msgbox('Not available for short sale', title, 'warn');
else
msg=sprintf('Unknown shortable value: %g',shortableValue);
msgbox(msg, title, 'error');
end
end % if shortable tickType
end % IBMatlab_CallbackTickGeneric
Note that in this particular example we could also have simply used the streaming quotes data, instead of using the callback:
>> dataS = IBMatlab('action','query','symbol','GOOG','quotesNumber',-1);
>> shortableValue = dataS.data.shortable; % =3 for GOOG
11.4 Example – using CallbackContractDetails to get a contract’s full options chain
In this example, we attach a user callback function to contractDetails events in order to receive the full list of LocalSymbols and associated contract properties of an underlying security’s options chain.140
As noted in §5.4, it is not possible to receive the entire list of option prices in a single command; each market price requires a separate request with a specific LocalSymbol.
However, we can use the contractDetails event to extract the full list of option LocalSymbols in a single command. This relies on the fact that when the Right and Strike parameters of an option security are empty, IB returns the full list of contracts matching the other specifications.
We first define our callback function for the event:
function IBCallbackContractDetails(ibConnector, eventData)
contract = eventData.contractDetails.summary;
fprintf([contract.localSymbol '\t' ...
contract.secType '\t' ...
contract.symbol '\t' ...
contract.expiry '\t' ...
contract.right '\t' ...
contract.multiplier '\t' ...
num2str(contract.strike) '\n']);
end % IBCallbackContractDetails
Now we ask IB for the current market data of the futures options for Light Sweet Crude Oil (CL) with empty Right and Strike. We can safely ignore the IB warning about ambiguous or missing security definition:
>> data=IBMatlab('action','query', 'symbol','CL', 'secType','FOP',...
'exchange','NYMEX', 'currency','USD', ...
'expiry','201306', 'right','', 'strike',0.0, ...
'CallbackContractDetails',@IBCallbackContractDetails)
[API.msg2] The contract description specified for CL is ambiguous;
you must specify the multiplier. {286356018, 200}
LOM3 P6650 FOP CL 20130516 P 1000 66.5
LOM3 P8900 FOP CL 20130516 P 1000 89
LOM3 P11150 FOP CL 20130516 P 1000 111.5
LOM3 C6400 FOP CL 20130516 C 1000 64
LOM3 C8650 FOP CL 20130516 C 1000 86.5
LOM3 C10900 FOP CL 20130516 C 1000 109
LOM3 C6650 FOP CL 20130516 C 1000 66.5
LOM3 C8900 FOP CL 20130516 C 1000 89
... (over 400 different contracts)
The returned data struct will naturally contain empty market data, but its contractDetails field will contain useful data about the requested security: 141
>> data
data =
reqId: 286356019
reqTime: '30-Apr-2013 12:55:28'
dataTime: '30-Apr-2013 12:55:31'
dataTimestamp: 735354.538562743
lastEventTime: 735354.538562743
ticker: 'CL'
bidPrice: -1
askPrice: -1
open: -1
close: -1
low: -1
high: -1
lastPrice: -1
volume: -1
tick: 0.01
contract: [1x1 struct]
contractDetails: [1x1 struct]
>> data.contract % these are the details for only one of the options
ans =
conId: 50318947
symbol: 'CL'
secType: 'FOP'
expiry: '20130516'
strike: 111.5
right: 'P'
multiplier: '1000'
exchange: 'NYMEX'
currency: 'USD'
localSymbol: 'LOM3 P11150'
...
>> data.contractDetails
ans =
summary: [1x1 struct]
marketName: 'LO'
tradingClass: 'LO'
minTick: 0.01
priceMagnifier: 1
orderTypes: [1x205 char]
validExchanges: 'NYMEX'
underConId: 43635367
longName: 'Light Sweet Crude Oil'
contractMonth: '201306'
industry: []
category: []
subcategory: []
timeZoneId: 'EST'
tradingHours: '20130430:1800-1715;20130501:1800-1715'
liquidHours: '20130430:0000-1715,1800-2359;20130501:0000-1715,1800-2359'
...
11.5 Example – using CallbackUpdateMktDepth for realtime order-book GUI update
In this example, we wish to update a real-time GUI display of the order-book (at least the top few rows of the book), based on Level II data.
As noted in §7.3 above, market-depth events may be sent at a very high rate from the IB server, and so it is not feasible or useful to update the Matlab GUI for each update. Instead, we update the GUI with the latest data at a steady rate of 2 Hz (twice a second). This can be achieved in two different ways: one alternative is to set-up a periodic timer that will run our GUI-update callback every 0.5 secs, which will call IBMatlab(…,’QuotesNumber’,-1) to fetch the latest data and update the GUI.
Another alternative, shown here below, is to attach a user callback function to updateMktDepth142 and updateMktDepthL2143 events, updating an internal data struct, but only updating the GUI if 0.5 secs or more have passed since the last GUI update:
% IBMatlab_MktDepth - sample Market-Depth usage function
function IBMatlab_MktDepth(varargin)
% Initialize data
numRows = 5;
depthData = cell(numRows,6);
lastUpdateTime = -1;
GUI_refresh_period = 0.5 * 1/24/60/60; % =0.5 secs
% Prepare the GUI
hFig = figure('Name','IB-Matlab market-depth example', ...
'NumberTitle','off','CloseReq',@figClosedCallback,...
'Menubar','none', 'Toolbar','none', ...
'Resize','off', 'Pos',[100,200,520,170]);
color = get(hFig,'Color');
headers = {'Ask exch.','Ask size','Ask price', ...
'Bid price','Bid size','Bid exch.'};
formats = {'char','numeric','long', 'long','numeric','char'};
hTable = uitable('Parent',hFig, 'Pos',[10,40,500,120], ...
'Data',depthData, ...
'ColumnName',headers, 'ColumnFormat',formats);
hButton = uicontrol('Parent',hFig, 'Pos',[50,10,60,20], ...
'String','Start', 'Callback',@buttonCallback);
hLabel1 = uicontrol('Parent',hFig, 'Pos',[120,10,100,17], ...
'Style','text', 'String','Last updated:', ...
'Horizontal','right', 'Background',color);
hLabelTime = uicontrol('Parent',hFig, 'Pos',[225,10,100,17], ...
'Style','text', 'String','(not yet)', ...
'Horizontal','left', 'Background',color);
% Send the market-depth request to IB using IB-Matlab
contractParams = {'symbol','EUR', 'localSymbol','EUR.USD', ...
'secType','cash', 'exchange','idealpro', ...
'NumberOfRows',5, varargin{:}};
reqId = IBMatlab('action','query', 'QuotesNumber',inf, ...
contractParams{:}, ...
'CallbackUpdateMktDepth', @mktDepthCallbackFcn,...
'CallbackUpdateMktDepthL2',@mktDepthCallbackFcn);
% Figure close callback function - stop the market-depth streaming
function figClosedCallback(hFig, eventData)
% Delete the figure window and stop any pending data streaming
delete(hFig);
IBMatlab('action','query', contractParams{:}, 'QuotesNumber',0);
end % figClosedCallback
% Start/stop button callback function
function buttonCallback(hButton, eventData)
currentString = get(hButton,'String');
if strcmp(currentString,'Start')
set(hButton,'String','Stop');
else
set(hButton,'String','Start');
end
end % buttonCallback
% Callback functions to handle IB Market Depth update events
function mktDepthCallbackFcn(ibConnObj, eventData)
% Ensure that it's the correct MktDepth event
if eventData.tickerId == reqId
% Get the updated data row
% Note: Java indices start at 0, Matlab starts at 1
row = eventData.position + 1;
% Get the size & price data fields from the event's data
size = eventData.size;
price = eventData.price;
% Prevent extra LS digits in uitable display
price = single(price + 0.00000001);
% Exchange (marketMaker) data is only available in L2:
try
exchange = eventData.marketMaker;
catch
exchange = '';
end
% Update the internal data table
if eventData.side == 0 % ask
if eventData.operation == 0 % insert
depthData(row+1:end,1:3) = depthData(row:end-1,1:3);
depthData(row,1:3) = {exchange, size, price};
elseif eventData.operation == 1 % update
depthData(row,1:3) = {exchange, size, price};
elseif eventData.operation == 2 % delete
depthData(row:end-1,1:3) = depthData(row+1:end,1:3);
depthData(end,1:3) = {[],[],[]};
else
% should never happen!
end
else % bid (same as ask but the data columns are reversed)
if eventData.operation == 0 % insert
depthData(row+1:end,4:6) = depthData(row:end-1,4:6);
depthData(row,4:6) = {price, size, exchange};
elseif eventData.operation == 1 % update
depthData(row,4:6) = {price, size, exchange};
elseif eventData.operation == 2 % delete
depthData(row:end-1,4:6) = depthData(row+1:end,4:6);
depthData(end,4:6) = {[],[],[]};
else
% should never happen!
end
end
% Update the GUI if more than 0.5 secs have passed and
% the <Stop> button was not pressed
isStopped = strcmp(get(hButton,'String'),'Start');
if now - lastUpdateTime > GUI_refresh_period && ~isStopped
set(hTable,'Data',depthData);
set(hLabelTime,'String',datestr(now,'HH:MM:SS'));
lastUpdateTime = now;
end
end
end % mktDepthCallbackFcn
end % IBMatlab_MktDepth
130 Until IBMatlab v2.00, eventData contained Java objects whose fields could be inspected via the struct function e.g. struct(eventData.contract). Starting in v2.01, such objects are regular Matlab structs. In IB-Matlab v2.19 the struct field names changed, by removing the m_ field prefix (e.g. eventData.contract.m_symbol à .symbol).
132 It is not an error to re-specify the callbacks in each IBMatlab command, it is simply useless and makes the code less readable
134 http://interactivebrokers.github.io/tws-api/classIBApi_1_1ComboLeg.html; http://interactivebrokers.com/php/whiteLabel/Interoperability/Socket_Client_Java/java_properties.htm
135 This file can be downloaded from: http://UndocumentedMatlab.com/files/IBMatlab_CallbackExecDetails.m
136 Until IBMatlab v2.00, eventData contained Java objects whose fields could be inspected via the struct function e.g. struct(eventData.contract). Starting in v2.01, such objects are regular Matlab structs. In IB-Matlab v2.19 the struct field names changed, by removing the m_ field prefix (e.g. eventData.contract.m_symbol à .symbol).
137 https://interactivebrokers.github.io/tws-api/tick_types.html#shortable. Additional details: https://ibkr.info/it/article/2024
139 This code is downloadable from: http://UndocumentedMatlab.com/files/IBMatlab_CallbackTickGeneric.m. Depending on your TWS/IBG installation version, tickGeneric’s eventData double-precision value field is called either “generic” or “value”.
140 A synchronous alternative for retrieving the options chain is explained in §5.4 above
141 Until IBMatlab v2.00, eventData contained Java objects whose fields could be inspected via the struct function e.g. struct(eventData.contract). Starting in v2.01, such objects are regular Matlab structs. In IB-Matlab v2.19 the struct field names changed, by removing the m_ field prefix (e.g. eventData.contract.m_symbol à .symbol).