ib-api 972.0
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 +7 -0
 - data/.gitignore +50 -0
 - data/.rspec +3 -0
 - data/.travis.yml +7 -0
 - data/CODE_OF_CONDUCT.md +74 -0
 - data/Gemfile +16 -0
 - data/Gemfile.lock +105 -0
 - data/Guardfile +24 -0
 - data/LICENSE +674 -0
 - data/README.md +65 -0
 - data/Rakefile +11 -0
 - data/VERSION +1 -0
 - data/api.gemspec +43 -0
 - data/bin/console +95 -0
 - data/bin/console.yml +3 -0
 - data/bin/setup +8 -0
 - data/changelog.md +7 -0
 - data/example/README.md +76 -0
 - data/example/account_info +54 -0
 - data/example/account_positions +30 -0
 - data/example/account_summary +88 -0
 - data/example/cancel_orders +74 -0
 - data/example/fa_accounts +25 -0
 - data/example/fundamental_data +40 -0
 - data/example/historic_data_cli +186 -0
 - data/example/list_orders +45 -0
 - data/example/portfolio_csv +81 -0
 - data/example/scanner_data +62 -0
 - data/example/template +19 -0
 - data/example/tick_data +28 -0
 - data/lib/extensions/class-extensions.rb +87 -0
 - data/lib/ib-api.rb +7 -0
 - data/lib/ib/base.rb +103 -0
 - data/lib/ib/base_properties.rb +160 -0
 - data/lib/ib/connection.rb +450 -0
 - data/lib/ib/constants.rb +393 -0
 - data/lib/ib/errors.rb +44 -0
 - data/lib/ib/logger.rb +26 -0
 - data/lib/ib/messages.rb +99 -0
 - data/lib/ib/messages/abstract_message.rb +101 -0
 - data/lib/ib/messages/incoming.rb +251 -0
 - data/lib/ib/messages/incoming/abstract_message.rb +116 -0
 - data/lib/ib/messages/incoming/account_value.rb +78 -0
 - data/lib/ib/messages/incoming/alert.rb +34 -0
 - data/lib/ib/messages/incoming/contract_data.rb +102 -0
 - data/lib/ib/messages/incoming/delta_neutral_validation.rb +23 -0
 - data/lib/ib/messages/incoming/execution_data.rb +50 -0
 - data/lib/ib/messages/incoming/historical_data.rb +84 -0
 - data/lib/ib/messages/incoming/market_depths.rb +44 -0
 - data/lib/ib/messages/incoming/next_valid_id.rb +18 -0
 - data/lib/ib/messages/incoming/open_order.rb +277 -0
 - data/lib/ib/messages/incoming/order_status.rb +85 -0
 - data/lib/ib/messages/incoming/portfolio_value.rb +78 -0
 - data/lib/ib/messages/incoming/real_time_bar.rb +32 -0
 - data/lib/ib/messages/incoming/scanner_data.rb +54 -0
 - data/lib/ib/messages/incoming/ticks.rb +268 -0
 - data/lib/ib/messages/outgoing.rb +437 -0
 - data/lib/ib/messages/outgoing/abstract_message.rb +88 -0
 - data/lib/ib/messages/outgoing/account_requests.rb +112 -0
 - data/lib/ib/messages/outgoing/bar_requests.rb +250 -0
 - data/lib/ib/messages/outgoing/place_order.rb +209 -0
 - data/lib/ib/messages/outgoing/request_marketdata.rb +99 -0
 - data/lib/ib/messages/outgoing/request_tick_data.rb +21 -0
 - data/lib/ib/model.rb +4 -0
 - data/lib/ib/models.rb +14 -0
 - data/lib/ib/server_versions.rb +114 -0
 - data/lib/ib/socket.rb +185 -0
 - data/lib/ib/support.rb +160 -0
 - data/lib/ib/version.rb +6 -0
 - data/lib/models/ib/account.rb +85 -0
 - data/lib/models/ib/account_value.rb +33 -0
 - data/lib/models/ib/bag.rb +55 -0
 - data/lib/models/ib/bar.rb +31 -0
 - data/lib/models/ib/combo_leg.rb +105 -0
 - data/lib/models/ib/condition.rb +245 -0
 - data/lib/models/ib/contract.rb +415 -0
 - data/lib/models/ib/contract_detail.rb +108 -0
 - data/lib/models/ib/execution.rb +67 -0
 - data/lib/models/ib/forex.rb +13 -0
 - data/lib/models/ib/future.rb +15 -0
 - data/lib/models/ib/index.rb +15 -0
 - data/lib/models/ib/option.rb +78 -0
 - data/lib/models/ib/option_detail.rb +55 -0
 - data/lib/models/ib/order.rb +519 -0
 - data/lib/models/ib/order_state.rb +152 -0
 - data/lib/models/ib/portfolio_value.rb +64 -0
 - data/lib/models/ib/stock.rb +16 -0
 - data/lib/models/ib/underlying.rb +34 -0
 - data/lib/models/ib/vertical.rb +96 -0
 - data/lib/requires.rb +12 -0
 - metadata +203 -0
 
| 
         @@ -0,0 +1,450 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'thread'
         
     | 
| 
      
 2 
     | 
    
         
            +
            #require 'active_support'
         
     | 
| 
      
 3 
     | 
    
         
            +
            require 'ib/socket'
         
     | 
| 
      
 4 
     | 
    
         
            +
            require 'ib/logger'
         
     | 
| 
      
 5 
     | 
    
         
            +
            require 'ib/messages'
         
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
      
 7 
     | 
    
         
            +
            module IB
         
     | 
| 
      
 8 
     | 
    
         
            +
              # Encapsulates API connection to TWS or Gateway
         
     | 
| 
      
 9 
     | 
    
         
            +
              class Connection
         
     | 
| 
      
 10 
     | 
    
         
            +
             
     | 
| 
      
 11 
     | 
    
         
            +
             
     | 
| 
      
 12 
     | 
    
         
            +
              ## -------------------------------------------- Interface ---------------------------------
         
     | 
| 
      
 13 
     | 
    
         
            +
              ## public attributes: socket, next_local_id ( alias next_order_id)
         
     | 
| 
      
 14 
     | 
    
         
            +
              ## public methods:  connect (alias open), disconnect, connected?
         
     | 
| 
      
 15 
     | 
    
         
            +
              ##		      subscribe, unsubscribe
         
     | 
| 
      
 16 
     | 
    
         
            +
              ##		      send_message (alias dispatch)
         
     | 
| 
      
 17 
     | 
    
         
            +
              ##		      place_order, modify_order, cancel_order
         
     | 
| 
      
 18 
     | 
    
         
            +
              ## public data-queue: received,  received?, wait_for, clear_received
         
     | 
| 
      
 19 
     | 
    
         
            +
              ## misc:	      reader_running?
         
     | 
| 
      
 20 
     | 
    
         
            +
             
     | 
| 
      
 21 
     | 
    
         
            +
              include LogDev   # provides default_logger
         
     | 
| 
      
 22 
     | 
    
         
            +
             
     | 
| 
      
 23 
     | 
    
         
            +
                mattr_accessor :current
         
     | 
| 
      
 24 
     | 
    
         
            +
                mattr_accessor :logger  ## borrowed from active_support
         
     | 
| 
      
 25 
     | 
    
         
            +
                # Please note, we are realizing only the most current TWS protocol versions,
         
     | 
| 
      
 26 
     | 
    
         
            +
                # thus improving performance at the expense of backwards compatibility.
         
     | 
| 
      
 27 
     | 
    
         
            +
                # Older protocol versions support can be found in older gem versions.
         
     | 
| 
      
 28 
     | 
    
         
            +
             
     | 
| 
      
 29 
     | 
    
         
            +
                attr_accessor  :socket #   Socket to IB server (TWS or Gateway)
         
     | 
| 
      
 30 
     | 
    
         
            +
                attr_accessor  :next_local_id # Next valid order id
         
     | 
| 
      
 31 
     | 
    
         
            +
                attr_accessor  :client_id
         
     | 
| 
      
 32 
     | 
    
         
            +
                attr_accessor  :server_version
         
     | 
| 
      
 33 
     | 
    
         
            +
                attr_accessor  :client_version
         
     | 
| 
      
 34 
     | 
    
         
            +
                alias next_order_id next_local_id
         
     | 
| 
      
 35 
     | 
    
         
            +
                alias next_order_id= next_local_id=
         
     | 
| 
      
 36 
     | 
    
         
            +
             
     | 
| 
      
 37 
     | 
    
         
            +
                def initialize host: '127.0.0.1',
         
     | 
| 
      
 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: default_logger,
         
     | 
| 
      
 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 
     | 
    
         
            +
             
     | 
| 
      
 53 
     | 
    
         
            +
                # convert parameters into instance-variables and assign them
         
     | 
| 
      
 54 
     | 
    
         
            +
            		method(__method__).parameters.each do |type, k|
         
     | 
| 
      
 55 
     | 
    
         
            +
            			next unless type == :key
         
     | 
| 
      
 56 
     | 
    
         
            +
            			case k
         
     | 
| 
      
 57 
     | 
    
         
            +
            			when :logger
         
     | 
| 
      
 58 
     | 
    
         
            +
            				self.logger = logger
         
     | 
| 
      
 59 
     | 
    
         
            +
            			else
         
     | 
| 
      
 60 
     | 
    
         
            +
            				v = eval(k.to_s)
         
     | 
| 
      
 61 
     | 
    
         
            +
            				instance_variable_set("@#{k}", v) unless v.nil?
         
     | 
| 
      
 62 
     | 
    
         
            +
            			end
         
     | 
| 
      
 63 
     | 
    
         
            +
            		end
         
     | 
| 
      
 64 
     | 
    
         
            +
             
     | 
| 
      
 65 
     | 
    
         
            +
            		# A couple of locks to avoid race conditions in JRuby
         
     | 
| 
      
 66 
     | 
    
         
            +
            		@subscribe_lock = Mutex.new
         
     | 
| 
      
 67 
     | 
    
         
            +
            		@receive_lock = Mutex.new
         
     | 
| 
      
 68 
     | 
    
         
            +
            		@message_lock = Mutex.new
         
     | 
| 
      
 69 
     | 
    
         
            +
             
     | 
| 
      
 70 
     | 
    
         
            +
            		@connected = false
         
     | 
| 
      
 71 
     | 
    
         
            +
            		self.next_local_id = nil
         
     | 
| 
      
 72 
     | 
    
         
            +
             
     | 
| 
      
 73 
     | 
    
         
            +
            		#     self.subscribe(:Alert) do |msg|
         
     | 
| 
      
 74 
     | 
    
         
            +
            		#       puts msg.to_human
         
     | 
| 
      
 75 
     | 
    
         
            +
            		#     end
         
     | 
| 
      
 76 
     | 
    
         
            +
             
     | 
| 
      
 77 
     | 
    
         
            +
            		# TWS always sends NextValidId message at connect -subscribe save this id
         
     | 
| 
      
 78 
     | 
    
         
            +
            		## this block is executed before tws-communication is established
         
     | 
| 
      
 79 
     | 
    
         
            +
            		yield self if block_given?
         
     | 
| 
      
 80 
     | 
    
         
            +
             
     | 
