Skip to content

Commit

Permalink
Merge pull request #8 from oliviermilla/jibmerge
Browse files Browse the repository at this point in the history
Updated to latest code of Jib.jl
  • Loading branch information
oliviermilla authored Jan 9, 2025
2 parents 3cf237b + 0a0e4f0 commit 93d2bb2
Show file tree
Hide file tree
Showing 23 changed files with 791 additions and 782 deletions.
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

2 comments on commit 93d2bb2

@oliviermilla
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@JuliaRegistrator register

Release notes:

@JuliaRegistrator
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Error while trying to register: Changing UUIDs is not allowed

Please sign in to comment.