wdevauld-ib-ruby 0.2
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.txt +5 -0
- data/LICENSE +504 -0
- data/Manifest.txt +20 -0
- data/README.txt +50 -0
- data/Rakefile +33 -0
- data/bin/AccountInfo +71 -0
- data/bin/HistoricToCSV +129 -0
- data/bin/RequestHistoricData +312 -0
- data/bin/RequestMarketData +80 -0
- data/bin/SimpleTimeAndSales +101 -0
- data/bin/ib-ruby +8 -0
- data/lib/ib-ruby.rb +49 -0
- data/lib/ib-ruby/datatypes.rb +402 -0
- data/lib/ib-ruby/ib.rb +242 -0
- data/lib/ib-ruby/messages.rb +1449 -0
- data/lib/ib-ruby/symbols/forex.rb +62 -0
- data/lib/ib-ruby/symbols/futures.rb +114 -0
- data/spec/ib-ruby_spec.rb +7 -0
- data/spec/spec_helper.rb +16 -0
- data/test/test_ib-ruby.rb +162 -0
- metadata +96 -0
@@ -0,0 +1,80 @@
|
|
1
|
+
#!/usr/bin/env ruby -w
|
2
|
+
#
|
3
|
+
# Copyright (C) 2007 Paul Legato.
|
4
|
+
#
|
5
|
+
# This library is free software; you can redistribute it and/or modify
|
6
|
+
# it under the terms of the GNU Lesser General Public License as
|
7
|
+
# published by the Free Software Foundation; either version 2.1 of the
|
8
|
+
# License, or (at your option) any later version.
|
9
|
+
#
|
10
|
+
# This library is distributed in the hope that it will be useful, but
|
11
|
+
# WITHOUT ANY WARRANTY; without even the implied warranty of
|
12
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
13
|
+
# Lesser General Public License for more details.
|
14
|
+
#
|
15
|
+
# You should have received a copy of the GNU Lesser General Public
|
16
|
+
# License along with this library; if not, write to the Free Software
|
17
|
+
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
18
|
+
# 02110-1301 USA
|
19
|
+
#
|
20
|
+
|
21
|
+
$:.push(File.dirname(__FILE__) + "/../")
|
22
|
+
|
23
|
+
require 'ib'
|
24
|
+
require 'datatypes'
|
25
|
+
require 'symbols/forex'
|
26
|
+
|
27
|
+
#
|
28
|
+
# Definition of what we want market data for. We have to keep track
|
29
|
+
# of what ticker id corresponds to what symbol ourselves, because the
|
30
|
+
# ticks don't include any other identifying information.
|
31
|
+
#
|
32
|
+
# The choice of ticker ids is, as far as I can tell, arbitrary.
|
33
|
+
#
|
34
|
+
@market =
|
35
|
+
{
|
36
|
+
123 => IB::Symbols::Forex[:gbpusd],
|
37
|
+
456 => IB::Symbols::Forex[:eurusd]
|
38
|
+
}
|
39
|
+
|
40
|
+
|
41
|
+
# First, connect to IB TWS.
|
42
|
+
ib = IB::IB.new
|
43
|
+
|
44
|
+
|
45
|
+
#
|
46
|
+
# Now, subscribe to TickerPrice and TickerSize events. The code
|
47
|
+
# passed in the block will be executed when a message of that type is
|
48
|
+
# received, with the received message as its argument. In this case,
|
49
|
+
# we just print out the tick.
|
50
|
+
#
|
51
|
+
# Note that we have to look the ticker id of each incoming message
|
52
|
+
# up in local memory to figure out what it's for.
|
53
|
+
#
|
54
|
+
# (N.B. The description field is not from IB TWS. It is defined
|
55
|
+
# locally in forex.rb, and is just arbitrary text.)
|
56
|
+
|
57
|
+
ib.subscribe(IB::IncomingMessages::TickPrice, lambda {|msg|
|
58
|
+
puts @market[msg.data[:ticker_id]].description + ": " + msg.to_human
|
59
|
+
})
|
60
|
+
|
61
|
+
ib.subscribe(IB::IncomingMessages::TickSize, lambda {|msg|
|
62
|
+
puts @market[msg.data[:ticker_id]].description + ": " + msg.to_human
|
63
|
+
})
|
64
|
+
|
65
|
+
|
66
|
+
# Now we actually request market data for the symbols we're interested in.
|
67
|
+
|
68
|
+
@market.each_pair {|id, contract|
|
69
|
+
msg = IB::OutgoingMessages::RequestMarketData.new({
|
70
|
+
:ticker_id => id,
|
71
|
+
:contract => contract
|
72
|
+
})
|
73
|
+
ib.dispatch(msg)
|
74
|
+
}
|
75
|
+
|
76
|
+
|
77
|
+
puts "Main thread going to sleep. Press ^C to quit.."
|
78
|
+
while true
|
79
|
+
sleep 2
|
80
|
+
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
#!/usr/bin/env ruby -w
|
2
|
+
#
|
3
|
+
# Copyright (C) 2007 Paul Legato.
|
4
|
+
#
|
5
|
+
# This library is free software; you can redistribute it and/or modify
|
6
|
+
# it under the terms of the GNU Lesser General Public License as
|
7
|
+
# published by the Free Software Foundation; either version 2.1 of the
|
8
|
+
# License, or (at your option) any later version.
|
9
|
+
#
|
10
|
+
# This library is distributed in the hope that it will be useful, but
|
11
|
+
# WITHOUT ANY WARRANTY; without even the implied warranty of
|
12
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
13
|
+
# Lesser General Public License for more details.
|
14
|
+
#
|
15
|
+
# You should have received a copy of the GNU Lesser General Public
|
16
|
+
# License along with this library; if not, write to the Free Software
|
17
|
+
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
18
|
+
# 02110-1301 USA
|
19
|
+
#
|
20
|
+
|
21
|
+
$:.push(File.dirname(__FILE__) + "/../")
|
22
|
+
|
23
|
+
require 'ib'
|
24
|
+
require 'datatypes'
|
25
|
+
require 'symbols/futures'
|
26
|
+
|
27
|
+
# First, connect to IB TWS.
|
28
|
+
ib = IB::IB.new
|
29
|
+
|
30
|
+
# Uncomment this for verbose debug messages:
|
31
|
+
# IB::IBLogger.level = Logger::Severity::DEBUG
|
32
|
+
|
33
|
+
# Define the symbols we're interested in.
|
34
|
+
@market =
|
35
|
+
{
|
36
|
+
123 => IB::Symbols::Futures[:gbp],
|
37
|
+
234 => IB::Symbols::Futures[:jpy]
|
38
|
+
}
|
39
|
+
|
40
|
+
|
41
|
+
# This method filters out non-:last type events, and filters out any
|
42
|
+
# sale < MIN_SIZE.
|
43
|
+
MIN_SIZE = 0
|
44
|
+
|
45
|
+
def showSales(msg)
|
46
|
+
return if msg.data[:type] != :last || msg.data[:size] < MIN_SIZE
|
47
|
+
puts @market[msg.data[:ticker_id]].description + ": " + msg.data[:size].to_s + " at " + msg.data[:price].to_digits
|
48
|
+
end
|
49
|
+
|
50
|
+
def showSize(msg)
|
51
|
+
puts @market[msg.data[:ticker_id]].description + ": " + msg.to_human
|
52
|
+
end
|
53
|
+
|
54
|
+
|
55
|
+
#
|
56
|
+
# Now, subscribe to TickerPrice and TickerSize events. The code
|
57
|
+
# passed in the block will be executed when a message of that type is
|
58
|
+
# received, with the received message as its argument. In this case,
|
59
|
+
# we just print out the tick.
|
60
|
+
#
|
61
|
+
# Note that we have to look the ticker id of each incoming message
|
62
|
+
# up in local memory to figure out what it's for.
|
63
|
+
#
|
64
|
+
# (N.B. The description field is not from IB TWS. It is defined
|
65
|
+
# locally in forex.rb, and is just arbitrary text.)
|
66
|
+
|
67
|
+
ib.subscribe(IB::IncomingMessages::TickPrice, lambda {|msg|
|
68
|
+
showSales(msg)
|
69
|
+
})
|
70
|
+
|
71
|
+
ib.subscribe(IB::IncomingMessages::TickSize, lambda {|msg|
|
72
|
+
showSize(msg)
|
73
|
+
})
|
74
|
+
|
75
|
+
|
76
|
+
# Now we actually request market data for the symbols we're interested in.
|
77
|
+
|
78
|
+
@market.each_pair {|id, contract|
|
79
|
+
msg = IB::OutgoingMessages::RequestMarketData.new({
|
80
|
+
:ticker_id => id,
|
81
|
+
:contract => contract
|
82
|
+
})
|
83
|
+
ib.dispatch(msg)
|
84
|
+
}
|
85
|
+
|
86
|
+
|
87
|
+
puts "\n\n\t******** Press <Enter> to quit.. *********\n\n"
|
88
|
+
|
89
|
+
gets
|
90
|
+
|
91
|
+
puts "Unsubscribing from TWS market data.."
|
92
|
+
|
93
|
+
@market.each_pair {|id, contract|
|
94
|
+
msg = IB::OutgoingMessages::CancelMarketData.new({
|
95
|
+
:ticker_id => id,
|
96
|
+
})
|
97
|
+
ib.dispatch(msg)
|
98
|
+
}
|
99
|
+
|
100
|
+
puts "Done."
|
101
|
+
|
data/bin/ib-ruby
ADDED
data/lib/ib-ruby.rb
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
|
2
|
+
module IbRuby
|
3
|
+
|
4
|
+
# :stopdoc:
|
5
|
+
VERSION = '1.0.0'
|
6
|
+
LIBPATH = ::File.expand_path(::File.dirname(__FILE__)) + ::File::SEPARATOR
|
7
|
+
PATH = ::File.dirname(LIBPATH) + ::File::SEPARATOR
|
8
|
+
# :startdoc:
|
9
|
+
|
10
|
+
# Returns the version string for the library.
|
11
|
+
#
|
12
|
+
def self.version
|
13
|
+
VERSION
|
14
|
+
end
|
15
|
+
|
16
|
+
# Returns the library path for the module. If any arguments are given,
|
17
|
+
# they will be joined to the end of the libray path using
|
18
|
+
# <tt>File.join</tt>.
|
19
|
+
#
|
20
|
+
def self.libpath( *args )
|
21
|
+
args.empty? ? LIBPATH : ::File.join(LIBPATH, args.flatten)
|
22
|
+
end
|
23
|
+
|
24
|
+
# Returns the lpath for the module. If any arguments are given,
|
25
|
+
# they will be joined to the end of the path using
|
26
|
+
# <tt>File.join</tt>.
|
27
|
+
#
|
28
|
+
def self.path( *args )
|
29
|
+
args.empty? ? PATH : ::File.join(PATH, args.flatten)
|
30
|
+
end
|
31
|
+
|
32
|
+
# Utility method used to rquire all files ending in .rb that lie in the
|
33
|
+
# directory below this file that has the same name as the filename passed
|
34
|
+
# in. Optionally, a specific _directory_ name can be passed in such that
|
35
|
+
# the _filename_ does not have to be equivalent to the directory.
|
36
|
+
#
|
37
|
+
def self.require_all_libs_relative_to( fname, dir = nil )
|
38
|
+
dir ||= ::File.basename(fname, '.*')
|
39
|
+
search_me = ::File.expand_path(
|
40
|
+
::File.join(::File.dirname(fname), dir, '**', '*.rb'))
|
41
|
+
|
42
|
+
Dir.glob(search_me).sort.each {|rb| require rb}
|
43
|
+
end
|
44
|
+
|
45
|
+
end # module IbRuby
|
46
|
+
|
47
|
+
IbRuby.require_all_libs_relative_to(__FILE__)
|
48
|
+
|
49
|
+
# EOF
|
@@ -0,0 +1,402 @@
|
|
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
|
+
require 'Logger'
|
25
|
+
|
26
|
+
module IB
|
27
|
+
|
28
|
+
logger = Logger.new(STDERR)
|
29
|
+
|
30
|
+
module Datatypes
|
31
|
+
attr_reader :created_at
|
32
|
+
|
33
|
+
class AbstractDatum
|
34
|
+
def init
|
35
|
+
@created_at = Time.now
|
36
|
+
end
|
37
|
+
|
38
|
+
# If a hash is given, keys are taken as attribute names, values as data.
|
39
|
+
# The attrs of the instance are set automatically from the attributeHash.
|
40
|
+
#
|
41
|
+
# If no hash is given, #init is called in the instance. #init
|
42
|
+
# should set the datum up in a generic state.
|
43
|
+
#
|
44
|
+
def initialize(attributeHash=nil)
|
45
|
+
if attributeHash.nil?
|
46
|
+
init
|
47
|
+
else
|
48
|
+
raise(ArgumentError.new("Argument must be a Hash")) unless attributeHash.is_a?(Hash)
|
49
|
+
attributeHash.keys.each {|key|
|
50
|
+
self.send((key.to_s + "=").to_sym, attributeHash[key])
|
51
|
+
}
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end # AbstractDatum
|
55
|
+
|
56
|
+
|
57
|
+
# This is used within HistoricData messages.
|
58
|
+
# Instantiate with a Hash of attributes, to be auto-set via initialize in AbstractDatum.
|
59
|
+
class Bar < AbstractDatum
|
60
|
+
attr_accessor :date, :open, :high, :low, :close, :volume, :wap, :has_gaps
|
61
|
+
|
62
|
+
def to_s
|
63
|
+
"<Bar: #{@date}; OHLC: #{@open.to_digits}, #{@high.to_digits}, #{@low.to_digits}, #{@close.to_digits}; volume: #{@volume}; wap: #{@wap.to_digits}; has_gaps: #{@has_gaps}>"
|
64
|
+
end
|
65
|
+
|
66
|
+
end # Bar
|
67
|
+
|
68
|
+
|
69
|
+
class Order < AbstractDatum
|
70
|
+
# Constants used in Order objects. Drawn from Order.java
|
71
|
+
Origin_Customer = 0
|
72
|
+
Origin_Firm = 1
|
73
|
+
|
74
|
+
Opt_Unknown = '?'
|
75
|
+
Opt_Broker_Dealer = 'b'
|
76
|
+
Opt_Customer = 'c'
|
77
|
+
Opt_Firm = 'f'
|
78
|
+
Opt_Isemm = 'm'
|
79
|
+
Opt_Farmm = 'n'
|
80
|
+
Opt_Specialist = 'y'
|
81
|
+
|
82
|
+
# Main order fields
|
83
|
+
attr_accessor(:id, :client_id, :perm_id, :action, :total_quantity, :order_type, :limit_price,
|
84
|
+
:aux_price, :shares_allocation)
|
85
|
+
|
86
|
+
# Extended order fields
|
87
|
+
attr_accessor(:tif, :oca_group, :account, :open_close, :origin, :order_ref,
|
88
|
+
:transmit, # if false, order will be created but not transmitted.
|
89
|
+
:parent_id, # Parent order id, to associate auto STP or TRAIL orders with the original order.
|
90
|
+
:block_order,
|
91
|
+
:sweep_to_fill,
|
92
|
+
:display_size,
|
93
|
+
:trigger_method,
|
94
|
+
:ignore_rth,
|
95
|
+
:hidden,
|
96
|
+
:discretionary_amount,
|
97
|
+
:good_after_time,
|
98
|
+
:good_till_date)
|
99
|
+
|
100
|
+
OCA_Cancel_with_block = 1
|
101
|
+
OCA_Reduce_with_block = 2
|
102
|
+
OCA_Reduce_non_block = 3
|
103
|
+
|
104
|
+
# No idea what the fa_* attributes are for, nor many of the others.
|
105
|
+
attr_accessor(:fa_group, :fa_profile, :fa_method, :fa_profile, :fa_method, :fa_percentage, :primary_exchange,
|
106
|
+
:short_sale_slot, # 1 or 2, says Order.java. (No idea what the difference is.)
|
107
|
+
:designated_location, # "when slot=2 only"
|
108
|
+
:oca_type, # 1 = CANCEL_WITH_BLOCK, 2 = REDUCE_WITH_BLOCK, 3 = REDUCE_NON_BLOCK
|
109
|
+
:rth_only, :override_percentage_constraints, :rule_80a, :settling_firm, :all_or_none,
|
110
|
+
:min_quantity, :percent_offset, :etrade_only, :firm_quote_only, :nbbo_price_cap)
|
111
|
+
|
112
|
+
# Box orders only:
|
113
|
+
Box_Auction_Match = 1
|
114
|
+
Box_Auction_Improvement = 2
|
115
|
+
Box_Auction_Transparent = 3
|
116
|
+
attr_accessor(:auction_strategy, # Box_* constants above
|
117
|
+
:starting_price, :stock_ref_price, :delta, :stock_range_lower, :stock_range_upper)
|
118
|
+
|
119
|
+
# Volatility orders only:
|
120
|
+
Volatility_Type_Daily = 1
|
121
|
+
Volatility_Type_Annual = 2
|
122
|
+
|
123
|
+
Volatility_Ref_Price_Average = 1
|
124
|
+
Volatility_Ref_Price_BidOrAsk = 2
|
125
|
+
|
126
|
+
attr_accessor(:volatility,
|
127
|
+
:volatility_type, # 1 = daily, 2 = annual, as above
|
128
|
+
:continuous_update,
|
129
|
+
:reference_price_type, # 1 = average, 2 = BidOrAsk
|
130
|
+
:delta_neutral_order_type,
|
131
|
+
:delta_neutral_aux_price)
|
132
|
+
|
133
|
+
Max_value = 99999999 # I don't know why IB uses a very large number as the default for certain fields
|
134
|
+
def init
|
135
|
+
super
|
136
|
+
|
137
|
+
@open_close = "0"
|
138
|
+
@origin = Origin_Customer
|
139
|
+
@transmit = true
|
140
|
+
@primary_exchange = ''
|
141
|
+
@designated_location = ''
|
142
|
+
@min_quantity = Max_value
|
143
|
+
@percent_offset = Max_value
|
144
|
+
@nbba_price_cap = Max_value
|
145
|
+
@starting_price = Max_value
|
146
|
+
@stock_ref_price = Max_value
|
147
|
+
@delta = Max_value
|
148
|
+
@delta_neutral_order_type = ''
|
149
|
+
@delta_neutral_aux_price = Max_value
|
150
|
+
@reference_price_type = Max_value
|
151
|
+
end # init
|
152
|
+
|
153
|
+
end # class Order
|
154
|
+
|
155
|
+
|
156
|
+
class Contract < AbstractDatum
|
157
|
+
|
158
|
+
# Valid security types (sec_type attribute)
|
159
|
+
SECURITY_TYPES =
|
160
|
+
{
|
161
|
+
:stock => "STK",
|
162
|
+
:option => "OPT",
|
163
|
+
:future => "FUT",
|
164
|
+
:index => "IND",
|
165
|
+
:futures_option => "FOP",
|
166
|
+
:forex => "CASH",
|
167
|
+
:bag => "BAG"
|
168
|
+
}
|
169
|
+
|
170
|
+
# note that the :description field is entirely local to ib-ruby, and not part of TWS.
|
171
|
+
# You can use it to store whatever arbitrary data you want.
|
172
|
+
|
173
|
+
attr_accessor(:symbol, :strike, :multiplier, :exchange, :currency,
|
174
|
+
:local_symbol, :combo_legs, :description)
|
175
|
+
|
176
|
+
# Bond values
|
177
|
+
attr_accessor(:cusip, :ratings, :desc_append, :bond_type, :coupon_type, :callable, :puttable,
|
178
|
+
:coupon, :convertible, :maturity, :issue_date)
|
179
|
+
|
180
|
+
attr_reader :sec_type, :expiry, :right, :primary_exchange
|
181
|
+
|
182
|
+
|
183
|
+
|
184
|
+
# some protective filters
|
185
|
+
|
186
|
+
def primary_exchange=(x)
|
187
|
+
x.upcase! if x.is_a?(String)
|
188
|
+
|
189
|
+
# per http://chuckcaplan.com/twsapi/index.php/Class%20Contract
|
190
|
+
raise(ArgumentError.new("Don't set primary_exchange to smart")) if x == "SMART"
|
191
|
+
|
192
|
+
@primary_exchange = x
|
193
|
+
end
|
194
|
+
|
195
|
+
def right=(x)
|
196
|
+
x.upcase! if x.is_a?(String)
|
197
|
+
x = nil if !x.nil? && x.empty?
|
198
|
+
raise(ArgumentError.new("Invalid right \"#{x}\" (must be one of PUT, CALL, P, C)")) unless x.nil? || [ "PUT", "CALL", "P", "C"].include?(x)
|
199
|
+
@right = x
|
200
|
+
end
|
201
|
+
|
202
|
+
def expiry=(x)
|
203
|
+
x = nil if !x.nil? && x.empty?
|
204
|
+
raise(ArgumentError.new("Invalid expiry \"#{x}\" (must be in format YYYYMM or YYYYMMDD)")) unless x.nil? || x.to_s =~ /^\d\d\d\d\d\d(\d\d)?$/
|
205
|
+
@expiry = x.to_s
|
206
|
+
end
|
207
|
+
|
208
|
+
def sec_type=(x)
|
209
|
+
x = nil if !x.nil? && x.empty?
|
210
|
+
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)
|
211
|
+
@sec_type = x
|
212
|
+
end
|
213
|
+
|
214
|
+
def reset
|
215
|
+
@combo_legs = Array.new
|
216
|
+
@strike = 0
|
217
|
+
end
|
218
|
+
|
219
|
+
# Different messages serialize contracts differently. Go figure.
|
220
|
+
def serialize_short(version)
|
221
|
+
q = [ self.symbol,
|
222
|
+
self.sec_type,
|
223
|
+
self.expiry,
|
224
|
+
self.strike,
|
225
|
+
self.right ]
|
226
|
+
|
227
|
+
q.push(self.multiplier) if version >= 15
|
228
|
+
q.concat([
|
229
|
+
self.exchange,
|
230
|
+
self.currency,
|
231
|
+
self.local_symbol
|
232
|
+
])
|
233
|
+
|
234
|
+
q
|
235
|
+
end # serialize
|
236
|
+
|
237
|
+
# This returns an Array of data from the given contract, in standard format.
|
238
|
+
# Note that it does not include the combo legs.
|
239
|
+
def serialize_long(version)
|
240
|
+
queue = [
|
241
|
+
self.symbol,
|
242
|
+
self.sec_type,
|
243
|
+
self.expiry,
|
244
|
+
self.strike,
|
245
|
+
self.right
|
246
|
+
]
|
247
|
+
|
248
|
+
queue.push(self.multiplier) if version >= 15
|
249
|
+
queue.push(self.exchange)
|
250
|
+
queue.push(self.primary_exchange) if version >= 14
|
251
|
+
queue.push(self.currency)
|
252
|
+
queue.push(self.local_symbol) if version >= 2
|
253
|
+
|
254
|
+
queue
|
255
|
+
end # serialize_long
|
256
|
+
|
257
|
+
#
|
258
|
+
# This produces a string uniquely identifying this contract, in the format used
|
259
|
+
# for command line arguments in the IB-Ruby examples. The format is:
|
260
|
+
#
|
261
|
+
# symbol:security_type:expiry:strike:right:multiplier:exchange:primary_exchange:currency:local_symbol
|
262
|
+
#
|
263
|
+
# Fields not needed for a particular security should be left blank (e.g. strike and right are only relevant for options.)
|
264
|
+
#
|
265
|
+
# For example, to query the British pound futures contract trading on Globex expiring in September, 2008,
|
266
|
+
# the string is:
|
267
|
+
#
|
268
|
+
# GBP:FUT:200809:::62500:GLOBEX::USD:
|
269
|
+
#
|
270
|
+
|
271
|
+
def serialize_ib_ruby(version)
|
272
|
+
serialize_long(version).join(":")
|
273
|
+
end
|
274
|
+
|
275
|
+
# This returns a Contract initialized from the serialize_ib_ruby format string.
|
276
|
+
def self.from_ib_ruby(string)
|
277
|
+
c = Contract.new
|
278
|
+
c.symbol, c.sec_type, c.expiry, c.strike, c.right, c.multiplier, c.exchange, c.primary_exchange, c.currency, c.local_symbol = string.split(":")
|
279
|
+
|
280
|
+
c
|
281
|
+
end
|
282
|
+
|
283
|
+
# Some messages send open_close too, some don't. WTF.
|
284
|
+
def serialize_combo_legs(include_open_close = false)
|
285
|
+
if self.combo_legs.nil?
|
286
|
+
[0]
|
287
|
+
else
|
288
|
+
[ self.combo_legs.size ].concat(self.combo_legs.serialize(include_open_close))
|
289
|
+
end
|
290
|
+
end
|
291
|
+
|
292
|
+
def init
|
293
|
+
super
|
294
|
+
|
295
|
+
@combo_legs = Array.new
|
296
|
+
@strike = 0
|
297
|
+
@sec_type = ''
|
298
|
+
end
|
299
|
+
|
300
|
+
def to_human
|
301
|
+
"<IB-Contract: " + [symbol, expiry, sec_type, strike, right, exchange, currency].join("-") + "}>"
|
302
|
+
end
|
303
|
+
|
304
|
+
def to_short
|
305
|
+
"#{symbol}#{expiry}#{strike}#{right}#{exchange}#{currency}"
|
306
|
+
end
|
307
|
+
|
308
|
+
def to_s
|
309
|
+
to_human
|
310
|
+
end
|
311
|
+
|
312
|
+
end # class Contract
|
313
|
+
|
314
|
+
|
315
|
+
class ContractDetails < AbstractDatum
|
316
|
+
attr_accessor :summary, :market_name, :trading_class, :con_id, :min_tick, :multiplier, :price_magnifier, :order_types, :valid_exchanges
|
317
|
+
|
318
|
+
def init
|
319
|
+
super
|
320
|
+
|
321
|
+
@summary = Contract.new
|
322
|
+
@con_id = 0
|
323
|
+
@min_tick = 0
|
324
|
+
end
|
325
|
+
end # class ContractDetails
|
326
|
+
|
327
|
+
|
328
|
+
class Execution < AbstractDatum
|
329
|
+
attr_accessor :order_id, :client_id, :exec_id, :time, :account_number, :exchange, :side, :shares, :price, :perm_id, :liquidation
|
330
|
+
|
331
|
+
def init
|
332
|
+
super
|
333
|
+
|
334
|
+
@order_id = 0
|
335
|
+
@client_id = 0
|
336
|
+
@shares = 0
|
337
|
+
@price = 0
|
338
|
+
@perm_id = 0
|
339
|
+
@liquidation =0
|
340
|
+
end
|
341
|
+
end # Execution
|
342
|
+
|
343
|
+
# EClientSocket.java tells us: 'Note that the valid format for m_time is "yyyymmdd-hh:mm:ss"'
|
344
|
+
class ExecutionFilter < AbstractDatum
|
345
|
+
attr_accessor :client_id, :acct_code, :time, :symbol, :sec_type, :exchange, :side
|
346
|
+
|
347
|
+
def init
|
348
|
+
super
|
349
|
+
|
350
|
+
@client_id = 0
|
351
|
+
end
|
352
|
+
|
353
|
+
end # ExecutionFilter
|
354
|
+
|
355
|
+
|
356
|
+
class ComboLeg < AbstractDatum
|
357
|
+
attr_accessor :con_id, :ratio, :action, :exchange, :open_close
|
358
|
+
|
359
|
+
def init
|
360
|
+
super
|
361
|
+
|
362
|
+
@con_id = 0
|
363
|
+
@ratio = 0
|
364
|
+
@open_close = 0
|
365
|
+
end
|
366
|
+
|
367
|
+
# Some messages include open_close, some don't. wtf.
|
368
|
+
def serialize(include_open_close = false)
|
369
|
+
self.collect { |leg|
|
370
|
+
[ leg.con_id, leg.ratio, leg.action, leg.exchange, (include_open_close ? leg.open_close : [] )]
|
371
|
+
}.flatten
|
372
|
+
end
|
373
|
+
end # ComboLeg
|
374
|
+
|
375
|
+
|
376
|
+
class ScannerSubscription < AbstractDatum
|
377
|
+
attr_accessor :number_of_rows, :instrument, :location_code, :scan_code, :above_price, :below_price,
|
378
|
+
:above_volume, :average_option_volume_above, :market_cap_above, :market_cap_below, :moody_rating_above,
|
379
|
+
:moody_rating_below, :sp_rating_above, :sp_rating_below, :maturity_date_above, :maturity_date_below,
|
380
|
+
:coupon_rate_above, :coupon_rate_below, :exclude_convertible, :scanner_setting_pairs, :stock_type_filter
|
381
|
+
|
382
|
+
def init
|
383
|
+
super
|
384
|
+
|
385
|
+
@coupon_rate_above = @coupon_rate_below = @market_cap_below = @market_cap_above = @average_option_volume_above =
|
386
|
+
@above_volume = @below_price = @above_price = nil
|
387
|
+
@number_of_rows = -1 # none specified, per ScannerSubscription.java
|
388
|
+
end
|
389
|
+
end # ScannerSubscription
|
390
|
+
|
391
|
+
|
392
|
+
# Just like a Hash, but throws an exception if you try to access a key that doesn't exist.
|
393
|
+
class StringentHash < Hash
|
394
|
+
def initialize(hash)
|
395
|
+
super() {|hash,key| raise Exception.new("key #{key.inspect} not found!") }
|
396
|
+
self.merge!(hash) unless hash.nil?
|
397
|
+
end
|
398
|
+
end
|
399
|
+
|
400
|
+
end # module Datatypes
|
401
|
+
|
402
|
+
end # module
|