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