ib-api 972.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (91) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +50 -0
  3. data/.rspec +3 -0
  4. data/.travis.yml +7 -0
  5. data/CODE_OF_CONDUCT.md +74 -0
  6. data/Gemfile +16 -0
  7. data/Gemfile.lock +105 -0
  8. data/Guardfile +24 -0
  9. data/LICENSE +674 -0
  10. data/README.md +65 -0
  11. data/Rakefile +11 -0
  12. data/VERSION +1 -0
  13. data/api.gemspec +43 -0
  14. data/bin/console +95 -0
  15. data/bin/console.yml +3 -0
  16. data/bin/setup +8 -0
  17. data/changelog.md +7 -0
  18. data/example/README.md +76 -0
  19. data/example/account_info +54 -0
  20. data/example/account_positions +30 -0
  21. data/example/account_summary +88 -0
  22. data/example/cancel_orders +74 -0
  23. data/example/fa_accounts +25 -0
  24. data/example/fundamental_data +40 -0
  25. data/example/historic_data_cli +186 -0
  26. data/example/list_orders +45 -0
  27. data/example/portfolio_csv +81 -0
  28. data/example/scanner_data +62 -0
  29. data/example/template +19 -0
  30. data/example/tick_data +28 -0
  31. data/lib/extensions/class-extensions.rb +87 -0
  32. data/lib/ib-api.rb +7 -0
  33. data/lib/ib/base.rb +103 -0
  34. data/lib/ib/base_properties.rb +160 -0
  35. data/lib/ib/connection.rb +450 -0
  36. data/lib/ib/constants.rb +393 -0
  37. data/lib/ib/errors.rb +44 -0
  38. data/lib/ib/logger.rb +26 -0
  39. data/lib/ib/messages.rb +99 -0
  40. data/lib/ib/messages/abstract_message.rb +101 -0
  41. data/lib/ib/messages/incoming.rb +251 -0
  42. data/lib/ib/messages/incoming/abstract_message.rb +116 -0
  43. data/lib/ib/messages/incoming/account_value.rb +78 -0
  44. data/lib/ib/messages/incoming/alert.rb +34 -0
  45. data/lib/ib/messages/incoming/contract_data.rb +102 -0
  46. data/lib/ib/messages/incoming/delta_neutral_validation.rb +23 -0
  47. data/lib/ib/messages/incoming/execution_data.rb +50 -0
  48. data/lib/ib/messages/incoming/historical_data.rb +84 -0
  49. data/lib/ib/messages/incoming/market_depths.rb +44 -0
  50. data/lib/ib/messages/incoming/next_valid_id.rb +18 -0
  51. data/lib/ib/messages/incoming/open_order.rb +277 -0
  52. data/lib/ib/messages/incoming/order_status.rb +85 -0
  53. data/lib/ib/messages/incoming/portfolio_value.rb +78 -0
  54. data/lib/ib/messages/incoming/real_time_bar.rb +32 -0
  55. data/lib/ib/messages/incoming/scanner_data.rb +54 -0
  56. data/lib/ib/messages/incoming/ticks.rb +268 -0
  57. data/lib/ib/messages/outgoing.rb +437 -0
  58. data/lib/ib/messages/outgoing/abstract_message.rb +88 -0
  59. data/lib/ib/messages/outgoing/account_requests.rb +112 -0
  60. data/lib/ib/messages/outgoing/bar_requests.rb +250 -0
  61. data/lib/ib/messages/outgoing/place_order.rb +209 -0
  62. data/lib/ib/messages/outgoing/request_marketdata.rb +99 -0
  63. data/lib/ib/messages/outgoing/request_tick_data.rb +21 -0
  64. data/lib/ib/model.rb +4 -0
  65. data/lib/ib/models.rb +14 -0
  66. data/lib/ib/server_versions.rb +114 -0
  67. data/lib/ib/socket.rb +185 -0
  68. data/lib/ib/support.rb +160 -0
  69. data/lib/ib/version.rb +6 -0
  70. data/lib/models/ib/account.rb +85 -0
  71. data/lib/models/ib/account_value.rb +33 -0
  72. data/lib/models/ib/bag.rb +55 -0
  73. data/lib/models/ib/bar.rb +31 -0
  74. data/lib/models/ib/combo_leg.rb +105 -0
  75. data/lib/models/ib/condition.rb +245 -0
  76. data/lib/models/ib/contract.rb +415 -0
  77. data/lib/models/ib/contract_detail.rb +108 -0
  78. data/lib/models/ib/execution.rb +67 -0
  79. data/lib/models/ib/forex.rb +13 -0
  80. data/lib/models/ib/future.rb +15 -0
  81. data/lib/models/ib/index.rb +15 -0
  82. data/lib/models/ib/option.rb +78 -0
  83. data/lib/models/ib/option_detail.rb +55 -0
  84. data/lib/models/ib/order.rb +519 -0
  85. data/lib/models/ib/order_state.rb +152 -0
  86. data/lib/models/ib/portfolio_value.rb +64 -0
  87. data/lib/models/ib/stock.rb +16 -0
  88. data/lib/models/ib/underlying.rb +34 -0
  89. data/lib/models/ib/vertical.rb +96 -0
  90. data/lib/requires.rb +12 -0
  91. 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
+ ##
@@ -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
+
@@ -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