ib-orientdb 1.0

Sign up to get free protection for your applications and to get access to all the features.
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
+