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,112 @@
1
+ module HC; end
2
+
3
+ module IB
4
+ module Setup
5
+ # connects to a running interactive brokers TWS or Gateway
6
+ # and to an active OrientDB-Server.
7
+ #
8
+ #
9
+ # Parameter tws: host: (default 'localhost')
10
+ # port: (default 4002 , specify 4001/4002 (Gateway) or 7496/7497 ())
11
+ # Parameter orientdb: server: ( default: 'localhost' )
12
+ # user:
13
+ # password:
14
+ # database: ( default: 'temp' )
15
+ #
16
+ # The parameter are transmitted to IB::Connection and OrientDB without further checking
17
+ #
18
+ # It returns the active Connection
19
+ #
20
+ # The optional block is evaluated in the context of the initialized, but not connected IB::Connection.
21
+ #
22
+ #
23
+ #
24
+ def self.connect tws:, orientdb:, kind: :connection
25
+ project_root = File.expand_path('../..', __FILE__)
26
+ ActiveOrient::Init.connect **orientdb
27
+ ActiveOrient::Model.keep_models_without_file = false
28
+ #IB::Model = V --> Dynamic Assignment Error, this does the same:
29
+ IB.const_set( :Model, V )
30
+
31
+ TG.connect { project_root + '/models'}
32
+
33
+ ## There are two locations of modelfiles
34
+ ## 1. ib-api-model files
35
+ ## 2. ib.orientdb-model files
36
+ model_dir =[ (Pathname.new( `gem which ib-api`.to_s[0..-2]).dirname + "models/").to_s ,
37
+ project_root + '/models']
38
+ ActiveOrient::Init.define_namespace { IB }
39
+ ActiveOrient::OrientDB.new model_dir: model_dir
40
+
41
+ init_database
42
+ require 'ib/messages'
43
+
44
+ target = (kind == :connection) ? IB::Connection : IB::OrientGateway
45
+ if block_given?
46
+ target.new( **tws ) { |c| yield c }
47
+ else
48
+ target.new **tws
49
+ end
50
+
51
+ require 'ib/extensions'
52
+
53
+ end
54
+
55
+ def self.init_database db= V.db
56
+ vertices= db.class_hierarchy( base_class: 'V' ).flatten
57
+ return if vertices.include? "ib_contract"
58
+ init_timegraph
59
+ ActiveOrient::Init.define_namespace { IB }
60
+
61
+ ### Vertices
62
+ V.create_class :contract , :account
63
+ IB::Account.create_class :advisor, :user
64
+ IB::Advisor.create_class :demo_advisor
65
+ IB::User.create_class :demo_user
66
+ V.create_class :portfolio_value, :account_value, :underlying, :contract_detail, :bar
67
+ IB::Contract.create_class :option, :future, :stock, :forex, :index, :bag
68
+ IB::Bag.create_class :spread
69
+
70
+ IB::Contract.create_property :con_id, type: :integer, index: :unique
71
+ IB::Contract.create_property :bars, type: :link_list, index: :notunique,
72
+ linked_class: IB::Bar
73
+ IB::Contract.create_property :contract_detail, type: :link, index: :notunique,
74
+ linked_class: IB::ContractDetail
75
+
76
+ IB::Account.create_property :account, type: :string, index: :unique
77
+ IB::Account.create_property :last_access, type: :date
78
+ IB::User.create_property :watchlist, type: :map
79
+ IB::PortfolioValue.create_property :position, type: :decimal
80
+ IB::PortfolioValue.create_property :market_price, type: :decimal
81
+ IB::PortfolioValue.create_property :market_value, type: :decimal
82
+ IB::PortfolioValue.create_property :average_cost, type: :decimal
83
+ IB::PortfolioValue.create_property :realized_pnl, type: :decimal
84
+ IB::PortfolioValue.create_property :unrealized_pnl, type: :decimal
85
+ IB::PortfolioValue.create_property :contract, type: :link, index: :notunique, linked_class: IB::Contract
86
+
87
+ ActiveOrient::Init.define_namespace { HC }
88
+ V.create_class :portfolio
89
+ HC::Portfolio.create_property :account, type: :link, index: :notunique, linked_class: IB::Account
90
+ HC::Portfolio.create_property :positions, type: :link_list, linked_class: IB::PortfolioValue
91
+ HC::Portfolio.create_property :values, type: :map
92
+ #
93
+ ## Edges (Namespace HC)
94
+ E.create_class :has_portfolio, :has_position, :grid, :has_account, :has_strategy
95
+ E.create_class :d2F, :p2U, :my_user
96
+ HC::GRID.uniq_index
97
+ end
98
+
99
+ def self.init_timegraph
100
+ ## Initialize TimeGraph
101
+ TG::Setup.init_database
102
+ TG::TimeGraph.populate 1980..2030
103
+ TG.info
104
+ end
105
+
106
+ def self.clear_database
107
+ IB::Account.delete all: true
108
+ IB::Contract.delete all: true
109
+ end
110
+
111
+ end
112
+ end
@@ -0,0 +1,34 @@
1
+ module Support
2
+ module Logging
3
+ def self.included(base)
4
+ base.extend ClassMethods
5
+ base.send :define_method, :logger do
6
+ base.logger
7
+ end
8
+ end
9
+
10
+ module ClassMethods
11
+ def logger
12
+ @logger
13
+ end
14
+
15
+ def logger=(logger)
16
+ @logger = logger
17
+ end
18
+
19
+ def configure_logger(log=nil)
20
+ if log
21
+ @logger = log
22
+ else
23
+ @logger = Logger.new(STDOUT)
24
+ @logger.level = Logger::INFO
25
+ @logger.formatter = proc do |severity, datetime, progname, msg|
26
+ # "#{datetime.strftime("%d.%m.(%X)")}#{"%5s" % severity}->#{msg}\n"
27
+ "#{"%5s" % severity}->#{msg}\n"
28
+ end
29
+ end # branch
30
+ end # def
31
+ end # module ClassMethods
32
+ end # module Logging
33
+ end # module Support
34
+
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
@@ -0,0 +1,161 @@
1
+ module HC
2
+ class Portfolio
3
+ # describes the snapshot of positions and account-values
4
+ # is stored on a daily base
5
+ #
6
+ #
7
+ # bake generates portfolio-entries for each detected IB::Account,
8
+ # stores converted account-values in the portfolio-record,
9
+ # assigns Portfolio-positions to the time-grid and
10
+ # connects the record to the referenced IB::Account.
11
+ #
12
+ # returns an array with rid's of successfully allocated portfolio-datasets
13
+ #
14
+ def self.bake
15
+
16
+ gateway= IB::OrientGateway.current
17
+ gateway.get_account_data
18
+ gateway.clients.map do | the_client |
19
+
20
+ p = self.new
21
+ p.import_account_data from: the_client # implies update of p, assigns a rid
22
+ p.import_positions from: the_client # assigns portfolio-values to the grid
23
+ HC::MY_USER.create from: p, to: the_client # bidirectional link to account
24
+ Date.today.to_tg.assign vertex: p, via: HC::D2F # bidirectional link to the time-grid
25
+ p.rid # return_value
26
+ end
27
+ end
28
+
29
+ # returns the projection for usage in OrientQuery
30
+ # It filters specific account-values
31
+ #
32
+ # To execute a query, inject the from-parameter via a block
33
+ #
34
+ def account_data_scan_projection search_key
35
+ # values.keys.match(/Ac/).compact.string => ["AccruedCash", "AccruedDividend"]
36
+ fields = values.keys.match(search_key).compact.string
37
+ p = fields.map{|y| "values[\"#{y}\"] as #{y} "}.join(", ")
38
+ if block_given?
39
+ q = OrientSupport::OrientQuery
40
+ o.from( yield )
41
+ .projection( p )
42
+ .execute # return result
43
+ else
44
+ p # return projection string
45
+ end
46
+
47
+
48
+ end
49
+
50
+ # pp Date.today.to_tg.environment(3,0) &.out_hc_d2F.compact.in.values.orient_flatten.map{|y| y[:SMA] }
51
+ #[{:ALL=>{:EUR=>-4701.63}, :S=>{:EUR=>-4701.63}},
52
+ # {:ALL=>{:EUR=>-4702.72}, :S=>{:EUR=>-4702.72}},
53
+ # {:ALL=>{:EUR=>-4658.85}, :S=>{:EUR=>-4658.85}},
54
+ # {:ALL=>{:EUR=>-4658.85}, :S=>{:EUR=>-4658.85}},
55
+ # {:ALL=>{:EUR=>-4658.85}, :S=>{:EUR=>-4658.85}},
56
+ # {:ALL=>{:EUR=>-5840.75}, :S=>{:EUR=>-5840.75}},
57
+ # {:ALL=>{:EUR=>-9033.37}, :S=>{:EUR=>-9033.37}}]
58
+ #
59
+
60
+
61
+ def account_data_scan search_key, search_currency=nil
62
+ return if values.nil? or values.empty?
63
+ values.keys.match(/Ac/).compact.string
64
+ if account_values.is_a? Array
65
+ if search_currency.present?
66
+ account_values.find_all{|x| x.key.match( search_key ) && x.currency == search_currency.upcase }
67
+ else
68
+ account_values.find_all{|x| x.key.match( search_key ) }
69
+ end
70
+ end
71
+ end
72
+
73
+ # Utility function to transform IB:AccountValues into a hash
74
+ #
75
+ # which is stored in the property »value«
76
+ #
77
+ # from is either a IB::Account or an array of IB::AccountValues
78
+ def import_account_data from:
79
+ from = from.account_values if from.is_a? IB::Account
80
+ account_values = Hash.new
81
+ currencies = from.find_all{|x| x.key == :Currency }.map &:value # => ["BASE", "EUR", "SEK", "USD"]
82
+ from.map{|y| y.key.to_s.split('-').first.to_sym}.uniq.each{|x| account_values[ x ] = {} }
83
+ subaccounts = [:C, :F, :S]
84
+
85
+ from.each do |a|
86
+ next if a.currency.empty? || a.value.to_i.zero? || a.value == "1.7976931348623157E308"
87
+ id, subaccount = a.key.to_s.split('-')
88
+ # a.value = a.value[-3] =='.' ? a.value.to_f : a.value.to_i if a.value.is_a?(String)
89
+ a.value = a.value.to_f
90
+ subaccount = 'ALL' if subaccount.nil?
91
+ if account_values[id.to_sym][subaccount.to_sym].present?
92
+ account_values[id.to_sym][subaccount.to_sym].merge! a.currency => a.value
93
+ else
94
+ account_values[id.to_sym][subaccount.to_sym] = { a.currency => a.value }
95
+ end
96
+ end
97
+ account_values.delete_if{|_,l| l.empty?} # return only elements with valid data, ie.non empty values
98
+
99
+ update values: account_values
100
+ end
101
+
102
+
103
+ # Utility function to add IB::PortfolioValues to the TimeGrid
104
+ #
105
+ # from is either a IB::Account or an array of IB::PortfolioValues
106
+ #
107
+ def import_positions from:
108
+ from = from.portfolio_values if from.is_a? IB::Account
109
+
110
+ # delete possibly previous entries
111
+ out_hc_has_position &.in &.map &:delete
112
+ # out_hc_has_position &.map &:delete # edge is automatically removed by delete(vertex)
113
+
114
+ from.each do | portfolio_position |
115
+
116
+ portfolio_position.save
117
+ # build link to portfolio-position
118
+ assign via: HC::HAS_POSITION, vertex: portfolio_position
119
+ # connect with previous entry
120
+ previous = prev_portfolio_position(portfolio_position.contract)
121
+ # puts "previous: #{previous}"
122
+ previous &.assign via: HC::GRID, vertex: pp
123
+ end
124
+ end
125
+
126
+ # go back one step in history, traverse to the portfolio_position where the contract matches
127
+ def prev_portfolio_position(contract)
128
+ prev &.position( contract )
129
+ end
130
+
131
+ # shortcut for the portfolio-positions-array
132
+ def positions
133
+ out( HC::HAS_POSITION ).in # ruby solution, without inheritance
134
+ end
135
+ #################
136
+ ## nodes :out, via: HC::HAS_POSITION # database-query solution with inheritance
137
+ #################
138
+
139
+ def self.positions
140
+ query.nodes :out, via: HC::HAS_POSITION, expand: false # returns a query-object
141
+ end
142
+
143
+ # get selected portfolio-value positions
144
+ def position *contract
145
+
146
+ # generated query: INFO->select outE('hc_has_position').in[ contract=208:0 ] from #249:0
147
+ nodes :out, via: HC::HAS_POSITION , where: { contract: contract } # database-query solution
148
+ #positions &.detect{|x| x.contract == contract } # ruby solution
149
+ end
150
+
151
+ def account
152
+ out( /user/ ).in.first
153
+ end
154
+
155
+
156
+ def to_human
157
+ "<#{self.class.to_s.split(':').last}[#{rid}]: account:#{account.to_human}, #{values.size} AccountValues; #{out_hc_has_position.size} Positions >"
158
+ end
159
+
160
+ end # class
161
+ end # module
@@ -0,0 +1,5 @@
1
+ module IB
2
+ class Account < IB::Model
3
+
4
+ end
5
+ end
@@ -0,0 +1,29 @@
1
+ module IB
2
+ class AccountValue
3
+
4
+ # Assigns an AccountValue to the TimeGrid and inserts a link to the account to the AccountValueRecord
5
+ def bake account:, date: Date.today, via: IB::HAS_ACCOUNT
6
+ self.attributes[:account]= account.rid
7
+ date.to_tg.assign vertex = self, via: via
8
+ end
9
+
10
+
11
+
12
+ def self.bake from:, with:
13
+
14
+ user = HC::Users.where( account_id: with.is_a?(String) ? with : with.account ) &.first
15
+
16
+ raise ArgumentError "No HC::Users record available" unless user.is_a? HC::Users
17
+
18
+ time_graph_index = Date.today.to_tg
19
+ # from.each do | account_value |
20
+ new_ds = create values: {}, account: user, date: Date.today
21
+ new_ds.values << from
22
+ HC::AV.create from: time_graph_index, to: new_ds
23
+ HC::MY_USER.create from: user, to: new_ds
24
+
25
+
26
+ end
27
+
28
+ end
29
+ end
File without changes
@@ -0,0 +1,15 @@
1
+ module IB
2
+ class Contract < IB::Model
3
+
4
+ # if con_id is not set, the contract cannot be saved
5
+ #
6
+ # if con_id or rid is present, the contract is updated
7
+ def save
8
+ return false if con_id.blank? || con_id.zero?
9
+ super
10
+ end
11
+ end
12
+
13
+ end
14
+
15
+
File without changes
File without changes
@@ -0,0 +1,6 @@
1
+ module IB
2
+ class Financials
3
+
4
+
5
+ end # class
6
+ end # module
File without changes
@@ -0,0 +1,10 @@
1
+ module IB
2
+ class PortfolioValue
3
+
4
+ def save
5
+ super
6
+ contract.save unless contract.rid.rid?
7
+ update contract: contract.rid
8
+ end
9
+ end
10
+ end
File without changes
File without changes
@@ -0,0 +1,16 @@
1
+ module TG
2
+ class Tag
3
+ def portfolios
4
+ iz= out( HC::D2F ).compact.map( &:in ) # eliminates entries if no HC::D2F is detected
5
+ iz unless iz.empty? # returns nil if no portfolio is detected
6
+ end
7
+
8
+ def self.portfolios
9
+ query.nodes( :out, via: HC::D2F , expand: false)
10
+ end
11
+ end
12
+ end
13
+
14
+ # current_date.environment2(1).execute.portfolios.positions.contract
15
+ # INFO->select expand ( $c ) let $a = (select from ( traverse inE('tg_grid_of').out from #45:1869 while $depth <= 1 ) where $depth >=1 ), $b = (select from ( traverse outE('tg_grid_of').in from #45:1869 while $depth <= 1 ) where $depth >=1 ), $c= UNIONALL($a,$b)
16
+ # => [[["#186:0", "#202:0", "#203:0", "#188:1", "#204:0", "#194:0", "#187:0", "#189:0", "#190:0", "#191:0", "#192:0", "#186:2", "#206:0", "#207:0", "#208:0", "#195:0", "#187:1", "#193:0", "#186:1", "#209:0"]]]
@@ -0,0 +1,21 @@
1
+ class Array
2
+ # enables calling members of an array. which are hashes by it name
3
+ # i.e
4
+ #
5
+ # 2.5.0 :006 > C.received[:OpenOrder].local_id
6
+ # => [16, 17, 21, 20, 19, 8, 7]
7
+ # 2.5.0 :007 > C.received[:OpenOrder].contract.to_human
8
+ # => ["<Bag: IECombo SMART USD legs: >", "<Stock: GE USD>", "<Stock: GE USD>", "<Stock: GE USD>", "<Stock: GE USD>", "<Stock: WFC USD>", "<Stock: WFC USD>"]
9
+ #
10
+ # its included temporary
11
+ #
12
+ # to_do: substitute with a delegation solution
13
+
14
+ def method_missing(method, *key)
15
+ unless method == :to_hash || method == :to_str #|| method == :to_int
16
+ return self.compact.map{|x| x.public_send(method, *key)}
17
+ end
18
+
19
+ end
20
+ end # Array
21
+