Skip to content

Commit

Permalink
Logging redefined
Browse files Browse the repository at this point in the history
Now using the standard-rails-aproach. 
ib/logger defines a default-logger which is used if no other logger is assigned.
The logger can be redefined at any time, simply by calling 
IB::Connection.current.logger =
Same for its behaivior: IB::Connection.current.logger.level= 3
or  IB::Connection.current.logger.formater= proc{ |a,b,c,msg| msg }
  • Loading branch information
Hartmut Bischoff committed Jan 13, 2015
1 parent 659a2c0 commit 57e51a0
Show file tree
Hide file tree
Showing 4 changed files with 63 additions and 44 deletions.
2 changes: 0 additions & 2 deletions contract_config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,13 @@
:option:
:currency: USD
:exchange: SMART
:multiplier: 100
:right: P
:expiry:
:strike:
:symbol:
:future:
:currency: USD
:exchange:
:multiplier: 1000
:expiry:
:symbol:
:forex:
Expand Down
35 changes: 20 additions & 15 deletions lib/ib/connection.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ module IB
# Encapsulates API connection to TWS or Gateway
class Connection

include LogDev

mattr_accessor :logger ## borrowed from active_support
# Please note, we are realizing only the most current TWS protocol versions,
# thus improving performance at the expense of backwards compatibility.
# Older protocol versions support can be found in older gem versions.
Expand Down Expand Up @@ -48,7 +51,7 @@ def initialize opts = {}
@subscribe_lock = Mutex.new
@receive_lock = Mutex.new

self.default_logger = options[:logger] if options[:logger]
self.logger = options[:logger].presence || default_logger
@connected = false
self.next_local_id = nil

Expand All @@ -59,12 +62,13 @@ def initialize opts = {}
### Working with connection

def connect
error "Already connected!" if connected?
logger.progname='IB::Connection#connect' if logger.is_a?(Logger)
logger.error { "Already connected!"} if connected?

# TWS always sends NextValidId message at connect - save this id
self.subscribe(:NextValidId) do |msg|
self.next_local_id = msg.local_id
log.info "Got next valid order id: #{next_local_id}."
logger.info { "Got next valid order id: #{next_local_id}." }
end

@socket = IBSocket.open(options[:host], options[:port])
Expand All @@ -74,7 +78,7 @@ def connect
socket.write_data @client_version
@server_version = socket.read_int
if @server_version < options[:server_version]
error "Server version #{@server_version}, #{options[:server_version]} required."
logger.error { "Server version #{@server_version}, #{options[:server_version]} required." }
end
@remote_connect_time = socket.read_string
@local_connect_time = Time.now
Expand All @@ -86,9 +90,9 @@ def connect
socket.write_data @client_id

@connected = true
log.info "Connected to server, ver: #{@server_version}, connection time: " +
logger.info { "Connected to server, ver: #{@server_version}, connection time: " +
"#{@local_connect_time} local, " +
"#{@remote_connect_time} remote."
"#{@remote_connect_time} remote."}

start_reader if options[:reader] # Allows reconnect
end
Expand Down Expand Up @@ -119,10 +123,10 @@ def connected?
# Returns subscriber id to allow unsubscribing
def subscribe *args, &block
@subscribe_lock.synchronize do
subscriber = args.last.respond_to?(:call) ? args.pop : block
subscriber = args.last.respond_to?(:call) ? s.pop : block
id = random_id

error "Need subscriber proc or block", :args unless subscriber.is_a? Proc
error "Need subscriber proc or block ", :args unless subscriber.is_a? Proc

args.each do |what|
message_classes =
Expand All @@ -134,7 +138,7 @@ def subscribe *args, &block
when what.is_a?(Regexp)
Messages::Incoming::Classes.values.find_all { |klass| klass.to_s =~ what }
else
error "#{what} must represent incoming IB message class", :args
error "#{what} must represent incoming IB message class", :args
end
message_classes.flatten.each do |message_class|
# TODO: Fix: RuntimeError: can't add a new key into hash during iteration
Expand All @@ -151,7 +155,7 @@ def unsubscribe *ids
removed = []
ids.each do |id|
removed_at_id = subscribers.map { |_, subscribers| subscribers.delete id }.compact
error "No subscribers with id #{id}" if removed_at_id.empty?
logger.error "No subscribers with id #{id}" if removed_at_id.empty?
removed << removed_at_id
end
removed.flatten
Expand Down Expand Up @@ -202,7 +206,7 @@ def satisfied? *conditions
elsif condition.respond_to?(:call)
condition.call
else
error "Unknown wait condition #{condition}"
logger.error { "Unknown wait condition #{condition}" }
end
end
end
Expand Down Expand Up @@ -252,22 +256,23 @@ def process_messages poll_time = 200 # in msec

# Process single incoming message (blocking!)
def process_message
logger.progname='IB::Connection#process_message' if logger.is_a?(Logger)
msg_id = socket.read_int # This read blocks!

# Debug:
log.debug "Got message #{msg_id} (#{Messages::Incoming::Classes[msg_id]})"
logger.debug { "Got message #{msg_id} (#{Messages::Incoming::Classes[msg_id]})"}

# Create new instance of the appropriate message type,
# and have it read the message from socket.
# NB: Failure here usually means unsupported message type received
error "Got unsupported message #{msg_id}" unless Messages::Incoming::Classes[msg_id]
logger.error { "Got unsupported message #{msg_id}" } unless Messages::Incoming::Classes[msg_id]
msg = Messages::Incoming::Classes[msg_id].new(socket)

