ib_ruby_proxy 0.0.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.
Files changed (70) hide show
  1. checksums.yaml +7 -0
  2. data/.circleci/config.yml +77 -0
  3. data/.gitignore +8 -0
  4. data/.rspec +1 -0
  5. data/.rubocop.yml +143 -0
  6. data/.ruby-version +1 -0
  7. data/.travis.yml +7 -0
  8. data/Gemfile +10 -0
  9. data/Gemfile.lock +74 -0
  10. data/LICENSE.txt +21 -0
  11. data/README.md +130 -0
  12. data/Rakefile +12 -0
  13. data/bin/console +14 -0
  14. data/bin/generate_ruby_classes +13 -0
  15. data/bin/ibproxy +32 -0
  16. data/bin/setup +8 -0
  17. data/docs/diagrams.key +0 -0
  18. data/docs/images/architecture.png +0 -0
  19. data/examples/common.rb +14 -0
  20. data/examples/plain_req_historical_ticks.rb +19 -0
  21. data/examples/req_contract_details.rb +9 -0
  22. data/examples/req_historical_bars_data.rb +10 -0
  23. data/examples/req_historical_ticks.rb +10 -0
  24. data/examples/req_tick_by_tick_data.rb +9 -0
  25. data/ib_ruby_proxy.gemspec +43 -0
  26. data/lib/ib_ruby_proxy.rb +35 -0
  27. data/lib/ib_ruby_proxy/client/callbacks_response_handler.rb +135 -0
  28. data/lib/ib_ruby_proxy/client/client.rb +69 -0
  29. data/lib/ib_ruby_proxy/client/ib/bar.rb +36 -0
  30. data/lib/ib_ruby_proxy/client/ib/combo_leg.rb +36 -0
  31. data/lib/ib_ruby_proxy/client/ib/contract.rb +56 -0
  32. data/lib/ib_ruby_proxy/client/ib/contract_details.rb +100 -0
  33. data/lib/ib_ruby_proxy/client/ib/delta_neutral_contract.rb +26 -0
  34. data/lib/ib_ruby_proxy/client/ib/historical_tick.rb +26 -0
  35. data/lib/ib_ruby_proxy/client/ib/historical_tick_bid_ask.rb +32 -0
  36. data/lib/ib_ruby_proxy/client/ib/historical_tick_last.rb +32 -0
  37. data/lib/ib_ruby_proxy/client/ib/order.rb +262 -0
  38. data/lib/ib_ruby_proxy/client/ib/tick_attrib_bid_ask.rb +24 -0
  39. data/lib/ib_ruby_proxy/client/ib/tick_attrib_last.rb +24 -0
  40. data/lib/ib_ruby_proxy/client/ib_callbacks_observer.rb +25 -0
  41. data/lib/ib_ruby_proxy/config.yml +43 -0
  42. data/lib/ib_ruby_proxy/server/ext/array.rb +22 -0
  43. data/lib/ib_ruby_proxy/server/ext/enum.rb +20 -0
  44. data/lib/ib_ruby_proxy/server/ext/idempotent_types.rb +23 -0
  45. data/lib/ib_ruby_proxy/server/ib/bar.rb +21 -0
  46. data/lib/ib_ruby_proxy/server/ib/combo_leg.rb +21 -0
  47. data/lib/ib_ruby_proxy/server/ib/contract.rb +31 -0
  48. data/lib/ib_ruby_proxy/server/ib/contract_details.rb +53 -0
  49. data/lib/ib_ruby_proxy/server/ib/delta_neutral_contract.rb +16 -0
  50. data/lib/ib_ruby_proxy/server/ib/historical_tick.rb +16 -0
  51. data/lib/ib_ruby_proxy/server/ib/historical_tick_bid_ask.rb +19 -0
  52. data/lib/ib_ruby_proxy/server/ib/historical_tick_last.rb +19 -0
  53. data/lib/ib_ruby_proxy/server/ib/order.rb +134 -0
  54. data/lib/ib_ruby_proxy/server/ib/tick_attrib_bid_ask.rb +15 -0
  55. data/lib/ib_ruby_proxy/server/ib/tick_attrib_last.rb +15 -0
  56. data/lib/ib_ruby_proxy/server/ib_client_adapter.rb +51 -0
  57. data/lib/ib_ruby_proxy/server/ib_proxy_service.rb +85 -0
  58. data/lib/ib_ruby_proxy/server/ib_ruby_class_files_generator.rb +77 -0
  59. data/lib/ib_ruby_proxy/server/ib_ruby_class_source_generator.rb +155 -0
  60. data/lib/ib_ruby_proxy/server/ib_wrapper_adapter.rb +47 -0
  61. data/lib/ib_ruby_proxy/server/reflection/ib_class.rb +62 -0
  62. data/lib/ib_ruby_proxy/server/reflection/ib_field.rb +60 -0
  63. data/lib/ib_ruby_proxy/util/has_logger.rb +10 -0
  64. data/lib/ib_ruby_proxy/util/string_utils.rb +29 -0
  65. data/lib/ib_ruby_proxy/version.rb +3 -0
  66. data/lib/server.rb +4 -0
  67. data/sandbox/drb_performance/client.rb +46 -0
  68. data/sandbox/drb_performance/server.rb +26 -0
  69. data/vendor/TwsApi.jar +0 -0
  70. metadata +226 -0
