ib-api 972.5 → 972.5.2
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 +4 -4
 - data/Gemfile +2 -2
 - data/Gemfile.lock +36 -41
 - data/README.md +21 -13
 - data/VERSION +1 -1
 - data/api.gemspec +2 -0
 - data/bin/console +15 -17
 - data/bin/console.yml +1 -1
 - data/lib/extensions/class-extensions.rb +9 -0
 - data/lib/ib/base_properties.rb +22 -1
 - data/lib/ib/connection.rb +122 -100
 - data/lib/ib/constants.rb +5 -2
 - data/lib/ib/errors.rb +3 -1
 - data/lib/ib/messages/incoming/alert.rb +4 -4
 - data/lib/ib/messages/incoming/ticks.rb +1 -1
 - data/lib/ib/messages/outgoing/bar_requests.rb +2 -2
 - data/lib/ib/messages/outgoing/place_order.rb +3 -3
 - data/lib/ib/messages/outgoing/request_marketdata.rb +1 -1
 - data/lib/ib/raw_message.rb +85 -0
 - data/lib/ib/socket.rb +2 -2
 - data/lib/logging.rb +9 -8
 - data/lib/models/ib/contract.rb +64 -5
 - data/lib/models/ib/index.rb +1 -1
 - data/lib/models/ib/option_detail.rb +13 -0
 - data/lib/models/ib/order.rb +23 -0
 - data/lib/models/ib/portfolio_value.rb +34 -12
 - data/lib/models/ib/spread.rb +33 -18
 - data/lib/requires.rb +2 -1
 - metadata +32 -4
 - data/lib/ib/logger.rb +0 -26
 
    
        data/lib/ib/connection.rb
    CHANGED
    
    | 
         @@ -35,76 +35,81 @@ module IB 
     | 
|
| 
       35 
35 
     | 
    
         
             
                alias next_order_id= next_local_id=
         
     | 
| 
       36 
36 
     | 
    
         | 
| 
       37 
37 
     | 
    
         
             
                def initialize host: '127.0.0.1',
         
     | 
| 
       38 
     | 
    
         
            -
             
     | 
| 
       39 
     | 
    
         
            -
             
     | 
| 
       40 
     | 
    
         
            -
             
     | 
| 
       41 
     | 
    
         
            -
             
     | 
| 
       42 
     | 
    
         
            -
            #									 redis: false,    # future plans
         
     | 
| 
       43 
     | 
    
         
            -
             
     | 
| 
       44 
     | 
    
         
            -
             
     | 
| 
       45 
     | 
    
         
            -
             
     | 
| 
       46 
     | 
    
         
            -
             
     | 
| 
       47 
     | 
    
         
            -
             
     | 
| 
       48 
     | 
    
         
            -
             
     | 
| 
       49 
     | 
    
         
            -
             
     | 
| 
       50 
     | 
    
         
            -
            # API messages sent at a higher rate than 50/second can now be paced by TWS at the 50/second rate instead of potentially causing a disconnection. This is now done automatically by the RTD Server API and can be done with other API technologies by invoking SetConnectOptions("+PACEAPI") prior to eConnect.
         
     | 
| 
       51 
     | 
    
         
            -
             
     | 
| 
       52 
     | 
    
         
            -
             
     | 
| 
       53 
     | 
    
         
            -
             
     | 
| 
       54 
     | 
    
         
            -
             
     | 
| 
       55 
     | 
    
         
            -
             
     | 
| 
       56 
     | 
    
         
            -
             
     | 
| 
       57 
     | 
    
         
            -
             
     | 
| 
       58 
     | 
    
         
            -
             
     | 
| 
       59 
     | 
    
         
            -
             
     | 
| 
       60 
     | 
    
         
            -
             
     | 
| 
       61 
     | 
    
         
            -
            		# A couple of locks to avoid race conditions in JRuby
         
     | 
| 
       62 
     | 
    
         
            -
            		@subscribe_lock = Mutex.new
         
     | 
| 
       63 
     | 
    
         
            -
            		@receive_lock = Mutex.new
         
     | 
| 
       64 
     | 
    
         
            -
            		@message_lock = Mutex.new
         
     | 
| 
       65 
     | 
    
         
            -
             
     | 
| 
       66 
     | 
    
         
            -
            		@connected = false
         
     | 
| 
       67 
     | 
    
         
            -
            		self.next_local_id = nil
         
     | 
| 
      
 38 
     | 
    
         
            +
                  port: '4002', # IB Gateway connection (default --> demo) 4001:  production
         
     | 
| 
      
 39 
     | 
    
         
            +
                  #:port => '7497', # TWS connection  --> demo				  7496:  production
         
     | 
| 
      
 40 
     | 
    
         
            +
                  connect: true, # Connect at initialization
         
     | 
| 
      
 41 
     | 
    
         
            +
                  received:  true, # Keep all received messages in a @received Hash
         
     | 
| 
      
 42 
     | 
    
         
            +
                  #									 redis: false,    # future plans
         
     | 
| 
      
 43 
     | 
    
         
            +
                  logger: nil,
         
     | 
| 
      
 44 
     | 
    
         
            +
                  client_id:  rand( 1001 .. 9999 ) ,
         
     | 
| 
      
 45 
     | 
    
         
            +
                  client_version: IB::Messages::CLIENT_VERSION,	# lib/ib/server_versions.rb
         
     | 
| 
      
 46 
     | 
    
         
            +
                  optional_capacities: "", # TWS-Version 974: "+PACEAPI"
         
     | 
| 
      
 47 
     | 
    
         
            +
                  #server_version: IB::Messages::SERVER_VERSION, # lib/messages.rb
         
     | 
| 
      
 48 
     | 
    
         
            +
                  **any_other_parameters_which_are_ignored
         
     | 
| 
      
 49 
     | 
    
         
            +
                # V 974 release motes
         
     | 
| 
      
 50 
     | 
    
         
            +
                # API messages sent at a higher rate than 50/second can now be paced by TWS at the 50/second rate instead of potentially causing a disconnection. This is now done automatically by the RTD Server API and can be done with other API technologies by invoking SetConnectOptions("+PACEAPI") prior to eConnect.
         
     | 
| 
      
 51 
     | 
    
         
            +
             
     | 
| 
      
 52 
     | 
    
         
            +
                  self.class.configure_logger logger
         
     | 
| 
      
 53 
     | 
    
         
            +
                  # convert parameters into instance-variables and assign them
         
     | 
| 
      
 54 
     | 
    
         
            +
                  method(__method__).parameters.each do |type, k|
         
     | 
| 
      
 55 
     | 
    
         
            +
                    next unless type == :key  ##  available: key , keyrest
         
     | 
| 
      
 56 
     | 
    
         
            +
                    next if k.to_s == 'logger'
         
     | 
| 
      
 57 
     | 
    
         
            +
                    v = eval(k.to_s)
         
     | 
| 
      
 58 
     | 
    
         
            +
                    instance_variable_set("@#{k}", v) unless v.nil?
         
     | 
| 
      
 59 
     | 
    
         
            +
                  end
         
     | 
| 
       68 
60 
     | 
    
         | 
| 
       69 
     | 
    
         
            -
             
     | 
| 
       70 
     | 
    
         
            -
             
     | 
| 
       71 
     | 
    
         
            -
             
     | 
| 
      
 61 
     | 
    
         
            +
                  # A couple of locks to avoid race conditions in JRuby
         
     | 
| 
      
 62 
     | 
    
         
            +
                  @subscribe_lock = Mutex.new
         
     | 
| 
      
 63 
     | 
    
         
            +
                  @receive_lock = Mutex.new
         
     | 
| 
      
 64 
     | 
    
         
            +
                  @message_lock = Mutex.new
         
     | 
| 
       72 
65 
     | 
    
         | 
