ib-api 972.5 → 972.5.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0beb8b8f1edf85049336ec063de7fcaa5c0c0e1dae3e6f813131f997718892ad
4
- data.tar.gz: c2178ec4cdff3a6d90c7ef0c1cd68dab1620ce57ab2feba811873a113d1a6536
3
+ metadata.gz: f7b56c267d93f567d8e601e801d98a90018fddb5e87f876c8eef2d89bd1a3e45
4
+ data.tar.gz: 23da0add2599afc4928e7008a9cf4bf3ed9f2c93a1cfd7d4e0bc8eeb3ebc6d4f
5
5
  SHA512:
6
- metadata.gz: e1b6919bbc9adf1044b602bb64da8b67f0bfe2ec54c354a6a6465e2bf7dfa49355315029c0bba3ae682b2226dea3002d74a0e3ba7b02834de7217d22ec2f2461
7
- data.tar.gz: 8e3bc0babb443933c0b843884a2ec76ceb2fb0df7ea89aad1912c68b672c217dd56b4a8ea90ed350b83660f838895853f3aadc9e1fb2c5baf2ddc029c5e7fc22
6
+ metadata.gz: 57bf9b6cf7c2350e60c62b1d574e46a8d9fb0be5ec3ec2fc225ca9208ae0fa913fa15ad890715b6675f10b67248e04c60945008022368935339ac41a468b9d79
7
+ data.tar.gz: 5b1d1168ae20163ea5ab1693bce66c731183ffb63db81640458290810c2e225973bd10a51a6efa8f326cdd8dadc2747b02ba4a07529d3d7226b577d901cee1e9
data/Gemfile CHANGED
@@ -5,8 +5,8 @@ git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
5
5
  # Specify your gem's dependencies in api.gemspec
6
6
  gemspec
7
7
 
8
- gem 'value_semantics' # models
9
- gem 'ox', git: 'https://github.com/ohler55/ox.git' # xml handling used for news and flex
8
+ # gem 'value_semantics' # models, future plans
9
+
10
10
 
11
11
 
12
12
  gem 'rspec'
data/Gemfile.lock CHANGED
@@ -1,22 +1,22 @@
1
1
  GIT
2
2
  remote: https://github.com/ohler55/ox.git
3
- revision: 59c4234fe01fb5de08d49b39ecf5fc7338c1ffdc
3
+ revision: 67ce6ecb45a0d1354e1f8ed9a155826ba986e21e
4
4
  specs:
5
- ox (2.14.4)
5
+ ox (2.13.4)
6
6
 
7
7
  PATH
8
8
  remote: .
9
9
  specs:
10
- ib-api (972.3.1)
10
+ ib-api (972.2)
11
11
  activemodel
12
12
  activesupport (>= 6.0)
13
13
 
14
14
  GEM
15
15
  remote: https://rubygems.org/
16
16
  specs:
17
- activemodel (6.1.3.2)
18
- activesupport (= 6.1.3.2)
19
- activesupport (6.1.3.2)
17
+ activemodel (6.1.2.1)
18
+ activesupport (= 6.1.2.1)
19
+ activesupport (6.1.2.1)
20
20
  concurrent-ruby (~> 1.0, >= 1.0.2)
21
21
  i18n (>= 1.6, < 2)
22
22
  minitest (>= 5.1)
@@ -25,7 +25,7 @@ GEM
25
25
  coderay (1.1.3)
26
26
  concurrent-ruby (1.1.8)
27
27
  diff-lcs (1.4.4)
28
- ffi (1.15.0)
28
+ ffi (1.13.1)
29
29
  formatador (0.2.5)
30
30
  guard (2.16.2)
31
31
  formatador (>= 0.2.4)
@@ -41,52 +41,52 @@ GEM
41
41
  guard (~> 2.1)
42
42
  guard-compat (~> 1.1)
43
43
  rspec (>= 2.99.0, < 4.0)
44
- i18n (1.8.10)
44
+ i18n (1.8.9)
45
45
  concurrent-ruby (~> 1.0)
46
- listen (3.5.1)
46
+ listen (3.2.1)
47
47
  rb-fsevent (~> 0.10, >= 0.10.3)
48
48
  rb-inotify (~> 0.9, >= 0.9.10)
49
49
  lumberjack (1.2.8)
50
50
  method_source (1.0.0)
51
- minitest (5.14.4)
51
+ minitest (5.14.3)
52
52
  nenv (0.3.0)
53
53
  notiffany (0.1.3)
54
54
  nenv (~> 0.1)
55
55
  shellany (~> 0.0)
56
- pry (0.14.1)
56
+ pry (0.13.1)
57
57
  coderay (~> 1.1)
58
58
  method_source (~> 1.0)
59
- rake (13.0.3)
60
- rb-fsevent (0.11.0)
59
+ rake (13.0.1)
60
+ rb-fsevent (0.10.4)
61
61
  rb-inotify (0.10.1)
62
62
  ffi (~> 1.0)
63
- rspec (3.10.0)
64
- rspec-core (~> 3.10.0)
65
- rspec-expectations (~> 3.10.0)
66
- rspec-mocks (~> 3.10.0)
63
+ rspec (3.9.0)
64
+ rspec-core (~> 3.9.0)
65
+ rspec-expectations (~> 3.9.0)
66
+ rspec-mocks (~> 3.9.0)
67
67
  rspec-collection_matchers (1.2.0)
68
68
  rspec-expectations (>= 2.99.0.beta1)
69
- rspec-core (3.10.1)
70
- rspec-support (~> 3.10.0)
71
- rspec-expectations (3.10.1)
69
+ rspec-core (3.9.3)
70
+ rspec-support (~> 3.9.3)
71
+ rspec-expectations (3.9.2)
72
72
  diff-lcs (>= 1.2.0, < 2.0)
73
- rspec-support (~> 3.10.0)
73
+ rspec-support (~> 3.9.0)
74
74
  rspec-its (1.3.0)
75
75
  rspec-core (>= 3.0.0)
76
76
  rspec-expectations (>= 3.0.0)