| 
      
 81 
     | 
    
         
            +
            		self.subscribe(:NextValidId) do |msg|
         
     | 
| 
      
 82 
     | 
    
         
            +
            			logger.progname = "Connection#connect"
         
     | 
| 
      
 83 
     | 
    
         
            +
            			self.next_local_id = msg.local_id
         
     | 
| 
      
 84 
     | 
    
         
            +
            			logger.info { "Got next valid order id: #{next_local_id}." }
         
     | 
| 
      
 85 
     | 
    
         
            +
            		end
         
     | 
| 
      
 86 
     | 
    
         
            +
             
     | 
| 
      
 87 
     | 
    
         
            +
            		# Ensure the transmission of NextValidId.
         
     | 
| 
      
 88 
     | 
    
         
            +
            		# works even if no reader_thread is established
         
     | 
| 
      
 89 
     | 
    
         
            +
            		if connect
         
     | 
| 
      
 90 
     | 
    
         
            +
            			disconnect if connected?
         
     | 
| 
      
 91 
     | 
    
         
            +
            			 update_next_order_id
         
     | 
| 
      
 92 
     | 
    
         
            +
            			Kernel.exit if self.next_local_id.nil?
         
     | 
| 
      
 93 
     | 
    
         
            +
            		end
         
     | 
| 
      
 94 
     | 
    
         
            +
            		#start_reader if @received && connected?
         
     | 
| 
      
 95 
     | 
    
         
            +
            		Connection.current = self
         
     | 
| 
      
 96 
     | 
    
         
            +
            		end
         
     | 
| 
      
 97 
     | 
    
         
            +
             
     | 
| 
      
 98 
     | 
    
         
            +
            		# read actual order_id and
         
     | 
| 
      
 99 
     | 
    
         
            +
            		# connect if not connected
         
     | 
| 
      
 100 
     | 
    
         
            +
            		def update_next_order_id
         
     | 
| 
      
 101 
     | 
    
         
            +
            			i,finish = 0, false
         
     | 
| 
      
 102 
     | 
    
         
            +
            			sub = self.subscribe(:NextValidID) { finish =  true }
         
     | 
| 
      
 103 
     | 
    
         
            +
            			connected? ?  self.send_message( :RequestIds )  : open()
         
     | 
| 
      
 104 
     | 
    
         
            +
            			Timeout::timeout(1, IB::TransmissionError,"Could not get NextValidId" ) do
         
     | 
| 
      
 105 
     | 
    
         
            +
            				loop { sleep 0.1; break if finish  }
         
     | 
| 
      
 106 
     | 
    
         
            +
            			end
         
     | 
| 
      
 107 
     | 
    
         
            +
            			self.unsubscribe sub
         
     | 
| 
      
 108 
     | 
    
         
            +
            		end
         
     | 
| 
      
 109 
     | 
    
         
            +
             
     | 
| 
      
 110 
     | 
    
         
            +
            		### Working with connection
         
     | 
| 
      
 111 
     | 
    
         
            +
             
     | 
| 
      
 112 
     | 
    
         
            +
            		def connect
         
     | 
| 
      
 113 
     | 
    
         
            +
            			logger.progname='IB::Connection#connect'
         
     | 
| 
      
 114 
     | 
    
         
            +
            			if connected?
         
     | 
| 
      
 115 
     | 
    
         
            +
            				error  "Already connected!"
         
     | 
| 
      
 116 
     | 
    
         
            +
            				return
         
     | 
| 
      
 117 
     | 
    
         
            +
            			end
         
     | 
| 
      
 118 
     | 
    
         
            +
             
     | 
| 
      
 119 
     | 
    
         
            +
            			self.socket = IBSocket.open(@host, @port)  # raises  Errno::ECONNREFUSED  if no connection is possible
         
     | 
| 
      
 120 
     | 
    
         
            +
            			socket.initialising_handshake
         
     | 
| 
      
 121 
     | 
    
         
            +
            			socket.decode_message( socket.recieve_messages ) do  | the_message |
         
     | 
| 
      
 122 
     | 
    
         
            +
            				#				logger.info{ "TheMessage :: #{the_message.inspect}" }
         
     | 
| 
      
 123 
     | 
    
         
            +
            				@server_version =  the_message.shift.to_i
         
     | 
| 
      
 124 
     | 
    
         
            +
            				error "ServerVersion does not match  #{@server_version} <--> #{MAX_CLIENT_VER}" if @server_version != MAX_CLIENT_VER
         
     | 
| 
      
 125 
     | 
    
         
            +
             
     | 
| 
      
 126 
     | 
    
         
            +
            				@remote_connect_time = DateTime.parse the_message.shift
         
     | 
| 
      
 127 
     | 
    
         
            +
            				@local_connect_time = Time.now
         
     | 
| 
      
 128 
     | 
    
         
            +
            			end
         
     | 
| 
      
 129 
     | 
    
         
            +
             
     | 
| 
      
 130 
     | 
    
         
            +
            			# Sending (arbitrary) client ID to identify subsequent communications.
         
     | 
| 
      
 131 
     | 
    
         
            +
            			# The client with a client_id of 0 can manage the TWS-owned open orders.
         
     | 
| 
      
 132 
     | 
    
         
            +
            			# Other clients can only manage their own open orders.
         
     | 
| 
      
 133 
     | 
    
         
            +
             
     | 
| 
      
 134 
     | 
    
         
            +
            			# V100 initial handshake
         
     | 
| 
      
 135 
     | 
    
         
            +
            			# Parameters borrowed from the python client
         
     | 
| 
      
 136 
     | 
    
         
            +
            			start_api = 71
         
     | 
| 
      
 137 
     | 
    
         
            +
            			version = 2
         
     | 
| 
      
 138 
     | 
    
         
            +
            			#			optcap = @optional_capacities.empty? ? "" : " "+ @optional_capacities
         
     | 
| 
      
 139 
     | 
    
         
            +
            			socket.send_messages start_api, version, @client_id  , @optional_capacities
         
     | 
| 
      
 140 
     | 
    
         
            +
            			@connected = true
         
     | 
| 
      
 141 
     | 
    
         
            +
            			logger.info { "Connected to server, version: #{@server_version},\n connection time: " +
         
     | 
| 
      
 142 
     | 
    
         
            +
            								 "#{@local_connect_time} local, " +
         
     | 
| 
      
 143 
     | 
    
         
            +
            									 "#{@remote_connect_time} remote."}
         
     | 
| 
      
 144 
     | 
    
         
            +
             
     | 
| 
      
 145 
     | 
    
         
            +
            			# if the client_id is wrong or the port is not accessible the first read attempt fails
         
     | 
| 
      
 146 
     | 
    
         
            +
            			# get the first message and proceed if something reasonable is recieved
         
     | 
| 
      
 147 
     | 
    
         
            +
            			the_message = process_message   # recieve next_order_id
         
     | 
| 
      
 148 
     | 
    
         
            +
            			error "Check Port/Client_id ", :reader if the_message == " "
         
     | 
| 
      
 149 
     | 
    
         
            +
            			start_reader
         
     | 
| 
      
 150 
     | 
    
         
            +
            		end
         
     | 
| 
      
 151 
     | 
    
         
            +
             
     | 
| 
      
 152 
     | 
    
         
            +
                alias open connect # Legacy alias
         
     | 
| 
      
 153 
     | 
    
         
            +
             
     | 
| 
      
 154 
     | 
    
         
            +
                def disconnect
         
     | 
| 
      
 155 
     | 
    
         
            +
                  if reader_running?
         
     | 
| 
      
 156 
     | 
    
         
            +
                    @reader_running = false
         
     | 
| 
      
 157 
     | 
    
         
            +
                    @reader_thread.join
         
     | 
| 
      
 158 
     | 
    
         
            +
                  end
         
     | 
| 
      
 159 
     | 
    
         
            +
                  if connected?
         
     | 
| 
      
 160 
     | 
    
         
            +
                    socket.close
         
     | 
| 
      
 161 
     | 
    
         
            +
                    @connected = false
         
     | 
| 
      
 162 
     | 
    
         
            +
                  end
         
     | 
| 
      
 163 
     | 
    
         
            +
                end
         
     | 
| 
      
 164 
     | 
    
         
            +
             
     | 
| 
      
 165 
     | 
    
         
            +
                alias close disconnect # Legacy alias
         
     | 
| 
      
 166 
     | 
    
         
            +
             
     | 
| 
      
 167 
     | 
    
         
            +
                def connected?
         
     | 
| 
      
 168 
     | 
    
         
            +
                  @connected
         
     | 
| 
      
 169 
     | 
    
         
            +
                end
         
     | 
| 
      
 170 
     | 
    
         
            +
             
     | 
| 
      
 171 
     | 
    
         
            +
                ### Working with message subscribers
         
     | 
| 
      
 172 
     | 
    
         
            +
             
     | 
| 
      
 173 
     | 
    
         
            +
                # Subscribe Proc or block to specific type(s) of incoming message events.
         
     | 
| 
      
 174 
     | 
    
         
            +
                # Listener will be called later with received message instance as its argument.
         
     | 
| 
      
 175 
     | 
    
         
            +
                # Returns subscriber id to allow unsubscribing
         
     | 
| 
      
 176 
     | 
    
         
            +
                def subscribe *args, &block
         
     | 
| 
      
 177 
     | 
    
         
            +
                  @subscribe_lock.synchronize do
         
     | 
| 
      
 178 
     | 
    
         
            +
                    subscriber = args.last.respond_to?(:call) ? args.pop : block
         
     | 
| 
      
 179 
     | 
    
         
            +
                    id = random_id
         
     | 
| 
      
 180 
     | 
    
         
            +
             
     | 
| 
      
 181 
     | 
    
         
            +
                    error  "Need subscriber proc or block ", :args  unless subscriber.is_a? Proc
         
     | 
| 
      
 182 
     | 
    
         
            +
             
     | 
| 
      
 183 
     | 
    
         
            +
                    args.each do |what|
         
     | 
| 
      
 184 
     | 
    
         
            +
                      message_classes =
         
     | 
| 
      
 185 
     | 
    
         
            +
                      case
         
     | 
| 
      
 186 
     | 
    
         
            +
                      when what.is_a?(Class) && what < Messages::Incoming::AbstractMessage
         
     | 
| 
      
 187 
     | 
    
         
            +
                        [what]
         
     | 
| 
      
 188 
     | 
    
         
            +
                      when what.is_a?(Symbol)
         
     | 
| 
      
 189 
     | 
    
         
            +
                        [Messages::Incoming.const_get(what)]
         
     | 
| 
      
 190 
     | 
    
         
            +
                      when what.is_a?(Regexp)
         
     | 
| 
      
 191 
     | 
    
         
            +
                        Messages::Incoming::Classes.values.find_all { |klass| klass.to_s =~ what }
         
     | 
| 
      
 192 
     | 
    
         
            +
                      else
         
     | 
| 
      
 193 
     | 
    
         
            +
                        error  "#{what} must represent incoming IB message class", :args
         
     | 
| 
      
 194 
     | 
    
         
            +
                      end
         
     | 
| 
      
 195 
     | 
    
         
            +
                 # @subscribers_lock.synchronize do
         
     | 
