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,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