| 
       73 
     | 
    
         
            -
             
     | 
| 
       74 
     | 
    
         
            -
             
     | 
| 
       75 
     | 
    
         
            -
            		yield self if block_given?
         
     | 
| 
      
 66 
     | 
    
         
            +
                  @connected = false
         
     | 
| 
      
 67 
     | 
    
         
            +
                  self.next_local_id = nil
         
     | 
| 
       76 
68 
     | 
    
         | 
| 
       77 
     | 
    
         
            -
             
     | 
| 
       78 
     | 
    
         
            -
                  self. 
     | 
| 
       79 
     | 
    
         
            -
             
     | 
| 
       80 
     | 
    
         
            -
             
     | 
| 
       81 
     | 
    
         
            -
             
     | 
| 
       82 
     | 
    
         
            -
             
     | 
| 
       83 
     | 
    
         
            -
             
     | 
| 
       84 
     | 
    
         
            -
             
     | 
| 
       85 
     | 
    
         
            -
             
     | 
| 
       86 
     | 
    
         
            -
             
     | 
| 
       87 
     | 
    
         
            -
             
     | 
| 
       88 
     | 
    
         
            -
             
     | 
| 
       89 
     | 
    
         
            -
             
     | 
| 
       90 
     | 
    
         
            -
             
     | 
| 
       91 
     | 
    
         
            -
             
     | 
| 
       92 
     | 
    
         
            -
             
     | 
| 
      
 69 
     | 
    
         
            +
                  # TWS always sends NextValidId message at connect -subscribe save this id
         
     | 
| 
      
 70 
     | 
    
         
            +
                  self.subscribe(:NextValidId) do |msg|
         
     | 
| 
      
 71 
     | 
    
         
            +
                    self.logger.progname = "Connection#connect"
         
     | 
| 
      
 72 
     | 
    
         
            +
                    self.next_local_id = msg.local_id
         
     | 
| 
      
 73 
     | 
    
         
            +
                    self.logger.info { "Got next valid order id: #{next_local_id}." }
         
     | 
| 
      
 74 
     | 
    
         
            +
                  end
         
     | 
| 
      
 75 
     | 
    
         
            +
                  #
         
     | 
| 
      
 76 
     | 
    
         
            +
                  # this block is executed before tws-communication is established
         
     | 
| 
      
 77 
     | 
    
         
            +
                  # Its intended for globally available subscriptions of tws-messages
         
     | 
| 
      
 78 
     | 
    
         
            +
                  yield self if block_given?
         
     | 
| 
      
 79 
     | 
    
         
            +
             
     | 
| 
      
 80 
     | 
    
         
            +
                  if connect
         
     | 
| 
      
 81 
     | 
    
         
            +
                    disconnect if connected?
         
     | 
| 
      
 82 
     | 
    
         
            +
                    update_next_order_id
         
     | 
| 
      
 83 
     | 
    
         
            +
                    Kernel.exit if self.next_local_id.nil?  # emergency exit. 
         
     | 
| 
      
 84 
     | 
    
         
            +
                    # update_next_order_id should have raised an error
         
     | 
| 
      
 85 
     | 
    
         
            +
                  end
         
     | 
| 
      
 86 
     | 
    
         
            +
                  Connection.current = self
         
     | 
| 
      
 87 
     | 
    
         
            +
                end
         
     | 
| 
       93 
88 
     | 
    
         | 
| 
       94 
89 
     | 
    
         
             
            		# read actual order_id and
         
     | 
| 
       95 
90 
     | 
    
         
             
            		# connect if not connected
         
     | 
| 
       96 
91 
     | 
    
         
             
            		def update_next_order_id
         
     | 
| 
       97 
     | 
    
         
            -
             
     | 
| 
       98 
     | 
    
         
            -
             
     | 
| 
       99 
     | 
    
         
            -
             
     | 
| 
       100 
     | 
    
         
            -
             
     | 
| 
       101 
     | 
    
         
            -
             
     | 
| 
       102 
     | 
    
         
            -
             
     | 
| 
       103 
     | 
    
         
            -
             
     | 
| 
       104 
     | 
    
         
            -
             
     | 
| 
      
 92 
     | 
    
         
            +
                  q = Queue.new
         
     | 
| 
      
 93 
     | 
    
         
            +
                  subscription = subscribe(:NextValidId){ |msg| q.push msg.local_id }
         
     | 
| 
      
 94 
     | 
    
         
            +
                  unless connected?
         
     | 
| 
      
 95 
     | 
    
         
            +
                    connect() # connect implies requesting NextValidId
         
     | 
| 
      
 96 
     | 
    
         
            +
                  else
         
     | 
| 
      
 97 
     | 
    
         
            +
                    send_message :RequestIds
         
     | 
| 
      
 98 
     | 
    
         
            +
                  end
         
     | 
| 
      
 99 
     | 
    
         
            +
                  th = Thread.new { sleep 5; q.close }
         
     | 
| 
      
 100 
     | 
    
         
            +
                  local_id = q.pop
         
     | 
| 
      
 101 
     | 
    
         
            +
                  if q.closed?
         
     | 
| 
      
 102 
     | 
    
         
            +
                    error "Could not get NextValidID", :reader
         
     | 
| 
      
 103 
     | 
    
         
            +
                  else
         
     | 
| 
      
 104 
     | 
    
         
            +
                    th.kill
         
     | 
| 
      
 105 
     | 
    
         
            +
                  end
         
     | 
| 
      
 106 
     | 
    
         
            +
                  unsubscribe subscription
         
     | 
| 
      
 107 
     | 
    
         
            +
                  local_id  # return next_id
         
     | 
| 
      
 108 
     | 
    
         
            +
                end
         
     | 
| 
       105 
109 
     | 
    
         | 
| 
       106 
110 
     | 
    
         
             
            		### Working with connection
         
     | 
| 
       107 
     | 
    
         
            -
             
     | 
| 
      
 111 
     | 
    
         
            +
                #
         
     | 
| 
      
 112 
     | 
    
         
            +
                ### connect can be called directly. but is mostly called through update_next_order_id
         
     | 
| 
       108 
113 
     | 
    
         
             
            		def connect
         
     | 
| 
       109 
114 
     | 
    
         
             
            			logger.progname='IB::Connection#connect'
         
     | 
| 
       110 
115 
     | 
    
         
             
            			if connected?
         
     | 
| 
         @@ -123,10 +128,6 @@ module IB 
     | 
|
| 
       123 
128 
     | 
    
         
             
            				@local_connect_time = Time.now
         
     | 
| 
       124 
129 
     | 
    
         
             
            			end
         
     | 
| 
       125 
130 
     | 
    
         | 
| 
       126 
     | 
    
         
            -
            			# Sending (arbitrary) client ID to identify subsequent communications.
         
     | 
| 
       127 
     | 
    
         
            -
            			# The client with a client_id of 0 can manage the TWS-owned open orders.
         
     | 
| 
       128 
     | 
    
         
            -
            			# Other clients can only manage their own open orders.
         
     | 
| 
       129 
     | 
    
         
            -
             
     | 
| 
       130 
131 
     | 
    
         
             
            			# V100 initial handshake
         
     | 
| 
       131 
132 
     | 
    
         
             
            			# Parameters borrowed from the python client
         
     | 
| 
       132 
133 
     | 
    
         
             
            			start_api = 71
         
     | 
| 
         @@ -134,14 +135,11 @@ module IB 
     | 
|
| 
       134 
135 
     | 
    
         
             
            			#			optcap = @optional_capacities.empty? ? "" : " "+ @optional_capacities
         
     | 
| 
       135 
136 
     | 
    
         
             
            			socket.send_messages start_api, version, @client_id  , @optional_capacities
         
     | 
| 
       136 
137 
     | 
    
         
             
            			@connected = true
         
     | 