| 
      
 196 
     | 
    
         
            +
                      message_classes.flatten.each do |message_class|
         
     | 
| 
      
 197 
     | 
    
         
            +
                        # TODO: Fix: RuntimeError: can't add a new key into hash during iteration
         
     | 
| 
      
 198 
     | 
    
         
            +
                        subscribers[message_class][id] = subscriber
         
     | 
| 
      
 199 
     | 
    
         
            +
                      end
         
     | 
| 
      
 200 
     | 
    
         
            +
                 # end  # lock
         
     | 
| 
      
 201 
     | 
    
         
            +
                    end
         
     | 
| 
      
 202 
     | 
    
         
            +
             
     | 
| 
      
 203 
     | 
    
         
            +
                    id
         
     | 
| 
      
 204 
     | 
    
         
            +
                  end
         
     | 
| 
      
 205 
     | 
    
         
            +
                end
         
     | 
| 
      
 206 
     | 
    
         
            +
             
     | 
| 
      
 207 
     | 
    
         
            +
                # Remove all subscribers with specific subscriber id
         
     | 
| 
      
 208 
     | 
    
         
            +
            		def unsubscribe *ids
         
     | 
| 
      
 209 
     | 
    
         
            +
            			@subscribe_lock.synchronize do
         
     | 
| 
      
 210 
     | 
    
         
            +
            				ids.collect do |id|
         
     | 
| 
      
 211 
     | 
    
         
            +
            					removed_at_id = subscribers.map { |_, subscribers| subscribers.delete id }.compact
         
     | 
| 
      
 212 
     | 
    
         
            +
            					logger.error  "No subscribers with id #{id}"   if removed_at_id.empty?
         
     | 
| 
      
 213 
     | 
    
         
            +
            					removed_at_id # return_value
         
     | 
| 
      
 214 
     | 
    
         
            +
            				end.flatten
         
     | 
| 
      
 215 
     | 
    
         
            +
            			end
         
     | 
| 
      
 216 
     | 
    
         
            +
            		end
         
     | 
| 
      
 217 
     | 
    
         
            +
                ### Working with received messages Hash
         
     | 
| 
      
 218 
     | 
    
         
            +
             
     | 
| 
      
 219 
     | 
    
         
            +
                # Clear received messages Hash
         
     | 
| 
      
 220 
     | 
    
         
            +
                def clear_received *message_types
         
     | 
| 
      
 221 
     | 
    
         
            +
                  @receive_lock.synchronize do
         
     | 
| 
      
 222 
     | 
    
         
            +
                    if message_types.empty?
         
     | 
| 
      
 223 
     | 
    
         
            +
                      received.each { |message_type, container| container.clear }
         
     | 
| 
      
 224 
     | 
    
         
            +
                    else
         
     | 
| 
      
 225 
     | 
    
         
            +
                      message_types.each { |message_type| received[message_type].clear }
         
     | 
| 
      
 226 
     | 
    
         
            +
                    end
         
     | 
| 
      
 227 
     | 
    
         
            +
                  end
         
     | 
| 
      
 228 
     | 
    
         
            +
                end
         
     | 
| 
      
 229 
     | 
    
         
            +
             
     | 
| 
      
 230 
     | 
    
         
            +
                # Hash of received messages, keyed by message type
         
     | 
| 
      
 231 
     | 
    
         
            +
                def received
         
     | 
| 
      
 232 
     | 
    
         
            +
                  @received_hash ||= Hash.new do |hash, message_type|
         
     | 
| 
      
 233 
     | 
    
         
            +
            				# enable access to the hash via
         
     | 
| 
      
 234 
     | 
    
         
            +
            				# ib.received[:MessageType].attribute
         
     | 
| 
      
 235 
     | 
    
         
            +
            				the_array = Array.new
         
     | 
| 
      
 236 
     | 
    
         
            +
            				def the_array.method_missing(method, *key)
         
     | 
| 
      
 237 
     | 
    
         
            +
            					unless method == :to_hash || method == :to_str #|| method == :to_int
         
     | 
| 
      
 238 
     | 
    
         
            +
            						return self.map{|x| x.public_send(method, *key)}
         
     | 
| 
      
 239 
     | 
    
         
            +
            					end
         
     | 
| 
      
 240 
     | 
    
         
            +
            				end
         
     | 
| 
      
 241 
     | 
    
         
            +
            			hash[message_type] = the_array
         
     | 
| 
      
 242 
     | 
    
         
            +
            			end
         
     | 
| 
      
 243 
     | 
    
         
            +
                end
         
     | 
| 
      
 244 
     | 
    
         
            +
             
     | 
| 
      
 245 
     | 
    
         
            +
                # Check if messages of given type were received at_least n times
         
     | 
| 
      
 246 
     | 
    
         
            +
                def received? message_type, times=1
         
     | 
| 
      
 247 
     | 
    
         
            +
                  @receive_lock.synchronize do
         
     | 
| 
      
 248 
     | 
    
         
            +
                    received[message_type].size >= times
         
     | 
| 
      
 249 
     | 
    
         
            +
                  end
         
     | 
| 
      
 250 
     | 
    
         
            +
                end
         
     | 
| 
      
 251 
     | 
    
         
            +
             
     | 
| 
      
 252 
     | 
    
         
            +
             
     | 
| 
      
 253 
     | 
    
         
            +
                # Wait for specific condition(s) - given as callable/block, or
         
     | 
| 
      
 254 
     | 
    
         
            +
                # message type(s) - given as Symbol or [Symbol, times] pair.
         
     | 
| 
      
 255 
     | 
    
         
            +
                # Timeout after given time or 1 second.
         
     | 
| 
      
 256 
     | 
    
         
            +
            		#
         
     | 
| 
      
 257 
     | 
    
         
            +
            		# wait_for depends heavyly on Connection#received. If collection of messages through recieved
         
     | 
| 
      
 258 
     | 
    
         
            +
            		# is turned off, wait_for loses most of its functionality
         
     | 
| 
      
 259 
     | 
    
         
            +
                def wait_for *args, &block
         
     | 
| 
      
 260 
     | 
    
         
            +
                  timeout = args.find { |arg| arg.is_a? Numeric } # extract timeout from args
         
     | 
| 
      
 261 
     | 
    
         
            +
                  end_time = Time.now + (timeout || 1) # default timeout 1 sec
         
     | 
| 
      
 262 
     | 
    
         
            +
                  conditions = args.delete_if { |arg| arg.is_a? Numeric }.push(block).compact
         
     | 
| 
      
 263 
     | 
    
         
            +
             
     | 
| 
      
 264 
     | 
    
         
            +
                  until end_time < Time.now || satisfied?(*conditions)
         
     | 
| 
      
 265 
     | 
    
         
            +
                    if reader_running?
         
     | 
| 
      
 266 
     | 
    
         
            +
                      sleep 0.05
         
     | 
| 
      
 267 
     | 
    
         
            +
                    else
         
     | 
| 
      
 268 
     | 
    
         
            +
                      process_messages 50
         
     | 
| 
      
 269 
     | 
    
         
            +
                    end
         
     | 
| 
      
 270 
     | 
    
         
            +
                  end
         
     | 
| 
      
 271 
     | 
    
         
            +
                end
         
     | 
| 
      
 272 
     | 
    
         
            +
             
     | 
| 
      
 273 
     | 
    
         
            +
                ### Working with Incoming messages from IB
         
     | 
| 
      
 274 
     | 
    
         
            +
             
     | 
| 
      
 275 
     | 
    
         
            +
             
     | 
| 
      
 276 
     | 
    
         
            +
                def reader_running?
         
     | 
| 
      
 277 
     | 
    
         
            +
                  @reader_running && @reader_thread && @reader_thread.alive?
         
     | 
| 
      
 278 
     | 
    
         
            +
                end
         
     | 
| 
      
 279 
     | 
    
         
            +
             
     | 
| 
      
 280 
     | 
    
         
            +
                # Process incoming messages during *poll_time* (200) msecs, nonblocking
         
     | 
| 
      
 281 
     | 
    
         
            +
                def process_messages poll_time = 50 # in msec
         
     | 
| 
      
 282 
     | 
    
         
            +
                  time_out = Time.now + poll_time/1000.0
         
     | 
| 
      
 283 
     | 
    
         
            +
                  while (time_left = time_out - Time.now) > 0
         
     | 
| 
      
 284 
     | 
    
         
            +
                    # If socket is readable, process single incoming message
         
     | 
| 
      
 285 
     | 
    
         
            +
            				#process_message if select [socket], nil, nil, time_left
         
     | 
| 
      
 286 
     | 
    
         
            +
            				# the following  checks for shutdown of TWS side; ensures we don't run in a spin loop.
         
     | 
| 
      
 287 
     | 
    
         
            +
            				# unfortunately, it raises Errors in windows environment
         
     | 
| 
      
 288 
     | 
    
         
            +
            				# disabled for now
         
     | 
| 
      
 289 
     | 
    
         
            +
                    if select [socket], nil, nil, time_left
         
     | 
| 
      
 290 
     | 
    
         
            +
                    #  # Peek at the message from the socket; if it's blank then the
         
     | 
| 
      
 291 
     | 
    
         
            +
                    #  # server side of connection (TWS) has likely shut down.
         
     | 
| 
      
 292 
     | 
    
         
            +
                      socket_likely_shutdown = socket.recvmsg(100, Socket::MSG_PEEK)[0] == ""
         
     | 
| 
      
 293 
     | 
    
         
            +
            				#
         
     | 
| 
      
 294 
     | 
    
         
            +
                    #  # We go ahead process messages regardless (a no-op if socket_likely_shutdown).
         
     | 
| 
      
 295 
     | 
    
         
            +
                      process_message
         
     | 
| 
      
 296 
     | 
    
         
            +
                    #
         
     | 
| 
      
 297 
     | 
    
         
            +
                    #  # After processing, if socket has shut down we sleep for 100ms
         
     | 
| 
      
 298 
     | 
    
         
            +
                    #  # to avoid spinning in a tight loop. If the server side somehow
         
     | 
| 
      
 299 
     | 
    
         
            +
                    #  # comes back up (gets reconnedted), normal processing
         
     | 
| 
      
 300 
     | 
    
         
            +
                    #  # (without the 100ms wait) should happen.
         
     | 
| 
      
 301 
     | 
    
         
            +
                     sleep(0.1) if socket_likely_shutdown
         
     | 
| 
      
 302 
     | 
    
         
            +
                    end
         
     | 
| 
      
 303 
     | 
    
         
            +
                  end
         
     | 
| 
      
 304 
     | 
    
         
            +
                end
         
     | 
| 
      
 305 
     | 
    
         
            +
             
     | 
| 
      
 306 
     | 
    
         
            +
                ### Sending Outgoing messages to IB
         
     | 
| 
      
 307 
     | 
    
         
            +
             
     | 
| 
      
 308 
     | 
    
         
            +
                # Send an outgoing message.
         
     | 
| 
      
 309 
     | 
    
         
            +
            		# returns the used request_id if appropiate, otherwise "true"
         
     | 
| 
      
 310 
     | 
    
         
            +
                def send_message what, *args
         
     | 
| 
      
 311 
     | 
    
         
            +
                  message =
         
     | 
