ib-api 10.33.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 +7 -0
- data/.gitignore +52 -0
- data/.rspec +3 -0
- data/.travis.yml +7 -0
- data/CLAUDE.md +131 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +17 -0
- data/Gemfile.lock +120 -0
- data/Guardfile +24 -0
- data/LICENSE +674 -0
- data/LLM_GUIDE.md +388 -0
- data/README.md +114 -0
- data/Rakefile +11 -0
- data/VERSION +1 -0
- data/api.gemspec +50 -0
- data/bin/console +96 -0
- data/bin/console.yml +3 -0
- data/bin/setup +8 -0
- data/bin/simple +91 -0
- data/changelog.md +32 -0
- data/conditions/ib/execution_condition.rb +31 -0
- data/conditions/ib/margin_condition.rb +28 -0
- data/conditions/ib/order_condition.rb +29 -0
- data/conditions/ib/percent_change_condition.rb +34 -0
- data/conditions/ib/price_condition.rb +44 -0
- data/conditions/ib/time_condition.rb +42 -0
- data/conditions/ib/volume_condition.rb +36 -0
- data/lib/class_extensions.rb +167 -0
- data/lib/ib/base.rb +109 -0
- data/lib/ib/base_properties.rb +178 -0
- data/lib/ib/connection.rb +573 -0
- data/lib/ib/constants.rb +402 -0
- data/lib/ib/contract.rb +30 -0
- data/lib/ib/errors.rb +52 -0
- data/lib/ib/messages/abstract_message.rb +68 -0
- data/lib/ib/messages/incoming/abstract_message.rb +116 -0
- data/lib/ib/messages/incoming/abstract_tick.rb +25 -0
- data/lib/ib/messages/incoming/account_message.rb +26 -0
- data/lib/ib/messages/incoming/alert.rb +34 -0
- data/lib/ib/messages/incoming/contract_data.rb +105 -0
- data/lib/ib/messages/incoming/contract_message.rb +13 -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/histogram_data.rb +30 -0
- data/lib/ib/messages/incoming/historical_data.rb +65 -0
- data/lib/ib/messages/incoming/historical_data_update.rb +50 -0
- data/lib/ib/messages/incoming/managed_accounts.rb +21 -0
- data/lib/ib/messages/incoming/market_depth.rb +34 -0
- data/lib/ib/messages/incoming/market_depth_l2.rb +15 -0
- data/lib/ib/messages/incoming/next_valid_id.rb +19 -0
- data/lib/ib/messages/incoming/open_order.rb +290 -0
- data/lib/ib/messages/incoming/order_status.rb +85 -0
- data/lib/ib/messages/incoming/portfolio_value.rb +47 -0
- data/lib/ib/messages/incoming/position_data.rb +21 -0
- data/lib/ib/messages/incoming/positions_multi.rb +15 -0
- data/lib/ib/messages/incoming/real_time_bar.rb +32 -0
- data/lib/ib/messages/incoming/receive_fa.rb +30 -0
- data/lib/ib/messages/incoming/scanner_data.rb +54 -0
- data/lib/ib/messages/incoming/tick_by_tick.rb +77 -0
- data/lib/ib/messages/incoming/tick_efp.rb +18 -0
- data/lib/ib/messages/incoming/tick_generic.rb +12 -0
- data/lib/ib/messages/incoming/tick_option.rb +60 -0
- data/lib/ib/messages/incoming/tick_price.rb +60 -0
- data/lib/ib/messages/incoming/tick_size.rb +55 -0
- data/lib/ib/messages/incoming/tick_string.rb +13 -0
- data/lib/ib/messages/incoming.rb +292 -0
- data/lib/ib/messages/outgoing/abstract_message.rb +84 -0
- data/lib/ib/messages/outgoing/bar_request_message.rb +247 -0
- data/lib/ib/messages/outgoing/new-place-order.rb +193 -0
- data/lib/ib/messages/outgoing/old-place-order.rb +147 -0
- data/lib/ib/messages/outgoing/place_order.rb +149 -0
- data/lib/ib/messages/outgoing/request_account_summary.rb +79 -0
- data/lib/ib/messages/outgoing/request_historical_data.rb +182 -0
- data/lib/ib/messages/outgoing/request_market_data.rb +102 -0
- data/lib/ib/messages/outgoing/request_market_depth.rb +57 -0
- data/lib/ib/messages/outgoing/request_real_time_bars.rb +48 -0
- data/lib/ib/messages/outgoing/request_scanner_subscription.rb +73 -0
- data/lib/ib/messages/outgoing/request_tick_by_tick_data.rb +21 -0
- data/lib/ib/messages/outgoing.rb +410 -0
- data/lib/ib/messages.rb +139 -0
- data/lib/ib/order_condition.rb +26 -0
- data/lib/ib/plugins.rb +27 -0
- data/lib/ib/prepare_data.rb +61 -0
- data/lib/ib/raw_message_parser.rb +99 -0
- data/lib/ib/socket.rb +83 -0
- data/lib/ib/support.rb +236 -0
- data/lib/ib/version.rb +6 -0
- data/lib/ib-api.rb +44 -0
- data/lib/server_versions.rb +145 -0
- data/lib/support/array_function.rb +28 -0
- data/lib/support/logging.rb +45 -0
- data/models/ib/account.rb +72 -0
- data/models/ib/account_value.rb +33 -0
- data/models/ib/bag.rb +55 -0
- data/models/ib/bar.rb +31 -0
- data/models/ib/combo_leg.rb +127 -0
- data/models/ib/contract.rb +411 -0
- data/models/ib/contract_detail.rb +118 -0
- data/models/ib/execution.rb +67 -0
- data/models/ib/forex.rb +12 -0
- data/models/ib/future.rb +64 -0
- data/models/ib/index.rb +14 -0
- data/models/ib/option.rb +149 -0
- data/models/ib/option_detail.rb +84 -0
- data/models/ib/order.rb +720 -0
- data/models/ib/order_state.rb +155 -0
- data/models/ib/portfolio_value.rb +86 -0
- data/models/ib/spread.rb +176 -0
- data/models/ib/stock.rb +25 -0
- data/models/ib/underlying.rb +32 -0
- data/plugins/ib/advanced-account.rb +442 -0
- data/plugins/ib/alerts/base-alert.rb +125 -0
- data/plugins/ib/alerts/gateway-alerts.rb +15 -0
- data/plugins/ib/alerts/order-alerts.rb +73 -0
- data/plugins/ib/auto-adjust.rb +0 -0
- data/plugins/ib/connection-tools.rb +122 -0
- data/plugins/ib/eod.rb +326 -0
- data/plugins/ib/greeks.rb +102 -0
- data/plugins/ib/managed-accounts.rb +274 -0
- data/plugins/ib/market-price.rb +150 -0
- data/plugins/ib/option-chain.rb +167 -0
- data/plugins/ib/order-flow.rb +157 -0
- data/plugins/ib/order-prototypes/abstract.rb +67 -0
- data/plugins/ib/order-prototypes/adaptive.rb +40 -0
- data/plugins/ib/order-prototypes/all-in-one.rb +46 -0
- data/plugins/ib/order-prototypes/combo.rb +46 -0
- data/plugins/ib/order-prototypes/forex.rb +40 -0
- data/plugins/ib/order-prototypes/limit.rb +193 -0
- data/plugins/ib/order-prototypes/market.rb +116 -0
- data/plugins/ib/order-prototypes/pegged.rb +169 -0
- data/plugins/ib/order-prototypes/premarket.rb +31 -0
- data/plugins/ib/order-prototypes/stop.rb +202 -0
- data/plugins/ib/order-prototypes/volatility.rb +39 -0
- data/plugins/ib/order-prototypes.rb +118 -0
- data/plugins/ib/probability-of-expiring.rb +109 -0
- data/plugins/ib/process-orders.rb +155 -0
- data/plugins/ib/roll.rb +86 -0
- data/plugins/ib/spread-prototypes/butterfly.rb +77 -0
- data/plugins/ib/spread-prototypes/calendar.rb +97 -0
- data/plugins/ib/spread-prototypes/stock-spread.rb +56 -0
- data/plugins/ib/spread-prototypes/straddle.rb +70 -0
- data/plugins/ib/spread-prototypes/strangle.rb +93 -0
- data/plugins/ib/spread-prototypes/vertical.rb +83 -0
- data/plugins/ib/spread-prototypes.rb +70 -0
- data/plugins/ib/symbols/abstract.rb +136 -0
- data/plugins/ib/symbols/bonds.rb +28 -0
- data/plugins/ib/symbols/cfd.rb +19 -0
- data/plugins/ib/symbols/combo.rb +46 -0
- data/plugins/ib/symbols/commodity.rb +17 -0
- data/plugins/ib/symbols/forex.rb +41 -0
- data/plugins/ib/symbols/futures.rb +127 -0
- data/plugins/ib/symbols/index.rb +43 -0
- data/plugins/ib/symbols/options.rb +99 -0
- data/plugins/ib/symbols/stocks.rb +44 -0
- data/plugins/ib/symbols/version.rb +5 -0
- data/plugins/ib/symbols.rb +118 -0
- data/plugins/ib/verify.rb +226 -0
- data/symbols/w20.yml +210 -0
- data/t.txt +20 -0
- data/update.md +71 -0
- metadata +327 -0
data/lib/ib/plugins.rb
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
module IB
|
|
2
|
+
module Plugins
|
|
3
|
+
def activate_plugin *names
|
|
4
|
+
root= Pathname(__dir__).parent.parent
|
|
5
|
+
names.map{|y| y.to_s.gsub("_","-")}.each do |n|
|
|
6
|
+
unless @plugins.include? n
|
|
7
|
+
# root= base directory of the ib-api source
|
|
8
|
+
# plugins are defined in ib-api/plugins/ib
|
|
9
|
+
filename = root.join( "plugins", "ib", n+".rb" )
|
|
10
|
+
if filename.exist?
|
|
11
|
+
if require filename
|
|
12
|
+
@plugins << n
|
|
13
|
+
true # return value
|
|
14
|
+
else
|
|
15
|
+
error "Could not load Plugin `#{n}` --> #{filename} "
|
|
16
|
+
end
|
|
17
|
+
else
|
|
18
|
+
error "Plugin `#{n}` not found in `plugins/ib/`"
|
|
19
|
+
nil
|
|
20
|
+
end
|
|
21
|
+
else
|
|
22
|
+
IB::Connection.logger.debug "Already activated plugin #{n}"
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
module IB
|
|
2
|
+
# includes methods from IB:.Support
|
|
3
|
+
# which adds a tws-method to
|
|
4
|
+
# - Array
|
|
5
|
+
# - Symbol
|
|
6
|
+
# - String
|
|
7
|
+
# - Numeric
|
|
8
|
+
# - TrueClass, FalseClass and NilClass
|
|
9
|
+
#
|
|
10
|
+
module PrepareData
|
|
11
|
+
using IB::Support
|
|
12
|
+
# First call the method #tws on the data-object
|
|
13
|
+
#
|
|
14
|
+
# Then transfom into an Array using the #Pack-Method
|
|
15
|
+
#
|
|
16
|
+
# The optional Block introduces a user-defined pattern to pack the data.
|
|
17
|
+
#
|
|
18
|
+
# Default is "Na*"
|
|
19
|
+
def prepare_message data
|
|
20
|
+
data = data.tws unless data.is_a?(String) && data[-1]== EOL
|
|
21
|
+
matrize = [data.size,data]
|
|
22
|
+
if block_given? # A user defined decoding-sequence is accepted via block
|
|
23
|
+
matrize.pack yield
|
|
24
|
+
else
|
|
25
|
+
matrize.pack "Na*"
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# The received package is decoded. The parameter (msg) is an Array
|
|
30
|
+
#
|
|
31
|
+
# The protocol is simple: Every Element is treated as Character.
|
|
32
|
+
# Exception: The first Element determines the expected length.
|
|
33
|
+
#
|
|
34
|
+
# The decoded raw-message can further modified by the optional block.
|
|
35
|
+
#
|
|
36
|
+
# The default is to instantiate a Hash: message_id becomes the key.
|
|
37
|
+
# The Hash is returned
|
|
38
|
+
#
|
|
39
|
+
# If a block is provided, no Hash is build and the modified raw-message is returned
|
|
40
|
+
def decode_message msg
|
|
41
|
+
m = Hash.new
|
|
42
|
+
while not msg.blank?
|
|
43
|
+
# the first item is the length
|
|
44
|
+
size = msg[ 0 .. 4 ].unpack( "N" ).first
|
|
45
|
+
msg = msg[ 4 .. -1 ]
|
|
46
|
+
# followed by a sequence of characters
|
|
47
|
+
message = msg.unpack( "A#{size}" ).first.split( "\0" )
|
|
48
|
+
# DEBUG display raw decoded message on STDOUT
|
|
49
|
+
# STDOUT::puts "message: #{message}"
|
|
50
|
+
if block_given?
|
|
51
|
+
yield message
|
|
52
|
+
else
|
|
53
|
+
m[ message.shift.to_i ] = message
|
|
54
|
+
end
|
|
55
|
+
msg = msg[ size .. -1 ]
|
|
56
|
+
end
|
|
57
|
+
return m unless m == {}
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
end
|
|
61
|
+
end
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module IB
|
|
4
|
+
# Convert data passed in from a TCP socket stream, and convert into raw messages. The messages
|
|
5
|
+
class RawMessageParser
|
|
6
|
+
HEADER_LNGTH = 4
|
|
7
|
+
def initialize socket
|
|
8
|
+
@socket = socket
|
|
9
|
+
@data = String.new
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def each
|
|
13
|
+
append_new_data
|
|
14
|
+
|
|
15
|
+
while valid_data?
|
|
16
|
+
# puts "looping: #{@data.inspect}"
|
|
17
|
+
|
|
18
|
+
length = next_msg_length
|
|
19
|
+
validate_data_header(length)
|
|
20
|
+
|
|
21
|
+
raw = grab_message(length)
|
|
22
|
+
validate_message_footer(raw, length)
|
|
23
|
+
msg = parse_message(raw, length)
|
|
24
|
+
remove_message
|
|
25
|
+
yield msg
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def valid_data?
|
|
30
|
+
# Make sure message length is available
|
|
31
|
+
return false unless length_data?
|
|
32
|
+
|
|
33
|
+
# Based on the length, do we have
|
|
34
|
+
# enough data to process a full
|
|
35
|
+
# message?
|
|
36
|
+
return false unless enough_data?
|
|
37
|
+
|
|
38
|
+
true
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# extract message and convert to
|
|
42
|
+
# an array split by null characters.
|
|
43
|
+
def grab_message(length)
|
|
44
|
+
@data.byteslice(HEADER_LNGTH, length)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def parse_message(raw, length)
|
|
48
|
+
raw.unpack1("A#{length}").split("\0")
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def remove_message
|
|
52
|
+
length = next_msg_length
|
|
53
|
+
leftovers = @data.byteslice( length + HEADER_LNGTH..-1 )
|
|
54
|
+
@data = if leftovers.nil?
|
|
55
|
+
String.new
|
|
56
|
+
else
|
|
57
|
+
leftovers
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def enough_data?
|
|
62
|
+
actual_lngth = next_msg_length + HEADER_LNGTH
|
|
63
|
+
if next_msg_length.nil?
|
|
64
|
+
Connection.current.logger.warn {"too little data --> #{@data} "}
|
|
65
|
+
false
|
|
66
|
+
else
|
|
67
|
+
@data.bytesize >= actual_lngth
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def length_data?
|
|
72
|
+
@data.bytesize > HEADER_LNGTH
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def next_msg_length
|
|
76
|
+
# can't check length if first 4 bytes don't exist
|
|
77
|
+
length = @data.byteslice(0..3).unpack1('N')
|
|
78
|
+
length.nil? ? 0 : length
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def append_new_data
|
|
82
|
+
@data += @socket.recvfrom(4096)[0]
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def validate_message_footer(msg, _length)
|
|
86
|
+
last = msg.bytesize
|
|
87
|
+
last_byte = msg.byteslice(last - 1, last)
|
|
88
|
+
raise 'Could not validate last byte' if last_byte.nil?
|
|
89
|
+
raise "Message has an invalid last byte. expecting \0, received: #{last_byte}" if last_byte != "\0"
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def validate_data_header(length) # disabled
|
|
93
|
+
# todo:: verify length according to the expected dataframe
|
|
94
|
+
# RequestHistoryData returns large datastreams
|
|
95
|
+
return true# if length <= 5000
|
|
96
|
+
raise "Message is longer then max length (#{length}/5000)"
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
data/lib/ib/socket.rb
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
module IB
|
|
2
|
+
# includes methods from IB:.Support
|
|
3
|
+
# which adds a tws-method to
|
|
4
|
+
# - Array
|
|
5
|
+
# - Symbol
|
|
6
|
+
# - String
|
|
7
|
+
# - Numeric
|
|
8
|
+
# - TrueClass, FalseClass and NilClass
|
|
9
|
+
#
|
|
10
|
+
class Socket < TCPSocket
|
|
11
|
+
include IB::PrepareData
|
|
12
|
+
using IB::Support
|
|
13
|
+
|
|
14
|
+
def initialising_handshake
|
|
15
|
+
v100_prefix = "API".tws.encode 'ascii'
|
|
16
|
+
v100_version = self.prepare_message Messages::SERVER_VERSION
|
|
17
|
+
write_data v100_prefix+v100_version
|
|
18
|
+
## start tws-log
|
|
19
|
+
# [QO] INFO [JTS-SocketListener-49] - State: HEADER, IsAPI: UNKNOWN
|
|
20
|
+
# [QO] INFO [JTS-SocketListener-49] - State: STOP, IsAPI: YES
|
|
21
|
+
# [QO] INFO [JTS-SocketListener-49] - ArEServer: Adding 392382055 with id 2147483647
|
|
22
|
+
# [QO] INFO [JTS-SocketListener-49] - eServersChanged: 1
|
|
23
|
+
# [QO] INFO [JTS-EServerSocket-287] - [2147483647:136:136:1:0:0:0:SYS] Starting new conversation with client on 127.0.0.1
|
|
24
|
+
# [QO] INFO [JTS-EServerSocketNotifier-288] - Starting async queue thread
|
|
25
|
+
# [QO] INFO [JTS-EServerSocket-287] - [2147483647:136:136:1:0:0:0:SYS] Server version is 136
|
|
26
|
+
# [QO] INFO [JTS-EServerSocket-287] - [2147483647:136:136:1:0:0:0:SYS] Client version is 136
|
|
27
|
+
# [QO] INFO [JTS-EServerSocket-287] - [2147483647:136:136:1:0:0:0:SYS] is 3rdParty true
|
|
28
|
+
## end tws-log
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def read_string
|
|
33
|
+
string = self.gets(EOL)
|
|
34
|
+
|
|
35
|
+
until string
|
|
36
|
+
# Silently ignores nils
|
|
37
|
+
string = self.gets(EOL)
|
|
38
|
+
sleep 0.1
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
string.chomp
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
# Sends null terminated data string into socket
|
|
46
|
+
def write_data data
|
|
47
|
+
self.syswrite data.tws
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# send the message (containing several instructions) to the socket,
|
|
51
|
+
# calls prepare_message to convert data-elements into NULL-terminated strings
|
|
52
|
+
def send_messages *data
|
|
53
|
+
self.syswrite prepare_message(data)
|
|
54
|
+
rescue Errno::ECONNRESET => e
|
|
55
|
+
Connection.logger.fatal{ "Data not accepted by IB \n
|
|
56
|
+
#{data.inspect} \n
|
|
57
|
+
Backtrace:\n "}
|
|
58
|
+
Connection.logger.error e.backtrace
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def receive_messages
|
|
62
|
+
begin
|
|
63
|
+
complete_message_buffer = []
|
|
64
|
+
begin
|
|
65
|
+
# this is the blocking version of recv
|
|
66
|
+
buffer = self.recvfrom(8192)[0]
|
|
67
|
+
# STDOUT.puts "BUFFER:: #{buffer.inspect}"
|
|
68
|
+
complete_message_buffer << buffer
|
|
69
|
+
|
|
70
|
+
end while buffer.size == 8192
|
|
71
|
+
complete_message_buffer.join('')
|
|
72
|
+
rescue Errno::ECONNRESET => e
|
|
73
|
+
Connection.logger.fatal{ "Data Buffer is not filling \n
|
|
74
|
+
The Buffer: #{buffer.inspect} \n
|
|
75
|
+
Backtrace:\n
|
|
76
|
+
#{e.backtrace.join("\n") } " }
|
|
77
|
+
Kernel.exit
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
end # class Socket
|
|
82
|
+
|
|
83
|
+
end # module IB
|
data/lib/ib/support.rb
ADDED
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
# Class-extensions only applied when data are read from the tws
|
|
2
|
+
# Array : read several formats
|
|
3
|
+
# Array, String, Symbol, true, false, nil : apply tws.method
|
|
4
|
+
#
|
|
5
|
+
# Apply through: module aaxx
|
|
6
|
+
# using IB::Support
|
|
7
|
+
#
|
|
8
|
+
module IB
|
|
9
|
+
module Support
|
|
10
|
+
|
|
11
|
+
refine Array do
|
|
12
|
+
|
|
13
|
+
def zero?
|
|
14
|
+
false
|
|
15
|
+
end
|
|
16
|
+
# Returns the integer.
|
|
17
|
+
# retuns nil otherwise or if no element is left on the stack
|
|
18
|
+
def read_int
|
|
19
|
+
i= self.shift rescue nil
|
|
20
|
+
i = i.to_i unless i.blank? # this includes conversion of string to zero(0)
|
|
21
|
+
i.is_a?( Integer ) && i != 2147483647 ? i : nil
|
|
22
|
+
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def read_float
|
|
26
|
+
i= self.shift rescue nil
|
|
27
|
+
i = i.to_f unless i.blank?
|
|
28
|
+
|
|
29
|
+
end
|
|
30
|
+
def read_decimal
|
|
31
|
+
i= self.shift rescue nil
|
|
32
|
+
i = i.to_d unless i.blank?
|
|
33
|
+
i.is_a?(Numeric) && i < IB::TWS_MAX ? i : nil # return nil, if a very large number is transmitted
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
alias read_decimal_max read_decimal
|
|
37
|
+
|
|
38
|
+
## Values -1 and below indicate: Not computed (TickOptionComputation)
|
|
39
|
+
def read_decimal_limit_1
|
|
40
|
+
i= read_decimal
|
|
41
|
+
i <= -1 ? nil : i
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
## Values -2 and below indicate: Not computed (TickOptionComputation)
|
|
45
|
+
def read_decimal_limit_2
|
|
46
|
+
i= read_decimal
|
|
47
|
+
i <= -2 ? nil : i
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def read_string
|
|
52
|
+
self.shift rescue ""
|
|
53
|
+
end
|
|
54
|
+
## reads a string and checks if NULL == IB::TWS_MAX is present.
|
|
55
|
+
## in that case: returns nil. otherwise: returns the string
|
|
56
|
+
def read_string_not_null
|
|
57
|
+
r = read_string
|
|
58
|
+
rd = r.to_d unless r.blank?
|
|
59
|
+
rd.is_a?(Numeric) && rd >= IB::TWS_MAX ? nil : r
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def read_symbol
|
|
63
|
+
read_string.to_sym
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# convert xml into a hash
|
|
67
|
+
def read_xml
|
|
68
|
+
Ox.load( read_string(), mode: :hash_no_attrs)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def read_int_date
|
|
73
|
+
t= read_int
|
|
74
|
+
s= Time.at(t.to_i)
|
|
75
|
+
# s.year == 1970 --> data is most likely a date-string
|
|
76
|
+
s.year == 1970 ? Date.parse(t.to_s) : s
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def read_parse_date
|
|
80
|
+
Time.parse read_string
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def read_boolean
|
|
84
|
+
|
|
85
|
+
v = self.shift rescue nil
|
|
86
|
+
case v
|
|
87
|
+
when "1"
|
|
88
|
+
true
|
|
89
|
+
when "0"
|
|
90
|
+
false
|
|
91
|
+
else nil
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def read_datetime
|
|
97
|
+
the_string = read_string
|
|
98
|
+
the_string.blank? ? nil : DateTime.parse(the_string)
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def read_date
|
|
102
|
+
the_string = read_string
|
|
103
|
+
the_string.blank? ? nil : Date.parse(the_string)
|
|
104
|
+
end
|
|
105
|
+
# def read_array
|
|
106
|
+
# count = read_int
|
|
107
|
+
# end
|
|
108
|
+
|
|
109
|
+
## originally provided in socket.rb
|
|
110
|
+
# # Returns loaded Array or [] if count was 0#
|
|
111
|
+
#
|
|
112
|
+
# Without providing a Block, the elements are treated as string
|
|
113
|
+
def read_array hashmode:false, &block
|
|
114
|
+
count = read_int
|
|
115
|
+
case count
|
|
116
|
+
when 0
|
|
117
|
+
[]
|
|
118
|
+
when nil
|
|
119
|
+
nil
|
|
120
|
+
else
|
|
121
|
+
count= count + count if hashmode
|
|
122
|
+
if block_given?
|
|
123
|
+
Array.new(count, &block)
|
|
124
|
+
else
|
|
125
|
+
Array.new( count ){ read_string }
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
#
|
|
130
|
+
# Returns a hash
|
|
131
|
+
# Expected Buffer-Format:
|
|
132
|
+
# count (of Hash-elements)
|
|
133
|
+
# count* key|Value
|
|
134
|
+
# Key's are transformed to symbols, values are treated as string
|
|
135
|
+
def read_hash
|
|
136
|
+
tags = read_array( hashmode: true ) # { |_| [read_string, read_string] }
|
|
137
|
+
result = if tags.nil? || tags.flatten.empty?
|
|
138
|
+
tags # {}
|
|
139
|
+
else
|
|
140
|
+
interim = if tags.size.modulo(2).zero?
|
|
141
|
+
Hash[*tags.flatten]
|
|
142
|
+
else
|
|
143
|
+
Hash[*tags[0..-2].flatten] # omit the last element
|
|
144
|
+
end
|
|
145
|
+
# symbolize Hash
|
|
146
|
+
interim.map { |k, v| [k.to_sym, v] unless k.nil? }.compact.to_h
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
#
|
|
150
|
+
|
|
151
|
+
def read_contract # read a standard contract and return als hash
|
|
152
|
+
{ con_id: read_int,
|
|
153
|
+
symbol: read_string,
|
|
154
|
+
sec_type: read_string,
|
|
155
|
+
expiry: read_string,
|
|
156
|
+
strike: read_decimal,
|
|
157
|
+
right: read_string,
|
|
158
|
+
multiplier: read_decimal,
|
|
159
|
+
exchange: read_string,
|
|
160
|
+
currency: read_string,
|
|
161
|
+
local_symbol: read_string,
|
|
162
|
+
trading_class: read_string }
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def read_bar # read a Historical data bar
|
|
167
|
+
# ** historicalDataUpdate: time open close high low ** covered here
|
|
168
|
+
# historicalData time open high low close <- covered in messages/incomming
|
|
169
|
+
{ :time => read_int_date, # conversion of epoche-time-integer to Dateime
|
|
170
|
+
# requires format_date in request to be "2"
|
|
171
|
+
# (outgoing/bar_requests # RequestHistoricalData#Encoding)
|
|
172
|
+
:open => read_decimal,
|
|
173
|
+
:close => read_decimal,
|
|
174
|
+
:high => read_decimal,
|
|
175
|
+
:low => read_decimal,
|
|
176
|
+
:wap => read_decimal,
|
|
177
|
+
:volume => read_int,
|
|
178
|
+
# :has_gaps => read_string, # only in ServerVersion < 124
|
|
179
|
+
:trades => read_int }
|
|
180
|
+
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
alias read_bool read_boolean
|
|
185
|
+
|
|
186
|
+
def tws
|
|
187
|
+
if blank?
|
|
188
|
+
nil.tws
|
|
189
|
+
else
|
|
190
|
+
self.flatten.map( &:tws ).join # [ "", [] , nil].flatten -> ["", nil]
|
|
191
|
+
# elements with empty array's are cut
|
|
192
|
+
# this is the desired behavior!
|
|
193
|
+
end
|
|
194
|
+
end
|
|
195
|
+
end # refining array
|
|
196
|
+
refine Symbol do
|
|
197
|
+
def tws
|
|
198
|
+
self.to_s.tws
|
|
199
|
+
end
|
|
200
|
+
end
|
|
201
|
+
refine String do
|
|
202
|
+
def tws
|
|
203
|
+
if empty?
|
|
204
|
+
IB::EOL
|
|
205
|
+
else
|
|
206
|
+
self[-1] == IB::EOL ? self : self+IB::EOL
|
|
207
|
+
end
|
|
208
|
+
end
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
refine Numeric do
|
|
212
|
+
def tws
|
|
213
|
+
self.to_s.tws
|
|
214
|
+
end
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
refine TrueClass do
|
|
218
|
+
def tws
|
|
219
|
+
1.tws
|
|
220
|
+
end
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
refine FalseClass do
|
|
224
|
+
def tws
|
|
225
|
+
0.tws
|
|
226
|
+
end
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
refine NilClass do
|
|
230
|
+
def tws
|
|
231
|
+
IB::EOL
|
|
232
|
+
end
|
|
233
|
+
end
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
end
|
data/lib/ib/version.rb
ADDED
data/lib/ib-api.rb
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
|
|
2
|
+
require "zeitwerk"
|
|
3
|
+
require "active_model"
|
|
4
|
+
require 'active_support/concern'
|
|
5
|
+
require 'active_support/core_ext/module/attribute_accessors.rb'
|
|
6
|
+
require 'bigdecimal/util' # provides .to_d for numeric and string classes
|
|
7
|
+
require 'class_extensions'
|
|
8
|
+
require 'logger'
|
|
9
|
+
require 'terminal-table'
|
|
10
|
+
require 'workflow'
|
|
11
|
+
|
|
12
|
+
#require 'ib/version'
|
|
13
|
+
#require 'ib/connection'
|
|
14
|
+
|
|
15
|
+
require "server_versions"
|
|
16
|
+
|
|
17
|
+
require 'ib/constants'
|
|
18
|
+
require 'ib/errors'
|
|
19
|
+
#loader = Zeitwerk::Loader.new
|
|
20
|
+
loader = Zeitwerk::Loader.for_gem(warn_on_extra_files: false)
|
|
21
|
+
loader.ignore("#{__dir__}/server_versions.rb")
|
|
22
|
+
loader.ignore("#{__dir__}/ib-api.rb")
|
|
23
|
+
loader.ignore("#{__dir__}/ib/contract.rb")
|
|
24
|
+
loader.ignore("#{__dir__}/ib/constants.rb")
|
|
25
|
+
loader.ignore("#{__dir__}/ib/errors.rb")
|
|
26
|
+
loader.ignore("#{__dir__}/ib/order_condition.rb")
|
|
27
|
+
loader.ignore("#{__dir__}/ib/messages/outgoing/old-place-order.rb")
|
|
28
|
+
loader.ignore("#{__dir__}/ib/messages/outgoing/new-place-order.rb")
|
|
29
|
+
#loader.ignore("#{__dir__}/models")
|
|
30
|
+
loader.inflector.inflect(
|
|
31
|
+
"ib" => "IB",
|
|
32
|
+
"receive_fa" => "ReceiveFA",
|
|
33
|
+
"tick_efp" => "TickEFP",
|
|
34
|
+
)
|
|
35
|
+
#loader.push_dir("#{__dir__}")
|
|
36
|
+
loader.push_dir("#{__dir__}/../models/")
|
|
37
|
+
loader.push_dir("#{__dir__}/../conditions/")
|
|
38
|
+
loader.setup
|
|
39
|
+
loader.eager_load
|
|
40
|
+
#require 'requires'
|
|
41
|
+
require 'ib/contract.rb'
|
|
42
|
+
require 'ib/order_condition.rb'
|
|
43
|
+
#IbRuby = Ib
|
|
44
|
+
#IB = Ib
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
=begin
|
|
2
|
+
taken from the python-client code
|
|
3
|
+
|
|
4
|
+
Copyright (C) 2016 Interactive Brokers LLC. All rights reserved. This code is
|
|
5
|
+
subject to the terms and conditions of the IB API Non-Commercial License or the
|
|
6
|
+
IB API Commercial License, as applicable.
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
The known server versions.
|
|
11
|
+
=end
|
|
12
|
+
|
|
13
|
+
KNOWN_SERVERS = {
|
|
14
|
+
#min_server_ver_real_time_bars => 34,
|
|
15
|
+
#min_server_ver_scale_orders => 35,
|
|
16
|
+
#min_server_ver_snapshot_mkt_data => 35,
|
|
17
|
+
#min_server_ver_sshort_combo_legs => 35,
|
|
18
|
+
#min_server_ver_what_if_orders => 36,
|
|
19
|
+
#min_server_ver_contract_conid => 37,
|
|
20
|
+
:min_server_ver_pta_orders => 39,
|
|
21
|
+
:min_server_ver_fundamental_data => 40,
|
|
22
|
+
:min_server_ver_delta_neutral => 40,
|
|
23
|
+
:min_server_ver_contract_data_chain => 40,
|
|
24
|
+
:min_server_ver_scale_orders2 => 40,
|
|
25
|
+
:min_server_ver_algo_orders => 41,
|
|
26
|
+
:min_server_ver_execution_data_chain => 42,
|
|
27
|
+
:min_server_ver_not_held => 44,
|
|
28
|
+
:min_server_ver_sec_id_type => 45,
|
|
29
|
+
:min_server_ver_place_order_conid => 46,
|
|
30
|
+
:min_server_ver_req_mkt_data_conid => 47,
|
|
31
|
+
:min_server_ver_req_calc_implied_volat => 49,
|
|
32
|
+
:min_server_ver_req_calc_option_price => 50,
|
|
33
|
+
:min_server_ver_sshortx_old => 51,
|
|
34
|
+
:min_server_ver_sshortx => 52,
|
|
35
|
+
:min_server_ver_req_global_cancel => 53,
|
|
36
|
+
:min_server_ver_hedge_orders => 54,
|
|
37
|
+
:min_server_ver_req_market_data_type => 55,
|
|
38
|
+
:min_server_ver_opt_out_smart_routing => 56,
|
|
39
|
+
:min_server_ver_smart_combo_routing_params => 57,
|
|
40
|
+
:min_server_ver_delta_neutral_conid => 58,
|
|
41
|
+
:min_server_ver_scale_orders3 => 60,
|
|
42
|
+
:min_server_ver_order_combo_legs_price => 61,
|
|
43
|
+
:min_server_ver_trailing_percent => 62,
|
|
44
|
+
:min_server_ver_delta_neutral_open_close => 66,
|
|
45
|
+
:min_server_ver_positions => 67,
|
|
46
|
+
:min_server_ver_account_summary => 67,
|
|
47
|
+
:min_server_ver_trading_class => 68,
|
|
48
|
+
:min_server_ver_scale_table => 69,
|
|
49
|
+
:min_server_ver_linking => 70,
|
|
50
|
+
:min_server_ver_algo_id => 71,
|
|
51
|
+
:min_server_ver_optional_capabilities => 72,
|
|
52
|
+
:min_server_ver_order_solicited => 73,
|
|
53
|
+
:min_server_ver_linking_auth => 74,
|
|
54
|
+
:min_server_ver_primaryexch => 75,
|
|
55
|
+
:min_server_ver_randomize_size_and_price => 76,
|
|
56
|
+
:min_server_ver_fractional_positions => 101,
|
|
57
|
+
:min_server_ver_pegged_to_benchmark => 102,
|
|
58
|
+
:min_server_ver_models_support => 103,
|
|
59
|
+
:min_server_ver_sec_def_opt_params_req => 104,
|
|
60
|
+
:min_server_ver_ext_operator => 105,
|
|
61
|
+
:min_server_ver_soft_dollar_tier => 106,
|
|
62
|
+
:min_server_ver_req_family_codes => 107,
|
|
63
|
+
:min_server_ver_req_matching_symbols => 108,
|
|
64
|
+
:min_server_ver_past_limit => 109,
|
|
65
|
+
:min_server_ver_md_size_multiplier => 110,
|
|
66
|
+
:min_server_ver_cash_qty => 111,
|
|
67
|
+
:min_server_ver_req_mkt_depth_exchanges => 112,
|
|
68
|
+
:min_server_ver_tick_news => 113,
|
|
69
|
+
:min_server_ver_req_smart_components => 114,
|
|
70
|
+
:min_server_ver_req_news_providers => 115,
|
|
71
|
+
:min_server_ver_req_news_article => 116,
|
|
72
|
+
:min_server_ver_req_historical_news => 117,
|
|
73
|
+
:min_server_ver_req_head_timestamp => 118,
|
|
74
|
+
:min_server_ver_req_histogram => 119,
|
|
75
|
+
:min_server_ver_service_data_type => 120,
|
|
76
|
+
:min_server_ver_agg_group => 121,
|
|
77
|
+
:min_server_ver_underlying_info => 122,
|
|
78
|
+
:min_server_ver_cancel_headtimestamp => 123,
|
|
79
|
+
:min_server_ver_synt_realtime_bars => 124,
|
|
80
|
+
:min_server_ver_cfd_reroute => 125,
|
|
81
|
+
:min_server_ver_market_rules => 126,
|
|
82
|
+
:min_server_ver_pnl => 127,
|
|
83
|
+
:min_server_ver_news_query_origins => 128,
|
|
84
|
+
:min_server_ver_unrealized_pnl => 129,
|
|
85
|
+
:min_server_ver_historical_ticks => 130,
|
|
86
|
+
:min_server_ver_market_cap_price => 131,
|
|
87
|
+
:min_server_ver_pre_open_bid_ask => 132,
|
|
88
|
+
:min_server_ver_real_expiration_date => 134,
|
|
89
|
+
:min_server_ver_realized_pnl => 135,
|
|
90
|
+
:min_server_ver_last_liquidity => 136,
|
|
91
|
+
:min_server_ver_tick_by_tick => 137,
|
|
92
|
+
:min_server_ver_decision_maker => 138,
|
|
93
|
+
:min_server_ver_mifid_execution => 139,
|
|
94
|
+
:min_server_ver_tick_by_tick_ignore_size => 140,
|
|
95
|
+
:min_server_ver_auto_price_for_hedge => 141,
|
|
96
|
+
:min_server_ver_what_if_ext_fields => 142,
|
|
97
|
+
:min_server_ver_scanner_generic_opts => 143,
|
|
98
|
+
:min_server_ver_api_bind_order => 144,
|
|
99
|
+
:min_server_ver_order_container => 145, ### > Version Field in Order dropped
|
|
100
|
+
:min_server_ver_smart_depth => 146,
|
|
101
|
+
:min_server_ver_remove_null_all_casting => 147,
|
|
102
|
+
:min_server_ver_d_peg_orders => 148,
|
|
103
|
+
:min_server_ver_mkt_depth_prim_exchange => 149,
|
|
104
|
+
:min_server_ver_completed_orders => 150,
|
|
105
|
+
:min_server_ver_price_mgmt_algo => 151,
|
|
106
|
+
:min_server_ver_stock_type => 152,
|
|
107
|
+
:min_server_ver_encode_msg_ascii7 => 153,
|
|
108
|
+
:min_server_ver_send_all_family_codes => 154,
|
|
109
|
+
:min_server_ver_no_default_open_close => 155,
|
|
110
|
+
:min_server_ver_price_based_volitility => 156,
|
|
111
|
+
:min_server_ver_replace_fa_end => 157,
|
|
112
|
+
:min_server_ver_duration => 158,
|
|
113
|
+
:min_server_ver_market_data_in_shares => 159,
|
|
114
|
+
:min_server_ver_post_to_ats => 160,
|
|
115
|
+
:min_server_ver_wshe_calendar => 161,
|
|
116
|
+
:min_server_ver_auto_cancel_parent => 162,
|
|
117
|
+
:min_server_ver_fractional_size_support => 163,
|
|
118
|
+
:min_server_ver_size_rules => 164,
|
|
119
|
+
:min_server_ver_historical_schedule => 165,
|
|
120
|
+
:min_server_ver_advanced_order_reject => 166,
|
|
121
|
+
:min_server_ver_user_info => 167,
|
|
122
|
+
:min_server_ver_crypto_aggregated_trades => 168,
|
|
123
|
+
:min_server_ver_manual_order_time => 169,
|
|
124
|
+
:min_server_ver_pegbest_pegmid_offsets => 170,
|
|
125
|
+
:min_server_ver_wsh_event_data_filters => 171,
|
|
126
|
+
:min_server_ver_ipo_prices => 172,
|
|
127
|
+
:min_server_ver_wsh_event_data_filters_date => 173,
|
|
128
|
+
:min_server_ver_instrument_timezone => 174,
|
|
129
|
+
:min_server_ver_hmds_market_data_in_shares => 175,
|
|
130
|
+
:min_server_ver_bond_issuerid => 176,
|
|
131
|
+
:min_server_ver_fa_profile_desupport => 177,
|
|
132
|
+
:min_server_ver_pending_price_revision => 178,
|
|
133
|
+
:min_server_ver_fund_data_fields => 179,
|
|
134
|
+
:min_server_ver_manual_order_time_exercise_options => 180,
|
|
135
|
+
:min_server_ver_open_order_ad_strategy => 181,
|
|
136
|
+
:min_server_ver_last_trade_date => 182,
|
|
137
|
+
:min_server_ver_customer_account => 183,
|
|
138
|
+
:min_server_ver_professional_customer => 184
|
|
139
|
+
}
|
|
140
|
+
# 100+ messaging */
|
|
141
|
+
# 100 = enhanced handshake, msg length prefixes
|
|
142
|
+
|
|
143
|
+
MIN_CLIENT_VER = 100
|
|
144
|
+
MAX_CLIENT_VER = KNOWN_SERVERS[:min_server_ver_historical_schedule] # 165
|
|
145
|
+
# imessages/outgoing/request_tick_Data is prepared for change to ver. 140 , its commented for now
|