| 
       137 
     | 
    
         
            -
            			logger. 
     | 
| 
      
 138 
     | 
    
         
            +
            			logger.fatal{ "Connected to server, version: #{@server_version}, " +
         
     | 
| 
      
 139 
     | 
    
         
            +
                             "using client-id: #{client_id},\n   connection time: " +
         
     | 
| 
       138 
140 
     | 
    
         
             
            								 "#{@local_connect_time} local, " +
         
     | 
| 
       139 
     | 
    
         
            -
            									 "#{@remote_connect_time} remote."}
         
     | 
| 
      
 141 
     | 
    
         
            +
            									 "#{@remote_connect_time} remote." }
         
     | 
| 
       140 
142 
     | 
    
         | 
| 
       141 
     | 
    
         
            -
            			# if the client_id is wrong or the port is not accessible the first read attempt fails
         
     | 
| 
       142 
     | 
    
         
            -
            			# get the first message and proceed if something reasonable is recieved
         
     | 
| 
       143 
     | 
    
         
            -
            			the_message = process_message   # recieve next_order_id
         
     | 
| 
       144 
     | 
    
         
            -
            			error "Check Port/Client_id ", :reader if the_message == " "
         
     | 
| 
       145 
143 
     | 
    
         
             
            			start_reader
         
     | 
| 
       146 
144 
     | 
    
         
             
            		end
         
     | 
| 
       147 
145 
     | 
    
         | 
| 
         @@ -184,7 +182,7 @@ module IB 
     | 
|
| 
       184 
182 
     | 
    
         
             
                      when what.is_a?(Symbol)
         
     | 
| 
       185 
183 
     | 
    
         
             
                        if Messages::Incoming.const_defined?(what)
         
     | 
| 
       186 
184 
     | 
    
         
             
                          [Messages::Incoming.const_get(what)]
         
     | 
| 
       187 
     | 
    
         
            -
                        elsif TechnicalAnalysis::Signals.const_defined?(what)
         
     | 
| 
      
 185 
     | 
    
         
            +
                        elsif defined?( TechnicalAnalysis ) && TechnicalAnalysis::Signals.const_defined?(what)
         
     | 
| 
       188 
186 
     | 
    
         
             
                          [TechnicalAnalysis::Signals.const_get?(what)]
         
     | 
| 
       189 
187 
     | 
    
         
             
                        else
         
     | 
| 
       190 
188 
     | 
    
         
             
                          error "#{what} is no IB::Messages or TechnicalAnalyis::Signals class"
         
     | 
| 
         @@ -258,6 +256,7 @@ module IB 
     | 
|
| 
       258 
256 
     | 
    
         
             
            		#
         
     | 
| 
       259 
257 
     | 
    
         
             
            		# wait_for depends heavyly on Connection#received. If collection of messages through recieved
         
     | 
| 
       260 
258 
     | 
    
         
             
            		# is turned off, wait_for loses most of its functionality
         
     | 
| 
      
 259 
     | 
    
         
            +
             
     | 
| 
       261 
260 
     | 
    
         
             
                def wait_for *args, &block
         
     | 
| 
       262 
261 
     | 
    
         
             
                  timeout = args.find { |arg| arg.is_a? Numeric } # extract timeout from args
         
     | 
| 
       263 
262 
     | 
    
         
             
                  end_time = Time.now + (timeout || 1) # default timeout 1 sec
         
     | 
| 
         @@ -282,25 +281,40 @@ module IB 
     | 
|
| 
       282 
281 
     | 
    
         
             
                # Process incoming messages during *poll_time* (200) msecs, nonblocking
         
     | 
| 
       283 
282 
     | 
    
         
             
                def process_messages poll_time = 50 # in msec
         
     | 
| 
       284 
283 
     | 
    
         
             
                  time_out = Time.now + poll_time/1000.0
         
     | 
| 
      
 284 
     | 
    
         
            +
                  begin
         
     | 
| 
       285 
285 
     | 
    
         
             
                  while (time_left = time_out - Time.now) > 0
         
     | 
| 
       286 
286 
     | 
    
         
             
                    # If socket is readable, process single incoming message
         
     | 
| 
       287 
     | 
    
         
            -
             
     | 
| 
      
 287 
     | 
    
         
            +
                    if  RUBY_PLATFORM.match(/cygwin|mswin|mingw|bccwin|wince|emx/)
         
     | 
| 
      
 288 
     | 
    
         
            +
                      process_message if select [socket], nil, nil, time_left
         
     | 
| 
      
 289 
     | 
    
         
            +
                    
         
     | 
| 
      
 290 
     | 
    
         
            +
             
     | 
| 
       288 
291 
     | 
    
         
             
            				# the following  checks for shutdown of TWS side; ensures we don't run in a spin loop.
         
     | 
| 
       289 
292 
     | 
    
         
             
            				# unfortunately, it raises Errors in windows environment
         
     | 
| 
       290 
     | 
    
         
            -
             
     | 
| 
       291 
     | 
    
         
            -
             
     | 
| 
       292 
     | 
    
         
            -
             
     | 
| 
       293 
     | 
    
         
            -
             
     | 
| 
       294 
     | 
    
         
            -
             
     | 
| 
       295 
     | 
    
         
            -
             
     | 
| 
       296 
     | 
    
         
            -
             
     | 
| 
       297 
     | 
    
         
            -
             
     | 
| 
       298 
     | 
    
         
            -
             
     | 
| 
       299 
     | 
    
         
            -
             
     | 
| 
       300 
     | 
    
         
            -
             
     | 
| 
       301 
     | 
    
         
            -
             
     | 
| 
       302 
     | 
    
         
            -
             
     | 
| 
       303 
     | 
    
         
            -
             
     | 
| 
      
 293 
     | 
    
         
            +
                    else
         
     | 
| 
      
 294 
     | 
    
         
            +
                      if select [socket], nil, nil, time_left
         
     | 
| 
      
 295 
     | 
    
         
            +
                        #  # Peek at the message from the socket; if it's blank then the
         
     | 
| 
      
 296 
     | 
    
         
            +
                        #  # server side of connection (TWS) has likely shut down.
         
     | 
| 
      
 297 
     | 
    
         
            +
                        socket_likely_shutdown = socket.recvmsg(100, Socket::MSG_PEEK)[0] == ""
         
     | 
| 
      
 298 
     | 
    
         
            +
                				#
         
     | 
| 
      
 299 
     | 
    
         
            +
                        #  # We go ahead process messages regardless (a no-op if socket_likely_shutdown).
         
     | 
| 
      
 300 
     | 
    
         
            +
                        process_message
         
     | 
| 
      
 301 
     | 
    
         
            +
                        #
         
     | 
| 
      
 302 
     | 
    
         
            +
                        #  # After processing, if socket has shut down we sleep for 100ms
         
     | 
| 
      
 303 
     | 
    
         
            +
                        #  # to avoid spinning in a tight loop. If the server side somehow
         
     | 
| 
      
 304 
     | 
    
         
            +
                        #  # comes back up (gets reconnedted), normal processing
         
     | 
| 
      
 305 
     | 
    
         
            +
                        #  # (without the 100ms wait) should happen.
         
     | 
| 
      
 306 
     | 
    
         
            +
                        sleep(0.1) if socket_likely_shutdown
         
     | 
| 
      
 307 
     | 
    
         
            +
                      end
         
     | 
| 
      
 308 
     | 
    
         
            +
                    end
         
     | 
| 
      
 309 
     | 
    
         
            +
                  end
         
     | 
| 
      
 310 
     | 
    
         
            +
                  rescue Errno::ECONNRESET => e
         
     | 
| 
      
 311 
     | 
    
         
            +
                    logger.fatal e.message
         
     | 