| 
      
 312 
     | 
    
         
            +
                  case
         
     | 
| 
      
 313 
     | 
    
         
            +
                  when what.is_a?(Messages::Outgoing::AbstractMessage)
         
     | 
| 
      
 314 
     | 
    
         
            +
                    what
         
     | 
| 
      
 315 
     | 
    
         
            +
                  when what.is_a?(Class) && what < Messages::Outgoing::AbstractMessage
         
     | 
| 
      
 316 
     | 
    
         
            +
                    what.new *args
         
     | 
| 
      
 317 
     | 
    
         
            +
                  when what.is_a?(Symbol)
         
     | 
| 
      
 318 
     | 
    
         
            +
                    Messages::Outgoing.const_get(what).new *args
         
     | 
| 
      
 319 
     | 
    
         
            +
                  else
         
     | 
| 
      
 320 
     | 
    
         
            +
                    error "Only able to send outgoing IB messages", :args
         
     | 
| 
      
 321 
     | 
    
         
            +
                  end
         
     | 
| 
      
 322 
     | 
    
         
            +
                  error   "Not able to send messages, IB not connected!"  unless connected?
         
     | 
| 
      
 323 
     | 
    
         
            +
            			begin
         
     | 
| 
      
 324 
     | 
    
         
            +
                  @message_lock.synchronize do
         
     | 
| 
      
 325 
     | 
    
         
            +
                  message.send_to socket
         
     | 
| 
      
 326 
     | 
    
         
            +
                  end
         
     | 
| 
      
 327 
     | 
    
         
            +
            			rescue Errno::EPIPE
         
     | 
| 
      
 328 
     | 
    
         
            +
            				logger.error{ "Broken Pipe, trying to reconnect"  }
         
     | 
| 
      
 329 
     | 
    
         
            +
            				disconnect
         
     | 
| 
      
 330 
     | 
    
         
            +
            				connect
         
     | 
| 
      
 331 
     | 
    
         
            +
            				retry
         
     | 
| 
      
 332 
     | 
    
         
            +
            			end
         
     | 
| 
      
 333 
     | 
    
         
            +
            			## return the transmitted message
         
     | 
| 
      
 334 
     | 
    
         
            +
            		  message.data[:request_id].presence || true
         
     | 
| 
      
 335 
     | 
    
         
            +
                end
         
     | 
| 
      
 336 
     | 
    
         
            +
             
     | 
| 
      
 337 
     | 
    
         
            +
                alias dispatch send_message # Legacy alias
         
     | 
| 
      
 338 
     | 
    
         
            +
             
     | 
| 
      
 339 
     | 
    
         
            +
                # Place Order (convenience wrapper for send_message :PlaceOrder).
         
     | 
| 
      
 340 
     | 
    
         
            +
                # Assigns client_id and order_id fields to placed order. Returns assigned order_id.
         
     | 
| 
      
 341 
     | 
    
         
            +
                def place_order order, contract
         
     | 
| 
      
 342 
     | 
    
         
            +
                 # order.place contract, self  ## old
         
     | 
| 
      
 343 
     | 
    
         
            +
                  error "Unable to place order, next_local_id not known" unless next_local_id
         
     | 
| 
      
 344 
     | 
    
         
            +
            			error "local_id present. Order is already placed.  Do might use  modify insteed"  unless  order.local_id.nil?
         
     | 
| 
      
 345 
     | 
    
         
            +
                  order.client_id = client_id
         
     | 
| 
      
 346 
     | 
    
         
            +
                  order.local_id = next_local_id
         
     | 
| 
      
 347 
     | 
    
         
            +
                  self.next_local_id += 1
         
     | 
| 
      
 348 
     | 
    
         
            +
                  order.placed_at = Time.now
         
     | 
| 
      
 349 
     | 
    
         
            +
            			modify_order order, contract
         
     | 
| 
      
 350 
     | 
    
         
            +
                end
         
     | 
| 
      
 351 
     | 
    
         
            +
             
     | 
| 
      
 352 
     | 
    
         
            +
                # Modify Order (convenience wrapper for send_message :PlaceOrder). Returns order_id.
         
     | 
| 
      
 353 
     | 
    
         
            +
                def modify_order order, contract
         
     | 
| 
      
 354 
     | 
    
         
            +
             #      order.modify contract, self    ## old
         
     | 
| 
      
 355 
     | 
    
         
            +
            			error "Unable to modify order; local_id not specified" if order.local_id.nil?
         
     | 
| 
      
 356 
     | 
    
         
            +
                  order.modified_at = Time.now
         
     | 
| 
      
 357 
     | 
    
         
            +
                  send_message :PlaceOrder,
         
     | 
| 
      
 358 
     | 
    
         
            +
                    :order => order,
         
     | 
| 
      
 359 
     | 
    
         
            +
                    :contract => contract,
         
     | 
| 
      
 360 
     | 
    
         
            +
                    :local_id => order.local_id
         
     | 
| 
      
 361 
     | 
    
         
            +
                  order.local_id  # return value
         
     | 
| 
      
 362 
     | 
    
         
            +
                end
         
     | 
| 
      
 363 
     | 
    
         
            +
             
     | 
| 
      
 364 
     | 
    
         
            +
                # Cancel Orders by their local ids (convenience wrapper for send_message :CancelOrder).
         
     | 
| 
      
 365 
     | 
    
         
            +
                def cancel_order *local_ids
         
     | 
| 
      
 366 
     | 
    
         
            +
                  local_ids.each do |local_id|
         
     | 
| 
      
 367 
     | 
    
         
            +
                    send_message :CancelOrder, :local_id => local_id.to_i
         
     | 
| 
      
 368 
     | 
    
         
            +
                  end
         
     | 
| 
      
 369 
     | 
    
         
            +
                end
         
     | 
| 
      
 370 
     | 
    
         
            +
             
     | 
| 
      
 371 
     | 
    
         
            +
                # Start reader thread that continuously reads messages from @socket in background.
         
     | 
| 
      
 372 
     | 
    
         
            +
                # If you don't start reader, you should manually poll @socket for messages
         
     | 
| 
      
 373 
     | 
    
         
            +
                # or use #process_messages(msec) API.
         
     | 
| 
      
 374 
     | 
    
         
            +
                def start_reader
         
     | 
| 
      
 375 
     | 
    
         
            +
            			return(@reader_thread) if @reader_running
         
     | 
| 
      
 376 
     | 
    
         
            +
            			if connected?
         
     | 
| 
      
 377 
     | 
    
         
            +
            				Thread.abort_on_exception = true
         
     | 
| 
      
 378 
     | 
    
         
            +
            				@reader_running = true
         
     | 
| 
      
 379 
     | 
    
         
            +
            				@reader_thread = Thread.new { process_messages while @reader_running }
         
     | 
| 
      
 380 
     | 
    
         
            +
            			else
         
     | 
| 
      
 381 
     | 
    
         
            +
            				logger.fatal {"Could not start reader, not connected!"}
         
     | 
| 
      
 382 
     | 
    
         
            +
            				nil  # return_value
         
     | 
| 
      
 383 
     | 
    
         
            +
            			end
         
     | 
| 
      
 384 
     | 
    
         
            +
                end
         
     | 
| 
      
 385 
     | 
    
         
            +
             
     | 
| 
      
 386 
     | 
    
         
            +
            		protected
         
     | 
| 
      
 387 
     | 
    
         
            +
            		# Message subscribers. Key is the message class to listen for.
         
     | 
| 
      
 388 
     | 
    
         
            +
            		# Value is a Hash of subscriber Procs, keyed by their subscription id.
         
     | 
| 
      
 389 
     | 
    
         
            +
            		# All subscriber Procs will be called with the message instance
         
     | 
| 
      
 390 
     | 
    
         
            +
            		# as an argument when a message of that type is received.
         
     | 
| 
      
 391 
     | 
    
         
            +
            		def subscribers
         
     | 
| 
      
 392 
     | 
    
         
            +
            			@subscribers ||= Hash.new { |hash, subs| hash[subs] = Hash.new }
         
     | 
| 
      
 393 
     | 
    
         
            +
            		end
         
     | 
| 
      
 394 
     | 
    
         
            +
             
     | 
| 
      
 395 
     | 
    
         
            +
            		# Process single incoming message (blocking!)
         
     | 
| 
      
 396 
     | 
    
         
            +
            		def process_message
         
     | 
| 
      
 397 
     | 
    
         
            +
            			logger.progname='IB::Connection#process_message' if logger.is_a?(Logger)
         
     | 
| 
      
 398 
     | 
    
         
            +
             
     | 
| 
      
 399 
     | 
    
         
            +
            			socket.decode_message(  socket.recieve_messages ) do | the_decoded_message |
         
     | 
| 
      
 400 
     | 
    
         
            +
            				#	puts "THE deCODED MESSAGE #{ the_decoded_message.inspect}"
         
     | 
| 
      
 401 
     | 
    
         
            +
            				msg_id = the_decoded_message.shift.to_i
         
     | 
| 
      
 402 
     | 
    
         
            +
             
     | 
| 
      
 403 
     | 
    
         
            +
            				# Debug:
         
     | 
| 
      
 404 
     | 
    
         
            +
            				logger.debug { "Got message #{msg_id} (#{Messages::Incoming::Classes[msg_id]})"}
         
     | 
| 
      
 405 
     | 
    
         
            +
             
     | 
| 
      
 406 
     | 
    
         
            +
            				# Create new instance of the appropriate message type,
         
     | 
| 
      
 407 
     | 
    
         
            +
            				# and have it read the message from socket.
         
     | 
| 
      
 408 
     | 
    
         
            +
            				# NB: Failure here usually means unsupported message type received
         
     | 
| 
      
 409 
     | 
    
         
            +
            				logger.error { "Got unsupported message #{msg_id}" } unless Messages::Incoming::Classes[msg_id]
         
     | 
| 
      
 410 
     | 
    
         
            +
            				error "Something strange happened - Reader has to be restarted" , :reader if msg_id.to_i.zero?
         
     | 
| 
      
 411 
     | 
    
         
            +
            				msg = Messages::Incoming::Classes[msg_id].new(the_decoded_message)
         
     | 
| 
      
 412 
     | 
    
         
            +
             
     | 
| 
      
 413 
     | 
    
         
            +
            				# Deliver message to all registered subscribers, alert if no subscribers
         
     | 
| 
      
 414 
     | 
    
         
            +
            				# Ruby 2.0 and above: Hashes are ordered.
         
     | 
| 
      
 415 
     | 
    
         
            +
            				# Thus first declared subscribers of  a class are executed first
         
     | 
| 
      
 416 
     | 
    
         
            +
            				@subscribe_lock.synchronize do
         
     | 
| 
      
 417 
     | 
    
         
            +
            					subscribers[msg.class].each { |_, subscriber| subscriber.call(msg) }
         
     | 
| 
      
 418 
     | 
    
         
            +
            				end
         
     | 
| 
      
 419 
     | 
    
         
            +
            				logger.warn { "No subscribers for message #{msg.class}!" } if subscribers[msg.class].empty?
         
     | 
| 
      
 420 
     | 
    
         
            +
             
     | 
| 
      
 421 
     | 
    
         
            +
            				# Collect all received messages into a @received Hash
         
     | 
