ib-ruby 0.5.7 → 0.5.9

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.
data/HISTORY CHANGED
@@ -73,3 +73,11 @@
73
73
  == 0.5.7 / 2011-12-16
74
74
 
75
75
  * Option subclass of Contract added
76
+
77
+ == 0.5.8 / 2011-12-16
78
+
79
+ * Critical bugs in Order-related messages fixed
80
+
81
+ == 0.5.9 / 2011-12-16
82
+
83
+ * Sample scripts to place and cancel orders added
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.5.7
1
+ 0.5.9
data/bin/global_cancel ADDED
@@ -0,0 +1,26 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # This script allows you to cancel ALL Orders opened via IB API at once.
4
+ # Useful if your robot goes crazy and opens gazillions of wrong limit orders.
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::Connection.new
16
+
17
+ # Subscribe to TWS alerts/errors and account-related messages
18
+ # that TWS sends in response to account data request
19
+ ib.subscribe(:Alert, :OpenOrder, :OrderStatus) { |msg| puts msg.to_human }
20
+
21
+ ib.send_message :RequestGlobalCancel
22
+
23
+ ib.send_message :RequestAllOpenOrders
24
+
25
+ puts "\n******** Press <Enter> to cancel... *********\n\n"
26
+ gets
data/bin/place_order ADDED
@@ -0,0 +1,34 @@
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::Connection.new
16
+
17
+ # Subscribe to TWS alerts/errors and account-related messages
18
+ # that TWS sends in response to account data request
19
+ ib.subscribe(:Alert, :OpenOrder, :OrderStatus) { |msg| puts msg.to_human }
20
+
21
+ wfc = IB::Symbols::Stocks[:wfc]
22
+ buy_order = IB::Models::Order.new :total_quantity => 100,
23
+ :limit_price => 1 + rand().round(2),
24
+ :action => 'BUY',
25
+ :order_type => 'LMT'
26
+
27
+ sleep 0.5 # waiting for :NextValidID
28
+
29
+ ib.place_order buy_order, wfc
30
+
31
+ ib.send_message :RequestAllOpenOrders
32
+
33
+ puts "\n******** Press <Enter> to cancel... *********\n\n"
34
+ gets
@@ -22,12 +22,13 @@ module IB
22
22
  DEFAULT_OPTIONS = {:host =>"127.0.0.1",
23
23
  :port => '4001', # IB Gateway connection (default)
24
24
  #:port => '7496', # TWS connection, with annoying pop-ups
25
+ :client_id => nil, # Will be randomly assigned
25
26
  :connect => true,
26
27
  :reader => true
27
28
  }
28
29
 
29
- attr_reader :server, # Info about IB server and server connection state
30
- :next_order_id # Next valid order id
30
+ attr_reader :server # Info about IB server and server connection state
31
+ attr_accessor :next_order_id # Next valid order id
31
32
 
32
33
  def initialize(opts = {})
33
34
  @options = DEFAULT_OPTIONS.merge(opts)
@@ -67,8 +68,10 @@ module IB
67
68
  @server[:local_connect_time] = Time.now()
68
69
  @server[:remote_connect_time] = @server[:socket].read_string
69
70
 
70
- # Sending arbitrary client ID to identify subsequent communications.
71
- @server[:client_id] = random_id
71
+ # Sending (arbitrary) client ID to identify subsequent communications.
72
+ # The client with a client_id of 0 can manage the TWS-owned open orders.
73
+ # Other clients can only manage their own open orders.
74
+ @server[:client_id] = @options[:client_id] || random_id
72
75
  @server[:socket].send(@server[:client_id])
73
76
 
74
77
  @connected = true
@@ -161,9 +164,9 @@ module IB
161
164
  msg_id = @server[:socket].read_int
162
165
 
163
166
  # Debug:
164
- #unless [1, 2, 4, 6, 7, 8, 9, 12, 21, 53].include? msg_id
165
- # puts "Got message #{msg_id} (#{Messages::Incoming::Table[msg_id]})"
166
- #end
167
+ unless [1, 2, 4, 6, 7, 8, 9, 12, 21, 53].include? msg_id
168
+ puts "Got message #{msg_id} (#{Messages::Incoming::Table[msg_id]})"
169
+ end
167
170
 
