ib-orientdb 1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
|