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,62 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# This Script needs testing. remove this line after sucessfully using the scanner facility
|
3
|
+
#
|
4
|
+
# This script setups a scan request and retreives the results.
|
5
|
+
|
6
|
+
require 'rubygems'
|
7
|
+
require 'bundler/setup'
|
8
|
+
require 'ib-api'
|
9
|
+
|
10
|
+
|
11
|
+
|
12
|
+
# Connect to IB TWS.
|
13
|
+
ib = IB::Connection.new :client_id => 1112 , port: 7496 do | gw | #, :port => 7497 # TWS
|
14
|
+
|
15
|
+
|
16
|
+
# Subscribe to TWS alerts/errors
|
17
|
+
gw.subscribe(:Alert) { |msg| puts msg.to_human }
|
18
|
+
|
19
|
+
|
20
|
+
# Subscribe to ScannerData incoming events. The code passed in the block
|
21
|
+
# will be executed when a message of that type is received, with the received
|
22
|
+
# message as its argument. In this case, we just print out the data.
|
23
|
+
#
|
24
|
+
gw.subscribe(IB::Messages::Incoming::ScannerData) do |msg|
|
25
|
+
puts "ID: #{msg.request_id} : #{msg.count} items:"
|
26
|
+
msg.results.each { |entry| puts " #{entry}" }
|
27
|
+
end
|
28
|
+
end
|
29
|
+
id = 42
|
30
|
+
# Now we actually request scanner data for the type of scan we are interested.
|
31
|
+
# Some scan types can be found here: http://www.interactivebrokers.com/php/apiUsersGuide/apiguide/tables/available_market_scanners.htm
|
32
|
+
mess = IB::Messages::Outgoing::RequestScannerSubscription.new(
|
33
|
+
:request_id => id,
|
34
|
+
:number_of_rows => 20,
|
35
|
+
:instrument => "STK",
|
36
|
+
:location_code => 'STK.US.MAJOR',
|
37
|
+
:scan_code => 'TOP_PERC_GAIN',
|
38
|
+
:above_price => 4.0,
|
39
|
+
:below_price => Float::MAX,
|
40
|
+
:above_volume => 5000,
|
41
|
+
:market_cap_above => 100000000,
|
42
|
+
:market_cap_below => Float::MAX,
|
43
|
+
:moody_rating_above => "",
|
44
|
+
:moody_rating_below => "",
|
45
|
+
:sp_rating_above => "",
|
46
|
+
:sp_rating_below => "",
|
47
|
+
:maturity_date_above => "",
|
48
|
+
:maturity_date_below => "",
|
49
|
+
:coupon_rate_above => Float::MAX,
|
50
|
+
:coupon_rate_below => Float::MAX,
|
51
|
+
:exclude_convertible => "",
|
52
|
+
:average_option_volume_above => "", # ?
|
53
|
+
:scanner_setting_pairs => "Annual,true",
|
54
|
+
:stock_type_filter => "Stock"
|
55
|
+
)
|
56
|
+
|
57
|
+
ib.send_message( mess )
|
58
|
+
|
59
|
+
# IB does not send any indication when all data is done being delivered.
|
60
|
+
# So we need to interrupt manually when our request is answered.
|
61
|
+
puts "\n******** Press <Enter> to exit... *********\n\n"
|
62
|
+
STDIN.gets
|
data/example/template
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# Your script description here...
|
4
|
+
|
5
|
+
require 'bundler/setup'
|
6
|
+
require 'ib-api'
|
7
|
+
|
8
|
+
# First, connect to IB TWS and subscribe for events.
|
9
|
+
ib = IB::Connection.new :client_id => 1112 do | gw | #, :port => 7497 # TWS
|
10
|
+
|
11
|
+
# Subscribe to TWS alerts/errors
|
12
|
+
gw.subscribe(:Alert, :ManagedAccounts) { |msg| puts msg.to_human }
|
13
|
+
# Set log level
|
14
|
+
gw.logger.level = Logger::FATAL # DEBUG -- INFO -- WARN -- ERROR -- FATAL
|
15
|
+
|
16
|
+
end
|
17
|
+
# Put your code here
|
18
|
+
# ...
|
19
|
+
|
data/example/tick_data
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
# This script reproduces https://github.com/ib-ruby/ib-ruby/issues/12
|
4
|
+
|
5
|
+
require 'bundler/setup'
|
6
|
+
require 'ib-api'
|
7
|
+
|
8
|
+
contract = IB::Stock.new :symbol=> 'AAPL'
|
9
|
+
|
10
|
+
# First, connect to IB Gateway.
|
11
|
+
ib = IB::Connection.new :client_id => 1112 # id to identify your script
|
12
|
+
# :port => 7497 # TWS connection (instead of default Gateway)
|
13
|
+
# :received => false # Do not keep all received messages in memory
|
14
|
+
|
15
|
+
ib.subscribe(:Alert) { |msg| puts msg.to_human }
|
16
|
+
ib.subscribe(:MarketDataType) { |msg| puts msg.to_human }
|
17
|
+
ib.subscribe(:TickGeneric, :TickString, :TickPrice, :TickSize) { |msg| puts msg.inspect }
|
18
|
+
ib.send_message :RequestMarketDataType, :market_data_type => :delayed
|
19
|
+
ib.send_message :RequestMarketData, id: 123, contract: contract
|
20
|
+
|
21
|
+
puts "\nSubscribed to market data"
|
22
|
+
puts "\n******** Press <Enter> to cancel... *********\n\n"
|
23
|
+
gets
|
24
|
+
puts "Cancelling market data subscription.."
|
25
|
+
|
26
|
+
ib.send_message :CancelMarketData, id: 123
|
27
|
+
sleep 1
|
28
|
+
puts "Done."
|
@@ -0,0 +1,87 @@
|
|
1
|
+
module CoreExtensions
|
2
|
+
module Array
|
3
|
+
module DuplicatesCounter
|
4
|
+
def count_duplicates
|
5
|
+
self.each_with_object(Hash.new(0)) { |element, counter| counter[element] += 1 }.sort_by{|k,v| -v}.to_h
|
6
|
+
end
|
7
|
+
end
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
Array.include CoreExtensions::Array::DuplicatesCounter
|
12
|
+
|
13
|
+
|
14
|
+
|
15
|
+
|
16
|
+
class Time
|
17
|
+
# Render datetime in IB format (zero padded "yyyymmdd HH:mm:ss")
|
18
|
+
def to_ib
|
19
|
+
"#{year}#{sprintf("%02d", month)}#{sprintf("%02d", day)} " +
|
20
|
+
"#{sprintf("%02d", hour)}:#{sprintf("%02d", min)}:#{sprintf("%02d", sec)}"
|
21
|
+
end
|
22
|
+
end # Time
|
23
|
+
|
24
|
+
class Numeric
|
25
|
+
# Conversion 0/1 into true/false
|
26
|
+
def to_bool
|
27
|
+
self == 0 ? false : true
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
class TrueClass
|
32
|
+
def to_bool
|
33
|
+
self
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
class FalseClass
|
38
|
+
def to_bool
|
39
|
+
self
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
class String
|
44
|
+
def to_bool
|
45
|
+
case self.chomp.upcase
|
46
|
+
when 'TRUE', 'T', '1'
|
47
|
+
true
|
48
|
+
when 'FALSE', 'F', '0', ''
|
49
|
+
false
|
50
|
+
else
|
51
|
+
error "Unable to convert #{self} to bool"
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
class NilClass
|
57
|
+
# We still need to pass on nil, meaning: no value
|
58
|
+
def to_bool
|
59
|
+
self
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
class Symbol
|
64
|
+
def to_f
|
65
|
+
0
|
66
|
+
end
|
67
|
+
|
68
|
+
# ActiveModel serialization depends on this method
|
69
|
+
def <=> other
|
70
|
+
to_s <=> other.to_s
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
class Object
|
75
|
+
# We still need to pass on nil, meaning: no value
|
76
|
+
def to_sup
|
77
|
+
self.to_s.upcase unless self.nil?
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
### Patching Object#error in ib/errors
|
82
|
+
# def error message, type=:standard
|
83
|
+
|
84
|
+
### Patching Object#log, #default_logger= in ib/logger
|
85
|
+
# def default_logger
|
86
|
+
# def default_logger= logger
|
87
|
+
# def log *args
|
data/lib/ib-api.rb
ADDED
data/lib/ib/base.rb
ADDED
@@ -0,0 +1,103 @@
|
|
1
|
+
module IB
|
2
|
+
|
3
|
+
# Base class for tableless IB data Models, extends ActiveModel API
|
4
|
+
class Base
|
5
|
+
extend ActiveModel::Naming
|
6
|
+
extend ActiveModel::Callbacks
|
7
|
+
include ActiveModel::Validations
|
8
|
+
include ActiveModel::Serialization
|
9
|
+
#include ActiveModel::Serializers::Xml
|
10
|
+
include ActiveModel::Serializers::JSON
|
11
|
+
|
12
|
+
define_model_callbacks :initialize
|
13
|
+
|
14
|
+
# If a opts hash is given, keys are taken as attribute names, values as data.
|
15
|
+
# The model instance fields are then set automatically from the opts Hash.
|
16
|
+
def initialize attributes={}, opts={}
|
17
|
+
run_callbacks :initialize do
|
18
|
+
error "Argument must be a Hash", :args unless attributes.is_a?(Hash)
|
19
|
+
|
20
|
+
self.attributes = attributes # set_attribute_defaults is now after_init callback
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
# ActiveModel API (for serialization)
|
25
|
+
|
26
|
+
def attributes
|
27
|
+
@attributes ||= Hash.new #HashWithIndifferentAccess.new
|
28
|
+
end
|
29
|
+
|
30
|
+
def attributes= attrs
|
31
|
+
attrs.keys.each { |key| self.send("#{key}=", attrs[key]) }
|
32
|
+
end
|
33
|
+
|
34
|
+
# ActiveModel-style read/write_attribute accessors
|
35
|
+
def [] key
|
36
|
+
attributes[key.to_sym]
|
37
|
+
end
|
38
|
+
|
39
|
+
def update_attribute key, value
|
40
|
+
@attributes[key.to_sym] = value
|
41
|
+
end
|
42
|
+
|
43
|
+
def []= key, val
|
44
|
+
# p key, val
|
45
|
+
attributes[key.to_sym] = val
|
46
|
+
end
|
47
|
+
|
48
|
+
def to_model
|
49
|
+
self
|
50
|
+
end
|
51
|
+
|
52
|
+
def new_record?
|
53
|
+
true
|
54
|
+
end
|
55
|
+
|
56
|
+
def save
|
57
|
+
valid?
|
58
|
+
end
|
59
|
+
|
60
|
+
alias save! save
|
61
|
+
|
62
|
+
### Noop methods mocking ActiveRecord::Base macros
|
63
|
+
|
64
|
+
def self.attr_protected *args
|
65
|
+
end
|
66
|
+
|
67
|
+
def self.attr_accessible *args
|
68
|
+
end
|
69
|
+
|
70
|
+
### ActiveRecord::Base association API mocks
|
71
|
+
|
72
|
+
def self.belongs_to model, *args
|
73
|
+
attr_accessor model
|
74
|
+
end
|
75
|
+
|
76
|
+
def self.has_one model, *args
|
77
|
+
attr_accessor model
|
78
|
+
end
|
79
|
+
|
80
|
+
def self.has_many models, *args
|
81
|
+
attr_accessor models
|
82
|
+
|
83
|
+
define_method(models) do
|
84
|
+
self.instance_variable_get("@#{models}") ||
|
85
|
+
self.instance_variable_set("@#{models}", [])
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def self.find *args
|
90
|
+
[]
|
91
|
+
end
|
92
|
+
|
93
|
+
### ActiveRecord::Base callback API mocks
|
94
|
+
|
95
|
+
define_model_callbacks :initialize, :only => :after
|
96
|
+
|
97
|
+
### ActiveRecord::Base misc
|
98
|
+
|
99
|
+
def self.serialize *properties
|
100
|
+
end
|
101
|
+
|
102
|
+
end # Model
|
103
|
+
end # module IB
|
@@ -0,0 +1,160 @@
|
|
1
|
+
require 'active_model'
|
2
|
+
require 'active_support/concern'
|
3
|
+
#require 'active_support/hash_with_indifferent_access'
|
4
|
+
|
5
|
+
module IB
|
6
|
+
|
7
|
+
# Module adds prop Macro and
|
8
|
+
module BaseProperties
|
9
|
+
extend ActiveSupport::Concern
|
10
|
+
|
11
|
+
### Instance methods
|
12
|
+
|
13
|
+
# Default presentation
|
14
|
+
def to_human
|
15
|
+
"<#{self.class.to_s.demodulize}: " + attributes.map do |attr, value|
|
16
|
+
"#{attr}: #{value}" unless value.nil?
|
17
|
+
end.compact.sort.join(' ') + ">"
|
18
|
+
end
|
19
|
+
|
20
|
+
# Comparison support
|
21
|
+
def content_attributes
|
22
|
+
#HashWithIndifferentAccess[attributes.reject do |(attr, _)|
|
23
|
+
#NoMethodError if a Hash is assigned to an attribute
|
24
|
+
Hash[attributes.reject do |(attr, _)|
|
25
|
+
attr.to_s =~ /(_count)\z/ ||
|
26
|
+
[:created_at, :updated_at, :type,
|
27
|
+
:id, :order_id, :contract_id].include?(attr.to_sym)
|
28
|
+
end]
|
29
|
+
end
|
30
|
+
|
31
|
+
=begin
|
32
|
+
Remove all Time-Stamps from the list of Attributes
|
33
|
+
=end
|
34
|
+
def invariant_attributes
|
35
|
+
attributes.reject{|x| x =~ /_at/}
|
36
|
+
end
|
37
|
+
|
38
|
+
# Update nil attributes from given Hash or model
|
39
|
+
def update_missing attrs
|
40
|
+
attrs = attrs.content_attributes unless attrs.kind_of?(Hash)
|
41
|
+
|
42
|
+
attrs.each { |attr, val| send "#{attr}=", val if send(attr).blank? }
|
43
|
+
self # for chaining
|
44
|
+
end
|
45
|
+
|
46
|
+
# Default Model comparison
|
47
|
+
def == other
|
48
|
+
case other
|
49
|
+
when String # Probably a Rails URI, delegate to AR::Base
|
50
|
+
super(other)
|
51
|
+
else
|
52
|
+
content_attributes.keys.inject(true) { |res, key|
|
53
|
+
res && other.respond_to?(key) && (send(key) == other.send(key)) }
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
### Default attributes support
|
58
|
+
|
59
|
+
def default_attributes
|
60
|
+
{:created_at => Time.now
|
61
|
+
# :updated_at => Time.now,
|
62
|
+
}
|
63
|
+
end
|
64
|
+
|
65
|
+
def set_attribute_defaults
|
66
|
+
default_attributes.each do |key, val|
|
67
|
+
self.send("#{key}=", val) if self.send(key).nil?
|
68
|
+
# self.send("#{key}=", val) if self[key].nil? # Problems with association defaults
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
included do
|
73
|
+
|
74
|
+
after_initialize :set_attribute_defaults
|
75
|
+
|
76
|
+
### Class macros
|
77
|
+
|
78
|
+
def self.prop *properties
|
79
|
+
prop_hash = properties.last.is_a?(Hash) ? properties.pop : {}
|
80
|
+
|
81
|
+
properties.each { |names| define_property names, nil }
|
82
|
+
prop_hash.each { |names, type| define_property names, type }
|
83
|
+
end
|
84
|
+
|
85
|
+
def self.define_property names, body
|
86
|
+
aliases = [names].flatten
|
87
|
+
name = aliases.shift
|
88
|
+
instance_eval do
|
89
|
+
|
90
|
+
define_property_methods name, body
|
91
|
+
|
92
|
+
aliases.each do |ali|
|
93
|
+
alias_method "#{ali}", name
|
94
|
+
alias_method "#{ali}=", "#{name}="
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def self.define_property_methods name, body={}
|
100
|
+
#p name, body
|
101
|
+
case body
|
102
|
+
when '' # default getter and setter
|
103
|
+
define_property_methods name
|
104
|
+
|
105
|
+
when Array # [setter, getter, validators]
|
106
|
+
define_property_methods name,
|
107
|
+
:get => body[0],
|
108
|
+
:set => body[1],
|
109
|
+
:validate => body[2]
|
110
|
+
|
111
|
+
when Hash # recursion base case
|
112
|
+
getter = case # Define getter
|
113
|
+
when body[:get].respond_to?(:call)
|
114
|
+
body[:get]
|
115
|
+
when body[:get]
|
116
|
+
proc { self[name].send "to_#{body[:get]}" }
|
117
|
+
when VALUES[name] # property is encoded
|
118
|
+
proc { VALUES[name][self[name]] }
|
119
|
+
else
|
120
|
+
proc { self[name] }
|
121
|
+
end
|
122
|
+
define_method name, &getter if getter
|
123
|
+
|
124
|
+
setter = case # Define setter
|
125
|
+
when body[:set].respond_to?(:call)
|
126
|
+
body[:set]
|
127
|
+
when body[:set]
|
128
|
+
proc { |value| self[name] = value.send "to_#{body[:set]}" }
|
129
|
+
when CODES[name] # property is encoded
|
130
|
+
proc { |value| self[name] = CODES[name][value] || value }
|
131
|
+
else
|
132
|
+
proc { |value| self[name] = value } # p name, value;
|
133
|
+
end
|
134
|
+
define_method "#{name}=", &setter if setter
|
135
|
+
|
136
|
+
# Define validator(s)
|
137
|
+
[body[:validate]].flatten.compact.each do |validator|
|
138
|
+
case validator
|
139
|
+
when Proc
|
140
|
+
validates_each name, &validator
|
141
|
+
when Hash
|
142
|
+
validates name, validator.dup
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
# TODO define self[:name] accessors for :virtual and :flag properties
|
147
|
+
|
148
|
+
else # setter given
|
149
|
+
define_property_methods name, :set => body, :get => body
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
# Timestamps in lightweight models
|
154
|
+
unless defined?(ActiveRecord::Base) && ancestors.include?(ActiveRecord::Base)
|
155
|
+
prop :created_at #, :updated_at
|
156
|
+
end
|
157
|
+
|
158
|
+
end # included
|
159
|
+
end # module BaseProperties
|
160
|
+
end
|