ib-orientdb 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.
Files changed (48) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +56 -0
  3. data/CODE_OF_CONDUCT.md +74 -0
  4. data/Gemfile +12 -0
  5. data/Gemfile.lock +128 -0
  6. data/Guardfile +24 -0
  7. data/LICENSE +21 -0
  8. data/README.md +41 -0
  9. data/Rakefile +6 -0
  10. data/bin/console +99 -0
  11. data/bin/gateway +92 -0
  12. data/bin/readme.md +1 -0
  13. data/changelog.md +10 -0
  14. data/ib-orientdb.gemspec +42 -0
  15. data/lib/alerts/base-alert.rb +143 -0
  16. data/lib/alerts/gateway-alerts.rb +16 -0
  17. data/lib/alerts/order-alerts.rb +68 -0
  18. data/lib/ib-orientdb.rb +12 -0
  19. data/lib/ib/account-infos.rb +115 -0
  20. data/lib/ib/account-init.rb +151 -0
  21. data/lib/ib/orient-gateway.rb +362 -0
  22. data/lib/ib/setup-orientdb.rb +112 -0
  23. data/lib/logging.rb +34 -0
  24. data/lib/models/hc/d2_f.rb +0 -0
  25. data/lib/models/hc/grid.rb +0 -0
  26. data/lib/models/hc/has_portfolio.rb +0 -0
  27. data/lib/models/hc/has_position.rb +0 -0
  28. data/lib/models/hc/has_strategy.rb +0 -0
  29. data/lib/models/hc/hc_grid.rb +0 -0
  30. data/lib/models/hc/my_user.rb +0 -0
  31. data/lib/models/hc/p2_u.rb +0 -0
  32. data/lib/models/hc/portfolio.rb +161 -0
  33. data/lib/models/ib/account.rb +5 -0
  34. data/lib/models/ib/account_value.rb +29 -0
  35. data/lib/models/ib/advisor.rb +0 -0
  36. data/lib/models/ib/contract.rb +15 -0
  37. data/lib/models/ib/demo_advisor.rb +0 -0
  38. data/lib/models/ib/demo_user.rb +0 -0
  39. data/lib/models/ib/financials.rb +6 -0
  40. data/lib/models/ib/has_account.rb +0 -0
  41. data/lib/models/ib/portfolio_value.rb +10 -0
  42. data/lib/models/ib/spread.rb +0 -0
  43. data/lib/models/ib/user.rb +0 -0
  44. data/lib/models/tg/tag.rb +16 -0
  45. data/lib/support.rb +21 -0
  46. data/lib/version.rb +5 -0
  47. data/setup.md +83 -0
  48. metadata +231 -0