| 
      
 422 
     | 
    
         
            +
            				if @received
         
     | 
| 
      
 423 
     | 
    
         
            +
            					@receive_lock.synchronize do
         
     | 
| 
      
 424 
     | 
    
         
            +
            						received[msg.message_type] << msg
         
     | 
| 
      
 425 
     | 
    
         
            +
            					end
         
     | 
| 
      
 426 
     | 
    
         
            +
            				end
         
     | 
| 
      
 427 
     | 
    
         
            +
            			end
         
     | 
| 
      
 428 
     | 
    
         
            +
            		end
         
     | 
| 
      
 429 
     | 
    
         
            +
             
     | 
| 
      
 430 
     | 
    
         
            +
            		def random_id
         
     | 
| 
      
 431 
     | 
    
         
            +
            			rand 999999
         
     | 
| 
      
 432 
     | 
    
         
            +
            		end
         
     | 
| 
      
 433 
     | 
    
         
            +
             
     | 
| 
      
 434 
     | 
    
         
            +
            		# Check if all given conditions are satisfied
         
     | 
| 
      
 435 
     | 
    
         
            +
            		def satisfied? *conditions
         
     | 
| 
      
 436 
     | 
    
         
            +
            			!conditions.empty? &&
         
     | 
| 
      
 437 
     | 
    
         
            +
            				conditions.inject(true) do |result, condition|
         
     | 
| 
      
 438 
     | 
    
         
            +
            				result && if condition.is_a?(Symbol)
         
     | 
| 
      
 439 
     | 
    
         
            +
            				received?(condition)
         
     | 
| 
      
 440 
     | 
    
         
            +
            			elsif condition.is_a?(Array)
         
     | 
| 
      
 441 
     | 
    
         
            +
            				received?(*condition)
         
     | 
| 
      
 442 
     | 
    
         
            +
            			elsif condition.respond_to?(:call)
         
     | 
| 
      
 443 
     | 
    
         
            +
            				condition.call
         
     | 
| 
      
 444 
     | 
    
         
            +
            			else
         
     | 
| 
      
 445 
     | 
    
         
            +
            				logger.error { "Unknown wait condition #{condition}" }
         
     | 
| 
      
 446 
     | 
    
         
            +
            			end
         
     | 
| 
      
 447 
     | 
    
         
            +
            		end
         
     | 
| 
      
 448 
     | 
    
         
            +
            	end
         
     | 
| 
      
 449 
     | 
    
         
            +
            end # class Connection
         
     | 
| 
      
 450 
     | 
    
         
            +
            end # module IB
         
     | 
    
        data/lib/ib/constants.rb
    ADDED
    
    | 
         @@ -0,0 +1,393 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module IB
         
     | 
| 
      
 2 
     | 
    
         
            +
              ### Widely used TWS constants:
         
     | 
| 
      
 3 
     | 
    
         
            +
             
     | 
| 
      
 4 
     | 
    
         
            +
              EOL = "\0"
         
     | 
| 
      
 5 
     | 
    
         
            +
              # TWS_MAX is TWSMAX (transmitted from the TWS) minus the first digit (1)
         
     | 
| 
      
 6 
     | 
    
         
            +
              # Anything bigger then TWS_MAX is considered as nil (in read_decimal @ messages/incomming/abstract_message.rb)
         
     | 
| 
      
 7 
     | 
    
         
            +
              TWS_MAX = 79769313486231570000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000.0 
         
     | 
| 
      
 8 
     | 
    
         
            +
              TWSMAX = 179769313486231570000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000.0 
         
     | 
| 
      
 9 
     | 
    
         
            +
              
         
     | 
| 
      
 10 
     | 
    
         
            +
             
     | 
| 
      
 11 
     | 
    
         
            +
              # Enumeration of bar size types for convenience.
         
     | 
| 
      
 12 
     | 
    
         
            +
              # Bar sizes less than 30 seconds do not work for some securities.
         
     | 
| 
      
 13 
     | 
    
         
            +
              BAR_SIZES = {'1 sec' => :sec1,
         
     | 
| 
      
 14 
     | 
    
         
            +
                           '5 secs' => :sec5,
         
     | 
| 
      
 15 
     | 
    
         
            +
                           '15 secs' =>:sec15,
         
     | 
| 
      
 16 
     | 
    
         
            +
                           '30 secs' =>:sec30,
         
     | 
| 
      
 17 
     | 
    
         
            +
                           '1 min' => :min1,
         
     | 
| 
      
 18 
     | 
    
         
            +
                           '2 mins' => :min2,
         
     | 
| 
      
 19 
     | 
    
         
            +
                           '3 mins' => :min3,
         
     | 
| 
      
 20 
     | 
    
         
            +
                           '5 mins' => :min5,
         
     | 
| 
      
 21 
     | 
    
         
            +
                           '15 mins' =>:min15,
         
     | 
| 
      
 22 
     | 
    
         
            +
                           '30 mins' =>:min30,
         
     | 
| 
      
 23 
     | 
    
         
            +
                           '1 hour' =>:hour1,
         
     | 
| 
      
 24 
     | 
    
         
            +
                           '1 day' => :day1
         
     | 
| 
      
 25 
     | 
    
         
            +
              }.freeze
         
     | 
| 
      
 26 
     | 
    
         
            +
             
     | 
| 
      
 27 
     | 
    
         
            +
              # Enumeration of data types.
         
     | 
| 
      
 28 
     | 
    
         
            +
              # Determines the nature of data being extracted. Valid values:
         
     | 
| 
      
 29 
     | 
    
         
            +
              DATA_TYPES = {'TRADES' => :trades,
         
     | 
| 
      
 30 
     | 
    
         
            +
                            'MIDPOINT' => :midpoint,
         
     | 
| 
      
 31 
     | 
    
         
            +
                            'BID' => :bid,
         
     | 
| 
      
 32 
     | 
    
         
            +
                            'ASK' => :ask,
         
     | 
| 
      
 33 
     | 
    
         
            +
                            'BID_ASK' => :bid_ask,
         
     | 
| 
      
 34 
     | 
    
         
            +
                            'HISTORICAL_VOLATILITY' => :historical_volatility,
         
     | 
| 
      
 35 
     | 
    
         
            +
                            'OPTION_IMPLIED_VOLATILITY' => :option_implied_volatility,
         
     | 
| 
      
 36 
     | 
    
         
            +
                            'OPTION_VOLUME' => :option_volume,
         
     | 
| 
      
 37 
     | 
    
         
            +
                            'OPTION_OPEN_INTEREST' => :option_open_interest
         
     | 
| 
      
 38 
     | 
    
         
            +
              }.freeze
         
     | 
| 
      
 39 
     | 
    
         
            +
             
     | 
| 
      
 40 
     | 
    
         
            +
              ### These values are typically received from TWS in incoming messages
         
     | 
| 
      
 41 
     | 
    
         
            +
             
     | 
| 
      
 42 
     | 
    
         
            +
              # Tick types as received in TickPrice and TickSize messages (enumeration)
         
     | 
