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.
- checksums.yaml +7 -0
- data/.gitignore +56 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +12 -0
- data/Gemfile.lock +128 -0
- data/Guardfile +24 -0
- data/LICENSE +21 -0
- data/README.md +41 -0
- data/Rakefile +6 -0
- data/bin/console +99 -0
- data/bin/gateway +92 -0
- data/bin/readme.md +1 -0
- data/changelog.md +10 -0
- data/ib-orientdb.gemspec +42 -0
- data/lib/alerts/base-alert.rb +143 -0
- data/lib/alerts/gateway-alerts.rb +16 -0
- data/lib/alerts/order-alerts.rb +68 -0
- data/lib/ib-orientdb.rb +12 -0
- data/lib/ib/account-infos.rb +115 -0
- data/lib/ib/account-init.rb +151 -0
- data/lib/ib/orient-gateway.rb +362 -0
- data/lib/ib/setup-orientdb.rb +112 -0
- data/lib/logging.rb +34 -0
- data/lib/models/hc/d2_f.rb +0 -0
- data/lib/models/hc/grid.rb +0 -0
- data/lib/models/hc/has_portfolio.rb +0 -0
- data/lib/models/hc/has_position.rb +0 -0
- data/lib/models/hc/has_strategy.rb +0 -0
- data/lib/models/hc/hc_grid.rb +0 -0
- data/lib/models/hc/my_user.rb +0 -0
- data/lib/models/hc/p2_u.rb +0 -0
- data/lib/models/hc/portfolio.rb +161 -0
- data/lib/models/ib/account.rb +5 -0
- data/lib/models/ib/account_value.rb +29 -0
- data/lib/models/ib/advisor.rb +0 -0
- data/lib/models/ib/contract.rb +15 -0
- data/lib/models/ib/demo_advisor.rb +0 -0
- data/lib/models/ib/demo_user.rb +0 -0
- data/lib/models/ib/financials.rb +6 -0
- data/lib/models/ib/has_account.rb +0 -0
- data/lib/models/ib/portfolio_value.rb +10 -0
- data/lib/models/ib/spread.rb +0 -0
- data/lib/models/ib/user.rb +0 -0
- data/lib/models/tg/tag.rb +16 -0
- data/lib/support.rb +21 -0
- data/lib/version.rb +5 -0
- data/setup.md +83 -0
- 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
|
data/lib/logging.rb
ADDED
@@ -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,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
|
File without changes
|
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"]]]
|
data/lib/support.rb
ADDED
@@ -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
|
+
|