| 
      
 312 
     | 
    
         
            +
                    if e.message =~ /Connection reset by peer/
         
     | 
| 
      
 313 
     | 
    
         
            +
                      logger.fatal "Is another client listening on the same port?"
         
     | 
| 
      
 314 
     | 
    
         
            +
                      error "try reconnecting with a different client-id", :reader
         
     | 
| 
      
 315 
     | 
    
         
            +
                    else
         
     | 
| 
      
 316 
     | 
    
         
            +
                      logger.fatal "Aborting"
         
     | 
| 
      
 317 
     | 
    
         
            +
                      Kernel.exit
         
     | 
| 
       304 
318 
     | 
    
         
             
                    end
         
     | 
| 
       305 
319 
     | 
    
         
             
                  end
         
     | 
| 
       306 
320 
     | 
    
         
             
                end
         
     | 
| 
         @@ -374,15 +388,20 @@ module IB 
     | 
|
| 
       374 
388 
     | 
    
         
             
                # If you don't start reader, you should manually poll @socket for messages
         
     | 
| 
       375 
389 
     | 
    
         
             
                # or use #process_messages(msec) API.
         
     | 
| 
       376 
390 
     | 
    
         
             
                def start_reader
         
     | 
| 
       377 
     | 
    
         
            -
             
     | 
| 
       378 
     | 
    
         
            -
             
     | 
| 
       379 
     | 
    
         
            -
             
     | 
| 
       380 
     | 
    
         
            -
             
     | 
| 
       381 
     | 
    
         
            -
             
     | 
| 
       382 
     | 
    
         
            -
             
     | 
| 
       383 
     | 
    
         
            -
             
     | 
| 
       384 
     | 
    
         
            -
             
     | 
| 
       385 
     | 
    
         
            -
             
     | 
| 
      
 391 
     | 
    
         
            +
                  if @reader_running
         
     | 
| 
      
 392 
     | 
    
         
            +
                    @reader_thread
         
     | 
| 
      
 393 
     | 
    
         
            +
                  elsif connected?
         
     | 
| 
      
 394 
     | 
    
         
            +
                    begin
         
     | 
| 
      
 395 
     | 
    
         
            +
                    Thread.abort_on_exception = true
         
     | 
| 
      
 396 
     | 
    
         
            +
                    @reader_running = true
         
     | 
| 
      
 397 
     | 
    
         
            +
                    @reader_thread = Thread.new { process_messages while @reader_running }
         
     | 
| 
      
 398 
     | 
    
         
            +
                  rescue Errno::ECONNRESET => e
         
     | 
| 
      
 399 
     | 
    
         
            +
                      logger.fatal e.message
         
     | 
| 
      
 400 
     | 
    
         
            +
                      Kernel.exit
         
     | 
| 
      
 401 
     | 
    
         
            +
                    end
         
     | 
| 
      
 402 
     | 
    
         
            +
                  else
         
     | 
| 
      
 403 
     | 
    
         
            +
                    error "Could not start reader, not connected!", :reader, true
         
     | 
| 
      
 404 
     | 
    
         
            +
                  end
         
     | 
| 
       386 
405 
     | 
    
         
             
                end
         
     | 
| 
       387 
406 
     | 
    
         | 
| 
       388 
407 
     | 
    
         
             
            		protected
         
     | 
| 
         @@ -408,13 +427,16 @@ module IB 
     | 
|
| 
       408 
427 
     | 
    
         
             
            				# Create new instance of the appropriate message type,
         
     | 
| 
       409 
428 
     | 
    
         
             
            				# and have it read the message from socket.
         
     | 
| 
       410 
429 
     | 
    
         
             
            				# NB: Failure here usually means unsupported message type received
         
     | 
| 
       411 
     | 
    
         
            -
             
     | 
| 
       412 
     | 
    
         
            -
             
     | 
| 
       413 
     | 
    
         
            -
             
     | 
| 
      
 430 
     | 
    
         
            +
                    unless Messages::Incoming::Classes[msg_id]
         
     | 
| 
      
 431 
     | 
    
         
            +
                      logger.error { "Got unsupported message #{msg_id}" }
         
     | 
| 
      
 432 
     | 
    
         
            +
                      error "Something strange happened - Reader has to be restarted" , :reader, true if msg_id.to_i.zero?
         
     | 
| 
      
 433 
     | 
    
         
            +
                    else
         
     | 
| 
      
 434 
     | 
    
         
            +
                      msg = Messages::Incoming::Classes[msg_id].new(the_decoded_message)
         
     | 
| 
      
 435 
     | 
    
         
            +
                    end
         
     | 
| 
       414 
436 
     | 
    
         | 
| 
       415 
437 
     | 
    
         
             
            				# Deliver message to all registered subscribers, alert if no subscribers
         
     | 
| 
       416 
438 
     | 
    
         
             
            				# Ruby 2.0 and above: Hashes are ordered.
         
     | 
| 
       417 
     | 
    
         
            -
            				# Thus first declared subscribers of 
     | 
| 
      
 439 
     | 
    
         
            +
            				# Thus first declared subscribers of a class are executed first
         
     | 
| 
       418 
440 
     | 
    
         
             
            				@subscribe_lock.synchronize do
         
     | 
| 
       419 
441 
     | 
    
         
             
            					subscribers[msg.class].each { |_, subscriber| subscriber.call(msg) }
         
     | 
| 
       420 
442 
     | 
    
         
             
            				end
         
     | 
    
        data/lib/ib/constants.rb
    CHANGED
    
    | 
         @@ -21,7 +21,11 @@ module IB 
     | 
|
| 
       21 
21 
     | 
    
         
             
                           '15 mins' =>:min15,
         
     | 
| 
       22 
22 
     | 
    
         
             
                           '30 mins' =>:min30,
         
     | 
| 
       23 
23 
     | 
    
         
             
                           '1 hour' =>:hour1,
         
     | 
| 
       24 
     | 
    
         
            -
                           ' 
     | 
| 
      
 24 
     | 
    
         
            +
                           '4 hours' =>:hour4,
         
     | 
| 
      
 25 
     | 
    
         
            +
                           '8 hours' =>:hour8,
         
     | 
| 
      
 26 
     | 
    
         
            +
                           '1 day' => :day1,
         
     | 
| 
      
 27 
     | 
    
         
            +
                           '1 week' => :week1,
         
     | 
| 
      
 28 
     | 
    
         
            +
                           '1 month' => :month1,
         
     | 
| 
       25 
29 
     | 
    
         
             
              }.freeze
         
     | 
| 
       26 
30 
     | 
    
         | 
| 
       27 
31 
     | 
    
         
             
              # Enumeration of data types.
         
     | 
| 
         @@ -199,7 +203,6 @@ module IB 
     | 
|
| 
       199 
203 
     | 
    
         
             
                   'PEG MID' => :pegged_to_midpoint, # Pegged-to-Midpoint
         
     | 
| 
       200 
204 
     | 
    
         
             
                   'PEG BENCH' => :pegged_to_benchmark, # Pegged-to-Benmchmark # Vers. 102
         
     | 
| 
       201 
205 
     | 
    
         
             
                   'VWAP' => :vwap, #                  VWAP-Guaranted
         
     | 
| 
       202 
     | 
    
         
            -
                   'OCA' => :one_cancels_all, #        One-Cancels-All
         
     | 
| 
       203 
206 
     | 
    
         
             
                   'VOL' => :volatility, #             Volatility
         
     | 
| 
       204 
207 
     | 
    
         
             
                   'SCALE' => :scale, #                Scale
         
     | 
| 
       205 
208 
     | 
    
         
             
                   'NONE' => :none, # Used to indicate no hedge in :delta_neutral_order_type
         
     | 
    
        data/lib/ib/errors.rb
    CHANGED
    
    | 
         @@ -38,7 +38,9 @@ def error message, type=:standard, backtrace=nil 
     | 
