Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Updated to latest code of Jib.jl #8

Merged
merged 16 commits into from
Jan 9, 2025
Merged
4 changes: 2 additions & 2 deletions Project.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name = "InteractiveBrokers"
uuid = "f310f2d2-a263-11e8-3998-47bd686f18f7"
uuid = "c9eee01f-432d-414e-83d3-5217cf8b3b71"
authors = ["Luca Billi <[email protected]>", "Olivier Milla <[email protected]>"]
version = "0.27"
version = "0.28"

[deps]
Sockets = "6462fe0b-24de-5631-8697-dd941f90decc"
Expand Down
33 changes: 15 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@
[![Build Status](https://github.com/oliviermilla/InteractiveBrokers.jl/actions/workflows/CI.yml/badge.svg?branch=main)](https://github.com/oliviermilla/InteractiveBrokers.jl/actions/workflows/CI.yml?query=branch%3Amain)
[![codecov](https://codecov.io/gh/oliviermilla/InteractiveBrokers.jl/graph/badge.svg?token=AFV1NV9CR9)](https://codecov.io/gh/oliviermilla/InteractiveBrokers.jl)

**A Julia implementation of Interactive Brokers API**
*A Julia implementation of Interactive Brokers API*

`InteractiveBrokers` is a native [Julia](https://julialang.org/) client that implements
[Interactive Brokers](https://www.interactivebrokers.com/) API to communicate
with TWS or IBGateway.

It aims to be feature complete, however it does not support legacy versions.
Currently, only API versions `v176+` are supported.
Currently, only API versions `v187+` are supported.

The package design follows the official C++/Java
[IB API](https://interactivebrokers.github.io/tws-api/),
Expand All @@ -37,7 +37,7 @@ To install from GitHub:
] add https://github.com/oliviermilla/InteractiveBrokers.jl
```

### Usage
## Usage
The user interacts mainly with these two objects:
- `Connection`: a handle holding a connection to the server
- `Wrapper`: a container for the callbacks that are invoked
Expand All @@ -55,8 +55,8 @@ using InteractiveBrokers

wrap = InteractiveBrokers.Wrapper(
# Customized methods go here
error= (err) ->
println("Error: $(something($(err.id), "NA")) $(err.errorCode) $(err.errorString) $(err.advancedOrderRejectJson)"),
error= (id, errorTime, errorCode, errorString, advancedOrderRejectJson) ->
println("Error: $(something(id, "NA")) $errorTime $errorCode $errorString $advancedOrderRejectJson"),

nextValidId= (orderId) -> println("Next OrderId: $orderId"),

Expand Down Expand Up @@ -93,7 +93,7 @@ InteractiveBrokers.placeOrder(ib, orderId, contract, order)
InteractiveBrokers.disconnect(ib)
```

##### Foreground vs. Background Processing
#### Foreground vs. Background Processing
It is possible to process the server responses either within the main process
or in a separate background `Task`:
- **foreground processing** is triggered by invoking `InteractiveBrokers.check_all(ib, wrap, Tab=Dict)`.
Expand All @@ -109,7 +109,7 @@ To avoid undesired effects, the two approaches should not be mixed together on t
`Tab` parameter of above examples is the sink format used when applicable. The library supports an extension for
DataFrames (just pass `DataFrame` as a last parameter), otherwise `Dict` is the default format.

### Implementation Details
## Implementation Details
The package does not export any name, therefore any functions
or types described here need to be prefixed by `InteractiveBrokers.*`.

Expand All @@ -126,7 +126,7 @@ As Julia is not an object-oriented language, the functionality of the IB
The only caveat is to remember to pass a `Connection` as first argument: _e.g._
`reqContractDetails(ib::Connection, reqId:Int, contract::Contract)`

##### [`Wrapper`](src/wrapper.jl)
#### [`Wrapper`](src/wrapper.jl)
Like the official IB `EWrapper` class, this `struct` holds the callbacks
that are dispatched when responses are processed.
The user provides the callback definitions as keyword arguments
Expand Down Expand Up @@ -157,30 +157,27 @@ refer to the official IB `EWrapper` class documentation.
As reference, the exact signatures used in this package
are found [here](data/wrapper_signatures.jl).

### Notes
## Notes
Callbacks are generally invoked with arguments and types matching the signatures
as described in the official documentation.
However, there are few exceptions:
- `tickPrice()` has an extra `size::Float64` argument,
which is meaningful only when `TickType ∈ {BID, ASK, LAST}`.
In these cases, the official IB API fires an extra `tickSize()` event instead.
- `historicalData()` is invoked only once per request,
presenting all the historical data as a single `DataFrame`,
presenting all the historical data as a single `Vector{Bar}`,
whereas the official IB API invokes it row-by-row.
- `scannerData()` is also invoked once per request and its arguments
are in fact vectors rather than single values.

These modifications make it possible to establish the rule:
_one callback per server response_.

Consequently, `historicalDataEnd()` and `scannerDataEnd()` are redundant and
are **not** used in this package.
Consequently, ~~`historicalDataEnd()`~~
(starting from `v196` it's sent in a separate message)
and `scannerDataEnd()` are redundant and are **not** used in this package.

`DataFrame` are passed to several other callbacks, such as:
`mktDepthExchanges()`, `smartComponents()`, `newsProviders()`, `histogramData()`,
`marketRule()` and the `historicalTicks*()` family.

##### Missing Values
#### Missing Values
Occasionally, for numerical types, there is the need to represent
the lack of a value.

Expand All @@ -194,7 +191,7 @@ for 64-bit floating point.
This package makes an effort to use Julia built-in `Nothing`
in all circumstances.

##### Data Structures
#### Data Structures
Other classes that mainly hold data are also replicated.
They are implemented as Julia `struct` or `mutable struct` with names,
types and default values matching the IB API counterparts: _e.g._
Expand Down
28 changes: 16 additions & 12 deletions data/wrapper_signatures.jl
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ managedAccounts(accountsList::String)

receiveFA(faDataType::FaDataType, xml::String)

historicalData(reqId::Int, bar::Dict)
historicalData(reqId::Int, bars::VBar)

scannerParameters(xml::String)

Expand Down Expand Up @@ -103,19 +103,19 @@ securityDefinitionOptionalParameterEnd(reqId::Int)

softDollarTiers(reqId::Int, tiers::Vector{SoftDollarTier})

familyCodes(familyCodes::Vector{FamilyCode})
familyCodes(familyCodes::VFamilyCode)

symbolSamples(reqId::Int, contractDescriptions::Vector{ContractDescription})

mktDepthExchanges(depthMktDataDescriptions::Dict)
mktDepthExchanges(depthMktDataDescriptions::VDepthMktDataDescription)

tickNews(tickerId::Int, timeStamp::Int, providerCode::String, articleId::String, headline::String, extraData::String)

smartComponents(reqId::Int, theMap::Dict)
smartComponents(reqId::Int, theMap::VSmartComponent)

tickReqParams(tickerId::Int, minTick::Union{Float64,Nothing}, bboExchange::String, snapshotPermissions::Int)

newsProviders(newsProviders::Dict)
newsProviders(newsProviders::VNewsProvider)

newsArticle(requestId::Int, articleType::Int, articleText::String)

Expand All @@ -125,33 +125,33 @@ historicalNewsEnd(requestId::Int, hasMore::Bool)

headTimestamp(reqId::Int, headTimestamp::String)

histogramData(reqId::Int, data::Dict)
histogramData(reqId::Int, data::VHistogramEntry)

historicalDataUpdate(reqId::Int, bar::Bar)

rerouteMktDataReq(reqId::Int, conid::Int, exchange::String)

rerouteMktDepthReq(reqId::Int, conid::Int, exchange::String)

marketRule(marketRuleId::Int, priceIncrements::Dict)
marketRule(marketRuleId::Int, priceIncrements::VPriceIncrement)

pnl(reqId::Int, dailyPnL::Float64, unrealizedPnL::Float64, realizedPnL::Float64)

pnlSingle(reqId::Int, pos::Int, dailyPnL::Float64, unrealizedPnL::Union{Float64,Nothing}, realizedPnL::Union{Float64,Nothing}, value::Float64)

historicalTicks(reqId::Int, ticks::Dict, done::Bool)
historicalTicks(reqId::Int, ticks::VHistoricalTick, done::Bool)

historicalTicksBidAsk(reqId::Int, ticks::Dict, done::Bool)
historicalTicksBidAsk(reqId::Int, ticks::VHistoricalTickBidAsk, done::Bool)

historicalTicksLast(reqId::Int, ticks::Dict, done::Bool)
historicalTicksLast(reqId::Int, ticks::VHistoricalTickLast, done::Bool)

tickByTickAllLast(reqId::Int, tickType::Int, time::Int, price::Float64, size::Float64, attribs::TickAttribLast, exchange::String, specialConditions::String)

tickByTickBidAsk(reqId::Int, time::Int, bidPrice::Float64, askPrice::Float64, bidSize::Float64, askSize::Float64, attribs::TickAttribBidAsk)

tickByTickMidPoint(reqId::Int, time::Int, midPoint::Float64)

orderBound(orderId::Int, apiClientId::Int, apiOrderId::Int)
orderBound(permId::Int, clientId::Int, orderId::Int)

completedOrder(contract::Contract, order::Order, orderState::OrderState)

Expand All @@ -163,6 +163,10 @@ wshMetaData(reqId::Int, dataJson::String)

wshEventData(reqId::Int, dataJson::String)

historicalSchedule(reqId::Int, startDateTime::String, endDateTime::String, timeZone::String, sessions::Dict)
historicalSchedule(reqId::Int, startDateTime::String, endDateTime::String, timeZone::String, sessions::VHistoricalSession)

userInfo(reqId::Int, whiteBrandingId::String)

historicalDataEnd(reqId::Int, startDateStr::String, endDateStr::String)

currentTimeInMillis(timeInMillis::Int)
21 changes: 12 additions & 9 deletions ext/DataFramesExt.jl
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,21 @@ import InteractiveBrokers

using DataFrames

function InteractiveBrokers.Reader.fill_table(cols, n::Int, it, Tab::Type{<:DataFrame})
# Deprecated at Job 0.26 when DataFrame were dropped as the default sink format.
# Kept for information.

df = Tab([k => Vector{T}(undef, n) for (k, T) ∈ pairs(cols)];
copycols=false)
# function InteractiveBrokers.Reader.fill_table(cols, n::Int, it, Tab::Type{<:DataFrame})

# df = Tab([k => Vector{T}(undef, n) for (k, T) ∈ pairs(cols)];
# copycols=false)

nr, nc = size(df)
# nr, nc = size(df)

for r ∈ 1:nr, c ∈ 1:nc
df[r, c] = InteractiveBrokers.Reader.pop(it)
end
# for r ∈ 1:nr, c ∈ 1:nc
# df[r, c] = InteractiveBrokers.Reader.pop(it)
# end

df
end
# df
# end

end
1 change: 1 addition & 0 deletions src/Errors.jl
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ export IbkrErrorMessage

struct IbkrErrorMessage <: Exception
id::Union{Int,Nothing}
errorTime::Int
errorCode::Union{Int,Nothing}
errorString::String
advancedOrderRejectJson::String
Expand Down
11 changes: 5 additions & 6 deletions src/InteractiveBrokers.jl
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ include("types_private.jl")
include("wrapper.jl")
include("reader.jl") ; using .Reader: check_all, start_reader
include("utils.jl")
include("TickTypes.jl")
include("TickTypes.jl") ; using .TickTypes: tickname

"""
Connection()
Expand Down Expand Up @@ -53,16 +53,15 @@ function connect(;host::IPAddr=getalladdrinfo("localhost")[1], port::Int=4002, c
# Handshake
Client.write_one(s, buf)

res = collect(String, Reader.read_msg(s))
msg = Reader.read_msg(s)

@assert length(res) == 2
v, t = Reader.decode_init(msg)

@info "connected" V=res[1] T=res[2]
@info "connected" v t

v = parse(Int, res[1])
m ≤ v ≤ M || error("unsupported version")

ib = Connection(s, clientId, connectOptions, Client.Version(v), res[2])
ib = Connection(s, clientId, connectOptions, Client.Version(v), t)

Requests.startApi(ib, clientId, optionalCapabilities)

Expand Down
38 changes: 26 additions & 12 deletions src/decode.jl
Original file line number Diff line number Diff line change
@@ -1,17 +1,31 @@
using Base.Iterators: take

include("process.jl")
include("fielditerator.jl")
include("TickTypes.jl")


function decode_init(msg)

it = FieldIterator(msg)

v::Int,
t::String = it

isempty(it) || @error "decode_init(): init message not fully parsed" M=msg

v, t
end


# Make a shortcut
const pop = popfirst!
function decode(msg, w, ver)

function decode(it, w, ver, Tab=Dict)
it = FieldIterator(msg)

# The first field is the message ID
id::Int = pop(it)
id::Int = it

# The second field (version) is ignored for id < 75 and != 3, 5, 10, 11, 17, 18, 21
if id < 75 && id ∉ (3, 5, 10, 11, 17, 18, 21)
# The second field (version) is ignored for id < 75 and != 3, 4, 5, 10, 11, 17, 18, 21
if id < 75 && id ∉ (3, 4, 5, 10, 11, 17, 18, 21) ||
id == 4 && ver < Client.ERROR_TIME
pop(it)
end

Expand All @@ -20,15 +34,15 @@ function decode(it, w, ver, Tab=Dict)
if isnothing(f)
@error "decode(): unknown message" id
else
#try ---- COMMENTED FROM JIB
f(it, w, ver, Tab)
#try --- Commented from JIB
f(it, w, ver)
# catch e
# @error "decode(): exception caught" M=it.msg
# @error "decode(): exception caught" M=msg
# # Print stacktrace to stderr
# Base.display_error(Base.current_exceptions())
# end

isempty(it) || @error "decode(): message not fully parsed" M=it.msg ignored=collect(String, it)
isempty(it) || @error "decode(): message not fully parsed" M=msg ignored=collect(String, rest(it))
end
end

6 changes: 1 addition & 5 deletions src/encoder.jl
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,4 @@ end
(e::Encoder)(x::Base.Generator) = foreach(e, x)

# Multiple arguments
function (e::Encoder)(x, y...)
e(x)
foreach(e, y)
end

(e::Encoder)(x...) = foreach(e, x)
Loading
Loading