ib-api 972.0
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.
- checksums.yaml +7 -0
- data/.gitignore +50 -0
- data/.rspec +3 -0
- data/.travis.yml +7 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +16 -0
- data/Gemfile.lock +105 -0
- data/Guardfile +24 -0
- data/LICENSE +674 -0
- data/README.md +65 -0
- data/Rakefile +11 -0
- data/VERSION +1 -0
- data/api.gemspec +43 -0
- data/bin/console +95 -0
- data/bin/console.yml +3 -0
- data/bin/setup +8 -0
- data/changelog.md +7 -0
- data/example/README.md +76 -0
- data/example/account_info +54 -0
- data/example/account_positions +30 -0
- data/example/account_summary +88 -0
- data/example/cancel_orders +74 -0
- data/example/fa_accounts +25 -0
- data/example/fundamental_data +40 -0
- data/example/historic_data_cli +186 -0
- data/example/list_orders +45 -0
- data/example/portfolio_csv +81 -0
- data/example/scanner_data +62 -0
- data/example/template +19 -0
- data/example/tick_data +28 -0
- data/lib/extensions/class-extensions.rb +87 -0
- data/lib/ib-api.rb +7 -0
- data/lib/ib/base.rb +103 -0
- data/lib/ib/base_properties.rb +160 -0
- data/lib/ib/connection.rb +450 -0
- data/lib/ib/constants.rb +393 -0
- data/lib/ib/errors.rb +44 -0
- data/lib/ib/logger.rb +26 -0
- data/lib/ib/messages.rb +99 -0
- data/lib/ib/messages/abstract_message.rb +101 -0
- data/lib/ib/messages/incoming.rb +251 -0
- data/lib/ib/messages/incoming/abstract_message.rb +116 -0
- data/lib/ib/messages/incoming/account_value.rb +78 -0
- data/lib/ib/messages/incoming/alert.rb +34 -0
- data/lib/ib/messages/incoming/contract_data.rb +102 -0
- data/lib/ib/messages/incoming/delta_neutral_validation.rb +23 -0
- data/lib/ib/messages/incoming/execution_data.rb +50 -0
- data/lib/ib/messages/incoming/historical_data.rb +84 -0
- data/lib/ib/messages/incoming/market_depths.rb +44 -0
- data/lib/ib/messages/incoming/next_valid_id.rb +18 -0
- data/lib/ib/messages/incoming/open_order.rb +277 -0
- data/lib/ib/messages/incoming/order_status.rb +85 -0
- data/lib/ib/messages/incoming/portfolio_value.rb +78 -0
- data/lib/ib/messages/incoming/real_time_bar.rb +32 -0
- data/lib/ib/messages/incoming/scanner_data.rb +54 -0
- data/lib/ib/messages/incoming/ticks.rb +268 -0
- data/lib/ib/messages/outgoing.rb +437 -0
- data/lib/ib/messages/outgoing/abstract_message.rb +88 -0
- data/lib/ib/messages/outgoing/account_requests.rb +112 -0
- data/lib/ib/messages/outgoing/bar_requests.rb +250 -0
- data/lib/ib/messages/outgoing/place_order.rb +209 -0
- data/lib/ib/messages/outgoing/request_marketdata.rb +99 -0
- data/lib/ib/messages/outgoing/request_tick_data.rb +21 -0
- data/lib/ib/model.rb +4 -0
- data/lib/ib/models.rb +14 -0
- data/lib/ib/server_versions.rb +114 -0
- data/lib/ib/socket.rb +185 -0
- data/lib/ib/support.rb +160 -0
- data/lib/ib/version.rb +6 -0
- data/lib/models/ib/account.rb +85 -0
- data/lib/models/ib/account_value.rb +33 -0
- data/lib/models/ib/bag.rb +55 -0
- data/lib/models/ib/bar.rb +31 -0
- data/lib/models/ib/combo_leg.rb +105 -0
- data/lib/models/ib/condition.rb +245 -0
- data/lib/models/ib/contract.rb +415 -0
- data/lib/models/ib/contract_detail.rb +108 -0
- data/lib/models/ib/execution.rb +67 -0
- data/lib/models/ib/forex.rb +13 -0
- data/lib/models/ib/future.rb +15 -0
- data/lib/models/ib/index.rb +15 -0
- data/lib/models/ib/option.rb +78 -0
- data/lib/models/ib/option_detail.rb +55 -0
- data/lib/models/ib/order.rb +519 -0
- data/lib/models/ib/order_state.rb +152 -0
- data/lib/models/ib/portfolio_value.rb +64 -0
- data/lib/models/ib/stock.rb +16 -0
- data/lib/models/ib/underlying.rb +34 -0
- data/lib/models/ib/vertical.rb +96 -0
- data/lib/requires.rb +12 -0
- metadata +203 -0
data/lib/ib/socket.rb
ADDED
@@ -0,0 +1,185 @@
|
|
1
|
+
require 'socket'
|
2
|
+
module IBSupport
|
3
|
+
refine Array do
|
4
|
+
def tws
|
5
|
+
if blank?
|
6
|
+
nil.tws
|
7
|
+
else
|
8
|
+
self.flatten.map( &:tws ).join # [ "", [] , nil].flatten -> ["", nil]
|
9
|
+
# elemets with empty array's are cut
|
10
|
+
# this is the desired behavior!
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
refine Symbol do
|
15
|
+
def tws
|
16
|
+
self.to_s.tws
|
17
|
+
end
|
18
|
+
end
|
19
|
+
refine String do
|
20
|
+
def tws
|
21
|
+
if empty?
|
22
|
+
IB::EOL
|
23
|
+
else
|
24
|
+
self[-1] == IB::EOL ? self : self+IB::EOL
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
refine Numeric do
|
30
|
+
def tws
|
31
|
+
self.to_s.tws
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
refine TrueClass do
|
36
|
+
def tws
|
37
|
+
1.tws
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
refine FalseClass do
|
42
|
+
def tws
|
43
|
+
0.tws
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
refine NilClass do
|
48
|
+
def tws
|
49
|
+
IB::EOL
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
module IB
|
54
|
+
# includes methods from IBSupport
|
55
|
+
# which adds a tws-method to
|
56
|
+
# - Array
|
57
|
+
# - Symbol
|
58
|
+
# - String
|
59
|
+
# - Numeric
|
60
|
+
# - TrueClass, FalseClass and NilClass
|
61
|
+
#
|
62
|
+
module PrepareData
|
63
|
+
using IBSupport
|
64
|
+
# First call the method #tws on the data-object
|
65
|
+
#
|
66
|
+
# Then transfom into an Array using the #Pack-Method
|
67
|
+
#
|
68
|
+
# The optional Block introduces a user-defined pattern to pack the data.
|
69
|
+
#
|
70
|
+
# Default is "Na*"
|
71
|
+
def prepare_message data
|
72
|
+
data = data.tws unless data.is_a?(String) && data[-1]== EOL
|
73
|
+
matrize = [data.size,data]
|
74
|
+
if block_given? # A user defined decoding-sequence is accepted via block
|
75
|
+
matrize.pack yield
|
76
|
+
else
|
77
|
+
matrize.pack "Na*"
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
# The received package is decoded. The parameter (msg) is an Array
|
82
|
+
#
|
83
|
+
# The protocol is simple: Every Element is treated as Character.
|
84
|
+
# Exception: The first Element determines the expected length.
|
85
|
+
#
|
86
|
+
# The decoded raw-message can further modified by the optional block.
|
87
|
+
#
|
88
|
+
# The default is to instantiate a Hash: message_id becomes the key.
|
89
|
+
# The Hash is returned
|
90
|
+
#
|
91
|
+
# If a block is provided, no Hash is build and the modified raw-message is returned
|
92
|
+
def decode_message msg
|
93
|
+
m = Hash.new
|
94
|
+
while not msg.blank?
|
95
|
+
# the first item is the length
|
96
|
+
size= msg[0..4].unpack("N").first
|
97
|
+
msg = msg[4..-1]
|
98
|
+
# followed by a sequence of characters
|
99
|
+
message = msg.unpack("A#{size}").first.split("\0")
|
100
|
+
if block_given?
|
101
|
+
yield message
|
102
|
+
else
|
103
|
+
m[message.shift.to_i] = message
|
104
|
+
end
|
105
|
+
msg = msg[size..-1]
|
106
|
+
end
|
107
|
+
return m unless block_given?
|
108
|
+
end
|
109
|
+
|
110
|
+
end
|
111
|
+
|
112
|
+
class IBSocket < TCPSocket
|
113
|
+
include PrepareData
|
114
|
+
using IBSupport
|
115
|
+
|
116
|
+
def initialising_handshake
|
117
|
+
v100_prefix = "API".tws.encode 'ascii'
|
118
|
+
v100_version = self.prepare_message Messages::SERVER_VERSION
|
119
|
+
write_data v100_prefix+v100_version
|
120
|
+
## start tws-log
|
121
|
+
# [QO] INFO [JTS-SocketListener-49] - State: HEADER, IsAPI: UNKNOWN
|
122
|
+
# [QO] INFO [JTS-SocketListener-49] - State: STOP, IsAPI: YES
|
123
|
+
# [QO] INFO [JTS-SocketListener-49] - ArEServer: Adding 392382055 with id 2147483647
|
124
|
+
# [QO] INFO [JTS-SocketListener-49] - eServersChanged: 1
|
125
|
+
# [QO] INFO [JTS-EServerSocket-287] - [2147483647:136:136:1:0:0:0:SYS] Starting new conversation with client on 127.0.0.1
|
126
|
+
# [QO] INFO [JTS-EServerSocketNotifier-288] - Starting async queue thread
|
127
|
+
# [QO] INFO [JTS-EServerSocket-287] - [2147483647:136:136:1:0:0:0:SYS] Server version is 136
|
128
|
+
# [QO] INFO [JTS-EServerSocket-287] - [2147483647:136:136:1:0:0:0:SYS] Client version is 136
|
129
|
+
# [QO] INFO [JTS-EServerSocket-287] - [2147483647:136:136:1:0:0:0:SYS] is 3rdParty true
|
130
|
+
## end tws-log
|
131
|
+
end
|
132
|
+
|
133
|
+
|
134
|
+
def read_string
|
135
|
+
string = self.gets(EOL)
|
136
|
+
|
137
|
+
until string
|
138
|
+
# Silently ignores nils
|
139
|
+
string = self.gets(EOL)
|
140
|
+
sleep 0.1
|
141
|
+
end
|
142
|
+
|
143
|
+
string.chomp
|
144
|
+
end
|
145
|
+
|
146
|
+
|
147
|
+
# Sends null terminated data string into socket
|
148
|
+
def write_data data
|
149
|
+
self.syswrite data.tws
|
150
|
+
end
|
151
|
+
|
152
|
+
# send the message (containing several instructions) to the socket,
|
153
|
+
# calls prepare_message to convert data-elements into NULL-terminated strings
|
154
|
+
def send_messages *data
|
155
|
+
self.syswrite prepare_message(data)
|
156
|
+
rescue Errno::ECONNRESET => e
|
157
|
+
Connection.logger.error{ "Data not accepted by IB \n
|
158
|
+
#{data.inspect} \n
|
159
|
+
Backtrace:\n "}
|
160
|
+
Connection.logger.error e.backtrace
|
161
|
+
end
|
162
|
+
|
163
|
+
def recieve_messages
|
164
|
+
begin
|
165
|
+
complete_message_buffer = []
|
166
|
+
begin
|
167
|
+
# this is the blocking version of recv
|
168
|
+
buffer = self.recvfrom(4096)[0]
|
169
|
+
# STDOUT.puts "BUFFER:: #{buffer.inspect}"
|
170
|
+
complete_message_buffer << buffer
|
171
|
+
|
172
|
+
end while buffer.size == 4096
|
173
|
+
complete_message_buffer.join('')
|
174
|
+
rescue Errno::ECONNRESET => e
|
175
|
+
Connection.logger.error{ "Data Buffer is not filling \n
|
176
|
+
The Buffer: #{buffer.inspect} \n
|
177
|
+
Backtrace:\n
|
178
|
+
#{e.backtrace.join("\n") } " }
|
179
|
+
Kernel.exit
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
end # class IBSocket
|
184
|
+
|
185
|
+
end # module IB
|
data/lib/ib/support.rb
ADDED
@@ -0,0 +1,160 @@
|
|
1
|
+
|
2
|
+
module IBSupport
|
3
|
+
refine Array do
|
4
|
+
|
5
|
+
def zero?
|
6
|
+
false
|
7
|
+
end
|
8
|
+
# Returns the integer.
|
9
|
+
# retuns nil otherwise or if no element is left on the stack
|
10
|
+
def read_int
|
11
|
+
i= self.shift rescue nil
|
12
|
+
i = i.to_i unless i.blank? # this includes conversion of string to zero(0)
|
13
|
+
i.is_a?( Integer ) ? i : nil
|
14
|
+
end
|
15
|
+
|
16
|
+
def read_float
|
17
|
+
i= self.shift rescue nil
|
18
|
+
i = i.to_f unless i.blank?
|
19
|
+
|
20
|
+
end
|
21
|
+
def read_decimal
|
22
|
+
i= self.shift rescue nil
|
23
|
+
i = i.to_d unless i.blank?
|
24
|
+
i.is_a?(Numeric) && i < IB::TWS_MAX ? i : nil # return nil, if a very large number is transmitted
|
25
|
+
end
|
26
|
+
|
27
|
+
alias read_decimal_max read_decimal
|
28
|
+
|
29
|
+
## Values -1 and below indicate: Not computed (TickOptionComputation)
|
30
|
+
def read_decimal_limit_1
|
31
|
+
i= read_decimal
|
32
|
+
i <= -1 ? nil : i
|
33
|
+
end
|
34
|
+
|
35
|
+
## Values -2 and below indicate: Not computed (TickOptionComputation)
|
36
|
+
def read_decimal_limit_2
|
37
|
+
i= read_decimal
|
38
|
+
i <= -2 ? nil : i
|
39
|
+
end
|
40
|
+
|
41
|
+
|
42
|
+
def read_string
|
43
|
+
self.shift rescue ""
|
44
|
+
end
|
45
|
+
## reads a string and proofs if NULL == IB::TWS_MAX is present.
|
46
|
+
## in that case: returns nil. otherwise: returns the string
|
47
|
+
def read_string_not_null
|
48
|
+
r = read_string
|
49
|
+
rd = r.to_d unless r.blank?
|
50
|
+
rd.is_a?(Numeric) && rd >= IB::TWS_MAX ? nil : r
|
51
|
+
end
|
52
|
+
|
53
|
+
def read_symbol
|
54
|
+
read_string.to_sym
|
55
|
+
end
|
56
|
+
|
57
|
+
# convert xml into a hash
|
58
|
+
def read_xml
|
59
|
+
Ox.load( read_string(), mode: :hash_no_attrs)
|
60
|
+
end
|
61
|
+
|
62
|
+
|
63
|
+
def read_int_date
|
64
|
+
t= read_int
|
65
|
+
s= Time.at(t)
|
66
|
+
# s.year == 1970 --> data is most likely a date-string
|
67
|
+
s.year == 1970 ? Date.parse(t.to_s) : s
|
68
|
+
end
|
69
|
+
|
70
|
+
def read_parse_date
|
71
|
+
Time.parse read_string
|
72
|
+
end
|
73
|
+
|
74
|
+
def read_boolean
|
75
|
+
|
76
|
+
v = self.shift rescue nil
|
77
|
+
case v
|
78
|
+
when "1"
|
79
|
+
true
|
80
|
+
when "0"
|
81
|
+
false
|
82
|
+
else nil
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
|
87
|
+
def read_datetime
|
88
|
+
the_string = read_string
|
89
|
+
the_string.blank? ? nil : DateTime.parse(the_string)
|
90
|
+
end
|
91
|
+
|
92
|
+
def read_date
|
93
|
+
the_string = read_string
|
94
|
+
the_string.blank? ? nil : Date.parse(the_string)
|
95
|
+
end
|
96
|
+
# def read_array
|
97
|
+
# count = read_int
|
98
|
+
# end
|
99
|
+
|
100
|
+
## originally provided in socket.rb
|
101
|
+
# # Returns loaded Array or [] if count was 0#
|
102
|
+
#
|
103
|
+
# Without providing a Block, the elements are treated as string
|
104
|
+
def read_array hashmode:false, &block
|
105
|
+
count = read_int
|
106
|
+
case count
|
107
|
+
when 0
|
108
|
+
[]
|
109
|
+
when nil
|
110
|
+
nil
|
111
|
+
else
|
112
|
+
count= count + count if hashmode
|
113
|
+
if block_given?
|
114
|
+
Array.new(count, &block)
|
115
|
+
else
|
116
|
+
Array.new( count ){ read_string }
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
#
|
121
|
+
# Returns a hash
|
122
|
+
# Expected Buffer-Format:
|
123
|
+
# count (of Hash-elements)
|
124
|
+
# count* key|Value
|
125
|
+
# Key's are transformed to symbols, values are treated as string
|
126
|
+
def read_hash
|
127
|
+
tags = read_array( hashmode: true ) # { |_| [read_string, read_string] }
|
128
|
+
result = if tags.nil? || tags.flatten.empty?
|
129
|
+
tags
|
130
|
+
else
|
131
|
+
interim = if tags.size.modulo(2).zero?
|
132
|
+
Hash[*tags.flatten]
|
133
|
+
else
|
134
|
+
Hash[*tags[0..-2].flatten] # omit the last element
|
135
|
+
end
|
136
|
+
# symbolize Hash
|
137
|
+
Hash[interim.map { |k, v| [k.to_sym, v] unless k.nil? }.compact]
|
138
|
+
end
|
139
|
+
end
|
140
|
+
#
|
141
|
+
|
142
|
+
def read_contract # read a standard contract and return als hash
|
143
|
+
{ con_id: read_int,
|
144
|
+
symbol: read_string,
|
145
|
+
sec_type: read_string,
|
146
|
+
expiry: read_string,
|
147
|
+
strike: read_decimal,
|
148
|
+
right: read_string,
|
149
|
+
multiplier: read_int,
|
150
|
+
exchange: read_string,
|
151
|
+
currency: read_string,
|
152
|
+
local_symbol: read_string,
|
153
|
+
trading_class: read_string } # new Version 8
|
154
|
+
|
155
|
+
end
|
156
|
+
|
157
|
+
|
158
|
+
alias read_bool read_boolean
|
159
|
+
end
|
160
|
+
end
|
data/lib/ib/version.rb
ADDED
@@ -0,0 +1,85 @@
|
|
1
|
+
module IB
|
2
|
+
class Account < IB::Model
|
3
|
+
include BaseProperties
|
4
|
+
# include Redis::Objects
|
5
|
+
# attr_accessible :alias, :account, :connected
|
6
|
+
|
7
|
+
prop :account, # String
|
8
|
+
:alias, #
|
9
|
+
:type,
|
10
|
+
:last_updated,
|
11
|
+
:connected => :bool
|
12
|
+
|
13
|
+
# redis_id_field :account
|
14
|
+
# value :my_alias
|
15
|
+
# value :the_account
|
16
|
+
# value :active
|
17
|
+
|
18
|
+
|
19
|
+
validates_format_of :account, :with => /\A[D]?[UF]{1}\d{5,8}\z/ , :message => 'should be (X)X00000'
|
20
|
+
|
21
|
+
# in tableless mode the scope is ignored
|
22
|
+
|
23
|
+
has_many :account_values
|
24
|
+
has_many :portfolio_values
|
25
|
+
has_many :contracts
|
26
|
+
has_many :orders
|
27
|
+
has_many :focuses
|
28
|
+
|
29
|
+
def default_attributes
|
30
|
+
super.merge account: 'X000000'
|
31
|
+
super.merge alias: ''
|
32
|
+
super.merge type: 'Account'
|
33
|
+
super.merge connected: false
|
34
|
+
end
|
35
|
+
|
36
|
+
def logger #nodoc#
|
37
|
+
Connection.logger
|
38
|
+
end
|
39
|
+
|
40
|
+
# Setze Account connect/disconnect und undate!
|
41
|
+
def connect!
|
42
|
+
update_attribute :connected , true
|
43
|
+
end
|
44
|
+
def disconnect!
|
45
|
+
update_attribute :connected , false
|
46
|
+
end
|
47
|
+
|
48
|
+
def print_type #nodoc#
|
49
|
+
(test_environment? ? "demo_" : "") + ( user? ? "user" : "advisor" )
|
50
|
+
end
|
51
|
+
|
52
|
+
def advisor?
|
53
|
+
!!(type =~ /Advisor/ || account =~ /\A[D]?[F]{1}/)
|
54
|
+
end
|
55
|
+
|
56
|
+
def user?
|
57
|
+
!!(type =~ /User/ || account =~ /\A[D]?[U]{1}/)
|
58
|
+
end
|
59
|
+
|
60
|
+
def test_environment?
|
61
|
+
!!(account =~ /^[D]{1}/)
|
62
|
+
end
|
63
|
+
|
64
|
+
def == other
|
65
|
+
super(other) ||
|
66
|
+
other.is_a?(self.class) && account == other.account
|
67
|
+
end
|
68
|
+
|
69
|
+
def to_human
|
70
|
+
a = if self.alias.present? && self.alias != account
|
71
|
+
" alias: "+ self.alias
|
72
|
+
else
|
73
|
+
""
|
74
|
+
end
|
75
|
+
"<#{print_type} #{account}#{a}>"
|
76
|
+
end
|
77
|
+
|
78
|
+
def name #nodoc#
|
79
|
+
self.alias.present? ? self.alias : account
|
80
|
+
end
|
81
|
+
|
82
|
+
# alias :id :account
|
83
|
+
end # class
|
84
|
+
|
85
|
+
end # module
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module IB
|
2
|
+
# Instantiate with a Hash of attributes, to be auto-set via initialize in Model.
|
3
|
+
class AccountValue < IB::Model
|
4
|
+
include BaseProperties
|
5
|
+
|
6
|
+
belongs_to :account
|
7
|
+
|
8
|
+
prop :key,
|
9
|
+
:value,
|
10
|
+
:currency
|
11
|
+
|
12
|
+
|
13
|
+
# comparison
|
14
|
+
def == other
|
15
|
+
super(other) ||
|
16
|
+
other.is_a?(self.class) &&
|
17
|
+
key == other.key &&
|
18
|
+
currency == other.currency &&
|
19
|
+
value == other.value
|
20
|
+
end
|
21
|
+
def default_attributes
|
22
|
+
super.merge key: 'AccountValue',
|
23
|
+
value: 0,
|
24
|
+
currency: 'USD'
|
25
|
+
end
|
26
|
+
|
27
|
+
def to_human
|
28
|
+
"<#{key}=#{value} #{currency}>"
|
29
|
+
end
|
30
|
+
|
31
|
+
alias to_s to_human
|
32
|
+
end # class
|
33
|
+
end # module IB
|