| 
      
 43 
     | 
    
         
            +
              TICK_TYPES = {
         
     | 
| 
      
 44 
     | 
    
         
            +
                  # int id => :Description #  Corresponding API Event/Function/Method
         
     | 
| 
      
 45 
     | 
    
         
            +
                  0 => :bid_size, #               tickSize()
         
     | 
| 
      
 46 
     | 
    
         
            +
                  1 => :bid_price, #              tickPrice()
         
     | 
| 
      
 47 
     | 
    
         
            +
                  2 => :ask_price, #              tickPrice()
         
     | 
| 
      
 48 
     | 
    
         
            +
                  3 => :ask_size, #               tickSize()
         
     | 
| 
      
 49 
     | 
    
         
            +
                  4 => :last_price, #             tickPrice()
         
     | 
| 
      
 50 
     | 
    
         
            +
                  5 => :last_size, #              tickSize()
         
     | 
| 
      
 51 
     | 
    
         
            +
                  6 => :high, #                   tickPrice()
         
     | 
| 
      
 52 
     | 
    
         
            +
                  7 => :low, #                    tickPrice()
         
     | 
| 
      
 53 
     | 
    
         
            +
                  8 => :volume, #                 tickSize()
         
     | 
| 
      
 54 
     | 
    
         
            +
                  9 => :close_price, #            tickPrice()
         
     | 
| 
      
 55 
     | 
    
         
            +
                  10 => :bid_option, #            tickOptionComputation() See Note 1 below
         
     | 
| 
      
 56 
     | 
    
         
            +
                  11 => :ask_option, #            tickOptionComputation() See => :Note 1 below
         
     | 
| 
      
 57 
     | 
    
         
            +
                  12 => :last_option, #           tickOptionComputation()  See Note 1 below
         
     | 
| 
      
 58 
     | 
    
         
            +
                  13 => :model_option, #          tickOptionComputation() See Note 1 below
         
     | 
| 
      
 59 
     | 
    
         
            +
                  14 => :open_tick, #             tickPrice()
         
     | 
| 
      
 60 
     | 
    
         
            +
                  15 => :low_13_week, #           tickPrice()
         
     | 
| 
      
 61 
     | 
    
         
            +
                  16 => :high_13_week, #          tickPrice()
         
     | 
| 
      
 62 
     | 
    
         
            +
                  17 => :low_26_week, #           tickPrice()
         
     | 
| 
      
 63 
     | 
    
         
            +
                  18 => :high_26_week, #          tickPrice()
         
     | 
| 
      
 64 
     | 
    
         
            +
                  19 => :low_52_week, #           tickPrice()
         
     | 
| 
      
 65 
     | 
    
         
            +
                  20 => :high_52_week, #          tickPrice()
         
     | 
| 
      
 66 
     | 
    
         
            +
                  21 => :avg_volume, #            tickSize()
         
     | 
| 
      
 67 
     | 
    
         
            +
                  22 => :open_interest, #         tickSize()
         
     | 
| 
      
 68 
     | 
    
         
            +
                  23 => :option_historical_vol, # tickGeneric()
         
     | 
| 
      
 69 
     | 
    
         
            +
                  24 => :option_implied_vol, #    tickGeneric()
         
     | 
| 
      
 70 
     | 
    
         
            +
                  25 => :option_bid_exch, #   not USED
         
     | 
| 
      
 71 
     | 
    
         
            +
                  26 => :option_ask_exch, #   not USED
         
     | 
| 
      
 72 
     | 
    
         
            +
                  27 => :option_call_open_interest, # tickSize()
         
     | 
| 
      
 73 
     | 
    
         
            +
                  28 => :option_put_open_interest, #  tickSize()
         
     | 
| 
      
 74 
     | 
    
         
            +
                  29 => :option_call_volume, #        tickSize()
         
     | 
| 
      
 75 
     | 
    
         
            +
                  30 => :option_put_volume, #         tickSize()
         
     | 
| 
      
 76 
     | 
    
         
            +
                  31 => :index_future_premium, #      tickGeneric()
         
     | 
| 
      
 77 
     | 
    
         
            +
                  32 => :bid_exch, #                  tickString()
         
     | 
| 
      
 78 
     | 
    
         
            +
                  33 => :ask_exch, #                  tickString()
         
     | 
| 
      
 79 
     | 
    
         
            +
                  34 => :auction_volume, #    not USED
         
     | 
| 
      
 80 
     | 
    
         
            +
                  35 => :auction_price, #     not USED
         
     | 
| 
      
 81 
     | 
    
         
            +
                  36 => :auction_imbalance, # not USED
         
     | 
| 
      
 82 
     | 
    
         
            +
                  37 => :mark_price, #              tickPrice()
         
     | 
| 
      
 83 
     | 
    
         
            +
                  38 => :bid_efp_computation, #     tickEFP()
         
     | 
| 
      
 84 
     | 
    
         
            +
                  39 => :ask_efp_computation, #     tickEFP()
         
     | 
| 
      
 85 
     | 
    
         
            +
                  40 => :last_efp_computation, #    tickEFP()
         
     | 
| 
      
 86 
     | 
    
         
            +
                  41 => :open_efp_computation, #    tickEFP()
         
     | 
| 
      
 87 
     | 
    
         
            +
                  42 => :high_efp_computation, #    tickEFP()
         
     | 
| 
      
 88 
     | 
    
         
            +
                  43 => :low_efp_computation, #     tickEFP()
         
     | 
| 
      
 89 
     | 
    
         
            +
                  44 => :close_efp_computation, #   tickEFP()
         
     | 
| 
      
 90 
     | 
    
         
            +
                  45 => :last_timestamp, #          tickString()
         
     | 
| 
      
 91 
     | 
    
         
            +
                  46 => :shortable, #               tickGeneric()
         
     | 
| 
      
 92 
     | 
    
         
            +
                  47 => :fundamental_ratios, #      tickString()
         
     | 
| 
      
 93 
     | 
    
         
            +
                  48 => :rt_volume, #               tickGeneric()
         
     | 
| 
      
 94 
     | 
    
         
            +
                  49 => :halted, #           see Note 2 below.
         
     | 
| 
      
 95 
     | 
    
         
            +
                  50 => :bid_yield, #               tickPrice() See Note 3 below
         
     | 
| 
      
 96 
     | 
    
         
            +
                  51 => :ask_yield, #               tickPrice() See Note 3 below
         
     | 
| 
      
 97 
     | 
    
         
            +
                  52 => :last_yield, #              tickPrice() See Note 3 below
         
     | 
| 
      
 98 
     | 
    
         
            +
                  53 => :cust_option_computation, # tickOptionComputation()
         
     | 
| 
      
 99 
     | 
    
         
            +
                  54 => :trade_count, #             tickGeneric()
         
     | 
| 
      
 100 
     | 
    
         
            +
                  55 => :trade_rate, #              tickGeneric()
         
     | 
| 
      
 101 
     | 
    
         
            +
                  56 => :volume_rate, #             tickGeneric()
         
     | 
| 
      
 102 
     | 
    
         
            +
                  57 => :last_rth_trade, #
         
     | 
| 
      
 103 
     | 
    
         
            +
            		  58 => :rt_historical_vol,
         
     | 
| 
      
 104 
     | 
    
         
            +
                  59 => :ib_dividends,
         
     | 
| 
      
 105 
     | 
    
         
            +
                  60 => :bond_factor_multiplier,
         
     | 
| 
      
 106 
     | 
    
         
            +
                  61 => :regulatory_imbalance,
         
     | 
| 
      
 107 
     | 
    
         
            +
                  62 => :news_tick,
         
     | 
| 
      
 108 
     | 
    
         
            +
                  63 => :short_term_volume_3_min,
         
     | 
| 
      
 109 
     | 
    
         
            +
                  64 => :short_term_volume_5_min,
         
     | 
| 
      
 110 
     | 
    
         
            +
                  65 => :short_term_volume_10_min,
         
     | 
| 
      
 111 
     | 
    
         
            +
                  66 => :delayed_bid,
         
     | 
| 
      
 112 
     | 
    
         
            +
                  67 => :delayed_ask,
         
     | 
| 
      
 113 
     | 
    
         
            +
                  68 => :delayed_last,
         
     | 
| 
      
 114 
     | 
    
         
            +
                  69 => :delayed_bid_size,
         
     | 
| 
      
 115 
     | 
    
         
            +
                  70 => :delayed_ask_size,
         
     | 
| 
      
 116 
     | 
    
         
            +
                  71 => :delayed_last_size,
         
     | 
| 
      
 117 
     | 
    
         
            +
                  72 => :delayed_high,
         
     | 
| 
      
 118 
     | 
    
         
            +
                  73 => :delayed_low,
         
     | 
| 
      
 119 
     | 
    
         
            +
                  74 => :delayed_volume,
         
     | 
| 
      
 120 
     | 
    
         
            +
                  75 => :delayed_close,
         
     | 
| 
      
 121 
     | 
    
         
            +
                  76 => :delayed_open,
         
     | 
| 
      
 122 
     | 
    
         
            +
                  77 => :rt_trd_volume,
         
     | 
| 
      
 123 
     | 
    
         
            +
                  78 => :creditman_mark_price,
         
     | 
| 
      
 124 
     | 
    
         
            +
                  79 => :creditman_slow_mark_price,
         
     | 
| 
      
 125 
     | 
    
         
            +
                  80 => :delayed_bid_option,
         
     | 
| 
      
 126 
     | 
    
         
            +
                  81 => :delayed_ask_option,
         
     | 
| 
      
 127 
     | 
    
         
            +
                  82 => :delayed_last_option,
         
     | 
| 
      
 128 
     | 
    
         
            +
                  83 => :delayed_model_option,
         
     | 
| 
      
 129 
     | 
    
         
            +
                  84 => :last_exch,
         
     | 
| 
      
 130 
     | 
    
         
            +
                  85 => :last_reg_time,
         
     | 
| 
      
 131 
     | 
    
         
            +
                  86 => :futures_open_interest,
         
     | 
| 
      
 132 
     | 
    
         
            +
                  87 => :avg_opt_volume,
         
     | 
| 
      
 133 
     | 
    
         
            +
                  88 => :not_set,
         
     | 
| 
      
 134 
     | 
    
         
            +
            			105 => :average_option_volume   #(for Stocks) tickGeneric()
         
     | 
| 
      
 135 
     | 
    
         
            +
             
     | 
| 
      
 136 
     | 
    
         
            +
            	
         
     | 
| 
      
 137 
     | 
    
         
            +
                  #   Note 1: Tick types BID_OPTION, ASK_OPTION, LAST_OPTION, and MODEL_OPTION return
         
     | 
| 
      
 138 
     | 
    
         
            +
                  #           all Greeks (delta, gamma, vega, theta), the underlying price and the
         
     | 
| 
      
 139 
     | 
    
         
            +
                  #           stock and option reference price when requested.
         
     | 
| 
      
 140 
     | 
    
         
            +
                  #           MODEL_OPTION also returns model implied volatility.
         
     | 
| 
      
 141 
     | 
    
         
            +
                  #   Note 2: When trading is halted for a contract, TWS receives a special tick:
         
     | 
| 
      
 142 
     | 
    
         
            +
                  #           haltedLast=1. When trading is resumed, TWS receives haltedLast=0.
         
     | 
| 
      
 143 
     | 
    
         
            +
                  #           A tick type, HALTED, tick ID= 49, is now available in regular market
         
     | 
| 
      
 144 
     | 
    
         
            +
                  #           data via the API to indicate this halted state. Possible values for
         
     | 
| 
      
 145 
     | 
    
         
            +
                  #           this new tick type are: 0 = Not halted, 1 = Halted.
         
     | 
| 
      
 146 
     | 
    
         
            +
                  #   Note 3: Applies to bond contracts only.
         
     | 
| 
      
 147 
     | 
    
         
            +
              }
         
     | 
| 
      
 148 
     | 
    
         
            +
             
     | 
| 
      
 149 
     | 
    
         
            +
              # Financial Advisor types (FaMsgTypeName)
         
     | 
| 
      
 150 
     | 
    
         
            +
              FA_TYPES = {
         
     | 
| 
      
 151 
     | 
    
         
            +
                  1 => :groups,
         
     | 
| 
      
 152 
     | 
    
         
            +
                  2 => :profiles,
         
     | 
| 
      
 153 
     | 
    
         
            +
                  3 => :aliases}.freeze
         
     | 
| 
      
 154 
     | 
    
         
            +
             
     | 
| 
      
 155 
     | 
    
         
            +
              # Received in new MarketDataType (58 incoming) message
         
     | 
| 
      
 156 
     | 
    
         
            +
              MARKET_DATA_TYPES = {
         
     | 
| 
      
 157 
     | 
    
         
            +
                  0 => :unknown,
         
     | 
| 
      
 158 
     | 
    
         
            +
                  1 => :real_time,
         
     | 
| 
      
 159 
     | 
    
         
            +
                  2 => :frozen,
         
     | 
| 
      
 160 
     | 
    
         
            +
            			3 => :delayed,
         
     | 
| 
      
 161 
     | 
    
         
            +
            			4 => :frozen_delayed }.freeze
         
     | 
| 
      
 162 
     | 
    
         
            +
             
     | 
| 
      
 163 
     | 
    
         
            +
              # Market depth messages contain these "operation" codes to tell you what to do with the data.
         
     | 
| 
      
 164 
     | 
    
         
            +
              # See also http://www.interactivebrokers.com/php/apiUsersGuide/apiguide/java/updatemktdepth.htm
         
     | 
| 
      
 165 
     | 
    
         
            +
              MARKET_DEPTH_OPERATIONS = {
         
     | 
| 
      
 166 
     | 
    
         
            +
                  0 => :insert, # New order, insert into the row identified by :position
         
     | 
| 
      
 167 
     | 
    
         
            +
                  1 => :update, # Update the existing order at the row identified by :position
         
     | 
| 
      
 168 
     | 
    
         
            +
                  2 => :delete # Delete the existing order at the row identified by :position
         
     | 
| 
      
 169 
     | 
    
         
            +
              }.freeze
         
     | 
| 
      
 170 
     | 
    
         
            +
             
     | 
| 
      
 171 
     | 
    
         
            +
              MARKET_DEPTH_SIDES = {
         
     | 
| 
      
 172 
     | 
    
         
            +
                  0 => :ask,
         
     | 
| 
      
 173 
     | 
    
         
            +
                  1 => :bid
         
     | 
| 
      
 174 
     | 
    
         
            +
              }.freeze
         
     | 
| 
      
 175 
     | 
    
         
            +
             
     | 
| 
      
 176 
     | 
    
         
            +
              ORDER_TYPES =
         
     | 