@@ -0,0 +1,151 @@
1
+ module IB
2
+ module AccountInfos
3
+
4
+ def load_managed_accounts
5
+
6
+ # defines the callback of the ManagedAccount message
7
+ #
8
+ # The @accounts-array is initialized with active accounts
9
+ Thread.new do
10
+ account_class = ->(a) do
11
+ case a
12
+ when /^U/
13
+ IB::User
14
+ when /^F/
15
+ IB::Advisor
16
+ when /^DF/
17
+ IB::DemoAdvisor
18
+ when /^DU/
19
+ IB::DemoUser
20
+ else
21
+ IB::Account
22
+ end
23
+ end
24
+ @accounts =[]
25
+ c= IB::Connection.current
26
+ man_id = c.subscribe( :ManagedAccounts ) do |msg|
27
+ @accounts = msg.accounts_list.split(',').map do |a|
28
+ account_class[a].new( account: a.upcase , last_access: Time.now ).save
29
+ end
30
+ end
31
+ rec_id = c.subscribe( :ReceiveFA ) do |msg|
32
+ msg.accounts.each{ |a| IB::Account.where( account: a.account ).first.update alias: a.alias }
33
+ end
34
+
35
+ loop{ sleep 1 ; break if !@accounts.empty? } # keep it active for 1 second
36
+ c.unsubscribe man_id , rec_id # release callbacks
37
+ end
38
+
39
+ end
40
+ end
41
+
42
+ #
43
+ #2.6.3 :013 > IB::Account.delete all: true
44
+ #03.12.(17:45:47) INFO->delete vertex ib_account
45
+ # => 8
46
+ #2.6.3 :014 > load_managed_accounts
47
+ # => #<Thread:0x0000000003d57c78@/home/ubuntu/workspace/ib-orientdb/lib/ib/account_init.rb:9 run>
48
+ #2.6.3 :015 > C.disconnect
49
+ # => false
50
+ #2.6.3 :016 > C.connect
51
+ #Connected to server, version: 137,
52
+ # connection time: 2020-12-03 17:46:03 +0000 local, 2020-12-03T17:46:03+00:00 remote.
53
+ #< ManagedAccounts: DF167347 - DU167348 - DU167349>
54
+ # => #<Thread:0x0000000003fecee8@/home/ubuntu/workspace/ib-api/lib/ib/connection.rb:379 run>
55
+ #2.6.3 :017 > Got next valid order id: 1.
56
+ #TWS Warning 2104: Market data farm connection is OK:eufarm
57
+ #
58
+ #2.6.3 :018 > IB::Account.all.to_human
59
+ #03.12.(17:46:12) INFO->select from ib_account
60
+ # => ["<demo_advisor DF167347>", "<demo_user DU167348>", "<demo_user DU167349>"]
61
+ #
62
+ #
63
+
64
+ =begin
65
+ Queries the tws for Account- and PortfolioValues
66
+ The parameter can either be the account_id, the IB::Account-Object or
67
+ an Array of account_id and IB::Account-Objects.
68
+
69
+ raises an IB::TransmissionError if the account-data are not transmitted in time (1 sec)
70
+
71
+ raises an IB::Error if less then 100 items are received-
72
+ =end
73
+ def get_account_data
74
+
75
+ logger.progname = 'Gateway#get_account_data'
76
+
77
+
78
+ # Account-infos have to be requested sequentially.
79
+ # subsequent (parallel) calls kill the former once on the tws-server-side
80
+ # In addition, there is no need to cancel the subscription of an request, as a new
81
+ # one overwrites the active one.
82
+ @accounts.each do | account |
83
+ # don't repeat the query until 170 sec. have passed since the previous update
84
+ if account.lad.nil? || ( Time.now - account.lad ) > 170 # sec
85
+ logger.debug{ "#{account.account} :: Requesting AccountData " }
86
+ account[:active] = false # indicates: AccountUpdate in Progress, volatile
87
+ # reset account and portfolio-values
88
+ account.portfolio_values = []
89
+ account.account_values = []
90
+ send_message :RequestAccountData, subscribe: true, account_code: account.account
91
+ loop{ sleep 0.1; break if account.active }
92
+ # if watchlists.present?
93
+ # watchlists.each{|w| error "Watchlists must be IB::Symbols--Classes :.#{w.inspect}" unless w.is_a? IB::Symbols }
94
+ # account.organize_portfolio_positions watchlists
95
+ # end
96
+ send_message :RequestAccountData, subscribe: false ## do this only once
97
+ else
98
+ logger.info{ "#{account.account} :: Using stored AccountData " }
99
+ end
100
+ end
101
+ nil
102
+ end
103
+
104
+
105
+ def all_contracts
106
+ clients.map(&:contracts).flat_map(&:itself).uniq(&:con_id)
107
+ end
108
+
109
+
110
+
111
+ # private
112
+
113
+ # The subscription method should called only once per session.
114
+ # It places subscribers to AccountValue and PortfolioValue Messages, which should remain
115
+ # active through its session.
116
+ #
117
+ # Account- and Portfolio-Values are stored in account.account_values and account.portfolio_values
118
+ # The Arrays are volatile.
119
+
120
+ def subscribe_account_updates continously: true
121
+ puts "SELF: #{self.class.to_s}"
122
+ IB::Connection.current.subscribe( :AccountValue, :PortfolioValue,:AccountDownloadEnd ) do | msg |
123
+ account = @accounts.detect{|a| a.account == msg.account_name }
124
+ case msg
125
+ when IB::Messages::Incoming::AccountValue
126
+ account.account_values = [] unless account.account_values.present?
127
+ account.account_values.<< msg.account_value
128
+
129
+ account[:lad] = Time.now
130
+ # logger.debug { "#{account.account} :: #{msg.account_value.to_human }"}
131
+ when IB::Messages::Incoming::AccountDownloadEnd
132
+ if account.account_values.size > 10
133
+ # simply don't cancel the subscription if continuously is specified
134
+ # the connected flag is set in any case, indicating that valid data are present
135
+ send_message :RequestAccountData, subscribe: false, account_code: account.account unless continously
136
+ account[:active] = true ## flag: Account is completely initialized
137
+ logger.info { "#{account.account} => Count of AccountValues: #{account.account_values.size}" }
138
+ else # unreasonable account_data received - request is still active
139
+ error "#{account.account} => Count of AccountValues too small: #{account.account_values.size}" , :reader
140
+ end
141
+ when IB::Messages::Incoming::PortfolioValue
142
+ account.contracts << msg.contract.save
143
+ account.portfolio_values << msg.portfolio_value
144
+ logger.debug { "#{ account.account } :: #{ msg.contract.to_human }" }
145
+ end # case
146
+ end # subscribe
147
+ end # def
148
+
149
+
150
+
151
+ end
@@ -0,0 +1,362 @@
1
+ #
2
+ require_relative 'account-init'
3
+ #require 'ib/gateway/order-handling'
4
+ require_relative '../alerts/base-alert'
5
+ require_relative '../alerts/gateway-alerts'
6
+ require_relative '../alerts/order-alerts'
7
+ require 'active_support/core_ext/module/attribute_accessors' # provides m_accessor
8
+ #module GWSupport
9
+ # provide AR4- ActiveRelation-like-methods to Array-Class
10
+
11
+ module IB
12
+
13
+ =begin
14
+ The Gateway-Class defines anything which has to be done before a connection can be established.
15
+ The Default Skeleton can easily be substituted by customized actions
16
+
17
+ The IB::Gateway can be used in three modes
18
+ (1) IB::Gateway.new( connect:true, --other arguments-- ) do | gateway |
19
+ ** subscribe to Messages and define the response **
20
+ # This block is executed before a connect-attempt is made
21
+ end
22
+ (2) gw = IB:Gateway.new
23
+ ** subscribe to Messages **
24
+ gw.connect
25
+ (3) IB::Gateway.new connect:true, host: 'localhost' ....
26
+
27
+ Independently IB::Alert.alert_#{nnn} should be defined for a proper response to warnings, error-
28
+ and system-messages.
29
+
30
+
31
+ The Connection to the TWS is realized throught IB::Connection. Additional to __IB::Connection.current__
32
+ IB::Gateway.tws points to the active Connection.
33
+
34
+ To support asynchronic access, the :recieved-Array of the Connection-Class is not active.
35
+ The Array is easily confused, if used in production mode with a FA-Account and has limits.
36
+ Thus IB::Conncetion.wait_for(message) is not available until the programm is called with
37
+ IB::Gateway.new serial_array: true (, ...)
38
+
39
+
40
+
41
+ =end
42
+
43
+ class OrientGateway
44
+
45
+ include LogDev # provides default_logger
46
+ include IB::AccountInfos
47
+ # include IB::AccountInfos # provides Handling of Account-Data provided by the tws
48
+ # include OrderHandling
49
+
50
+ # include GWSupport # introduces update_or_create, first_or_create and intercept to the Array-Class
51
+
52
+ # from active-support. Add Logging at Class + Instance-Level
53
+ mattr_accessor :logger
54
+ # similar to the Connection-Class: current represents the active instance of Gateway
55
+ mattr_accessor :current
56
+ mattr_accessor :tws
57
+
58
+
59
+
60
+ def initialize port: 4002, # 7497,
61
+ host: '127.0.0.1', # 'localhost:4001' is also accepted
62
+ client_id: random_id,
63
+ subscribe_managed_accounts: true,
64
+ subscribe_alerts: true,
65
+ subscribe_order_messages: true,
66
+ connect: true,
67
+ get_account_data: false,
68
+ serial_array: false,
69
+ logger: default_logger,
70
+ watchlists: [] , # array of watchlists (IB::Symbols::{watchlist}) containing descriptions for complex positions
71
+ **other_agruments_which_are_ignored,
72
+ &b
73
+
74
+ host, port = (host+':'+port.to_s).split(':')
75
+
76
+ self.logger = logger
77
+ logger.info { '-' * 20 +' initialize ' + '-' * 20 }
78
+ logger.tap{|l| l.progname = 'Gateway#Initialize' }
79
+
80
+ @connection_parameter = { received: serial_array, port: port, host: host, connect: false, logger: logger, client_id: client_id }
81
+
82
+ @accounts = []
83
+ @watchlists = watchlists
84
+ @gateway_parameter = { s_m_a: subscribe_managed_accounts,
85
+ s_a: subscribe_alerts,
86
+ s_o_m: subscribe_order_messages,
87
+ g_a_d: get_account_data }
88
+
89
+
90
+ Thread.report_on_exception = true
91
+ # https://blog.bigbinary.com/2018/04/18/ruby-2-5-enables-thread-report_on_exception-by-default.html
92
+ self.current = self
93
+ # establish Alert-framework
94
+ #IB::Alert.logger = logger
95
+ # initialise Connection without connecting
96
+ prepare_connection &b
97
+ # finally connect to the tws
98
+ connect = true if get_account_data
99
+ if connect
100
+ if connect(100) # tries to connect for about 2h
101
+ get_account_data() if get_account_data
102
+ # request_open_orders() if request_open_orders || get_account_data
103
+ else
104
+ @accounts = [] # definitivley reset @accounts
105
+ end
106
+ end
107
+
108
+ end
109
+
110
+ def active_watchlists
111
+ @watchlists
112
+ end
113
+
114
+ def get_host
115
+ "#{@connection_parameter[:host]}: #{@connection_parameter[:port] }"
116
+ end
117
+
118
+ def update_local_order order
119
+ # @local_orders is initialized by #PrepareConnection
120
+ @local_orders.update_or_create order, :local_id
121
+ end
122
+
123
+
124
+ ## ------------------------------------- connect ---------------------------------------------##
125
+ =begin
126
+ Zentrale Methode
127
+ Es wird ein Connection-Objekt (IB::Connection.current) angelegt.
128
+ Sollte keine TWS vorhanden sein, wird eine entsprechende Meldung ausgegeben und der Verbindungsversuch
129
+ wiederholt.
130
+ Weiterhin meldet sich die Anwendung zur Auswertung von Messages der TWS an.
131
+
132
+ =end
133
+ def connect maximal_count_of_retry=100
134
+
135
+ i= -1
136
+ logger.progname = 'Gateway#connect'
137
+ begin
138
+ tws.connect
139
+ rescue Errno::ECONNREFUSED => e
140
+ i+=1
141
+ if i < maximal_count_of_retry
142
+ if i.zero?
143
+ logger.info 'No TWS!'
144
+ else
145
+ logger.info {"No TWS Retry #{i}/ #{maximal_count_of_retry} " }
146
+ end
147
+ sleep i<50 ? 10 : 60 # Die ersten 50 Versuche im 10 Sekunden Abstand, danach 1 Min.
148
+ retry
149
+ else
150
+ logger.info { "Giving up!!" }
151
+ return false
152
+ end
153
+ rescue Errno::EHOSTUNREACH => e
154
+ logger.error 'Cannot connect to specified host'
155
+ logger.error e
156
+ return false
157
+ rescue SocketError => e
158
+ logger.error 'Wrong Adress, connection not possible'
159
+ return false
160
+ end
161
+
162
+ tws.start_reader
163
+ # let NextValidId-Event appear
164
+ (1..30).each do |r|
165
+ break if tws.next_local_id.present?
166
+ sleep 0.1
167
+ if r == 30
168
+ error "Connected, NextLocalId is not initialized. Repeat with another client_id"
169
+ end
170
+ end
171
+ # initialize @accounts (incl. aliases)
172
+ tws.send_message :RequestFA, fa_data_type: 3
173
+ logger.debug { "Communications successfully established" }
174
+ end # def
175
+
176
+
177
+
178
+
179
+
180
+ def reconnect
181
+ logger.progname = 'Gateway#reconnect'
182
+ if tws.present?
183
+ disconnect
184
+ sleep 1
185
+ end
186
+ logger.info "trying to reconnect ..."
187
+ connect
188
+ end
189
+
190
+ def disconnect
191
+ logger.progname = 'Gateway#disconnect'
192
+
193
+ tws.disconnect if tws.present?
194
+ @accounts = [] # each{|y| y.update_attribute :connected, false }
195
+ logger.info "Connection closed"
196
+ end
197
+
198
+
199
+ =begin
200
+ Proxy for Connection#SendMessage
201
+ allows reconnection if a socket_error occurs
202
+
203
+ checks the connection before sending a message.
204
+
205
+ =end
206
+
207
+ def send_message what, *args
208
+ logger.tap{|l| l.progname = 'Gateway#SendMessage' }
209
+ begin
210
+ if check_connection
211
+ tws.send_message what, *args
212
+ else
213
+ error( "Connection lost. Could not send message #{what}" )
214
+ end
215
+ end
216
+ end
217
+
218
+ =begin
219
+ Cancels one or multible orders
220
+
221
+ Argument is either an order-object or a local_id
222
+
223
+ =end
224
+
225
+ def cancel_order *orders
226
+
227
+ logger.tap{|l| l.progname = 'Gateway#CancelOrder' }
228
+
229
+ orders.compact.each do |o|
230
+ local_id = if o.is_a? (IB::Order)
231
+ logger.info{ "Cancelling #{o.to_human}" }
232
+ o.local_id
233
+ else
234
+ o
235
+ end
236
+ send_message :CancelOrder, :local_id => local_id.to_i
237
+ end
238
+
239
+ end
240
+
241
+ =begin
242
+ clients returns a list of Account-Objects
243
+
244
+ If only one Account is present, Client and Advisor are identical.
245
+
246
+ =end
247
+ def clients
248
+ @accounts.find_all{ |x| x.is_a? IB::User } # IB::User and IB::DemoUser
249
+ end
250
+ =begin
251
+ The Advisor is always the first account. Anything works with single user accounts as well.
252
+ =end
253
+ def advisor
254
+ @accounts.first
255
+ end
256
+
257
+ =begin
258
+ account_data provides a thread-safe access to linked content of accounts
259
+
260
+ (AccountValues, Portfolio-Values, Contracts and Orders)
261
+
262
+ It returns an Array of the return-values of the block
263
+
264
+ If called without a parameter, all clients are accessed
265
+ =end
266
+
267
+ def account_data account_or_id=nil
268
+
269
+ safe = ->(account) do
270
+ @account_lock.synchronize do
271
+ yield account
272
+ end
273
+ end
274
+
275
+ if block_given?
276
+ if account_or_id.present?
277
+ sa = account_or_id.is_a?(IB::Account) ? account_or_id : @accounts.detect{|x| x.account == account_or_id }
278
+ safe[sa] if sa.is_a? IB::Account
279
+ else
280
+ clients.map{|sa| safe[sa]}
281
+ end
282
+ end
283
+ end
284
+
285
+
286
+ private
287
+
288
+ def random_id
289
+ rand 99999
290
+ end
291
+
292
+
293
+ def prepare_connection &b
294
+ self.tws = IB::Connection.new @connection_parameter, &b
295
+ @accounts = @local_orders = Array.new
296
+ load_managed_accounts if @gateway_parameter[:s_m_a]
297
+ # prepare Advisor-User hierachy
298
+ initialize_alerts if @gateway_parameter[:s_a]
299
+ # initialize_order_handling if @gateway_parameter[:s_o_m] || @gateway_parameter[:g_a_d]
300
+ subscribe_account_updates # account-init.rb
301
+
302
+ ## apply other initialisations which should apper before the connection as block
303
+ ## i.e. after connection order-state events are fired if an open-order is pending
304
+ ## a possible response is best defined before the connect-attempt is done
305
+ # ## Attention
306
+ # ## @accounts are not initialized yet (empty array)
307
+ # if block_given?
308
+ # yield self
309
+
310
+ # end
311
+ end
312
+
313
+
314
+ def initialize_alerts
315
+
316
+ tws.subscribe( :AccountUpdateTime ){| msg | logger.debug{ msg.to_human }}
317
+ tws.subscribe(:Alert) do |msg|
318
+ logger.progname = 'Gateway#Alerts'
319
+ logger.debug " ----------------#{msg.code}-----"
320
+ # delegate anything to IB::Alert
321
+ IB::Alert.send("alert_#{msg.code}", msg )
322
+ end
323
+ end
324
+
325
+
326
+ # Handy method to ensure that a connection is established and active.
327
+ #
328
+ # The connection is reset on the IB-side at least once a day. Then the
329
+ # IB-Ruby-Connection has to be reestablished, too.
330
+ #
331
+ # check_connection reconnects if necessary and returns false if the connection is lost.
332
+ #
333
+ # It delays the process by 6 ms (150 MBit Cable connection)
334
+ #
335
+ # a = Time.now; G.check_connection; b= Time.now ;b-a
336
+ # => 0.00066005
337
+ #
338
+ def check_connection
339
+ answer = nil; count=0
340
+ z= tws.subscribe( :CurrentTime ) { answer = true }
341
+ while (answer.nil?)
342
+ begin
343
+ tws.send_message(:RequestCurrentTime) # 10 ms ##
344
+ i=0; loop{ break if answer || i > 40; i+=1; sleep 0.0001}
345
+ rescue IOError, Errno::ECONNREFUSED # connection lost
346
+ count = 6
347
+ rescue IB::Error # not connected
348
+ reconnect
349
+ count +=1
350
+ sleep 1
351
+ retry if count <= 5
352
+ end
353
+ count +=1
354
+ break if count > 5
355
+ end
356
+ tws.unsubscribe z
357
+ count < 5 && answer # return value
358
+ end
359
+ end # class
360
+
361
+ end # module
362
+