ib-ruby 0.4.3 → 0.4.20

Sign up to get free protection for your applications and to get access to all the features.
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