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
@@ -0,0 +1,73 @@
|
|
1
|
+
require 'ib/messages/abstract_message'
|
2
|
+
|
3
|
+
module IB
|
4
|
+
module Messages
|
5
|
+
module Outgoing
|
6
|
+
|
7
|
+
# Container for specific message classes, keyed by their message_ids
|
8
|
+
Classes = {}
|
9
|
+
|
10
|
+
class AbstractMessage < IB::Messages::AbstractMessage
|
11
|
+
|
12
|
+
def initialize data={}
|
13
|
+
@data = data
|
14
|
+
@created_at = Time.now
|
15
|
+
end
|
16
|
+
|
17
|
+
# This causes the message to send itself over the server socket in server[:socket].
|
18
|
+
# "server" is the @server instance variable from the IB object.
|
19
|
+
# You can also use this to e.g. get the server version number.
|
20
|
+
#
|
21
|
+
# Subclasses can either override this method for precise control over how
|
22
|
+
# stuff gets sent to the server, or else define a method encode() that returns
|
23
|
+
# an Array of elements that ought to be sent to the server by calling to_s on
|
24
|
+
# each one and postpending a '\0'.
|
25
|
+
#
|
26
|
+
def send_to socket
|
27
|
+
self.preprocess.each {|data| socket.write_data data}
|
28
|
+
end
|
29
|
+
|
30
|
+
# Same message representation as logged by TWS into API messages log file
|
31
|
+
def to_s
|
32
|
+
self.preprocess.join('-')
|
33
|
+
end
|
34
|
+
|
35
|
+
# Pre-process encoded message Array before sending into socket, such as
|
36
|
+
# changing booleans into 0/1 and stuff
|
37
|
+
def preprocess
|
38
|
+
self.encode.flatten.map {|data| data == true ? 1 : data == false ? 0 : data }
|
39
|
+
end
|
40
|
+
|
41
|
+
# Encode message content into (possibly, nested) Array of values.
|
42
|
+
# At minimum, encoded Outgoing message contains message_id and version.
|
43
|
+
# Most messages also contain (ticker, request or order) :id.
|
44
|
+
# Then, content of @data Hash is encoded per instructions in data_map.
|
45
|
+
# This method may be modified by message subclasses!
|
46
|
+
def encode
|
47
|
+
[self.class.message_id,
|
48
|
+
self.class.version,
|
49
|
+
@data[:id] || @data[:ticker_id] || @data[:request_id] ||
|
50
|
+
@data[:local_id] || @data[:order_id] || [],
|
51
|
+
self.class.data_map.map do |(field, default_method, args)|
|
52
|
+
case
|
53
|
+
when default_method.nil?
|
54
|
+
@data[field]
|
55
|
+
|
56
|
+
when default_method.is_a?(Symbol) # method name with args
|
57
|
+
@data[field].send default_method, *args
|
58
|
+
|
59
|
+
when default_method.respond_to?(:call) # callable with args
|
60
|
+
default_method.call @data[field], *args
|
61
|
+
|
62
|
+
else # default
|
63
|
+
@data[field].nil? ? default_method : @data[field] # may be false still
|
64
|
+
end
|
65
|
+
end
|
66
|
+
]
|
67
|
+
# TWS wants to receive booleans as 1 or 0
|
68
|
+
end
|
69
|
+
|
70
|
+
end # AbstractMessage
|
71
|
+
end # module Outgoing
|
72
|
+
end # module Messages
|
73
|
+
end # module IB
|
@@ -0,0 +1,189 @@
|
|
1
|
+
module IB
|
2
|
+
module Messages
|
3
|
+
module Outgoing
|
4
|
+
|
5
|
+
# Messages that request bar data have special processing of @data
|
6
|
+
|
7
|
+
class BarRequestMessage < AbstractMessage
|
8
|
+
# Preprocessor for some data fields
|
9
|
+
def parse data
|
10
|
+
type = data[:data_type] || data[:what_to_show]
|
11
|
+
data_type = DATA_TYPES.invert[type] || type
|
12
|
+
unless DATA_TYPES.keys.include?(data_type)
|
13
|
+
error ":data_type must be one of #{DATA_TYPES.inspect}", :args
|
14
|
+
end
|
15
|
+
|
16
|
+
#size = data[:bar_size] || data[:size]
|
17
|
+
#bar_size = BAR_SIZES.invert[size] || size
|
18
|
+
# unless BAR_SIZES.keys.include?(bar_size)
|
19
|
+
# error ":bar_size must be one of #{BAR_SIZES.inspect}", :args
|
20
|
+
# end
|
21
|
+
|
22
|
+
contract = data[:contract].is_a?(IB::Contract) ?
|
23
|
+
data[:contract] : IB::Contract.from_ib_ruby(data[:contract])
|
24
|
+
|
25
|
+
[data_type, nil, contract]
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
# data = { :id => ticker_id (int),
|
30
|
+
# :contract => Contract ,
|
31
|
+
# :bar_size => int/Symbol? Currently only 5 second bars are supported,
|
32
|
+
# if any other value is used, an exception will be thrown.,
|
33
|
+
# :data_type => Symbol: Determines the nature of data being extracted.
|
34
|
+
# :trades, :midpoint, :bid, :ask, :bid_ask,
|
35
|
+
# :historical_volatility, :option_implied_volatility,
|
36
|
+
# :option_volume, :option_open_interest
|
37
|
+
# - converts to "TRADES," "MIDPOINT," "BID," etc...
|
38
|
+
# :use_rth => int: 0 - all data available during the time span requested
|
39
|
+
# is returned, even data bars covering time intervals where the
|
40
|
+
# market in question was illiquid. 1 - only data within the
|
41
|
+
# "Regular Trading Hours" of the product in question is returned,
|
42
|
+
# even if the time span requested falls partially or completely
|
43
|
+
# outside of them.
|
44
|
+
RequestRealTimeBars = def_message 50, BarRequestMessage
|
45
|
+
|
46
|
+
class RequestRealTimeBars
|
47
|
+
def parse data
|
48
|
+
data_type, bar_size, contract = super data
|
49
|
+
|
50
|
+
size = data[:bar_size] || data[:size]
|
51
|
+
bar_size = size.to_i
|
52
|
+
[data_type, bar_size, contract]
|
53
|
+
end
|
54
|
+
|
55
|
+
def encode
|
56
|
+
data_type, bar_size, contract = parse @data
|
57
|
+
|
58
|
+
[super,
|
59
|
+
contract.serialize_long,
|
60
|
+
bar_size,
|
61
|
+
data_type.to_s.upcase,
|
62
|
+
@data[:use_rth]]
|
63
|
+
end
|
64
|
+
end # RequestRealTimeBars
|
65
|
+
|
66
|
+
# data = { :id => int: Ticker id, needs to be different than the reqMktData ticker
|
67
|
+
# id. If you use the same ticker ID you used for the symbol when
|
68
|
+
# you did ReqMktData, nothing comes back for the historical data call
|
69
|
+
# :contract => Contract: requested ticker description
|
70
|
+
# :end_date_time => String: "yyyymmdd HH:mm:ss", with optional time zone
|
71
|
+
# allowed after a space: "20050701 18:26:44 GMT"
|
72
|
+
# :duration => String, time span the request will cover, and is specified
|
73
|
+
# using the format: <integer> <unit>, eg: '1 D', valid units are:
|
74
|
+
# '1 S' (seconds, default if no unit is specified)
|
75
|
+
# '1 D' (days)
|
76
|
+
# '1 W' (weeks)
|
77
|
+
# '1 M' (months)
|
78
|
+
# '1 Y' (years, currently limited to one)
|
79
|
+
# :bar_size => String: Specifies the size of the bars that will be returned
|
80
|
+
# (within IB/TWS limits). Valid values include:
|
81
|
+
# '1 sec'
|
82
|
+
# '5 secs'
|
83
|
+
# '15 secs'
|
84
|
+
# '30 secs'
|
85
|
+
# '1 min'
|
86
|
+
# '2 mins'
|
87
|
+
# '3 mins'
|
88
|
+
# '5 mins'
|
89
|
+
# '15 mins'
|
90
|
+
# '30 min'
|
91
|
+
# '1 hour'
|
92
|
+
# '1 day'
|
93
|
+
# :what_to_show => Symbol: Determines the nature of data being extracted.
|
94
|
+
# Valid values:
|
95
|
+
# :trades, :midpoint, :bid, :ask, :bid_ask,
|
96
|
+
# :historical_volatility, :option_implied_volatility,
|
97
|
+
# :option_volume, :option_open_interest
|
98
|
+
# - converts to "TRADES," "MIDPOINT," "BID," etc...
|
99
|
+
# :use_rth => int: 0 - all data available during the time span requested
|
100
|
+
# is returned, even data bars covering time intervals where the
|
101
|
+
# market in question was illiquid. 1 - only data within the
|
102
|
+
# "Regular Trading Hours" of the product in question is returned,
|
103
|
+
# even if the time span requested falls partially or completely
|
104
|
+
# outside of them.
|
105
|
+
# :format_date => int: 1 - text format, like "20050307 11:32:16".
|
106
|
+
# 2 - offset from 1970-01-01 in sec (UNIX epoch)
|
107
|
+
# }
|
108
|
+
#
|
109
|
+
# NB: using the D :duration only returns bars in whole days, so requesting "1 D"
|
110
|
+
# for contract ending at 08:05 will only return 1 bar, for 08:00 on that day.
|
111
|
+
# But requesting "86400 S" gives 86400/barlengthsecs bars before the end Time.
|
112
|
+
#
|
113
|
+
# Note also that the :duration for any request must be such that the start Time is not
|
114
|
+
# more than one year before the CURRENT-Time-less-one-day (not 1 year before the end
|
115
|
+
# Time in the Request)
|
116
|
+
#
|
117
|
+
# Bar Size Max Duration
|
118
|
+
# -------- ------------
|
119
|
+
# 1 sec 2000 S
|
120
|
+
# 5 sec 10000 S
|
121
|
+
# 15 sec 30000 S
|
122
|
+
# 30 sec 86400 S
|
123
|
+
# 1 minute 86400 S, 6 D
|
124
|
+
# 2 minutes 86400 S, 6 D
|
125
|
+
# 5 minutes 86400 S, 6 D
|
126
|
+
# 15 minutes 86400 S, 6 D, 20 D, 2 W
|
127
|
+
# 30 minutes 86400 S, 34 D, 4 W, 1 M
|
128
|
+
# 1 hour 86400 S, 34 D, 4 w, 1 M
|
129
|
+
# 1 day 60 D, 12 M, 52 W, 1 Y
|
130
|
+
#
|
131
|
+
# NB: as of 4/07 there is no historical data available for forex spot.
|
132
|
+
#
|
133
|
+
# data[:contract] may either be a Contract object or a String. A String should be
|
134
|
+
# in serialize_ib_ruby format; that is, it should be a colon-delimited string in
|
135
|
+
# the format (e.g. for Globex British pound futures contract expiring in Sep-2008):
|
136
|
+
#
|
137
|
+
# symbol:security_type:expiry:strike:right:multiplier:exchange:primary_exchange:currency:local_symbol
|
138
|
+
# GBP:FUT:200809:::62500:GLOBEX::USD:
|
139
|
+
#
|
140
|
+
# Fields not needed for a particular security should be left blank (e.g. strike
|
141
|
+
# and right are only relevant for options.)
|
142
|
+
#
|
143
|
+
# A Contract object will be automatically serialized into the required format.
|
144
|
+
#
|
145
|
+
# See also http://chuckcaplan.com/twsapi/index.php/void%20reqIntradayData%28%29
|
146
|
+
# for general information about how TWS handles historic data requests, whence
|
147
|
+
# the following has been adapted:
|
148
|
+
#
|
149
|
+
# The server providing historical prices appears to not always be
|
150
|
+
# available outside of market hours. If you call it outside of its
|
151
|
+
# supported time period, or if there is otherwise a problem with
|
152
|
+
# it, you will receive error #162 "Historical Market Data Service
|
153
|
+
# query failed.:HMDS query returned no data."
|
154
|
+
#
|
155
|
+
# For backfill on futures data, you may need to leave the Primary
|
156
|
+
# Exchange field of the Contract structure blank; see
|
157
|
+
# http://www.interactivebrokers.com/discus/messages/2/28477.html?1114646754
|
158
|
+
RequestHistoricalData = def_message [20, 4], BarRequestMessage
|
159
|
+
|
160
|
+
class RequestHistoricalData
|
161
|
+
def parse data
|
162
|
+
data_type, bar_size, contract = super data
|
163
|
+
|
164
|
+
size = data[:bar_size] || data[:size]
|
165
|
+
bar_size = BAR_SIZES.invert[size] || size
|
166
|
+
unless BAR_SIZES.keys.include?(bar_size)
|
167
|
+
error ":bar_size must be one of #{BAR_SIZES.inspect}", :args
|
168
|
+
end
|
169
|
+
[data_type, bar_size, contract]
|
170
|
+
end
|
171
|
+
|
172
|
+
def encode
|
173
|
+
data_type, bar_size, contract = parse @data
|
174
|
+
|
175
|
+
[super,
|
176
|
+
contract.serialize_long(:include_expired),
|
177
|
+
@data[:end_date_time],
|
178
|
+
bar_size,
|
179
|
+
@data[:duration],
|
180
|
+
@data[:use_rth],
|
181
|
+
data_type.to_s.upcase,
|
182
|
+
@data[:format_date],
|
183
|
+
contract.serialize_legs]
|
184
|
+
end
|
185
|
+
end # RequestHistoricalData
|
186
|
+
|
187
|
+
end # module Outgoing
|
188
|
+
end # module Messages
|
189
|
+
end # module IB
|
@@ -0,0 +1,141 @@
|
|
1
|
+
module IB
|
2
|
+
module Messages
|
3
|
+
module Outgoing
|
4
|
+
|
5
|
+
# Data format is { :id => int: local_id,
|
6
|
+
# :contract => Contract,
|
7
|
+
# :order => Order }
|
8
|
+
PlaceOrder = def_message [3, 38]
|
9
|
+
|
10
|
+
class PlaceOrder
|
11
|
+
|
12
|
+
def encode
|
13
|
+
|
14
|
+
order = @data[:order]
|
15
|
+
contract = @data[:contract]
|
16
|
+
|
17
|
+
[super,
|
18
|
+
|
19
|
+
contract.serialize_long(:con_id, :sec_id),
|
20
|
+
|
21
|
+
# main order fields
|
22
|
+
(order.side == :short ? 'SSHORT' : order.side == :short_exempt ? 'SSHORTX' : order.side.to_sup),
|
23
|
+
order.quantity,
|
24
|
+
order[:order_type], # Internal code, 'LMT' instead of :limit
|
25
|
+
order.limit_price,
|
26
|
+
order.aux_price,
|
27
|
+
order[:tif],
|
28
|
+
order.oca_group,
|
29
|
+
order.account,
|
30
|
+
order.open_close.to_sup[0..0],
|
31
|
+
order[:origin],
|
32
|
+
order.order_ref,
|
33
|
+
order.transmit,
|
34
|
+
order.parent_id,
|
35
|
+
order.block_order || false,
|
36
|
+
order.sweep_to_fill || false,
|
37
|
+
order.display_size,
|
38
|
+
order[:trigger_method],
|
39
|
+
order.outside_rth || false, # was: ignore_rth
|
40
|
+
order.hidden || false,
|
41
|
+
contract.serialize_legs(:extended),
|
42
|
+
|
43
|
+
|
44
|
+
if contract.bag?
|
45
|
+
[
|
46
|
+
## Support for per-leg prices in Order
|
47
|
+
[contract.legs.size] + contract.legs.map { |_| nil },
|
48
|
+
## Support for combo routing params in Order
|
49
|
+
order.combo_params.empty? ? 0 : [order.combo_params.size] + order.combo_params.to_a
|
50
|
+
]
|
51
|
+
else
|
52
|
+
[]
|
53
|
+
end,
|
54
|
+
|
55
|
+
'', # deprecated shares_allocation field
|
56
|
+
order.discretionary_amount,
|
57
|
+
order.good_after_time,
|
58
|
+
order.good_till_date,
|
59
|
+
order.fa_group,
|
60
|
+
order.fa_method,
|
61
|
+
order.fa_percentage,
|
62
|
+
order.fa_profile,
|
63
|
+
order[:short_sale_slot], # 0 only for retail, 1 or 2 for institution (Institutional)
|
64
|
+
order.designated_location, # only populate when short_sale_slot == 2 (Institutional)
|
65
|
+
order.exempt_code,
|
66
|
+
order[:oca_type],
|
67
|
+
order[:rule_80a], #.to_sup[0..0],
|
68
|
+
order.settling_firm,
|
69
|
+
order.all_or_none || false,
|
70
|
+
order.min_quantity,
|
71
|
+
order.percent_offset,
|
72
|
+
order.etrade_only || false,
|
73
|
+
order.firm_quote_only || false,
|
74
|
+
order.nbbo_price_cap,
|
75
|
+
order[:auction_strategy],
|
76
|
+
order.starting_price,
|
77
|
+
order.stock_ref_price,
|
78
|
+
order.delta,
|
79
|
+
order.stock_range_lower,
|
80
|
+
order.stock_range_upper,
|
81
|
+
order.override_percentage_constraints || false,
|
82
|
+
order.volatility, # Volatility orders
|
83
|
+
order[:volatility_type], # Volatility orders
|
84
|
+
|
85
|
+
# Support for delta neutral orders with parameters
|
86
|
+
if order.delta_neutral_order_type && order.delta_neutral_order_type != :none
|
87
|
+
[order[:delta_neutral_order_type],
|
88
|
+
order.delta_neutral_aux_price,
|
89
|
+
order.delta_neutral_con_id,
|
90
|
+
order.delta_neutral_settling_firm,
|
91
|
+
order.delta_neutral_clearing_account,
|
92
|
+
order[:delta_neutral_clearing_intent]
|
93
|
+
]
|
94
|
+
else
|
95
|
+
['', '']
|
96
|
+
end,
|
97
|
+
|
98
|
+
order.continuous_update, # Volatility orders
|
99
|
+
order[:reference_price_type], # Volatility orders
|
100
|
+
|
101
|
+
order.trail_stop_price, # TRAIL_STOP_LIMIT stop price
|
102
|
+
order.trailing_percent, # Support for trailing percent
|
103
|
+
|
104
|
+
order.scale_init_level_size, # Scale Orders
|
105
|
+
order.scale_subs_level_size, # Scale Orders
|
106
|
+
order.scale_price_increment, # Scale Orders
|
107
|
+
|
108
|
+
# Support for extended scale orders parameters
|
109
|
+
if order.scale_price_increment && order.scale_price_increment > 0
|
110
|
+
[order.scale_price_adjust_value,
|
111
|
+
order.scale_price_adjust_interval,
|
112
|
+
order.scale_profit_offset,
|
113
|
+
order.scale_auto_reset || false,
|
114
|
+
order.scale_init_position,
|
115
|
+
order.scale_init_fill_qty,
|
116
|
+
order.scale_random_percent || false
|
117
|
+
]
|
118
|
+
else
|
119
|
+
[]
|
120
|
+
end,
|
121
|
+
|
122
|
+
# Support for hedgeType
|
123
|
+
order.hedge_type, # MIN_SERVER_VER_HEDGE_ORDERS
|
124
|
+
order.hedge_param || [],
|
125
|
+
|
126
|
+
order.opt_out_smart_routing || false, # MIN_SERVER_VER_OPT_OUT_SMART_ROUTING
|
127
|
+
|
128
|
+
order.clearing_account,
|
129
|
+
order.clearing_intent,
|
130
|
+
order.not_held || false,
|
131
|
+
contract.serialize_under_comp,
|
132
|
+
order.serialize_algo(),
|
133
|
+
order.what_if]
|
134
|
+
|
135
|
+
end
|
136
|
+
end # PlaceOrder
|
137
|
+
|
138
|
+
|
139
|
+
end # module Outgoing
|
140
|
+
end # module Messages
|
141
|
+
end # module IB
|
data/lib/ib/model.rb
ADDED
data/lib/ib/models.rb
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
require 'ib/model'
|
2
|
+
|
3
|
+
require 'models/ib/contract'
|
4
|
+
require 'models/ib/contract_detail'
|
5
|
+
require 'models/ib/underlying'
|
6
|
+
require 'models/ib/order_state'
|
7
|
+
require 'models/ib/order'
|
8
|
+
require 'models/ib/combo_leg'
|
9
|
+
require 'models/ib/execution'
|
10
|
+
require 'models/ib/bar'
|
data/lib/ib/requires.rb
ADDED
data/lib/ib/socket.rb
ADDED
@@ -0,0 +1,81 @@
|
|
1
|
+
require 'socket'
|
2
|
+
|
3
|
+
module IB
|
4
|
+
class IBSocket < TCPSocket
|
5
|
+
|
6
|
+
# Sends null terminated data string into socket
|
7
|
+
def write_data data
|
8
|
+
self.syswrite(data.to_s + EOL)
|
9
|
+
end
|
10
|
+
|
11
|
+
def read_string
|
12
|
+
string = self.gets(EOL)
|
13
|
+
|
14
|
+
until string
|
15
|
+
# Silently ignores nils
|
16
|
+
string = self.gets(EOL)
|
17
|
+
sleep 0.1
|
18
|
+
end
|
19
|
+
|
20
|
+
string.chop
|
21
|
+
end
|
22
|
+
|
23
|
+
def read_int
|
24
|
+
self.read_string.to_i
|
25
|
+
end
|
26
|
+
|
27
|
+
def read_int_max
|
28
|
+
str = self.read_string
|
29
|
+
str.to_i unless str.nil? || str.empty?
|
30
|
+
end
|
31
|
+
|
32
|
+
def read_boolean
|
33
|
+
str = self.read_string
|
34
|
+
str.nil? ? false : str.to_i != 0
|
35
|
+
end
|
36
|
+
|
37
|
+
def read_decimal
|
38
|
+
# Floating-point numbers shouldn't be used to store money...
|
39
|
+
# ...but BigDecimals are too unwieldy to use in this case... maybe later
|
40
|
+
# self.read_string.to_d
|
41
|
+
self.read_string.to_f
|
42
|
+
end
|
43
|
+
|
44
|
+
def read_decimal_max
|
45
|
+
str = self.read_string
|
46
|
+
# Floating-point numbers shouldn't be used to store money...
|
47
|
+
# ...but BigDecimals are too unwieldy to use in this case... maybe later
|
48
|
+
# str.nil? || str.empty? ? nil : str.to_d
|
49
|
+
str.to_f unless str.nil? || str.empty? || str.to_f > 1.797 * 10.0 ** 306
|
50
|
+
end
|
51
|
+
|
52
|
+
# If received decimal is below limit ("not yet computed"), return nil
|
53
|
+
def read_decimal_limit limit = -1
|
54
|
+
value = self.read_decimal
|
55
|
+
# limit is the "not yet computed" indicator
|
56
|
+
value <= limit ? nil : value
|
57
|
+
end
|
58
|
+
|
59
|
+
alias read_decimal_limit_1 read_decimal_limit
|
60
|
+
|
61
|
+
def read_decimal_limit_2
|
62
|
+
read_decimal_limit -2
|
63
|
+
end
|
64
|
+
|
65
|
+
### Complex operations
|
66
|
+
|
67
|
+
# Returns loaded Array or [] if count was 0
|
68
|
+
def read_array &block
|
69
|
+
count = read_int
|
70
|
+
count > 0 ? Array.new(count, &block) : []
|
71
|
+
end
|
72
|
+
|
73
|
+
# Returns loaded Hash
|
74
|
+
def read_hash
|
75
|
+
tags = read_array { |_| [read_string, read_string] }
|
76
|
+
tags.empty? ? Hash.new : Hash[*tags.flatten]
|
77
|
+
end
|
78
|
+
|
79
|
+
end # class IBSocket
|
80
|
+
|
81
|
+
end # module IB
|