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,92 @@
1
+ #!/usr/bin/env ruby
2
+ ### loads the active-orient environment
3
+ ### and starts an interactive shell
4
+ ###
5
+ ### Parameter: t)ws | g)ateway (or number of port ) Default: Gateway ,
6
+ ### client_id , Default 2000
7
+ ###
8
+ ### Define Parameter in file console.yml
9
+ ###
10
+ require 'bundler/setup'
11
+ require 'yaml'
12
+ require 'ib-orientdb'
13
+ require 'ib/orient-gateway'
14
+ require 'logger'
15
+
16
+
17
+
18
+
19
+ # read items from console.yml
20
+ read_yml = -> (key) do
21
+ YAML::load_file( File.expand_path('../../connect.yml',__FILE__))[key]
22
+ end
23
+ project_root = File.expand_path('../..', __FILE__)
24
+ model_dir = project_root + '/lib/models'
25
+
26
+ puts
27
+ puts ">> IB-OrientDB Interactive Console <<"
28
+ puts '-'* 45
29
+ puts
30
+ puts " ... preparing environment"
31
+ include LogDev
32
+ include IB
33
+ require 'irb'
34
+
35
+ environment = ARGV[0] || 'Development'
36
+ environment = case environment
37
+ when /^[pP]/
38
+ :production
39
+ when /^[dD]/
40
+ :development
41
+ when /^[tT]/
42
+ :test
43
+ end
44
+ tws = read_yml[:tws][environment]
45
+ orientdb = read_yml[:orientdb][environment]
46
+
47
+ ARGV.clear
48
+ logger = default_logger # Logger.new STDOUT
49
+
50
+ ## The Block takes instructions which are executed after initializing all instance-variables
51
+ ## and prior to the connection-process
52
+ ## Here we just subscribe to some events
53
+
54
+ module TG; end
55
+ ActiveOrient::Model.keep_models_without_file = false
56
+ ActiveOrient::Model.model_dir = model_dir
57
+ IB::Setup.connect( tws: tws, orientdb: orientdb, kind: :gateway) do |c|
58
+ module HC; end
59
+ ActiveOrient::Init.define_namespace { HC }
60
+
61
+ ActiveOrient::OrientDB.new# model_dir: model_dir
62
+
63
+ puts '-'* 83
64
+ print '-'* 35
65
+ print " TWS Connect "
66
+ puts '-'* 35
67
+ c.subscribe( :ContractData, :BondContractData) { |msg| logger.info { msg.contract.to_human } }
68
+ c.subscribe( :Alert, :ContractDataEnd, :ManagedAccounts, :OrderStatus ) {| m| logger.info { m.to_human } }
69
+ c.subscribe( :PortfolioValue, :AccountValue, :OrderStatus, :OpenOrderEnd, :ExecutionData ) {| m| logger.info { m.to_human }}
70
+
71
+ c.logger.level = Logger::WARN # INFO
72
+ end
73
+
74
+ C = IB::Connection.current
75
+ G = IB::OrientGateway.current
76
+ puts ActiveOrient::show_classes
77
+ # G.logger.level = 3
78
+ # G.get_account_data
79
+
80
+
81
+ puts ">> IB-OrientDB Interactive Console is ready -- using #{environment.to_s.upcase} environment <<"
82
+ puts '-'* 45
83
+ puts
84
+ puts "Namespace is IB ! "
85
+ puts
86
+ puts "----> C points to the Connection-Instance"
87
+ puts "----> G points to the Gateway-Instance"
88
+ puts
89
+ puts "some basic Messages are subscribed and accordingly displayed"
90
+ puts
91
+ puts '-'* 45
92
+ IRB.start(__FILE__)
@@ -0,0 +1 @@
1
+ ../spec/readme.md
@@ -0,0 +1,10 @@
1
+ # Changelog
2
+
3
+ * Setup OrientDB-Database: lib/init_db.rb
4
+ * Test spec/lib/init_db_spec.rb
5
+ * Creating Setup.connect and integrate in console & spec_helper
6
+ * Migrating Setup.connect to /lib/setup.rb
7
+
8
+
9
+ * TG::Tag#portfolios : returns nil if no entry is detected (instead of [])
10
+
@@ -0,0 +1,42 @@
1
+ require_relative 'lib/version'
2
+
3
+ Gem::Specification.new do |spec|
4
+ spec.name = "ib-orientdb"
5
+ spec.version = IB::OrientDB::VERSION
6
+ spec.authors = ["Hartmut Bischoff"]
7
+ spec.email = ["topofocus@gmail.com"]
8
+
9
+ spec.summary = %q{Part of IB-Ruby. Methods do store data from the TWS in a OrientDB-Database.}
10
+ spec.homepage = "https://ib-ruby.github.io/ib-doc/"
11
+ spec.required_ruby_version = Gem::Requirement.new(">= 2.6.0")
12
+
13
+ # spec.metadata["allowed_push_host"] = "TODO: Set to 'http://mygemserver.com"
14
+
15
+ spec.metadata["homepage_uri"] = spec.homepage
16
+ spec.metadata["source_code_uri"] = "https://github.cm/ib-ruby/ib-orientdb"
17
+ spec.metadata["changelog_uri"] = "https://github.cm/ib-ruby/ib-orientdb/changelog.md"
18
+
19
+ # Specify which files should be added to the gem when it is released.
20
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
21
+ spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
22
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
23
+ end
24
+ spec.bindir = "exe"
25
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
26
+ spec.require_paths = ["lib"]
27
+
28
+
29
+ spec.add_development_dependency "bundler", "~> 2.0"
30
+ spec.add_development_dependency "rspec", "~> 3.0"
31
+ spec.add_development_dependency 'rspec-collection_matchers'
32
+ spec.add_development_dependency 'rspec-its'
33
+
34
+ spec.add_development_dependency "guard"
35
+ spec.add_development_dependency "guard-rspec"
36
+
37
+
38
+ spec.add_dependency "ib-api", "~>972.1"
39
+ spec.add_dependency "ib-extensions"
40
+ spec.add_dependency "active-orient", "~> 0.8"
41
+ spec.add_dependency 'orientdb-time-graph'
42
+ end
@@ -0,0 +1,143 @@
1
+ module IB
2
+ class Alert
3
+ =begin
4
+ The Singleton IB::Alert handles any response to IB:Messages::Incomming:Alert
5
+
6
+ Individual methods can be defined as well as methods responding to a group of error-codes.
7
+ The default-behavior is defined in the method_missing-method. This just logs the object at the debug level.
8
+
9
+ To use the IB::Alert facility, first a logger has to be assigned to the Class.
10
+ IB::Alert.logger = Logger.new(STDOUT)
11
+
12
+ Default-wrappers to completely ignore the error-message (ignore_alert)
13
+ and to log the object in a different log-level (log_alert_in [warn,info,error] ) are defined in base_alert
14
+ Just add
15
+ module IB
16
+ class Alert
17
+ log_alert_in_warn {list of codennumbers}
18
+ end
19
+ end
20
+ to your code
21
+
22
+
23
+ IB::Gateway calls the methods in response of subscribing to the :Alert signal by calling
24
+ IB::Alert.send("alert_#{msg.code}", msg )
25
+
26
+ To define a response to the code 134 ( Modify order failed) a method like
27
+ module IB
28
+ class Alert
29
+ def self.alert_134 msg
30
+ (your code)
31
+ end
32
+ end
33
+ end
34
+ has to be written.
35
+
36
+ Important: The class is accessed asynchronically. Be careful while raising interrupts.
37
+
38
+ =end
39
+
40
+ # acts as prototype for any generated method
41
+ #require 'active_support'
42
+
43
+ def self.logger
44
+ IB::Connection.logger
45
+ end
46
+
47
+ def self.method_missing( method_id =nil, msg = nil , *args, &block )
48
+ # puts "METHOD MISSING"
49
+ # puts msg
50
+ if msg.is_a? IB::Messages::Incoming::Alert
51
+ logger.debug { msg.to_human }
52
+ else
53
+ logger.error { "Argument to IB::Alert is not a IB::Messages::Incoming::Alert" }
54
+ logger.error { "The object: #{msg.inspect} " }
55
+ end
56
+ rescue NoMethodError
57
+ unless logger.nil?
58
+ logger.error { "The Argument is not a valid IB::Messages:Incoming::Alert object"}
59
+ logger.error { "The object: #{msg.inspect} " }
60
+ else
61
+ puts "No Logging-Device specified"
62
+ puts "The object: #{msg.inspect} "
63
+ end
64
+ end
65
+
66
+
67
+
68
+ class << self
69
+
70
+ def ignore_alert *codes
71
+ codes.each do |n|
72
+ class_eval <<-EOD
73
+ def self.alert_#{n} msg
74
+ # even the log_debug entry is suppressed
75
+ end
76
+ EOD
77
+ end
78
+ end
79
+ def log_alert_in_debug *codes
80
+ codes.each do |n|
81
+ class_eval <<-EOD
82
+ def self.alert_#{n} msg
83
+ logger.debug { msg.to_human }
84
+ end
85
+ EOD
86
+ end
87
+ end
88
+ def log_alert_in_info *codes
89
+ codes.each do |n|
90
+ class_eval <<-EOD
91
+ def self.alert_#{n} msg
92
+ logger.info { msg.to_human }
93
+ end
94
+ EOD
95
+ end
96
+ end
97
+ def log_alert_in_warn *codes
98
+ codes.each do |n|
99
+ class_eval <<-EOD
100
+ def self.alert_#{n} msg
101
+ logger.warn { msg.to_human }
102
+ end
103
+ EOD
104
+ end
105
+ end
106
+
107
+ def log_alert_in_error *codes
108
+ codes.each do |n|
109
+ class_eval <<-EOD
110
+ def self.alert_#{n} msg
111
+ if msg.error_id.present? && msg.error_id > 0
112
+ logger.error { msg.message + ' id: ' + msg.error_id.to_s }
113
+ else
114
+ logger.error { msg.message }
115
+ end
116
+ end
117
+ EOD
118
+ end
119
+ end
120
+ end
121
+
122
+ ignore_alert 200 , # is handled by IB::Contract.update_contract
123
+ 2100, # API client has been unsubscribed from account data
124
+ 2104, #
125
+ 2105,
126
+ 2106,
127
+ 2158,
128
+ 399, # your order will not be placed at the exchange until
129
+ 321 # Error validating request.-'ce': cause - FA data operations ignored for non FA customers
130
+
131
+ log_alert_in_info 1102, #Connectivity between IB and Trader Workstation has been restored
132
+ 2103 #Market data farm connection is broken
133
+
134
+ log_alert_in_error 320, 323, 324, #ServerError
135
+ ## 110, # The price does not conform to the minimum price variation
136
+ # 103, #duplicate order ## order-alerts
137
+ # 201, #deleted objecta ## order-alerts
138
+ 326 #Unable connect as the client id is already in use
139
+
140
+ log_alert_in_warn 354, #Requested market data is not subscribed
141
+ 1100 #Connectivity between IB and Trader Workstation has been lost.
142
+ end
143
+ end
@@ -0,0 +1,16 @@
1
+ # These Alerts are always active
2
+ module IB
3
+ class Alert
4
+
5
+ def self.alert_2102 msg
6
+ # Connectivity between IB and Trader Workstation has been restored - data maintained.
7
+ sleep 0.1 # no need to wait too long.
8
+ if IB::OrientGateway.current.check_connection
9
+ IB::Connection.logger.debug { "Alert 2102: Connection stable" }
10
+ else
11
+ IB::OrientGateway.current.reconnect
12
+ end
13
+ end
14
+
15
+ end
16
+ end
@@ -0,0 +1,68 @@
1
+ module IB
2
+ class Alert
3
+
4
+ def self.alert_202 msg
5
+ # do anything in a secure mutex-synchronized-environment
6
+ any_order = IB::Gateway.current.account_data do | account |
7
+ order= account.locate_order( local_id: msg.error_id )
8
+ if order.present? && ( order.order_state.status != 'Cancelled' )
9
+ order.order_states.update_or_create( IB::OrderState.new( status: 'Cancelled',
10
+ perm_id: order.perm_id,
11
+ local_id: order.local_id ) ,
12
+ :status )
13
+
14
+ end
15
+ order # return_value
16
+ end
17
+ if any_order.compact.empty?
18
+ IB::Gateway.logger.error{"Alert 202: The deleted order was not registered: local_id #{msg.error_id}"}
19
+ end
20
+
21
+ end
22
+
23
+
24
+ class << self
25
+ =begin
26
+ IB::Alert#AddOrderstateAlert
27
+
28
+ The OrderState-Record is used to record the history of the order.
29
+ If selected Alert-Messages appear, they are added to the Order.order_state-Array.
30
+ The last Status is available as Order.order_state, all states are accessible by Order.order_states
31
+
32
+ The TWS-Message-text is stored to the »warning-text«-field.
33
+ The Status is always »rejected«.
34
+ If the first OrderState-object of a Order is »rejected«, the order is not placed at all.
35
+ Otherwise only the last action is not applied and the order is unchanged.
36
+
37
+ =end
38
+ def add_orderstate_alert *codes
39
+ codes.each do |n|
40
+ class_eval <<-EOD
41
+ def self.alert_#{n} msg
42
+
43
+ if msg.error_id.present?
44
+ IB::Gateway.current.account_data do | account |
45
+ order= account.locate_order( local_id: msg.error_id )
46
+ if order.present? && ( order.order_state.status != 'Rejected' )
47
+ order.order_states.update_or_create( IB::OrderState.new( status: 'Rejected' ,
48
+ perm_id: order.perm_id,
49
+ warning_text: '#{n}: '+ msg.message,
50
+ local_id: msg.error_id ), :status )
51
+
52
+ IB::Gateway.logger.error{ msg.to_human }
53
+ end # order present?
54
+ end # mutex-environment
55
+ end # branch
56
+ end # def
57
+ EOD
58
+ end # loop
59
+ end # def
60
+ end
61
+ add_orderstate_alert 103, # duplicate order
62
+ 201, # deleted object
63
+ 105, # Order being modified does not match original order
64
+ 462, # Cannot change to the new Time in Force:GTD
65
+ 329, # Cannot change to the new order type:STP
66
+ 10147 # OrderId 0 that needs to be cancelled is not found.
67
+ end # class Alert
68
+ end # module IB
@@ -0,0 +1,12 @@
1
+ require 'bundler/setup'
2
+
3
+ require 'active-orient'
4
+ require 'orientdb_time_graph'
5
+
6
+ DB = true
7
+ require 'ib/base_properties'
8
+ require 'ib-api'
9
+ require_relative 'ib/setup-orientdb'
10
+ require_relative 'support'
11
+
12
+
@@ -0,0 +1,115 @@
1
+ require 'ib/alerts/base-alert'
2
+ require 'ib/models/account'
3
+
4
+ module IB
5
+ class Alert
6
+ class << self
7
+ def alert_2101 msg
8
+ logger.error {msg.message}
9
+ @status_2101 = msg.dup
10
+ end
11
+
12
+ def status_2101 account # resets status and raises IB::TransmissionError
13
+ error account.account + ": " +@status_2101.message, :reader unless @status_2101.nil?
14
+ @status_2101 = nil # always returns nil
15
+ end
16
+ end
17
+ end
18
+ end # module
19
+
20
+ module AccountInfos
21
+
22
+ =begin
23
+ Queries the tws for Account- and PortfolioValues
24
+ The parameter can either be the account_id, the IB::Account-Object or
25
+ an Array of account_id and IB::Account-Objects.
26
+
27
+ raises an IB::TransmissionError if the account-data are not transmitted in time (1 sec)
28
+
29
+ raises an IB::Error if less then 100 items are recieved-
30
+ =end
31
+ def get_account_data *accounts, watchlists: []
32
+
33
+ logger.progname = 'Gateway#get_account_data'
34
+
35
+ @account_data_subscription ||= subscribe_account_updates
36
+
37
+ accounts = clients if accounts.empty?
38
+ logger.warn{ "No active account present. AccountData are NOT requested" } if accounts.empty?
39
+ # Account-infos have to be requested sequencially.
40
+ # subsequent (parallel) calls kill the former once on the tws-server-side
41
+ # In addition, there is no need to cancel the subscription of an request, as a new
42
+ # one overwrites the active one.
43
+ accounts.each do | ac |
44
+ account = ac.is_a?( IB::Account ) ? ac : clients.find{|x| x.account == ac }
45
+ error( "No Account detected " ) unless account.is_a? IB::Account
46
+ # don't repeat the query until 170 sec. have passed since the previous update
47
+ if account.last_updated.nil? || ( Time.now - account.last_updated ) > 170 # sec
48
+ logger.debug{ "#{account.account} :: Requesting AccountData " }
49
+ account.update_attribute :connected, false # indicates: AccountUpdate in Progress
50
+ # reset account and portfolio-values
51
+ account.portfolio_values = []
52
+ account.account_values = []
53
+ send_message :RequestAccountData, subscribe: true, account_code: account.account
54
+ Timeout::timeout(3, IB::TransmissionError, "RequestAccountData failed (#{account.account})") do
55
+ # initialize requests sequencially
56
+ loop{ sleep 0.1; break if account.connected }
57
+ end
58
+ if watchlists.present?
59
+ watchlists.each{|w| error "Watchlists must be IB::Symbols--Classes :.#{w.inspect}" unless w.is_a? IB::Symbols }
60
+ account.organize_portfolio_positions watchlists
61
+ end
62
+ send_message :RequestAccountData, subscribe: false ## do this only once
63
+ else
64
+ logger.info{ "#{account.account} :: Using stored AccountData " }
65
+ end
66
+ end
67
+ end
68
+
69
+
70
+ def all_contracts
71
+ clients.map(&:contracts).flat_map(&:itself).uniq(&:con_id)
72
+ end
73
+
74
+
75
+ private
76
+
77
+ # The subscription method should called only once per session.
78
+ # It places subscribers to AccountValue and PortfolioValue Messages, which should remain
79
+ # active through its session.
80
+ #
81
+
82
+ def subscribe_account_updates continously: true
83
+ tws.subscribe( :AccountValue, :PortfolioValue,:AccountDownloadEnd ) do | msg |
84
+ account_data( msg.account_name ) do | account | # enter mutex controlled zone
85
+ case msg
86
+ when IB::Messages::Incoming::AccountValue
87
+ account.account_values << msg.account_value
88
+ account.update_attribute :last_updated, Time.now
89
+ logger.debug { "#{account.account} :: #{msg.account_value.to_human }"}
90
+ when IB::Messages::Incoming::AccountDownloadEnd
91
+ if account.account_values.size > 10
92
+ # simply don't cancel the subscripton if continously is specified
93
+ # the connected flag is set in any case, indicating that valid data are present
94
+ send_message :RequestAccountData, subscribe: false, account_code: account.account unless continously
95
+ account.update_attribute :connected, true ## flag: Account is completely initialized
96
+ logger.info { "#{account.account} => Count of AccountValues: #{account.account_values.size}" }
97
+ else # unreasonable account_data recieved - request is still active
98
+ error "#{account.account} => Count of AccountValues too small: #{account.account_values.size}" , :reader
99
+ end
100
+ when IB::Messages::Incoming::PortfolioValue
101
+ account.contracts.update_or_create msg.contract
102
+ account.portfolio_values << msg.portfolio_value
103
+ # msg.portfolio_value.account = account
104
+ # link contract -> portfolio value
105
+ # account.contracts.find{ |x| x.con_id == msg.contract.con_id }
106
+ # .portfolio_values
107
+ # .update_or_create( msg.portfolio_value ) { :account }
108
+ logger.debug { "#{ account.account } :: #{ msg.contract.to_human }" }
109
+ end # case
110
+ end # account_data
111
+ end # subscribe
112
+ end # def
113
+
114
+
115
+ end # module