| 
      
 177 
     | 
    
         
            +
                  {'LMT' => :limit, #                  Limit Order
         
     | 
| 
      
 178 
     | 
    
         
            +
                   'LIT' => :limit_if_touched, #       Limit if Touched
         
     | 
| 
      
 179 
     | 
    
         
            +
                   'LOC' => :limit_on_close, #         Limit-on-Close      LMTCLS ?
         
     | 
| 
      
 180 
     | 
    
         
            +
                   'LOO' => :limit_on_open, #          Limit-on-Open
         
     | 
| 
      
 181 
     | 
    
         
            +
                   'MKT' => :market, #                 Market
         
     | 
| 
      
 182 
     | 
    
         
            +
                   'MIT' => :market_if_touched, #      Market-if-Touched
         
     | 
| 
      
 183 
     | 
    
         
            +
                   'MOC' => :market_on_close, #        Market-on-Close     MKTCLSL ?
         
     | 
| 
      
 184 
     | 
    
         
            +
                   'MOO' => :market_on_open, #         Market-on-Open
         
     | 
| 
      
 185 
     | 
    
         
            +
                   'MTL' => :market_to_limit, #        Market-to-Limit
         
     | 
| 
      
 186 
     | 
    
         
            +
                   'MKT PRT' => :market_protected, #   Market with Protection
         
     | 
| 
      
 187 
     | 
    
         
            +
                   'QUOTE' => :request_for_quote, #    Request for Quote
         
     | 
| 
      
 188 
     | 
    
         
            +
                   'STP' => :stop, #                   Stop
         
     | 
| 
      
 189 
     | 
    
         
            +
                   'STP LMT' => :stop_limit, #         Stop Limit
         
     | 
| 
      
 190 
     | 
    
         
            +
                   'STP PRT' => :stop_protected, #	   Stop with Protection
         
     | 
| 
      
 191 
     | 
    
         
            +
                   'TRAIL' => :trailing_stop, #        Trailing Stop
         
     | 
| 
      
 192 
     | 
    
         
            +
                   'TRAIL LIMIT' => :trailing_limit, # Trailing Stop Limit
         
     | 
| 
      
 193 
     | 
    
         
            +
                   'TRAIL LIT' => :trailing_limit_if_touched, #  Trailing Limit if Touched
         
     | 
| 
      
 194 
     | 
    
         
            +
                   'TRAIL MIT' => :trailing_market_if_touched, # Trailing Market If Touched
         
     | 
| 
      
 195 
     | 
    
         
            +
                   'REL' => :relative, #               Relative
         
     | 
| 
      
 196 
     | 
    
         
            +
                   'BOX TOP' => :box_top, #            Box Top
         
     | 
| 
      
 197 
     | 
    
         
            +
                   'PEG MKT' => :pegged_to_market, # Pegged-to-Market
         
     | 
| 
      
 198 
     | 
    
         
            +
                   'PEG STK' => :pegged_to_market, #   Pegged-to-Stock
         
     | 
| 
      
 199 
     | 
    
         
            +
                   'PEG MID' => :pegged_to_midpoint, # Pegged-to-Midpoint
         
     | 
| 
      
 200 
     | 
    
         
            +
                   'PEG BENCH' => :pegged_to_benchmark, # Pegged-to-Benmchmark # Vers. 102
         
     | 
| 
      
 201 
     | 
    
         
            +
                   'VWAP' => :vwap, #                  VWAP-Guaranted
         
     | 
| 
      
 202 
     | 
    
         
            +
                   'OCA' => :one_cancels_all, #        One-Cancels-All
         
     | 
| 
      
 203 
     | 
    
         
            +
                   'VOL' => :volatility, #             Volatility
         
     | 
| 
      
 204 
     | 
    
         
            +
                   'SCALE' => :scale, #                Scale
         
     | 
| 
      
 205 
     | 
    
         
            +
                   'NONE' => :none, # Used to indicate no hedge in :delta_neutral_order_type
         
     | 
| 
      
 206 
     | 
    
         
            +
                   'None' => :none, # Used to indicate no hedge in :delta_neutral_order_type
         
     | 
| 
      
 207 
     | 
    
         
            +
                  }.freeze
         
     | 
| 
      
 208 
     | 
    
         
            +
              # Valid security types (sec_type attribute of IB::Contract)
         
     | 
| 
      
 209 
     | 
    
         
            +
              SECURITY_TYPES =
         
     | 
| 
      
 210 
     | 
    
         
            +
                {	'BAG' =>  :bag,
         
     | 
| 
      
 211 
     | 
    
         
            +
                   'BOND' =>  :bond,
         
     | 
| 
      
 212 
     | 
    
         
            +
                   'CASH' =>  :forex,
         
     | 
| 
      
 213 
     | 
    
         
            +
                   'CMDTY'=>  :commodity,
         
     | 
| 
      
 214 
     | 
    
         
            +
                   'CFD'  =>  :cfd,
         
     | 
| 
      
 215 
     | 
    
         
            +
                   'FUT'  =>  :future,
         
     | 
| 
      
 216 
     | 
    
         
            +
                   'CONTFUT' => :continous_future,
         
     | 
| 
      
 217 
     | 
    
         
            +
                   'FUT+CONTFUT' => :all_futures,
         
     | 
| 
      
 218 
     | 
    
         
            +
                   'FOP'  =>  :futures_option,
         
     | 
| 
      
 219 
     | 
    
         
            +
                   'FUND' =>  :fund, # ETF?
         
     | 
| 
      
 220 
     | 
    
         
            +
                   'IND'  =>  :index,
         
     | 
| 
      
 221 
     | 
    
         
            +
                   'NEWS'  => :news,
         
     | 
| 
      
 222 
     | 
    
         
            +
                   'OPT'  =>  :option,
         
     | 
| 
      
 223 
     | 
    
         
            +
                   'IOPT' =>  :dutch_option,
         
     | 
| 
      
 224 
     | 
    
         
            +
                   'STK'  =>  :stock,
         
     | 
| 
      
 225 
     | 
    
         
            +
                   'WAR'  =>  :warrant,
         
     | 
| 
      
 226 
     | 
    
         
            +
            	     'ICU' =>   :icu,
         
     | 
| 
      
 227 
     | 
    
         
            +
            			 'ICS' =>  :ics, 
         
     | 
| 
      
 228 
     | 
    
         
            +
            	     'BILL' =>  :bill,
         
     | 
| 
      
 229 
     | 
    
         
            +
            	     'BSK'  =>  :basket,
         
     | 
| 
      
 230 
     | 
    
         
            +
            			 'FWD' =>   :forward,
         
     | 
| 
      
 231 
     | 
    
         
            +
            			 'FIXED' => :fixed }.freeze
         
     | 
| 
      
 232 
     | 
    
         
            +
             
     | 
| 
      
 233 
     | 
    
         
            +
              # Obtain symbolic value from given property code:
         
     | 
| 
      
 234 
     | 
    
         
            +
              # VALUES[:side]['B'] -> :buy
         
     | 