|
| 
       38 
38 
     | 
    
         
             
                IB::FlexError.new message
         
     | 
| 
       39 
39 
     | 
    
         
             
              when :reader
         
     | 
| 
       40 
40 
     | 
    
         
             
                IB::TransmissionError.new message
         
     | 
| 
      
 41 
     | 
    
         
            +
              when :verify
         
     | 
| 
      
 42 
     | 
    
         
            +
                IB::VerifyError.new message
         
     | 
| 
       41 
43 
     | 
    
         
             
              end
         
     | 
| 
       42 
     | 
    
         
            -
              e.set_backtrace( 
     | 
| 
      
 44 
     | 
    
         
            +
              e.set_backtrace(caller) if backtrace
         
     | 
| 
       43 
45 
     | 
    
         
             
              raise e
         
     | 
| 
       44 
46 
     | 
    
         
             
            end
         
     | 
| 
         @@ -4,10 +4,10 @@ module IB 
     | 
|
| 
       4 
4 
     | 
    
         | 
| 
       5 
5 
     | 
    
         
             
                  # Called Error in Java code, but in fact this type of messages also
         
     | 
| 
       6 
6 
     | 
    
         
             
                  # deliver system alerts and additional (non-error) info from TWS.
         
     | 
| 
       7 
     | 
    
         
            -
                   
     | 
| 
       8 
     | 
    
         
            -
             
     | 
| 
       9 
     | 
    
         
            -
             
     | 
| 
       10 
     | 
    
         
            -
             
     | 
| 
      
 7 
     | 
    
         
            +
                  Alert = def_message([4, 2],
         
     | 
| 
      
 8 
     | 
    
         
            +
                                      [:error_id, :int],
         
     | 
| 
      
 9 
     | 
    
         
            +
                                      [:code, :int],
         
     | 
| 
      
 10 
     | 
    
         
            +
                                      [:message, :string])
         
     | 
| 
       11 
11 
     | 
    
         
             
                  class Alert
         
     | 
| 
       12 
12 
     | 
    
         
             
                    # Is it an Error message?
         
     | 
| 
       13 
13 
     | 
    
         
             
                    def error?
         
     | 
| 
         @@ -62,7 +62,7 @@ module IB 
     | 
|
| 
       62 
62 
     | 
    
         
             
                       bar_size,
         
     | 
| 
       63 
63 
     | 
    
         
             
                       data_type.to_s.upcase,
         
     | 
| 
       64 
64 
     | 
    
         
             
                       @data[:use_rth] ,
         
     | 
| 
       65 
     | 
    
         
            -
            	   " 
     | 
| 
      
 65 
     | 
    
         
            +
            	   ""   # not suported realtimebars option string
         
     | 
| 
       66 
66 
     | 
    
         
             
            	  ]
         
     | 
| 
       67 
67 
     | 
    
         
             
                    end
         
     | 
| 
       68 
68 
     | 
    
         
             
                  end # RequestRealTimeBars
         
     | 
| 
         @@ -189,7 +189,7 @@ module IB 
     | 
|
| 
       189 
189 
     | 
    
         
             
                      2 , # @data[:format_date], format-date is hard-coded as int_date in incoming/historicalData 
         
     | 
| 
       190 
190 
     | 
    
         
             
                       contract.serialize_legs ,
         
     | 
| 
       191 
191 
     | 
    
         
             
            	   @data[:keep_up_todate],   # 0 / 1
         
     | 
| 
       192 
     | 
    
         
            -
            	  ' 
     | 
| 
      
 192 
     | 
    
         
            +
            	  ''	#  chartOptions:TagValueList - For internal use only. Use default value XYZ. 	
         
     | 
| 
       193 
193 
     | 
    
         
             
            	  ]
         
     | 
| 
       194 
194 
     | 
    
         
             
                    end
         
     | 
| 
       195 
195 
     | 
    
         
             
                  end # RequestHistoricalData
         
     | 
| 
         @@ -74,9 +74,9 @@ module IB 
     | 
|
| 
       74 
74 
     | 
    
         
             
                       order.all_or_none || false,
         
     | 
| 
       75 
75 
     | 
    
         
             
                       order.min_quantity || "",
         
     | 
| 
       76 
76 
     | 
    
         
             
                       order.percent_offset || '',
         
     | 
| 
       77 
     | 
    
         
            -
                       order.etrade_only || false,
         
     | 
| 
       78 
     | 
    
         
            -
                       order.firm_quote_only || false,
         
     | 
| 
       79 
     | 
    
         
            -
                       order.nbbo_price_cap || "",
         
     | 
| 
      
 77 
     | 
    
         
            +
                       false, # was: order.etrade_only || false,  desupported in TWS > 981
         
     | 
| 
      
 78 
     | 
    
         
            +
                       false, # was: order.firm_quote_only || false,    desupported in TWS > 981
         
     | 
| 
      
 79 
     | 
    
         
            +
                       order.nbbo_price_cap || "", ## desupported in TWS > 981, too. maybe we have to insert a hard-coded "" here
         
     | 
| 
       80 
80 
     | 
    
         
             
                       order[:auction_strategy],
         
     | 
| 
       81 
81 
     | 
    
         
             
                       order.starting_price,
         
     | 
| 
       82 
82 
     | 
    
         
             
                       order.stock_ref_price || "",
         
     | 
| 
         @@ -0,0 +1,85 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            module IB
         
     | 
| 
      
 4 
     | 
    
         
            +
              # Convert data passed in from a TCP socket stream, and convert into
         
     | 
| 
      
 5 
     | 
    
         
            +
              # raw messages. The messages
         
     | 
| 
      
 6 
     | 
    
         
            +
              class RawMessageParser
         
     | 
| 
      
 7 
     | 
    
         
            +
                HEADER_LNGTH = 4
         
     | 
| 
      
 8 
     | 
    
         
            +
                def initialize(socket)
         
     | 
| 
      
 9 
     | 
    
         
            +
                  @socket = socket
         
     | 
| 
      
 10 
     | 
    
         
            +
                  @data = String.new
         
     | 
| 
      
 11 
     | 
    
         
            +
                end
         
     | 
| 
      
 12 
     | 
    
         
            +
             
     | 
| 
      
 13 
     | 
    
         
            +
                def each
         
     | 
| 
      
 14 
     | 
    
         
            +
                  while true
         
     | 
| 
      
 15 
     | 
    
         
            +
                  append_new_data
         
     | 
| 
      
 16 
     | 
    
         
            +
             
     | 
| 
      
 17 
     | 
    
         
            +
                  next unless length_data?
         
     | 
| 
      
 18 
     | 
    
         
            +
                  next unless enough_data?
         
     | 
| 
      
 19 
     | 
    
         
            +
             
     | 
| 
      
 20 
     | 
    
         
            +
                  length = next_msg_length
         
     | 
| 
      
 21 
     | 
    
         
            +
                  validate_data_header(length)
         
     | 
| 
      
 22 
     | 
    
         
            +
             
     | 
| 
      
 23 
     | 
    
         
            +
                  raw = grab_message(length)
         
     | 
| 
      
 24 
     | 
    
         
            +
                  validate_message_footer(raw, length)
         
     | 
| 
      
 25 
     | 
    
         
            +
                  msg = parse_message(raw, length)
         
     | 
| 
      
 26 
     | 
    
         
            +
                  remove_message
         
     | 
| 
      
 27 
     | 
    
         
            +
                  yield msg
         
     | 
| 
      
 28 
     | 
    
         
            +
                  end
         
     | 
| 
      
 29 
     | 
    
         
            +
                end
         
     | 
| 
      
 30 
     | 
    
         
            +
             
     | 
| 
      
 31 
     | 
    
         
            +
                # extract message and convert to
         
     | 
