ib-extensions 1.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 +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
|
+
|