ib-ruby 0.4.3 → 0.4.20

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 (50) hide show
  1. data/.gitignore +32 -0
  2. data/HISTORY +68 -0
  3. data/README.rdoc +9 -6
  4. data/VERSION +1 -1
  5. data/bin/account_info +29 -0
  6. data/bin/contract_details +37 -0
  7. data/bin/depth_of_market +43 -0
  8. data/bin/historic_data +62 -0
  9. data/bin/{RequestHistoricData → historic_data_cli} +46 -91
  10. data/bin/market_data +49 -0
  11. data/bin/option_data +45 -0
  12. data/bin/template +21 -0
  13. data/bin/time_and_sales +63 -0
  14. data/lib/ib-ruby/connection.rb +166 -0
  15. data/lib/ib-ruby/constants.rb +91 -0
  16. data/lib/ib-ruby/messages/incoming.rb +807 -0
  17. data/lib/ib-ruby/messages/outgoing.rb +573 -0
  18. data/lib/ib-ruby/messages.rb +8 -1445
  19. data/lib/ib-ruby/models/bar.rb +26 -0
  20. data/lib/ib-ruby/models/contract.rb +335 -0
  21. data/lib/ib-ruby/models/execution.rb +55 -0
  22. data/lib/ib-ruby/models/model.rb +20 -0
  23. data/lib/ib-ruby/models/order.rb +262 -0
  24. data/lib/ib-ruby/models.rb +11 -0
  25. data/lib/ib-ruby/socket.rb +50 -0
  26. data/lib/ib-ruby/symbols/forex.rb +32 -72
  27. data/lib/ib-ruby/symbols/futures.rb +47 -68
  28. data/lib/ib-ruby/symbols/options.rb +30 -0
  29. data/lib/ib-ruby/symbols/stocks.rb +23 -0
  30. data/lib/ib-ruby/symbols.rb +9 -0
  31. data/lib/ib-ruby.rb +7 -8
  32. data/lib/legacy/bin/account_info_old +36 -0
  33. data/lib/legacy/bin/historic_data_old +81 -0
  34. data/lib/legacy/bin/market_data_old +68 -0
  35. data/lib/legacy/datatypes.rb +485 -0
  36. data/lib/legacy/ib-ruby.rb +10 -0
  37. data/lib/legacy/ib.rb +226 -0
  38. data/lib/legacy/messages.rb +1458 -0
  39. data/lib/version.rb +2 -3
  40. data/spec/ib-ruby/models/contract_spec.rb +261 -0
  41. data/spec/ib-ruby/models/order_spec.rb +64 -0
  42. data/spec/ib-ruby_spec.rb +0 -131
  43. metadata +106 -76
  44. data/bin/AccountInfo +0 -67
  45. data/bin/HistoricToCSV +0 -111
  46. data/bin/RequestMarketData +0 -78
  47. data/bin/SimpleTimeAndSales +0 -98
  48. data/bin/ib-ruby +0 -8
  49. data/lib/ib-ruby/datatypes.rb +0 -400
  50. data/lib/ib-ruby/ib.rb +0 -242
@@ -0,0 +1,9 @@
1
+ module IB
2
+ module Symbols
3
+ end
4
+ end
5
+
6
+ require 'ib-ruby/symbols/forex'
7
+ require 'ib-ruby/symbols/futures'
8
+ require 'ib-ruby/symbols/stocks'
9
+ require 'ib-ruby/symbols/options'
data/lib/ib-ruby.rb CHANGED
@@ -1,10 +1,9 @@
1
- require 'version'
2
-
3
- module IbRuby
4
- end # module IbRuby
1
+ module IB
2
+ end # module IbRuby
5
3
 
6
- require 'ib-ruby/datatypes'
7
- require 'ib-ruby/ib'
4
+ require 'version'
5
+ require 'ib-ruby/constants'
6
+ require 'ib-ruby/connection'
7
+ require 'ib-ruby/models'
8
+ require 'ib-ruby/symbols'
8
9
  require 'ib-ruby/messages'