| 
      
 32 
     | 
    
         
            +
                # an array split by null characters.
         
     | 
| 
      
 33 
     | 
    
         
            +
                def grab_message(length)
         
     | 
| 
      
 34 
     | 
    
         
            +
                  @data.byteslice(HEADER_LNGTH, length)
         
     | 
| 
      
 35 
     | 
    
         
            +
                end
         
     | 
| 
      
 36 
     | 
    
         
            +
             
     | 
| 
      
 37 
     | 
    
         
            +
                def parse_message(raw, length)
         
     | 
| 
      
 38 
     | 
    
         
            +
                  raw.unpack1("A#{length}").split("\0")
         
     | 
| 
      
 39 
     | 
    
         
            +
                end
         
     | 
| 
      
 40 
     | 
    
         
            +
             
     | 
| 
      
 41 
     | 
    
         
            +
                def remove_message
         
     | 
| 
      
 42 
     | 
    
         
            +
                  length = next_msg_length
         
     | 
| 
      
 43 
     | 
    
         
            +
                  leftovers = @data.byteslice(length + HEADER_LNGTH..-1)
         
     | 
| 
      
 44 
     | 
    
         
            +
                  @data = if leftovers.nil?
         
     | 
| 
      
 45 
     | 
    
         
            +
                            String.new
         
     | 
| 
      
 46 
     | 
    
         
            +
                          else
         
     | 
| 
      
 47 
     | 
    
         
            +
                            leftovers
         
     | 
| 
      
 48 
     | 
    
         
            +
                          end
         
     | 
| 
      
 49 
     | 
    
         
            +
                end
         
     | 
| 
      
 50 
     | 
    
         
            +
             
     | 
| 
      
 51 
     | 
    
         
            +
                def enough_data?
         
     | 
| 
      
 52 
     | 
    
         
            +
                  actual_lngth = next_msg_length + HEADER_LNGTH
         
     | 
| 
      
 53 
     | 
    
         
            +
                  echo 'too little data' if next_msg_length.nil?
         
     | 
| 
      
 54 
     | 
    
         
            +
                  return false if next_msg_length.nil?
         
     | 
| 
      
 55 
     | 
    
         
            +
                  @data.bytesize >= actual_lngth
         
     | 
| 
      
 56 
     | 
    
         
            +
                end
         
     | 
| 
      
 57 
     | 
    
         
            +
             
     | 
| 
      
 58 
     | 
    
         
            +
                def length_data?
         
     | 
| 
      
 59 
     | 
    
         
            +
                  @data.bytesize > HEADER_LNGTH
         
     | 
| 
      
 60 
     | 
    
         
            +
                end
         
     | 
| 
      
 61 
     | 
    
         
            +
             
     | 
| 
      
 62 
     | 
    
         
            +
                def next_msg_length
         
     | 
| 
      
 63 
     | 
    
         
            +
                  #can't check length if first 4 bytes don't exist
         
     | 
| 
      
 64 
     | 
    
         
            +
                  length = @data.byteslice(0..3).unpack1('N')
         
     | 
| 
      
 65 
     | 
    
         
            +
                  return 0 if length.nil?
         
     | 
| 
      
 66 
     | 
    
         
            +
                  length
         
     | 
| 
      
 67 
     | 
    
         
            +
                end
         
     | 
| 
      
 68 
     | 
    
         
            +
             
     | 
| 
      
 69 
     | 
    
         
            +
                def append_new_data
         
     | 
| 
      
 70 
     | 
    
         
            +
                  @data += @socket.recv_from
         
     | 
| 
      
 71 
     | 
    
         
            +
                end
         
     | 
| 
      
 72 
     | 
    
         
            +
             
     | 
| 
      
 73 
     | 
    
         
            +
                def validate_message_footer(msg,length)
         
     | 
| 
      
 74 
     | 
    
         
            +
                  last = msg.bytesize
         
     | 
| 
      
 75 
     | 
    
         
            +
                  last_byte = msg.byteslice(last-1,last)
         
     | 
| 
      
 76 
     | 
    
         
            +
                  raise 'Could not validate last byte' if last_byte.nil?
         
     | 
| 
      
 77 
     | 
    
         
            +
                  raise "Message has an invalid last byte. expecting \0, received: #{last_byte}" if last_byte != "\0"
         
     | 
| 
      
 78 
     | 
    
         
            +
                end
         
     | 
| 
      
 79 
     | 
    
         
            +
             
     | 
| 
      
 80 
     | 
    
         
            +
                def validate_data_header(length)
         
     | 
| 
      
 81 
     | 
    
         
            +
                  return true if length <= 5000
         
     | 
| 
      
 82 
     | 
    
         
            +
                  raise 'Message is longer than sane max length'
         
     | 
| 
      
 83 
     | 
    
         
            +
                end
         
     | 
| 
      
 84 
     | 
    
         
            +
              end
         
     | 
| 
      
 85 
     | 
    
         
            +
            end
         
     | 
    
        data/lib/ib/socket.rb
    CHANGED
    
    | 
         @@ -154,7 +154,7 @@ module IB 
     | 
|
| 
       154 
154 
     | 
    
         
             
                def send_messages *data
         
     | 
| 
       155 
155 
     | 
    
         
             
                  self.syswrite prepare_message(data)
         
     | 
| 
       156 
156 
     | 
    
         
             
                rescue Errno::ECONNRESET =>  e
         
     | 
| 
       157 
     | 
    
         
            -
                  Connection.logger. 
     | 
