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.
Files changed (55) hide show
  1. checksums.yaml +7 -0
  2. data/lib/ib-api.rb +10 -0
  3. data/lib/ib/base.rb +99 -0
  4. data/lib/ib/base_properties.rb +154 -0
  5. data/lib/ib/connection.rb +327 -0
  6. data/lib/ib/constants.rb +334 -0
  7. data/lib/ib/db.rb +29 -0
  8. data/lib/ib/engine.rb +35 -0
  9. data/lib/ib/errors.rb +40 -0
  10. data/lib/ib/extensions.rb +72 -0
  11. data/lib/ib/flex.rb +106 -0
  12. data/lib/ib/logger.rb +25 -0
  13. data/lib/ib/messages.rb +88 -0
  14. data/lib/ib/messages/abstract_message.rb +89 -0
  15. data/lib/ib/messages/incoming.rb +134 -0
  16. data/lib/ib/messages/incoming/abstract_message.rb +99 -0
  17. data/lib/ib/messages/incoming/alert.rb +34 -0
  18. data/lib/ib/messages/incoming/contract_data.rb +102 -0
  19. data/lib/ib/messages/incoming/delta_neutral_validation.rb +23 -0
  20. data/lib/ib/messages/incoming/execution_data.rb +54 -0
  21. data/lib/ib/messages/incoming/historical_data.rb +55 -0
  22. data/lib/ib/messages/incoming/market_depths.rb +44 -0
  23. data/lib/ib/messages/incoming/next_valid_id.rb +18 -0
  24. data/lib/ib/messages/incoming/open_order.rb +232 -0
  25. data/lib/ib/messages/incoming/order_status.rb +81 -0
  26. data/lib/ib/messages/incoming/portfolio_value.rb +39 -0
  27. data/lib/ib/messages/incoming/real_time_bar.rb +32 -0
  28. data/lib/ib/messages/incoming/scanner_data.rb +53 -0
  29. data/lib/ib/messages/incoming/ticks.rb +131 -0
  30. data/lib/ib/messages/outgoing.rb +331 -0
  31. data/lib/ib/messages/outgoing/abstract_message.rb +73 -0
  32. data/lib/ib/messages/outgoing/bar_requests.rb +189 -0
  33. data/lib/ib/messages/outgoing/place_order.rb +141 -0
  34. data/lib/ib/model.rb +6 -0
  35. data/lib/ib/models.rb +10 -0
  36. data/lib/ib/requires.rb +9 -0
  37. data/lib/ib/socket.rb +81 -0
  38. data/lib/ib/symbols.rb +35 -0
  39. data/lib/ib/symbols/bonds.rb +28 -0
  40. data/lib/ib/symbols/forex.rb +41 -0
  41. data/lib/ib/symbols/futures.rb +117 -0
  42. data/lib/ib/symbols/options.rb +39 -0
  43. data/lib/ib/symbols/stocks.rb +37 -0
  44. data/lib/ib/version.rb +6 -0
  45. data/lib/models/ib/bag.rb +51 -0
  46. data/lib/models/ib/bar.rb +45 -0
  47. data/lib/models/ib/combo_leg.rb +103 -0
  48. data/lib/models/ib/contract.rb +292 -0
  49. data/lib/models/ib/contract_detail.rb +89 -0
  50. data/lib/models/ib/execution.rb +65 -0
  51. data/lib/models/ib/option.rb +60 -0
  52. data/lib/models/ib/order.rb +391 -0
  53. data/lib/models/ib/order_state.rb +128 -0
  54. data/lib/models/ib/underlying.rb +34 -0
  55. 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
@@ -0,0 +1,6 @@
1
+ require 'ib/base_properties'
2
+ require 'ib/base'
3
+
4
+ module IB
5
+ Model = IB::Base
6
+ end
@@ -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'
@@ -0,0 +1,9 @@
1
+ require 'ib/version'
2
+ require 'ib/extensions'
3
+ require 'ib/errors'
4
+ require 'ib/constants'
5
+ require 'ib/connection'
6
+ require 'ib/flex'
7
+ require 'ib/models'
8
+ require 'ib/messages'
9
+ require 'ib/symbols'
@@ -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