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,74 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# This script allows you to cancel either a set of open Orders by their ids,
|
4
|
+
# or ALL Orders opened via IB API at once. The latter is useful when your
|
5
|
+
# robot goes crazy and opens gazillions of wrong limit orders.
|
6
|
+
#
|
7
|
+
# Accepts the port to connect to as first command-parameter, T)ws or G)ateway, which is the default
|
8
|
+
#
|
9
|
+
# Numeric Parameter are interpreted as keys for specific orders to kill
|
10
|
+
|
11
|
+
require 'bundler/setup'
|
12
|
+
require 'ib-api'
|
13
|
+
specified_port = ARGV[0] || 'Gateway'
|
14
|
+
port = case specified_port
|
15
|
+
when /^[gG]/
|
16
|
+
ARGV.shift # consume the first argument
|
17
|
+
4002
|
18
|
+
when /^[Tt]/
|
19
|
+
ARGV.shift # consume the first argument
|
20
|
+
7497
|
21
|
+
end
|
22
|
+
|
23
|
+
# First, connect to IB TWS.
|
24
|
+
ib = IB::Connection.new client_id: 1111, port: port do | gw |
|
25
|
+
|
26
|
+
# Subscribe to TWS alerts/errors and order-related messages
|
27
|
+
gw.subscribe(:Alert, :ManagedAccounts, :OpenOrder, :OrderStatus, :OpenOrderEnd) { |msg| puts msg.to_human }
|
28
|
+
# Set log level
|
29
|
+
gw.logger.level = Logger::FATAL # DEBUG -- INFO -- WARN -- ERROR -- FATAL
|
30
|
+
end
|
31
|
+
|
32
|
+
if ARGV.empty?
|
33
|
+
ib.send_message :RequestGlobalCancel
|
34
|
+
else
|
35
|
+
puts "ARGV: #{ARGV}"
|
36
|
+
# Will only work for Orders placed under the same :client_id
|
37
|
+
ib.cancel_order *ARGV
|
38
|
+
puts '-'*55
|
39
|
+
puts "Remaining Orders"
|
40
|
+
puts '-'*55
|
41
|
+
end
|
42
|
+
|
43
|
+
puts '-'*55
|
44
|
+
puts "Remaining Orders"
|
45
|
+
ib.send_message :RequestAllOpenOrders
|
46
|
+
puts '-'*55
|
47
|
+
|
48
|
+
sleep 3
|
49
|
+
|
50
|
+
|
51
|
+
## Expected output
|
52
|
+
#12:20:25.154 Connected to server, version: 137,
|
53
|
+
# connection time: 2018-02-27 12:20:25 +0000 local, 2018-02-27T12:20:25+00:00 remote.
|
54
|
+
#12:20:25.156 Got message 5 (IB::Messages::Incoming::OpenOrder)
|
55
|
+
#<OpenOrder: <Stock: WFC USD> <Order: LMT GTC buy 100.0 1.13 Submitted #1/1562725191 from 1112/DU167348 fee 0.0>>
|
56
|
+
#12:20:25.158 Got message 3 (IB::Messages::Incoming::OrderStatus)
|
57
|
+
#<OrderStatus: <OrderState: Submitted #1/1562725191 from 1112 filled 0.0/100.0 at 0.0/0.0 why_held >>
|
58
|
+
#12:20:25.197 Got message 53 (IB::Messages::Incoming::OpenOrderEnd)
|
59
|
+
#<OpenOrderEnd: >
|
60
|
+
#12:20:25.197 Got message 15 (IB::Messages::Incoming::ManagedAccounts)
|
61
|
+
#12:20:25.197 No subscribers for message IB::Messages::Incoming::ManagedAccounts!
|
62
|
+
#12:20:25.197 Got message 9 (IB::Messages::Incoming::NextValidId)
|
63
|
+
#12:20:25.197 Got next valid order id: 2.
|
64
|
+
#12:20:25.254 Got message 5 (IB::Messages::Incoming::OpenOrder)
|
65
|
+
#<OpenOrder: <Stock: WFC USD> <Order: LMT GTC buy 100.0 1.13 PendingCancel #1/1562725191 from 1112/DU167348 fee 0.0>>
|
66
|
+
#12:20:25.256 Got message 3 (IB::Messages::Incoming::OrderStatus)
|
67
|
+
#<OrderStatus: <OrderState: PendingCancel #1/1562725191 from 1112 filled 0.0/100.0 at 0.0/0.0 why_held >>
|
68
|
+
#12:20:25.297 Got message 53 (IB::Messages::Incoming::OpenOrderEnd)
|
69
|
+
#<OpenOrderEnd: >
|
70
|
+
#12:20:25.342 Got message 3 (IB::Messages::Incoming::OrderStatus)
|
71
|
+
#<OrderStatus: <OrderState: Cancelled #1/1562725191 from 1112 filled 0.0/100.0 at 0.0/0.0 why_held >>
|
72
|
+
#12:20:25.343 Got message 4 (IB::Messages::Incoming::Alert)
|
73
|
+
#TWS Error 202: Order Canceled - reason:
|
74
|
+
##
|
data/example/fa_accounts
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# This script receives Financial Adviser and Managed Accounts info
|
4
|
+
|
5
|
+
require 'bundler/setup'
|
6
|
+
require 'ib-api'
|
7
|
+
|
8
|
+
# First, connect to IB TWS.
|
9
|
+
ib = IB::Connection.new client_id: 1114, port: 4002 #, :port => 7496 # TWS
|
10
|
+
|
11
|
+
# Subscribe to TWS alerts/errors and FA/managed account info
|
12
|
+
ib.subscribe(:Alert, :ManagedAccounts, :ReceiveFA) { |msg| puts msg.to_human }
|
13
|
+
|
14
|
+
##
|
15
|
+
## type of Financial Advisor configuration data
|
16
|
+
# being received from TWS. Valid values include:
|
17
|
+
# 1 = GROUPS, 2 = PROFILE, 3 = ACCOUNT ALIASES
|
18
|
+
|
19
|
+
ib.send_message :RequestFA, fa_data_type: 3
|
20
|
+
ib.send_message :RequestManagedAccounts
|
21
|
+
|
22
|
+
ib.wait_for :ReceiveFA
|
23
|
+
|
24
|
+
|
25
|
+
|
@@ -0,0 +1,40 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# This script downloads Fundamental data for specific symbols from IB
|
4
|
+
# This only works IF you have Reuters data subscription!
|
5
|
+
|
6
|
+
require 'bundler/setup'
|
7
|
+
|
8
|
+
require 'ib-api'
|
9
|
+
|
10
|
+
# allocate two variables
|
11
|
+
request_id, snapshot_data = nil
|
12
|
+
|
13
|
+
ib = IB::Connection.new port: 7496, :client_id => 1112 do | gw | #, :port => 7496 # TWS
|
14
|
+
gw.subscribe(:Alert) { |msg| puts msg.to_human }
|
15
|
+
|
16
|
+
# Fundamental Data will arrive in XML format, we need to parse it
|
17
|
+
gw.subscribe(:FundamentalData) { |msg| snapshot_data = msg.xml if msg.request_id == request_id }
|
18
|
+
end
|
19
|
+
|
20
|
+
# Request Fundamental Data for IBM. Possible report types:
|
21
|
+
# 'estimates' - Estimates
|
22
|
+
# 'finstat' - Financial statements
|
23
|
+
# 'snapshot' - Summary
|
24
|
+
request_id = ib.send_message :RequestFundamentalData,
|
25
|
+
:contract => IB::Stock.new( symbol: 'ibm' ),
|
26
|
+
:report_type => 'snapshot'
|
27
|
+
|
28
|
+
# Needs some time to receive and parse XML. Standard timeout of 1 sec is just too low.
|
29
|
+
ib.wait_for :FundamentalData
|
30
|
+
|
31
|
+
# Now just extract and use all the fundamental data you needed
|
32
|
+
puts snapshot_data[:ReportSnapshot][:TextInfo][:Text]
|
33
|
+
|
34
|
+
pp snapshot_data[:ReportSnapshot][:Ratios]
|
35
|
+
STDIN.gets
|
36
|
+
|
37
|
+
puts "Displaying the complete transmitted data:"
|
38
|
+
puts ""
|
39
|
+
|
40
|
+
pp snapshot_data
|
@@ -0,0 +1,186 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# This script connects to IB API, and downloads historic data
|
3
|
+
|
4
|
+
require 'rubygems'
|
5
|
+
require 'time'
|
6
|
+
require 'getopt/long'
|
7
|
+
require 'bundler/setup'
|
8
|
+
require 'ib-api'
|
9
|
+
|
10
|
+
include Getopt
|
11
|
+
|
12
|
+
opt = Getopt::Long.getopts(
|
13
|
+
["--help", BOOLEAN],
|
14
|
+
["--end", REQUIRED],
|
15
|
+
["--port", REQUIRED],
|
16
|
+
["--security", REQUIRED],
|
17
|
+
["--duration", REQUIRED],
|
18
|
+
["--barsize", REQUIRED],
|
19
|
+
["--csv", BOOLEAN],
|
20
|
+
["--dateformat", REQUIRED],
|
21
|
+
["--nonregularhours", BOOLEAN],
|
22
|
+
["--what", OPTIONAL]
|
23
|
+
)
|
24
|
+
|
25
|
+
if opt["help"] || opt["security"].nil? || opt["security"].empty?
|
26
|
+
puts <<ENDHELP
|
27
|
+
|
28
|
+
This program requires a TWS or Gateway running on localhost.
|
29
|
+
|
30
|
+
----------
|
31
|
+
|
32
|
+
One argument is required: --security, the security specification you want, in
|
33
|
+
"long serialized IB-Ruby" format. This is a colon-separated string of the format:
|
34
|
+
|
35
|
+
symbol:security_type:expiry:strike:right:multiplier:exchange:primary_exchange:currency:local_symbol
|
36
|
+
|
37
|
+
Fields not needed for a particular security should be left blank (e.g. strike and right are only relevant for options.)
|
38
|
+
|
39
|
+
For example, to query the Apple 500 Strike Call expiring in January 2013, use:
|
40
|
+
|
41
|
+
$ ./historic_data_cli --security AAPL:OPT:202101:200:CALL::SMART::USD:
|
42
|
+
|
43
|
+
Consult contract.rb for allowed values, and see also the examples in the symbols/ directory
|
44
|
+
(load them in irb and run security#serialize_ib_ruby to see the appropriate string.)
|
45
|
+
|
46
|
+
----------
|
47
|
+
|
48
|
+
Options:
|
49
|
+
|
50
|
+
--end is is the last time we want data for. The default is now.
|
51
|
+
This is eval'ed by Ruby, so you can use a Ruby expression, which must return a Time object.
|
52
|
+
|
53
|
+
--duration is time period before --end, in seconds. The default is 86400 sec (1 day).
|
54
|
+
TWS imposes limit of 86400 sec (1 day) worth of historic data per request.
|
55
|
+
|
56
|
+
--what determines what the data will be comprised of. This can be
|
57
|
+
"trades", "midpoint", "bid", or "ask". The default is "trades".
|
58
|
+
|
59
|
+
--barsize determines how long each bar will be.
|
60
|
+
|
61
|
+
Possible bar values (from the IB documentation):
|
62
|
+
Values less than 30 sec do not appear to work for some securities.
|
63
|
+
|
64
|
+
sec1 = 1 sec
|
65
|
+
sec5 = 5 sec
|
66
|
+
sec15 = 15 sec
|
67
|
+
sec30 = 30 sec
|
68
|
+
min1 = 1 minute
|
69
|
+
min2 = 2 minutes
|
70
|
+
min5 = 5 minutes
|
71
|
+
min15 = 15 minutes (default)
|
72
|
+
min30 = 30 minutes
|
73
|
+
hour1 = 1 hour
|
74
|
+
day1 = 1 day
|
75
|
+
|
76
|
+
--nonregularhours : Normally, only data from the instrument's regular trading
|
77
|
+
hours is returned. If --nonregularhours is given, all data available during the time
|
78
|
+
span requested is returned, even for time intervals when the market was illiquid.
|
79
|
+
|
80
|
+
--dateformat : 1 (default) human-readable time/date format, like "20050307 11:32:16".
|
81
|
+
If you set it to 2 instead, you will get UNIX epoch offsets (seconds since Jan 1, 1970).
|
82
|
+
|
83
|
+
--csv : print out the historic data in CSV format, with header.
|
84
|
+
|
85
|
+
--port : 4001 for Gateway (default), 7496 for TWS, or your custom port
|
86
|
+
|
87
|
+
ENDHELP
|
88
|
+
exit
|
89
|
+
end
|
90
|
+
|
91
|
+
### Parameters
|
92
|
+
|
93
|
+
# DURATION is how much historic data we want, in seconds, before END_DATE_TIME.
|
94
|
+
DURATION = (opt["duration"] && opt["duration"].to_i) || 86400
|
95
|
+
|
96
|
+
if DURATION > 86400
|
97
|
+
STDERR.puts("\nTWS rejects --duration longer than 86400 seconds (1 day).\n")
|
98
|
+
exit(1)
|
99
|
+
end
|
100
|
+
|
101
|
+
# This is the last time we want data for.
|
102
|
+
END_DATE_TIME = (opt["end"] && eval(opt["end"]).to_ib) || Time.now.to_ib
|
103
|
+
|
104
|
+
# This can be :trades, :midpoint, :bid, or :asked
|
105
|
+
WHAT = (opt["what"] && opt["what"].to_sym) || :trades
|
106
|
+
|
107
|
+
|
108
|
+
# Values less than 4 do not appear to actually work; they are rejected by the server.
|
109
|
+
#
|
110
|
+
BAR_SIZE = (opt["barsize"] && opt["barsize"].to_sym) || :min15
|
111
|
+
|
112
|
+
# If REGULAR_HOURS_ONLY is set to 0, all data available during the time
|
113
|
+
# span requested is returned, even data bars covering time
|
114
|
+
# intervals where the market in question was illiquid. If useRTH
|
115
|
+
# has a non-zero value, only data within the "Regular Trading
|
116
|
+
# Hours" of the product in question is returned, even if the time
|
117
|
+
# span requested falls partially or completely outside of them.
|
118
|
+
|
119
|
+
REGULAR_HOURS_ONLY = opt["nonregularhours"] ? 0 : 1
|
120
|
+
|
121
|
+
# Using a DATE_FORMAT of 1 will cause the dates in the returned
|
122
|
+
# messages with the historic data to be in a text format, like
|
123
|
+
# "20050307 11:32:16". If you set :format_date to 2 instead, you
|
124
|
+
# will get an offset in seconds from the beginning of 1970, which
|
125
|
+
# is the same format as the UNIX epoch time.
|
126
|
+
|
127
|
+
DATE_FORMAT = (opt["dateformat"] && opt["dateformat"].to_i) || 1
|
128
|
+
|
129
|
+
PORT = (opt["port"] && opt["port"]) || '4002'
|
130
|
+
|
131
|
+
lastMessageTime = Queue.new # for communicating with the reader thread.
|
132
|
+
|
133
|
+
# First, connect to IB TWS.
|
134
|
+
ib = IB::Connection.new( :client_id => 1112, :port => PORT) do | gw|
|
135
|
+
|
136
|
+
# Subscribe to TWS alerts/errors
|
137
|
+
gw.subscribe(:Alert) { |msg| puts msg.to_human }
|
138
|
+
|
139
|
+
|
140
|
+
# Subscribe to incoming HistoricalData events. The code passed in the
|
141
|
+
# block will be executed when a message of the subscribed type is
|
142
|
+
# received, with the received message as its argument. In this case,
|
143
|
+
# we just print out the data.
|
144
|
+
#
|
145
|
+
# Note that we have to look the ticker id of each incoming message
|
146
|
+
# up in local memory to figure out what security it relates to.
|
147
|
+
# The incoming message packet from TWS just identifies it by ticker id.
|
148
|
+
#
|
149
|
+
gw.subscribe(:HistoricalData) do |msg|
|
150
|
+
if opt["csv"]
|
151
|
+
puts "date,time,open,high,low,close,volume,wap,has_gaps"
|
152
|
+
msg.results.each do |datum|
|
153
|
+
puts "#{datum.time},#{datum.open},#{datum.high},#{datum.low}," +
|
154
|
+
"#{datum.close},#{datum.volume},#{datum.wap},#{datum.has_gaps}"
|
155
|
+
end
|
156
|
+
n
|
157
|
+
STDERR.puts "Received #{msg.count} items:"
|
158
|
+
msg.results.each { |datum| puts datum.to_s }
|
159
|
+
end
|
160
|
+
lastMessageTime.push(Time.now)
|
161
|
+
end
|
162
|
+
end
|
163
|
+
# Now we actually request historical data for the symbols we're
|
164
|
+
# interested in. TWS will respond with a HistoricalData message,
|
165
|
+
# which will be received by the code above.
|
166
|
+
ib.send_message :RequestHistoricalData,
|
167
|
+
:request_id => 123,
|
168
|
+
:contract => opt["security"],
|
169
|
+
:end_date_time => END_DATE_TIME,
|
170
|
+
:duration => DURATION, # seconds == 1 hour
|
171
|
+
:bar_size => BAR_SIZE, # 1 minute bars
|
172
|
+
:what_to_show => WHAT,
|
173
|
+
:use_RTH => REGULAR_HOURS_ONLY,
|
174
|
+
:keep_up_todate => 0,
|
175
|
+
:format_date => DATE_FORMAT
|
176
|
+
|
177
|
+
# A complication here is that IB does not send any indication when all historic data
|
178
|
+
# is done being delivered. So we have to guess - when there is no more new data for
|
179
|
+
# some period, we interpret that as "end of data" and exit.
|
180
|
+
sleep 2
|
181
|
+
while true
|
182
|
+
lastTime = lastMessageTime.pop # blocks until a message is ready on the queue
|
183
|
+
sleep 0.1 # .. wait ..
|
184
|
+
exit if lastMessageTime.empty? # if still no more messages after 2 more seconds, exit.
|
185
|
+
end
|
186
|
+
|
data/example/list_orders
ADDED
@@ -0,0 +1,45 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# This script retrieves list of all Orders from TWS
|
4
|
+
|
5
|
+
require 'bundler/setup'
|
6
|
+
require 'ib-api'
|
7
|
+
|
8
|
+
# Connect to IB as 0 (TWS) to retrieve all Orders, including TWS-generated ones
|
9
|
+
ib = IB::Connection.new :client_id => 0 do |gw| # , :port => 7497 # TWS
|
10
|
+
|
11
|
+
# Subscribe to TWS alerts/errors and order-related messages
|
12
|
+
counter = 0
|
13
|
+
|
14
|
+
gw.subscribe(:Alert, :ManagedAccounts, :OrderStatus, :OpenOrderEnd) { |msg| puts msg.to_human }
|
15
|
+
|
16
|
+
gw.subscribe(:OpenOrder) do |msg|
|
17
|
+
counter += 1
|
18
|
+
puts "#{counter}: #{msg.to_human}"
|
19
|
+
|
20
|
+
# Get rid of logging verbosity
|
21
|
+
gw.logger.level = Logger::FATAL
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
## fire request
|
26
|
+
ib.send_message :RequestAllOpenOrders
|
27
|
+
|
28
|
+
# Wait for IB to respond to our request
|
29
|
+
ib.wait_for :OpenOrderEnd
|
30
|
+
sleep 1 # Let printer do the job
|
31
|
+
|
32
|
+
=begin
|
33
|
+
---> expected output
|
34
|
+
1: <OpenOrder: <Bag: GOOG SMART USD legs: 269330045|1,269562569|1,301964119|-2 > <Order: LMT GTC buy 10.0 0.06 PreSubmitted #9/561364885 from 1112/DU167349 fee 0.0>>
|
35
|
+
<OrderStatus: <OrderState: PreSubmitted #9/561364885 from 1112 filled 0.0/10.0 at 0.0/0.0 why_held locate>>
|
36
|
+
2:<OpenOrder: <Stock: WFC USD> <Order: LMT DAY sell 100.0 34.05 PreSubmitted #19/561364895 from 1112/DU167349 fee 0.0>>
|
37
|
+
<OrderStatus: <OrderState: PreSubmitted #19/561364895 from 1112 filled 0.0/100.0 at 0.0/0.0 why_held child,locate>>
|
38
|
+
3: <OpenOrder: <Stock: WFC USD> <Order: STP DAY sell 100.0 0.0 PreSubmitted /33.55#18/561364894 from 1112/DU167349 fee 0.0>>
|
39
|
+
<OrderStatus: <OrderState: PreSubmitted #18/561364894 from 1112 filled 0.0/100.0 at 0.0/0.0 why_held child,locate,trigger>>
|
40
|
+
4: <OpenOrder: <Bag: GOOG SMART USD legs: 269330045|1,269562569|1,301964119|-2 > <Order: LMT GTC buy 10.0 0.06 PreSubmitted #8/1562725198 from 1112/DU167349 fee 0.0>>
|
41
|
+
<OrderStatus: <OrderState: PreSubmitted #8/1562725198 from 1112 filled 0.0/10.0 at 0.0/0.0 why_held locate>>
|
42
|
+
5: <OpenOrder: <Stock: WFC USD> <Order: LMT GTC buy 100.0 1.72 Submitted #3/1562725193 from 1112/DU167349 fee 0.0>>
|
43
|
+
<OrderStatus: <OrderState: Submitted #3/1562725193 from 1112 filled 0.0/100.0 at 0.0/0.0 why_held >>
|
44
|
+
<OpenOrderEnd: >
|
45
|
+
=end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# This script exports your IB portfolio in a CSV format. Usage:
|
4
|
+
# $ example/portfolio_csv [account] > portfolio.csv
|
5
|
+
|
6
|
+
require 'bundler/setup'
|
7
|
+
require 'ib-ruby'
|
8
|
+
|
9
|
+
# Only for Advisors: you need to provide account id such as U666777
|
10
|
+
account = ARGV[0] || ''
|
11
|
+
accounts = []
|
12
|
+
|
13
|
+
# Connect to IB TWS and subscribe to events
|
14
|
+
ib = IB::Connection.new( :client_id => 1112 , port: 4002 ) do |gw| # , :port => 7496 # TWS
|
15
|
+
|
16
|
+
# Redirect TWS alerts/errors to STDERR to keep output file clean
|
17
|
+
#gw.subscribe(:Alert) { |msg| STDERR.puts msg.to_human }
|
18
|
+
|
19
|
+
# Subscribe to TWS alerts/errors and account-related messages
|
20
|
+
# that TWS sends in response to account data request
|
21
|
+
gw.subscribe(:Alert) do |msg|
|
22
|
+
## if an account is not given. but required, (Error 321 indicates this)
|
23
|
+
## fetch data from the last account detected. (The first is most probably the Advisor-Account)
|
24
|
+
if msg.code == 321
|
25
|
+
account = accounts.last
|
26
|
+
gw.send_message :RequestAccountData, :subscribe => true, :account_code => account
|
27
|
+
else
|
28
|
+
puts msg.to_human
|
29
|
+
end
|
30
|
+
end
|
31
|
+
# Silently ignore account-related messages other than :PortfolioValue
|
32
|
+
gw.subscribe(:AccountUpdateTime, :AccountValue, :ManagedAccounts, :AccountDownloadEnd) {}
|
33
|
+
# Get rid of logging verbosity
|
34
|
+
gw.logger.level = Logger::FATAL
|
35
|
+
|
36
|
+
## Just in case: put account-names into accounts-array
|
37
|
+
gw.subscribe(:ManagedAccounts){ |msg| accounts = msg.accounts_list.split ',' }
|
38
|
+
end
|
39
|
+
|
40
|
+
# Output CSV headers
|
41
|
+
puts %w[Symbol
|
42
|
+
SecType
|
43
|
+
Expiry
|
44
|
+
Strike
|
45
|
+
Right
|
46
|
+
Currency
|
47
|
+
LocalSymbol
|
48
|
+
Position
|
49
|
+
MarketPrice
|
50
|
+
MarketValue
|
51
|
+
AvgCost
|
52
|
+
UnrealizedPNL
|
53
|
+
RealizedPNL
|
54
|
+
Account].map {|val| "\"#{val}\""}.join(",")
|
55
|
+
|
56
|
+
# Output each portfolio position as a single line in CSV format
|
57
|
+
ib.subscribe(:PortfolioValue) do |msg|
|
58
|
+
contract = msg.contract
|
59
|
+
csv = [ contract.symbol,
|
60
|
+
IB::CODES[:sec_type][contract.sec_type],
|
61
|
+
contract.expiry,
|
62
|
+
contract.strike == 0 ? "" : contract.strike,
|
63
|
+
contract.right == :none ? "" : contract.right,
|
64
|
+
contract.currency,
|
65
|
+
contract.local_symbol,
|
66
|
+
msg.position,
|
67
|
+
msg.market_price,
|
68
|
+
msg.market_value,
|
69
|
+
msg.average_cost,
|
70
|
+
msg.unrealized_pnl,
|
71
|
+
msg.realized_pnl,
|
72
|
+
msg.account_name
|
73
|
+
].map {|val| "\"#{val}\""}.join(",")
|
74
|
+
puts csv
|
75
|
+
end
|
76
|
+
|
77
|
+
# Request account data, wait for its end, unsubscribe
|
78
|
+
ib.send_message :RequestAccountData, :subscribe => true, :account_code => account
|
79
|
+
ib.wait_for :AccountDownloadEnd, 30
|
80
|
+
ib.send_message :RequestAccountData, :subscribe => false, :account_code => account
|
81
|
+
sleep 0.5
|