| 
      
 157 
     | 
    
         
            +
                  Connection.logger.fatal{ "Data not accepted by IB \n
         
     | 
| 
       158 
158 
     | 
    
         
             
            		    #{data.inspect} \n
         
     | 
| 
       159 
159 
     | 
    
         
             
            		    Backtrace:\n "}
         
     | 
| 
       160 
160 
     | 
    
         
             
                  Connection.logger.error   e.backtrace
         
     | 
| 
         @@ -172,7 +172,7 @@ module IB 
     | 
|
| 
       172 
172 
     | 
    
         
             
            	end while buffer.size == 4096
         
     | 
| 
       173 
173 
     | 
    
         
             
            	complete_message_buffer.join('')
         
     | 
| 
       174 
174 
     | 
    
         
             
                  rescue Errno::ECONNRESET =>  e
         
     | 
| 
       175 
     | 
    
         
            -
            	    Connection.logger. 
     | 
| 
      
 175 
     | 
    
         
            +
            	    Connection.logger.fatal{ "Data Buffer is not filling \n
         
     | 
| 
       176 
176 
     | 
    
         
             
            		    The Buffer: #{buffer.inspect} \n
         
     | 
| 
       177 
177 
     | 
    
         
             
            		    Backtrace:\n 
         
     | 
| 
       178 
178 
     | 
    
         
             
            	 #{e.backtrace.join("\n") } " }
         
     | 
    
        data/lib/logging.rb
    CHANGED
    
    | 
         @@ -26,20 +26,21 @@ module Support 
     | 
|
| 
       26 
26 
     | 
    
         
             
            				@logger = logger
         
     | 
| 
       27 
27 
     | 
    
         
             
            			end
         
     | 
| 
       28 
28 
     | 
    
         | 
| 
       29 
     | 
    
         
            -
            			def configure_logger(log= 
     | 
| 
       30 
     | 
    
         
            -
             
     | 
| 
      
 29 
     | 
    
         
            +
            			def configure_logger(log= STDOUT)
         
     | 
| 
      
 30 
     | 
    
         
            +
                    if log.is_a? Logger
         
     | 
| 
       31 
31 
     | 
    
         
             
            					@logger = log
         
     | 
| 
       32 
32 
     | 
    
         
             
            				else
         
     | 
| 
       33 
     | 
    
         
            -
             
     | 
| 
       34 
     | 
    
         
            -
             
     | 
| 
       35 
     | 
    
         
            -
             
     | 
| 
      
 33 
     | 
    
         
            +
            					@logger = Logger.new log
         
     | 
| 
      
 34 
     | 
    
         
            +
                    end
         
     | 
| 
      
 35 
     | 
    
         
            +
                    @logger.level = Logger::INFO
         
     | 
| 
      
 36 
     | 
    
         
            +
                    @logger.formatter = proc do |severity, datetime, progname, msg|
         
     | 
| 
       36 
37 
     | 
    
         
             
            					#	"#{datetime.strftime("%d.%m.(%X)")}#{"%5s" % severity}->#{msg}\n"
         
     | 
| 
       37 
     | 
    
         
            -
            						"#{"% 
     | 
| 
      
 38 
     | 
    
         
            +
            						"#{"%1s" % severity[0]}: #{msg}\n"
         
     | 
| 
       38 
39 
     | 
    
         
             
            					end
         
     | 
| 
       39 
     | 
    
         
            -
             
     | 
| 
       40 
     | 
    
         
            -
            				end # branch
         
     | 
| 
      
 40 
     | 
    
         
            +
                    @logger.debug "------------------------------ start logging ----------------------------"
         
     | 
| 
       41 
41 
     | 
    
         
             
            			end # def
         
     | 
| 
       42 
42 
     | 
    
         
             
            		end # module ClassMethods
         
     | 
| 
       43 
43 
     | 
    
         
             
            	end # module Logging
         
     | 
| 
       44 
44 
     | 
    
         
             
            end # module Support
         
     | 
| 
       45 
45 
     | 
    
         | 
| 
      
 46 
     | 
    
         
            +
            # source: https://github.com/jondot/sneakers/blob/master/lib/sneakers/concerns/logging.rb
         
     | 
    
        data/lib/models/ib/contract.rb
    CHANGED
    
    | 
         @@ -5,7 +5,7 @@ require 'models/ib/underlying' 
     | 
|
| 
       5 
5 
     | 
    
         | 
| 
       6 
6 
     | 
    
         
             
            module IB
         
     | 
| 
       7 
7 
     | 
    
         | 
| 
       8 
     | 
    
         
            -
              if defined?(Contract) 
     | 
| 
      
 8 
     | 
    
         
            +
              if defined?(Contract)
         
     | 
| 
       9 
9 
     | 
    
         
             
            		#Connection.current.logger.warn "Contract already a #{defined?(Contract)}"
         
     | 
| 
       10 
10 
     | 
    
         | 
| 
       11 
11 
     | 
    
         
             
            #		puts Contract.ancestors
         
     | 
| 
         @@ -235,10 +235,46 @@ module IB 
     | 
|
| 
       235 
235 
     | 
    
         | 
| 
       236 
236 
     | 
    
         
             
            		# creates a new Contract substituting attributes by the provided key-value pairs.
         
     | 
| 
       237 
237 
     | 
    
         
             
            		#
         
     | 
| 
       238 
     | 
    
         
            -
             
     | 
| 
       239 
     | 
    
         
            -
            		 
     | 
| 
       240 
     | 
    
         
            -
             
     | 
| 
       241 
     | 
    
         
            -
             
     | 
| 
      
 238 
     | 
    
         
            +
                # for convenience
         
     | 
| 
      
 239 
     | 
    
         
            +
            		# con_id, local_symbol and last_trading_day are resetted, 
         
     | 
| 
      
 240 
     | 
    
         
            +
                # the link to contract-details is  savaged
         
     | 
| 
      
 241 
     | 
    
         
            +
                #
         
     | 
| 
      
 242 
     | 
    
         
            +
                # Example
         
     | 
| 
      
 243 
     | 
    
         
            +
                #   ge =  Stock.new( symbol: :ge).verify.first
         
     | 
| 
      
 244 
     | 
    
         
            +
                #   f = ge.merge symbol: :f
         
     | 
| 
      
 245 
     | 
    
         
            +
                #
         
     | 
| 
      
 246 
     | 
    
         
            +
                #   c =  Contract.new( con_id: 428520002,  exchange: 'Globex')
         
     | 
| 
      
 247 
     | 
    
         
            +
                #puts c.verify.as_table
         
     | 
| 
      
 248 
     | 
    
         
            +
            #┌────────┬────────┬───────────┬──────────┬──────────┬────────────┬───────────────┬───────┬────────┬──────────┐
         
     | 
| 
      
 249 
     | 
    
         
            +
            #│        │ symbol │ con_id    │ exchange │ expiry   │ multiplier │ trading-class │ right │ strike │ currency │
         
     | 
| 
      
 250 
     | 
    
         
            +
            #╞════════╪════════╪═══════════╪══════════╪══════════╪════════════╪═══════════════╪═══════╪════════╪══════════╡
         
     | 
| 
      
 251 
     | 
    
         
            +
            #│ Future │ NQ     │ 428520002 │  GLOBEX  │ 20210917 │     20     │      NQ       │       │        │   USD    │
         
     | 
| 
      
 252 
     | 
    
         
            +
            #└────────┴────────┴───────────┴──────────┴──────────┴────────────┴───────────────┴───────┴────────┴──────────┘
         
     | 
| 
      
 253 
     | 
    
         
            +
                # d= c.merge symbol: :es, trading_class: '', multiplier: 50
         
     | 
| 
      
 254 
     | 
    
         
            +
                # puts d.verify.as_table
         
     | 
| 
      
 255 
     | 
    
         
            +
            #┌────────┬────────┬───────────┬──────────┬──────────┬────────────┬───────────────┬───────┬────────┬──────────┐
         
     | 
| 
      
 256 
     | 
    
         
            +
            #│        │ symbol │ con_id    │ exchange │ expiry   │ multiplier │ trading-class │ right │ strike │ currency │
         
     | 
| 
      
 257 
     | 
    
         
            +
            #╞════════╪════════╪═══════════╪══════════╪══════════╪════════════╪═══════════════╪═══════╪════════╪══════════╡
         
     | 
| 
      
 258 
     | 
    
         
            +
            #│ Future │ ES     │ 428520022 │  GLOBEX  │ 20210917 │     50     │      ES       │       │        │   USD    │
         
     | 
| 
      
 259 
     | 
    
         
            +
            #│ Future │ ES     │ 446091461 │  GLOBEX  │ 20211217 │     50     │      ES       │       │        │   USD    │
         
     | 
| 
      
 260 
     | 
    
         
            +
            #│ Future │ ES     │ 461318816 │  GLOBEX  │ 20220318 │     50     │      ES       │       │        │   USD    │
         
     | 
| 
      
 261 
     | 
    
         
            +
            #│ Future │ ES     │ 477836957 │  GLOBEX  │ 20220617 │     50     │      ES       │       │        │   USD    │
         
     | 
| 
      
 262 
     | 
    
         
            +
            #│ Future │ ES     │ 495512551 │  GLOBEX  │ 20221216 │     50     │      ES       │       │        │   USD    │
         
     | 
| 
      
 263 
     | 
    
         
            +
            #│ Future │ ES     │ 495512552 │  GLOBEX  │ 20231215 │     50     │      ES       │       │        │   USD    │
         
     | 
| 
      
 264 
     | 
    
         
            +
            #│ Future │ ES     │ 495512557 │  GLOBEX  │ 20241220 │     50     │      ES       │       │        │   USD    │
         
     | 
| 
      
 265 
     | 
    
         
            +
            #│ Future │ ES     │ 495512563 │  GLOBEX  │ 20251219 │     50     │      ES       │       │        │   USD    │
         
     | 
| 
      
 266 
     | 
    
         
            +
            #│ Future │ ES     │ 495512566 │  GLOBEX  │ 20220916 │     50     │      ES       │       │        │   USD    │
         
     | 
| 
      
 267 
     | 
    
         
            +
            #│ Future │ ES     │ 495512569 │  GLOBEX  │ 20230616 │     50     │      ES       │       │        │   USD    │
         
     | 
| 
      
 268 
     | 
    
         
            +
            #│ Future │ ES     │ 495512572 │  GLOBEX  │ 20230317 │     50     │      ES       │       │        │   USD    │
         
     | 
| 
      
 269 
     | 
    
         
            +
            #│ Future │ ES     │ 497222760 │  GLOBEX  │ 20230915 │     50     │      ES       │       │        │   USD    │
         
     | 
| 
      
 270 
     | 
    
         
            +
            #└────────┴────────┴───────────┴──────────┴──────────┴────────────┴───────────────┴───────┴────────┴──────────┘
         
     | 
| 
      
 271 
     | 
    
         
            +
                
         
     | 
| 
      
 272 
     | 
    
         
            +
                def merge **new_attributes
         
     | 
| 
      
 273 
     | 
    
         
            +
             
     | 
| 
      
 274 
     | 
    
         
            +
                  resetted_attributes = [:con_id, :local_symbol, :contract_detail]
         
     | 
| 
      
 275 
     | 
    
         
            +
                  ## last_trading_day / expiry needs special treatment
         
     | 
| 
      
 276 
     | 
    
         
            +
                  resetted_attributes << :last_trading_day if  new_attributes.keys.include? :expiry
         
     | 
| 
      
 277 
     | 
    
         
            +
                  self.class.new attributes.reject{|k,_| resetted_attributes.include? k}.merge(new_attributes)
         
     | 
| 
       242 
278 
     | 
    
         
             
            		end
         
     | 
| 
       243 
279 
     | 
    
         | 
| 
       244 
280 
     | 
    
         
             
                # Contract comparison
         
     | 
| 
         @@ -376,6 +412,29 @@ In places where these terms are used to indicate a concept, we have left them as 
     | 
|
| 
       376 
412 
     | 
    
         
             
            			Hash.new
         
     | 
| 
       377 
413 
     | 
    
         
             
            		end
         
     | 
| 
       378 
414 
     | 
    
         | 
| 
      
 415 
     | 
    
         
            +
             
     | 
| 
      
 416 
     | 
    
         
            +
                def table_header( &b )
         
     | 
| 
      
 417 
     | 
    
         
            +
                  if block_given?
         
     | 
| 
      
 418 
     | 
    
         
            +
                  [ yield(self) , 'symbol',  'con_id', 'exchange', 'expiry','multiplier', 'trading-class' , 'right', 'strike', 'currency' ]
         
     | 
| 
      
 419 
     | 
    
         
            +
                  else
         
     | 
| 
      
 420 
     | 
    
         
            +
                  [ '', 'symbol',  'con_id', 'exchange', 'expiry','multiplier', 'trading-class' , 'right', 'strike', 'currency' ]
         
     | 
| 
      
 421 
     | 
    
         
            +
                  end
         
     | 
| 
      
 422 
     | 
    
         
            +
                end
         
     | 
| 
      
 423 
     | 
    
         
            +
             
         
     | 
| 
      
 424 
     | 
    
         
            +
                def table_row
         
     | 
| 
      
 425 
     | 
    
         
            +
                  [ self.class.to_s.demodulize, symbol, 
         
     | 
| 
      
 426 
     | 
    
         
            +
                    { value: con_id.zero? ? '' : con_id , alignment: :right}, 
         
     | 
| 
      
 427 
     | 
    
         
            +
                     { value: exchange, alignment: :center}, 
         
     | 
| 
      
 428 
     | 
    
         
            +
                     expiry, 
         
     | 
| 
      
 429 
     | 
    
         
            +
                     { value: multiplier.zero??  "" : multiplier, alignment: :center}, 
         
     | 
| 
      
 430 
     | 
    
         
            +
                     { value: trading_class, alignment: :center},
         
     | 
| 
      
 431 
     | 
    
         
            +
                     {value: right == :none ? "": right, alignment: :center }, 
         
     | 
| 
      
 432 
     | 
    
         
            +
                     { value: strike.zero? ? "": strike, alignment: :right}, 
         
     | 
| 
      
 433 
     | 
    
         
            +
                     { value: currency, alignment: :center} ]
         
     | 
| 
      
 434 
     | 
    
         
            +
             
     | 
| 
      
 435 
     | 
    
         
            +
                end
         
     | 
| 
      
 436 
     | 
    
         
            +
             
     | 
| 
      
 437 
     | 
    
         
            +
             
     | 
| 
       379 
438 
     | 
    
         
             
              end # class Contract
         
     | 
| 
       380 
439 
     | 
    
         | 
| 
       381 
440 
     | 
    
         | 
    
        data/lib/models/ib/index.rb
    CHANGED
    
    | 
         @@ -4,7 +4,7 @@ module IB 
     | 
|
| 
       4 
4 
     | 
    
         
             
                validates_format_of :sec_type, :with => /\Aind\z/,
         
     | 
| 
       5 
5 
     | 
    
         
             
                  :message => "should be a Index"
         
     | 
| 
       6 
6 
     | 
    
         
             
                def default_attributes
         
     | 
| 
       7 
     | 
    
         
            -
                  super.merge :sec_type => 
     | 
| 
      
 7 
     | 
    
         
            +
                  super.merge :sec_type =>  'IND'
         
     | 
| 
       8 
8 
     | 
    
         
             
                end
         
     | 
| 
       9 
9 
     | 
    
         
             
                def to_human
         
     | 
| 
       10 
10 
     | 
    
         
             
                  "<Index: " + [symbol, currency].join(" ") + " (#{description}) >"
         
     | 
| 
         @@ -67,5 +67,18 @@ module IB 
     | 
|
| 
       67 
67 
     | 
    
         | 
| 
       68 
68 
     | 
    
         
             
                end
         
     | 
| 
       69 
69 
     | 
    
         | 
| 
      
 70 
     | 
    
         
            +
                def table_header
         
     | 
| 
      
 71 
     | 
    
         
            +
                  [ 'Greeks', 'price',  'impl. vola', 'dividend', 'delta','gamma', 'vega' , 'theta']
         
     | 
| 
      
 72 
     | 
    
         
            +
                end
         
     | 
| 
      
 73 
     | 
    
         
            +
             
         
     | 
| 
      
 74 
     | 
    
         
            +
                def table_row
         
     | 
| 
      
 75 
     | 
    
         
            +
                  outstr= ->( item ) { { value: item.nil? ? "--" : sprintf("%g" , item) , alignment: :right } } 
         
     | 
| 
      
 76 
     | 
    
         
            +
                  outprice= ->( item ) { { value: item.nil? ? "--" : sprintf("%7.2f" , item) , alignment: :right } } 
         
     | 
| 
      
 77 
     | 
    
         
            +
                  option_short = ->{"#{option.right}  #{option.symbol}#{ "/"+ option.trading_class unless option.trading_class == option.symbol } #{option.expiry}  #{option.strike}"}
         
     | 
| 
      
 78 
     | 
    
         
            +
                  [ option_short[], outprice[ option_price ], outprice[ implied_volatility ],
         
     | 
| 
      
 79 
     | 
    
         
            +
                    outprice[ pv_dividend ], 
         
     | 
| 
      
 80 
     | 
    
         
            +
                    outprice[ delta ], outprice[ gamma ], outprice[ vega ] , outprice[ theta ] ]
         
     | 
| 
      
 81 
     | 
    
         
            +
                end
         
     | 
| 
      
 82 
     | 
    
         
            +
             
     | 
| 
       70 
83 
     | 
    
         
             
              end  # class
         
     | 
| 
       71 
84 
     | 
    
         
             
            end # module
         
     |