| 
      
 235 
     | 
    
         
            +
              VALUES = {
         
     | 
| 
      
 236 
     | 
    
         
            +
                  :sec_type => SECURITY_TYPES,
         
     | 
| 
      
 237 
     | 
    
         
            +
                  :order_type => ORDER_TYPES,
         
     | 
| 
      
 238 
     | 
    
         
            +
                  :delta_neutral_order_type => ORDER_TYPES,
         
     | 
| 
      
 239 
     | 
    
         
            +
             
     | 
| 
      
 240 
     | 
    
         
            +
                  :origin => {0 => :customer, 1 => :firm},
         
     | 
| 
      
 241 
     | 
    
         
            +
                  :volatility_type => {1 => :daily, 2 => :annual},
         
     | 
| 
      
 242 
     | 
    
         
            +
                  :reference_price_type => {1 => :average, 2 => :bid_or_ask},
         
     | 
| 
      
 243 
     | 
    
         
            +
             
     | 
| 
      
 244 
     | 
    
         
            +
                  # This property encodes differently for ComboLeg and Order objects,
         
     | 
| 
      
 245 
     | 
    
         
            +
                  # we use ComboLeg codes and transcode for Order codes as needed
         
     | 
| 
      
 246 
     | 
    
         
            +
                  :open_close =>
         
     | 
| 
      
 247 
     | 
    
         
            +
                      {0 => :same, # Default for Legs, same as the parent (combo) security.
         
     | 
| 
      
 248 
     | 
    
         
            +
                       1 => :open, #  Open. For Legs, this value is only used by institutions.
         
     | 
| 
      
 249 
     | 
    
         
            +
                       2 => :close, # Close. For Legs, this value is only used by institutions.
         
     | 
| 
      
 250 
     | 
    
         
            +
                       3 => :unknown}, # WTF
         
     | 
| 
      
 251 
     | 
    
         
            +
             
     | 
| 
      
 252 
     | 
    
         
            +
                  :right =>
         
     | 
| 
      
 253 
     | 
    
         
            +
                      {'' => :none, # Not an option
         
     | 
| 
      
 254 
     | 
    
         
            +
                       'P' => :put,
         
     | 
| 
      
 255 
     | 
    
         
            +
                       'C' => :call},
         
     | 
| 
      
 256 
     | 
    
         
            +
             
     | 
| 
      
 257 
     | 
    
         
            +
                  :side => # AKA action
         
     | 
| 
      
 258 
     | 
    
         
            +
                      {'B' => :buy, # or BOT
         
     | 
| 
      
 259 
     | 
    
         
            +
                       'S' => :sell, # or SLD
         
     | 
| 
      
 260 
     | 
    
         
            +
                       'T' => :short, # short
         
     | 
| 
      
 261 
     | 
    
         
            +
                       'X' => :short_exempt # Short Sale Exempt action. This allows some orders
         
     | 
| 
      
 262 
     | 
    
         
            +
                       # to be exempt from the SEC recent changes to Regulation SHO, which
         
     | 
| 
      
 263 
     | 
    
         
            +
                       # eliminated the old uptick rule and replaced it with a new "circuit breaker"
         
     | 
| 
      
 264 
     | 
    
         
            +
                       # rule, and allows some orders to be exempt from the new rule.
         
     | 
| 
      
 265 
     | 
    
         
            +
                      },
         
     | 
| 
      
 266 
     | 
    
         
            +
             
     | 
| 
      
 267 
     | 
    
         
            +
                  :short_sale_slot =>
         
     | 
| 
      
 268 
     | 
    
         
            +
                      {0 => :default, #      The only valid option for retail customers
         
     | 
| 
      
 269 
     | 
    
         
            +
                       1 => :broker, #       Shares are at your clearing broker, institutions
         
     | 
| 
      
 270 
     | 
    
         
            +
                       2 => :third_party}, # Shares will be delivered from elsewhere, institutions
         
     | 
| 
      
 271 
     | 
    
         
            +
             
     | 
| 
      
 272 
     | 
    
         
            +
                  :oca_type =>
         
     | 
| 
      
 273 
     | 
    
         
            +
                      {0 => :none, # Not a member of OCA group
         
     | 
| 
      
 274 
     | 
    
         
            +
                       1 => :cancel_with_block, # Cancel all remaining orders with block
         
     | 
| 
      
 275 
     | 
    
         
            +
                       2 => :reduce_with_block, # Remaining orders are reduced in size with block
         
     | 
| 
      
 276 
     | 
    
         
            +
                       3 => :reduce_no_block}, # Remaining orders are reduced in size with no block
         
     | 
| 
      
 277 
     | 
    
         
            +
             
     | 
| 
      
 278 
     | 
    
         
            +
                  :auction_strategy =>
         
     | 
| 
      
 279 
     | 
    
         
            +
                      {0 => :none, # Not a BOX order
         
     | 
| 
      
 280 
     | 
    
         
            +
                       1 => :match,
         
     | 
| 
      
 281 
     | 
    
         
            +
                       2 => :improvement,
         
     | 
| 
      
 282 
     | 
    
         
            +
                       3 => :transparent},
         
     | 
| 
      
 283 
     | 
    
         
            +
             
     | 
| 
      
 284 
     | 
    
         
            +
                  :trigger_method =>
         
     | 
| 
      
 285 
     | 
    
         
            +
                      {0 => :default, # "double bid/ask" used for OTC/US options, "last" otherswise.
         
     | 
| 
      
 286 
     | 
    
         
            +
                       1 => :double_bid_ask, # stops are triggered by 2 consecutive bid or ask prices.
         
     | 
| 
      
 287 
     | 
    
         
            +
                       2 => :last, # stops are triggered based on the last price.
         
     | 
| 
      
 288 
     | 
    
         
            +
                       3 => :double_last,
         
     | 
| 
      
 289 
     | 
    
         
            +
                       4 => :bid_ask, # bid >= trigger price for buy orders, ask <= trigger for sell orders
         
     | 
| 
      
 290 
     | 
    
         
            +
                       7 => :last_or_bid_ask, # bid OR last price >= trigger price for buy orders
         
     | 
| 
      
 291 
     | 
    
         
            +
                       8 => :mid_point}, # midpoint >= trigger price for buy orders and the
         
     | 
| 
      
 292 
     | 
    
         
            +
                  #      spread between the bid and ask must be less than 0.1% of the midpoint
         
     | 
| 
      
 293 
     | 
    
         
            +
             
     | 
| 
      
 294 
     | 
    
         
            +
                  :hedge_type =>
         
     | 
| 
      
 295 
     | 
    
         
            +
                      {'D' => :delta, # parent order is an option and the child order is a stock
         
     | 
| 
      
 296 
     | 
    
         
            +
                       'B' => :beta, # offset market risk by entering into a position with
         
     | 
| 
      
 297 
     | 
    
         
            +
                       #               another contract based on the system or user-defined beta
         
     | 
| 
      
 298 
     | 
    
         
            +
                       'F' => :forex, # offset risk with currency different from your base currency
         
     | 
| 
      
 299 
     | 
    
         
            +
                       'P' => :pair}, # trade a mis-valued pair of contracts and provide the
         
     | 
| 
      
 300 
     | 
    
         
            +
                  #                     ratio between the parent and hedging child order
         
     | 
| 
      
 301 
     | 
    
         
            +
             
     | 
| 
      
 302 
     | 
    
         
            +
                  :clearing_intent =>
         
     | 
| 
      
 303 
     | 
    
         
            +
                      {'' => :none,
         
     | 
| 
      
 304 
     | 
    
         
            +
                       'IB' => :ib,
         
     | 
| 
      
 305 
     | 
    
         
            +
                       'AWAY' => :away,
         
     | 
| 
      
 306 
     | 
    
         
            +
                       'PTA' => :post_trade_allocation},
         
     | 
| 
      
 307 
     | 
    
         
            +
             
     | 
| 
      
 308 
     | 
    
         
            +
                  :delta_neutral_clearing_intent =>
         
     | 
| 
      
 309 
     | 
    
         
            +
                      {'' => :none,
         
     | 
| 
      
 310 
     | 
    
         
            +
                       'IB' => :ib,
         
     | 
| 
      
 311 
     | 
    
         
            +
                       'AWAY' => :away,
         
     | 
| 
      
 312 
     | 
    
         
            +
                       'PTA' => :post_trade_allocation},
         
     | 
| 
      
 313 
     | 
    
         
            +
             
     | 
| 
      
 314 
     | 
    
         
            +
                  :tif =>
         
     | 
| 
      
 315 
     | 
    
         
            +
                      {'DAY' => :day,
         
     | 
| 
      
 316 
     | 
    
         
            +
                       'GAT' => :good_after_time,
         
     | 
| 
      
 317 
     | 
    
         
            +
                       'GTD' => :good_till_date,
         
     | 
| 
      
 318 
     | 
    
         
            +
                       'GTC' => :good_till_cancelled,
         
     | 
| 
      
 319 
     | 
    
         
            +
                       'IOC' => :immediate_or_cancel,
         
     | 
| 
      
 320 
     | 
    
         
            +
            	   'OPG' => :opening_price, 
         
     | 
| 
      
 321 
     | 
    
         
            +
            	   'AUC' => :at_auction},
         
     | 
| 
      
 322 
     | 
    
         
            +
             
     | 
| 
      
 323 
     | 
    
         
            +
                  :rule_80a =>
         
     | 
| 
      
 324 
     | 
    
         
            +
                      {'I' => :individual,
         
     | 
| 
      
 325 
     | 
    
         
            +
                       'A' => :agency,
         
     | 
| 
      
 326 
     | 
    
         
            +
                       'W' => :agent_other_member,
         
     | 
| 
      
 327 
     | 
    
         
            +
                       'J' => :individual_ptia,
         
     | 
| 
      
 328 
     | 
    
         
            +
                       'U' => :agency_ptia,
         
     | 
| 
      
 329 
     | 
    
         
            +
                       'M' => :agent_other_member_ptia,
         
     | 
| 
      
 330 
     | 
    
         
            +
                       'K' => :individual_pt,
         
     | 
| 
      
 331 
     | 
    
         
            +
                       'Y' => :agency_pt,
         
     | 
| 
      
 332 
     | 
    
         
            +
                       'N' => :agent_other_member_pt},
         
     | 
| 
      
 333 
     | 
    
         
            +
             
     | 
| 
      
 334 
     | 
    
         
            +
                  :opt? => # TODO: unknown Order property, like OPT_BROKER_DEALER... in Order.java
         
     | 
| 
      
 335 
     | 
    
         
            +
                      {'?' => :unknown,
         
     | 
| 
      
 336 
     | 
    
         
            +
                       'b' => :broker_dealer,
         
     | 
| 
      
 337 
     | 
    
         
            +
                       'c' => :customer,
         
     | 
| 
      
 338 
     | 
    
         
            +
                       'f' => :firm,
         
     | 
| 
      
 339 
     | 
    
         
            +
                       'm' => :isemm,
         
     | 
| 
      
 340 
     | 
    
         
            +
                       'n' => :farmm,
         
     | 
| 
      
 341 
     | 
    
         
            +
                       'y' => :specialist},
         
     | 
| 
      
 342 
     | 
    
         
            +
            # conditions
         
     | 
| 
      
 343 
     | 
    
         
            +
            		   :conjunction_connection => { 'o' =>  :or, 'a' => :and },
         
     | 
| 
      
 344 
     | 
    
         
            +
            			 :operator => { 1 => '>=' , 0 => '<=' }
         
     | 
| 
      
 345 
     | 
    
         
            +
             
         
     | 
| 
      
 346 
     | 
    
         
            +
              }.freeze
         
     | 
| 
      
 347 
     | 
    
         
            +
             
     | 
| 
      
 348 
     | 
    
         
            +
              # Obtain property code from given symbolic value:
         
     | 
| 
      
 349 
     | 
    
         
            +
              # CODES[:side][:buy] -> 'B'
         
     | 
| 
      
 350 
     | 
    
         
            +
              CODES = Hash[VALUES.map { |property, hash| [property, hash.invert] }].freeze
         
     | 
| 
      
 351 
     | 
    
         
            +
             
     | 
| 
      
 352 
     | 
    
         
            +
              # Most common property processors
         
     | 
| 
      
 353 
     | 
    
         
            +
              PROPS = {:side =>
         
     | 
| 
      
 354 
     | 
    
         
            +
                           {:set => proc { |val| # BUY(BOT)/SELL(SLD)/SSHORT/SSHORTX
         
     | 
| 
      
 355 
     | 
    
         
            +
                             self[:side] = case val.to_s.upcase
         
     | 
| 
      
 356 
     | 
    
         
            +
                                             when /SHORT.*X|\AX\z/
         
     | 
| 
      
 357 
     | 
    
         
            +
                                               'X'
         
     | 
| 
      
 358 
     | 
    
         
            +
                                             when /SHORT|\AT\z/
         
     | 
| 
      
 359 
     | 
    
         
            +
                                               'T'
         
     | 
| 
      
 360 
     | 
    
         
            +
                                             when /\AB/
         
     | 
| 
      
 361 
     | 
    
         
            +
                                               'B'
         
     | 
| 
      
 362 
     | 
    
         
            +
                                             when /\AS/
         
     | 
| 
      
 363 
     | 
    
         
            +
                                               'S'
         
     | 
| 
      
 364 
     | 
    
         
            +
                                           end },
         
     | 
| 
      
 365 
     | 
    
         
            +
                            :validate =>
         
     | 
| 
      
 366 
     | 
    
         
            +
                                {:format =>
         
     | 
| 
      
 367 
     | 
    
         
            +
                                     {:with => /\Abuy\z|\Asell\z|\Ashort\z|\Ashort_exempt\z/,
         
     | 
| 
      
 368 
     | 
    
         
            +
                                      :message => "should be buy/sell/short"}
         
     | 
| 
      
 369 
     | 
    
         
            +
                                }
         
     | 
| 
      
 370 
     | 
    
         
            +
                           },
         
     | 
| 
      
 371 
     | 
    
         
            +
             
     | 
| 
      
 372 
     | 
    
         
            +
                       :open_close =>
         
     | 
| 
      
 373 
     | 
    
         
            +
                           {:set => proc { |val|
         
     | 
| 
      
 374 
     | 
    
         
            +
                             self[:open_close] = case val.to_s.upcase[0..0]
         
     | 
| 
      
 375 
     | 
    
         
            +
                                                   when 'S', '0' # SAME
         
     | 
| 
      
 376 
     | 
    
         
            +
                                                     0
         
     | 
| 
      
 377 
     | 
    
         
            +
                                                   when 'O', '1' # OPEN
         
     | 
| 
      
 378 
     | 
    
         
            +
                                                     1
         
     | 
| 
      
 379 
     | 
    
         
            +
                                                   when 'C', '2' # CLOSE
         
     | 
| 
      
 380 
     | 
    
         
            +
                                                     2
         
     | 
| 
      
 381 
     | 
    
         
            +
                                                   when 'U', '3' # Unknown
         
     | 
| 
      
 382 
     | 
    
         
            +
                                                     3
         
     | 
| 
      
 383 
     | 
    
         
            +
                                                 end
         
     | 
| 
      
 384 
     | 
    
         
            +
                           },
         
     | 
| 
      
 385 
     | 
    
         
            +
                            :validate =>
         
     | 
| 
      
 386 
     | 
    
         
            +
                                {:format =>
         
     | 
| 
      
 387 
     | 
    
         
            +
                                     {:with => /\Asame\z|\Aopen\z|\Aclose\z|\Aunknown\z/,
         
     | 
| 
      
 388 
     | 
    
         
            +
                                      :message => "should be same/open/close/unknown"}
         
     | 
| 
      
 389 
     | 
    
         
            +
                                },
         
     | 
| 
      
 390 
     | 
    
         
            +
                           }
         
     | 
| 
      
 391 
     | 
    
         
            +
              }.freeze
         
     | 
| 
      
 392 
     | 
    
         
            +
             
     | 
| 
      
 393 
     | 
    
         
            +
            end # module IB
         
     |