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.
- data/.gitignore +32 -0
- data/HISTORY +68 -0
- data/README.rdoc +9 -6
- data/VERSION +1 -1
- data/bin/account_info +29 -0
- data/bin/contract_details +37 -0
- data/bin/depth_of_market +43 -0
- data/bin/historic_data +62 -0
- data/bin/{RequestHistoricData → historic_data_cli} +46 -91
- data/bin/market_data +49 -0
- data/bin/option_data +45 -0
- data/bin/template +21 -0
- data/bin/time_and_sales +63 -0
- data/lib/ib-ruby/connection.rb +166 -0
- data/lib/ib-ruby/constants.rb +91 -0
- data/lib/ib-ruby/messages/incoming.rb +807 -0
- data/lib/ib-ruby/messages/outgoing.rb +573 -0
- data/lib/ib-ruby/messages.rb +8 -1445
- data/lib/ib-ruby/models/bar.rb +26 -0
- data/lib/ib-ruby/models/contract.rb +335 -0
- data/lib/ib-ruby/models/execution.rb +55 -0
- data/lib/ib-ruby/models/model.rb +20 -0
- data/lib/ib-ruby/models/order.rb +262 -0
- data/lib/ib-ruby/models.rb +11 -0
- data/lib/ib-ruby/socket.rb +50 -0
- data/lib/ib-ruby/symbols/forex.rb +32 -72
- data/lib/ib-ruby/symbols/futures.rb +47 -68
- data/lib/ib-ruby/symbols/options.rb +30 -0
- data/lib/ib-ruby/symbols/stocks.rb +23 -0
- data/lib/ib-ruby/symbols.rb +9 -0
- data/lib/ib-ruby.rb +7 -8
- data/lib/legacy/bin/account_info_old +36 -0
- data/lib/legacy/bin/historic_data_old +81 -0
- data/lib/legacy/bin/market_data_old +68 -0
- data/lib/legacy/datatypes.rb +485 -0
- data/lib/legacy/ib-ruby.rb +10 -0
- data/lib/legacy/ib.rb +226 -0
- data/lib/legacy/messages.rb +1458 -0
- data/lib/version.rb +2 -3
- data/spec/ib-ruby/models/contract_spec.rb +261 -0
- data/spec/ib-ruby/models/order_spec.rb +64 -0
- data/spec/ib-ruby_spec.rb +0 -131
- metadata +106 -76
- data/bin/AccountInfo +0 -67
- data/bin/HistoricToCSV +0 -111
- data/bin/RequestMarketData +0 -78
- data/bin/SimpleTimeAndSales +0 -98
- data/bin/ib-ruby +0 -8
- data/lib/ib-ruby/datatypes.rb +0 -400
- data/lib/ib-ruby/ib.rb +0 -242
data/bin/time_and_sales
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# This script connects to IB API and subscribes to market data for specific symbols.
|
4
|
+
# It then prints out all trades that exceed certain size.
|
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
|
+
# Define the symbols we're interested in.
|
15
|
+
@market = {
|
16
|
+
123 => IB::Symbols::Futures[:gbp],
|
17
|
+
234 => IB::Symbols::Futures[:jpy]
|
18
|
+
}
|
19
|
+
|
20
|
+
# To determine when the timeout has passed.
|
21
|
+
@last_msg_time = Time.now.to_i + 2
|
22
|
+
|
23
|
+
# Connect to IB TWS.
|
24
|
+
ib = IB::Connection.new
|
25
|
+
|
26
|
+
# Subscribe to TWS alerts/errors
|
27
|
+
ib.subscribe(:Alert) { |msg| puts msg.to_human }
|
28
|
+
|
29
|
+
MIN_SIZE = 0
|
30
|
+
|
31
|
+
# This method filters out non-:last type events, and filters out any sale < MIN_SIZE.
|
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
|
+
# (N.B. The description field is not from IB TWS. It is defined
|
35
|
+
# locally in forex.rb, and is just arbitrary text.)
|
36
|
+
def show_sales_and_size(msg)
|
37
|
+
#return if msg.type != :last_price || msg.data[:size] < MIN_SIZE
|
38
|
+
puts @market[msg.data[:id]].description + ": " +
|
39
|
+
(msg.is_a?(IB::Messages::Incoming::TickPrice) ?
|
40
|
+
"#{msg.data[:size]} at #{msg.data[:price]}" : msg.to_human)
|
41
|
+
end
|
42
|
+
|
43
|
+
def show_size(msg)
|
44
|
+
return if msg.type != :last_size || msg.data[:size] < MIN_SIZE
|
45
|
+
end
|
46
|
+
|
47
|
+
# Now, subscribe to TickerPrice and TickerSize events. The code passed in the block
|
48
|
+
# will be executed when a message of that type is received, with the received message
|
49
|
+
# as its argument. In this case, we just print out the tick.
|
50
|
+
ib.subscribe(:TickPrice, :TickSize, :TickString) { |msg| show_sales_and_size(msg) }
|
51
|
+
|
52
|
+
# Now we actually request market data for the symbols we're interested in.
|
53
|
+
|
54
|
+
@market.each_pair do |id, contract|
|
55
|
+
ib.send :RequestMarketData, :id => id, :contract => contract
|
56
|
+
end
|
57
|
+
|
58
|
+
puts "\nSubscribed to TWS market data"
|
59
|
+
puts "\n******** Press <Enter> to cancel... *********\n\n"
|
60
|
+
gets
|
61
|
+
puts "Unsubscribing from TWS market data.."
|
62
|
+
|
63
|
+
@market.each_pair { |id, contract| ib.send :CancelMarketData, :id => id }
|
@@ -0,0 +1,166 @@
|
|
1
|
+
require 'ib-ruby/socket'
|
2
|
+
require 'logger'
|
3
|
+
|
4
|
+
if RUBY_VERSION < "1.9"
|
5
|
+
require 'sha1'
|
6
|
+
else
|
7
|
+
require 'digest/sha1'
|
8
|
+
include Digest
|
9
|
+
end
|
10
|
+
|
11
|
+
# Add method to_ib to render datetime in IB format (zero padded "yyyymmdd HH:mm:ss")
|
12
|
+
class Time
|
13
|
+
def to_ib
|
14
|
+
"#{self.year}#{sprintf("%02d", self.month)}#{sprintf("%02d", self.day)} " +
|
15
|
+
"#{sprintf("%02d", self.hour)}:#{sprintf("%02d", self.min)}:#{sprintf("%02d", self.sec)}"
|
16
|
+
end
|
17
|
+
end # Time
|
18
|
+
|
19
|
+
module IB
|
20
|
+
# Encapsulates API connection to TWS or Gateway
|
21
|
+
class Connection
|
22
|
+
|
23
|
+
# Please note, we are realizing only the most current TWS protocol versions,
|
24
|
+
# thus improving performance at the expense of backwards compatibility.
|
25
|
+
# Older protocol versions support can be found in older gem versions.
|
26
|
+
|
27
|
+
CLIENT_VERSION = 48 # Was 27 in original Ruby code
|
28
|
+
SERVER_VERSION = 53 # Minimal server version. Latest, was 38 in current Java code.
|
29
|
+
DEFAULT_OPTIONS = {:host =>"127.0.0.1",
|
30
|
+
:port => '4001', # Gateway, TWS: '7496'
|
31
|
+
:open => true
|
32
|
+
}
|
33
|
+
|
34
|
+
attr_reader :next_order_id
|
35
|
+
|
36
|
+
def initialize(opts = {})
|
37
|
+
@options = DEFAULT_OPTIONS.merge(opts)
|
38
|
+
|
39
|
+
@connected = false
|
40
|
+
@next_order_id = nil
|
41
|
+
@server = Hash.new # information about server and server connection state
|
42
|
+
|
43
|
+
# Message listeners. Key is the message class to listen for.
|
44
|
+
# Value is an Array of Procs. The proc will be called with the populated message
|
45
|
+
# instance as its argument when a message of that type is received.
|
46
|
+
# TODO: change Array of Procs into a Hash to allow unsubscribing
|
47
|
+
@listeners = Hash.new { |hash, key| hash[key] = Array.new }
|
48
|
+
|
49
|
+
self.open(@options) if @options[:open]
|
50
|
+
end
|
51
|
+
|
52
|
+
def server_version
|
53
|
+
@server[:version]
|
54
|
+
end
|
55
|
+
|
56
|
+
def open(opts = {})
|
57
|
+
raise Exception.new("Already connected!") if @connected
|
58
|
+
|
59
|
+
opts = @options.merge(opts)
|
60
|
+
|
61
|
+
# Subscribe to the NextValidID message from TWS that is always
|
62
|
+
# sent at connect, and save the id.
|
63
|
+
self.subscribe(Messages::Incoming::NextValidID) do |msg|
|
64
|
+
@next_order_id = msg.data[:id]
|
65
|
+
puts "Got next valid order id #{@next_order_id}."
|
66
|
+
end
|
67
|
+
|
68
|
+
@server[:socket] = IBSocket.open(opts[:host], opts[:port])
|
69
|
+
|
70
|
+
# Secret handshake.
|
71
|
+
@server[:socket].send(CLIENT_VERSION)
|
72
|
+
@server[:version] = @server[:socket].read_int
|
73
|
+
@server[:local_connect_time] = Time.now()
|
74
|
+
raise(Exception.new("TWS version >= #{SERVER_VERSION} required.")) if @server[:version] < SERVER_VERSION
|
75
|
+
|
76
|
+
puts "\tGot server version: #{@server[:version]}."
|
77
|
+
#logger.debug("\tGot server version: #{@server[:version]}.")
|
78
|
+
|
79
|
+
# Server version >= 20 sends the server time back. Our min server version is 38
|
80
|
+
@server[:remote_connect_time] = @server[:socket].read_string
|
81
|
+
#logger.debug("\tServer connect time: #{@server[:remote_connect_time]}.")
|
82
|
+
|
83
|
+
# Server wants an arbitrary client ID at this point. This can be used
|
84
|
+
# to identify subsequent communications.
|
85
|
+
@server[:client_id] = SHA1.digest(Time.now.to_s + $$.to_s).unpack("C*").join.to_i % 999999999
|
86
|
+
@server[:socket].send(@server[:client_id])
|
87
|
+
#logger.debug("\tSent client id # #{@server[:client_id]}.")
|
88
|
+
|
89
|
+
#logger.debug("Starting reader thread..")
|
90
|
+
Thread.abort_on_exception = true
|
91
|
+
@server[:reader_thread] = Thread.new { self.reader }
|
92
|
+
|
93
|
+
@connected = true
|
94
|
+
end
|
95
|
+
|
96
|
+
def close
|
97
|
+
@server[:reader_thread].kill # Thread uses blocking I/O, so join is useless.
|
98
|
+
@server[:socket].close()
|
99
|
+
@server = Hash.new
|
100
|
+
@@server_version = nil
|
101
|
+
@connected = false
|
102
|
+
#logger.debug("Disconnected.")
|
103
|
+
end
|
104
|
+
|
105
|
+
def to_s
|
106
|
+
"IB Connector: #{ @connected ? "connected." : "disconnected."}"
|
107
|
+
end
|
108
|
+
|
109
|
+
# Subscribe to incoming message events of type message_class.
|
110
|
+
# code is a Proc that will be called with the message instance as its argument.
|
111
|
+
def subscribe(*args, &block)
|
112
|
+
code = args.last.respond_to?(:call) ? args.pop : block
|
113
|
+
|
114
|
+
raise ArgumentError.new "Need listener proc or block" unless code.is_a? Proc
|
115
|
+
|
116
|
+
args.each do |message_class|
|
117
|
+
if message_class.is_a? Symbol
|
118
|
+
message_class = Messages::Incoming.const_get(message_class)
|
119
|
+
end
|
120
|
+
|
121
|
+
unless message_class < Messages::Incoming::AbstractMessage
|
122
|
+
raise ArgumentError.new "#{message_class} must be an IB message class"
|
123
|
+
end
|
124
|
+
|
125
|
+
@listeners[message_class].push(code)
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
# Send an outgoing message.
|
130
|
+
def send(message, *args)
|
131
|
+
if message.is_a? Symbol
|
132
|
+
message = Messages::Outgoing.const_get(message).new *args
|
133
|
+
end
|
134
|
+
|
135
|
+
raise Exception.new("only sending Messages::Outgoing") unless message.is_a? Messages::Outgoing::AbstractMessage
|
136
|
+
|
137
|
+
message.send(@server)
|
138
|
+
end
|
139
|
+
|
140
|
+
protected
|
141
|
+
|
142
|
+
def reader
|
143
|
+
loop do
|
144
|
+
# this blocks, so Thread#join is useless.
|
145
|
+
msg_id = @server[:socket].read_int
|
146
|
+
|
147
|
+
# Debug:
|
148
|
+
unless [1, 2, 4, 6, 7, 8, 9, 21, 53].include? msg_id
|
149
|
+
puts "Got message #{msg_id} (#{Messages::Incoming::Table[msg_id]})"
|
150
|
+
end
|
151
|
+
|
152
|
+
# Create a new instance of the appropriate message type, and have it read the message.
|
153
|
+
# NB: Failure here usually means unsupported message type received
|
154
|
+
msg = Messages::Incoming::Table[msg_id].new(@server[:socket], @server[:version])
|
155
|
+
|
156
|
+
if @listeners[msg.class].size > 0
|
157
|
+
@listeners[msg.class].each { |listener| listener.call(msg) }
|
158
|
+
else
|
159
|
+
# Warn if nobody listened to an incoming message.
|
160
|
+
puts " WARNING: Nobody listened to incoming message #{msg.class}"
|
161
|
+
end
|
162
|
+
end # loop
|
163
|
+
end # reader
|
164
|
+
end # class Connection
|
165
|
+
IB = Connection # Legacy alias
|
166
|
+
end # module IB
|
@@ -0,0 +1,91 @@
|
|
1
|
+
module IB
|
2
|
+
### Widely used TWS constants:
|
3
|
+
|
4
|
+
EOL = "\0"
|
5
|
+
|
6
|
+
#FaMsgTypeName = {1 => "GROUPS",
|
7
|
+
# 2 => "PROFILES",
|
8
|
+
# 3 =>"ALIASES"}
|
9
|
+
|
10
|
+
# Enumeration of data types
|
11
|
+
DATA_TYPES = [:trades, :midpoint, :bid, :ask]
|
12
|
+
|
13
|
+
# Enumeration of bar size types for convenience.
|
14
|
+
# Bar sizes less than 30 seconds do not work for some securities.
|
15
|
+
BAR_SIZES = ['1 sec', '5 secs', '15 secs', '30 secs',
|
16
|
+
'1 min', '2 mins', '3 mins', '5 mins',
|
17
|
+
'15 mins', '30 mins', '1 hour', '1 day']
|
18
|
+
|
19
|
+
# Tick types as received in TickPrice and TickSize messages (enumeration)
|
20
|
+
TICK_TYPES = {
|
21
|
+
# int id => :Description # Corresponding API Event/Function/Method
|
22
|
+
0 => :bid_size, # tickSize()
|
23
|
+
1 => :bid_price, # tickPrice()
|
24
|
+
2 => :ask_price, # tickPrice()
|
25
|
+
3 => :ask_size, # tickSize()
|
26
|
+
4 => :last_price, # tickPrice()
|
27
|
+
5 => :last_size, # tickSize()
|
28
|
+
6 => :high, # tickPrice()
|
29
|
+
7 => :low, # tickPrice()
|
30
|
+
8 => :volume, # tickSize()
|
31
|
+
9 => :close_price, # tickPrice()
|
32
|
+
10 => :bid_option, # tickOptionComputation() See Note 1 below
|
33
|
+
11 => :ask_option, # tickOptionComputation() See => :Note 1 below
|
34
|
+
12 => :last_option, # tickOptionComputation() See Note 1 below
|
35
|
+
13 => :model_option, # tickOptionComputation() See Note 1 below
|
36
|
+
14 => :open_tick, # tickPrice()
|
37
|
+
15 => :low_13_week, # tickPrice()
|
38
|
+
16 => :high_13_week, # tickPrice()
|
39
|
+
17 => :low_26_week, # tickPrice()
|
40
|
+
18 => :high_26_week, # tickPrice()
|
41
|
+
19 => :low_52_week, # tickPrice()
|
42
|
+
20 => :high_52_week, # tickPrice()
|
43
|
+
21 => :avg_volume, # tickSize()
|
44
|
+
22 => :open_interest, # tickSize()
|
45
|
+
23 => :option_historical_vol, # tickGeneric()
|
46
|
+
24 => :option_implied_vol, # tickGeneric()
|
47
|
+
25 => :option_bid_exch, # not USED
|
48
|
+
26 => :option_ask_exch, # not USED
|
49
|
+
27 => :option_call_open_interest, # tickSize()
|
50
|
+
28 => :option_put_open_interest, # tickSize()
|
51
|
+
29 => :option_call_volume, # tickSize()
|
52
|
+
30 => :option_put_volume, # tickSize()
|
53
|
+
31 => :index_future_premium, # tickGeneric()
|
54
|
+
32 => :bid_exch, # tickString()
|
55
|
+
33 => :ask_exch, # tickString()
|
56
|
+
34 => :auction_volume, # not USED
|
57
|
+
35 => :auction_price, # not USED
|
58
|
+
36 => :auction_imbalance, # not USED
|
59
|
+
37 => :mark_price, # tickPrice()
|
60
|
+
38 => :bid_efp_computation, # tickEFP()
|
61
|
+
39 => :ask_efp_computation, # tickEFP()
|
62
|
+
40 => :last_efp_computation, # tickEFP()
|
63
|
+
41 => :open_efp_computation, # tickEFP()
|
64
|
+
42 => :high_efp_computation, # tickEFP()
|
65
|
+
43 => :low_efp_computation, # tickEFP()
|
66
|
+
44 => :close_efp_computation, # tickEFP()
|
67
|
+
45 => :last_timestamp, # tickString()
|
68
|
+
46 => :shortable, # tickGeneric()
|
69
|
+
47 => :fundamental_ratios, # tickString()
|
70
|
+
48 => :rt_volume, # tickGeneric()
|
71
|
+
49 => :halted, # see Note 2 below.
|
72
|
+
50 => :bid_yield, # tickPrice() See Note 3 below
|
73
|
+
51 => :asky_ield, # tickPrice() See Note 3 below
|
74
|
+
52 => :last_yield, # tickPrice() See Note 3 below
|
75
|
+
53 => :cust_option_computation, # tickOptionComputation()
|
76
|
+
54 => :trade_count, # tickGeneric()
|
77
|
+
55 => :trade_rate, # tickGeneric()
|
78
|
+
56 => :volume_rate, # tickGeneric()
|
79
|
+
57 => :last_rth_trade, # ?
|
80
|
+
# Note 1: Tick types BID_OPTION, ASK_OPTION, LAST_OPTION, and MODEL_OPTION return
|
81
|
+
# all Greeks (delta, gamma, vega, theta), the underlying price and the
|
82
|
+
# stock and option reference price when requested.
|
83
|
+
# MODEL_OPTION also returns model implied volatility.
|
84
|
+
# Note 2: When trading is halted for a contract, TWS receives a special tick:
|
85
|
+
# haltedLast=1. When trading is resumed, TWS receives haltedLast=0.
|
86
|
+
# A tick type, HALTED, tick ID= 49, is now available in regular market
|
87
|
+
# data via the API to indicate this halted state. Possible values for
|
88
|
+
# this new tick type are: 0 = Not halted, 1 = Halted.
|
89
|
+
# Note 3: Applies to bond contracts only.
|
90
|
+
}
|
91
|
+
end # module IB
|