Skip to content

Commit

Permalink
Improved Version of Gateway.
Browse files Browse the repository at this point in the history
Added the option: get_account_data, witch initialises the account_value-, portfolio_value- and contract-Array at the IB::Account-Level by querying the TWS after a successful connection.

The example "account_info" is updated accordingly
  • Loading branch information
Hartmut Bischoff committed Mar 21, 2015
1 parent 0179986 commit 7a0af5e
Show file tree
Hide file tree
Showing 15 changed files with 258 additions and 72 deletions.
6 changes: 4 additions & 2 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@ end

# Platform-specific dependencies
#gem 'sqlite3', '> 1.3.3', :platforms => [:ruby_18, :ruby_19] #:ruby
gem 'activerecord-jdbcsqlite3-adapter', '>= 1.2.2', :platforms => :jruby
gem 'jdbc-sqlite3', '>= 3.7.2', :platforms => :jruby
#gem 'activerecord-jdbcsqlite3-adapter', '>= 1.2.2', :platforms => :jruby
#gem 'jdbc-sqlite3', '>= 3.7.2', :platforms => :jruby
gem 'activemodel', "~>4.2"
gem 'activesupport', "~>4.2"
gem 'mysql2'
#gem "backports"
9 changes: 7 additions & 2 deletions Guardfile
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
# A sample Guardfile
# More info at https://github.com/guard/guard#readme

guard :rspec, cmd: "bundle exec rspec -rdb" do
#guard :rspec, cmd: "bundle exec rspec -rdb" do
guard :rspec, cmd: "bundle exec rspec " do
require "ostruct"

# Generic Ruby apps
Expand All @@ -16,9 +17,13 @@ guard :rspec, cmd: "bundle exec rspec -rdb" do
#