9
- require 'ib-ruby/symbols/forex'
10
- require 'ib-ruby/symbols/futures'
@@ -0,0 +1,36 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # This script connects to IB API, subscribes to account info and prints out
4
+ # messages received from IB (update every 3 minute or so)
5
+
6
+ require 'pathname'
7
+ LIB_DIR = (Pathname.new(__FILE__).dirname + '../lib/').realpath.to_s
8
+ $LOAD_PATH.unshift LIB_DIR unless $LOAD_PATH.include?(LIB_DIR)
9
+
10
+ require 'rubygems'
11
+ require 'bundler/setup'
12
+ require 'ib-ruby'
13
+
14
+ # First, connect to IB TWS.
15
+ ib = IB::IB.new
16
+
17
+ # Uncomment this for verbose debug messages:
18
+ # IB::IBLogger.level = Logger::Severity::DEBUG
19
+
20
+ ## Subscribe to the messages that TWS sends in response to account data request
21
+ ib.subscribe(IB::IncomingMessages::AccountValue) { |msg| puts msg.to_human }
22
+
23
+ ib.subscribe(IB::IncomingMessages::PortfolioValue) { |msg| puts msg.to_human }
24
+
25
+ ib.subscribe(IB::IncomingMessages::AccountUpdateTime) { |msg| puts msg.to_human }
26
+
27
+ ib.dispatch(IB::OutgoingMessages::RequestAccountData.new(:subscribe => true,
28
+ :account_code => ''))
29
+
30
+ puts "\nSubscribing to IB account data"
31
+ puts "\n******** Press <Enter> to cancel... *********\n\n"
32
+ gets
33
+ puts "Cancelling account data subscription.."
34
+
35
+ ib.dispatch(IB::OutgoingMessages::RequestAccountData.new(:subscribe => false,
36
+ :account_code => ''))
@@ -0,0 +1,81 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # This script downloads historic data for specific symbol from IB
4
+ #
5
+ # TODO: Fix the Historical command line client
6
+
7
+ require 'pathname'
8
+ LIB_DIR = (Pathname.new(__FILE__).dirname + '../lib/').realpath.to_s
9
+ $LOAD_PATH.unshift LIB_DIR unless $LOAD_PATH.include?(LIB_DIR)
10
+
11
+ require 'rubygems'
12
+ require 'bundler/setup'
13
+ require 'ib-ruby'
14
+
15
+ ### Configurable Options
16
+ Quiet = false # if Quiet == false, status data will be printed to STDERR
17
+ Timeout = 10 # How long to wait when no messages are received from TWS before exiting, in seconds
18
+ SymbolToRequest = IB::Symbols::Forex[:gbpusd]
19
+
20
+ # Definition of what we want market data for. We have to keep track
21
+ # of what ticker id corresponds to what symbol ourselves, because the
22
+ # ticks don't include any other identifying information.
23
+ #
24
+ # The choice of ticker ids is, as far as I can tell, arbitrary.
25
+ #
26
+ # Note that as of 4/07 there is no historical data available for forex spot.
27
+ #
28
+ @market = {123 => SymbolToRequest}
29
+
30
+ # To determine when the timeout has passed.
31
+ @last_msg_time = Time.now.to_i + 2
32
+
33
+ # Connect to IB TWS.
34
+ ib = IB::IB.new
35
+
36
+ # Uncomment this for verbose debug messages:
37
+ # IB::IBLogger.level = Logger::Severity::DEBUG
38
+
39
+ # Now, subscribe to HistoricalData incoming events. The code passed in the block
40
+ # will be executed when a message of that type is received, with the received
41
+ # message as its argument. In this case, we just print out the data.
42
+ #
43
+ # Note that we have to look the ticker id of each incoming message
44
+ # up in local memory to figure out what it's for.
45
+ #
46
+ # (N.B. The description field is not from IB TWS. It is defined
47
+ # locally in forex.rb, and is just arbitrary text.)
48
+
49
+ ib.subscribe(IB::IncomingMessages::HistoricalData, lambda { |msg|
50
+
51
+ STDERR.puts @market[msg.data[:req_id]].description + ": " + msg.data[:item_count].to_s("F") + " items:" unless Quiet
52
+
53
+ msg.data[:history].each { |datum|
54
+
55
+ @last_msg_time = Time.now.to_i
56
+
57
+ STDERR.puts " " + datum.to_s("F") unless Quiet
58
+ STDOUT.puts "#{datum.date},#{datum.open.to_s("F")},#{datum.high.to_s("F")},#{datum.low.to_s("F")},#{datum.close.to_s("F")},#{datum.volume}"
59
+ }
60
+ })
61
+
62
+ # Now we actually request historical data for the symbols we're
63
+ # interested in. TWS will respond with a HistoricalData message,
64
+ # which will be received by the code above.
65
+
66
+ @market.each_pair do |id, contract|
67
+ msg = IB::OutgoingMessages::RequestHistoricalData.new(:ticker_id => id,
68
+ :contract => contract,
69
+ :end_date_time => Time.now.to_ib,
70
+ :duration => (360).to_s, # how long before end_date_time to request in seconds - this means 1 day
71
+ :bar_size => IB::OutgoingMessages::RequestHistoricalData::BarSizes.index(:hour),
72
+ :what_to_show => :trades,
73
+ :use_RTH => 0,
74
+ :format_date => 2)
75
+ ib.dispatch(msg)
76
+ end
77
+
78
+ while true
79
+ exit(0) if Time.now.to_i > @last_msg_time + Timeout
80
+ sleep 1
81
+ end
@@ -0,0 +1,68 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # This script connects to IB API, subscribes to market data for specific symbols
4
+
5
+ require 'rubygems'
6
+ require 'pathname'
7
+ require 'bundler/setup'
8
+
9
+ LIB_DIR = (Pathname.new(__FILE__).dirname + '../lib/').realpath.to_s
10
+ $LOAD_PATH.unshift LIB_DIR unless $LOAD_PATH.include?(LIB_DIR)
11
+
12
+ require 'ib-ruby'
13
+
14
+ # Definition of what we want market data for. We have to keep track
15
+ # of what ticker id corresponds to what symbol ourselves, because the
16
+ # ticks don't include any other identifying information.
17
+ #
18
+ # The choice of ticker ids is, as far as I can tell, arbitrary.
19
+ #
20
+ @market = {123 => IB::Symbols::Forex[:gbpusd],
21
+ 456 => IB::Symbols::Forex[:eurusd],
22
+ 789 => IB::Symbols::Forex[:usdcad]}
23
+
24
+ # First, connect to IB TWS.
25
+ ib = IB::IB.new
26
+
27
+ # Now, subscribe to TickerPrice and TickerSize events. The code
28
+ # passed in the block will be executed when a message of that type is
29
+ # received, with the received message as its argument. In this case,
30
+ # we just print out the tick.
31
+ #
32
+ # Note that we have to look the ticker id of each incoming message
33
+ # up in local memory to figure out what it's for.
34
+ #
35
+ # (N.B. The description field is not from IB TWS. It is defined
36
+ # locally in forex.rb, and is just arbitrary text.)
37
+
38
+ ib.subscribe(IB::IncomingMessages::TickPrice, lambda { |msg|
39
+ puts @market[msg.data[:ticker_id]].description + ": " + msg.to_human
40
+ })
41
+
42
+ ib.subscribe(IB::IncomingMessages::TickSize, lambda { |msg|
43
+ puts @market[msg.data[:ticker_id]].description + ": " + msg.to_human
44
+ })
45
+
46
+
47
+ # Now we actually request market data for the symbols we're interested in.
48
+
49
+ @market.each_pair { |id, contract|
50
+ msg = IB::OutgoingMessages::RequestMarketData.new({
51
+ :ticker_id => id,
52
+ :contract => contract
53
+ })
54
+ ib.dispatch(msg)
55
+ }
56
+
57
+ puts "\nSubscribed to market data"
58
+ puts "\n******** Press <Enter> to cancel... *********\n\n"
59
+ gets
60
+ puts "Cancelling market data subscription.."
61
+
62
+ @market.each_pair { |id, contract|
63
+ msg = IB::OutgoingMessages::CancelMarketData.new({
64
+ :ticker_id => id,
65
+ :contract => contract
66
+ })
67
+ ib.dispatch(msg)
68
+ }
@@ -0,0 +1,485 @@
1
+ #
2
+ # Copyright (C) 2006 Blue Voodoo Magic LLC.
3
+ #
4
+ # This library is free software; you can redistribute it and/or modify
5
+ # it under the terms of the GNU Lesser General Public License as
6
+ # published by the Free Software Foundation; either version 2.1 of the
7
+ # License, or (at your option) any later version.
8
+ #
9
+ # This library is distributed in the hope that it will be useful, but
10
+ # WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12
+ # Lesser General Public License for more details.
13
+ #
14
+ # You should have received a copy of the GNU Lesser General Public
15
+ # License along with this library; if not, write to the Free Software
16
+ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
17
+ # 02110-1301 USA
18
+ #
19
+
20
+ #
21
+ # TODO: Implement equals() according to the criteria in IB's Java client.
22
+ #
23
+
24
+ module IB
25
+
26
+ module Datatypes
27
+ attr_reader :created_at
28
+
29
+ class AbstractDatum
30
+
31
+ def init
32
+ @created_at = Time.now
33
+ end
34
+
35
+ # If a hash is given, keys are taken as attribute names, values as data.
36
+ # The attrs of the instance are set automatically from the attributeHash.
37
+ #
38
+ # If no hash is given, #init is called in the instance. #init
39
+ # should set the datum up in a generic state.
40
+ #
41
+ def initialize(attributeHash=nil)
42
+ if attributeHash.nil?
43
+ init
44
+ else
45
+ raise ArgumentError.new("Argument must be a Hash") unless attributeHash.is_a?(Hash)
46
+ attributeHash.keys.each { |key|
47
+ self.send((key.to_s + "=").to_sym, attributeHash[key])
48
+ }
49
+ end
50
+ end
51
+ end # AbstractDatum
52
+
53
+
54
+ # This is used within HistoricData messages.
55
+ # Instantiate with a Hash of attributes, to be auto-set via initialize in AbstractDatum.
56
+ class Bar < AbstractDatum
57
+ attr_accessor :date, :open, :high, :low, :close, :volume, :wap, :has_gaps
58
+ # :bar_count => @socket.read_int
59
+
60
+ def to_s
61
+ "<Bar: #{@date}; OHLC: #{@open.to_s}, #{@high.to_s}, #{@low.to_s}, #{@close.to_s}; volume: #{@volume}; wap: #{@wap.to_s}; has_gaps: #{@has_gaps}>"
62
+ end
63
+ end # Bar
64
+
65
+
66
+ class Order < AbstractDatum
67
+ # Constants used in Order objects. Drawn from Order.java
68
+ Origin_Customer = 0
69
+ Origin_Firm = 1
70
+
71
+ Opt_Unknown = '?'
72
+ Opt_Broker_Dealer = 'b'
73
+ Opt_Customer = 'c'
74
+ Opt_Firm = 'f'
75
+ Opt_Isemm = 'm'
76
+ Opt_Farmm = 'n'
77
+ Opt_Specialist = 'y'
78
+
79
+ OCA_Cancel_with_block = 1
80
+ OCA_Reduce_with_block = 2
81
+ OCA_Reduce_non_block = 3
82
+
83
+ # Box orders consts:
84
+ Box_Auction_Match = 1
85
+ Box_Auction_Improvement = 2
86
+ Box_Auction_Transparent = 3
87
+
88
+ # Volatility orders consts:
89
+ Volatility_Type_Daily = 1
90
+ Volatility_Type_Annual = 2
91
+ Volatility_Ref_Price_Average = 1
92
+ Volatility_Ref_Price_BidOrAsk = 2
93
+
94
+ # No idea why IB uses a large number as the default for some fields
95
+ Max_Value = 99999999
96
+
97
+ # Main order fields
98
+ attr_accessor :id, # int m_orderId; ?
99
+ :client_id, # int
100
+ :perm_id, # int
101
+ :action, # String
102
+ :total_quantity, # int
103
+ :order_type, # String
104
+ :limit_price, # double
105
+ :aux_price, # double
106
+ #:shares_allocation, # deprecated sharesAllocation field
107
+
108
+ # Extended order fields
109
+ :tif, # String: Time in Force - DAY, GTC, etc.
110
+ :oca_group, # String: one cancels all group name
111
+ :oca_type, # 1 = CANCEL_WITH_BLOCK, 2 = REDUCE_WITH_BLOCK, 3 = REDUCE_NON_BLOCK
112
+ :order_ref, # String
113
+ :transmit, # bool:if false, order will be created but not transmitted.
114
+ :parent_id, # int: Parent order id, to associate auto STP or TRAIL orders with the original order.
115
+ :block_order, # bool
116
+ :sweep_to_fill, # bool
117
+ :display_size, # int
118
+ :trigger_method, # 0=Default, 1=Double_Bid_Ask, 2=Last, 3=Double_Last,
119
+ # 4=Bid_Ask, 7=Last_or_Bid_Ask, 8=Mid-point
120
+ :outside_rth, # bool: WAS ignore_rth
121
+ :hidden, # bool
122
+ :good_after_time, # FORMAT: 20060505 08:00:00 {time zone}
123
+ :good_till_date, # FORMAT: 20060505 08:00:00 {time zone}
124
+ :override_percentage_constraints, # bool
125
+ :rule_80a, # Individual = 'I', Agency = 'A', AgentOtherMember = 'W',
126
+ # IndividualPTIA = 'J', AgencyPTIA = 'U', AgentOtherMemberPTIA = 'M',
127
+ # IndividualPT = 'K', AgencyPT = 'Y', AgentOtherMemberPT = 'N'
128
+ :all_or_none, # bool
129
+ :min_quantity, # int
130
+ :percent_offset, # double: REL orders only
131
+ :trail_stop_price, # double: for TRAILLIMIT orders only
132
+
133
+ # Financial advisors only, all Strings
134
+ :fa_group, :fa_profile, :fa_method, :fa_percentage,
135
+
136
+ # Institutional orders only
137
+ :open_close, # String: O=Open, C=Close
138
+ :origin, # int: 0=Customer, 1=Firm
139
+ :short_sale_slot, # 1 - you hold the shares, 2 - they will be delivered from elsewhere. Only for Action="SSHORT
140
+ :designated_location, # String: set when slot==2 only
141
+ :exempt_code, # int
142
+
143
+ # SMART routing only
144
+ :discretionary_amount, # double
145
+ :etrade_only, # bool
146
+ :firm_quote_only, # bool
147
+ :nbbo_price_cap, # double
148
+
149
+ # BOX or VOL ORDERS ONLY
150
+ :auction_strategy, # 1=AUCTION_MATCH, 2=AUCTION_IMPROVEMENT, 3=AUCTION_TRANSPARENT
151
+ :starting_price, # double, BOX ORDERS ONLY
152
+ :stock_ref_price, # double, BOX ORDERS ONLY
153
+ :delta, # double, BOX ORDERS ONLY
154
+
155
+ # Pegged to stock or VOL orders
156
+ :stock_range_lower, # double
157
+ :stock_range_upper, # double
158
+
159
+ # VOLATILITY ORDERS ONLY
160
+ :volatility, # double
161
+ :volatility_type, # int: 1=daily, 2=annual
162
+ :continuous_update, # int
163
+ :reference_price_type, # int: 1=Average, 2 = BidOrAsk
164
+ :delta_neutral_order_type, # String
165
+ :delta_neutral_aux_price, # double
166
+
167
+ # COMBO ORDERS ONLY
168
+ :basis_points, # double: EFP orders only
169
+ :basis_points_type, # double: EFP orders only
170
+
171
+ # SCALE ORDERS ONLY
172
+ :scale_init_level_size, # int
173
+ :scale_subs_level_size, # int
174
+ :scale_price_increment, # double
175
+
176
+ # Clearing info
177
+ :account, # String: IB account
178
+ :settling_firm, # String
179
+ :clearing_account, # String: True beneficiary of the order
180
+ :clearing_intent, # "" (Default), "IB", "Away", "PTA" (PostTrade)
181
+
182
+ # ALGO ORDERS ONLY
183
+ :algo_strategy, # String
184
+ :algo_params, # public Vector<TagValue> m_algoParams; ?!
185
+
186
+ # WTF?!
187
+ :what_if, #public boolean m_whatIf; // What-if
188
+ :not_held #public boolean m_notHeld; // Not Held
189
+
190
+ def init
191
+ super
192
+
193
+ @open_close = "0"
194
+ @origin = Origin_Customer
195
+ @transmit = true
196
+ @primary_exchange = ''
197
+ @designated_location = ''
198
+ @min_quantity = Max_Value # TODO: Initialize with nil instead of Max_Value, or change
199
+ # Order sending code in IB::Messages::Outgoing::PlaceOrder
200
+ @percent_offset = Max_Value # -"-
201
+ @nbba_price_cap = Max_Value # -"-
202
+ @starting_price = Max_Value # -"-
203
+ @stock_ref_price = Max_Value # -"-
204
+ @delta = Max_Value
205
+ @delta_neutral_order_type = ''
206
+ @delta_neutral_aux_price = Max_Value # -"-
207
+ @reference_price_type = Max_Value # -"-
208
+ end # init
209
+
210
+ end # class Order
211
+
212
+
213
+ class Contract < AbstractDatum
214
+
215
+ # Valid security types (sec_type attribute)
216
+ SECURITY_TYPES =
217
+ {
218
+ :stock => "STK",
219
+ :option => "OPT",
220
+ :future => "FUT",
221
+ :index => "IND",
222
+ :futures_option => "FOP",
223
+ :forex => "CASH",
224
+ :bag => "BAG"
225
+ }
226
+
227
+ # note that the :description field is entirely local to ib-ruby, and not part of TWS.
228
+ # You can use it to store whatever arbitrary data you want.
229
+ attr_accessor :symbol, :strike, :multiplier, :exchange, :currency,
230
+ :local_symbol, :combo_legs, :description
231
+
232
+ # Bond values
233
+ attr_accessor(:cusip, :ratings, :desc_append, :bond_type, :coupon_type, :callable,
234
+ :puttable, :coupon, :convertible, :maturity, :issue_date)
235
+
236
+ attr_reader :sec_type, :expiry, :right, :primary_exchange
237
+
238
+
239
+ # some protective filters
240
+ def primary_exchange=(x)
241
+ x.upcase! if x.is_a?(String)
242
+
243
+ # per http://chuckcaplan.com/twsapi/index.php/Class%20Contract
244
+ raise(ArgumentError.new("Don't set primary_exchange to smart")) if x == "SMART"
245
+
246
+ @primary_exchange = x
247
+ end
248
+
249
+ def right=(x)
250
+ x.upcase! if x.is_a?(String)
251
+ x = nil if !x.nil? && x.empty?
252
+ raise(ArgumentError.new("Invalid right \"#{x}\" (must be one of PUT, CALL, P, C)")) unless x.nil? || ["PUT", "CALL", "P", "C", "0"].include?(x)
253
+ @right = x
254
+ end
255
+
256
+ def expiry=(x)
257
+ x = x.to_s
258
+ if (x.nil? || !(x =~ /\d{6,8}/)) and !x.empty? then
259
+ raise ArgumentError.new("Invalid expiry \"#{x}\" (must be in format YYYYMM or YYYYMMDD)")
260
+ end
261
+ @expiry = x
262
+ end
263
+
264
+ def sec_type=(x)
265
+ x = nil if !x.nil? && x.empty?
266
+ raise(ArgumentError.new("Invalid security type \"#{x}\" (see SECURITY_TYPES constant in Contract class for valid types)")) unless x.nil? || SECURITY_TYPES.values.include?(x)
267
+ @sec_type = x
268
+ end
269
+
270
+ def reset
271
+ @combo_legs = Array.new
272
+ @strike = 0
273
+ end
274
+
275
+ # This returns an Array of data from the given contract, in standard format.
276
+ # Different messages serialize contracts differently. Go figure.
277
+ # Note that it does not include the combo legs.
278
+ def serialize(type = :long)
279
+ [self.symbol,
280
+ self.sec_type,
281
+ self.expiry,
282
+ self.strike,
283
+ self.right,
284
+ self.multiplier,
285
+ self.exchange] +
286
+ (type == :long ? [self.primary_exchange] : []) +
287
+ [self.currency,
288
+ self.local_symbol]
289
+ end
290
+
291
+ # @Legacy
292
+ def serialize_long(version)
293
+ serialize(:long)
294
+ end
295
+
296
+ # @Legacy
297
+ def serialize_short(version)
298
+ serialize(:short)
299
+ end
300
+
301
+ # This produces a string uniquely identifying this contract, in the format used
302
+ # for command line arguments in the IB-Ruby examples. The format is:
303
+ #
304
+ # symbol:security_type:expiry:strike:right:multiplier:exchange:primary_exchange:currency:local_symbol
305
+ #
306
+ # Fields not needed for a particular security should be left blank
307
+ # (e.g. strike and right are only relevant for options.)
308
+ #
309
+ # For example, to query the British pound futures contract trading on Globex
310
+ # expiring in September, 2008, the string is:
311
+ #
312
+ # GBP:FUT:200809:::62500:GLOBEX::USD:
313
+ def serialize_ib_ruby(version)
314
+ serialize.join(":")
315
+ end
316
+
317
+ # This returns a Contract initialized from the serialize_ib_ruby format string.
318
+ def self.from_ib_ruby(string)
319
+ c = Contract.new
320
+ c.symbol, c.sec_type, c.expiry, c.strike, c.right, c.multiplier,
321
+ c.exchange, c.primary_exchange, c.currency, c.local_symbol = string.split(":")
322
+ c
323
+ end
324
+
325
+ def serialize_under_comp(*args)
326
+ raise "Unimplemented"
327
+ # EClientSocket.java, line 471:
328
+ #if (m_serverVersion >= MIN_SERVER_VER_UNDER_COMP) {
329
+ # if (contract.m_underComp != null) {
330
+ # UnderComp underComp = contract.m_underComp;
331
+ # send( true);
332
+ # send( underComp.m_conId);
333
+ # send( underComp.m_delta);
334
+ # send( underComp.m_price);
335
+ # }
336
+ end
337
+
338
+ def serialize_algo(*args)
339
+ raise "Unimplemented"
340
+ #if (m_serverVersion >= MIN_SERVER_VER_ALGO_ORDERS) {
341
+ # send( order.m_algoStrategy);
342
+ # if( !IsEmpty(order.m_algoStrategy)) {
343
+ # java.util.Vector algoParams = order.m_algoParams;
344
+ # int algoParamsCount = algoParams == null ? 0 : algoParams.size();
345
+ # send( algoParamsCount);
346
+ # if( algoParamsCount > 0) {
347
+ # for( int i = 0; i < algoParamsCount; ++i) {
348
+ # TagValue tagValue = (TagValue)algoParams.get(i);
349
+ # send( tagValue.m_tag);
350
+ # send( tagValue.m_value);
351
+ # }
352
+ # }
353
+ # }
354
+ #}
355
+ end
356
+
357
+ # Some messages send open_close too, some don't. WTF.
358
+ def serialize_combo_legs(include_open_close = false)
359
+ if self.combo_legs.nil?
360
+ [0]
361
+ else
362
+ [self.combo_legs.size].concat(self.combo_legs.serialize(include_open_close))
363
+ end
364
+ end
365
+
366
+ def init
367
+ super
368
+
369
+ @combo_legs = Array.new
370
+ @strike = 0
371
+ @sec_type = ''
372
+ end
373
+
374
+ def to_human
375
+ "<IB-Contract: " + [symbol, expiry, sec_type, strike, right, exchange, currency].join("-") + "}>"
376
+ end
377
+
378
+ def to_short
379
+ "#{symbol}#{expiry}#{strike}#{right}#{exchange}#{currency}"
380
+ end
381
+
382
+ def to_s
383
+ to_human
384
+ end
385
+
386
+ end # class Contract
387
+
388
+ class ContractDetails < AbstractDatum
389
+ attr_accessor :summary, :market_name, :trading_class, :con_id, :min_tick,
390
+ :multiplier, :price_magnifier, :order_types, :valid_exchanges
391
+
392
+ def init
393
+ super
394
+
395
+ @summary = Contract.new
396
+ @con_id = 0
397
+ @min_tick = 0
398
+ end
399
+ end # class ContractDetails
400
+
401
+ class Execution < AbstractDatum
402
+ attr_accessor :order_id, :client_id, :exec_id, :time, :account_number, :exchange,
403
+ :side, :shares, :price, :perm_id, :liquidation
404
+
405
+ def init
406
+ super
407
+
408
+ @order_id = 0
409
+ @client_id = 0
410
+ @shares = 0
411
+ @price = 0
412
+ @perm_id = 0
413
+ @liquidation =0
414
+ end
415
+ end # Execution
416
+
417
+ # From EClientSocket.java: Note that the valid format for m_time is "yyyymmdd-hh:mm:ss"
418
+ class ExecutionFilter < AbstractDatum
419
+ attr_accessor :client_id, :acct_code, :time, :symbol, :sec_type, :exchange, :side
420
+
421
+ def init
422
+ super
423
+
424
+ @client_id = 0
425
+ end
426
+
427
+ end # ExecutionFilter
428
+
429
+
430
+ class ComboLeg < AbstractDatum
431
+ attr_accessor :con_id, :ratio, :action, :exchange, :open_close
432
+
433
+ def init
434
+ super
435
+
436
+ @con_id = 0
437
+ @ratio = 0
438
+ @open_close = 0
439
+ end
440
+
441
+ # Some messages include open_close, some don't. wtf.
442
+ def serialize(include_open_close = false)
443
+ self.map { |leg|
444
+ [leg.con_id,
445
+ leg.ratio,
446
+ leg.action,
447
+ leg.exchange,
448
+ (include_open_close ? leg.open_close : [])]
449
+ }.flatten
450
+ end
451
+ end # ComboLeg
452
+
453
+
454
+ class ScannerSubscription < AbstractDatum
455
+
456
+ attr_accessor :number_of_rows, :instrument, :location_code, :scan_code, :above_price,
457
+ :below_price, :above_volume, :average_option_volume_above,
458
+ :market_cap_above, :market_cap_below, :moody_rating_above,
459
+ :moody_rating_below, :sp_rating_above, :sp_rating_below,
460
+ :maturity_date_above, :maturity_date_below, :coupon_rate_above,
461
+ :coupon_rate_below, :exclude_convertible, :scanner_setting_pairs,
462
+ :stock_type_filter
463
+
464
+ def init
465
+ super
466
+
467
+ @coupon_rate_above = @coupon_rate_below = @market_cap_below = @market_cap_above =
468
+ @average_option_volume_above = @above_volume = @below_price = @above_price = nil
469
+ @number_of_rows = -1 # none specified, per ScannerSubscription.java
470
+ end
471
+ end # ScannerSubscription
472
+
473
+
474
+ # Just like a Hash, but throws an exception if you try to access a key that doesn't exist.
475
+ class StringentHash < Hash
476
+ def initialize(hash)
477
+ super() { |hash, key| raise Exception.new("key #{key.inspect} not found!") }
478
+ self.merge!(hash) unless hash.nil?
479
+ end
480
+ end
481
+
482
+ end # module Datatypes
483
+ Models = Datatypes
484
+
485
+ end # module