168
171
  # Create new instance of the appropriate message type, and have it read the message.
169
172
  # NB: Failure here usually means unsupported message type received
@@ -173,6 +176,15 @@ module IB
173
176
  puts "No subscribers for message #{msg.class}!" if subscribers[msg.class].empty?
174
177
  end
175
178
 
179
+ # Place Order (convenience wrapper for message :PlaceOrder)
180
+ def place_order order, contract
181
+ send_message :PlaceOrder,
182
+ :order => order,
183
+ :contract => contract,
184
+ :id => @next_order_id
185
+ @next_order_id += 1
186
+ end
187
+
176
188
  protected
177
189
 
178
190
  def random_id
@@ -364,7 +364,8 @@ module IB
364
364
 
365
365
  @order = Models::Order.new :id => @socket.read_int
366
366
 
367
- @contract = Models::Contract.new :symbol => @socket.read_string,
367
+ @contract = Models::Contract.new :con_id => @socket.read_string,
368
+ :symbol => @socket.read_string,
368
369
  :sec_type => @socket.read_string,
369
370
  :expiry => @socket.read_string,
370
371
  :strike => @socket.read_decimal,
@@ -397,7 +398,7 @@ module IB
397
398
  @order.fa_percentage = @socket.read_string
398
399
  @order.fa_profile = @socket.read_string
399
400
  @order.good_till_date = @socket.read_string
400
- @order.rule_80A = @socket.read_string
401
+ @order.rule_80a = @socket.read_string
401
402
  @order.percent_offset = @socket.read_decimal
402
403
  @order.settling_firm = @socket.read_string
403
404
  @order.short_sale_slot = @socket.read_int
@@ -431,7 +432,7 @@ module IB
431
432
  @order.trail_stop_price = @socket.read_decimal
432
433
  @order.basis_points = @socket.read_decimal
433
434
  @order.basis_points_type = @socket.read_int
434
- @order.combo_legs_description = @socket.read_string
435
+ @contract.combo_legs_description = @socket.read_string
435
436
  @order.scale_init_level_size = @socket.read_int_max
436
437
  @order.scale_subs_level_size = @socket.read_int_max
437
438
  @order.scale_price_increment = @socket.read_decimal_max
@@ -73,35 +73,46 @@ module IB
73
73
 
74
74
  ### Defining (short) Outgoing Message classes for IB:
75
75
 
76
- # Empty messages (no data)
76
+ ## Empty messages (no data)
77
+
78
+ # Request the open orders that were placed from THIS client. Each open order
79
+ # will be fed back through the OpenOrder and OrderStatus messages.
80
+ # NB: Client with a client_id of 0 will also receive the TWS-owned open orders.
81
+ # These orders will be associated with the client and a new orderId will be
82
+ # generated. This association will persist over multiple API and TWS sessions.
77
83
  RequestOpenOrders = def_message 5
78
- CancelNewsBulletins = def_message 13
84
+
85
+ # Request the open orders placed from all clients and also from TWS. Each open
86
+ # order will be fed back through the OpenOrder and OrderStatus messages.
79
87
  RequestAllOpenOrders = def_message 16
80
- RequestManagedAccounts = def_message 17
88
+
81
89
  # Requests an XML document that describes the valid parameters that a scanner
82
90
  # subscription can have (for outgoing RequestScannerSubscription message).
83
91
  RequestScannerParameters = def_message 24
92
+
93
+ CancelNewsBulletins = def_message 13
94
+ RequestManagedAccounts = def_message 17
84
95
  RequestCurrentTime = def_message 49
85
96
  RequestGlobalCancel = def_message 58
86
97
 
87
- # Data format is: @data = { :id => ticker_id}
98
+ ## Data format is: @data = { :id => ticker_id}
88
99
  CancelMarketData = def_message 2
89
100
  CancelMarketDepth = def_message 11
90
101
  CancelScannerSubscription = def_message 23
