IB-Ruby translates data received from the TWS in IB::Model
objects which are subsequently stored
in database classes prefixed by 'ib'
(Namespace: IB
).
The date-grid is implemented by database-classes prefixed by 'tg'
(Namespace: TG
).
It is advisable to put calculations and further analyses to another namespace.
HC::Portfolio
As living example, HC::Portfolio
is included, which is initialized through bake
:
module HC
class Portfolio
def self.bake
gateway= IB::OrientGateway.current # OrientDB-Instance
gateway.get_account_data # ask TWS for portfolio-data
gateway.clients.map do | the_client | # perform task for all clients
p = self.new
p.import_account_data from: the_client # implies update of p, assigns a rid
p.import_positions from: the_client # assigns portfolio-values to the grid
HC::MY_USER.create from: p, to: the_client # bidirectional link to account
Date.today.to_tg.assign vertex: p, via: HC::D2F # bidirectional link to the time-grid
p.rid # return_value
end
end
end
end
The last command of the bake-method assigns the portfolio-object via HC::D2F
to the time-grid.
In the process, a bidirectional link TimeGrid -- out HC::D2F -- in -- Portfolio
is implemented.
to_human
Most classes in IB-Ruby ship with a to_human
-method. HC::Portfolio#to_human
summarizes
the associated objects:
> HC::Portfolio.last.to_human
=> "<Portfolio[244:0]: account:<user U1****12>, 36 AccountValues; 18 Positions >"
Traverse through connected nodes
Active-Orient provides an intuitive interface to connected nodes
> "7.12.2020".to_tg.out( HC::D2F ).in => Array of assigned Portfolio's
> HC::Portfolio.last.in( /d2f/ ).out.datum => ["7.12.2020"] # Array of time-grid-objects
Edges can be pronounced either by the class-name or a regular expression, which relies on the database-class name.
portfolios
is provided for TG::TAG
(TimeGrid)-Vertices.Get Account-Data
In the IB-Gateway-Section a primitive approach to access Account-Value-Data is presented.
HC::Portfolio#import_account_data
imports IB::AccountValue
-Objects as hash into the database.
HC::Portfolio.values[ :key ][:ALL][:EUR] => value
[:USD] => value
[:S] [:EUR] => Value
[:C] [:EUR] => value
Thus to access the data from yesterday:
Date.today.to_tg.prev.portfolios &.values.map{|y| y[:SMA][:ALL][:EUR] }
=> [-9033.37, 12565.67]
# or
Date.yesterday.to_tg.portfolios &.values.map{|y| y.slice /SM/ }
=> [{:SMA=>{:ALL=>{:EUR=>-4701.63}, :S=>{:EUR=>-4701.63}}}, (...)
slice
is a method of OrienhtSupport::Array
. It overwrites Array#slice
(available since ruby 2.6) Database Solution
The same result is achieved using a simple database query
select values[ key ] as key from hc_portfolio
which can be send to the database through execute
> V.db.execute{ "select values[\"SMA\"] as SMA from hc_portfolio " }
=> [{:SMA=>{:ALL=>{:EUR=>-4701.63}, :S=>{:EUR=>-4701.63}}}, ...
Monitor the Portfolio
HC::Portfolio.bake
imports IB::PortfolioValues
as well. The records are connected via
HC::HAS_POSITION
-edges.
`TG::Tag -- out `HC::D2F` in -- `HC::Portfolio` -- `out` HC_HAS_POSITION `in` -- `IB::PortfolioValue`
To access PortfolioValues just join the edges.
> Date.today.to_tg.out( /d2F/).in.out( /has/).in.contract
=> [["#202:0", "#203:0", "#204:0"], ...
# contracts are stored as rid-valus which have to be expanded:
> puts Date.today.to_tg.out(HC::D2F).in.out( HC::HAS_POSITION ).in.contract.expand.to_human
<Stock: J36 USD SGX>
<Stock: PRX EUR AEB>
<Stock: WFC USD NYSE>
<Stock: ALB USD NYSE>
(..)
Other properties of IB::PortfolioValue
can be obtained through ruby methods
> portfolio_values = Date.today.to_tg.out(TG::D2F).in.out( HC::HAS_POSITION ).in
> puts portfolio_values.map{|y| [ y.contract.expand.to_human, y.unrealized_pnl ].join " -> \t "}
<Option: AIR 20210115 put 86.0 DTB EUR> -> 277.44
<Stock: BABA USD NYSE> -> 222.84
<Stock: BEPC USD NYSE> -> 394.47
<Option: BOSS 20210219 put 25.0 DTB EUR> -> -83.07
(...)
Specific positions can be filtered in the process.
Instead of in
and out
, edges have to be accessed through Vertex#nodes.
# Get Jardine Matheson
> jardine = IB::Contract.where symbol: 'J36'
# Fetch PortfolioPositions
> portfolio = Date.today.to_tg.out(HC::D2F).in.first
> portfolio.nodes( via: /has/, where: { contract: jardine.rid } ).market_value
#INFO->select outE('hc_has_position').in[ contract in [202:0] ] from #246:20
=> [6480.48]
Monitor the progress of a position
This approach works with a range of datasets as well.
Assume, we »baked« a few HC::Portfolios
in the last days. Then Date.today.to_tg.environment(7,0)
addresses the TG::Tag
vertices of the last week. Documentation: Time-Grid Gem
Then
> Date.today.to_tg
.environment(7,0)
.out(HC::D2F).in
.nodes( via: /has/, where: { contract: jardine.rid } )
.market_value
=> [[[6424.77]], [[6522.31]], [[6480.48]]]
In this case, only one portfolio is monitored by IB-Ruby, thus only one element is left in each sub-array.
Note that only three TG_Tag
vertices were populated. Empty TimeGrid-Entries are silently ignored.
IB::Account, IB::Advisor, IB::User
Finally each HC::Portfolio
-record is connected to an IB::Account
-reference. Again, the connection
is bidirectional, each IB::Account
knows how many HC::Portfolio
records are saved and each
HC::Portfolio
is bind to an Account. Inheritance is maintained.
> IB::Account.first.to_human
INFO->select from ib_account order by @rid limit 1
=> "<user U1****12>"
> puts IB::Account.first.in.out.to_human
<Portfolio[242:0]: account:<user U12****2>, 35 AccountValues; 19 Positions >
<Portfolio[243:0]: account:<user U12****2>, 35 AccountValues; 19 Positions >
<Portfolio[244:0]: account:<user U12****2>, 36 AccountValues; 18 Positions >
<Portfolio[245:0]: account:<user U12****2>, 36 AccountValues; 18 Positions >
<Portfolio[246:0]: account:<user U12****2>, 36 AccountValues; 18 Positions >
<Portfolio[247:0]: account:<user U12****2>, 36 AccountValues; 18 Positions >
<Portfolio[248:0]: account:<user U12****2>, 34 AccountValues; 20 Positions >
<Portfolio[249:0]: account:<user U12****2>, 35 AccountValues; 19 Positions >
<Portfolio[242:1]: account:<user U12****2>, 38 AccountValues; 21 Positions >
<Portfolio[243:1]: account:<user U12****2>, 38 AccountValues; 22 Positions >
> IB::Account.first.in.out.in( /d2F/).out.datum
=> [[Mon, 07 Dec 2020], [Tue, 08 Dec 2020], [Wed, 09 Dec 2020], ...