ib-api 972.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|