@@ -0,0 +1,12 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rake/testtask'
3
+
4
+ begin
5
+ require 'rspec/core/rake_task'
6
+
7
+ RSpec::Core::RakeTask.new(:spec)
8
+
9
+ task default: :spec
10
+ rescue LoadError
11
+ # no rspec available
12
+ end
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "ib_ruby_proxy"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start(__FILE__)
@@ -0,0 +1,13 @@
1
+ #!/usr/bin/env jruby
2
+
3
+ $LOAD_PATH.unshift './lib'
4
+ $LOAD_PATH.unshift File.join(__dir__, '..')
5
+ $LOAD_PATH.unshift File.join(__dir__, '..', 'lib')
6
+
7
+ require 'ib_ruby_proxy'
8
+ require 'server'
9
+
10
+ client_code_dir = File.join(__dir__, '..', 'lib', 'ib_ruby_proxy', 'client', 'ib')
11
+ server_code_dir = File.join(__dir__, '..', 'lib', 'ib_ruby_proxy', 'server', 'ib')
12
+ IbRubyProxy::Server::IbRubyClassFilesGenerator.new(client_code_dir: client_code_dir, server_code_dir: server_code_dir).generate_all
13
+
@@ -0,0 +1,32 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'rubygems'
4
+ require 'commander/import'
5
+
6
+ $LOAD_PATH.unshift './lib'
7
+ $LOAD_PATH.unshift File.join(__dir__, '..')
8
+ $LOAD_PATH.unshift File.join(__dir__, '..', 'lib')
9
+
10
+ require 'ib_ruby_proxy'
11
+ require 'server'
12
+
13
+ program :name, 'ibproxy'
14
+ program :version, IbRubyProxy::VERSION
15
+ program :description, 'Invoke Interactive Brokers API from Ruby'
16
+ default_command :server
17
+
18
+ command :server do |c|
19
+ c.syntax = 'ibproxy server [options]'
20
+ c.summary = 'Start ibproxy server'
21
+ c.option '--ib-port PORT', Integer, "Interactive brokers client port. #{IbRubyProxy::Server::IbProxyService::DEFAULT_IB_GATEWAY_PORT} by default (Gateway). Default for TWS is whatever"
22
+ c.option '--drb-port PORT', Integer, "Port for the served drb endpoint. #{IbRubyProxy::Server::IbProxyService::DEFAULT_DRB_PORT} by default"
23
+ c.action do |args, options|
24
+ options.default ib_port: IbRubyProxy::Server::IbProxyService::DEFAULT_IB_GATEWAY_PORT,
25
+ drb_port: IbRubyProxy::Server::IbProxyService::DEFAULT_DRB_PORT
26
+ ib_port = options.ib_port
27
+ drb_port = options.drb_port
28
+ puts "Starting with ib port #{ib_port} and drb port #{drb_port}..."
29
+ IbRubyProxy::Server::IbProxyService.new(ib_port: ib_port, drb_port: drb_port).start
30
+ end
31
+ end
32
+
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
Binary file
@@ -0,0 +1,14 @@
1
+ require 'drb'
2
+ require 'ib_ruby_proxy'
3
+
4
+ class Securities
5
+ class << self
6
+ def emini(expiration_month = '201909')
7
+ IbRubyProxy::Client::Ib::Contract.new symbol: 'ES',
8
+ sec_type: 'FUT',
9
+ currency: 'USD',
10
+ exchange: 'GLOBEX',
11
+ last_trade_date_or_contract_month: expiration_month
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,19 @@
1
+ require_relative './common'
2
+
3
+ client = IbRubyProxy::Client::Client.from_drb
4
+
5
+ class CallbacksObserver < IbRubyProxy::Client::IbCallbacksObserver
6
+ def historical_ticks(_request_id, ticks, _done)
7
+ ap ticks
8
+ end
9
+ end
10
+
11
+ aapl = IbRubyProxy::Client::Ib::Contract.new symbol: 'AAPL',
12
+ sec_type: 'STK',
13
+ exchange: 'ISLAND'
14
+
15
+ client.add_ib_callbacks_observer CallbacksObserver.new
16
+ client.req_historical_ticks(18009, aapl, '20190304 12:00:00', nil, 100,
17
+ 'MIDPOINT', 1, false, nil)
18
+
19
+ sleep
@@ -0,0 +1,9 @@
1
+ require_relative './common'
2
+
3
+ client = IbRubyProxy::Client::Client.from_drb
4
+
5
+ client.req_contract_details(18009, Securities.emini) do |_callback, _request_id, contract_details|
6
+ ap contract_details
7
+ end
8
+
9
+ sleep
@@ -0,0 +1,10 @@
1
+ require_relative './common'
2
+
3
+ client = IbRubyProxy::Client::Client.from_drb
4
+
5
+ client.req_historical_data(18009, Securities.emini, '20190304 17:00:01', '1 M', '1 day',
6
+ 'TRADES', 1, 1, false, nil) do |_callback, _request_id, bar|
7
+ ap bar
8
+ end
9
+
10
+ sleep
@@ -0,0 +1,10 @@
1
+ require_relative './common'
2
+
3
+ client = IbRubyProxy::Client::Client.from_drb
4
+
5
+ client.req_historical_ticks(18009, Securities.emini, nil, '20190304 17:00:00', 100,
6
+ 'MIDPOINT', 1, false, nil) do |_callback, _request_id, ticks, _done|
7
+ ap ticks
8
+ end
9
+
10
+ sleep
@@ -0,0 +1,9 @@
1
+ require_relative './common'
2
+
3
+ client = IbRubyProxy::Client::Client.from_drb
4
+
5
+ client.req_tick_by_tick_data(18002, Securities.emini, 'Last', 0, false) do |*arguments|
6
+ ap arguments
7
+ end
8
+
9
+ sleep
@@ -0,0 +1,43 @@
1
+ lib = File.expand_path('lib', __dir__)
2
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
+ require 'ib_ruby_proxy/version'
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = 'ib_ruby_proxy'
7
+ spec.version = IbRubyProxy::VERSION
8
+ spec.authors = ['Jorge Manrubia']
9
+ spec.email = ['jorge.manrubia@gmail.com']
10
+
11
+ spec.summary = 'Invoke IB Java API from Ruby'
12
+ spec.description = 'Invoke IB Java API from Ruby'
13
+ spec.homepage = 'https://github.com/jorgemanrubia/ib_ruby_proxy'
14
+ spec.license = 'MIT'
15
+
16
+ # Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
17
+ # to allow pushing to a single host or delete this section to allow pushing to any host.
18
+ if spec.respond_to?(:metadata)
19
+ spec.metadata['homepage_uri'] = spec.homepage
20
+ spec.metadata['source_code_uri'] = 'https://github.com/jorgemanrubia/ib_ruby_proxy'
21
+ else
22
+ raise 'RubyGems 2.0 or newer is required to protect against ' \
23
+ 'public gem pushes.'
24
+ end
25
+
26
+ # Specify which files should be added to the gem when it is released.
27
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
28
+ spec.files = Dir.chdir(File.expand_path(__dir__)) do
29
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
30
+ end
31
+ spec.bindir = 'exe'
32
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
33
+ spec.require_paths = ['lib']
34
+
35
+ spec.add_dependency 'awesome_print'
36
+ spec.add_dependency 'commander'
37
+ spec.add_dependency 'concurrent-ruby'
38
+ spec.add_development_dependency 'bundler', '~> 2.0.1'
39
+ spec.add_development_dependency 'impersonator'
40
+ spec.add_development_dependency 'rake', '~> 10.0'
41
+ spec.add_development_dependency 'rspec', '~> 3.8.0'
42
+ spec.add_development_dependency 'rspec_junit_formatter'
43
+ end
@@ -0,0 +1,35 @@
1
+ require 'ib_ruby_proxy/version'
2
+ require 'awesome_print'
3
+ require 'drb'
4
+ require 'logger'
5
+ require 'concurrent-ruby'
6
+ require 'yaml'
7
+
8
+ Dir["#{__dir__}/ib_ruby_proxy/util/**/*.rb"].each { |file| require file }
9
+ Dir["#{__dir__}/ib_ruby_proxy/client/**/*.rb"].each { |file| require file }
10
+
11
+ module IbRubyProxy
12
+ LOGGER_LEVEL = Logger::DEBUG
13
+
14
+ # Gem logger
15
+ #
16
+ # @return [Logger]
17
+ def self.logger
18
+ @logger ||= Logger.new(STDOUT).tap do |logger|
19
+ logger.level = LOGGER_LEVEL
20
+ end
21
+ end
22
+
23
+ # Configuration options parsed from +ib_ruby_proxy/config.yml+
24
+ #
25
+ # @return [Hash]
26
+ def self.config
27
+ @config ||= begin
28
+ file_path = File.join(__dir__, 'ib_ruby_proxy/config.yml')
29
+ YAML.load_file(file_path)
30
+ end
31
+ end
32
+
33
+ class Error < StandardError
34
+ end
35
+ end
@@ -0,0 +1,135 @@
1
+ module IbRubyProxy
2
+ module Client
3
+ # This class maps callbacks with method invocations that originated them so that
4
+ # a block can be passed to the api method and it will be invoked when a callback is
5
+ # received
6
+ class CallbacksResponseHandler
7
+ IB_CALLBACKS_MAPPING = {
8
+ req_historical_ticks: { callbacks: %i[historical_ticks historical_ticks_bid_ask
9
+ historical_ticks_last],
10
+ discriminate_by_argument_nth: 0 },
11
+ req_contract_details: { callbacks: %i[contract_details contract_details_end],
12
+ discriminate_by_argument_nth: 0 },
13
+ req_tick_by_tick_data: { callbacks: %i[tick_by_tick_bid_ask tick_by_tick_all_last
14
+ tick_by_tick_mid_point],
15
+ discriminate_by_argument_nth: 0 },
16
+ req_historical_data: { callbacks: %i[historical_data historical_data_end
17
+ historical_data_update],
18
+ discriminate_by_argument_nth: 0 }
19
+ }.freeze
20
+
21
+ def initialize
22
+ @method_handlers = {}
23
+ @callback_handlers = {}
24
+ end
25
+
26
+ # Handle invoked method
27
+ #
28
+ # @param [String, Symbol] method_name
29
+ # @param [Array<Object>] arguments
30
+ # @param [Proc] block
31
+ def method_invoked(method_name, *arguments, &block)
32
+ method_name = method_name.to_sym
33
+ method_handlers[method_name]&.method_invoked(*arguments, &block)
34
+ end
35
+
36
+ # Handle callback received: it will invoke the block passed in the corresponding
37
+ # {#method_invoked}, if any.
38
+ #
39
+ # @param [Symbol] callback_name
40
+ # @param [Array<Object>] arguments
41
+ def callback_received(callback_name, *arguments)
42
+ callback_name = callback_name.to_sym
43
+ callback_handlers[callback_name]&.callback_received(callback_name, *arguments)
44
+ end
45
+
46
+ # @private
47
+ def self.for_ib
48
+ new.tap do |handler|
49
+ IbRubyProxy.config['mapped_callbacks'].each do |method, callback_config|
50
+ nth_argument = callback_config['discriminate_by_argument_nth']
51
+ handler.configure_block_callback method: method.to_sym,
52
+ callbacks: callback_config['callbacks'],
53
+ discriminate_by_argument_nth: nth_argument
54
+ end
55
+ end
56
+ end
57
+
58
+ # Configures a mapping between a method invocation and a received callback
59
+ #
60
+ # @param [String, Symbol] method
61
+ # @param [String, Symbol] callbacks
62
+ # @param [Integer, nil] discriminate_by_argument_nth The position of the argument that
63
+ # will be used to discriminate received callbacks and match them with invocation methods.
64
+ # +nil+ indicates no argument should be used for discriminating (default)
65
+ def configure_block_callback(method:, callbacks:, discriminate_by_argument_nth: nil)
66
+ validate_can_add_callback_on_method!(method)
67
+
68
+ handler = BlockCallbackHandler.new(discriminate_by_argument_nth)
69
+
70
+ configure_callbacks_handler(callbacks, handler)
71
+ configure_method_handler(method, handler)
72
+ end
73
+
74
+ private
75
+
76
+ attr_reader :method_handlers, :callback_handlers
77
+
78
+ def configure_method_handler(method, handler)
79
+ method_handlers[method.to_sym] = handler
80
+ end
81
+
82
+ def configure_callbacks_handler(callbacks, handler)
83
+ callbacks << :error
84
+ callbacks.each do |callback_name|
85
+ callback_handlers[callback_name.to_sym] = handler
86
+ end
87
+ end
88
+
89
+ def validate_can_add_callback_on_method!(method)
90
+ raise "Already configured handler for #{method}" if method_handlers[method]
91
+ end
92
+
93
+ # b_proxy_service.rb:13@private
94
+ class BlockCallbackHandler
95
+ include IbRubyProxy::Util::HasLogger
96
+
97
+ attr_reader :discriminate_by_argument_nth, :block
98
+
99
+ def initialize(discriminate_by_argument_nth)
100
+ @discriminate_by_argument_nth = discriminate_by_argument_nth
101
+ @blocks_by_discriminator = {}
102
+ end
103
+
104
+ def method_invoked(*arguments, &block)
105
+ if @discriminate_by_argument_nth
106
+ discrminator = arguments[@discriminate_by_argument_nth]
107
+ unless discrminator
108
+ raise "No argument #{@discriminate_by_argument_nth} to discriminate with?"\
109
+ " #{arguments.inspect}"
110
+ end
111
+
112
+ @blocks_by_discriminator[discrminator] = block
113
+ else
114
+ @block = block
115
+ end
116
+ nil
117
+ end
118
+
119
+ def callback_received(callback_name, *arguments)
120
+ if callback_name.to_s == 'error'
121
+ raise StandardError, arguments.join('. ')
122
+ else
123
+ block = if @discriminate_by_argument_nth
124
+ @blocks_by_discriminator[arguments[@discriminate_by_argument_nth]]
125
+ else
126
+ @block
127
+ end
128
+
129
+ block&.call(callback_name, *arguments)
130
+ end
131
+ end
132
+ end
133
+ end
134
+ end
135
+ end
@@ -0,0 +1,69 @@
1
+ require 'drb/timeridconv'
2
+
3
+ module IbRubyProxy
4
+ module Client
5
+ # A client for interacting with the DRb service
6
+ #
7
+ # If can be instantiated directly from the DRb service with {.create_drb_ib_client}
8
+ #
9
+ # It offers the same interface than a {IbRubyProxy::Server::IbClientAdapter} and, also,
10
+ # configures a {CallbacksResponseHandler} so that you can use Ruby blocks to capture
11
+ # callbacks invocations based on the semantics of the invoked methods.
12
+ #
13
+ # @see CallbacksResponseHandler
14
+ class Client
15
+ attr_reader :ib_client
16
+
17
+ # @return [Client]
18
+ # @param [String] host DRb host +localhost+ by default
19
+ # @param [Object] port DRb port +1992+ by default
20
+ def self.from_drb(host: 'localhost', port: 1992)
21
+ new(create_drb_ib_client(host: host, port: port))
22
+ end
23
+
24
+ # @param [IbRubyProxy::Server::IbClientAdapter] ib_client
25
+ def initialize(ib_client)
26
+ @ib_client = ib_client
27
+ @promises_by_request_id = {}
28
+ @callbacks_response_handler = CallbacksResponseHandler.for_ib
29
+ @ib_client.add_ib_callbacks_observer ResponseHandleObserver.new(@callbacks_response_handler)
30
+ end
31
+
32
+ # @private
33
+ def self.create_drb_ib_client(host:, port:)
34
+ drb_ib_client = DRbObject.new(nil, "druby://#{host}:#{port}")
35
+ DRb::DRbServer.verbose = true
36
+ DRb.install_id_conv ::DRb::TimerIdConv.new 60
37
+ DRb.start_service
38
+ drb_ib_client
39
+ end
40
+
41
+ private
42
+
43
+ def respond_to_missing?(name, include_private = false)
44
+ @ib_client.respond_to?(name, include_private)
45
+ end
46
+
47
+ def method_missing(method, *arguments, &block)
48
+ @ib_client.public_send(method, *arguments, &block)
49
+ @callbacks_response_handler.method_invoked method, *arguments, &block
50
+ end
51
+ end
52
+
53
+ # An observer that will delegate callbacks to a {CallbacksResponseHandler}
54
+ #
55
+ # @private
56
+ class ResponseHandleObserver
57
+ include DRb::DRbUndumped
58
+
59
+ def initialize(callbacks_response_handler)
60
+ @callbacks_response_handler = callbacks_response_handler
61
+ end
62
+
63
+ def update(*params)
64
+ method, *arguments = params
65
+ @callbacks_response_handler.callback_received(method, *arguments)
66
+ end
67
+ end
68
+ end
69
+ end