my-ib-api 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/lib/ib-api.rb +10 -0
- data/lib/ib/base.rb +99 -0
- data/lib/ib/base_properties.rb +154 -0
- data/lib/ib/connection.rb +327 -0
- data/lib/ib/constants.rb +334 -0
- data/lib/ib/db.rb +29 -0
- data/lib/ib/engine.rb +35 -0
- data/lib/ib/errors.rb +40 -0
- data/lib/ib/extensions.rb +72 -0
- data/lib/ib/flex.rb +106 -0
- data/lib/ib/logger.rb +25 -0
- data/lib/ib/messages.rb +88 -0
- data/lib/ib/messages/abstract_message.rb +89 -0
- data/lib/ib/messages/incoming.rb +134 -0
- data/lib/ib/messages/incoming/abstract_message.rb +99 -0
- data/lib/ib/messages/incoming/alert.rb +34 -0
- data/lib/ib/messages/incoming/contract_data.rb +102 -0
- data/lib/ib/messages/incoming/delta_neutral_validation.rb +23 -0
- data/lib/ib/messages/incoming/execution_data.rb +54 -0
- data/lib/ib/messages/incoming/historical_data.rb +55 -0
- data/lib/ib/messages/incoming/market_depths.rb +44 -0
- data/lib/ib/messages/incoming/next_valid_id.rb +18 -0
- data/lib/ib/messages/incoming/open_order.rb +232 -0
- data/lib/ib/messages/incoming/order_status.rb +81 -0
- data/lib/ib/messages/incoming/portfolio_value.rb +39 -0
- data/lib/ib/messages/incoming/real_time_bar.rb +32 -0
- data/lib/ib/messages/incoming/scanner_data.rb +53 -0
- data/lib/ib/messages/incoming/ticks.rb +131 -0
- data/lib/ib/messages/outgoing.rb +331 -0
- data/lib/ib/messages/outgoing/abstract_message.rb +73 -0
- data/lib/ib/messages/outgoing/bar_requests.rb +189 -0
- data/lib/ib/messages/outgoing/place_order.rb +141 -0
- data/lib/ib/model.rb +6 -0
- data/lib/ib/models.rb +10 -0
- data/lib/ib/requires.rb +9 -0
- data/lib/ib/socket.rb +81 -0
- data/lib/ib/symbols.rb +35 -0
- data/lib/ib/symbols/bonds.rb +28 -0
- data/lib/ib/symbols/forex.rb +41 -0
- data/lib/ib/symbols/futures.rb +117 -0
- data/lib/ib/symbols/options.rb +39 -0
- data/lib/ib/symbols/stocks.rb +37 -0
- data/lib/ib/version.rb +6 -0
- data/lib/models/ib/bag.rb +51 -0
- data/lib/models/ib/bar.rb +45 -0
- data/lib/models/ib/combo_leg.rb +103 -0
- data/lib/models/ib/contract.rb +292 -0
- data/lib/models/ib/contract_detail.rb +89 -0
- data/lib/models/ib/execution.rb +65 -0
- data/lib/models/ib/option.rb +60 -0
- data/lib/models/ib/order.rb +391 -0
- data/lib/models/ib/order_state.rb +128 -0
- data/lib/models/ib/underlying.rb +34 -0
- metadata +96 -0
data/lib/ib/flex.rb
ADDED
@@ -0,0 +1,106 @@
|
|
1
|
+
require 'net/http'
|
2
|
+
require 'net/https'
|
3
|
+
require 'xmlsimple'
|
4
|
+
|
5
|
+
module IB
|
6
|
+
|
7
|
+
# FLEX is a web-based service from IB that helps you to retrieve your activity,
|
8
|
+
# trades and positions. It is working independently from TWS or Gateway, using your
|
9
|
+
# internet connection directly. See /misc/flex for extended FLEX documentation.
|
10
|
+
#
|
11
|
+
# In order to use this service, activate it and configure your token first.
|
12
|
+
# Your Token is located at Account Management->Reports->Delivery Settings->Flex Web Service.
|
13
|
+
# You need to activate Flex Web Service and generate new token(s) there.
|
14
|
+
# Your Flex Query Ids are in Account Management->Reports->Activity->Flex Queries.
|
15
|
+
# Create new Flex query and make sure to set its output format to XML.
|
16
|
+
#
|
17
|
+
# IB::Flex object incapsulates a single pre-defined Flex query.
|
18
|
+
class Flex
|
19
|
+
class << self
|
20
|
+
attr_accessor :token, :uri
|
21
|
+
|
22
|
+
# By default, uri is a well known FLEX Web Service URI
|
23
|
+
def uri
|
24
|
+
#@uri || 'https://www.interactivebrokers.com/Universal/servlet/FlexStatementService.SendRequest'
|
25
|
+
@uri || 'https://gdcdyn.interactivebrokers.com/Universal/servlet/FlexStatementService.SendRequest'
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
# Create new Flex query with options:
|
30
|
+
# :token => 1111111111111111111111111111111111 # CHANGE to your actual token!
|
31
|
+
# :query_id => 11111 # CHANGE to actual query id!
|
32
|
+
# :format => :xml (default) / :csv
|
33
|
+
# :verbose => true / false (default)
|
34
|
+
def initialize opts
|
35
|
+
@query_id = opts[:query_id]
|
36
|
+
@token = opts[:token] || Flex.token
|
37
|
+
@format = opts[:format] || :xml
|
38
|
+
@verbose = !!opts[:verbose]
|
39
|
+
yield self if block_given?
|
40
|
+
end
|
41
|
+
|
42
|
+
# Run a pre-defined Flex query against IB Flex Web Service
|
43
|
+
# Returns a (parsed) report or raises FlexError in case of problems
|
44
|
+
def run
|
45
|
+
# Initiate FLEX request at a known FLEX Web Service URI
|
46
|
+
resp = get_content Flex.uri, :t => @token, :q => @query_id, :v => 3
|
47
|
+
error("#{resp['ErrorCode']}: #{resp['ErrorMessage']}", :flex) if resp['Status'] == 'Fail'
|
48
|
+
|
49
|
+
reference_code = resp['ReferenceCode']
|
50
|
+
report_uri = resp['Url']
|
51
|
+
|
52
|
+
# Retrieve the FLEX report
|
53
|
+
report = nil
|
54
|
+
until report do
|
55
|
+
report = get_content(report_uri, :t => @token, :q => reference_code, :v => 3,
|
56
|
+
:text_ok => @format != :xml)
|
57
|
+
|
58
|
+
# If Status is specified, returned xml contains only error message, not actual report
|
59
|
+
if report.is_a?(Hash) && report['Status'] =~ /Fail|Warn/
|
60
|
+
error_code = report['ErrorCode'].to_i
|
61
|
+
error_message = "#{error_code}: #{report['ErrorMessage']}"
|
62
|
+
|
63
|
+
case error_code
|
64
|
+
when 1001..1009, 1018, 1019, 1021
|
65
|
+
# Report is just not ready yet, wait and retry
|
66
|
+
puts error_message if @verbose
|
67
|
+
report = nil
|
68
|
+
sleep 1
|
69
|
+
else # Fatal error
|
70
|
+
error error_message, :flex
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
report
|
75
|
+
end
|
76
|
+
|
77
|
+
# Helper method to get (and parse XML) responses from IB Flex Web Service
|
78
|
+
def get_content address, fields
|
79
|
+
text_ok = fields.delete(:text_ok)
|
80
|
+
resp = get address, fields
|
81
|
+
if resp.content_type == 'text/xml'
|
82
|
+
XmlSimple.xml_in(resp.body, :ForceArray => false)
|
83
|
+
else
|
84
|
+
error("Expected xml, got #{resp.content_type}", :flex) unless text_ok
|
85
|
+
resp.body
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
# Helper method to get raw responses from IB Flex Web Service
|
90
|
+
def get address, fields
|
91
|
+
uri = URI("#{address}?" + fields.map { |k, v| "#{k}=#{URI.encode(v.to_s)}" }.join('&'))
|
92
|
+
|
93
|
+
server = Net::HTTP.new(uri.host, uri.port)
|
94
|
+
server.use_ssl = (uri.scheme == 'https')
|
95
|
+
server.verify_mode = OpenSSL::SSL::VERIFY_NONE if server.use_ssl? # Avoid OpenSSL failures
|
96
|
+
|
97
|
+
resp = server.start do |http|
|
98
|
+
req = Net::HTTP::Get.new(uri.request_uri)
|
99
|
+
http.request(req)
|
100
|
+
end
|
101
|
+
error("URI responded with #{resp.code}", :flex) unless resp.code.to_i == 200
|
102
|
+
resp
|
103
|
+
end
|
104
|
+
|
105
|
+
end
|
106
|
+
end
|
data/lib/ib/logger.rb
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
require "logger"
|
2
|
+
|
3
|
+
# Add default_logger accessor into Object
|
4
|
+
def default_logger
|
5
|
+
@default_logger ||= Logger.new(STDOUT).tap do |logger|
|
6
|
+
time_format = RUBY_VERSION =~ /1\.8\./ ? '%H:%M:%S.%N' : '%H:%M:%S.%3N'
|
7
|
+
logger.formatter = proc do |level, time, prog, msg|
|
8
|
+
# "#{time.strftime(time_format)} #{msg}\n"
|
9
|
+
"#{time.strftime('%Y-%m-%d %H:%M:%S')} #{msg}\n"
|
10
|
+
# Hardcoded 20201204 by PA!
|
11
|
+
end
|
12
|
+
logger.level = Logger::INFO
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def default_logger= logger
|
17
|
+
@default_logger = logger
|
18
|
+
end
|
19
|
+
|
20
|
+
# Add universally accessible log method/accessor into Object
|
21
|
+
def log *args
|
22
|
+
default_logger.tap do |logger|
|
23
|
+
logger.fatal *args unless args.empty?
|
24
|
+
end
|
25
|
+
end
|
data/lib/ib/messages.rb
ADDED
@@ -0,0 +1,88 @@
|
|
1
|
+
module IB
|
2
|
+
module Messages
|
3
|
+
# This gem supports incoming/outgoing IB messages compatible with the following
|
4
|
+
# IB client/server versions:
|
5
|
+
CLIENT_VERSION = 59 # 59? Maximal client version implemented
|
6
|
+
SERVER_VERSION = 62 # 38? 53? 62? Minimal server version required
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
require 'ib/messages/outgoing'
|
11
|
+
require 'ib/messages/incoming'
|
12
|
+
|
13
|
+
__END__
|
14
|
+
// Client version history
|
15
|
+
//
|
16
|
+
// 6 = Added parentId to orderStatus
|
17
|
+
// 7 = The new execDetails event returned for an order filled status and reqExecDetails
|
18
|
+
// Also market depth is available.
|
19
|
+
// 8 = Added lastFillPrice to orderStatus() event and permId to execution details
|
20
|
+
// 9 = Added 'averageCost', 'unrealizedPNL', and 'unrealizedPNL' to updatePortfolio event
|
21
|
+
// 10 = Added 'serverId' to the 'open order' & 'order status' events.
|
22
|
+
// We send back all the API open orders upon connection.
|
23
|
+
// Added new methods reqAllOpenOrders, reqAutoOpenOrders()
|
24
|
+
// Added FA support - reqExecution has filter.
|
25
|
+
// - reqAccountUpdates takes acct code.
|
26
|
+
// 11 = Added permId to openOrder event.
|
27
|
+
// 12 = requsting open order attributes ignoreRth, hidden, and discretionary
|
28
|
+
// 13 = added goodAfterTime
|
29
|
+
// 14 = always send size on bid/ask/last tick
|
30
|
+
// 15 = send allocation description string on openOrder
|
31
|
+
// 16 = can receive account name in account and portfolio updates, and fa params in openOrder
|
32
|
+
// 17 = can receive liquidation field in exec reports, and notAutoAvailable field in mkt data
|
33
|
+
// 18 = can receive good till date field in open order messages, and request intraday backfill
|
34
|
+
// 19 = can receive rthOnly flag in ORDER_STATUS
|
35
|
+
// 20 = expects TWS time string on connection after server version >= 20.
|
36
|
+
// 21 = can receive bond contract details.
|
37
|
+
// 22 = can receive price magnifier in version 2 contract details message
|
38
|
+
// 23 = support for scanner
|
39
|
+
// 24 = can receive volatility order parameters in open order messages
|
40
|
+
// 25 = can receive HMDS query start and end times
|
41
|
+
// 26 = can receive option vols in option market data messages
|
42
|
+
// 27 = can receive delta neutral order type and delta neutral aux price in place order version 20: API 8.85
|
43
|
+
// 28 = can receive option model computation ticks: API 8.9
|
44
|
+
// 29 = can receive trail stop limit price in open order and can place them: API 8.91
|
45
|
+
// 30 = can receive extended bond contract def, new ticks, and trade count in bars
|
46
|
+
// 31 = can receive EFP extensions to scanner and market data, and combo legs on open orders
|
47
|
+
// ; can receive RT bars
|
48
|
+
// 32 = can receive TickType.LAST_TIMESTAMP
|
49
|
+
// ; can receive "whyHeld" in order status messages
|
50
|
+
// 33 = can receive ScaleNumComponents and ScaleComponentSize is open order messages
|
51
|
+
// 34 = can receive whatIf orders / order state
|
52
|
+
// 35 = can receive contId field for Contract objects
|
53
|
+
// 36 = can receive outsideRth field for Order objects
|
54
|
+
// 37 = can receive clearingAccount and clearingIntent for Order objects
|
55
|
+
// 38 = can receive multiplier and primaryExchange in portfolio updates
|
56
|
+
// ; can receive cumQty and avgPrice in execution
|
57
|
+
// ; can receive fundamental data
|
58
|
+
// ; can receive underComp for Contract objects
|
59
|
+
// ; can receive reqId and end marker in contractDetails/bondContractDetails
|
60
|
+
// ; can receive ScaleInitComponentSize and ScaleSubsComponentSize for Order objects
|
61
|
+
// 39 = can receive underConId in contractDetails
|
62
|
+
// 40 = can receive algoStrategy/algoParams in openOrder
|
63
|
+
// 41 = can receive end marker for openOrder
|
64
|
+
// ; can receive end marker for account download
|
65
|
+
// ; can receive end marker for executions download
|
66
|
+
// 42 = can receive deltaNeutralValidation
|
67
|
+
// 43 = can receive longName(companyName)
|
68
|
+
// ; can receive listingExchange
|
69
|
+
// ; can receive RTVolume tick
|
70
|
+
// 44 = can receive end market for ticker snapshot
|
71
|
+
// 45 = can receive notHeld field in openOrder
|
72
|
+
// 46 = can receive contractMonth, industry, category, subcategory fields in contractDetails
|
73
|
+
// ; can receive timeZoneId, tradingHours, liquidHours fields in contractDetails
|
74
|
+
// 47 = can receive gamma, vega, theta, undPrice fields in TICK_OPTION_COMPUTATION
|
75
|
+
// 48 = can receive exemptCode in openOrder
|
76
|
+
// 49 = can receive hedgeType and hedgeParam in openOrder
|
77
|
+
// 50 = can receive optOutSmartRouting field in openOrder
|
78
|
+
// 51 = can receive smartComboRoutingParams in openOrder
|
79
|
+
// 52 = can receive deltaNeutralConId, deltaNeutralSettlingFirm, deltaNeutralClearingAccount and deltaNeutralClearingIntent in openOrder
|
80
|
+
// 53 = can receive orderRef in execution
|
81
|
+
// 54 = can receive scale order fields (PriceAdjustValue, PriceAdjustInterval, ProfitOffset, AutoReset,
|
82
|
+
// InitPosition, InitFillQty and RandomPercent) in openOrder
|
83
|
+
// 55 = can receive orderComboLegs (price) in openOrder
|
84
|
+
// 56 = can receive trailingPercent in openOrder
|
85
|
+
// 57 = can receive commissionReport message
|
86
|
+
// 58 = can receive CUSIP/ISIN/etc. in contractDescription/bondContractDescription
|
87
|
+
// 59 = can receive evRule, evMultiplier in contractDescription/bondContractDescription/executionDetails
|
88
|
+
// can receive multiplier in executionDetails
|
@@ -0,0 +1,89 @@
|
|
1
|
+
module IB
|
2
|
+
module Messages
|
3
|
+
|
4
|
+
# This is just a basic generic message from the server.
|
5
|
+
#
|
6
|
+
# Class variables:
|
7
|
+
# @message_id - int: message id.
|
8
|
+
# @message_type - Symbol: message type (e.g. :OpenOrderEnd)
|
9
|
+
#
|
10
|
+
# Instance attributes (at least):
|
11
|
+
# @version - int: current version of message format.
|
12
|
+
# @data - Hash of actual data read from a stream.
|
13
|
+
class AbstractMessage
|
14
|
+
|
15
|
+
# Class methods
|
16
|
+
def self.data_map # Map for converting between structured message and raw data
|
17
|
+
@data_map ||= []
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.version # Per class, minimum message version supported
|
21
|
+
@version || 1
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.message_id
|
25
|
+
@message_id
|
26
|
+
end
|
27
|
+
|
28
|
+
# Returns message type Symbol (e.g. :OpenOrderEnd)
|
29
|
+
def self.message_type
|
30
|
+
to_s.split(/::/).last.to_sym
|
31
|
+
end
|
32
|
+
|
33
|
+
def message_id
|
34
|
+
self.class.message_id
|
35
|
+
end
|
36
|
+
|
37
|
+
def message_type
|
38
|
+
self.class.message_type
|
39
|
+
end
|
40
|
+
|
41
|
+
attr_accessor :created_at, :data
|
42
|
+
|
43
|
+
def to_human
|
44
|
+
"<#{self.message_type}:" +
|
45
|
+
@data.map do |key, value|
|
46
|
+
unless [:version].include?(key)
|
47
|
+
" #{key} #{ value.is_a?(Hash) ? value.inspect : value}"
|
48
|
+
end
|
49
|
+
end.compact.join(',') + " >"
|
50
|
+
end
|
51
|
+
|
52
|
+
end # class AbstractMessage
|
53
|
+
|
54
|
+
# Macro that defines short message classes using a one-liner.
|
55
|
+
# First arg is either a [message_id, version] pair or just message_id (version 1)
|
56
|
+
# data_map contains instructions for processing @data Hash. Format:
|
57
|
+
# Incoming messages: [field, type] or [group, field, type]
|
58
|
+
# Outgoing messages: field, [field, default] or [field, method, [args]]
|
59
|
+
def def_message message_id_version, *data_map, &to_human
|
60
|
+
base = data_map.first.is_a?(Class) ? data_map.shift : self::AbstractMessage
|
61
|
+
message_id, version = message_id_version
|
62
|
+
|
63
|
+
# Define new message class
|
64
|
+
message_class = Class.new(base) do
|
65
|
+
@message_id, @version = message_id, version || 1
|
66
|
+
@data_map = data_map
|
67
|
+
|
68
|
+
@data_map.each do |(name, _, type_args)|
|
69
|
+
# Avoid redefining existing accessor methods
|
70
|
+
unless instance_methods.include?(name.to_s) || instance_methods.include?(name.to_sym)
|
71
|
+
if type_args.is_a?(Symbol) # This is Incoming with [group, field, type]
|
72
|
+
attr_reader name
|
73
|
+
else
|
74
|
+
define_method(name) { @data[name] }
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
define_method(:to_human, &to_human) if to_human
|
80
|
+
end
|
81
|
+
|
82
|
+
# Add defined message class to Classes Hash keyed by its message_id
|
83
|
+
self::Classes[message_id] = message_class
|
84
|
+
|
85
|
+
message_class
|
86
|
+
end
|
87
|
+
|
88
|
+
end # module Messages
|
89
|
+
end # module IB
|
@@ -0,0 +1,134 @@
|
|
1
|
+
require 'ib/messages/incoming/abstract_message'
|
2
|
+
|
3
|
+
# EClientSocket.java uses sendMax() rather than send() for a number of these.
|
4
|
+
# It sends an EOL rather than a number if the value == Integer.MAX_VALUE (or Double.MAX_VALUE).
|
5
|
+
# These fields are initialized to this MAX_VALUE.
|
6
|
+
# This has been implemented with nils in Ruby to represent the case where an EOL should be sent.
|
7
|
+
|
8
|
+
# TODO: Don't instantiate messages, use their classes as just namespace for .encode/decode
|
9
|
+
# TODO: realize Message#fire method that raises EWrapper events
|
10
|
+
|
11
|
+
module IB
|
12
|
+
module Messages
|
13
|
+
|
14
|
+
# Incoming IB messages (received from TWS/Gateway)
|
15
|
+
module Incoming
|
16
|
+
extend Messages # def_message macros
|
17
|
+
|
18
|
+
### Define short message classes in-line:
|
19
|
+
|
20
|
+
AccountValue = def_message([6, 2], [:key, :string],
|
21
|
+
[:value, :string],
|
22
|
+
[:currency, :string],
|
23
|
+
[:account_name, :string]) do
|
24
|
+
"<AccountValue: #{account_name}, #{key}=#{value} #{currency}>"
|
25
|
+
end
|
26
|
+
|
27
|
+
AccountUpdateTime = def_message 8, [:time_stamp, :string]
|
28
|
+
|
29
|
+
NewsBulletins =
|
30
|
+
def_message 14, [:request_id, :int], # unique incrementing bulletin ID.
|
31
|
+
[:type, :int], # Type of bulletin. Valid values include:
|
32
|
+
# 1 = Regular news bulletin
|
33
|
+
# 2 = Exchange no longer available for trading
|
34
|
+
# 3 = Exchange is available for trading
|
35
|
+
[:text, :string], # The bulletin's message text.
|
36
|
+
[:exchange, :string] # Exchange from which this message originated.
|
37
|
+
|
38
|
+
ManagedAccounts =
|
39
|
+
def_message 15, [:accounts_list, :string]
|
40
|
+
|
41
|
+
# Receives previously requested FA configuration information from TWS.
|
42
|
+
ReceiveFA =
|
43
|
+
def_message 16, [:type, :int], # type of Financial Advisor configuration data
|
44
|
+
# being received from TWS. Valid values include:
|
45
|
+
# 1 = GROUPS, 2 = PROFILE, 3 = ACCOUNT ALIASES
|
46
|
+
[:xml, :string] # XML string with requested FA configuration information.
|
47
|
+
|
48
|
+
# Receives an XML document that describes the valid parameters that a scanner
|
49
|
+
# subscription can have (for outgoing RequestScannerSubscription message).
|
50
|
+
ScannerParameters = def_message 19, [:xml, :string]
|
51
|
+
|
52
|
+
# Receives the current system time on the server side.
|
53
|
+
CurrentTime = def_message 49, [:time, :int] # long!
|
54
|
+
|
55
|
+
# Receive Reuters global fundamental market data. There must be a subscription to
|
56
|
+
# Reuters Fundamental set up in Account Management before you can receive this data.
|
57
|
+
FundamentalData = def_message 51, [:request_id, :int], [:xml, :string]
|
58
|
+
|
59
|
+
ContractDataEnd = def_message 52, [:request_id, :int]
|
60
|
+
|
61
|
+
OpenOrderEnd = def_message 53
|
62
|
+
|
63
|
+
AccountDownloadEnd = def_message 54, [:account_name, :string]
|
64
|
+
|
65
|
+
ExecutionDataEnd = def_message 55, [:request_id, :int]
|
66
|
+
|
67
|
+
MarketDataType = def_message 58, [:request_id, :int], [:market_data_type, :int]
|
68
|
+
|
69
|
+
CommissionReport =
|
70
|
+
def_message 59, [:exec_id, :string],
|
71
|
+
[:commission, :decimal], # Commission amount.
|
72
|
+
[:currency, :string], # Commission currency
|
73
|
+
[:realized_pnl, :decimal_max],
|
74
|
+
[:yield, :decimal_max],
|
75
|
+
[:yield_redemption_date, :int] # YYYYMMDD format
|
76
|
+
|
77
|
+
### Require standalone source files for more complex message classes:
|
78
|
+
|
79
|
+
require 'ib/messages/incoming/alert'
|
80
|
+
require 'ib/messages/incoming/contract_data'
|
81
|
+
require 'ib/messages/incoming/delta_neutral_validation'
|
82
|
+
require 'ib/messages/incoming/execution_data'
|
83
|
+
require 'ib/messages/incoming/historical_data'
|
84
|
+
require 'ib/messages/incoming/market_depths'
|
85
|
+
require 'ib/messages/incoming/next_valid_id'
|
86
|
+
require 'ib/messages/incoming/open_order'
|
87
|
+
require 'ib/messages/incoming/order_status'
|
88
|
+
require 'ib/messages/incoming/portfolio_value'
|
89
|
+
require 'ib/messages/incoming/real_time_bar'
|
90
|
+
require 'ib/messages/incoming/scanner_data'
|
91
|
+
require 'ib/messages/incoming/ticks'
|
92
|
+
|
93
|
+
end # module Incoming
|
94
|
+
end # module Messages
|
95
|
+
end # module IB
|
96
|
+
|
97
|
+
|
98
|
+
__END__
|
99
|
+
// incoming msg id's
|
100
|
+
static final int TICK_PRICE = 1; *
|
101
|
+
static final int TICK_SIZE = 2; *
|
102
|
+
static final int ORDER_STATUS = 3; *
|
103
|
+
static final int ERR_MSG = 4; *
|
104
|
+
static final int OPEN_ORDER = 5; *
|
105
|
+
static final int ACCT_VALUE = 6; *
|
106
|
+
static final int PORTFOLIO_VALUE = 7; *
|
107
|
+
static final int ACCT_UPDATE_TIME = 8; *
|
108
|
+
static final int NEXT_VALID_ID = 9; *
|
109
|
+
static final int CONTRACT_DATA = 10; *
|
110
|
+
static final int EXECUTION_DATA = 11; ?
|
111
|
+
static final int MARKET_DEPTH = 12; *
|
112
|
+
static final int MARKET_DEPTH_L2 = 13; *
|
113
|
+
static final int NEWS_BULLETINS = 14; *
|
114
|
+
static final int MANAGED_ACCTS = 15; *
|
115
|
+
static final int RECEIVE_FA = 16; *
|
116
|
+
static final int HISTORICAL_DATA = 17; *
|
117
|
+
static final int BOND_CONTRACT_DATA = 18; *
|
118
|
+
static final int SCANNER_PARAMETERS = 19; *
|
119
|
+
static final int SCANNER_DATA = 20; *
|
120
|
+
static final int TICK_OPTION_COMPUTATION = 21; *
|
121
|
+
static final int TICK_GENERIC = 45; *
|
122
|
+
static final int TICK_STRING = 46; *
|
123
|
+
static final int TICK_EFP = 47; *
|
124
|
+
static final int CURRENT_TIME = 49; *
|
125
|
+
static final int REAL_TIME_BARS = 50; *
|
126
|
+
static final int FUNDAMENTAL_DATA = 51; *
|
127
|
+
static final int CONTRACT_DATA_END = 52; *
|
128
|
+
static final int OPEN_ORDER_END = 53; *
|
129
|
+
static final int ACCT_DOWNLOAD_END = 54; *
|
130
|
+
static final int EXECUTION_DATA_END = 55; *
|
131
|
+
static final int DELTA_NEUTRAL_VALIDATION = 56; *
|
132
|
+
static final int TICK_SNAPSHOT_END = 57; *
|
133
|
+
static final int MARKET_DATA_TYPE = 58; ?
|
134
|
+
static final int COMMISSION_REPORT = 59; ?
|
@@ -0,0 +1,99 @@
|
|
1
|
+
require 'ib/messages/abstract_message'
|
2
|
+
|
3
|
+
module IB
|
4
|
+
module Messages
|
5
|
+
module Incoming
|
6
|
+
|
7
|
+
# Container for specific message classes, keyed by their message_ids
|
8
|
+
Classes = {}
|
9
|
+
|
10
|
+
class AbstractMessage < IB::Messages::AbstractMessage
|
11
|
+
|
12
|
+
attr_accessor :socket
|
13
|
+
|
14
|
+
def version # Per message, received messages may have the different versions
|
15
|
+
@data[:version]
|
16
|
+
end
|
17
|
+
|
18
|
+
def check_version actual, expected
|
19
|
+
unless actual == expected || expected.is_a?(Array) && expected.include?(actual)
|
20
|
+
error "Unsupported version #{actual} received, expected #{expected}"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
# Create incoming message from a given source (IB Socket or data Hash)
|
25
|
+
def initialize source
|
26
|
+
@created_at = Time.now
|
27
|
+
if source.is_a?(Hash) # Source is a @data Hash
|
28
|
+
@data = source
|
29
|
+
else # Source is a Socket
|
30
|
+
@socket = source
|
31
|
+
@data = Hash.new
|
32
|
+
self.load
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
# Every message loads received message version first
|
37
|
+
# Override the load method in your subclass to do actual reading into @data.
|
38
|
+
def load
|
39
|
+
if socket
|
40
|
+
@data[:version] = socket.read_int
|
41
|
+
|
42
|
+
check_version @data[:version], self.class.version
|
43
|
+
|
44
|
+
load_map *self.class.data_map
|
45
|
+
else
|
46
|
+
raise "Unable to load, no socket"
|
47
|
+
end
|
48
|
+
|
49
|
+
rescue => e
|
50
|
+
error "Reading #{self.class}: #{e.class}: #{e.message}", :load, e.backtrace
|
51
|
+
end
|
52
|
+
|
53
|
+
# Load @data from the socket according to the given data map.
|
54
|
+
#
|
55
|
+
# map is a series of Arrays in the format of
|
56
|
+
# [ :name, :type ], [ :group, :name, :type]
|
57
|
+
# type identifiers must have a corresponding read_type method on socket (read_int, etc.).
|
58
|
+
# group is used to lump together aggregates, such as Contract or Order fields
|
59
|
+
def load_map(*map)
|
60
|
+
map.each do |instruction|
|
61
|
+
# We determine the function of the first element
|
62
|
+
head = instruction.first
|
63
|
+
case head
|
64
|
+
when Integer # >= Version condition: [ min_version, [map]]
|
65
|
+
load_map *instruction.drop(1) if version >= head
|
66
|
+
|
67
|
+
when Proc # Callable condition: [ condition, [map]]
|
68
|
+
load_map *instruction.drop(1) if head.call
|
69
|
+
|
70
|
+
when true # Pre-condition already succeeded!
|
71
|
+
load_map *instruction.drop(1)
|
72
|
+
|
73
|
+
when nil, false # Pre-condition already failed! Do nothing...
|
74
|
+
|
75
|
+
when Symbol # Normal map
|
76
|
+
group, name, type, block =
|
77
|
+
if instruction[2].nil? || instruction[2].is_a?(Proc)
|
78
|
+
[nil] + instruction # No group, [ :name, :type, (:block) ]
|
79
|
+
else
|
80
|
+
instruction # [ :group, :name, :type, (:block)]
|
81
|
+
end
|
82
|
+
|
83
|
+
data = socket.__send__("read_#{type}", &block)
|
84
|
+
if group
|
85
|
+
@data[group] ||= {}
|
86
|
+
@data[group][name] = data
|
87
|
+
else
|
88
|
+
@data[name] = data
|
89
|
+
end
|
90
|
+
else
|
91
|
+
error "Unrecognized instruction #{instruction}"
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
end # class AbstractMessage
|
97
|
+
end # module Incoming
|
98
|
+
end # module Messages
|
99
|
+
end # module IB
|