ib-extensions 1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +11 -0
- data/.rspec +3 -0
- data/.travis.yml +6 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +9 -0
- data/Gemfile.lock +112 -0
- data/Guardfile +24 -0
- data/README.md +99 -0
- data/Rakefile +6 -0
- data/bin/console +96 -0
- data/bin/console.yml +3 -0
- data/bin/gateway.rb +97 -0
- data/bin/setup +8 -0
- data/changelog.md +31 -0
- data/examples/cancel_orders +74 -0
- data/examples/eod +35 -0
- data/examples/input.rb +475 -0
- data/examples/market_price +57 -0
- data/examples/option_chain +67 -0
- data/examples/place_and_modify_order +162 -0
- data/examples/place_bracket_order +62 -0
- data/examples/place_butterfly_order +104 -0
- data/examples/place_combo_order +70 -0
- data/examples/place_limit_order +82 -0
- data/examples/place_the_limit_order +145 -0
- data/examples/volatility_research +139 -0
- data/examples/what_if_order +90 -0
- data/ib-extensions.gemspec +37 -0
- data/lib/ib-gateway.rb +5 -0
- data/lib/ib/alerts/base-alert.rb +128 -0
- data/lib/ib/alerts/gateway-alerts.rb +15 -0
- data/lib/ib/alerts/order-alerts.rb +68 -0
- data/lib/ib/eod.rb +152 -0
- data/lib/ib/extensions.rb +9 -0
- data/lib/ib/extensions/contract.rb +37 -0
- data/lib/ib/extensions/version.rb +5 -0
- data/lib/ib/flex.rb +150 -0
- data/lib/ib/gateway.rb +425 -0
- data/lib/ib/gateway/account-infos.rb +115 -0
- data/lib/ib/gateway/order-handling.rb +150 -0
- data/lib/ib/market-price.rb +134 -0
- data/lib/ib/models/account.rb +329 -0
- data/lib/ib/models/spread.rb +159 -0
- data/lib/ib/option-chain.rb +198 -0
- data/lib/ib/option-greeks.rb +88 -0
- data/lib/ib/order-prototypes.rb +110 -0
- data/lib/ib/order_prototypes/abstract.rb +67 -0
- data/lib/ib/order_prototypes/combo.rb +46 -0
- data/lib/ib/order_prototypes/forex.rb +40 -0
- data/lib/ib/order_prototypes/limit.rb +177 -0
- data/lib/ib/order_prototypes/market.rb +116 -0
- data/lib/ib/order_prototypes/pegged.rb +173 -0
- data/lib/ib/order_prototypes/premarket.rb +31 -0
- data/lib/ib/order_prototypes/stop.rb +202 -0
- data/lib/ib/order_prototypes/volatility.rb +39 -0
- data/lib/ib/spread-prototypes.rb +62 -0
- data/lib/ib/spread_prototypes/butterfly.rb +79 -0
- data/lib/ib/spread_prototypes/calendar.rb +85 -0
- data/lib/ib/spread_prototypes/stock-spread.rb +48 -0
- data/lib/ib/spread_prototypes/straddle.rb +75 -0
- data/lib/ib/spread_prototypes/strangle.rb +96 -0
- data/lib/ib/spread_prototypes/vertical.rb +84 -0
- data/lib/ib/verify.rb +226 -0
- metadata +206 -0
data/lib/ib/flex.rb
ADDED
@@ -0,0 +1,150 @@
|
|
1
|
+
require 'net/http'
|
2
|
+
require 'net/https'
|
3
|
+
#require 'xmlsimple'
|
4
|
+
require 'ox'
|
5
|
+
module Ox
|
6
|
+
class Element
|
7
|
+
def ox
|
8
|
+
nodes.first
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
module IB
|
14
|
+
|
15
|
+
# FLEX is a web-based service from IB that helps you to retrieve your activity,
|
16
|
+
# trades and positions. It is working independently from TWS or Gateway, using your
|
17
|
+
# internet connection directly. See /misc/flex for extended FLEX documentation.
|
18
|
+
#
|
19
|
+
# In order to use this service, activate it and configure your token first.
|
20
|
+
# Your Token is located at Account Management->Reports->Delivery Settings->Flex Web Service.
|
21
|
+
# You need to activate Flex Web Service and generate new token(s) there.
|
22
|
+
# Your Flex Query Ids are in Account Management->Reports->Activity->Flex Queries.
|
23
|
+
# Create new Flex query and make sure to set its output format to XML.
|
24
|
+
#
|
25
|
+
# IB::Flex object incapsulates a single pre-defined Flex query.
|
26
|
+
#
|
27
|
+
#Details:
|
28
|
+
#
|
29
|
+
# https://www.interactivebrokers.com/en/software/am/am/reports/flex_web_service_version_3.htm
|
30
|
+
#
|
31
|
+
# Error Codes
|
32
|
+
# https://www.interactivebrokers.com/en/software/am/am/reports/version_3_error_codes.htm
|
33
|
+
class Flex
|
34
|
+
|
35
|
+
mattr_accessor :token, :uri
|
36
|
+
# Create new Flex query with options:
|
37
|
+
# :token => 1111111111111111111111111111111111 # CHANGE to your actual token!
|
38
|
+
# :query_id => 11111 # CHANGE to actual query id!
|
39
|
+
# :format => :xml (default) / :csv
|
40
|
+
# :verbose => true / false (default)
|
41
|
+
# :return_hash => true / false (default)
|
42
|
+
def initialize query_id: nil , # must be specified
|
43
|
+
token: nil , # must be specified once
|
44
|
+
format: :xml,# or :csv
|
45
|
+
verbose: false,
|
46
|
+
return_hash: false
|
47
|
+
|
48
|
+
# By default, uri is a well known FLEX Web Service URI
|
49
|
+
self.uri =
|
50
|
+
'https://gdcdyn.interactivebrokers.com/Universal/servlet/FlexStatementService.SendRequest'
|
51
|
+
|
52
|
+
# convert parameters into instance-variables and assign them
|
53
|
+
method(__method__).parameters.each do |type, k|
|
54
|
+
next unless type == :key
|
55
|
+
case k
|
56
|
+
when :token
|
57
|
+
self.token = token unless token.nil?
|
58
|
+
else
|
59
|
+
v = eval(k.to_s)
|
60
|
+
instance_variable_set("@#{k}", v) unless v.nil?
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
error "Token not set", :flex if Flex.token.nil?
|
65
|
+
error "query_id not set", :flex if @query_id.nil?
|
66
|
+
yield self if block_given?
|
67
|
+
end
|
68
|
+
|
69
|
+
# Run a pre-defined Flex query against IB Flex Web Service
|
70
|
+
# Returns a (parsed) report or raises FlexError in case of problems
|
71
|
+
def run
|
72
|
+
# Initiate FLEX request at a known FLEX Web Service URI
|
73
|
+
# get_content returns an Ox-Element-Object
|
74
|
+
# Nodes > FlexStatementResponse and
|
75
|
+
# > if Status is other then 'suscess' ErrorCode and ErrorMessage
|
76
|
+
# > otherwise Uri and ReferenceCode
|
77
|
+
|
78
|
+
statement = get_content( Flex.uri, :t => Flex.token, :q => @query_id, :v => 3)
|
79
|
+
if @verbose
|
80
|
+
puts "the Statement returned by the request of the query"
|
81
|
+
puts Ox.load(Ox.dump( statement), mode: :hash)
|
82
|
+
end
|
83
|
+
error("Flex Query is invalid", :flex ) unless statement.value == 'FlexStatementResponse'
|
84
|
+
error("#{statement.ErrorCode.ox.to_i}: #{statement.ErrorMessage.ox}", :flex) if statement.Status.ox != 'Success'
|
85
|
+
#
|
86
|
+
# Retrieve the FLEX report
|
87
|
+
report = nil
|
88
|
+
until report do
|
89
|
+
sleep 5 # wait for the report to be prepared
|
90
|
+
report = get_content(statement.Url.ox,:t => Flex.token, :q => statement.ReferenceCode.ox, :v => 3,
|
91
|
+
:text_ok => @format != :xml)
|
92
|
+
|
93
|
+
# If Status is specified, returned xml contains only error message, not actual report
|
94
|
+
if report.nodes.include?('Status') && report.Status.ox =~ /Fail|Warn/
|
95
|
+
error_code = report.ErrorCode.ox.to_i
|
96
|
+
error_message = "#{error_code}: #{report.ErrorMessage.ox}"
|
97
|
+
|
98
|
+
case error_code
|
99
|
+
when 1001..1009, 1018, 1019, 1021
|
100
|
+
# Report is just not ready yet, wait and retry
|
101
|
+
puts error_message if @verbose
|
102
|
+
report = nil
|
103
|
+
else # Fatal error
|
104
|
+
error error_message, :flex
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
@return_hash ? Ox.load( Ox.dump(report), mode: :hash) : report # return hash or the Ox-Element
|
109
|
+
end
|
110
|
+
|
111
|
+
# Helper method to get (and parse XML) responses from IB Flex Web Service
|
112
|
+
def get_content address, fields
|
113
|
+
get = -> ( the_uri ) do
|
114
|
+
server = Net::HTTP.new(the_uri.host, the_uri.port)
|
115
|
+
server.use_ssl = true
|
116
|
+
server.verify_mode = OpenSSL::SSL::VERIFY_NONE if server.use_ssl? # Avoid OpenSSL failures
|
117
|
+
server.start{ |http| http.request( Net::HTTP::Get.new(the_uri.request_uri) ) }
|
118
|
+
end
|
119
|
+
|
120
|
+
text_ok = fields.delete(:text_ok)
|
121
|
+
the_uri = URI("#{address}?" + fields.map { |k, v| "#{k}=#{URI.encode(v.to_s)}" }.join('&'))
|
122
|
+
response = get[ the_uri ]
|
123
|
+
|
124
|
+
error("URI responded with #{response.code}", :flex) unless response.code.to_i == 200
|
125
|
+
if text_ok
|
126
|
+
response.body
|
127
|
+
else
|
128
|
+
Ox.parse response.body
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
# Helper method to get the body of response from IB Flex Web Service
|
133
|
+
# def get address, fields
|
134
|
+
# uri = URI("#{address}?" + fields.map { |k, v| "#{k}=#{URI.encode(v.to_s)}" }.join('&'))
|
135
|
+
# puts "URI #{uri}"
|
136
|
+
# server = Net::HTTP.new(uri.host, uri.port)
|
137
|
+
# server.use_ssl = (uri.scheme == 'https')
|
138
|
+
# server.verify_mode = OpenSSL::SSL::VERIFY_NONE if server.use_ssl? # Avoid OpenSSL failures
|
139
|
+
|
140
|
+
# resp = server.start do |http|
|
141
|
+
# req = Net::HTTP::Get.new(uri.request_uri)
|
142
|
+
# http.request(req)
|
143
|
+
# end
|
144
|
+
# error("URI responded with #{resp.code}", :flex) unless resp.code.to_i == 200
|
145
|
+
#
|
146
|
+
# resp.body
|
147
|
+
# end
|
148
|
+
|
149
|
+
end
|
150
|
+
end
|
data/lib/ib/gateway.rb
ADDED
@@ -0,0 +1,425 @@
|
|
1
|
+
#
|
2
|
+
require 'ib/gateway/account-infos'
|
3
|
+
require 'ib/gateway/order-handling'
|
4
|
+
require_relative 'alerts/gateway-alerts'
|
5
|
+
require_relative 'alerts/order-alerts'
|
6
|
+
require 'active_support/core_ext/module/attribute_accessors' # provides m_accessor
|
7
|
+
#module GWSupport
|
8
|
+
# provide AR4- ActiveRelation-like-methods to Array-Class
|
9
|
+
#refine Array do
|
10
|
+
class Array
|
11
|
+
# returns the item (in case of first) or the hole array (in case of create)
|
12
|
+
def first_or_create item, *condition, &b
|
13
|
+
int_array = if condition.empty?
|
14
|
+
[ find_all{ |x| x == item } ] if !block_given?
|
15
|
+
else
|
16
|
+
condition.map{ |c| find_all{|x| x[ c ] == item[ c ] }}
|
17
|
+
end || []
|
18
|
+
if block_given?
|
19
|
+
relation = yield
|
20
|
+
part_2 = find_all{ |x| x.send( relation ) == item.send( relation ) }
|
21
|
+
int_array << part_2 unless part_2.empty?
|
22
|
+
end
|
23
|
+
# reduce performs a logical "&" between the array-elements
|
24
|
+
# we are only interested in the first entry
|
25
|
+
r= int_array.reduce( :& )
|
26
|
+
r.present? ? r.first : self.push( item )
|
27
|
+
end
|
28
|
+
def update_or_create item, *condition, &b
|
29
|
+
member = first_or_create( item, *condition, &b)
|
30
|
+
self[ index( member ) ] = item unless member.is_a?(Array)
|
31
|
+
self # always returns the array
|
32
|
+
end
|
33
|
+
|
34
|
+
# performs [ [ array ] & [ array ] & [..] ].first
|
35
|
+
def intercept
|
36
|
+
a = self.dup
|
37
|
+
s = a.pop
|
38
|
+
while a.present?
|
39
|
+
s = s & a.pop
|
40
|
+
end
|
41
|
+
s.first unless s.nil? # return_value (or nil)
|
42
|
+
end
|
43
|
+
end # refine / class
|
44
|
+
#end # module
|
45
|
+
|
46
|
+
module IB
|
47
|
+
|
48
|
+
=begin
|
49
|
+
The Gateway-Class defines anything which has to be done before a connection can be established.
|
50
|
+
The Default Skeleton can easily be substituted by customized actions
|
51
|
+
|
52
|
+
The IB::Gateway can be used in three modes
|
53
|
+
(1) IB::Gateway.new( connect:true, --other arguments-- ) do | gateway |
|
54
|
+
** subscribe to Messages and define the response **
|
55
|
+
# This block is executed before a connect-attempt is made
|
56
|
+
end
|
57
|
+
(2) gw = IB:Gateway.new
|
58
|
+
** subscribe to Messages **
|
59
|
+
gw.connect
|
60
|
+
(3) IB::Gateway.new connect:true, host: 'localhost' ....
|
61
|
+
|
62
|
+
Independently IB::Alert.alert_#{nnn} should be defined for a proper response to warnings, error-
|
63
|
+
and system-messages.
|
64
|
+
|
65
|
+
|
66
|
+
The Connection to the TWS is realized throught IB::Connection. Additional to __IB::Connection.current__
|
67
|
+
IB::Gateway.tws points to the active Connection.
|
68
|
+
|
69
|
+
To support asynchronic access, the :recieved-Array of the Connection-Class is not active.
|
70
|
+
The Array is easily confused, if used in production mode with a FA-Account and has limits.
|
71
|
+
Thus IB::Conncetion.wait_for(message) is not available until the programm is called with
|
72
|
+
IB::Gateway.new serial_array: true (, ...)
|
73
|
+
|
74
|
+
|
75
|
+
|
76
|
+
=end
|
77
|
+
|
78
|
+
class Gateway
|
79
|
+
|
80
|
+
include LogDev # provides default_logger
|
81
|
+
include AccountInfos # provides Handling of Account-Data provided by the tws
|
82
|
+
include OrderHandling
|
83
|
+
|
84
|
+
# include GWSupport # introduces update_or_create, first_or_create and intercept to the Array-Class
|
85
|
+
|
86
|
+
# from active-support. Add Logging at Class + Instance-Level
|
87
|
+
mattr_accessor :logger
|
88
|
+
# similar to the Connection-Class: current represents the active instance of Gateway
|
89
|
+
mattr_accessor :current
|
90
|
+
mattr_accessor :tws
|
91
|
+
|
92
|
+
|
93
|
+
|
94
|
+
def initialize port: 4002, # 7497,
|
95
|
+
host: '127.0.0.1', # 'localhost:4001' is also accepted
|
96
|
+
client_id: random_id,
|
97
|
+
subscribe_managed_accounts: true,
|
98
|
+
subscribe_alerts: true,
|
99
|
+
subscribe_order_messages: true,
|
100
|
+
connect: true,
|
101
|
+
get_account_data: false,
|
102
|
+
serial_array: false,
|
103
|
+
logger: default_logger,
|
104
|
+
watchlists: [] , # array of watchlists (IB::Symbols::{watchlist}) containing descriptions for complex positions
|
105
|
+
**other_agruments_which_are_ignored,
|
106
|
+
&b
|
107
|
+
|
108
|
+
host, port = (host+':'+port.to_s).split(':')
|
109
|
+
|
110
|
+
self.logger = logger
|
111
|
+
logger.info { '-' * 20 +' initialize ' + '-' * 20 }
|
112
|
+
logger.tap{|l| l.progname = 'Gateway#Initialize' }
|
113
|
+
|
114
|
+
@connection_parameter = { received: serial_array, port: port, host: host, connect: false, logger: logger, client_id: client_id }
|
115
|
+
|
116
|
+
@account_lock = Mutex.new
|
117
|
+
@watchlists = watchlists
|
118
|
+
@gateway_parameter = { s_m_a: subscribe_managed_accounts,
|
119
|
+
s_a: subscribe_alerts,
|
120
|
+
s_o_m: subscribe_order_messages,
|
121
|
+
g_a_d: get_account_data }
|
122
|
+
|
123
|
+
|
124
|
+
Thread.report_on_exception = true
|
125
|
+
# https://blog.bigbinary.com/2018/04/18/ruby-2-5-enables-thread-report_on_exception-by-default.html
|
126
|
+
Gateway.current = self
|
127
|
+
# establish Alert-framework
|
128
|
+
IB::Alert.logger = logger
|
129
|
+
# initialise Connection without connecting
|
130
|
+
prepare_connection &b
|
131
|
+
# finally connect to the tws
|
132
|
+
connect = true if get_account_data
|
133
|
+
if connect
|
134
|
+
if connect(100) # tries to connect for about 2h
|
135
|
+
get_account_data(watchlists: watchlists.map{|b| IB::Symbols.allocate_collection b}) if get_account_data
|
136
|
+
# request_open_orders() if request_open_orders || get_account_data
|
137
|
+
else
|
138
|
+
@accounts = [] # definitivley reset @accounts
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
end
|
143
|
+
|
144
|
+
def active_watchlists
|
145
|
+
@watchlists
|
146
|
+
end
|
147
|
+
|
148
|
+
def get_host
|
149
|
+
"#{@connection_parameter[:host]}: #{@connection_parameter[:port] }"
|
150
|
+
end
|
151
|
+
|
152
|
+
def update_local_order order
|
153
|
+
# @local_orders is initialized by #PrepareConnection
|
154
|
+
@local_orders.update_or_create order, :local_id
|
155
|
+
end
|
156
|
+
|
157
|
+
|
158
|
+
## ------------------------------------- connect ---------------------------------------------##
|
159
|
+
=begin
|
160
|
+
Zentrale Methode
|
161
|
+
Es wird ein Connection-Objekt (IB::Connection.current) angelegt.
|
162
|
+
Sollte keine TWS vorhanden sein, wird eine entsprechende Meldung ausgegeben und der Verbindungsversuch
|
163
|
+
wiederholt.
|
164
|
+
Weiterhin meldet sich die Anwendung zur Auswertung von Messages der TWS an.
|
165
|
+
|
166
|
+
=end
|
167
|
+
def connect maximal_count_of_retry=100
|
168
|
+
|
169
|
+
i= -1
|
170
|
+
logger.progname = 'Gateway#connect'
|
171
|
+
begin
|
172
|
+
tws.connect
|
173
|
+
rescue Errno::ECONNREFUSED => e
|
174
|
+
i+=1
|
175
|
+
if i < maximal_count_of_retry
|
176
|
+
if i.zero?
|
177
|
+
logger.info 'No TWS!'
|
178
|
+
else
|
179
|
+
logger.info {"No TWS Retry #{i}/ #{maximal_count_of_retry} " }
|
180
|
+
end
|
181
|
+
sleep i<50 ? 10 : 60 # Die ersten 50 Versuche im 10 Sekunden Abstand, danach 1 Min.
|
182
|
+
retry
|
183
|
+
else
|
184
|
+
logger.info { "Giving up!!" }
|
185
|
+
return false
|
186
|
+
end
|
187
|
+
rescue Errno::EHOSTUNREACH => e
|
188
|
+
logger.error 'Cannot connect to specified host'
|
189
|
+
logger.error e
|
190
|
+
return false
|
191
|
+
rescue SocketError => e
|
192
|
+
logger.error 'Wrong Adress, connection not possible'
|
193
|
+
return false
|
194
|
+
end
|
195
|
+
|
196
|
+
tws.start_reader
|
197
|
+
# let NextValidId-Event appear
|
198
|
+
(1..30).each do |r|
|
199
|
+
break if tws.next_local_id.present?
|
200
|
+
sleep 0.1
|
201
|
+
if r == 30
|
202
|
+
error "Connected, NextLocalId is not initialized. Repeat with another client_id"
|
203
|
+
end
|
204
|
+
end
|
205
|
+
# initialize @accounts (incl. aliases)
|
206
|
+
tws.send_message :RequestFA, fa_data_type: 3
|
207
|
+
logger.debug { "Communications successfully established" }
|
208
|
+
end # def
|
209
|
+
|
210
|
+
|
211
|
+
|
212
|
+
|
213
|
+
|
214
|
+
def reconnect
|
215
|
+
logger.progname = 'Gateway#reconnect'
|
216
|
+
if tws.present?
|
217
|
+
disconnect
|
218
|
+
sleep 1
|
219
|
+
end
|
220
|
+
logger.info "trying to reconnect ..."
|
221
|
+
connect
|
222
|
+
end
|
223
|
+
|
224
|
+
def disconnect
|
225
|
+
logger.progname = 'Gateway#disconnect'
|
226
|
+
|
227
|
+
tws.disconnect if tws.present?
|
228
|
+
@accounts = [] # each{|y| y.update_attribute :connected, false }
|
229
|
+
logger.info "Connection closed"
|
230
|
+
end
|
231
|
+
|
232
|
+
|
233
|
+
=begin
|
234
|
+
Proxy for Connection#SendMessage
|
235
|
+
allows reconnection if a socket_error occurs
|
236
|
+
|
237
|
+
checks the connection before sending a message.
|
238
|
+
|
239
|
+
=end
|
240
|
+
|
241
|
+
def send_message what, *args
|
242
|
+
logger.tap{|l| l.progname = 'Gateway#SendMessage' }
|
243
|
+
begin
|
244
|
+
if check_connection
|
245
|
+
tws.send_message what, *args
|
246
|
+
else
|
247
|
+
error( "Connection lost. Could not send message #{what}" )
|
248
|
+
end
|
249
|
+
end
|
250
|
+
end
|
251
|
+
|
252
|
+
=begin
|
253
|
+
Cancels one or multible orders
|
254
|
+
|
255
|
+
Argument is either an order-object or a local_id
|
256
|
+
|
257
|
+
=end
|
258
|
+
|
259
|
+
def cancel_order *orders
|
260
|
+
|
261
|
+
logger.tap{|l| l.progname = 'Gateway#CancelOrder' }
|
262
|
+
|
263
|
+
orders.compact.each do |o|
|
264
|
+
local_id = if o.is_a? (IB::Order)
|
265
|
+
logger.info{ "Cancelling #{o.to_human}" }
|
266
|
+
o.local_id
|
267
|
+
else
|
268
|
+
o
|
269
|
+
end
|
270
|
+
send_message :CancelOrder, :local_id => local_id.to_i
|
271
|
+
end
|
272
|
+
|
273
|
+
end
|
274
|
+
|
275
|
+
=begin
|
276
|
+
clients returns a list of Account-Objects
|
277
|
+
|
278
|
+
If only one Account is present, Client and Advisor are identical.
|
279
|
+
|
280
|
+
=end
|
281
|
+
def clients
|
282
|
+
@accounts.find_all &:user?
|
283
|
+
end
|
284
|
+
=begin
|
285
|
+
The Advisor is always the first account
|
286
|
+
=end
|
287
|
+
def advisor
|
288
|
+
@accounts.first
|
289
|
+
end
|
290
|
+
|
291
|
+
=begin
|
292
|
+
account_data provides a thread-safe access to linked content of accounts
|
293
|
+
|
294
|
+
(AccountValues, Portfolio-Values, Contracts and Orders)
|
295
|
+
|
296
|
+
It returns an Array of the return-values of the block
|
297
|
+
|
298
|
+
If called without a parameter, all clients are accessed
|
299
|
+
=end
|
300
|
+
|
301
|
+
def account_data account_or_id=nil
|
302
|
+
|
303
|
+
safe = ->(account) do
|
304
|
+
@account_lock.synchronize do
|
305
|
+
yield account
|
306
|
+
end
|
307
|
+
end
|
308
|
+
|
309
|
+
if block_given?
|
310
|
+
if account_or_id.present?
|
311
|
+
sa = account_or_id.is_a?(IB::Account) ? account_or_id : @accounts.detect{|x| x.account == account_or_id }
|
312
|
+
safe[sa] if sa.is_a? IB::Account
|
313
|
+
else
|
314
|
+
clients.map{|sa| safe[sa]}
|
315
|
+
end
|
316
|
+
end
|
317
|
+
end
|
318
|
+
|
319
|
+
|
320
|
+
private
|
321
|
+
|
322
|
+
def random_id
|
323
|
+
rand 99999
|
324
|
+
end
|
325
|
+
|
326
|
+
|
327
|
+
|
328
|
+
def prepare_connection &b
|
329
|
+
tws.disconnect if tws.is_a? IB::Connection
|
330
|
+
self.tws = IB::Connection.new @connection_parameter
|
331
|
+
@accounts = @local_orders = Array.new
|
332
|
+
|
333
|
+
# prepare Advisor-User hierachy
|
334
|
+
initialize_managed_accounts if @gateway_parameter[:s_m_a]
|
335
|
+
initialize_alerts if @gateway_parameter[:s_a]
|
336
|
+
initialize_order_handling if @gateway_parameter[:s_o_m] || @gateway_parameter[:g_a_d]
|
337
|
+
## apply other initialisations which should apper before the connection as block
|
338
|
+
## i.e. after connection order-state events are fired if an open-order is pending
|
339
|
+
## a possible response is best defined before the connect-attempt is done
|
340
|
+
# ## Attention
|
341
|
+
# ## @accounts are not initialized yet (empty array)
|
342
|
+
if block_given?
|
343
|
+
yield self
|
344
|
+
|
345
|
+
end
|
346
|
+
end
|
347
|
+
|
348
|
+
=begin
|
349
|
+
InitializeManagedAccounts
|
350
|
+
defines the Message-Handler for :ManagedAccounts
|
351
|
+
Its always active.
|
352
|
+
=end
|
353
|
+
|
354
|
+
def initialize_managed_accounts
|
355
|
+
rec_id = tws.subscribe( :ReceiveFA ) do |msg|
|
356
|
+
msg.accounts.each do |a|
|
357
|
+
account_data( a.account ){| the_account | the_account.update_attribute :alias, a.alias } unless a.alias.blank?
|
358
|
+
end
|
359
|
+
logger.info { "Accounts initialized \n #{@accounts.map( &:to_human ).join " \n " }" }
|
360
|
+
end
|
361
|
+
|
362
|
+
man_id = tws.subscribe( :ManagedAccounts ) do |msg|
|
363
|
+
logger.progname = 'Gateway#InitializeManagedAccounts'
|
364
|
+
if @accounts.empty?
|
365
|
+
# just validate the message and put all together into an array
|
366
|
+
@accounts = msg.accounts_list.split(',').map do |a|
|
367
|
+
account = IB::Account.new( account: a.upcase , connected: true )
|
368
|
+
end
|
369
|
+
else
|
370
|
+
logger.info {"already #{@accounts.size} accounts initialized "}
|
371
|
+
@accounts.each{|x| x.update_attribute :connected , true }
|
372
|
+
end # if
|
373
|
+
end # subscribe do
|
374
|
+
end # def
|
375
|
+
|
376
|
+
|
377
|
+
def initialize_alerts
|
378
|
+
|
379
|
+
tws.subscribe( :AccountUpdateTime ){| msg | logger.debug{ msg.to_human }}
|
380
|
+
tws.subscribe(:Alert) do |msg|
|
381
|
+
logger.progname = 'Gateway#Alerts'
|
382
|
+
logger.debug " ----------------#{msg.code}-----"
|
383
|
+
# delegate anything to IB::Alert
|
384
|
+
IB::Alert.send("alert_#{msg.code}", msg )
|
385
|
+
end
|
386
|
+
end
|
387
|
+
|
388
|
+
|
389
|
+
# Handy method to ensure that a connection is established and active.
|
390
|
+
#
|
391
|
+
# The connection is reset on the IB-side at least once a day. Then the
|
392
|
+
# IB-Ruby-Connection has to be reestablished, too.
|
393
|
+
#
|
394
|
+
# check_connection reconnects if necessary and returns false if the connection is lost.
|
395
|
+
#
|
396
|
+
# It delays the process by 6 ms (150 MBit Cable connection)
|
397
|
+
#
|
398
|
+
# a = Time.now; G.check_connection; b= Time.now ;b-a
|
399
|
+
# => 0.00066005
|
400
|
+
#
|
401
|
+
def check_connection
|
402
|
+
answer = nil; count=0
|
403
|
+
z= tws.subscribe( :CurrentTime ) { answer = true }
|
404
|
+
while (answer.nil?)
|
405
|
+
begin
|
406
|
+
tws.send_message(:RequestCurrentTime) # 10 ms ##
|
407
|
+
i=0; loop{ break if answer || i > 40; i+=1; sleep 0.0001}
|
408
|
+
rescue IOError, Errno::ECONNREFUSED # connection lost
|
409
|
+
count = 6
|
410
|
+
rescue IB::Error # not connected
|
411
|
+
reconnect
|
412
|
+
count +=1
|
413
|
+
sleep 1
|
414
|
+
retry if count <= 5
|
415
|
+
end
|
416
|
+
count +=1
|
417
|
+
break if count > 5
|
418
|
+
end
|
419
|
+
tws.unsubscribe z
|
420
|
+
count < 5 && answer # return value
|
421
|
+
end
|
422
|
+
end # class
|
423
|
+
|
424
|
+
end # module
|
425
|
+
|