77
- rspec-mocks (3.10.2)
77
+ rspec-mocks (3.9.1)
78
78
  diff-lcs (>= 1.2.0, < 2.0)
79
- rspec-support (~> 3.10.0)
80
- rspec-support (3.10.2)
79
+ rspec-support (~> 3.9.0)
80
+ rspec-support (3.9.3)
81
81
  shellany (0.0.1)
82
- thor (1.1.0)
82
+ thor (1.0.1)
83
83
  tzinfo (2.0.4)
84
84
  concurrent-ruby (~> 1.0)
85
85
  value_semantics (3.6.0)
86
86
  zeitwerk (2.4.2)
87
87
 
88
88
  PLATFORMS
89
- x86_64-linux
89
+ ruby
90
90
 
91
91
  DEPENDENCIES
92
92
  bundler
@@ -101,4 +101,4 @@ DEPENDENCIES
101
101
  value_semantics
102
102
 
103
103
  BUNDLED WITH
104
- 2.2.3
104
+ 1.17.3
data/README.md CHANGED
@@ -3,6 +3,7 @@ Ruby interface to Interactive Brokers' TWS API
3
3
 
4
4
  Reimplementation of the basic functions of ib-ruby
5
5
 
6
+
6
7
  __Documentation: [https://ib-ruby.github.io/ib-doc/](https://ib-ruby.github.io/ib-doc/)__ (_work in progress_)
7
8
 
8
9
  ----
@@ -20,7 +21,7 @@ $ gem install ib-api
20
21
 
21
22
  In its plain vanilla usage, it just exchanges messages with the TWS. Any response is stored in the `recieved-Array`.
22
23
 
23
- Even then, it needs just a few lines of code to place an order
24
+ It needs just a few lines of code to place an order
24
25
 
25
26
  ```ruby
26
27
  require 'ib-api'
@@ -64,29 +65,36 @@ or occationally
64
65
 
65
66
  ```ruby
66
67
  # first define actions
67
- a = ib.subscribe(:Alert, :ContractData ) do |msg|
68
+ q = Queue.new # Initialize as Queue
69
+ request_id = nil # declare variable
70
+ a = ib.subscribe(:Alert, :ContractData, :ContractDataEnd ) do |msg|
68
71
  case msg
69
72
  when Messages::Incoming::Alert
70
- if msg.code == 200 # No security found
71
- # do someting
72
- end
73
+ q.close if msg.code == 200 # No security found
73
74
  when Messages::Incoming::ContractData # security returned
74
- # do something
75
-
75
+ q.push msg.contract if msg.request_id == request_id
76
+ when Messages::Incoming::ContractDataEnd
77
+ q.close if msg.request_id == request_id
76
78
  end # case
77
79
  end
78
80
  # perform request
79
- ib.send_message :RequestContractData, :contract => #{some contract}
80
-
81
- # wait until the :ContractDataEnd message returned
82
- ib.wait_for :ContractDataEnd
83
-
81
+ request_id = ib.send_message :RequestContractData, :contract => Stock.new(symbol: 'T')
82
+
83
+ while contract = q.pop
84
+ puts contract.as_table
85
+ end
86
+ ┌───────┬────────┬──────────┬──────────┬────────┬────────────┬───────────────┬───────┬────────┬──────────┐
87
+ │ │ symbol │ con_id │ exchange │ expiry │ multiplier │ trading-class │ right │ strike │ currency │
88
+ ╞═══════╪════════╪══════════╪══════════╪════════╪════════════╪═══════════════╪═══════╪════════╪══════════╡
89
+ │ Stock │ T │ 37018770 │ SMART │ │ │ T │ │ │ USD │
90
+ └───────┴────────┴──────────┴──────────┴────────┴────────────┴───────────────┴───────┴────────┴──────────┘
91
+
84
92
  ib.unsubscribe a # release subscriptions
85
93
 
86
94
  ```
87
95
  ## Minimal TWS-Version
88
96
 
89
- `ib-api` is tested via the _stable IB-Gateway_ (Version 9.72) and should work with any current tws-installation.
97
+ `ib-api` is tested via the _stable IB-Gateway_ (Version 10.12) and should work with any current tws-installation.
90
98
 
91
99
  ## Tests
92
100
 
data/VERSION CHANGED
@@ -1 +1 @@
1
- 972.5
1
+ 972.5.1
data/api.gemspec CHANGED
@@ -40,4 +40,6 @@ Gem::Specification.new do |spec|
40
40
  spec.add_development_dependency "rspec", "~> 3.0"
41
41
  spec.add_dependency 'activesupport', '>= 6.0'
42
42
  spec.add_dependency 'activemodel'
43
+ spec.add_dependency 'ox'
44
+ spec.add_dependency 'terminal-table'
43
45
  end
data/bin/console CHANGED
@@ -76,7 +76,7 @@ read_yml = -> (key) do
76
76
 
77
77
  c.subscribe( :OpenOrder){ |msg| "Open Order detected and stored: C.received[:OpenOrders] " }
78
78
  end
79
- C.logger.level = Logger::INFO
79
+ #C.logger.level = Logger::FATAL
80
80
  unless C.received[:OpenOrder].blank?
81
81
  puts "------------------------------- OpenOrders ----------------------------------"
82
82
  puts C.received[:OpenOrder].to_human.join "\n"
@@ -5,10 +5,19 @@ module CoreExtensions
5
5
  self.each_with_object(Hash.new(0)) { |element, counter| counter[element] += 1 }.sort_by{|k,v| -v}.to_h
6
6
  end
7
7
  end
8
+
9
+ module TablePresenter
10
+ def as_table(&b)
11
+ the_table_header = first.table_header(&b)
12
+ the_table_rows = map &:table_row
13
+ Terminal::Table.new headings: the_table_header, rows: the_table_rows , style: { border: :unicode }
14
+ end
15
+ end
8
16
  end
9
17
  end
10
18
 
11
19
  Array.include CoreExtensions::Array::DuplicatesCounter
20
+ Array.include CoreExtensions::Array::TablePresenter
12
21
 
13
22
 
14
23
 
@@ -17,12 +17,33 @@ module IB
17
17
  end.compact.sort.join(' ') + ">"
18
18
  end
19
19
 
20
+ def table_header
21
+ [ self.class.to_s.demodulize ] + content_attributes.keys
22
+ end
23
+ def table_row
24
+ [ self.class.to_s.demodulize ] + content_attributes.values
25
+ end
26
+ # the optional block specifies a title
27
+ # i.e.
28
+ #s = Symbols::Spreads.stoxx_dez
29
+ # puts s.portfolio_value( U).as_table{ s.description[1..-2] }
30
+ #┌───────────┬─────────────────────────────────────────────┬─────┬────────┬─────────┬──────────┬────────────┬──────────┐
31
+ #│ │ Straddle ESTX50(4200.0)[Dec 2021] │ pos │ entry │ market │ value │ unrealized │ realized │
32
+ #╞═══════════╪═════════════════════════════════════════════╪═════╪════════╪═════════╪══════════╪════════════╪══════════╡
33
+ #│ U7274612 │ Option: ESTX50 20211217 put 4200.0 DTB EUR │ -4 │ 179.85 │ 169.831 │ -6793.22 │ 400.78 │ │
34
+ #│ U7274612 │ Option: ESTX50 20211217 call 4200.0 DTB EUR │ -4 │ 97.85 │ 131.438 │ -5257.51 │ -1343.51 │ │
35
+ #└───────────┴─────────────────────────────────────────────┴─────┴────────┴─────────┴──────────┴────────────┴──────────┘
36
+ ##
37
+ def as_table &b
38
+ Terminal::Table.new headings: table_header(&b), rows: [table_row ], style: { border: :unicode }
39
+ end
40
+
20
41
  # Comparison support
21
42
  def content_attributes
22
43
  #NoMethodError if a Hash is assigned to an attribute
23
44
  Hash[attributes.reject do |(attr, _)|
24
45
  attr.to_s =~ /(_count)\z/ ||
25
- [:created_at, :type, # :updated_at,
46
+ [:created_at, :type, :updated_at,
26
47
  :id, :order_id, :contract_id].include?(attr.to_sym)
27
48
  end]
28
49
  end
data/lib/ib/connection.rb CHANGED
@@ -35,76 +35,81 @@ module IB
35
35
  alias next_order_id= next_local_id=
36
36
 
37
37
  def initialize host: '127.0.0.1',
38
- port: '4002', # IB Gateway connection (default --> demo) 4001: production
39
- #:port => '7497', # TWS connection --> demo 7496: production
40
- connect: true, # Connect at initialization
41
- received: true, # Keep all received messages in a @received Hash
42
- # redis: false, # future plans
43
- logger: nil,
44
- client_id: rand( 1001 .. 9999 ) ,
45
- client_version: IB::Messages::CLIENT_VERSION, # lib/ib/server_versions.rb
46
- optional_capacities: "", # TWS-Version 974: "+PACEAPI"
47
- #server_version: IB::Messages::SERVER_VERSION, # lib/messages.rb
48
- **any_other_parameters_which_are_ignored
49
- # V 974 release motes
50
- # API messages sent at a higher rate than 50/second can now be paced by TWS at the 50/second rate instead of potentially causing a disconnection. This is now done automatically by the RTD Server API and can be done with other API technologies by invoking SetConnectOptions("+PACEAPI") prior to eConnect.
51
-
52
- self.class.configure_logger logger
53
- # convert parameters into instance-variables and assign them
54
- method(__method__).parameters.each do |type, k|
55
- next unless type == :key ## available: key , keyrest
56
- next if k.to_s == 'logger'
57
- v = eval(k.to_s)
58
- instance_variable_set("@#{k}", v) unless v.nil?
59
- end
60
-
61
- # A couple of locks to avoid race conditions in JRuby
62
- @subscribe_lock = Mutex.new
63
- @receive_lock = Mutex.new
64
- @message_lock = Mutex.new
65
-
66
- @connected = false
67
- self.next_local_id = nil
68
-
69
- # self.subscribe(:Alert) do |msg|
70
- # puts msg.to_human
71
- # end
38
+ port: '4002', # IB Gateway connection (default --> demo) 4001: production
39
+ #:port => '7497', # TWS connection --> demo 7496: production
40
+ connect: true, # Connect at initialization
41
+ received: true, # Keep all received messages in a @received Hash
42
+ # redis: false, # future plans
43
+ logger: nil,
44
+ client_id: rand( 1001 .. 9999 ) ,
45
+ client_version: IB::Messages::CLIENT_VERSION, # lib/ib/server_versions.rb
46
+ optional_capacities: "", # TWS-Version 974: "+PACEAPI"
47
+ #server_version: IB::Messages::SERVER_VERSION, # lib/messages.rb
48
+ **any_other_parameters_which_are_ignored
49
+ # V 974 release motes
50
+ # API messages sent at a higher rate than 50/second can now be paced by TWS at the 50/second rate instead of potentially causing a disconnection. This is now done automatically by the RTD Server API and can be done with other API technologies by invoking SetConnectOptions("+PACEAPI") prior to eConnect.
51
+
52
+ self.class.configure_logger logger
53
+ # convert parameters into instance-variables and assign them
54
+ method(__method__).parameters.each do |type, k|
55
+ next unless type == :key ## available: key , keyrest
56
+ next if k.to_s == 'logger'
57
+ v = eval(k.to_s)
58
+ instance_variable_set("@#{k}", v) unless v.nil?
59
+ end
72
60
 
73
- # TWS always sends NextValidId message at connect -subscribe save this id
74
- ## this block is executed before tws-communication is established
75
- yield self if block_given?
61
+ # A couple of locks to avoid race conditions in JRuby
62
+ @subscribe_lock = Mutex.new
63
+ @receive_lock = Mutex.new
64
+ @message_lock = Mutex.new
76
65
 
77
- self.subscribe(:NextValidId) do |msg|
78
- self.logger.progname = "Connection#connect"
79
- self.next_local_id = msg.local_id
80
- self.logger.info { "Got next valid order id: #{next_local_id}." }
81
- end
66
+ @connected = false
67
+ self.next_local_id = nil
82
68
 
83
- # Ensure the transmission of NextValidId.
84
- # works even if no reader_thread is established
85
- if connect
86
- disconnect if connected?
87
- update_next_order_id
88
- Kernel.exit if self.next_local_id.nil?
89
- end
90
- #start_reader if @received && connected?
91
- Connection.current = self
92
- end
69
+ # TWS always sends NextValidId message at connect -subscribe save this id
70
+ self.subscribe(:NextValidId) do |msg|
71
+ self.logger.progname = "Connection#connect"
72
+ self.next_local_id = msg.local_id
73
+ self.logger.info { "Got next valid order id: #{next_local_id}." }
74
+ end
75
+ #
76
+ # this block is executed before tws-communication is established
77
+ # Its intended for globally available subscriptions of tws-messages
78
+ yield self if block_given?
79
+
80
+ if connect
81
+ disconnect if connected?
82
+ update_next_order_id
83
+ Kernel.exit if self.next_local_id.nil? # emergency exit.
84
+ # update_next_order_id should have raised an error
85
+ end
86
+ Connection.current = self
87
+ end
93
88
 
94
89
  # read actual order_id and
95
90
  # connect if not connected
96
91
  def update_next_order_id
97
- i,finish = 0, false
98
- sub = self.subscribe(:NextValidID) { finish = true }
99
- connected? ? self.send_message( :RequestIds ) : open()
100
- Timeout::timeout(1, IB::TransmissionError,"Could not get NextValidId" ) do
101
- loop { sleep 0.1; break if finish }
102
- end
103
- self.unsubscribe sub
104
- end
92
+ q = Queue.new
93
+ subscription = subscribe(:NextValidId){ |msg| q.push msg.local_id }
94
+ unless connected?
95
+ connect() # connect implies requesting NextValidId
96
+ else
97
+ send_message :RequestIds
98
+ end
99
+ th = Thread.new { sleep 5; q.close }
100
+ local_id = q.pop
101
+ if q.closed?
102
+ error "Could not get NextValidID", :reader
103
+ else
104
+ th.kill
105
+ end
106
+ unsubscribe subscription
107
+ local_id # return next_id
108
+ end
105
109
 
106
110
  ### Working with connection
107
-
111
+ #
112
+ ### connect can be called directly. but is mostly called through update_next_order_id
108
113
  def connect
109
114
  logger.progname='IB::Connection#connect'
110
115
  if connected?
@@ -123,10 +128,6 @@ module IB
123
128
  @local_connect_time = Time.now
124
129
  end
125
130
 
126
- # Sending (arbitrary) client ID to identify subsequent communications.
127
- # The client with a client_id of 0 can manage the TWS-owned open orders.
128
- # Other clients can only manage their own open orders.
129
-
130
131
  # V100 initial handshake
131
132
  # Parameters borrowed from the python client
132
133
  start_api = 71
@@ -134,14 +135,11 @@ module IB
134
135
  # optcap = @optional_capacities.empty? ? "" : " "+ @optional_capacities
135
136
  socket.send_messages start_api, version, @client_id , @optional_capacities
136
137
  @connected = true
137
- logger.info { "Connected to server, version: #{@server_version},\n connection time: " +
138
+ logger.fatal{ "Connected to server, version: #{@server_version}, " +
139
+ "using client-id: #{client_id},\n connection time: " +
138
140
  "#{@local_connect_time} local, " +
139
- "#{@remote_connect_time} remote."}
141
+ "#{@remote_connect_time} remote." }
140
142
 
141
- # if the client_id is wrong or the port is not accessible the first read attempt fails
142
- # get the first message and proceed if something reasonable is recieved
143
- the_message = process_message # recieve next_order_id
144
- error "Check Port/Client_id ", :reader if the_message == " "
145
143
  start_reader
146
144
  end
147
145
 
@@ -258,6 +256,7 @@ module IB
258
256
  #
259
257
  # wait_for depends heavyly on Connection#received. If collection of messages through recieved
260
258
  # is turned off, wait_for loses most of its functionality
259
+
261
260
  def wait_for *args, &block
262
261
  timeout = args.find { |arg| arg.is_a? Numeric } # extract timeout from args
263
262
  end_time = Time.now + (timeout || 1) # default timeout 1 sec
@@ -282,12 +281,12 @@ module IB
282
281
  # Process incoming messages during *poll_time* (200) msecs, nonblocking
283
282
  def process_messages poll_time = 50 # in msec
284
283
  time_out = Time.now + poll_time/1000.0
284
+ begin
285
285
  while (time_left = time_out - Time.now) > 0
286
286
  # If socket is readable, process single incoming message
287
287
  #process_message if select [socket], nil, nil, time_left
288
288
  # the following checks for shutdown of TWS side; ensures we don't run in a spin loop.
289
289
  # unfortunately, it raises Errors in windows environment
290
- # disabled for now
291
290
  if select [socket], nil, nil, time_left
292
291
  # # Peek at the message from the socket; if it's blank then the
293
292
  # # server side of connection (TWS) has likely shut down.
@@ -303,6 +302,16 @@ module IB
303
302
  sleep(0.1) if socket_likely_shutdown
304
303
  end
305
304
  end
305
+ rescue Errno::ECONNRESET => e
306
+ logger.fatal e.message
307
+ if e.message =~ /Connection reset by peer/
308
+ logger.fatal "Is another client listening on the same port?"
309
+ error "try reconnecting with a different client-id", :reader
310
+ else
311
+ logger.fatal "Aborting"
312
+ Kernel.exit
313
+ end
314
+ end
306
315
  end
307
316
 
308
317
  ### Sending Outgoing messages to IB
@@ -374,15 +383,20 @@ module IB
374
383
  # If you don't start reader, you should manually poll @socket for messages
375
384
  # or use #process_messages(msec) API.
376
385
  def start_reader
377
- return(@reader_thread) if @reader_running
378
- if connected?
379
- Thread.abort_on_exception = true
380
- @reader_running = true
381
- @reader_thread = Thread.new { process_messages while @reader_running }
382
- else
383
- logger.fatal {"Could not start reader, not connected!"}
384
- nil # return_value
385
- end
386
+ if @reader_running
387
+ @reader_thread
388
+ elsif connected?
389
+ begin
390
+ Thread.abort_on_exception = true
391
+ @reader_running = true
392
+ @reader_thread = Thread.new { process_messages while @reader_running }
393
+ rescue Errno::ECONNRESET => e
394
+ logger.fatal e.message
395
+ Kernel.exit
396
+ end
397
+ else
398
+ error "Could not start reader, not connected!", :reader, true
399
+ end
386
400
  end
387
401
 
388
402
  protected
@@ -403,18 +417,18 @@ module IB
403
417
  msg_id = the_decoded_message.shift.to_i
404
418
 
405
419
  # Debug:
406
- logger.debug { "Got message #{msg_id} (#{Messages::Incoming::Classes[msg_id]})"}
420
+ # logger.debug { "Got message #{msg_id} (#{Messages::Incoming::Classes[msg_id]})"}
407
421
 
408
422
  # Create new instance of the appropriate message type,
409
423
  # and have it read the message from socket.
410
424
  # NB: Failure here usually means unsupported message type received
411
425
  logger.error { "Got unsupported message #{msg_id}" } unless Messages::Incoming::Classes[msg_id]
412
- error "Something strange happened - Reader has to be restarted" , :reader if msg_id.to_i.zero?
426
+ error "Something strange happened - Reader has to be restarted" , :reader, true if msg_id.to_i.zero?
413
427
  msg = Messages::Incoming::Classes[msg_id].new(the_decoded_message)
414
428
 
415
429
  # Deliver message to all registered subscribers, alert if no subscribers
416
430
  # Ruby 2.0 and above: Hashes are ordered.
417
- # Thus first declared subscribers of a class are executed first
431
+ # Thus first declared subscribers of a class are executed first
418
432
  @subscribe_lock.synchronize do
419
433
  subscribers[msg.class].each { |_, subscriber| subscriber.call(msg) }
420
434
  end
data/lib/ib/constants.rb CHANGED
@@ -21,7 +21,11 @@ module IB
21
21
  '15 mins' =>:min15,
22
22
  '30 mins' =>:min30,
23
23
  '1 hour' =>:hour1,
24
- '1 day' => :day1
24
+ '4 hours' =>:hour4,
25
+ '8 hours' =>:hour8,
26
+ '1 day' => :day1,
27
+ '1 week' => :week1,
28
+ '1 month' => :month1,
25
29
  }.freeze
26
30
 
27
31
  # Enumeration of data types.
@@ -199,7 +203,6 @@ module IB
199
203
  'PEG MID' => :pegged_to_midpoint, # Pegged-to-Midpoint
200
204
  'PEG BENCH' => :pegged_to_benchmark, # Pegged-to-Benmchmark # Vers. 102
201
205
  'VWAP' => :vwap, # VWAP-Guaranted
202
- 'OCA' => :one_cancels_all, # One-Cancels-All
203
206
  'VOL' => :volatility, # Volatility
204
207
  'SCALE' => :scale, # Scale
205
208
  'NONE' => :none, # Used to indicate no hedge in :delta_neutral_order_type
data/lib/ib/errors.rb CHANGED
@@ -38,7 +38,9 @@ def error message, type=:standard, backtrace=nil
38
38
  IB::FlexError.new message
39
39
  when :reader
40
40
  IB::TransmissionError.new message
41
+ when :verify
42
+ IB::VerifyError.new message
41
43
  end
42
- e.set_backtrace(backtrace) if backtrace
44
+ e.set_backtrace(caller) if backtrace
43
45
  raise e
44
46
  end
@@ -4,10 +4,10 @@ module IB
4
4
 
5
5
  # Called Error in Java code, but in fact this type of messages also
6
6
  # deliver system alerts and additional (non-error) info from TWS.
7
- ErrorMessage = Error = Alert = def_message([4, 2],
8
- [:error_id, :int],
9
- [:code, :int],
10
- [:message, :string])
7
+ Alert = def_message([4, 2],
8
+ [:error_id, :int],
9
+ [:code, :int],
10
+ [:message, :string])
11
11
  class Alert
12
12
  # Is it an Error message?
13
13
  def error?
@@ -83,7 +83,7 @@ module IB
83
83
  TickGeneric = def_message [45, 6], AbstractTick,
84
84
  [:ticker_id, :int],
85
85
  [:tick_type, :int],
86
- [:value, :decimal]
86
+ [:value, :float]
87
87
 
88
88
  TickString = def_message [46, 6], AbstractTick,
89
89
  [:ticker_id, :int],
@@ -74,9 +74,9 @@ module IB
74
74
  order.all_or_none || false,
75
75
  order.min_quantity || "",
76
76
  order.percent_offset || '',
77
- order.etrade_only || false,
78
- order.firm_quote_only || false,
79
- order.nbbo_price_cap || "",
77
+ false, # was: order.etrade_only || false, desupported in TWS > 981
78
+ false, # was: order.firm_quote_only || false, desupported in TWS > 981
79
+ order.nbbo_price_cap || "", ## desupported in TWS > 981, too. maybe we have to insert a hard-coded "" here
80
80
  order[:auction_strategy],
81
81
  order.starting_price,
82
82
  order.stock_ref_price || "",
@@ -96,7 +96,7 @@ module IB
96
96
  [:tick_list, ->(tick_list){ tick_list.is_a?(Array) ? tick_list.join(',') : (tick_list || '')}, []],
97
97
  [:snapshot, false],
98
98
  [:regulatory_snapshot, false],
99
- [:mkt_data_options, "XYZ"]
99
+ [:mkt_data_options, ""]
100
100
  end
101
101
  end
102
102
  end
data/lib/ib/socket.rb CHANGED
@@ -154,7 +154,7 @@ module IB
154
154
  def send_messages *data
155
155
  self.syswrite prepare_message(data)
156
156
  rescue Errno::ECONNRESET => e
157
- Connection.logger.error{ "Data not accepted by IB \n
157
+ Connection.logger.fatal{ "Data not accepted by IB \n
158
158
  #{data.inspect} \n
159
159
  Backtrace:\n "}
160
160
  Connection.logger.error e.backtrace
@@ -172,7 +172,7 @@ module IB
172
172
  end while buffer.size == 4096
173
173
  complete_message_buffer.join('')
174
174
  rescue Errno::ECONNRESET => e
175
- Connection.logger.error{ "Data Buffer is not filling \n
175
+ Connection.logger.fatal{ "Data Buffer is not filling \n
176
176
  The Buffer: #{buffer.inspect} \n
177
177
  Backtrace:\n
178
178
  #{e.backtrace.join("\n") } " }
data/lib/logging.rb CHANGED
@@ -34,7 +34,7 @@ module Support
34
34
  @logger.level = Logger::INFO
35
35
  @logger.formatter = proc do |severity, datetime, progname, msg|
36
36
  # "#{datetime.strftime("%d.%m.(%X)")}#{"%5s" % severity}->#{msg}\n"
37
- "#{"%5s" % severity}::#{msg}\n"
37
+ "#{"%1s" % severity[0]}: #{msg}\n"
38
38
  end
39
39
  @logger.debug "------------------------------ start logging ----------------------------"
40
40
  end # branch
@@ -235,10 +235,46 @@ module IB
235
235
 
236
236
  # creates a new Contract substituting attributes by the provided key-value pairs.
237
237
  #
238
- # con_id is resetted
239
- def merge **new_attributes
240
- self.con_id = 0
241
- self.class.new attributes.merge new_attributes
238
+ # for convenience
239
+ # con_id, local_symbol and last_trading_day are resetted,
240
+ # the link to contract-details is savaged
241
+ #
242
+ # Example
243
+ # ge = Stock.new( symbol: :ge).verify.first
244
+ # f = ge.merge symbol: :f
245
+ #
246
+ # c = Contract.new( con_id: 428520002, exchange: 'Globex')
247
+ #puts c.verify.as_table
248
+ #┌────────┬────────┬───────────┬──────────┬──────────┬────────────┬───────────────┬───────┬────────┬──────────┐
249
+ #│ │ symbol │ con_id │ exchange │ expiry │ multiplier │ trading-class │ right │ strike │ currency │
250
+ #╞════════╪════════╪═══════════╪══════════╪══════════╪════════════╪═══════════════╪═══════╪════════╪══════════╡
251
+ #│ Future │ NQ │ 428520002 │ GLOBEX │ 20210917 │ 20 │ NQ │ │ │ USD │
252
+ #└────────┴────────┴───────────┴──────────┴──────────┴────────────┴───────────────┴───────┴────────┴──────────┘
253
+ # d= c.merge symbol: :es, trading_class: '', multiplier: 50
254
+ # puts d.verify.as_table
255
+ #┌────────┬────────┬───────────┬──────────┬──────────┬────────────┬───────────────┬───────┬────────┬──────────┐
256
+ #│ │ symbol │ con_id │ exchange │ expiry │ multiplier │ trading-class │ right │ strike │ currency │
257
+ #╞════════╪════════╪═══════════╪══════════╪══════════╪════════════╪═══════════════╪═══════╪════════╪══════════╡
258
+ #│ Future │ ES │ 428520022 │ GLOBEX │ 20210917 │ 50 │ ES │ │ │ USD │
259
+ #│ Future │ ES │ 446091461 │ GLOBEX │ 20211217 │ 50 │ ES │ │ │ USD │
260
+ #│ Future │ ES │ 461318816 │ GLOBEX │ 20220318 │ 50 │ ES │ │ │ USD │
261
+ #│ Future │ ES │ 477836957 │ GLOBEX │ 20220617 │ 50 │ ES │ │ │ USD │
262
+ #│ Future │ ES │ 495512551 │ GLOBEX │ 20221216 │ 50 │ ES │ │ │ USD │
263
+ #│ Future │ ES │ 495512552 │ GLOBEX │ 20231215 │ 50 │ ES │ │ │ USD │
264
+ #│ Future │ ES │ 495512557 │ GLOBEX │ 20241220 │ 50 │ ES │ │ │ USD │
265
+ #│ Future │ ES │ 495512563 │ GLOBEX │ 20251219 │ 50 │ ES │ │ │ USD │
266
+ #│ Future │ ES │ 495512566 │ GLOBEX │ 20220916 │ 50 │ ES │ │ │ USD │
267
+ #│ Future │ ES │ 495512569 │ GLOBEX │ 20230616 │ 50 │ ES │ │ │ USD │
268
+ #│ Future │ ES │ 495512572 │ GLOBEX │ 20230317 │ 50 │ ES │ │ │ USD │
269
+ #│ Future │ ES │ 497222760 │ GLOBEX │ 20230915 │ 50 │ ES │ │ │ USD │
270
+ #└────────┴────────┴───────────┴──────────┴──────────┴────────────┴───────────────┴───────┴────────┴──────────┘
271
+
272
+ def merge **new_attributes
273
+
274
+ resetted_attributes = [:con_id, :local_symbol, :contract_detail]
275
+ ## last_trading_day / expiry needs special treatment
276
+ resetted_attributes << :last_trading_day if new_attributes.keys.include? :expiry
277
+ self.class.new attributes.reject{|k,_| resetted_attributes.include? k}.merge(new_attributes)
242
278
  end
243
279
 
244
280
  # Contract comparison
@@ -376,6 +412,29 @@ In places where these terms are used to indicate a concept, we have left them as
376
412
  Hash.new
377
413
  end
378
414
 
415
+
416
+ def table_header( &b )
417
+ if block_given?
418
+ [ yield(self) , 'symbol', 'con_id', 'exchange', 'expiry','multiplier', 'trading-class' , 'right', 'strike', 'currency' ]
419
+ else
420
+ [ '', 'symbol', 'con_id', 'exchange', 'expiry','multiplier', 'trading-class' , 'right', 'strike', 'currency' ]
421
+ end
422
+ end
423
+
424
+ def table_row
425
+ [ self.class.to_s.demodulize, symbol,
426
+ { value: con_id.zero? ? '' : con_id , alignment: :right},
427
+ { value: exchange, alignment: :center},
428
+ expiry,
429
+ { value: multiplier.zero?? "" : multiplier, alignment: :center},
430
+ { value: trading_class, alignment: :center},
431
+ {value: right == :none ? "": right, alignment: :center },
432
+ { value: strike.zero? ? "": strike, alignment: :right},
433
+ { value: currency, alignment: :center} ]
434
+
435
+ end
436
+
437
+
379
438
  end # class Contract
380
439
 
381
440
 
@@ -4,7 +4,7 @@ module IB
4
4
  validates_format_of :sec_type, :with => /\Aind\z/,
5
5
  :message => "should be a Index"
6
6
  def default_attributes
7
- super.merge :sec_type => :ind
7
+ super.merge :sec_type => 'IND'
8
8
  end
9
9
  def to_human
10
10
  "<Index: " + [symbol, currency].join(" ") + " (#{description}) >"
@@ -67,5 +67,18 @@ module IB
67
67
 
68
68
  end
69
69
 
70
+ def table_header
71
+ [ 'Greeks', 'price', 'impl. vola', 'dividend', 'delta','gamma', 'vega' , 'theta']
72
+ end
73
+
74
+ def table_row
75
+ outstr= ->( item ) { { value: item.nil? ? "--" : sprintf("%g" , item) , alignment: :right } }
76
+ outprice= ->( item ) { { value: item.nil? ? "--" : sprintf("%7.2f" , item) , alignment: :right } }
77
+ option_short = ->{"#{option.right} #{option.symbol}#{ "/"+ option.trading_class unless option.trading_class == option.symbol } #{option.expiry} #{option.strike}"}
78
+ [ option_short[], outprice[ option_price ], outprice[ implied_volatility ],
79
+ outprice[ pv_dividend ],
80
+ outprice[ delta ], outprice[ gamma ], outprice[ vega ] , outprice[ theta ] ]
81
+ end
82
+
70
83
  end # class
71
84
  end # module
@@ -514,6 +514,23 @@ Format of serialisation
514
514
  (account ? "/#{account}" : '') +
515
515
  (commission ? " fee #{commission}" : '') + ">"
516
516
  end
517
+
518
+
519
+ def table_header
520
+ [ 'account','status' ,'', 'Type', 'tif', 'action', 'amount','price' , 'id/fee' ]
521
+ end
522
+
523
+ def table_row
524
+ [ account, order_ref.present? ? order_ref.to_s : status,
525
+ contract.to_human[1..-2],
526
+ self[:order_type] ,
527
+ self[:tif],
528
+ action,
529
+ total_quantity,
530
+ (limit_price ? "#{limit_price} " : '') + ((aux_price && aux_price != 0) ? "/#{aux_price}" : '') ,
531
+ commission ? " fee #{commission}" : local_id ]
532
+ end
533
+
517
534
  def serialize_rabbit
518
535
  { 'Contract' => contract.present? ? contract.serialize( :option, :trading_class ): '' ,
519
536
  'Order' => self,
@@ -47,18 +47,40 @@ class PortfolioValue < IB::Model
47
47
  end
48
48
  alias to_s to_human
49
49
 
50
+ def table_header
51
+ if block_given?
52
+ [ '' , yield , 'pos', 'entry', 'market', 'value', 'unrealized', 'realized' ]
53
+ else
54
+ [ '' , '', 'pos', 'entry', 'market', 'value', 'unrealized', 'realized' ]
55
+ end
56
+ end
57
+
58
+ def table_row
59
+ outprice= ->( item ) { { value: item.nil? ? "--" : item , alignment: :right } }
60
+
61
+ the_account = if account.present?
62
+ if account.is_a?(String)
63
+ account + " "
64
+ else
65
+ account.account+" "
66
+ end
67
+ else
68
+ ""
69
+ end
70
+
71
+ entry = average_cost.to_f / (contract.multiplier.to_i.zero? ? 1 : contract.multiplier.to_i)
72
+
73
+ [ the_account,
74
+ contract.to_human[1..-2],
75
+ outprice[position.to_i],
76
+ outprice[entry.to_f.round(3)],
77
+ outprice[market_price.to_f.round(3)],
78
+ outprice[market_value.to_f.round(2)],
79
+ unrealized_pnl.to_i.zero? ? "": outprice[unrealized_pnl],
80
+ realized_pnl.to_i.zero? ? "" : outprice[realized_pnl]
81
+ ]
82
+ end
83
+
50
84
 
51
- # def to_invest
52
- # a=attributes
53
- # a.delete "created_at"
54
- # a.delete "updated_at"
55
- # a.delete "id"
56
- # a.delete "account_id"
57
- # a.delete "currency_id"
58
- # a[:currency] = currency.symbol.presence || currency.name.presence || nil unless currency.nil?
59
- # a #return_value
60
- #
61
- #
62
- # end
63
85
  end # class
64
86
  end # module
@@ -1,4 +1,3 @@
1
- # require 'ib/verify'
2
1
  module IB
3
2
  if defined?(Spread)
4
3
  puts "Bag already a #{defined?(Spread)}"
@@ -21,7 +20,7 @@ Adds (or substracts) relative (back) measures to the front month, just passes ab
21
20
  front: 20180908 back: 1w (-1w) --> 20180918 (20180902)
22
21
  =end
23
22
 
24
- def transform_distance front, back
23
+ def self.transform_distance front, back
25
24
  # Check Format of back: 201809 --> > 200.000
26
25
  # 20180989 ---> 20.000.000
27
26
  start_date = front.to_i < 20000000 ? Date.strptime(front.to_s,"%Y%m") : Date.strptime(front.to_s,"%Y%m%d")
@@ -80,17 +79,14 @@ Adds (or substracts) relative (back) measures to the front month, just passes ab
80
79
  # Default: action: :buy, weight: 1
81
80
 
82
81
  def add_leg contract, **leg_params
83
- evaluated_contracts = []
84
- nc = contract.verify.first.essential
82
+ error "need a IB::Contract as first argument" unless contract.is_a? IB::Contract
83
+ self.legs << contract
84
+ error "cannot add leg if no con_id is provided" if contract.con_id.blank?
85
85
  # weigth = 1 --> sets Combo.side to buy and overwrites the action statement
86
86
  # leg_params[:weight] = 1 unless leg_params.key?(:weight) || leg_params.key?(:ratio)
87
- if nc.is_a?( IB::Contract) && nc.con_id.present?
88
- the_leg= ComboLeg.new( nc.attributes.slice( :con_id, :exchange )
89
- .merge( leg_params ))
90
- self.combo_legs << the_leg
91
- self.description = "#{description.nil? ? "": description} added #{nc.to_human}" rescue "Spread: #{nc.to_human}"
92
- self.legs << nc
93
- end
87
+ self.combo_legs << ComboLeg.new( contract.attributes.slice( :con_id, :exchange ).merge( leg_params ))
88
+ self.description = "#{description.nil? ? "": description} added #{contract.to_human}" rescue "Spread: #{contract.to_human}"
89
+
94
90
  self # return value to enable chaining
95
91
 
96
92
 
@@ -98,13 +94,20 @@ Adds (or substracts) relative (back) measures to the front month, just passes ab
98
94
 
99
95
  # removes the contract from the spread definition
100
96
  #
101
- def remove_leg contract
102
- contract.verify do |c|
103
- legs.delete_if { |x| x.con_id == c.con_id }
104
- combo_legs.delete_if { |x| x.con_id == c.con_id }
105
- self.description = description + " removed #{c.to_human}"
106
- end
107
- self
97
+ def remove_leg contract_or_position = nil
98
+ contract = if contract_or_position.is_a? (IB::Contract)
99
+ contract_or_position
100
+ elsif contract_or_position.is_a? Numeric
101
+ legs.at contract_or_position
102
+ else
103
+ error "Specify a contract to be removed or the position in the legs-array as parameter to remove a leg"
104
+ end
105
+ the_con_id = contract.verify.first &.con_id
106
+ error "Invalid Contract specified" unless the_con_id.is_a? Numeric
107
+ legs.delete_if { |x| x.con_id == the_con_id }
108
+ combo_legs.delete_if { |x| x.con_id == the_con_id }
109
+ self.description = description + " removed #{contract.to_human}"
110
+ self # make method chainable
108
111
  end
109
112
 
110
113
  # essentail
@@ -146,6 +149,18 @@ Adds (or substracts) relative (back) measures to the front month, just passes ab
146
149
  # end
147
150
 
148
151
 
152
+ def as_table
153
+ t= Terminal::Table.new title: description[1..-2] ,
154
+ headings: table_header,
155
+
156
+ style: { border: :unicode }
157
+
158
+ t.add_row table_row
159
+ legs.each{ |y| t.add_row y.table_row }
160
+ t.render
161
+
162
+ end
163
+
149
164
  def self.build_from_json container
150
165
  read_leg = ->(a) do
151
166
  IB::ComboLeg.new :con_id => a.read_int,
data/lib/requires.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  require 'active_support/core_ext/module/attribute_accessors.rb'
2
- require 'extensions/class-extensions'
2
+ require_relative 'extensions/class-extensions'
3
3
 
4
+ require 'terminal-table'
4
5
 
5
6
  require 'ib/version'
6
7
  require 'ib/errors'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ib-api
3
3
  version: !ruby/object:Gem::Version
4
- version: '972.5'
4
+ version: 972.5.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Hartmut Bischoff
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-06-01 00:00:00.000000000 Z
11
+ date: 2023-01-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -80,6 +80,34 @@ dependencies:
80
80
  - - ">="
81
81
  - !ruby/object:Gem::Version
82
82
  version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: ox
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: terminal-table
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :runtime
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
83
111
  description: Ruby Implementation of the Interactive Brokers TWS API
84
112
  email:
85
113
  - topofocus@gmail.com
@@ -110,7 +138,6 @@ files:
110
138
  - lib/ib/connection.rb
111
139
  - lib/ib/constants.rb
112
140
  - lib/ib/errors.rb
113
- - lib/ib/logger.rb
114
141
  - lib/ib/messages.rb
115
142
  - lib/ib/messages/abstract_message.rb
116
143
  - lib/ib/messages/incoming.rb
@@ -185,7 +212,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
185
212
  - !ruby/object:Gem::Version
186
213
  version: '0'
187
214
  requirements: []
188
- rubygems_version: 3.2.3
215
+ rubygems_version: 3.3.7
189
216
  signing_key:
190
217
  specification_version: 4
191
218
  summary: Ruby Implementation of the Interactive Brokers TWS API
data/lib/ib/logger.rb DELETED
@@ -1,26 +0,0 @@
1
- require "logger"
2
- module LogDev
3
- # define default_logger
4
- def default_logger
5
- @default_logger ||= Logger.new(STDOUT).tap do |l|
6
- l.formatter = proc do |severity, datetime, progname, msg|
7
- # "#{datetime.strftime("%d.%m.(%X)")}#{"%5s" % severity}->#{progname}##{msg}\n"
8
- ## the default logger displays the message only
9
- msg.to_s + "\n"
10
- end
11
- l.level = Logger::INFO
12
- end
13
- end
14
-
15
-
16
- def default_logger= logger
17
- @default_logger = logger
18
- end
19
-
20
- # Add universally accessible log method/accessor into Object
21
- def logger *args
22
- default_logger.tap do |logger|
23
- logger.fatal *args unless args.empty?
24
- end
25
- end
26
- end # module