watch(%r{^spec/.+_spec\.rb$})
watch(%r{^lib/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
watch(%r{^lib/models/ib/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
watch(%r{^lib/ib/alerts/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
watch(%r{^lib/ib/messages/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
# watch(%r{^lib/ib/alerts/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
watch('spec/spec_helper.rb') { "spec" }

watch(%r{^ib/(.+)\.rb$}) { |m| "spec/ib/#{m[1]}_spec.rb" }
watch(%r{^models/(.+)\.rb$}) { |m| "spec/models/#{m[1]}_spec.rb" }
watch(%r{^spec/support/(.+)\.rb$}) { "spec" }
end
Expand Down
46 changes: 23 additions & 23 deletions example/account_info
Original file line number Diff line number Diff line change
Expand Up @@ -8,26 +8,26 @@ require 'bundler/setup'
$LOAD_PATH.unshift File.expand_path(File.dirname(__FILE__) + '/../lib')
require 'ib-ruby'

# Set log level
log.level = Logger::FATAL

# Connect to IB TWS
ib = IB::Connection.new :client_id => 1112 #, :port => 7496 # TWS

# Only for Advisor accounts: you need to provide account_code such as U666777
account_code = ARGV[0] || ''

# Subscribe to TWS alerts/errors and account-related messages
# that TWS sends in response to account data request
ib.subscribe(:Alert, :AccountValue,
:PortfolioValue, :AccountUpdateTime) { |msg| puts msg.to_human }

ib.send_message :RequestAccountData, :subscribe => true, :account_code => account_code

puts "\nSubscribing to IB account data"
puts "\n******** Press <Enter> to cancel... *********\n\n"
STDIN.gets
puts "Cancelling account data subscription.."

ib.send_message :RequestAccountData, :subscribe => false
sleep 2
# Start the Gateway and initialize the active-accounts
IB::Gateway.new get_account_data:true

accounts = IB::Gateway.current.active_accounts
puts ""
accounts.each do |account|
puts "Contracts ------------------> #{account.account } <----------------------"
puts account.contracts.map &:to_human
puts "-----------------"

end
accounts.each do |account|
puts "Portfolio_values ------------> #{account.account } <----------------------"
puts account.portfolio_values.map &:to_human
puts "-----------------"
end

accounts.each do |account|
puts "Account_values ------------> #{account.account } <----------------------"
puts account.account_values.map{|x| x.to_human if x.value.present? && !x.value.to_i.zero? }.compact
puts "-----------------"

end
2 changes: 1 addition & 1 deletion ib-ruby.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ Gem::Specification.new do |gem|

# Dependencies
gem.add_dependency 'bundler', '~> 1.7'
gem.add_dependency 'activerecord', '~> 4.1'
gem.add_dependency 'activesupport', '~> 4.2'
#gem.add_dependency 'activerecord-jdbcsqlite3-adapter', '>= 1.2.2'
#gem.add_dependency 'jdbc-sqlite3', '>= 3.7.2'
gem.add_dependency 'xml-simple', '~> 1.1'
Expand Down
70 changes: 70 additions & 0 deletions lib/ib/account_infos.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
module AccountInfos
def initialize_account_infos

valid_account = ->( account_id ) do
if IB.db_backed?
Account.single!( account_id)
else
active_accounts.find{| ac | ac.account == account_id }
end
end


tws.subscribe( :AccountValue, :PortfolioValue, :AccountUpdateTime, :AccountDownloadEnd ) do | msg |
logger.progname = 'Gateway#account_infos'
case msg
when IB::Messages::Incoming::AccountUpdateTime
# print "heartbeat "

when IB::Messages::Incoming::AccountValue
account = valid_account[ msg.account_name ]
if account.is_a? IB::Account
logger.progname = 'Gateway#account_value'
account.account_values << msg.account_value
# account.attach_portfoliodata msg.data
else
logger.error{ "wrong AccountValueDataset #{msg.inspect}" }
end
when IB::Messages::Incoming::AccountDownloadEnd

logger.progname = 'AccountDataStorage#account_download_end'
account = valid_account[ msg.account_name ]
logger.info { "#{account.name} => Count of AccountValues: #{account.account_values.size} " }
tws.send_message :RequestAccountData, subscribe: false, account_code: account.account
# account.send_account_data
# @active_subscription=false
when IB::Messages::Incoming::PortfolioValue
account = valid_account[ msg.account_name ]
account.portfolio_values << msg.portfolio_value
account.contracts << msg.contract
end # case
ActiveRecord::Base.connection.close if IB.db_backed?
end # do block


end
=begin
Query's the tws for Account- and PortfolioValues
The parameter can either be the account_id, the IB::Account-Object or
an Array of account_id and IB::Account-Objects.
=end
def get_account_data accounts: :all
logger.progname = 'Gateway#get_account_data'
accounts = active_accounts if accounts == :all
accounts = [accounts] unless accounts.is_a? Array
accounts.each do | account |
account = active_accounts.find{|x| x.account == account } unless account.is_a? IB::Account
if account.is_a? IB::Account
# reset account (works only with tabelless models)
account.portfolio_values, account.account_values, account.contracts = []
tws.send_message :RequestAccountData, :subscribe => true, :account_code => account.account
sleep 1
else
logger.error{ "Invalid Account specified :#{accounts.inspect}" }
end
end

end

end # module
23 changes: 16 additions & 7 deletions lib/ib/gateway.rb
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,12 @@ class Gateway
require 'active_support'

include LogDev # provides default_logger
include AccountInfos # provides Handling of Account-Data provided by the tws
# from active-support. Add Logging at Class + Instance-Level
mattr_accessor :logger
# similar to the Connection-Class: current represents the active instance of Gateway
mattr_accessor :current
mattr_reader :tws
mattr_accessor :tws


=begin
Expand Down Expand Up @@ -61,7 +62,9 @@ def initialize port: 7496,
host: '127.0.0.1',
subscribe_managed_accounts: true,
subscribe_alerts: true,
subscribe_account_infos: true,
connect: false,
get_account_data: false,
logger: default_logger
self.logger = logger
logger.info { '-' * 20 +' initialize ' + '-' * 20 }
Expand All @@ -71,12 +74,13 @@ def initialize port: 7496,
# establish Alert-framework
IB::Alert.logger = logger
# initialise Connection without connecting
tws = IB::Connection.new connection_parameter
self.tws = IB::Connection.new connection_parameter
# prepare Advisor-User hierachie
@accounts=Array.new

initialize_managed_accounts if subscribe_managed_accounts
initialize_alerts if subscribe_alerts
initialize_account_infos if subscribe_account_infos || get_account_data
## apply other initialisations which should apper before the connection as block
## i.e. after connection order-state events are fired if an open-order is pending
## a possible response is best defined before the connect-attempt is done
Expand All @@ -86,7 +90,10 @@ def initialize port: 7496,
# subscribe_order_messages
end
# finally connect to the tws
connect() if connect
connect() if connect || get_account_data
get_account_data() if get_account_data


end
## ------------------------------------- connect ---------------------------------------------##
=begin
Expand Down Expand Up @@ -136,13 +143,13 @@ def connect maximal_count_of_retry=100
def initialize_managed_accounts


IB::Connection.current.subscribe(:ManagedAccounts) do |msg|
tws.subscribe(:ManagedAccounts) do |msg|
logger.progname = 'Gateway#InitializeManagedAccounts'
if @accounts.empty?
unless IB.db_backed?
# just validate the mmessage and put all together into an array
@accounts = msg.accounts_list.split(',').map do |a|
account = IB::Account.new( account: a, connected: true )
account = IB::Account.new( account: a.upcase , connected: true )
if account.save
logger.info {"new #{account.print_type} detected => #{account.account}"}
account
Expand All @@ -160,7 +167,7 @@ def initialize_managed_accounts
# alle Konten auf disconnected setzen
advisor = if (a= Advisor.of_ib_user_id(advisor_id)).empty?
logger.info {"creating a new advisor #{advisor_id}"}
Advisor.create( :account => advisor_id.downcase, :connected => true, :name => 'advisor' )
Advisor.create( :account => advisor_id.upcase, :connected => true, :name => 'advisor' )
else
logger.info {"updating active-Advisor-Flag #{advisor_id}"}
a.first.update_attribute :connected , true
Expand All @@ -169,7 +176,7 @@ def initialize_managed_accounts
user_id.each do | this_user_id |
if (a= advisor.users.of_ib_user_id(this_user_id)).empty?
logger.info {"creating a new user #{this_user_id}"}
advisor.users << User.new( :account => this_user_id.downcase, :connected => true , :name => "user#{this_user_id[-2 ..-1]}")
advisor.users << User.new( :account => this_user_id.upcase, :connected => true , :name => "user#{this_user_id[-2 ..-1]}")
else
a.first.update_attribute :connected, true
logger.info {"updating active-User-Flag #{this_user_id}"}
Expand Down Expand Up @@ -222,6 +229,8 @@ def disconnect
end

end


end # class

end # module
1 change: 1 addition & 0 deletions lib/ib/messages/incoming.rb
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ module Incoming
require 'ib/messages/incoming/next_valid_id'
require 'ib/messages/incoming/open_order'
require 'ib/messages/incoming/order_status'
require 'ib/messages/incoming/account_value'
require 'ib/messages/incoming/portfolio_value'
require 'ib/messages/incoming/real_time_bar'
require 'ib/messages/incoming/scanner_data'
Expand Down
35 changes: 8 additions & 27 deletions lib/ib/messages/incoming/account_value.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,36 +2,17 @@ module IB
module Messages
module Incoming

PortfolioValue = def_message [7, 7],
[:contract, :con_id, :int],
[:contract, :symbol, :string],
[:contract, :sec_type, :string],
[:contract, :expiry, :string],
[:contract, :strike, :decimal],
[:contract, :right, :string],
[:contract, :multiplier, :string],
[:contract, :primary_exchange, :string],
[:contract, :currency, :string],
[:contract, :local_symbol, :string],
[:position, :int],
[:market_price, :decimal],
[:market_value, :decimal],
[:average_cost, :decimal],
[:unrealized_pnl, :decimal_max], # May be nil!
[:realized_pnl, :decimal_max], # May be nil!
[:account_name, :string]
class PortfolioValue
AccountValue = def_message([6, 2], [:account_value, :key, :string],
[:account_value, :value, :string],
[:account_value, :currency, :string],
[:account_name, :string])
class AccountValue

def contract
@contract = IB::Contract.build @data[:contract]
def account_value
@account_value = IB::AccountValue.new @data[:account_value]
end

def to_human
"<PortfolioValue: #{contract.to_human} (#{position}): Market #{market_price}" +
" price #{market_value} value; PnL: #{unrealized_pnl} unrealized," +
" #{realized_pnl} realized; account #{account_name}>"
end
end # PortfolioValue
end # AccountValue


end # module Incoming
Expand Down
14 changes: 13 additions & 1 deletion lib/ib/messages/incoming/portfolio_value.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
module IB
module IB
module Messages
module Incoming

Expand Down Expand Up @@ -31,6 +31,18 @@ def to_human
" price #{market_value} value; PnL: #{unrealized_pnl} unrealized," +
" #{realized_pnl} realized; account #{account_name}>"
end

def portfolio_value
@portfolio_value = IB::PortfolioValue.new position: @data[:position],
market_price: @data[:market_price],
market_value: @data[:market_value],
average_cost: @data[:average_cost],
unrealized_pnl: @data[:unrealized_pnl],
realized_pnl: @data[:realized_pnl],
contract: contract


end
end # PortfolioValue


Expand Down
1 change: 1 addition & 0 deletions lib/ib/models.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
require 'models/ib/user'
require 'models/ib/advisor'
require 'models/ib/account_value'
require 'models/ib/portfolio_value'
require 'models/ib/order_state'
require 'models/ib/order'
require 'models/ib/combo_leg'
Expand Down
1 change: 1 addition & 0 deletions lib/ib/requires.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

require 'ib/models'
require 'ib/messages'
require 'ib/account_infos'
require 'ib/symbols'
require 'ib/alerts'
require 'ib/connection'
Expand Down
19 changes: 19 additions & 0 deletions lib/models/ib/account.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ class Account < IB::Model
# in tableless mode the scope is ignored
scope :of_ib_user_id, ->(account) { where :account => account.downcase } rescue nil

has_many :account_values
has_many :portfolio_values
has_many :contracts

def default_attributes
super.merge account: 'X000000'
Expand Down Expand Up @@ -51,5 +54,21 @@ def == other
other.is_a?(self.class) && account == other.account
end

def simple_account_data_scan search_key, search_currency=nil
if account_values.is_a? Array
if search_currency.present?
account_values.find_all{|x| x.key.match( search_key ) && x.currency == search_currency.upcase }
else
account_values.find_all{|x| x.key.match( search_key ) }
end

else
if search_currency.present?
account_values.where( ['key like %', search_key] ).where( currency: search_currency )
else # any currency
account_values.where( ['key like %', search_key] )
end
end
end
end
end
Loading

0 comments on commit 7a0af5e

Please sign in to comment.