ib-extensions 1.2 → 1.3.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 +4 -4
- data/Gemfile +3 -4
- data/Gemfile.lock +52 -42
- data/README.md +56 -10
- data/bin/console +10 -3
- data/bin/gateway +14 -9
- data/changelog.md +54 -0
- data/ib-extensions.gemspec +7 -5
- data/lib/ib/alerts/base-alert.rb +10 -13
- data/lib/ib/eod.rb +243 -127
- data/lib/ib/extensions/version.rb +14 -1
- data/lib/ib/extensions.rb +5 -0
- data/lib/ib/gateway/account-infos.rb +74 -47
- data/lib/ib/gateway/order-handling.rb +57 -25
- data/lib/ib/gateway.rb +45 -31
- data/lib/ib/market-price.rb +34 -34
- data/lib/ib/models/account.rb +177 -144
- data/lib/ib/models/bag.rb +19 -0
- data/lib/ib/models/contract.rb +16 -0
- data/lib/ib/models/future.rb +20 -0
- data/lib/ib/models/option.rb +20 -13
- data/lib/ib/option-chain.rb +4 -6
- data/lib/ib/option-greeks.rb +27 -21
- data/lib/ib/order_prototypes/all-in-one.rb +46 -0
- data/lib/ib/plot-poec.rb +60 -0
- data/lib/ib/probability_of_expiring.rb +109 -0
- data/lib/ib/spread-prototypes.rb +1 -0
- data/lib/ib/spread_prototypes/butterfly.rb +2 -4
- data/lib/ib/spread_prototypes/calendar.rb +25 -23
- data/lib/ib/spread_prototypes/straddle.rb +3 -3
- data/lib/ib/spread_prototypes/strangle.rb +8 -9
- data/lib/ib/spread_prototypes/vertical.rb +6 -7
- data/lib/ib/verify.rb +34 -39
- data/lib/ib-gateway.rb +12 -0
- metadata +53 -5
data/lib/ib/eod.rb
CHANGED
@@ -1,8 +1,10 @@
|
|
1
1
|
module IB
|
2
2
|
require 'active_support/core_ext/date/calculations'
|
3
3
|
require 'csv'
|
4
|
+
|
5
|
+
module Eod
|
4
6
|
module BuisinesDays
|
5
|
-
|
7
|
+
# https://stackoverflow.com/questions/4027768/calculate-number-of-business-days-between-two-days
|
6
8
|
|
7
9
|
# Calculates the number of business days in range (start_date, end_date]
|
8
10
|
#
|
@@ -14,138 +16,198 @@ require 'csv'
|
|
14
16
|
days_between = (end_date - start_date).to_i
|
15
17
|
return 0 unless days_between > 0
|
16
18
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
19
|
+
# Assuming we need to calculate days from 9th to 25th, 10-23 are covered
|
20
|
+
# by whole weeks, and 24-25 are extra days.
|
21
|
+
#
|
22
|
+
# Su Mo Tu We Th Fr Sa # Su Mo Tu We Th Fr Sa
|
23
|
+
# 1 2 3 4 5 # 1 2 3 4 5
|
24
|
+
# 6 7 8 9 10 11 12 # 6 7 8 9 ww ww ww
|
25
|
+
# 13 14 15 16 17 18 19 # ww ww ww ww ww ww ww
|
26
|
+
# 20 21 22 23 24 25 26 # ww ww ww ww ed ed 26
|
27
|
+
# 27 28 29 30 31 # 27 28 29 30 31
|
28
|
+
whole_weeks, extra_days = days_between.divmod(7)
|
29
|
+
|
30
|
+
unless extra_days.zero?
|
31
|
+
# Extra days start from the week day next to start_day,
|
32
|
+
# and end on end_date's week date. The position of the
|
33
|
+
# start date in a week can be either before (the left calendar)
|
34
|
+
# or after (the right one) the end date.
|
35
|
+
#
|
36
|
+
# Su Mo Tu We Th Fr Sa # Su Mo Tu We Th Fr Sa
|
37
|
+
# 1 2 3 4 5 # 1 2 3 4 5
|
38
|
+
# 6 7 8 9 10 11 12 # 6 7 8 9 10 11 12
|
39
|
+
# ## ## ## ## 17 18 19 # 13 14 15 16 ## ## ##
|
40
|
+
# 20 21 22 23 24 25 26 # ## 21 22 23 24 25 26
|
41
|
+
# 27 28 29 30 31 # 27 28 29 30 31
|
42
|
+
#
|
43
|
+
# If some of the extra_days fall on a weekend, they need to be subtracted.
|
44
|
+
# In the first case only corner days can be days off,
|
45
|
+
# and in the second case there are indeed two such days.
|
46
|
+
extra_days -= if start_date.tomorrow.wday <= end_date.wday
|
47
|
+
[start_date.tomorrow.sunday?, end_date.saturday?].count(true)
|
48
|
+
else
|
49
|
+
2
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
(whole_weeks * 5) + extra_days
|
52
54
|
end
|
53
55
|
end
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
56
|
+
# Receive EOD-Data and store the data in the `:bars`-property of IB::Contract
|
57
|
+
#
|
58
|
+
# contract.eod duration: {String or Integer}, start: {Date}, to: {Date}, what: {see below}, polars: {true|false}
|
59
|
+
#
|
60
|
+
#
|
61
|
+
#
|
62
|
+
# The Enddate has to be specified (as Date Object), `:to`, default: Date.today
|
63
|
+
#
|
64
|
+
# The Duration can either be a String "yx D", "yd W", "yx M" or an Integer ( implies "D").
|
65
|
+
# *notice* "W" fetchtes weekly and "M" monthly bars
|
66
|
+
#
|
67
|
+
# A start date can be given with the `:start` parameter.
|
68
|
+
#
|
69
|
+
# The parameter `:what` specifies the kind of received data.
|
70
|
+
#
|
71
|
+
# Valid values:
|
72
|
+
# :trades, :midpoint, :bid, :ask, :bid_ask,
|
73
|
+
# :historical_volatility, :option_implied_volatility,
|
74
|
+
# :option_volume, :option_open_interest
|
75
|
+
#
|
76
|
+
# Polars DataFrames
|
77
|
+
# -----------------
|
78
|
+
# If »polars: true« is specified the response is stored as PolarsDataframe.
|
79
|
+
# For further processing: https://github.com/ankane/polars-ruby
|
80
|
+
# https://pola-rs.github.io/polars/py-polars/html/index.html
|
81
|
+
#
|
82
|
+
# Error-handling
|
83
|
+
# --------------
|
84
|
+
# * Basically all Errors simply lead to log-entries:
|
85
|
+
# * the contract is not valid,
|
86
|
+
# * no market data subscriptions
|
87
|
+
# * other servers-side errors
|
88
|
+
#
|
89
|
+
# If the duration is longer then the maximum range, the response is
|
90
|
+
# cut to the maximum allowed range
|
91
|
+
#
|
92
|
+
# Customize the result
|
93
|
+
# --------------------
|
94
|
+
# The results are stored in the `:bars` property of the contract
|
95
|
+
#
|
96
|
+
#
|
97
|
+
# Limitations
|
98
|
+
# -----------
|
99
|
+
# To identify a request, the con_id of the asset is used
|
100
|
+
# Thus, parallel requests of a single asset with different time-frames will fail
|
101
|
+
#
|
102
|
+
# Examples
|
103
|
+
# --------
|
104
|
+
#
|
105
|
+
# puts Stock.new( symbol: :iwm).eod( start: Date.new(2019,10,9), duration: 3, polars: true)
|
106
|
+
# shape: (3, 8)
|
107
|
+
# ┌────────────┬────────┬────────┬────────┬────────┬────────┬─────────┬────────┐
|
108
|
+
# │ time ┆ open ┆ high ┆ low ┆ close ┆ volume ┆ wap ┆ trades │
|
109
|
+
# │ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- │
|
110
|
+
# │ date ┆ f64 ┆ f64 ┆ f64 ┆ f64 ┆ i64 ┆ f64 ┆ i64 │
|
111
|
+
# ╞════════════╪════════╪════════╪════════╪════════╪════════╪═════════╪════════╡
|
112
|
+
# │ 2019-10-08 ┆ 148.62 ┆ 149.37 ┆ 146.11 ┆ 146.45 ┆ 156625 ┆ 146.831 ┆ 88252 │
|
113
|
+
# │ 2019-10-09 ┆ 147.18 ┆ 148.0 ┆ 145.38 ┆ 145.85 ┆ 94337 ┆ 147.201 ┆ 51294 │
|
114
|
+
# │ 2019-10-10 ┆ 146.9 ┆ 148.74 ┆ 146.87 ┆ 148.24 ┆ 134549 ┆ 147.792 ┆ 71084 │
|
115
|
+
# └────────────┴────────┴────────┴────────┴────────┴────────┴─────────┴────────┘
|
116
|
+
#
|
117
|
+
# puts Stock.new( symbol: :iwm).eod( start: Date.new(2021,10,9), duration: '3W', polars: true)
|
118
|
+
# shape: (3, 8)
|
119
|
+
# ┌────────────┬────────┬────────┬────────┬────────┬─────────┬─────────┬────────┐
|
120
|
+
# │ time ┆ open ┆ high ┆ low ┆ close ┆ volume ┆ wap ┆ trades │
|
121
|
+
# │ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- │
|
122
|
+
# │ date ┆ f64 ┆ f64 ┆ f64 ┆ f64 ┆ i64 ┆ f64 ┆ i64 │
|
123
|
+
# ╞════════════╪════════╪════════╪════════╪════════╪═════════╪═════════╪════════╡
|
124
|
+
# │ 2021-10-01 ┆ 223.99 ┆ 227.68 ┆ 216.12 ┆ 222.8 ┆ 1295495 ┆ 222.226 ┆ 792711 │
|
125
|
+
# │ 2021-10-08 ┆ 221.4 ┆ 224.95 ┆ 216.76 ┆ 221.65 ┆ 1044233 ┆ 220.855 ┆ 621984 │
|
126
|
+
# │ 2021-10-15 ┆ 220.69 ┆ 228.41 ┆ 218.94 ┆ 225.05 ┆ 768065 ┆ 223.626 ┆ 437817 │
|
127
|
+
# └────────────┴────────┴────────┴────────┴────────┴─────────┴─────────┴────────┘
|
128
|
+
#
|
129
|
+
# puts Stock.new( symbol: :iwm).eod( start: Date.new(2022,10,1), duration: '3M', polars: true)
|
130
|
+
# shape: (3, 8)
|
131
|
+
# ┌────────────┬────────┬────────┬────────┬────────┬─────────┬─────────┬─────────┐
|
132
|
+
# │ time ┆ open ┆ high ┆ low ┆ close ┆ volume ┆ wap ┆ trades │
|
133
|
+
# │ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- │
|
134
|
+
# │ date ┆ f64 ┆ f64 ┆ f64 ┆ f64 ┆ i64 ┆ f64 ┆ i64 │
|
135
|
+
# ╞════════════╪════════╪════════╪════════╪════════╪═════════╪═════════╪═════════╡
|
136
|
+
# │ 2022-09-30 ┆ 181.17 ┆ 191.37 ┆ 162.77 ┆ 165.16 ┆ 4298969 ┆ 175.37 ┆ 2202407 │
|
137
|
+
# │ 2022-10-31 ┆ 165.5 ┆ 184.24 ┆ 162.5 ┆ 183.5 ┆ 4740014 ┆ 173.369 ┆ 2474286 │
|
138
|
+
# │ 2022-11-30 ┆ 184.51 ┆ 189.56 ┆ 174.11 ┆ 188.19 ┆ 3793861 ┆ 182.594 ┆ 1945674 │
|
139
|
+
# └────────────┴────────┴────────┴────────┴────────┴─────────┴─────────┴─────────┘
|
140
|
+
#
|
141
|
+
# puts Stock.new( symbol: :iwm).eod( start: Date.new(2020,1,1), duration: '3M', what: :option_implied_vol, polars: true
|
142
|
+
# atility )
|
143
|
+
# shape: (3, 8)
|
144
|
+
# ┌────────────┬──────────┬──────────┬──────────┬──────────┬────────┬──────────┬────────┐
|
145
|
+
# │ time ┆ open ┆ high ┆ low ┆ close ┆ volume ┆ wap ┆ trades │
|
146
|
+
# │ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- ┆ --- │
|
147
|
+
# │ date ┆ f64 ┆ f64 ┆ f64 ┆ f64 ┆ i64 ┆ f64 ┆ i64 │
|
148
|
+
# ╞════════════╪══════════╪══════════╪══════════╪══════════╪════════╪══════════╪════════╡
|
149
|
+
# │ 2019-12-31 ┆ 0.134933 ┆ 0.177794 ┆ 0.115884 ┆ 0.138108 ┆ 0 ┆ 0.178318 ┆ 0 │
|
150
|
+
# │ 2020-01-31 ┆ 0.139696 ┆ 0.190494 ┆ 0.120646 ┆ 0.185732 ┆ 0 ┆ 0.19097 ┆ 0 │
|
151
|
+
# │ 2020-02-28 ┆ 0.185732 ┆ 0.436549 ┆ 0.134933 ┆ 0.39845 ┆ 0 ┆ 0.435866 ┆ 0 │
|
152
|
+
# └────────────┴──────────┴──────────┴──────────┴──────────┴────────┴──────────┴────────┘
|
153
|
+
#
|
154
|
+
def eod start: nil, to: nil, duration: nil , what: :trades, polars: false
|
95
155
|
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
end
|
105
|
-
recieved.push Time.now
|
106
|
-
end
|
107
|
-
b = tws.subscribe( IB::Messages::Incoming::Alert) do |msg|
|
108
|
-
if [321,162,200].include? msg.code
|
109
|
-
tws.logger.info msg.message
|
110
|
-
# TWS Error 200: No security definition has been found for the request
|
111
|
-
# TWS Error 354: Requested market data is not subscribed.
|
112
|
-
# TWS Error 162 # Historical Market Data Service error
|
113
|
-
recieved.close
|
114
|
-
end
|
115
|
-
end
|
116
|
-
|
117
|
-
duration = if duration.present?
|
118
|
-
duration.is_a?(String) ? duration : duration.to_s + " D"
|
119
|
-
elsif start.present?
|
120
|
-
BuisinesDays.business_days_between(start, to).to_s + " D"
|
121
|
-
else
|
122
|
-
"1 D"
|
123
|
-
end
|
124
|
-
|
125
|
-
tws.send_message IB::Messages::Outgoing::RequestHistoricalData.new(
|
126
|
-
:request_id => con_id,
|
127
|
-
:contract => self,
|
128
|
-
:end_date_time => to.to_time.to_ib, # Time.now.to_ib,
|
129
|
-
:duration => duration, # ?
|
130
|
-
:bar_size => :day1, # IB::BAR_SIZES.key(:hour)?
|
131
|
-
:what_to_show => what,
|
132
|
-
:use_rth => 0,
|
133
|
-
:format_date => 2,
|
134
|
-
:keep_up_todate => 0)
|
135
|
-
|
136
|
-
recieved.pop # blocks until a message is ready on the queue or the queue is closed
|
137
|
-
|
138
|
-
tws.unsubscribe a
|
139
|
-
tws.unsubscribe b
|
156
|
+
# error "EOD:: Start-Date (parameter: to) must be a Date-Object" unless to.is_a? Date
|
157
|
+
normalize_duration = ->(d) do
|
158
|
+
if d.is_a?(Integer) || !["D","M","W","Y"].include?( d[-1].upcase )
|
159
|
+
d.to_i.to_s + "D"
|
160
|
+
else
|
161
|
+
d.gsub(" ","")
|
162
|
+
end.insert(-2, " ")
|
163
|
+
end
|
140
164
|
|
141
|
-
|
165
|
+
get_end_date = -> do
|
166
|
+
d = normalize_duration.call(duration)
|
167
|
+
case d[-1]
|
168
|
+
when "D"
|
169
|
+
start + d.to_i - 1
|
170
|
+
when 'W'
|
171
|
+
Date.commercial( start.year, start.cweek + d.to_i - 1, 1)
|
172
|
+
when 'M'
|
173
|
+
Date.new( start.year, start.month + d.to_i - 1 , start.day )
|
174
|
+
end
|
175
|
+
end
|
142
176
|
|
143
|
-
|
177
|
+
if to.nil?
|
178
|
+
# case eod start= Date.new ...
|
179
|
+
to = if start.present? && duration.nil?
|
180
|
+
# case eod start= Date.new
|
181
|
+
duration = BuisinesDays.business_days_between(start, to).to_s + "D"
|
182
|
+
Date.today # assign to var: to
|
183
|
+
elsif start.present? && duration.present?
|
184
|
+
# case eod start= Date.new , duration: 'nN'
|
185
|
+
get_end_date.call # assign to var: to
|
186
|
+
elsif duration.present?
|
187
|
+
# case start is not present, we are collecting until the present day
|
188
|
+
Date.today # assign to var: to
|
189
|
+
else
|
190
|
+
duration = "1D"
|
191
|
+
Date.today
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
barsize = case normalize_duration.call(duration)[-1].upcase
|
196
|
+
when "W"
|
197
|
+
:week1
|
198
|
+
when "M"
|
199
|
+
:month1
|
200
|
+
else
|
201
|
+
:day1
|
202
|
+
end
|
144
203
|
|
145
|
-
# creates (or overwrites) the specified file (or symbol.csv) and saves bar-data
|
146
|
-
def to_csv file:nil
|
147
|
-
file ||= "#{symbol}.csv"
|
148
204
|
|
205
|
+
get_bars(to.to_time.to_ib , normalize_duration[duration], barsize, what, polars)
|
206
|
+
|
207
|
+
end # def
|
208
|
+
|
209
|
+
# creates (or overwrites) the specified file (or symbol.csv) and saves bar-data
|
210
|
+
def to_csv file: "#{symbol}.csv"
|
149
211
|
if bars.present?
|
150
212
|
headers = bars.first.invariant_attributes.keys
|
151
213
|
CSV.open( file, 'w' ) {|f| f << headers ; bars.each {|y| f << y.invariant_attributes.values } }
|
@@ -160,6 +222,60 @@ require 'csv'
|
|
160
222
|
self.bars << IB::Bar.new( **row.to_h )
|
161
223
|
end
|
162
224
|
end
|
225
|
+
|
226
|
+
def get_bars(end_date_time, duration, bar_size, what_to_show, polars)
|
227
|
+
|
228
|
+
tws = IB::Connection.current
|
229
|
+
received = Queue.new
|
230
|
+
r = nil
|
231
|
+
# the hole response is transmitted at once!
|
232
|
+
a = tws.subscribe(IB::Messages::Incoming::HistoricalData) do |msg|
|
233
|
+
if msg.request_id == con_id
|
234
|
+
self.bars = if polars
|
235
|
+
# msg.results.each { |entry| puts " #{entry}" }
|
236
|
+
Polars::DataFrame.new msg.results.map( &:invariant_attributes )
|
237
|
+
else
|
238
|
+
msg.results
|
239
|
+
end
|
240
|
+
end
|
241
|
+
received.push Time.now
|
242
|
+
end
|
243
|
+
b = tws.subscribe( IB::Messages::Incoming::Alert) do |msg|
|
244
|
+
if [321,162,200].include? msg.code
|
245
|
+
tws.logger.info msg.message
|
246
|
+
# TWS Error 200: No security definition has been found for the request
|
247
|
+
# TWS Error 354: Requested market data is not subscribed.
|
248
|
+
# TWS Error 162 # Historical Market Data Service error
|
249
|
+
received.close
|
250
|
+
elsif msg.code.to_i == 2174
|
251
|
+
tws.logger.info "Please switch to the \"10-19\"-Branch of the git-repository"
|
252
|
+
end
|
253
|
+
end
|
254
|
+
|
255
|
+
|
256
|
+
tws.send_message IB::Messages::Outgoing::RequestHistoricalData.new(
|
257
|
+
:request_id => con_id,
|
258
|
+
:contract => self,
|
259
|
+
:end_date_time => end_date_time,
|
260
|
+
:duration => duration, # see ib/messages/outgoing/bar_request.rb => max duration for 5sec bar lookback is 10 000 - i.e. will yield 2000 bars
|
261
|
+
:bar_size => bar_size, # IB::BAR_SIZES.key(:hour)
|
262
|
+
:what_to_show => what_to_show,
|
263
|
+
:use_rth => 0,
|
264
|
+
:format_date => 2,
|
265
|
+
:keep_up_todate => 0)
|
266
|
+
|
267
|
+
received.pop # blocks until a message is ready on the queue or the queue is closed
|
268
|
+
|
269
|
+
tws.unsubscribe a
|
270
|
+
tws.unsubscribe b
|
271
|
+
|
272
|
+
block_given? ? bars.map{|y| yield y} : bars # return bars or result of block
|
273
|
+
|
274
|
+
end # def
|
275
|
+
end # module eod
|
276
|
+
|
277
|
+
class Contract
|
278
|
+
include Eod
|
163
279
|
end # class
|
164
|
-
end # module
|
280
|
+
end # module IB
|
165
281
|
|
@@ -1,5 +1,18 @@
|
|
1
1
|
module IB
|
2
2
|
module Extensions
|
3
|
-
VERSION = "1.
|
3
|
+
VERSION = "1.3.1"
|
4
4
|
end
|
5
5
|
end
|
6
|
+
|
7
|
+
|
8
|
+
__END__
|
9
|
+
|
10
|
+
Changelog V. 1.2 -> 1.3
|
11
|
+
|
12
|
+
* added probability_of_expiring to IB::Option
|
13
|
+
* IB::Contract.eod returns a Polars DataFrame
|
14
|
+
* improved IB::Option.request_greeks
|
15
|
+
* improved IB::Contract.verify
|
16
|
+
|
17
|
+
1.3.1: Contract.eod: Parameter polars: true|false
|
18
|
+
|
data/lib/ib/extensions.rb
CHANGED
@@ -1,4 +1,6 @@
|
|
1
1
|
## require any file, except gateway and related
|
2
|
+
require "distribution"
|
3
|
+
require "polars"
|
2
4
|
require "ib/extensions/version"
|
3
5
|
require "ib/verify"
|
4
6
|
require "ib/eod"
|
@@ -8,3 +10,6 @@ require "ib/option-greeks"
|
|
8
10
|
require "ib/order-prototypes"
|
9
11
|
require "ib/spread-prototypes"
|
10
12
|
require "ib/models/option"
|
13
|
+
require "ib/models/future"
|
14
|
+
require "ib/models/contract"
|
15
|
+
require "ib/models/bag"
|
@@ -5,64 +5,82 @@ module IB
|
|
5
5
|
class Alert
|
6
6
|
class << self
|
7
7
|
def alert_2101 msg
|
8
|
-
|
9
|
-
@status_2101 = msg.dup
|
8
|
+
IB::Connection.logger.error {msg.message}
|
9
|
+
@status_2101 = msg.dup
|
10
10
|
end
|
11
11
|
|
12
12
|
def status_2101 account # resets status and raises IB::TransmissionError
|
13
13
|
error account.account + ": " +@status_2101.message, :reader unless @status_2101.nil?
|
14
|
-
@status_2101 = nil # always returns nil
|
14
|
+
@status_2101 = nil # always returns nil
|
15
15
|
end
|
16
16
|
end
|
17
|
-
end
|
17
|
+
end
|
18
18
|
end # module
|
19
19
|
|
20
20
|
module AccountInfos
|
21
21
|
|
22
22
|
=begin
|
23
23
|
Queries the tws for Account- and PortfolioValues
|
24
|
-
The parameter can either be the account_id, the IB::Account-Object or
|
24
|
+
The parameter can either be the account_id, the IB::Account-Object or
|
25
25
|
an Array of account_id and IB::Account-Objects.
|
26
26
|
|
27
|
-
|
27
|
+
Resets Account#portfolio_values and -account_values
|
28
|
+
|
29
|
+
Raises an IB::TransmissionError if the account-data are not transmitted in time (1 sec)
|
28
30
|
|
29
|
-
|
31
|
+
Raises an IB::Error if less then 100 items are received.
|
30
32
|
=end
|
31
|
-
|
33
|
+
def get_account_data *accounts, **compatibily_argument
|
32
34
|
|
33
35
|
|
34
|
-
|
36
|
+
subscription = subscribe_account_updates( continuously: false )
|
37
|
+
download_end = nil # declare variable
|
35
38
|
|
36
|
-
accounts =
|
37
|
-
|
38
|
-
# Account-infos have to be requested
|
39
|
-
# subsequent (parallel) calls kill the former
|
39
|
+
accounts = clients if accounts.empty?
|
40
|
+
IB::Connection.logger.warn{ "No active account present. AccountData are NOT requested" } if accounts.empty?
|
41
|
+
# Account-infos have to be requested sequentially.
|
42
|
+
# subsequent (parallel) calls kill the former on the tws-server-side
|
40
43
|
# In addition, there is no need to cancel the subscription of an request, as a new
|
41
44
|
# one overwrites the active one.
|
42
45
|
accounts.each do | ac |
|
43
|
-
account = ac.is_a?( IB::Account ) ? ac : clients.find{|x| x.account == ac }
|
46
|
+
account = ac.is_a?( IB::Account ) ? ac : clients.find{|x| x.account == ac }
|
44
47
|
error( "No Account detected " ) unless account.is_a? IB::Account
|
45
48
|
# don't repeat the query until 170 sec. have passed since the previous update
|
46
|
-
if account.last_updated.nil? || ( Time.now - account.last_updated ) > 170 # sec
|
47
|
-
|
48
|
-
|
49
|
+
if account.last_updated.nil? || ( Time.now - account.last_updated ) > 170 # sec
|
50
|
+
IB::Connection.logger.debug{ "#{account.account} :: Erasing Account- and Portfolio Data " }
|
51
|
+
IB::Connection.logger.debug{ "#{account.account} :: Requesting AccountData " }
|
52
|
+
|
53
|
+
q = Queue.new
|
54
|
+
download_end = tws.subscribe( :AccountDownloadEnd ) do | msg |
|
55
|
+
q.push true if msg.account_name == account.account
|
56
|
+
end
|
49
57
|
# reset account and portfolio-values
|
50
|
-
account.portfolio_values =
|
51
|
-
account.account_values =
|
58
|
+
account.portfolio_values = []
|
59
|
+
account.account_values = []
|
60
|
+
# Data are gathered asynchron through the active subscription defined in `subscribe_account_updates`
|
52
61
|
send_message :RequestAccountData, subscribe: true, account_code: account.account
|
53
|
-
|
54
|
-
#
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
+
|
63
|
+
th = Thread.new{ sleep 10 ; q.close } # close the queue after 10 seconds
|
64
|
+
q.pop # wait for the data (or the closing event)
|
65
|
+
|
66
|
+
if q.closed?
|
67
|
+
error "No AccountData received", :reader
|
68
|
+
else
|
69
|
+
q.close
|
70
|
+
tws.unsubscribe download_end
|
71
|
+
end
|
72
|
+
|
73
|
+
account.organize_portfolio_positions unless IB::Gateway.current.active_watchlists.empty?
|
62
74
|
else
|
63
|
-
|
75
|
+
IB::Connection.logger.info{ "#{account.account} :: Using stored AccountData " }
|
64
76
|
end
|
65
77
|
end
|
78
|
+
tws.send_message :RequestAccountData, subscribe: false ## do this only once
|
79
|
+
tws.unsubscribe subscription
|
80
|
+
rescue IB::TransmissionError => e
|
81
|
+
tws.unsubscribe download_end unless download_end.nil?
|
82
|
+
tws.unsubscribe subscription
|
83
|
+
raise
|
66
84
|
end
|
67
85
|
|
68
86
|
|
@@ -75,40 +93,49 @@ raises an IB::Error if less then 100 items are recieved-
|
|
75
93
|
|
76
94
|
# The subscription method should called only once per session.
|
77
95
|
# It places subscribers to AccountValue and PortfolioValue Messages, which should remain
|
78
|
-
# active through
|
79
|
-
|
80
|
-
|
81
|
-
|
96
|
+
# active through the session.
|
97
|
+
#
|
98
|
+
# The method returns the subscription-number.
|
99
|
+
#
|
100
|
+
# thus
|
101
|
+
# subscription = subscribe_account_updates
|
102
|
+
# # some code
|
103
|
+
# IB::Connection.current.unsubscribe subscription
|
104
|
+
#
|
105
|
+
# clears the subscription
|
106
|
+
#
|
107
|
+
|
108
|
+
def subscribe_account_updates continuously: true
|
82
109
|
tws.subscribe( :AccountValue, :PortfolioValue,:AccountDownloadEnd ) do | msg |
|
83
110
|
account_data( msg.account_name ) do | account | # enter mutex controlled zone
|
84
111
|
case msg
|
85
112
|
when IB::Messages::Incoming::AccountValue
|
86
113
|
account.account_values << msg.account_value
|
87
114
|
account.update_attribute :last_updated, Time.now
|
88
|
-
|
89
|
-
when IB::Messages::Incoming::AccountDownloadEnd
|
115
|
+
IB::Connection.logger.debug { "#{account.account} :: #{msg.account_value.to_human }"}
|
116
|
+
when IB::Messages::Incoming::AccountDownloadEnd
|
90
117
|
if account.account_values.size > 10
|
91
|
-
# simply don't cancel the
|
118
|
+
# simply don't cancel the subscription if continuously is specified
|
92
119
|
# the connected flag is set in any case, indicating that valid data are present
|
93
|
-
|
120
|
+
# tws.send_message :RequestAccountData, subscribe: false, account_code: account.account unless continuously
|
94
121
|
account.update_attribute :connected, true ## flag: Account is completely initialized
|
95
|
-
|
96
|
-
else # unreasonable account_data
|
97
|
-
error "#{account.account} => Count of AccountValues too small: #{account.account_values.size}" , :reader
|
122
|
+
IB::Connection.logger.info { "#{account.account} => Count of AccountValues: #{account.account_values.size}" }
|
123
|
+
else # unreasonable account_data received - request is still active
|
124
|
+
error "#{account.account} => Count of AccountValues too small: #{account.account_values.size}" , :reader
|
98
125
|
end
|
99
126
|
when IB::Messages::Incoming::PortfolioValue
|
100
|
-
|
101
|
-
|
127
|
+
account.contracts << msg.contract unless account.contracts.detect{|y| y.con_id == msg.contract.con_id }
|
128
|
+
account.portfolio_values << msg.portfolio_value
|
102
129
|
# msg.portfolio_value.account = account
|
103
|
-
|
130
|
+
# # link contract -> portfolio value
|
104
131
|
# account.contracts.find{ |x| x.con_id == msg.contract.con_id }
|
105
132
|
# .portfolio_values
|
106
|
-
# .update_or_create( msg.portfolio_value ) { :account }
|
107
|
-
|
133
|
+
# .update_or_create( msg.portfolio_value ) { :account }
|
134
|
+
IB::Connection.logger.debug { "#{ account.account } :: #{ msg.contract.to_human }" }
|
108
135
|
end # case
|
109
|
-
end # account_data
|
136
|
+
end # account_data
|
110
137
|
end # subscribe
|
111
|
-
end # def
|
138
|
+
end # def
|
112
139
|
|
113
140
|
|
114
141
|
end # module
|