# Deliver message to all registered subscribers, alert if no subscribers
@subscribe_lock.synchronize do
subscribers[msg.class].each { |_, subscriber| subscriber.call(msg) }
end
log.warn "No subscribers for message #{msg.class}!" if subscribers[msg.class].empty?
logger.warn { "No subscribers for message #{msg.class}!" } if subscribers[msg.class].empty?

# Collect all received messages into a @received Hash
if options[:received]
Expand All @@ -292,7 +297,7 @@ def send_message what, *args
else
error "Only able to send outgoing IB messages", :args
end
error "Not able to send messages, IB not connected!" unless connected?
logger.error { "Not able to send messages, IB not connected!" } unless connected?
message.send_to socket
end

Expand Down
26 changes: 9 additions & 17 deletions lib/ib/logger.rb
Original file line number Diff line number Diff line change
@@ -1,24 +1,16 @@
require "logger"

# Add default_logger accessor into Object
module LogDev
# define default_logger
def default_logger
@default_logger ||= Logger.new(STDOUT).tap do |logger|
time_format = RUBY_VERSION =~ /1\.8\./ ? '%H:%M:%S.%N' : '%H:%M:%S.%3N'
logger.formatter = proc do |level, time, prog, msg|

"#{time.strftime(time_format)} #{msg}\n"
Logger.new(STDOUT).tap do |l|
l.formatter = proc do |severity, datetime, progname, msg|
# "#{datetime.strftime("%d.%m.(%X)")}#{"%5s" % severity}->#{progname}##{msg}\n"
## the default logger displays the message only
msg
end
logger.level = Logger::INFO
l.level = Logger::INFO
end
end

def default_logger= logger
@default_logger = logger
end

# Add universally accessible log method/accessor into Object
def log *args
default_logger.tap do |logger|
logger.fatal *args unless args.empty?
end
end
end # module
44 changes: 34 additions & 10 deletions lib/ib/tws_reader.rb
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ def query_contract invalid_record:true
raise "#{items_as_string[nessesary_items]} are needed to retrieve Contract, got: #{item_values[nessesary_items].join(',')}" if item_values[nessesary_items].any?( &:nil? )
IB::Contract.new item_attributehash[nessesary_items].merge(:sec_type=> sec_type)
else #if currency.present?
# IB::Contract.new con_id:con_id , :currency => currency.presence || item_attributehash[nessesary_items][:currency]
IB::Contract.new con_id:con_id , :exchange => exchange.presence || item_attributehash[nessesary_items][:exchange]
end # if
## modify the Object, ie. set con_id to zero
if new_record?
Expand All @@ -46,20 +46,36 @@ def yml_file
File.expand_path('../../../contract_config.yml',__FILE__ )
end
=begin
Update_contract requests ContractData from the TWS
read_contract_from_tws (alias update_contract) requests ContractData from the TWS and
stores/updates them in this contract_object.
If the parameter »unique« is set (the default), the #yml_file is used to check the nessesary
query-attributes, otherwise the check is suppressed.
ContractDetails are not saved, by default. Instead they are supposed to be adressed by
yielding a block.
The msg-object returned by the tws is assesible via an optional block.
The msg-object returned by the tws is asessible via an optional block.
If many Contracts match the definition by the attributs of the IB::Contract
the block is executed for each returned Contract
A ActiveRecord::RecordNotUnique-Error is raised, if an update of attributes in the DB fails
The responsible DB-Index reflects the uniqueness of ["con_id", "symbol", "sec_type"]
Example:
given an active-record-Model with min_tick-attribute:
--------------------------------------------------
contract = IB::Contract.find x
count_datasets,lth = 0
contract.update_contract do |msg|
update_attibute :min_tic , msg.contractdetail.min_tick
count_datasets +=1
lth= msg.contractdetail.liquid_trading_hours
end
puts "More then one dataset specified: #{count_datasets)" if count_datasets > 1
puts "Invalid Attribute Combination or unknown symbol/con_id" if count_datasets.zero?
puts "Trading-hours: #{lth}" unless lth.zero?
=end
def read_contract_from_tws unique:true
def read_contract_from_tws unique: true, save_details: false

ib = IB::Connection.current
raise "NO TWS" unless ib.present?
to_be_saved = IB.db_backed? && !new_record?
Expand Down Expand Up @@ -114,10 +130,10 @@ def read_contract_from_tws unique:true
if to_be_saved
update attributes_to_be_transfered[msg.contract]
if contract_detail.nil?
self.contract_detail = msg.contract_detail
self.contract_detail = msg.contract_detail
else
contract_detail.update attributes_to_be_transfered[msg.contract_detail] ## AR4 specific
end
end if save_details
else
self.attributes = msg.contract.attributes # AR4 specific

Expand All @@ -130,17 +146,25 @@ def read_contract_from_tws unique:true
end # subscribe

### send the request !
begin
ib.send_message :RequestContractData,
:id => new_record? ? message_id : id,
:contract => unique ? query_contract : self rescue self # prevents error if Ruby vers < 2.1.0
:contract => ( unique ? query_contract : self rescue self ) # prevents error if Ruby vers < 2.1.0
# we do not rely on the received hash, we simply wait for the ContractDataEnd Event
# (or 5 sec).
wait_until_exitcondition[]
ib.unsubscribe a
rescue ThreadError => e
puts "ERROR"
puts e.inspect
raise
end

warn{ "NO Contract returned by TWS -->#{self.to_human} "} unless exitcondition
warn{ "Multible Contracts are detected, only the last is returned -->#{contract.to_human} "} if count>1
count>1 ? count : local_symbol # return_value
end # def

alias update_contract read_contract_from_tws
end # module
end # module

0 comments on commit 57e51a0

Please sign in to comment.