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
|
@@ -0,0 +1,442 @@
|
|
|
1
|
+
module IB
|
|
2
|
+
=begin
|
|
3
|
+
|
|
4
|
+
Plugin that provides helper methods for orders
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
Public API
|
|
8
|
+
==========
|
|
9
|
+
|
|
10
|
+
Extends IB::Account
|
|
11
|
+
|
|
12
|
+
=end
|
|
13
|
+
|
|
14
|
+
module Advanced
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def account_data_scan search_key, search_currency=nil
|
|
18
|
+
if search_currency.present?
|
|
19
|
+
account_values.find_all{|x| x.key.match( search_key ) && x.currency == search_currency.upcase }
|
|
20
|
+
else
|
|
21
|
+
account_values.find_all{|x| x.key.match( search_key ) }
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
=begin rdoc
|
|
28
|
+
given any key of local_id, perm_id or order_ref
|
|
29
|
+
and an optional status, which can be a string or a
|
|
30
|
+
regexp ( status: /mitted/ matches Submitted and Presubmitted)
|
|
31
|
+
the last associated Order-record is returned.
|
|
32
|
+
|
|
33
|
+
Thus if several Orders are placed with the same order_ref, the active one is returned
|
|
34
|
+
|
|
35
|
+
(If multible keys are specified, local_id preceeds perm_id)
|
|
36
|
+
|
|
37
|
+
=end
|
|
38
|
+
def locate_order local_id: nil, perm_id: nil, order_ref: nil, status: /ubmitted/, contract: nil, con_id: nil
|
|
39
|
+
search_option = [ local_id.present? ? [:local_id , local_id] : nil ,
|
|
40
|
+
perm_id.present? ? [:perm_id, perm_id] : nil,
|
|
41
|
+
order_ref.present? ? [:order_ref , order_ref ] : nil ].compact.first
|
|
42
|
+
matched_items = if search_option.nil?
|
|
43
|
+
orders # select all orders of the current account
|
|
44
|
+
else
|
|
45
|
+
key,value = search_option
|
|
46
|
+
orders.find_all{|x| x[key].to_i == value.to_i }
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
if contract.present?
|
|
50
|
+
if contract.con_id.zero? && !contract.is_a?( IB::Bag )
|
|
51
|
+
contract = contract.verify.first
|
|
52
|
+
end
|
|
53
|
+
matched_items = matched_items.find_all{|o| o.contract.essential == contract.essential }
|
|
54
|
+
elsif con_id.present?
|
|
55
|
+
matched_items = matched_items.find_all{|o| o.contract.con_id == con_id }
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
if status.present?
|
|
59
|
+
status = Regexp.new(status) unless status.is_a? Regexp
|
|
60
|
+
matched_items.detect{|x| x.order_state.status =~ status }
|
|
61
|
+
else
|
|
62
|
+
matched_items.last # return the last item
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
=begin rdoc
|
|
68
|
+
requires an IB::Order as parameter.
|
|
69
|
+
|
|
70
|
+
If attached, the associated IB::Contract is used to specify the tws-command
|
|
71
|
+
|
|
72
|
+
The associated Contract overtakes the specified (as parameter)
|
|
73
|
+
|
|
74
|
+
auto_adjust: Limit- and Aux-Prices are adjusted to Min-Tick
|
|
75
|
+
|
|
76
|
+
convert_size: The action-attribute (:buy :sell) is associated according the content of :total_quantity.
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
The parameter «order» is modified!
|
|
80
|
+
|
|
81
|
+
It can further used to modify and eventually cancel
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
Example
|
|
85
|
+
|
|
86
|
+
j36 = IB::Stock.new symbol: 'J36', exchange: 'SGX'
|
|
87
|
+
order = IB::Limit.order size: 100, price: 65.5
|
|
88
|
+
g = IB::Connection.current.clients.last
|
|
89
|
+
|
|
90
|
+
g.preview contract: j36, order: order
|
|
91
|
+
=> {:init_margin=>0.10864874e6,
|
|
92
|
+
:maint_margin=>0.9704137e5,
|
|
93
|
+
:equity_with_loan=>0.97877973e6,
|
|
94
|
+
:commission=>0.524e1,
|
|
95
|
+
:commission_currency=>"USD",
|
|
96
|
+
:warning=>""
|
|
97
|
+
|
|
98
|
+
g.place order: order
|
|
99
|
+
=> 67 # returns local_id
|
|
100
|
+
order.contract # updated (and verifired) contract-record
|
|
101
|
+
=> #<IB::Contract:0x00000000013c94b0 @attributes={:con_id=>9534669,
|
|
102
|
+
:exchange=>"SGX",
|
|
103
|
+
:right=>"",
|
|
104
|
+
:include_expired=>false}>
|
|
105
|
+
|
|
106
|
+
order.limit_price = 65 # set new price
|
|
107
|
+
g.modify order: order # and transmit
|
|
108
|
+
=> 67 # returns local_id
|
|
109
|
+
|
|
110
|
+
g.locate_order( local_id: {a number} )
|
|
111
|
+
=> returns the assigned order-record for inspection
|
|
112
|
+
|
|
113
|
+
g.cancel order: order
|
|
114
|
+
# logger output: 05:17:11 Cancelling 65 New #250/ from 3000/DU167349>
|
|
115
|
+
=end
|
|
116
|
+
|
|
117
|
+
def place_order order:, contract: nil, auto_adjust: true, convert_size: true
|
|
118
|
+
result = ->(l){ orders.detect{|x| x.local_id == l && x.submitted? } }
|
|
119
|
+
qualified_contract = ->(c) do
|
|
120
|
+
c.is_a?(IB::Contract) &&
|
|
121
|
+
#·IB::Symbols are always qualified. They carry a description-field
|
|
122
|
+
( c.description.present? || !c.con_id.to_i.zero? ||
|
|
123
|
+
(c.con_id.to_i <0 && c.sec_type == :bag ) ) # bags that carry a negative con_id are qualified
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
# assign qualificated contract to the order object if not present
|
|
127
|
+
order.contract ||= if qualified_contract[ contract ]
|
|
128
|
+
contract
|
|
129
|
+
else
|
|
130
|
+
contract.verify.first
|
|
131
|
+
end
|
|
132
|
+
# disable auto-adjust if min_tick is not available
|
|
133
|
+
auto_adjust = false if order.contract.contract_detail.nil?
|
|
134
|
+
|
|
135
|
+
error "No valid contract given" unless order.contract.is_a?(IB::Contract)
|
|
136
|
+
|
|
137
|
+
## sending of plain vanilla IB::Bags will fail using account.place, unless a (negative) con-id is provided!
|
|
138
|
+
error "place order: ContractVerification failed. No con_id assigned" unless qualified_contract[order.contract] or contract.nil?
|
|
139
|
+
|
|
140
|
+
# declare some variables
|
|
141
|
+
ib = IB::Connection.current
|
|
142
|
+
wrong_order = nil
|
|
143
|
+
the_local_id = nil
|
|
144
|
+
q = Queue.new
|
|
145
|
+
|
|
146
|
+
### Handle Error messages
|
|
147
|
+
### Default action: log message and raise IB::TransmissionError
|
|
148
|
+
sa = ib.subscribe( :Alert ) do | msg |
|
|
149
|
+
if msg.error_id == the_local_id
|
|
150
|
+
wrong_order = msg.message
|
|
151
|
+
if msg.code == 110 # The price does not confirm to the minimum price variation for this contract
|
|
152
|
+
if auto_adjust
|
|
153
|
+
wrong_order = nil
|
|
154
|
+
the_local_id = -1
|
|
155
|
+
ib.logger.info "adjusting order-price"
|
|
156
|
+
else
|
|
157
|
+
ib.logger.error "The price #{order.limit_price}/ #{order.aux_price} not confirm to the minimum price variation for #{order.contract.to_human}"
|
|
158
|
+
end
|
|
159
|
+
elsif [ 201, # Order rejected, No Trading permissions
|
|
160
|
+
203, # Security is not allowed for trading
|
|
161
|
+
325, # Discretionary Orders are not supported for this combination of order-type and exchange
|
|
162
|
+
355, # Order size does not conform to market rule
|
|
163
|
+
361, 362, 363, 364, # invalid trigger or stop-price
|
|
164
|
+
388, # Order size x is smaller than the minimum required size of yy.
|
|
165
|
+
].include? msg.code
|
|
166
|
+
ib.logger.error msg.message
|
|
167
|
+
end
|
|
168
|
+
q.close # closing the queue indicates that no order was transmitted
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
# transfer the received openOrder to the queue
|
|
172
|
+
sb = ib.subscribe( :OpenOrder ){|m| q << m.order if m.order.local_id.to_i == the_local_id.to_i }
|
|
173
|
+
# modify order (parameter)
|
|
174
|
+
order.account = account # assign the account_id to the account-field of IB::Order
|
|
175
|
+
self.orders.save_insert order, :order_ref
|
|
176
|
+
order.auto_adjust if ib.plugins.include?( "auto-adjust" ) && auto_adjust
|
|
177
|
+
if convert_size
|
|
178
|
+
order.action = order.total_quantity.to_d < 0 ? :sell : :buy unless order.action == :sell
|
|
179
|
+
logger.info{ "Converted ordersize to #{order.total_quantity} and triggered a #{order.action} order"} if order.total_quantity.to_d < 0
|
|
180
|
+
order.total_quantity = order.total_quantity.to_d.abs
|
|
181
|
+
end
|
|
182
|
+
# con_id and exchange fully qualify a contract, no need to transmit other data
|
|
183
|
+
# if no contract is passed to order.place, order.contract is used for placement
|
|
184
|
+
# ... delegated to order#modify...
|
|
185
|
+
# the_contract = order.contract.con_id.to_i > 0 ? Contract.new( con_id: order.contract.con_id, exchange: order.contract.exchange) : nil
|
|
186
|
+
contract = order.contract
|
|
187
|
+
order = order.then{|x| x.contract = nil; x }
|
|
188
|
+
loop do
|
|
189
|
+
the_local_id = ib.place_order order, contract # return the local_id
|
|
190
|
+
# if transmit is false, just include the local_id in the order-record
|
|
191
|
+
Thread.new{ if order.transmit || order.what_if then sleep 1 else sleep 0.001 end ; q.close }
|
|
192
|
+
tws_answer = q.pop
|
|
193
|
+
|
|
194
|
+
adjust_price = ->(p) do
|
|
195
|
+
if order.action == :sell
|
|
196
|
+
p + contract.contract_detail.min_tick
|
|
197
|
+
else
|
|
198
|
+
p - contract.contract_detail.min_tick
|
|
199
|
+
end
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
if q.closed?
|
|
203
|
+
if wrong_order.present?
|
|
204
|
+
raise IB::SymbolError, wrong_order
|
|
205
|
+
elsif the_local_id.present?
|
|
206
|
+
if the_local_id < 0 # auto-adjust condition
|
|
207
|
+
order.local_id = nil # reset order record
|
|
208
|
+
order.aux_price = adjust_price.call( order.aux_price ) unless order.aux_price.to_i.zero?
|
|
209
|
+
order.limit_price = adjust_price.call( order.limit_price ) unless order.limit_price.to_i.zero?
|
|
210
|
+
else
|
|
211
|
+
order.local_id = the_local_id
|
|
212
|
+
end
|
|
213
|
+
else
|
|
214
|
+
error " #{order.to_human} is not transmitted properly", :symbol
|
|
215
|
+
end
|
|
216
|
+
else
|
|
217
|
+
order=tws_answer # return order-record received from tws
|
|
218
|
+
end
|
|
219
|
+
break unless order.local_id.nil?
|
|
220
|
+
q = Queue.new # reset queue
|
|
221
|
+
end
|
|
222
|
+
order.contract = contract
|
|
223
|
+
ib.unsubscribe sa
|
|
224
|
+
ib.unsubscribe sb
|
|
225
|
+
order # return the order-record
|
|
226
|
+
end # place
|
|
227
|
+
|
|
228
|
+
|
|
229
|
+
# shortcut to enable
|
|
230
|
+
# account.place order: {} , contract: {}
|
|
231
|
+
# account.preview order: {} , contract: {}
|
|
232
|
+
# account.modify order: {}
|
|
233
|
+
alias place place_order
|
|
234
|
+
|
|
235
|
+
=begin #rdoc
|
|
236
|
+
Account#ModifyOrder operates in two modi:
|
|
237
|
+
|
|
238
|
+
First: The order is specified via local_id, perm_id or order_ref.
|
|
239
|
+
It is checked, whether the order is still modifyable.
|
|
240
|
+
Then the Order ist provided through the block. Any modification is done there.
|
|
241
|
+
Important: The Block has to return the modified IB::Order
|
|
242
|
+
|
|
243
|
+
Second: The order can be provided as parameter as well. This will be used
|
|
244
|
+
without further checking. The block is now optional.
|
|
245
|
+
Important: The OrderRecord must provide a valid Contract.
|
|
246
|
+
|
|
247
|
+
The simple version does not adjust the given prices to tick-limits.
|
|
248
|
+
This has to be done manually in the provided block
|
|
249
|
+
=end
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
def modify_order local_id: nil, order_ref: nil, order:nil, contract: nil
|
|
253
|
+
|
|
254
|
+
result = ->(l){ orders.detect{|x| x.local_id == l && x.submitted? } }
|
|
255
|
+
order ||= locate_order( local_id: local_id,
|
|
256
|
+
status: /ubmitted/ ,
|
|
257
|
+
order_ref: order_ref )
|
|
258
|
+
if order.is_a? IB::Order
|
|
259
|
+
order.modify
|
|
260
|
+
else
|
|
261
|
+
error "No suitable IB::Order provided/detected. Instead: #{order.inspect}"
|
|
262
|
+
end
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
alias modify modify_order
|
|
266
|
+
|
|
267
|
+
# Preview
|
|
268
|
+
#
|
|
269
|
+
# Submits a "WhatIf" Order
|
|
270
|
+
#
|
|
271
|
+
# Returns the presubmitted order record, where the local_id is erased and the what_if-flag is false
|
|
272
|
+
#
|
|
273
|
+
#
|
|
274
|
+
# output of the results:
|
|
275
|
+
# u = Connection.current.clients.last
|
|
276
|
+
# o = Limit.order ...
|
|
277
|
+
# c = Contract.new ...
|
|
278
|
+
# preview = u.preview contract: c, order: o
|
|
279
|
+
# puts preview.order_state.forcast
|
|
280
|
+
#
|
|
281
|
+
# The returned order can be used as argument for a subsequent order-placement
|
|
282
|
+
# i.e
|
|
283
|
+
# fits_margin_minium = ->(x) do
|
|
284
|
+
# buffer = x[equity_with_loan] - x[:init_margin]
|
|
285
|
+
# net_liquidation = account_data_scan( /NetLiq/ ).first.value.to_i
|
|
286
|
+
# buffer > net_liquidation * 0.1 # 90 Percent margin usage is aceptable
|
|
287
|
+
# end
|
|
288
|
+
# u.preview( contract: c, order: o )
|
|
289
|
+
# .check_margin(u){|y| fits_margin_minumum[ y.order_state.forcast ] } &.place
|
|
290
|
+
#
|
|
291
|
+
#
|
|
292
|
+
# The order received from the TWS is also kept in account.orders
|
|
293
|
+
#
|
|
294
|
+
# Raises IB::SymbolError if the Order could not be placed properly
|
|
295
|
+
#
|
|
296
|
+
def preview order:, contract: nil, **args_which_are_ignored
|
|
297
|
+
# to_do: use a copy of order instead of temporary setting order.what_if
|
|
298
|
+
q = Queue.new
|
|
299
|
+
ib = IB::Connection.current
|
|
300
|
+
contract = order.contract if contract.nil?
|
|
301
|
+
order = order.then{|x| x.contract = nil; x }
|
|
302
|
+
the_local_id = nil
|
|
303
|
+
# put the order into the queue (and exit) if the event is fired
|
|
304
|
+
req = ib.subscribe( :OpenOrder ) do |m|
|
|
305
|
+
q << m.order if m.order.local_id.to_i == the_local_id.to_i && !m.order.order_state.init_margin_after.nil?
|
|
306
|
+
end
|
|
307
|
+
|
|
308
|
+
order.what_if = true
|
|
309
|
+
order.account = account
|
|
310
|
+
the_local_id = ib.place_order order, contract
|
|
311
|
+
Thread.new{ sleep 2 ; q.close } # wait max 2 sec.
|
|
312
|
+
returned_order = q.pop
|
|
313
|
+
ib.unsubscribe req
|
|
314
|
+
# order.what_if = false # reset what_if flag
|
|
315
|
+
# order.local_id = nil # reset local_id to enable re-using the order-object for placing
|
|
316
|
+
raise IB::SymbolError,"(Preview-) #{order.to_human} is not transmitted properly" if q.closed?
|
|
317
|
+
#order.order_state.forcast # return_value
|
|
318
|
+
returned_order.local_id = nil
|
|
319
|
+
returned_order.what_if = false
|
|
320
|
+
returned_order.contract = contract
|
|
321
|
+
returned_order
|
|
322
|
+
end
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
# closes the contract by submitting an appropriate order
|
|
326
|
+
# the action- and total_amount attributes of the assigned order are overwritten.
|
|
327
|
+
#
|
|
328
|
+
# if a ratio-value (0 ..1) is specified in _order.total_quantity_ only a fraction of the position is closed.
|
|
329
|
+
# Other values are silently ignored
|
|
330
|
+
#
|
|
331
|
+
# if _reverse_ is specified, the opposite position is established.
|
|
332
|
+
# Any value in total_quantity is overwritten
|
|
333
|
+
#
|
|
334
|
+
# returns the order transmitted
|
|
335
|
+
#v # raises an IB::Error if no PortfolioValues have been loaded to the IB::Account
|
|
336
|
+
def close order:, contract: nil, reverse: false, **args_which_are_ignored
|
|
337
|
+
error "must only be called after initializing portfolio_values " if portfolio_values.blank?
|
|
338
|
+
contract_size = ->(c) do # note: portfolio_value.position is either positiv or negativ
|
|
339
|
+
if c.con_id <0 # Spread
|
|
340
|
+
p = portfolio_values.detect{|p| p.contract.con_id ==c.legs.first.con_id} &.position.to_i
|
|
341
|
+
p/ c.combo_legs.first.weight unless p.to_i.zero?
|
|
342
|
+
else
|
|
343
|
+
portfolio_values.detect{|x| x.contract.con_id == c.con_id} &.position.to_i # nil.to_i -->0
|
|
344
|
+
end
|
|
345
|
+
end
|
|
346
|
+
|
|
347
|
+
order.contract = contract.verify.first unless contract.nil? || contract.con_id.to_i <=0
|
|
348
|
+
error "Cannot transmit the order – No Contract given " unless order.contract.is_a?( IB::Contract )
|
|
349
|
+
|
|
350
|
+
the_quantity = if reverse
|
|
351
|
+
-contract_size[order.contract] * 2
|
|
352
|
+
elsif order.total_quantity.abs < 1 && !order.total_quantity.zero?
|
|
353
|
+
-contract_size[order.contract] * order.total_quantity.abs
|
|
354
|
+
else
|
|
355
|
+
-contract_size[order.contract]
|
|
356
|
+
end
|
|
357
|
+
if the_quantity.zero?
|
|
358
|
+
logger.info{ "Cannot close #{order.contract.to_human} - no position detected"}
|
|
359
|
+
else
|
|
360
|
+
order.total_quantity = the_quantity
|
|
361
|
+
order.action = nil
|
|
362
|
+
order.local_id = nil # in any case, close is a new order
|
|
363
|
+
logger.info { "Order modified to close, reduce or revese position: #{order.to_human}" }
|
|
364
|
+
place order: order, convert_size: true
|
|
365
|
+
end
|
|
366
|
+
end
|
|
367
|
+
|
|
368
|
+
# just a wrapper to the Gateway-cancel-order method
|
|
369
|
+
def cancel order:
|
|
370
|
+
Connection.current.cancel_order order.local_id
|
|
371
|
+
end
|
|
372
|
+
|
|
373
|
+
## ToDo ... needs adaption !
|
|
374
|
+
#returns an hash where portfolio_positions are grouped into Watchlists.
|
|
375
|
+
#
|
|
376
|
+
# Watchlist => [ contract => [ portfoliopositon] , ... ] ]
|
|
377
|
+
#
|
|
378
|
+
def organize_portfolio_positions the_watchlistsi #= IB::Gateway.current.active_watchlists
|
|
379
|
+
the_watchlists = [ the_watchlists ] unless the_watchlists.is_a?(Array)
|
|
380
|
+
self.focuses = portfolio_values.map do | pw | # iterate over pw
|
|
381
|
+
ref_con_id = pw.contract.con_id
|
|
382
|
+
z = the_watchlists.map do | w | # iterate over w and assign to z
|
|
383
|
+
watchlist_contract = w.find do |c| # iterate over c
|
|
384
|
+
if c.is_a? IB::Bag
|
|
385
|
+
c.combo_legs.map( &:con_id ).include?( ref_con_id )
|
|
386
|
+
else
|
|
387
|
+
c.con_id == ref_con_id
|
|
388
|
+
end
|
|
389
|
+
end rescue nil
|
|
390
|
+
watchlist_contract.present? ? [w,watchlist_contract] : nil
|
|
391
|
+
end.compact
|
|
392
|
+
|
|
393
|
+
z.empty? ? [ IB::Symbols::Unspecified, pw.contract, pw ] : z.first + pw
|
|
394
|
+
end.group_by{|a,_,_| a }.map{|x,y|[x, y.map{|_,d,e|[d,e]}.group_by{|e,_| e}.map{|f,z| [f, z.map(&:last)]} ] }.to_h
|
|
395
|
+
# group:by --> [a,b,c] .group_by {|_g,_| g} --->{ a => [a,b,c] }
|
|
396
|
+
# group_by+map --> removes "a" from the resulting array
|
|
397
|
+
end
|
|
398
|
+
|
|
399
|
+
|
|
400
|
+
def locate_contract con_id
|
|
401
|
+
contracts.detect{|x| x.con_id.to_i == con_id.to_i }
|
|
402
|
+
end
|
|
403
|
+
|
|
404
|
+
## returns the contract definition of an complex portfolio-position detected in the account
|
|
405
|
+
def complex_position con_id
|
|
406
|
+
con_id = con_id.con_id if con_id.is_a?(IB::Contract)
|
|
407
|
+
focuses.map{|x,y| y.detect{|x,y| x.con_id.to_i== con_id.to_i} }.compact.flatten.first
|
|
408
|
+
end
|
|
409
|
+
end # module Advanced
|
|
410
|
+
##
|
|
411
|
+
# in the console (call gateway with watchlist: [:Spreads, :BuyAndHold])
|
|
412
|
+
#head :001 > .clients.first.focuses.to_a.to_human
|
|
413
|
+
#Unspecified
|
|
414
|
+
#<Stock: BLUE EUR SBF>
|
|
415
|
+
#<PortfolioValue: DU167348 Pos=720 @ 15.88;Value=11433.24;PNL=-4870.05 unrealized;<Stock: BLUE EUR SBF>
|
|
416
|
+
#<Stock: CSCO USD NASDAQ>
|
|
417
|
+
#<PortfolioValue: DU167348 Pos=44 @ 44.4;Value=1953.6;PNL=1009.8 unrealized;<Stock: CSCO USD NASDAQ>
|
|
418
|
+
#<Stock: DBB USD ARCA>
|
|
419
|
+
#<PortfolioValue: DU167348 Pos=-1 @ 16.575;Value=-16.58;PNL=1.05 unrealized;<Stock: DBB USD ARCA>
|
|
420
|
+
#<Stock: NEU USD NYSE>
|
|
421
|
+
#<PortfolioValue: DU167348 Pos=1 @ 375.617;Value=375.62;PNL=98.63 unrealized;<Stock: NEU USD NYSE>
|
|
422
|
+
#<Stock: WFC USD NYSE>
|
|
423
|
+
#<PortfolioValue: DU167348 Pos=100 @ 51.25;Value=5125.0;PNL=-171.0 unrealized;<Stock: WFC USD NYSE>
|
|
424
|
+
#BuyAndHold
|
|
425
|
+
#<Stock: CIEN USD NYSE>
|
|
426
|
+
#<PortfolioValue: DU167348 Pos=812 @ 29.637;Value=24065.57;PNL=4841.47 unrealized;<Stock: CIEN USD NYSE>
|
|
427
|
+
#<Stock: J36 USD SGX>
|
|
428
|
+
#<PortfolioValue: DU167348 Pos=100 @ 56.245;Value=5624.5;PNL=-830.66 unrealized;<Stock: J36 USD SGX>
|
|
429
|
+
#Spreads
|
|
430
|
+
#<Strangle Estx50(3200.0,3000.0)[Dec 2018]>
|
|
431
|
+
#<PortfolioValue: DU167348 Pos=-3 @ 168.933;Value=-5067.99;PNL=603.51 unrealized;<Option: ESTX50 20181221 call 3000.0 EUR>
|
|
432
|
+
#<PortfolioValue: DU167348 Pos=-3 @ 142.574;Value=-4277.22;PNL=-867.72 unrealized;<Option: ESTX50 20181221 put 3200.0 EUR>
|
|
433
|
+
# => nil
|
|
434
|
+
#
|
|
435
|
+
#
|
|
436
|
+
# load managed-accounts first and switch to gateway-mode
|
|
437
|
+
Connection.current.activate_plugin 'managed-accounts'
|
|
438
|
+
Connection.current.activate_plugin 'order-flow'
|
|
439
|
+
class Account
|
|
440
|
+
include Advanced
|
|
441
|
+
end
|
|
442
|
+
end ## module IB
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
module IB
|
|
2
|
+
class Alert
|
|
3
|
+
=begin
|
|
4
|
+
The Singleton IB::Alert handles any response to IB:Messages::Incomming:Alert
|
|
5
|
+
|
|
6
|
+
Individual methods can be defined as well as methods responding to a group of error-codes.
|
|
7
|
+
The default-behavior is defined in the method_missing-method. This just logs the object at the debug level.
|
|
8
|
+
|
|
9
|
+
Default-wrappers to completely ignore the error-message (ignore_alert)
|
|
10
|
+
and to log the object in a different log-level (log_alert_in [warn,info,error] ) are defined in base_alert.
|
|
11
|
+
|
|
12
|
+
Just add
|
|
13
|
+
```
|
|
14
|
+
module IB
|
|
15
|
+
class Alert
|
|
16
|
+
log_alert_in_warn {list of codennumbers}
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
```
|
|
20
|
+
to your code
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
IB::Gateway calls the methods in response of subscribing to the :Alert signal by calling
|
|
24
|
+
IB::Alert.send("alert_#{msg.code}", msg )
|
|
25
|
+
|
|
26
|
+
To define a response to the code 134 ( Modify order failed) a method like
|
|
27
|
+
module IB
|
|
28
|
+
class Alert
|
|
29
|
+
def self.alert_134 msg
|
|
30
|
+
(your code)
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
has to be written.
|
|
35
|
+
|
|
36
|
+
Important: The class is accessed asynchronically. Be careful while raising interrupts.
|
|
37
|
+
|
|
38
|
+
=end
|
|
39
|
+
|
|
40
|
+
# acts as prototype for any generated method
|
|
41
|
+
#require 'active_support'
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def self.method_missing( method_id, msg , *args, &block )
|
|
45
|
+
if msg.is_a? IB::Messages::Incoming::Alert
|
|
46
|
+
# IB::Connection.logger.debug { msg.to_human }
|
|
47
|
+
else
|
|
48
|
+
IB::Connection.logger.error { "Argument to IB::Alert is not a IB::Messages::Incoming::Alert" }
|
|
49
|
+
IB::Connection.logger.error { "The object: #{msg.inspect} " }
|
|
50
|
+
end
|
|
51
|
+
rescue NoMethodError
|
|
52
|
+
unless IB::Connection.logger.nil?
|
|
53
|
+
IB::Connection.logger.error { "The Argument is not a valid IB::Messages:Incoming::Alert object"}
|
|
54
|
+
IB::Connection.logger.error { "The object: #{msg.inspect} " }
|
|
55
|
+
else
|
|
56
|
+
puts "No Logging-Device specified"
|
|
57
|
+
puts "The object: #{msg.inspect} "
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class << self
|
|
64
|
+
|
|
65
|
+
def ignore_alert *codes
|
|
66
|
+
codes.each do |n|
|
|
67
|
+
class_eval <<-EOD
|
|
68
|
+
def self.alert_#{n} msg
|
|
69
|
+
# even the log_debug entry is suppressed
|
|
70
|
+
end
|
|
71
|
+
EOD
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
def log_alert_in_info *codes
|
|
75
|
+
codes.each do |n|
|
|
76
|
+
class_eval <<-EOD
|
|
77
|
+
def self.alert_#{n} msg
|
|
78
|
+
# IB::Connection.logger.info { msg.to_human }
|
|
79
|
+
end
|
|
80
|
+
EOD
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
def log_alert_in_warn *codes
|
|
84
|
+
codes.each do |n|
|
|
85
|
+
class_eval <<-EOD
|
|
86
|
+
def self.alert_#{n} msg
|
|
87
|
+
# IB::Connection.logger.warn { msg.to_human }
|
|
88
|
+
end
|
|
89
|
+
EOD
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def log_alert_in_error *codes
|
|
94
|
+
codes.each do |n|
|
|
95
|
+
class_eval <<-EOD
|
|
96
|
+
def self.alert_#{n} msg
|
|
97
|
+
if msg.error_id.present? && msg.error_id > 0
|
|
98
|
+
# IB::Connection.logger.error { msg.message + ' id: ' + msg.error_id.to_s }
|
|
99
|
+
else
|
|
100
|
+
# IB::Connection.logger.error { msg.message }
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
EOD
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
ignore_alert 200 , # is handled by IB::Contract.update_contract
|
|
109
|
+
2100, # API client has been unsubscribed from account data
|
|
110
|
+
2105,
|
|
111
|
+
399 # your order will not be placed at the exchange until
|
|
112
|
+
|
|
113
|
+
log_alert_in_info 1102 #Connectivity between IB and Trader Workstation has been restored
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
log_alert_in_error 320, 321, 323, 324, #ServerError
|
|
117
|
+
## 110, # The price does not conform to the minimum price variation
|
|
118
|
+
# 103, #duplicate order ## order-alerts
|
|
119
|
+
# 201, #deleted objecta ## order-alerts
|
|
120
|
+
326 #Unable connect as the client id is already in use
|
|
121
|
+
|
|
122
|
+
log_alert_in_warn 354 #Requested market data is not subscribed
|
|
123
|
+
|
|
124
|
+
end
|
|
125
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# These Alerts are always active
|
|
2
|
+
module IB
|
|
3
|
+
class Alert
|
|
4
|
+
|
|
5
|
+
def self.alert_2102 msg
|
|
6
|
+
# Connectivity between IB and Trader Workstation has been restored - data maintained.
|
|
7
|
+
sleep 0.1 # no need to wait too long.
|
|
8
|
+
if IB::Gateway.current.check_connection
|
|
9
|
+
IB::Gateway.logger.debug { "Alert 2102: Connection stable" }
|
|
10
|
+
else
|
|
11
|
+
IB::Gateway.current.reconnect
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
module IB
|
|
2
|
+
class Alert
|
|
3
|
+
|
|
4
|
+
def self.alert_388 msg
|
|
5
|
+
# Order size x is smaller than the minimum required size of yy.
|
|
6
|
+
IB::Gateway.logger.error msg.inspect
|
|
7
|
+
# error msg, :order, nil
|
|
8
|
+
end
|
|
9
|
+
def self.alert_202 msg
|
|
10
|
+
# do anything in a secure mutex-synchronized-environment
|
|
11
|
+
any_order = IB::Gateway.current.account_data do | account |
|
|
12
|
+
order= account.locate_order( local_id: msg.error_id )
|
|
13
|
+
if order.present? && ( order.order_state.status != 'Cancelled' )
|
|
14
|
+
order.order_states.update_or_create( IB::OrderState.new( status: 'Cancelled',
|
|
15
|
+
perm_id: order.perm_id,
|
|
16
|
+
local_id: order.local_id ) ,
|
|
17
|
+
:status )
|
|
18
|
+
|
|
19
|
+
end
|
|
20
|
+
order # return_value
|
|
21
|
+
end
|
|
22
|
+
if any_order.compact.empty?
|
|
23
|
+
IB::Gateway.logger.error{"Alert 202: The deleted order was not registered: local_id #{msg.error_id}"}
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
class << self
|
|
30
|
+
=begin
|
|
31
|
+
IB::Alert#AddOrderstateAlert
|
|
32
|
+
|
|
33
|
+
The OrderState-Record is used to record the history of the order.
|
|
34
|
+
If selected Alert-Messages appear, they are added to the Order.order_state-Array.
|
|
35
|
+
The last Status is available as Order.order_state, all states are accessible by Order.order_states
|
|
36
|
+
|
|
37
|
+
The TWS-Message-text is stored to the »warning-text«-field.
|
|
38
|
+
The Status is always »rejected«.
|
|
39
|
+
If the first OrderState-object of a Order is »rejected«, the order is not placed at all.
|
|
40
|
+
Otherwise only the last action is not applied and the order is unchanged.
|
|
41
|
+
|
|
42
|
+
=end
|
|
43
|
+
def add_orderstate_alert *codes
|
|
44
|
+
codes.each do |n|
|
|
45
|
+
class_eval <<-EOD
|
|
46
|
+
def self.alert_#{n} msg
|
|
47
|
+
|
|
48
|
+
if msg.error_id.present?
|
|
49
|
+
IB::Gateway.current.account_data do | account |
|
|
50
|
+
order= account.locate_order( local_id: msg.error_id )
|
|
51
|
+
if order.present? && ( order.order_state.status != 'Rejected' )
|
|
52
|
+
order.order_states.update_or_create( IB::OrderState.new( status: 'Rejected' ,
|
|
53
|
+
perm_id: order.perm_id,
|
|
54
|
+
warning_text: '#{n}: '+ msg.message,
|
|
55
|
+
local_id: msg.error_id ), :status )
|
|
56
|
+
|
|
57
|
+
IB::Gateway.logger.error{ msg.to_human }
|
|
58
|
+
end # order present?
|
|
59
|
+
end # mutex-environment
|
|
60
|
+
end # branch
|
|
61
|
+
end # def
|
|
62
|
+
EOD
|
|
63
|
+
end # loop
|
|
64
|
+
end # def
|
|
65
|
+
end
|
|
66
|
+
add_orderstate_alert 103, # duplicate order
|
|
67
|
+
201, # deleted object
|
|
68
|
+
105, # Order being modified does not match original order
|
|
69
|
+
462, # Cannot change to the new Time in Force:GTD
|
|
70
|
+
329, # Cannot change to the new order type:STP
|
|
71
|
+
10147 # OrderId 0 that needs to be cancelled is not found.
|
|
72
|
+
end # class Alert
|
|
73
|
+
end # module IB
|
|
File without changes
|