91
102
  CancelHistoricalData = def_message 25
92
103
  CancelRealTimeBars = def_message 51
93
104
 
94
- # Data format is: @data = { :id => request_id }
105
+ ## Data format is: @data = { :id => request_id }
95
106
  CancelFundamentalData = def_message 53
96
107
  CancelImpliedVolatility = def_message 56
97
108
  CancelCalculateImpliedVolatility = CancelImpliedVolatility
98
109
  CancelOptionPrice = def_message 57
99
110
  CancelCalculateOptionPrice = CancelOptionPrice
100
111
 
101
- # Data format is: @data ={ :id => order-id-to-cancel }
112
+ ## Data format is: @data ={ :id => order-id-to-cancel }
102
113
  CancelOrder = def_message 4
103
114
 
104
- # These messages contain just one or two keys, shown in the end of definition
115
+ ## These messages contain just one or two keys, shown in the end of definition
105
116
  # @data = { :number_of_ids => int }
106
117
  RequestIds = def_message 8, 1, :number_of_ids
107
118
  # data = { :all_messages => boolean }
@@ -34,7 +34,7 @@ module IB
34
34
  def initialize opts = {}
35
35
  @con_id = 0
36
36
  @ratio = 0
37
- @open_close = 0
37
+ @open_close = SAME
38
38
  @short_sale_slot = 0
39
39
  @designated_location = ''
40
40
  @exempt_code = -1
@@ -141,7 +141,7 @@ module IB
141
141
  def right=(x)
142
142
  x.upcase! if x.is_a?(String)
143
143
  x = nil if !x.nil? && x.empty?
144
- x = nil if x == "0"
144
+ x = nil if x == "0" || x == "?"
145
145
  raise(ArgumentError.new("Invalid right \"#{x}\" (must be one of PUT, CALL, P, C)")) unless x.nil? || ["PUT", "CALL", "P", "C", "0"].include?(x)
146
146
  @right = x
147
147
  end
@@ -261,7 +261,7 @@ module IB
261
261
  # This returns an Array of data from the given order,
262
262
  # mixed with data from associated contract. Ugly mix, indeed.
263
263
  def serialize_with contract
264
- [contract.serialize_long(:sec_id),
264
+ [contract.serialize_long(:con_id, :sec_id),
265
265
  action, # main order fields
266
266
  total_quantity,
267
267
  order_type,
@@ -336,6 +336,14 @@ module IB
336
336
  end
337
337
  end
338
338
 
339
+ def to_s #human
340
+ "<Order::" + #self.class.
341
+ instance_variables.map do |key|
342
+ value = instance_variable_get(key)
343
+ " #{key}=#{value}" unless value.nil? || value == '' || value == 0
344
+ end.compact.join(',') + " >"
345
+ end
346
+
339
347
  end # class Order
340
348
  end # module Models
341
349
  end # module IB
metadata CHANGED
@@ -2,7 +2,7 @@
2
2
  name: ib-ruby
3
3
  version: !ruby/object:Gem::Version
4
4
  prerelease:
5
- version: 0.5.7
5
+ version: 0.5.9
6
6
  platform: ruby
7
7
  authors:
8
8
  - Paul Legato
@@ -44,10 +44,12 @@ executables:
44
44
  - account_info
45
45
  - contract_details
46
46
  - depth_of_market
47
+ - global_cancel
47
48
  - historic_data
48
49
  - historic_data_cli
49
50
  - market_data
50
51
  - option_data
52
+ - place_order
51
53
  - template
52
54
  - time_and_sales
53
55
  extensions: []
@@ -58,10 +60,12 @@ files:
58
60
  - bin/account_info
59
61
  - bin/contract_details
60
62
  - bin/depth_of_market
63
+ - bin/global_cancel
61
64
  - bin/historic_data
62
65
  - bin/historic_data_cli
63
66
  - bin/market_data
64
67
  - bin/option_data
68
+ - bin/place_order
65
69
  - bin/template
66
70
  - bin/time_and_sales
67
71
  - lib/ib-ruby/connection.rb