ibapi-stable 10.37.2__py3-none-any.whl

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.
Files changed (57) hide show
  1. ibapi/__init__.py +16 -0
  2. ibapi/account_summary_tags.py +70 -0
  3. ibapi/client.py +4891 -0
  4. ibapi/client_utils.py +412 -0
  5. ibapi/comm.py +100 -0
  6. ibapi/commission_and_fees_report.py +31 -0
  7. ibapi/common.py +315 -0
  8. ibapi/connection.py +126 -0
  9. ibapi/const.py +12 -0
  10. ibapi/contract.py +265 -0
  11. ibapi/decoder.py +1711 -0
  12. ibapi/decoder_utils.py +420 -0
  13. ibapi/enum_implem.py +20 -0
  14. ibapi/errors.py +43 -0
  15. ibapi/execution.py +95 -0
  16. ibapi/ineligibility_reason.py +18 -0
  17. ibapi/message.py +186 -0
  18. ibapi/news.py +13 -0
  19. ibapi/object_implem.py +12 -0
  20. ibapi/order.py +263 -0
  21. ibapi/order_cancel.py +21 -0
  22. ibapi/order_condition.py +289 -0
  23. ibapi/order_state.py +117 -0
  24. ibapi/orderdecoder.py +539 -0
  25. ibapi/protobuf/CancelOrderRequest_pb2.py +38 -0
  26. ibapi/protobuf/ComboLeg_pb2.py +37 -0
  27. ibapi/protobuf/Contract_pb2.py +39 -0
  28. ibapi/protobuf/DeltaNeutralContract_pb2.py +37 -0
  29. ibapi/protobuf/ErrorMessage_pb2.py +37 -0
  30. ibapi/protobuf/ExecutionDetailsEnd_pb2.py +37 -0
  31. ibapi/protobuf/ExecutionDetails_pb2.py +39 -0
  32. ibapi/protobuf/ExecutionFilter_pb2.py +37 -0
  33. ibapi/protobuf/ExecutionRequest_pb2.py +38 -0
  34. ibapi/protobuf/Execution_pb2.py +37 -0
  35. ibapi/protobuf/GlobalCancelRequest_pb2.py +38 -0
  36. ibapi/protobuf/OpenOrder_pb2.py +40 -0
  37. ibapi/protobuf/OpenOrdersEnd_pb2.py +37 -0
  38. ibapi/protobuf/OrderAllocation_pb2.py +37 -0
  39. ibapi/protobuf/OrderCancel_pb2.py +37 -0
  40. ibapi/protobuf/OrderCondition_pb2.py +37 -0
  41. ibapi/protobuf/OrderState_pb2.py +38 -0
  42. ibapi/protobuf/OrderStatus_pb2.py +37 -0
  43. ibapi/protobuf/Order_pb2.py +51 -0
  44. ibapi/protobuf/PlaceOrderRequest_pb2.py +39 -0
  45. ibapi/protobuf/SoftDollarTier_pb2.py +37 -0
  46. ibapi/reader.py +49 -0
  47. ibapi/scanner.py +77 -0
  48. ibapi/server_versions.py +159 -0
  49. ibapi/softdollartier.py +16 -0
  50. ibapi/tag_value.py +22 -0
  51. ibapi/ticktype.py +117 -0
  52. ibapi/utils.py +233 -0
  53. ibapi/wrapper.py +885 -0
  54. ibapi_stable-10.37.2.dist-info/METADATA +136 -0
  55. ibapi_stable-10.37.2.dist-info/RECORD +57 -0
  56. ibapi_stable-10.37.2.dist-info/WHEEL +5 -0
  57. ibapi_stable-10.37.2.dist-info/top_level.txt +1 -0
ibapi/client.py ADDED
@@ -0,0 +1,4891 @@
1
+ """
2
+ Copyright (C) 2025 Interactive Brokers LLC. All rights reserved. This code is subject to the terms
3
+ and conditions of the IB API Non-Commercial License or the IB API Commercial License, as applicable.
4
+
5
+ The main class to use from API user's point of view.
6
+ It takes care of almost everything:
7
+ - implementing the requests
8
+ - creating the answer decoder
9
+ - creating the connection to TWS/IBGW
10
+ The user just needs to override EWrapper methods to receive the answers.
11
+ """
12
+
13
+ import logging
14
+ import queue
15
+ import socket
16
+ import sys
17
+
18
+ from ibapi import decoder, reader, comm
19
+ from ibapi.comm import make_field, make_field_handle_empty
20
+ from ibapi.common import * # @UnusedWildImport
21
+ from ibapi.connection import Connection
22
+ from ibapi.const import NO_VALID_ID, MAX_MSG_LEN, UNSET_DOUBLE
23
+ from ibapi.contract import Contract
24
+ from ibapi.errors import (
25
+ NOT_CONNECTED,
26
+ CONNECT_FAIL,
27
+ BAD_LENGTH,
28
+ UPDATE_TWS,
29
+ FA_PROFILE_NOT_SUPPORTED,
30
+ BAD_MESSAGE,
31
+ )
32
+ from ibapi.execution import ExecutionFilter
33
+ from ibapi.message import OUT
34
+ from ibapi.order import Order, COMPETE_AGAINST_BEST_OFFSET_UP_TO_MID
35
+ from ibapi.order_cancel import OrderCancel
36
+ from ibapi.scanner import ScannerSubscription
37
+ from ibapi.server_versions import (
38
+ MIN_SERVER_VER_OPTIONAL_CAPABILITIES,
39
+ MIN_CLIENT_VER,
40
+ MAX_CLIENT_VER,
41
+ MIN_SERVER_VER_DELTA_NEUTRAL,
42
+ MIN_SERVER_VER_REQ_MKT_DATA_CONID,
43
+ MIN_SERVER_VER_TRADING_CLASS,
44
+ MIN_SERVER_VER_REQ_SMART_COMPONENTS,
45
+ MIN_SERVER_VER_LINKING,
46
+ MIN_SERVER_VER_REQ_MARKET_DATA_TYPE,
47
+ MIN_SERVER_VER_MARKET_RULES,
48
+ MIN_SERVER_VER_TICK_BY_TICK,
49
+ MIN_SERVER_VER_TICK_BY_TICK_IGNORE_SIZE,
50
+ MIN_SERVER_VER_REQ_CALC_IMPLIED_VOLAT,
51
+ MIN_SERVER_VER_SCALE_ORDERS2,
52
+ MIN_SERVER_VER_ALGO_ORDERS,
53
+ MIN_SERVER_VER_NOT_HELD,
54
+ MIN_SERVER_VER_SEC_ID_TYPE,
55
+ MIN_SERVER_VER_PLACE_ORDER_CONID,
56
+ MIN_SERVER_VER_SSHORTX,
57
+ MIN_SERVER_VER_HEDGE_ORDERS,
58
+ MIN_SERVER_VER_OPT_OUT_SMART_ROUTING,
59
+ MIN_SERVER_VER_DELTA_NEUTRAL_CONID,
60
+ MIN_SERVER_VER_DELTA_NEUTRAL_OPEN_CLOSE,
61
+ MIN_SERVER_VER_SCALE_ORDERS3,
62
+ MIN_SERVER_VER_ORDER_COMBO_LEGS_PRICE,
63
+ MIN_SERVER_VER_TRAILING_PERCENT,
64
+ MIN_SERVER_VER_SCALE_TABLE,
65
+ MIN_SERVER_VER_ALGO_ID,
66
+ MIN_SERVER_VER_ORDER_SOLICITED,
67
+ MIN_SERVER_VER_MODELS_SUPPORT,
68
+ MIN_SERVER_VER_EXT_OPERATOR,
69
+ MIN_SERVER_VER_SOFT_DOLLAR_TIER,
70
+ MIN_SERVER_VER_CASH_QTY,
71
+ MIN_SERVER_VER_DECISION_MAKER,
72
+ MIN_SERVER_VER_MIFID_EXECUTION,
73
+ MIN_SERVER_VER_AUTO_PRICE_FOR_HEDGE,
74
+ MIN_SERVER_VER_ORDER_CONTAINER,
75
+ MIN_SERVER_VER_PRICE_MGMT_ALGO,
76
+ MIN_SERVER_VER_DURATION,
77
+ MIN_SERVER_VER_POST_TO_ATS,
78
+ MIN_SERVER_VER_AUTO_CANCEL_PARENT,
79
+ MIN_SERVER_VER_ADVANCED_ORDER_REJECT,
80
+ MIN_SERVER_VER_MANUAL_ORDER_TIME,
81
+ MIN_SERVER_VER_PEGBEST_PEGMID_OFFSETS,
82
+ MIN_SERVER_VER_FRACTIONAL_POSITIONS,
83
+ MIN_SERVER_VER_SSHORTX_OLD,
84
+ MIN_SERVER_VER_SMART_COMBO_ROUTING_PARAMS,
85
+ MIN_SERVER_VER_FA_PROFILE_DESUPPORT,
86
+ MIN_SERVER_VER_PTA_ORDERS,
87
+ MIN_SERVER_VER_RANDOMIZE_SIZE_AND_PRICE,
88
+ MIN_SERVER_VER_PEGGED_TO_BENCHMARK,
89
+ MIN_SERVER_VER_D_PEG_ORDERS,
90
+ MIN_SERVER_VER_POSITIONS,
91
+ MIN_SERVER_VER_PNL,
92
+ MIN_SERVER_VER_EXECUTION_DATA_CHAIN,
93
+ MIN_SERVER_VER_BOND_ISSUERID,
94
+ MIN_SERVER_VER_CONTRACT_DATA_CHAIN,
95
+ MIN_SERVER_VER_PRIMARYEXCH,
96
+ MIN_SERVER_VER_REQ_MKT_DEPTH_EXCHANGES,
97
+ MIN_SERVER_VER_SMART_DEPTH,
98
+ MIN_SERVER_VER_MKT_DEPTH_PRIM_EXCHANGE,
99
+ MIN_SERVER_VER_REPLACE_FA_END,
100
+ MIN_SERVER_VER_HISTORICAL_SCHEDULE,
101
+ MIN_SERVER_VER_SYNT_REALTIME_BARS,
102
+ MIN_SERVER_VER_REQ_HEAD_TIMESTAMP,
103
+ MIN_SERVER_VER_CANCEL_HEADTIMESTAMP,
104
+ MIN_SERVER_VER_REQ_HISTOGRAM,
105
+ MIN_SERVER_VER_HISTORICAL_TICKS,
106
+ MIN_SERVER_VER_SCANNER_GENERIC_OPTS,
107
+ MIN_SERVER_VER_FUNDAMENTAL_DATA,
108
+ MIN_SERVER_VER_REQ_NEWS_PROVIDERS,
109
+ MIN_SERVER_VER_REQ_NEWS_ARTICLE,
110
+ MIN_SERVER_VER_NEWS_QUERY_ORIGINS,
111
+ MIN_SERVER_VER_REQ_HISTORICAL_NEWS,
112
+ MIN_SERVER_VER_SEC_DEF_OPT_PARAMS_REQ,
113
+ MIN_SERVER_VER_REQ_FAMILY_CODES,
114
+ MIN_SERVER_VER_REQ_MATCHING_SYMBOLS,
115
+ MIN_SERVER_VER_WSHE_CALENDAR,
116
+ MIN_SERVER_VER_WSH_EVENT_DATA_FILTERS,
117
+ MIN_SERVER_VER_WSH_EVENT_DATA_FILTERS_DATE,
118
+ MIN_SERVER_VER_USER_INFO,
119
+ MIN_SERVER_VER_MANUAL_ORDER_TIME_EXERCISE_OPTIONS,
120
+ MIN_SERVER_VER_CUSTOMER_ACCOUNT,
121
+ MIN_SERVER_VER_PROFESSIONAL_CUSTOMER,
122
+ MIN_SERVER_VER_RFQ_FIELDS,
123
+ MIN_SERVER_VER_INCLUDE_OVERNIGHT,
124
+ MIN_SERVER_VER_UNDO_RFQ_FIELDS,
125
+ MIN_SERVER_VER_CME_TAGGING_FIELDS,
126
+ MIN_SERVER_VER_CURRENT_TIME_IN_MILLIS,
127
+ MIN_SERVER_VER_IMBALANCE_ONLY,
128
+ MIN_SERVER_VER_PARAMETRIZED_DAYS_OF_EXECUTIONS,
129
+ MIN_SERVER_VER_PROTOBUF
130
+ )
131
+
132
+ from ibapi.utils import ClientException, log_
133
+ from ibapi.utils import (
134
+ current_fn_name,
135
+ BadMessage,
136
+ isPegBenchOrder,
137
+ isPegMidOrder,
138
+ isPegBestOrder,
139
+ currentTimeMillis,
140
+ )
141
+ from ibapi.errors import INVALID_SYMBOL
142
+ from ibapi.utils import isAsciiPrintable
143
+ from ibapi.common import PROTOBUF_MSG_ID
144
+ from ibapi.client_utils import createExecutionRequestProto, createPlaceOrderRequestProto, createCancelOrderRequestProto, createGlobalCancelRequestProto
145
+
146
+ from ibapi.protobuf.ComboLeg_pb2 import ComboLeg as ComboLegProto
147
+ from ibapi.protobuf.ExecutionFilter_pb2 import ExecutionFilter as ExecutionFilterProto
148
+ from ibapi.protobuf.ExecutionRequest_pb2 import ExecutionRequest as ExecutionRequestProto
149
+ from ibapi.protobuf.PlaceOrderRequest_pb2 import PlaceOrderRequest as PlaceOrderRequestProto
150
+ from ibapi.protobuf.CancelOrderRequest_pb2 import CancelOrderRequest as CancelOrderRequestProto
151
+ from ibapi.protobuf.GlobalCancelRequest_pb2 import GlobalCancelRequest as GlobalCancelRequestProto
152
+
153
+ # TODO: use pylint
154
+
155
+ logger = logging.getLogger(__name__)
156
+
157
+
158
+ class EClient(object):
159
+ (DISCONNECTED, CONNECTING, CONNECTED, REDIRECT) = range(4)
160
+
161
+ # TODO: support redirect !!
162
+
163
+ def __init__(self, wrapper):
164
+ self.msg_queue = queue.Queue()
165
+ self.wrapper = wrapper
166
+ self.decoder = None
167
+ self.nKeybIntHard = 0
168
+ self.conn = None
169
+ self.host = None
170
+ self.port = None
171
+ self.extraAuth = False
172
+ self.clientId = None
173
+ self.serverVersion_ = None
174
+ self.connTime = None
175
+ self.connState = None
176
+ self.optCapab = None
177
+ self.asynchronous = False
178
+ self.reader = None
179
+ self.decode = None
180
+ self.setConnState(EClient.DISCONNECTED)
181
+ self.connectOptions = None
182
+ self.reset()
183
+
184
+ def reset(self):
185
+ self.nKeybIntHard = 0
186
+ self.conn = None
187
+ self.host = None
188
+ self.port = None
189
+ self.extraAuth = False
190
+ self.clientId = None
191
+ self.serverVersion_ = None
192
+ self.connTime = None
193
+ self.connState = None
194
+ self.optCapab = None
195
+ self.asynchronous = False
196
+ self.reader = None
197
+ self.decode = None
198
+ self.setConnState(EClient.DISCONNECTED)
199
+ self.connectOptions = None
200
+
201
+ def setConnState(self, connState):
202
+ _connState = self.connState
203
+ self.connState = connState
204
+ logger.debug(f"{id(self)} connState: {_connState} -> {self.connState}")
205
+
206
+ def sendMsgProtoBuf(self, msgId: int, msg: bytes):
207
+ full_msg = comm.make_msg_proto(msgId, msg)
208
+ logger.info("%s %s %s", "SENDING", current_fn_name(1), full_msg)
209
+ self.conn.sendMsg(full_msg)
210
+
211
+ def sendMsg(self, msgId:int, msg: str):
212
+ useRawIntMsgId = self.serverVersion() >= MIN_SERVER_VER_PROTOBUF
213
+ full_msg = comm.make_msg(msgId, useRawIntMsgId, msg)
214
+ logger.info("%s %s %s", "SENDING", current_fn_name(1), full_msg)
215
+ self.conn.sendMsg(full_msg)
216
+
217
+ def logRequest(self, fnName, fnParams):
218
+ log_(fnName, fnParams, "REQUEST")
219
+
220
+ def validateInvalidSymbols(self, host):
221
+ if host is not None and not isAsciiPrintable(host):
222
+ raise ClientException(
223
+ INVALID_SYMBOL.code(),
224
+ INVALID_SYMBOL.msg(),
225
+ host.encode(sys.stdout.encoding, errors="ignore").decode(sys.stdout.encoding),
226
+ )
227
+
228
+ if self.connectOptions is not None and not isAsciiPrintable(self.connectOptions):
229
+ raise ClientException(
230
+ INVALID_SYMBOL.code(),
231
+ INVALID_SYMBOL.msg(),
232
+ self.connectOptions.encode(sys.stdout.encoding, errors="ignore").decode(sys.stdout.encoding),
233
+ )
234
+
235
+ if self.optCapab is not None and not isAsciiPrintable(self.optCapab):
236
+ raise ClientException(
237
+ INVALID_SYMBOL.code(),
238
+ INVALID_SYMBOL.msg(),
239
+ self.optCapab.encode(sys.stdout.encoding, errors="ignore").decode(sys.stdout.encoding),
240
+ )
241
+
242
+ def useProtoBuf(self, msgId: int) -> bool:
243
+ unifiedVersion = PROTOBUF_MSG_IDS.get(msgId)
244
+ return unifiedVersion is not None and unifiedVersion <= self.serverVersion()
245
+
246
+ def startApi(self):
247
+ """Initiates the message exchange between the client application and
248
+ the TWS/IB Gateway."""
249
+
250
+ self.logRequest(current_fn_name(), vars())
251
+
252
+ if not self.isConnected():
253
+ self.wrapper.error(NO_VALID_ID, currentTimeMillis(), NOT_CONNECTED.code(), NOT_CONNECTED.msg())
254
+ return
255
+
256
+ try:
257
+ VERSION = 2
258
+
259
+ msg = f"{make_field(VERSION)}{make_field(self.clientId)}"
260
+
261
+ if self.serverVersion() >= MIN_SERVER_VER_OPTIONAL_CAPABILITIES:
262
+ msg += make_field(self.optCapab if self.optCapab is not None else "")
263
+
264
+ except ClientException as ex:
265
+ self.wrapper.error(NO_VALID_ID, currentTimeMillis(), ex.code, ex.msg + ex.text)
266
+ return
267
+
268
+ self.sendMsg(OUT.START_API, msg)
269
+
270
+ def connect(self, host, port, clientId):
271
+ """This function must be called before any other. There is no
272
+ feedback for a successful connection, but a subsequent attempt to
273
+ connect will return the message \"Already connected.\"
274
+
275
+ host:str - The host name or IP address of the machine where TWS is
276
+ running. Leave blank to connect to the local host.
277
+ port:int - Must match the port specified in TWS on the
278
+ Configure>API>Socket Port field.
279
+ clientId:int - A number used to identify this client connection. All
280
+ orders placed/modified from this client will be associated with
281
+ this client identifier.
282
+
283
+ Note: Each client MUST connect with a unique clientId."""
284
+
285
+ try:
286
+ self.validateInvalidSymbols(host)
287
+ except ClientException as ex:
288
+ self.wrapper.error(NO_VALID_ID, currentTimeMillis(), ex.code, ex.msg + ex.text)
289
+ return
290
+
291
+ try:
292
+ self.host = host
293
+ self.port = port
294
+ self.clientId = clientId
295
+ logger.debug(
296
+ "Connecting to %s:%d w/ id:%d", self.host, self.port, self.clientId
297
+ )
298
+
299
+ self.conn = Connection(self.host, self.port)
300
+
301
+ self.conn.connect()
302
+ self.setConnState(EClient.CONNECTING)
303
+
304
+ # TODO: support async mode
305
+
306
+ v100prefix = "API\0"
307
+ v100version = "v%d..%d" % (MIN_CLIENT_VER, MAX_CLIENT_VER)
308
+
309
+ if self.connectOptions:
310
+ v100version = v100version + " " + self.connectOptions
311
+
312
+ # v100version = "v%d..%d" % (MIN_CLIENT_VER, 101)
313
+ msg = comm.make_initial_msg(v100version)
314
+ logger.debug("msg %s", msg)
315
+ msg2 = str.encode(v100prefix, "ascii") + msg
316
+ logger.debug("REQUEST %s", msg2)
317
+ self.conn.sendMsg(msg2)
318
+
319
+ self.decoder = decoder.Decoder(self.wrapper, self.serverVersion())
320
+ fields = []
321
+
322
+ # sometimes I get news before the server version, thus the loop
323
+ while len(fields) != 2:
324
+ self.decoder.interpret(fields, 0)
325
+ buf = self.conn.recvMsg()
326
+ if not self.conn.isConnected():
327
+ # recvMsg() triggers disconnect() where there's a socket.error or 0 length buffer
328
+ # if we don't then drop out of the while loop it infinitely loops
329
+ logger.warning("Disconnected; resetting connection")
330
+ self.reset()
331
+ return
332
+ logger.debug("ANSWER %s", buf)
333
+ if len(buf) > 0:
334
+ (size, msg, rest) = comm.read_msg(buf)
335
+ logger.debug("size:%d msg:%s rest:%s|", size, msg, rest)
336
+ fields = comm.read_fields(msg)
337
+ logger.debug("fields %s", fields)
338
+ else:
339
+ fields = []
340
+
341
+ (server_version, conn_time) = fields
342
+ server_version = int(server_version)
343
+ logger.debug("ANSWER Version:%d time:%s", server_version, conn_time)
344
+ self.connTime = conn_time
345
+ self.serverVersion_ = server_version
346
+ self.decoder.serverVersion = self.serverVersion()
347
+
348
+ self.setConnState(EClient.CONNECTED)
349
+
350
+ self.reader = reader.EReader(self.conn, self.msg_queue)
351
+ self.reader.start() # start thread
352
+ logger.info("sent startApi")
353
+ self.startApi()
354
+ self.wrapper.connectAck()
355
+ except socket.error:
356
+ if self.wrapper:
357
+ self.wrapper.error(NO_VALID_ID, currentTimeMillis(), CONNECT_FAIL.code(), CONNECT_FAIL.msg())
358
+ logger.info("could not connect")
359
+ self.disconnect()
360
+
361
+ def disconnect(self):
362
+ """Call this function to terminate the connections with TWS.
363
+ Calling this function does not cancel orders that have already been
364
+ sent."""
365
+
366
+ self.setConnState(EClient.DISCONNECTED)
367
+ if self.conn is not None:
368
+ logger.info("disconnecting")
369
+ self.conn.disconnect()
370
+ self.wrapper.connectionClosed()
371
+ self.reset()
372
+
373
+ def isConnected(self):
374
+ """Call this function to check if there is a connection with TWS"""
375
+
376
+ connConnected = self.conn and self.conn.isConnected()
377
+ logger.debug(
378
+ f"{id(self)} isConn: {self.connState}, connConnected: {str(connConnected)}"
379
+ )
380
+ return EClient.CONNECTED == self.connState and connConnected
381
+
382
+ def keyboardInterrupt(self):
383
+ # intended to be overloaded
384
+ pass
385
+
386
+ def keyboardInterruptHard(self):
387
+ self.nKeybIntHard += 1
388
+ if self.nKeybIntHard > 5:
389
+ raise SystemExit()
390
+
391
+ def setConnectOptions(self, opts):
392
+ self.connectOptions = opts
393
+
394
+ def setOptionalCapabilities(self, optCapab):
395
+ self.optCapab = optCapab
396
+
397
+ def msgLoopTmo(self):
398
+ # intended to be overloaded
399
+ pass
400
+
401
+ def msgLoopRec(self):
402
+ # intended to be overloaded
403
+ pass
404
+
405
+ def run(self):
406
+ """This is the function that has the message loop."""
407
+
408
+ try:
409
+ while self.isConnected() or not self.msg_queue.empty():
410
+ try:
411
+ try:
412
+ text = self.msg_queue.get(block=True, timeout=0.2)
413
+ if len(text) > MAX_MSG_LEN:
414
+ self.wrapper.error(
415
+ NO_VALID_ID,
416
+ currentTimeMillis(),
417
+ BAD_LENGTH.code(),
418
+ f"{BAD_LENGTH.msg()}:{len(text)}:{text}",
419
+ )
420
+ break
421
+ except queue.Empty:
422
+ logger.debug("queue.get: empty")
423
+ self.msgLoopTmo()
424
+ else:
425
+
426
+ if self.serverVersion() >= MIN_SERVER_VER_PROTOBUF:
427
+ sMsgId = text[:4]
428
+ msgId = int.from_bytes(sMsgId, 'big')
429
+ text = text[4:]
430
+ else:
431
+ sMsgId = text[:text.index(b"\0")]
432
+ text = text[text.index(b"\0") + len(b"\0"):]
433
+ msgId = int(sMsgId)
434
+
435
+ if msgId > PROTOBUF_MSG_ID:
436
+ msgId -= PROTOBUF_MSG_ID
437
+ logger.debug("msgId: %d, protobuf: %s", msgId, text)
438
+ self.decoder.processProtoBuf(text, msgId)
439
+ else:
440
+ fields = comm.read_fields(text)
441
+ logger.debug("msgId: %d, fields: %s", msgId, fields)
442
+ self.decoder.interpret(fields, msgId)
443
+
444
+ self.msgLoopRec()
445
+ except (KeyboardInterrupt, SystemExit):
446
+ logger.info("detected KeyboardInterrupt, SystemExit")
447
+ self.keyboardInterrupt()
448
+ self.keyboardInterruptHard()
449
+ except BadMessage:
450
+ logger.info("BadMessage")
451
+
452
+ logger.debug(
453
+ "conn:%d queue.sz:%d", self.isConnected(), self.msg_queue.qsize()
454
+ )
455
+ finally:
456
+ self.disconnect()
457
+
458
+ def reqCurrentTime(self):
459
+ """Asks the current system time on the server side."""
460
+
461
+ self.logRequest(current_fn_name(), vars())
462
+
463
+ if not self.isConnected():
464
+ self.wrapper.error(NO_VALID_ID, currentTimeMillis(), NOT_CONNECTED.code(), NOT_CONNECTED.msg())
465
+ return
466
+
467
+ VERSION = 1
468
+
469
+ msg = f"{make_field(VERSION)}"
470
+
471
+ self.sendMsg(OUT.REQ_CURRENT_TIME, msg)
472
+
473
+ def serverVersion(self):
474
+ """Returns the version of the TWS instance to which the API application is connected."""
475
+
476
+ return self.serverVersion_
477
+
478
+ def setServerLogLevel(self, logLevel: int):
479
+ """The default detail level is ERROR. For more details, see API
480
+ Logging."""
481
+
482
+ self.logRequest(current_fn_name(), vars())
483
+
484
+ if not self.isConnected():
485
+ self.wrapper.error(NO_VALID_ID, currentTimeMillis(), NOT_CONNECTED.code(), NOT_CONNECTED.msg())
486
+ return
487
+
488
+ VERSION = 1
489
+
490
+ msg = f"{make_field(VERSION)}{make_field(logLevel)}"
491
+
492
+ self.sendMsg(OUT.SET_SERVER_LOGLEVEL, msg)
493
+
494
+ def twsConnectionTime(self):
495
+ """Returns the time the API application made a connection to TWS."""
496
+
497
+ return self.connTime
498
+
499
+ ##########################################################################
500
+ # Market Data
501
+ ##########################################################################
502
+
503
+ def reqMktData(
504
+ self,
505
+ reqId: TickerId,
506
+ contract: Contract,
507
+ genericTickList: str,
508
+ snapshot: bool,
509
+ regulatorySnapshot: bool,
510
+ mktDataOptions: TagValueList,
511
+ ):
512
+ """Call this function to request market data. The market data
513
+ will be returned by the tickPrice and tickSize events.
514
+
515
+ reqId: TickerId - The ticker id. Must be a unique value. When the
516
+ market data returns, it will be identified by this tag. This is
517
+ also used when canceling the market data.
518
+ contract:Contract - This structure contains a description of the
519
+ Contractt for which market data is being requested.
520
+ genericTickList:str - A comma delimited list of generic tick types.
521
+ Tick types can be found in the Generic Tick Types page.
522
+ Prefixing w/ 'mdoff' indicates that top mkt data shouldn't tick.
523
+ You can specify the news source by postfixing w/ ':<source>.
524
+ Example: "mdoff,292:FLY+BRF"
525
+ snapshot:bool - Check to return a single snapshot of Market data and
526
+ have the market data subscription cancel. Do not enter any
527
+ genericTicklist values if you use snapshots.
528
+ regulatorySnapshot: bool - With the US Value Snapshot Bundle for stocks,
529
+ regulatory snapshots are available for 0.01 USD each.
530
+ mktDataOptions:TagValueList - For internal use only.
531
+ Use default value XYZ."""
532
+
533
+ self.logRequest(current_fn_name(), vars())
534
+
535
+ if not self.isConnected():
536
+ self.wrapper.error(reqId, currentTimeMillis(), NOT_CONNECTED.code(), NOT_CONNECTED.msg())
537
+ return
538
+
539
+ if self.serverVersion() < MIN_SERVER_VER_DELTA_NEUTRAL:
540
+ if contract.deltaNeutralContract:
541
+ self.wrapper.error(
542
+ reqId,
543
+ currentTimeMillis(),
544
+ UPDATE_TWS.code(),
545
+ UPDATE_TWS.msg() + " It does not support delta-neutral orders.",
546
+ )
547
+ return
548
+
549
+ if self.serverVersion() < MIN_SERVER_VER_REQ_MKT_DATA_CONID:
550
+ if contract.conId > 0:
551
+ self.wrapper.error(
552
+ reqId,
553
+ currentTimeMillis(),
554
+ UPDATE_TWS.code(),
555
+ UPDATE_TWS.msg() + " It does not support conId parameter.",
556
+ )
557
+ return
558
+
559
+ if self.serverVersion() < MIN_SERVER_VER_TRADING_CLASS:
560
+ if contract.tradingClass:
561
+ self.wrapper.error(
562
+ reqId,
563
+ currentTimeMillis(),
564
+ UPDATE_TWS.code(),
565
+ UPDATE_TWS.msg()
566
+ + " It does not support tradingClass parameter in reqMktData.",
567
+ )
568
+ return
569
+
570
+ try:
571
+ VERSION = 11
572
+
573
+ # send req mkt data msg
574
+ flds = []
575
+ flds += [
576
+ make_field(VERSION),
577
+ make_field(reqId),
578
+ ]
579
+
580
+ # send contract fields
581
+ if self.serverVersion() >= MIN_SERVER_VER_REQ_MKT_DATA_CONID:
582
+ flds += [
583
+ make_field(contract.conId),
584
+ ]
585
+
586
+ flds += [
587
+ make_field(contract.symbol),
588
+ make_field(contract.secType),
589
+ make_field(contract.lastTradeDateOrContractMonth),
590
+ make_field_handle_empty(contract.strike),
591
+ make_field(contract.right),
592
+ make_field(contract.multiplier), # srv v15 and above
593
+ make_field(contract.exchange),
594
+ make_field(contract.primaryExchange), # srv v14 and above
595
+ make_field(contract.currency),
596
+ make_field(contract.localSymbol),
597
+ ] # srv v2 and above
598
+
599
+ if self.serverVersion() >= MIN_SERVER_VER_TRADING_CLASS:
600
+ flds += [
601
+ make_field(contract.tradingClass),
602
+ ]
603
+
604
+ # Send combo legs for BAG requests (srv v8 and above)
605
+ if contract.secType == "BAG":
606
+ comboLegsCount = len(contract.comboLegs) if contract.comboLegs else 0
607
+ flds += [
608
+ make_field(comboLegsCount),
609
+ ]
610
+ for comboLeg in contract.comboLegs:
611
+ flds += [
612
+ make_field(comboLeg.conId),
613
+ make_field(comboLeg.ratio),
614
+ make_field(comboLeg.action),
615
+ make_field(comboLeg.exchange),
616
+ ]
617
+
618
+ if self.serverVersion() >= MIN_SERVER_VER_DELTA_NEUTRAL:
619
+ if contract.deltaNeutralContract:
620
+ flds += [
621
+ make_field(True),
622
+ make_field(contract.deltaNeutralContract.conId),
623
+ make_field(contract.deltaNeutralContract.delta),
624
+ make_field(contract.deltaNeutralContract.price),
625
+ ]
626
+ else:
627
+ flds += [
628
+ make_field(False),
629
+ ]
630
+
631
+ flds += [
632
+ make_field(genericTickList), # srv v31 and above
633
+ make_field(snapshot),
634
+ ] # srv v35 and above
635
+
636
+ if self.serverVersion() >= MIN_SERVER_VER_REQ_SMART_COMPONENTS:
637
+ flds += [
638
+ make_field(regulatorySnapshot),
639
+ ]
640
+
641
+ # send mktDataOptions parameter
642
+ if self.serverVersion() >= MIN_SERVER_VER_LINKING:
643
+ # current doc says this part if for "internal use only" -> won't support it
644
+ if mktDataOptions:
645
+ raise NotImplementedError("not supported")
646
+ mktDataOptionsStr = ""
647
+ flds += [
648
+ make_field(mktDataOptionsStr),
649
+ ]
650
+
651
+ msg = "".join(flds)
652
+
653
+ except ClientException as ex:
654
+ self.wrapper.error(reqId, currentTimeMillis(), ex.code, ex.msg + ex.text)
655
+ return
656
+
657
+ self.sendMsg(OUT.REQ_MKT_DATA, msg)
658
+
659
+ def cancelMktData(self, reqId: TickerId):
660
+ """After calling this function, market data for the specified id
661
+ will stop flowing.
662
+
663
+ reqId: TickerId - The ID that was specified in the call to
664
+ reqMktData()."""
665
+
666
+ self.logRequest(current_fn_name(), vars())
667
+
668
+ if not self.isConnected():
669
+ self.wrapper.error(reqId, currentTimeMillis(), NOT_CONNECTED.code(), NOT_CONNECTED.msg())
670
+ return
671
+
672
+ VERSION = 2
673
+
674
+ # send req mkt data msg
675
+ flds = []
676
+ flds += [
677
+ make_field(VERSION),
678
+ make_field(reqId),
679
+ ]
680
+
681
+ msg = "".join(flds)
682
+ self.sendMsg(OUT.CANCEL_MKT_DATA, msg)
683
+
684
+ def reqMarketDataType(self, marketDataType: int):
685
+ """The API can receive frozen market data from Trader
686
+ Workstation. Frozen market data is the last data recorded in our system.
687
+ During normal trading hours, the API receives real-time market data. If
688
+ you use this function, you are telling TWS to automatically switch to
689
+ frozen market data after the close. Then, before the opening of the next
690
+ trading day, market data will automatically switch back to real-time
691
+ market data.
692
+
693
+ marketDataType:int - 1 for real-time streaming market data or 2 for
694
+ frozen market data"""
695
+
696
+ self.logRequest(current_fn_name(), vars())
697
+
698
+ if not self.isConnected():
699
+ self.wrapper.error(NO_VALID_ID, currentTimeMillis(), NOT_CONNECTED.code(), NOT_CONNECTED.msg())
700
+ return
701
+
702
+ if self.serverVersion() < MIN_SERVER_VER_REQ_MARKET_DATA_TYPE:
703
+ self.wrapper.error(
704
+ NO_VALID_ID,
705
+ currentTimeMillis(),
706
+ UPDATE_TWS.code(),
707
+ UPDATE_TWS.msg() + " It does not support market data type requests.",
708
+ )
709
+ return
710
+
711
+ VERSION = 1
712
+
713
+ # send req mkt data msg
714
+ flds = []
715
+ flds += [
716
+ make_field(VERSION),
717
+ make_field(marketDataType),
718
+ ]
719
+
720
+ msg = "".join(flds)
721
+ self.sendMsg(OUT.REQ_MARKET_DATA_TYPE, msg)
722
+
723
+ def reqSmartComponents(self, reqId: int, bboExchange: str):
724
+ self.logRequest(current_fn_name(), vars())
725
+
726
+ if not self.isConnected():
727
+ self.wrapper.error(NO_VALID_ID, currentTimeMillis(), NOT_CONNECTED.code(), NOT_CONNECTED.msg())
728
+ return
729
+
730
+ if self.serverVersion() < MIN_SERVER_VER_REQ_SMART_COMPONENTS:
731
+ self.wrapper.error(
732
+ NO_VALID_ID,
733
+ currentTimeMillis(),
734
+ UPDATE_TWS.code(),
735
+ UPDATE_TWS.msg() + " It does not support smart components request.",
736
+ )
737
+ return
738
+
739
+ try:
740
+ msg = (
741
+ make_field(reqId)
742
+ + make_field(bboExchange)
743
+ )
744
+
745
+ except ClientException as ex:
746
+ self.wrapper.error(reqId, currentTimeMillis(), ex.code, ex.msg + ex.text)
747
+ return
748
+
749
+ self.sendMsg(OUT.REQ_SMART_COMPONENTS, msg)
750
+
751
+ def reqMarketRule(self, marketRuleId: int):
752
+ self.logRequest(current_fn_name(), vars())
753
+
754
+ if not self.isConnected():
755
+ self.wrapper.error(NO_VALID_ID, currentTimeMillis(), NOT_CONNECTED.code(), NOT_CONNECTED.msg())
756
+ return
757
+
758
+ if self.serverVersion() < MIN_SERVER_VER_MARKET_RULES:
759
+ self.wrapper.error(
760
+ NO_VALID_ID,
761
+ currentTimeMillis(),
762
+ UPDATE_TWS.code(),
763
+ UPDATE_TWS.msg() + " It does not support market rule requests.",
764
+ )
765
+ return
766
+
767
+ msg = make_field(marketRuleId)
768
+
769
+ self.sendMsg(OUT.REQ_MARKET_RULE, msg)
770
+
771
+ def reqTickByTickData(
772
+ self,
773
+ reqId: int,
774
+ contract: Contract,
775
+ tickType: str,
776
+ numberOfTicks: int,
777
+ ignoreSize: bool,
778
+ ):
779
+ self.logRequest(current_fn_name(), vars())
780
+
781
+ if not self.isConnected():
782
+ self.wrapper.error(NO_VALID_ID, currentTimeMillis(), NOT_CONNECTED.code(), NOT_CONNECTED.msg())
783
+ return
784
+
785
+ if self.serverVersion() < MIN_SERVER_VER_TICK_BY_TICK:
786
+ self.wrapper.error(
787
+ NO_VALID_ID,
788
+ currentTimeMillis(),
789
+ UPDATE_TWS.code(),
790
+ UPDATE_TWS.msg() + " It does not support tick-by-tick data requests.",
791
+ )
792
+ return
793
+
794
+ if self.serverVersion() < MIN_SERVER_VER_TICK_BY_TICK_IGNORE_SIZE:
795
+ self.wrapper.error(
796
+ NO_VALID_ID,
797
+ currentTimeMillis(),
798
+ UPDATE_TWS.code(),
799
+ UPDATE_TWS.msg()
800
+ + " It does not support ignoreSize and numberOfTicks parameters "
801
+ "in tick-by-tick data requests.",
802
+ )
803
+ return
804
+
805
+ try:
806
+ msg = (
807
+ make_field(reqId)
808
+ + make_field(contract.conId)
809
+ + make_field(contract.symbol)
810
+ + make_field(contract.secType)
811
+ + make_field(contract.lastTradeDateOrContractMonth)
812
+ + make_field_handle_empty(contract.strike)
813
+ + make_field(contract.right)
814
+ + make_field(contract.multiplier)
815
+ + make_field(contract.exchange)
816
+ + make_field(contract.primaryExchange)
817
+ + make_field(contract.currency)
818
+ + make_field(contract.localSymbol)
819
+ + make_field(contract.tradingClass)
820
+ + make_field(tickType)
821
+ )
822
+
823
+ if self.serverVersion() >= MIN_SERVER_VER_TICK_BY_TICK_IGNORE_SIZE:
824
+ msg += make_field(numberOfTicks) + make_field(ignoreSize)
825
+
826
+ except ClientException as ex:
827
+ self.wrapper.error(reqId, currentTimeMillis(), ex.code, ex.msg + ex.text)
828
+ return
829
+
830
+ self.sendMsg(OUT.REQ_TICK_BY_TICK_DATA, msg)
831
+
832
+ def cancelTickByTickData(self, reqId: int):
833
+ self.logRequest(current_fn_name(), vars())
834
+
835
+ if not self.isConnected():
836
+ self.wrapper.error(NO_VALID_ID, currentTimeMillis(), NOT_CONNECTED.code(), NOT_CONNECTED.msg())
837
+ return
838
+
839
+ if self.serverVersion() < MIN_SERVER_VER_TICK_BY_TICK:
840
+ self.wrapper.error(
841
+ NO_VALID_ID,
842
+ currentTimeMillis(),
843
+ UPDATE_TWS.code(),
844
+ UPDATE_TWS.msg() + " It does not support tick-by-tick data requests.",
845
+ )
846
+ return
847
+
848
+ msg = make_field(reqId)
849
+
850
+ self.sendMsg(OUT.CANCEL_TICK_BY_TICK_DATA, msg)
851
+
852
+ ##########################################################################
853
+ # Options
854
+ ##########################################################################
855
+
856
+ def calculateImpliedVolatility(
857
+ self,
858
+ reqId: TickerId,
859
+ contract: Contract,
860
+ optionPrice: float,
861
+ underPrice: float,
862
+ implVolOptions: TagValueList,
863
+ ):
864
+ """Call this function to calculate volatility for a supplied
865
+ option price and underlying price. Result will be delivered
866
+ via EWrapper.tickOptionComputation()
867
+
868
+ reqId:TickerId - The request id.
869
+ contract:Contract - Describes the contract.
870
+ optionPrice:double - The price of the option.
871
+ underPrice:double - Price of the underlying."""
872
+
873
+ self.logRequest(current_fn_name(), vars())
874
+
875
+ if not self.isConnected():
876
+ self.wrapper.error(reqId, currentTimeMillis(), NOT_CONNECTED.code(), NOT_CONNECTED.msg())
877
+ return
878
+
879
+ if self.serverVersion() < MIN_SERVER_VER_REQ_CALC_IMPLIED_VOLAT:
880
+ self.wrapper.error(
881
+ reqId,
882
+ currentTimeMillis(),
883
+ UPDATE_TWS.code(),
884
+ UPDATE_TWS.msg()
885
+ + " It does not support calculateImpliedVolatility req.",
886
+ )
887
+ return
888
+
889
+ if self.serverVersion() < MIN_SERVER_VER_TRADING_CLASS:
890
+ if contract.tradingClass:
891
+ self.wrapper.error(
892
+ reqId,
893
+ currentTimeMillis(),
894
+ UPDATE_TWS.code(),
895
+ UPDATE_TWS.msg()
896
+ + " It does not support tradingClass parameter in calculateImpliedVolatility.",
897
+ )
898
+ return
899
+
900
+ try:
901
+ VERSION = 3
902
+
903
+ # send req mkt data msg
904
+ flds = []
905
+ flds += [
906
+ make_field(VERSION),
907
+ make_field(reqId),
908
+ # send contract fields
909
+ make_field(contract.conId),
910
+ make_field(contract.symbol),
911
+ make_field(contract.secType),
912
+ make_field(contract.lastTradeDateOrContractMonth),
913
+ make_field_handle_empty(contract.strike),
914
+ make_field(contract.right),
915
+ make_field(contract.multiplier),
916
+ make_field(contract.exchange),
917
+ make_field(contract.primaryExchange),
918
+ make_field(contract.currency),
919
+ make_field(contract.localSymbol),
920
+ ]
921
+ if self.serverVersion() >= MIN_SERVER_VER_TRADING_CLASS:
922
+ flds += [
923
+ make_field(contract.tradingClass),
924
+ ]
925
+ flds += [make_field(optionPrice), make_field(underPrice)]
926
+
927
+ if self.serverVersion() >= MIN_SERVER_VER_LINKING:
928
+ implVolOptStr = ""
929
+ tagValuesCount = len(implVolOptions) if implVolOptions else 0
930
+ if implVolOptions:
931
+ for implVolOpt in implVolOptions:
932
+ implVolOptStr += str(implVolOpt)
933
+ flds += [make_field(tagValuesCount), make_field(implVolOptStr)]
934
+
935
+ msg = "".join(flds)
936
+
937
+ except ClientException as ex:
938
+ self.wrapper.error(reqId, currentTimeMillis(), ex.code, ex.msg + ex.text)
939
+ return
940
+
941
+ self.sendMsg(OUT.REQ_CALC_IMPLIED_VOLAT, msg)
942
+
943
+ def cancelCalculateImpliedVolatility(self, reqId: TickerId):
944
+ """Call this function to cancel a request to calculate
945
+ volatility for a supplied option price and underlying price.
946
+
947
+ reqId:TickerId - The request ID."""
948
+
949
+ self.logRequest(current_fn_name(), vars())
950
+
951
+ if not self.isConnected():
952
+ self.wrapper.error(reqId, currentTimeMillis(), NOT_CONNECTED.code(), NOT_CONNECTED.msg())
953
+ return
954
+
955
+ if self.serverVersion() < MIN_SERVER_VER_REQ_CALC_IMPLIED_VOLAT:
956
+ self.wrapper.error(
957
+ reqId,
958
+ currentTimeMillis(),
959
+ UPDATE_TWS.code(),
960
+ UPDATE_TWS.msg()
961
+ + " It does not support calculateImpliedVolatility req.",
962
+ )
963
+ return
964
+
965
+ VERSION = 1
966
+
967
+ msg = (
968
+ make_field(VERSION)
969
+ + make_field(reqId)
970
+ )
971
+
972
+ self.sendMsg(OUT.CANCEL_CALC_IMPLIED_VOLAT, msg)
973
+
974
+ def calculateOptionPrice(
975
+ self,
976
+ reqId: TickerId,
977
+ contract: Contract,
978
+ volatility: float,
979
+ underPrice: float,
980
+ optPrcOptions: TagValueList,
981
+ ):
982
+ """Call this function to calculate option price and greek values
983
+ for a supplied volatility and underlying price.
984
+
985
+ reqId:TickerId - The ticker ID.
986
+ contract:Contract - Describes the contract.
987
+ volatility:double - The volatility.
988
+ underPrice:double - Price of the underlying."""
989
+
990
+ self.logRequest(current_fn_name(), vars())
991
+
992
+ if not self.isConnected():
993
+ self.wrapper.error(reqId, currentTimeMillis(), NOT_CONNECTED.code(), NOT_CONNECTED.msg())
994
+ return
995
+
996
+ if self.serverVersion() < MIN_SERVER_VER_REQ_CALC_IMPLIED_VOLAT:
997
+ self.wrapper.error(
998
+ reqId,
999
+ currentTimeMillis(),
1000
+ UPDATE_TWS.code(),
1001
+ UPDATE_TWS.msg()
1002
+ + " It does not support calculateImpliedVolatility req.",
1003
+ )
1004
+ return
1005
+
1006
+ if self.serverVersion() < MIN_SERVER_VER_TRADING_CLASS:
1007
+ if contract.tradingClass:
1008
+ self.wrapper.error(
1009
+ reqId,
1010
+ currentTimeMillis(),
1011
+ UPDATE_TWS.code(),
1012
+ UPDATE_TWS.msg()
1013
+ + " It does not support tradingClass parameter in calculateImpliedVolatility.",
1014
+ )
1015
+ return
1016
+
1017
+ try:
1018
+ VERSION = 3
1019
+
1020
+ # send req mkt data msg
1021
+ flds = []
1022
+ flds += [
1023
+ make_field(VERSION),
1024
+ make_field(reqId),
1025
+ # send contract fields
1026
+ make_field(contract.conId),
1027
+ make_field(contract.symbol),
1028
+ make_field(contract.secType),
1029
+ make_field(contract.lastTradeDateOrContractMonth),
1030
+ make_field_handle_empty(contract.strike),
1031
+ make_field(contract.right),
1032
+ make_field(contract.multiplier),
1033
+ make_field(contract.exchange),
1034
+ make_field(contract.primaryExchange),
1035
+ make_field(contract.currency),
1036
+ make_field(contract.localSymbol),
1037
+ ]
1038
+ if self.serverVersion() >= MIN_SERVER_VER_TRADING_CLASS:
1039
+ flds += [
1040
+ make_field(contract.tradingClass),
1041
+ ]
1042
+ flds += [make_field(volatility), make_field(underPrice)]
1043
+
1044
+ if self.serverVersion() >= MIN_SERVER_VER_LINKING:
1045
+ optPrcOptStr = ""
1046
+ tagValuesCount = len(optPrcOptions) if optPrcOptions else 0
1047
+ if optPrcOptions:
1048
+ for implVolOpt in optPrcOptions:
1049
+ optPrcOptStr += str(implVolOpt)
1050
+ flds += [make_field(tagValuesCount), make_field(optPrcOptStr)]
1051
+
1052
+ msg = "".join(flds)
1053
+
1054
+ except ClientException as ex:
1055
+ self.wrapper.error(reqId, currentTimeMillis(), ex.code, ex.msg + ex.text)
1056
+ return
1057
+
1058
+ self.sendMsg(OUT.REQ_CALC_OPTION_PRICE, msg)
1059
+
1060
+ def cancelCalculateOptionPrice(self, reqId: TickerId):
1061
+ """Call this function to cancel a request to calculate the option
1062
+ price and greek values for a supplied volatility and underlying price.
1063
+
1064
+ reqId:TickerId - The request ID."""
1065
+
1066
+ self.logRequest(current_fn_name(), vars())
1067
+
1068
+ if not self.isConnected():
1069
+ self.wrapper.error(reqId, currentTimeMillis(), NOT_CONNECTED.code(), NOT_CONNECTED.msg())
1070
+ return
1071
+
1072
+ if self.serverVersion() < MIN_SERVER_VER_REQ_CALC_IMPLIED_VOLAT:
1073
+ self.wrapper.error(
1074
+ reqId,
1075
+ currentTimeMillis(),
1076
+ UPDATE_TWS.code(),
1077
+ UPDATE_TWS.msg()
1078
+ + " It does not support calculateImpliedVolatility req.",
1079
+ )
1080
+ return
1081
+
1082
+ VERSION = 1
1083
+
1084
+ msg = (
1085
+ make_field(VERSION)
1086
+ + make_field(reqId)
1087
+ )
1088
+
1089
+ self.sendMsg(OUT.CANCEL_CALC_OPTION_PRICE, msg)
1090
+
1091
+ def exerciseOptions(
1092
+ self,
1093
+ reqId: TickerId,
1094
+ contract: Contract,
1095
+ exerciseAction: int,
1096
+ exerciseQuantity: int,
1097
+ account: str,
1098
+ override: int,
1099
+ manualOrderTime: str,
1100
+ customerAccount: str,
1101
+ professionalCustomer: bool
1102
+ ):
1103
+ """reqId:TickerId - The ticker id. multipleust be a unique value.
1104
+ contract:Contract - This structure contains a description of the
1105
+ contract to be exercised
1106
+ exerciseAction:int - Specifies whether you want the option to lapse
1107
+ or be exercised.
1108
+ Values are 1 = exercise, 2 = lapse.
1109
+ exerciseQuantity:int - The quantity you want to exercise.
1110
+ account:str - destination account
1111
+ override:int - Specifies whether your setting will override the system's
1112
+ natural action. For example, if your action is "exercise" and the
1113
+ option is not in-the-money, by natural action the option would not
1114
+ exercise. If you have override set to "yes" the natural action would
1115
+ be overridden and the out-of-the money option would be exercised.
1116
+ Values are: 0 = no, 1 = yes.
1117
+ manualOrderTime:str - manual order time
1118
+ customerAccount:str - customer account
1119
+ professionalCustomer:bool - professional customer"""
1120
+
1121
+ self.logRequest(current_fn_name(), vars())
1122
+
1123
+ if not self.isConnected():
1124
+ self.wrapper.error(reqId, currentTimeMillis(), NOT_CONNECTED.code(), NOT_CONNECTED.msg())
1125
+ return
1126
+
1127
+ if self.serverVersion() < MIN_SERVER_VER_TRADING_CLASS:
1128
+ if contract.tradingClass or contract.conId > 0:
1129
+ self.wrapper.error(
1130
+ reqId,
1131
+ currentTimeMillis(),
1132
+ UPDATE_TWS.code(),
1133
+ UPDATE_TWS.msg()
1134
+ + " It does not support conId and tradingClass parameters in exerciseOptions.",
1135
+ )
1136
+ return
1137
+
1138
+ if self.serverVersion() < MIN_SERVER_VER_MANUAL_ORDER_TIME_EXERCISE_OPTIONS and manualOrderTime:
1139
+ self.wrapper.error(
1140
+ reqId,
1141
+ currentTimeMillis(),
1142
+ UPDATE_TWS.code(),
1143
+ UPDATE_TWS.msg()
1144
+ + " It does not support manual order time parameter in exerciseOptions.",
1145
+ )
1146
+ return
1147
+
1148
+ if (
1149
+ self.serverVersion() < MIN_SERVER_VER_CUSTOMER_ACCOUNT
1150
+ and customerAccount
1151
+ ):
1152
+ self.wrapper.error(
1153
+ reqId,
1154
+ currentTimeMillis(),
1155
+ UPDATE_TWS.code(),
1156
+ UPDATE_TWS.msg() + " It does not support customer account parameter in exerciseOptions.",
1157
+ )
1158
+ return
1159
+
1160
+ if (
1161
+ self.serverVersion() < MIN_SERVER_VER_PROFESSIONAL_CUSTOMER
1162
+ and professionalCustomer
1163
+ ):
1164
+ self.wrapper.error(
1165
+ reqId,
1166
+ currentTimeMillis(),
1167
+ UPDATE_TWS.code(),
1168
+ UPDATE_TWS.msg() + " It does not support professional customer parameter in exerciseOptions.",
1169
+ )
1170
+ return
1171
+
1172
+ try:
1173
+ VERSION = 2
1174
+
1175
+ # send req mkt data msg
1176
+ fields = []
1177
+ fields += [
1178
+ make_field(VERSION),
1179
+ make_field(reqId),
1180
+ ]
1181
+ # send contract fields
1182
+ if self.serverVersion() >= MIN_SERVER_VER_TRADING_CLASS:
1183
+ fields += [
1184
+ make_field(contract.conId),
1185
+ ]
1186
+ fields += [
1187
+ make_field(contract.symbol),
1188
+ make_field(contract.secType),
1189
+ make_field(contract.lastTradeDateOrContractMonth),
1190
+ make_field_handle_empty(contract.strike),
1191
+ make_field(contract.right),
1192
+ make_field(contract.multiplier),
1193
+ make_field(contract.exchange),
1194
+ make_field(contract.currency),
1195
+ make_field(contract.localSymbol),
1196
+ ]
1197
+ if self.serverVersion() >= MIN_SERVER_VER_TRADING_CLASS:
1198
+ fields += [
1199
+ make_field(contract.tradingClass),
1200
+ ]
1201
+ fields += [
1202
+ make_field(exerciseAction),
1203
+ make_field(exerciseQuantity),
1204
+ make_field(account),
1205
+ make_field(override),
1206
+ ]
1207
+ if self.serverVersion() >= MIN_SERVER_VER_MANUAL_ORDER_TIME_EXERCISE_OPTIONS:
1208
+ fields += [
1209
+ make_field(manualOrderTime),
1210
+ ]
1211
+ if self.serverVersion() >= MIN_SERVER_VER_CUSTOMER_ACCOUNT:
1212
+ fields += [
1213
+ make_field(customerAccount),
1214
+ ]
1215
+ if self.serverVersion() >= MIN_SERVER_VER_PROFESSIONAL_CUSTOMER:
1216
+ fields += [
1217
+ make_field(professionalCustomer),
1218
+ ]
1219
+
1220
+ msg = "".join(fields)
1221
+
1222
+ except ClientException as ex:
1223
+ self.wrapper.error(reqId, currentTimeMillis(), ex.code, ex.msg + ex.text)
1224
+ return
1225
+
1226
+ self.sendMsg(OUT.EXERCISE_OPTIONS, msg)
1227
+
1228
+ #########################################################################
1229
+ # Orders
1230
+ ########################################################################
1231
+
1232
+ def placeOrder(self, orderId: OrderId, contract: Contract, order: Order):
1233
+ """Call this function to place an order. The order status will
1234
+ be returned by the orderStatus event.
1235
+
1236
+ orderId:OrderId - The order id. You must specify a unique value. When the
1237
+ order START_APItus returns, it will be identified by this tag.
1238
+ This tag is also used when canceling the order.
1239
+ contract:Contract - This structure contains a description of the
1240
+ contract which is being traded.
1241
+ order:Order - This structure contains the details of tradedhe order.
1242
+ Note: Each client MUST connect with a unique clientId."""
1243
+
1244
+ if (self.useProtoBuf(OUT.PLACE_ORDER)):
1245
+ placeOrderRequestProto = createPlaceOrderRequestProto(orderId, contract, order)
1246
+ self.placeOrderProtoBuf(placeOrderRequestProto)
1247
+ return
1248
+
1249
+ self.logRequest(current_fn_name(), vars())
1250
+
1251
+ if not self.isConnected():
1252
+ self.wrapper.error(orderId, currentTimeMillis(), NOT_CONNECTED.code(), NOT_CONNECTED.msg())
1253
+ return
1254
+
1255
+ if self.serverVersion() < MIN_SERVER_VER_DELTA_NEUTRAL:
1256
+ if contract.deltaNeutralContract:
1257
+ self.wrapper.error(
1258
+ orderId,
1259
+ currentTimeMillis(),
1260
+ UPDATE_TWS.code(),
1261
+ UPDATE_TWS.msg() + " It does not support delta-neutral orders.",
1262
+ )
1263
+ return
1264
+
1265
+ if self.serverVersion() < MIN_SERVER_VER_SCALE_ORDERS2:
1266
+ if order.scaleSubsLevelSize != UNSET_INTEGER:
1267
+ self.wrapper.error(
1268
+ orderId,
1269
+ currentTimeMillis(),
1270
+ UPDATE_TWS.code(),
1271
+ UPDATE_TWS.msg()
1272
+ + " It does not support Subsequent Level Size for Scale orders.",
1273
+ )
1274
+ return
1275
+
1276
+ if self.serverVersion() < MIN_SERVER_VER_ALGO_ORDERS:
1277
+ if order.algoStrategy:
1278
+ self.wrapper.error(
1279
+ orderId,
1280
+ currentTimeMillis(),
1281
+ UPDATE_TWS.code(),
1282
+ UPDATE_TWS.msg() + " It does not support algo orders.",
1283
+ )
1284
+ return
1285
+
1286
+ if self.serverVersion() < MIN_SERVER_VER_NOT_HELD:
1287
+ if order.notHeld:
1288
+ self.wrapper.error(
1289
+ orderId,
1290
+ currentTimeMillis(),
1291
+ UPDATE_TWS.code(),
1292
+ UPDATE_TWS.msg() + " It does not support notHeld parameter.",
1293
+ )
1294
+ return
1295
+
1296
+ if self.serverVersion() < MIN_SERVER_VER_SEC_ID_TYPE:
1297
+ if contract.secIdType or contract.secId:
1298
+ self.wrapper.error(
1299
+ orderId,
1300
+ currentTimeMillis(),
1301
+ UPDATE_TWS.code(),
1302
+ UPDATE_TWS.msg()
1303
+ + " It does not support secIdType and secId parameters.",
1304
+ )
1305
+ return
1306
+
1307
+ if self.serverVersion() < MIN_SERVER_VER_PLACE_ORDER_CONID:
1308
+ if contract.conId and contract.conId > 0:
1309
+ self.wrapper.error(
1310
+ orderId,
1311
+ currentTimeMillis(),
1312
+ UPDATE_TWS.code(),
1313
+ UPDATE_TWS.msg() + " It does not support conId parameter.",
1314
+ )
1315
+ return
1316
+
1317
+ if self.serverVersion() < MIN_SERVER_VER_SSHORTX:
1318
+ if order.exemptCode != -1:
1319
+ self.wrapper.error(
1320
+ orderId,
1321
+ currentTimeMillis(),
1322
+ UPDATE_TWS.code(),
1323
+ UPDATE_TWS.msg() + " It does not support exemptCode parameter.",
1324
+ )
1325
+ return
1326
+
1327
+ if self.serverVersion() < MIN_SERVER_VER_SSHORTX:
1328
+ if contract.comboLegs:
1329
+ for comboLeg in contract.comboLegs:
1330
+ if comboLeg.exemptCode != -1:
1331
+ self.wrapper.error(
1332
+ orderId,
1333
+ currentTimeMillis(),
1334
+ UPDATE_TWS.code(),
1335
+ UPDATE_TWS.msg()
1336
+ + " It does not support exemptCode parameter.",
1337
+ )
1338
+ return
1339
+
1340
+ if self.serverVersion() < MIN_SERVER_VER_HEDGE_ORDERS:
1341
+ if order.hedgeType:
1342
+ self.wrapper.error(
1343
+ orderId,
1344
+ currentTimeMillis(),
1345
+ UPDATE_TWS.code(),
1346
+ UPDATE_TWS.msg() + " It does not support hedge orders.",
1347
+ )
1348
+ return
1349
+
1350
+ if self.serverVersion() < MIN_SERVER_VER_OPT_OUT_SMART_ROUTING:
1351
+ if order.optOutSmartRouting:
1352
+ self.wrapper.error(
1353
+ orderId,
1354
+ currentTimeMillis(),
1355
+ UPDATE_TWS.code(),
1356
+ UPDATE_TWS.msg()
1357
+ + " It does not support optOutSmartRouting parameter.",
1358
+ )
1359
+ return
1360
+
1361
+ if self.serverVersion() < MIN_SERVER_VER_DELTA_NEUTRAL_CONID:
1362
+ if (
1363
+ order.deltaNeutralConId > 0
1364
+ or order.deltaNeutralSettlingFirm
1365
+ or order.deltaNeutralClearingAccount
1366
+ or order.deltaNeutralClearingIntent
1367
+ ):
1368
+ self.wrapper.error(
1369
+ orderId,
1370
+ currentTimeMillis(),
1371
+ UPDATE_TWS.code(),
1372
+ UPDATE_TWS.msg()
1373
+ + " It does not support deltaNeutral parameters: "
1374
+ + "ConId, SettlingFirm, ClearingAccount, ClearingIntent.",
1375
+ )
1376
+ return
1377
+
1378
+ if self.serverVersion() < MIN_SERVER_VER_DELTA_NEUTRAL_OPEN_CLOSE:
1379
+ if (
1380
+ order.deltaNeutralOpenClose
1381
+ or order.deltaNeutralShortSale
1382
+ or order.deltaNeutralShortSaleSlot > 0
1383
+ or order.deltaNeutralDesignatedLocation
1384
+ ):
1385
+ self.wrapper.error(
1386
+ orderId,
1387
+ currentTimeMillis(),
1388
+ UPDATE_TWS.code(),
1389
+ UPDATE_TWS.msg() + " It does not support deltaNeutral parameters: "
1390
+ "OpenClose, ShortSale, ShortSaleSlot, DesignatedLocation.",
1391
+ )
1392
+ return
1393
+
1394
+ if self.serverVersion() < MIN_SERVER_VER_SCALE_ORDERS3:
1395
+ if (
1396
+ order.scalePriceIncrement > 0
1397
+ and order.scalePriceIncrement != UNSET_DOUBLE
1398
+ ):
1399
+ if (
1400
+ order.scalePriceAdjustValue != UNSET_DOUBLE
1401
+ or order.scalePriceAdjustInterval != UNSET_INTEGER
1402
+ or order.scaleProfitOffset != UNSET_DOUBLE
1403
+ or order.scaleAutoReset
1404
+ or order.scaleInitPosition != UNSET_INTEGER
1405
+ or order.scaleInitFillQty != UNSET_INTEGER
1406
+ or order.scaleRandomPercent
1407
+ ):
1408
+ self.wrapper.error(
1409
+ orderId,
1410
+ currentTimeMillis(),
1411
+ UPDATE_TWS.code(),
1412
+ UPDATE_TWS.msg()
1413
+ + " It does not support Scale order parameters: PriceAdjustValue, PriceAdjustInterval, "
1414
+ + "ProfitOffset, AutoReset, InitPosition, InitFillQty and RandomPercent",
1415
+ )
1416
+ return
1417
+
1418
+ if (
1419
+ self.serverVersion() < MIN_SERVER_VER_ORDER_COMBO_LEGS_PRICE
1420
+ and contract.secType == "BAG"
1421
+ ):
1422
+ if order.orderComboLegs:
1423
+ for orderComboLeg in order.orderComboLegs:
1424
+ if orderComboLeg.price != UNSET_DOUBLE:
1425
+ self.wrapper.error(
1426
+ orderId,
1427
+ currentTimeMillis(),
1428
+ UPDATE_TWS.code(),
1429
+ UPDATE_TWS.msg()
1430
+ + " It does not support per-leg prices for order combo legs.",
1431
+ )
1432
+ return
1433
+
1434
+ if self.serverVersion() < MIN_SERVER_VER_TRAILING_PERCENT:
1435
+ if order.trailingPercent != UNSET_DOUBLE:
1436
+ self.wrapper.error(
1437
+ orderId,
1438
+ currentTimeMillis(),
1439
+ UPDATE_TWS.code(),
1440
+ UPDATE_TWS.msg()
1441
+ + " It does not support trailing percent parameter",
1442
+ )
1443
+ return
1444
+
1445
+ if self.serverVersion() < MIN_SERVER_VER_TRADING_CLASS:
1446
+ if contract.tradingClass:
1447
+ self.wrapper.error(
1448
+ orderId,
1449
+ currentTimeMillis(),
1450
+ UPDATE_TWS.code(),
1451
+ UPDATE_TWS.msg()
1452
+ + " It does not support tradingClass parameter in placeOrder.",
1453
+ )
1454
+ return
1455
+
1456
+ if self.serverVersion() < MIN_SERVER_VER_SCALE_TABLE:
1457
+ if order.scaleTable or order.activeStartTime or order.activeStopTime:
1458
+ self.wrapper.error(
1459
+ orderId,
1460
+ currentTimeMillis(),
1461
+ UPDATE_TWS.code(),
1462
+ UPDATE_TWS.msg()
1463
+ + " It does not support scaleTable, activeStartTime and activeStopTime parameters",
1464
+ )
1465
+ return
1466
+
1467
+ if self.serverVersion() < MIN_SERVER_VER_ALGO_ID:
1468
+ if order.algoId:
1469
+ self.wrapper.error(
1470
+ orderId,
1471
+ currentTimeMillis(),
1472
+ UPDATE_TWS.code(),
1473
+ UPDATE_TWS.msg() + " It does not support algoId parameter",
1474
+ )
1475
+ return
1476
+
1477
+ if self.serverVersion() < MIN_SERVER_VER_ORDER_SOLICITED:
1478
+ if order.solicited:
1479
+ self.wrapper.error(
1480
+ orderId,
1481
+ currentTimeMillis(),
1482
+ UPDATE_TWS.code(),
1483
+ UPDATE_TWS.msg()
1484
+ + " It does not support order solicited parameter.",
1485
+ )
1486
+ return
1487
+
1488
+ if self.serverVersion() < MIN_SERVER_VER_MODELS_SUPPORT:
1489
+ if order.modelCode:
1490
+ self.wrapper.error(
1491
+ orderId,
1492
+ currentTimeMillis(),
1493
+ UPDATE_TWS.code(),
1494
+ UPDATE_TWS.msg() + " It does not support model code parameter.",
1495
+ )
1496
+ return
1497
+
1498
+ if self.serverVersion() < MIN_SERVER_VER_EXT_OPERATOR:
1499
+ if order.extOperator:
1500
+ self.wrapper.error(
1501
+ orderId,
1502
+ currentTimeMillis(),
1503
+ UPDATE_TWS.code(),
1504
+ UPDATE_TWS.msg() + " It does not support ext operator parameter",
1505
+ )
1506
+ return
1507
+
1508
+ if self.serverVersion() < MIN_SERVER_VER_SOFT_DOLLAR_TIER:
1509
+ if order.softDollarTier.name or order.softDollarTier.val:
1510
+ self.wrapper.error(
1511
+ orderId,
1512
+ currentTimeMillis(),
1513
+ UPDATE_TWS.code(),
1514
+ UPDATE_TWS.msg() + " It does not support soft dollar tier",
1515
+ )
1516
+ return
1517
+
1518
+ if self.serverVersion() < MIN_SERVER_VER_CASH_QTY:
1519
+ if order.cashQty:
1520
+ self.wrapper.error(
1521
+ orderId,
1522
+ currentTimeMillis(),
1523
+ UPDATE_TWS.code(),
1524
+ UPDATE_TWS.msg() + " It does not support cash quantity parameter",
1525
+ )
1526
+ return
1527
+
1528
+ if self.serverVersion() < MIN_SERVER_VER_DECISION_MAKER and (
1529
+ order.mifid2DecisionMaker != "" or order.mifid2DecisionAlgo != ""
1530
+ ):
1531
+ self.wrapper.error(
1532
+ orderId,
1533
+ currentTimeMillis(),
1534
+ UPDATE_TWS.code(),
1535
+ UPDATE_TWS.msg()
1536
+ + " It does not support MIFID II decision maker parameters",
1537
+ )
1538
+ return
1539
+
1540
+ if self.serverVersion() < MIN_SERVER_VER_MIFID_EXECUTION and (
1541
+ order.mifid2ExecutionTrader != "" or order.mifid2ExecutionAlgo != ""
1542
+ ):
1543
+ self.wrapper.error(
1544
+ orderId,
1545
+ currentTimeMillis(),
1546
+ UPDATE_TWS.code(),
1547
+ UPDATE_TWS.msg() + " It does not support MIFID II execution parameters",
1548
+ )
1549
+ return
1550
+
1551
+ if (
1552
+ self.serverVersion() < MIN_SERVER_VER_AUTO_PRICE_FOR_HEDGE
1553
+ and order.dontUseAutoPriceForHedge
1554
+ ):
1555
+ self.wrapper.error(
1556
+ orderId,
1557
+ currentTimeMillis(),
1558
+ UPDATE_TWS.code(),
1559
+ UPDATE_TWS.msg()
1560
+ + " It does not support dontUseAutoPriceForHedge parameter",
1561
+ )
1562
+ return
1563
+
1564
+ if (
1565
+ self.serverVersion() < MIN_SERVER_VER_ORDER_CONTAINER
1566
+ and order.isOmsContainer
1567
+ ):
1568
+ self.wrapper.error(
1569
+ orderId,
1570
+ currentTimeMillis(),
1571
+ UPDATE_TWS.code(),
1572
+ UPDATE_TWS.msg() + " It does not support oms container parameter",
1573
+ )
1574
+ return
1575
+
1576
+ if (
1577
+ self.serverVersion() < MIN_SERVER_VER_PRICE_MGMT_ALGO
1578
+ and order.usePriceMgmtAlgo
1579
+ ):
1580
+ self.wrapper.error(
1581
+ orderId,
1582
+ currentTimeMillis(),
1583
+ UPDATE_TWS.code(),
1584
+ UPDATE_TWS.msg()
1585
+ + " It does not support Use price management algo requests",
1586
+ )
1587
+ return
1588
+
1589
+ if (
1590
+ self.serverVersion() < MIN_SERVER_VER_DURATION
1591
+ and order.duration != UNSET_INTEGER
1592
+ ):
1593
+ self.wrapper.error(
1594
+ orderId,
1595
+ currentTimeMillis(),
1596
+ UPDATE_TWS.code(),
1597
+ UPDATE_TWS.msg() + " It does not support duration attribute",
1598
+ )
1599
+ return
1600
+
1601
+ if (
1602
+ self.serverVersion() < MIN_SERVER_VER_POST_TO_ATS
1603
+ and order.postToAts != UNSET_INTEGER
1604
+ ):
1605
+ self.wrapper.error(
1606
+ orderId,
1607
+ currentTimeMillis(),
1608
+ UPDATE_TWS.code(),
1609
+ UPDATE_TWS.msg() + " It does not support postToAts attribute",
1610
+ )
1611
+ return
1612
+
1613
+ if (
1614
+ self.serverVersion() < MIN_SERVER_VER_AUTO_CANCEL_PARENT
1615
+ and order.autoCancelParent
1616
+ ):
1617
+ self.wrapper.error(
1618
+ orderId,
1619
+ currentTimeMillis(),
1620
+ UPDATE_TWS.code(),
1621
+ UPDATE_TWS.msg() + " It does not support autoCancelParent attribute",
1622
+ )
1623
+ return
1624
+
1625
+ if (
1626
+ self.serverVersion() < MIN_SERVER_VER_ADVANCED_ORDER_REJECT
1627
+ and order.advancedErrorOverride
1628
+ ):
1629
+ self.wrapper.error(
1630
+ orderId,
1631
+ currentTimeMillis(),
1632
+ UPDATE_TWS.code(),
1633
+ UPDATE_TWS.msg()
1634
+ + " It does not support advanced error override attribute",
1635
+ )
1636
+ return
1637
+
1638
+ if (
1639
+ self.serverVersion() < MIN_SERVER_VER_MANUAL_ORDER_TIME
1640
+ and order.manualOrderTime
1641
+ ):
1642
+ self.wrapper.error(
1643
+ orderId,
1644
+ currentTimeMillis(),
1645
+ UPDATE_TWS.code(),
1646
+ UPDATE_TWS.msg() + " It does not support manual order time attribute",
1647
+ )
1648
+ return
1649
+
1650
+ if self.serverVersion() < MIN_SERVER_VER_PEGBEST_PEGMID_OFFSETS:
1651
+ if (
1652
+ order.minTradeQty != UNSET_INTEGER
1653
+ or order.minCompeteSize != UNSET_INTEGER
1654
+ or order.competeAgainstBestOffset != UNSET_DOUBLE
1655
+ or order.midOffsetAtWhole != UNSET_DOUBLE
1656
+ or order.midOffsetAtHalf != UNSET_DOUBLE
1657
+ ):
1658
+ self.wrapper.error(
1659
+ orderId,
1660
+ currentTimeMillis(),
1661
+ UPDATE_TWS.code(),
1662
+ UPDATE_TWS.msg()
1663
+ + " It does not support PEG BEST / PEG MID order parameters: minTradeQty, minCompeteSize, "
1664
+ + "competeAgainstBestOffset, midOffsetAtWhole and midOffsetAtHalf",
1665
+ )
1666
+ return
1667
+
1668
+ if (
1669
+ self.serverVersion() < MIN_SERVER_VER_CUSTOMER_ACCOUNT
1670
+ and order.customerAccount
1671
+ ):
1672
+ self.wrapper.error(
1673
+ orderId,
1674
+ currentTimeMillis(),
1675
+ UPDATE_TWS.code(),
1676
+ UPDATE_TWS.msg() + " It does not support customer account parameter",
1677
+ )
1678
+ return
1679
+
1680
+ if (
1681
+ self.serverVersion() < MIN_SERVER_VER_PROFESSIONAL_CUSTOMER
1682
+ and order.professionalCustomer
1683
+ ):
1684
+ self.wrapper.error(
1685
+ orderId,
1686
+ currentTimeMillis(),
1687
+ UPDATE_TWS.code(),
1688
+ UPDATE_TWS.msg() + " It does not support professional customer parameter",
1689
+ )
1690
+ return
1691
+
1692
+ if (
1693
+ self.serverVersion() < MIN_SERVER_VER_INCLUDE_OVERNIGHT
1694
+ and order.includeOvernight
1695
+ ):
1696
+ self.wrapper.error(
1697
+ orderId,
1698
+ currentTimeMillis(),
1699
+ UPDATE_TWS.code(),
1700
+ UPDATE_TWS.msg() + " It does not support include overnight parameter",
1701
+ )
1702
+ return
1703
+
1704
+ if (
1705
+ self.serverVersion() < MIN_SERVER_VER_CME_TAGGING_FIELDS
1706
+ and order.manualOrderIndicator != UNSET_INTEGER
1707
+ ):
1708
+ self.wrapper.error(
1709
+ NO_VALID_ID,
1710
+ currentTimeMillis(),
1711
+ UPDATE_TWS.code(),
1712
+ UPDATE_TWS.msg() + " It does not support manual order indicator parameters",
1713
+ )
1714
+ return
1715
+
1716
+ if (
1717
+ self.serverVersion() < MIN_SERVER_VER_IMBALANCE_ONLY
1718
+ and order.imbalanceOnly
1719
+ ):
1720
+ self.wrapper.error(
1721
+ orderId,
1722
+ currentTimeMillis(),
1723
+ UPDATE_TWS.code(),
1724
+ UPDATE_TWS.msg() + " It does not support imbalance only parameter",
1725
+ )
1726
+ return
1727
+
1728
+ try:
1729
+ VERSION = 27 if (self.serverVersion() < MIN_SERVER_VER_NOT_HELD) else 45
1730
+
1731
+ # send place order msg
1732
+ flds = []
1733
+
1734
+ if self.serverVersion() < MIN_SERVER_VER_ORDER_CONTAINER:
1735
+ flds += [make_field(VERSION)]
1736
+
1737
+ flds += [make_field(orderId)]
1738
+
1739
+ # send contract fields
1740
+ if self.serverVersion() >= MIN_SERVER_VER_PLACE_ORDER_CONID:
1741
+ flds.append(make_field(contract.conId))
1742
+ flds += [
1743
+ make_field(contract.symbol),
1744
+ make_field(contract.secType),
1745
+ make_field(contract.lastTradeDateOrContractMonth),
1746
+ make_field_handle_empty(contract.strike),
1747
+ make_field(contract.right),
1748
+ make_field(contract.multiplier), # srv v15 and above
1749
+ make_field(contract.exchange),
1750
+ make_field(contract.primaryExchange), # srv v14 and above
1751
+ make_field(contract.currency),
1752
+ make_field(contract.localSymbol),
1753
+ ] # srv v2 and above
1754
+ if self.serverVersion() >= MIN_SERVER_VER_TRADING_CLASS:
1755
+ flds.append(make_field(contract.tradingClass))
1756
+
1757
+ if self.serverVersion() >= MIN_SERVER_VER_SEC_ID_TYPE:
1758
+ flds += [make_field(contract.secIdType), make_field(contract.secId)]
1759
+
1760
+ # send main order fields
1761
+ flds.append(make_field(order.action))
1762
+
1763
+ if self.serverVersion() >= MIN_SERVER_VER_FRACTIONAL_POSITIONS:
1764
+ flds.append(make_field(order.totalQuantity))
1765
+ else:
1766
+ flds.append(make_field(int(order.totalQuantity)))
1767
+
1768
+ flds.append(make_field(order.orderType))
1769
+ if self.serverVersion() < MIN_SERVER_VER_ORDER_COMBO_LEGS_PRICE:
1770
+ flds.append(
1771
+ make_field(order.lmtPrice if order.lmtPrice != UNSET_DOUBLE else 0)
1772
+ )
1773
+ else:
1774
+ flds.append(make_field_handle_empty(order.lmtPrice))
1775
+ if self.serverVersion() < MIN_SERVER_VER_TRAILING_PERCENT:
1776
+ flds.append(
1777
+ make_field(order.auxPrice if order.auxPrice != UNSET_DOUBLE else 0)
1778
+ )
1779
+ else:
1780
+ flds.append(make_field_handle_empty(order.auxPrice))
1781
+
1782
+ # send extended order fields
1783
+ flds += [
1784
+ make_field(order.tif),
1785
+ make_field(order.ocaGroup),
1786
+ make_field(order.account),
1787
+ make_field(order.openClose),
1788
+ make_field(order.origin),
1789
+ make_field(order.orderRef),
1790
+ make_field(order.transmit),
1791
+ make_field(order.parentId), # srv v4 and above
1792
+ make_field(order.blockOrder), # srv v5 and above
1793
+ make_field(order.sweepToFill), # srv v5 and above
1794
+ make_field(order.displaySize), # srv v5 and above
1795
+ make_field(order.triggerMethod), # srv v5 and above
1796
+ make_field(order.outsideRth), # srv v5 and above
1797
+ make_field(order.hidden),
1798
+ ] # srv v7 and above
1799
+
1800
+ # Send combo legs for BAG requests (srv v8 and above)
1801
+ if contract.secType == "BAG":
1802
+ comboLegsCount = len(contract.comboLegs) if contract.comboLegs else 0
1803
+ flds.append(make_field(comboLegsCount))
1804
+ if comboLegsCount > 0:
1805
+ for comboLeg in contract.comboLegs:
1806
+ assert comboLeg
1807
+ flds += [
1808
+ make_field(comboLeg.conId),
1809
+ make_field(comboLeg.ratio),
1810
+ make_field(comboLeg.action),
1811
+ make_field(comboLeg.exchange),
1812
+ make_field(comboLeg.openClose),
1813
+ make_field(comboLeg.shortSaleSlot), # srv v35 and above
1814
+ make_field(comboLeg.designatedLocation),
1815
+ ] # srv v35 and above
1816
+ if self.serverVersion() >= MIN_SERVER_VER_SSHORTX_OLD:
1817
+ flds.append(make_field(comboLeg.exemptCode))
1818
+
1819
+ # Send order combo legs for BAG requests
1820
+ if (
1821
+ self.serverVersion() >= MIN_SERVER_VER_ORDER_COMBO_LEGS_PRICE
1822
+ and contract.secType == "BAG"
1823
+ ):
1824
+ orderComboLegsCount = (
1825
+ len(order.orderComboLegs) if order.orderComboLegs else 0
1826
+ )
1827
+ flds.append(make_field(orderComboLegsCount))
1828
+ if orderComboLegsCount:
1829
+ for orderComboLeg in order.orderComboLegs:
1830
+ assert orderComboLeg
1831
+ flds.append(make_field_handle_empty(orderComboLeg.price))
1832
+
1833
+ if (
1834
+ self.serverVersion() >= MIN_SERVER_VER_SMART_COMBO_ROUTING_PARAMS
1835
+ and contract.secType == "BAG"
1836
+ ):
1837
+ smartComboRoutingParamsCount = (
1838
+ len(order.smartComboRoutingParams)
1839
+ if order.smartComboRoutingParams
1840
+ else 0
1841
+ )
1842
+ flds.append(make_field(smartComboRoutingParamsCount))
1843
+ if smartComboRoutingParamsCount > 0:
1844
+ for tagValue in order.smartComboRoutingParams:
1845
+ flds += [make_field(tagValue.tag), make_field(tagValue.value)]
1846
+
1847
+ ######################################################################
1848
+ # Send the shares allocation.
1849
+ #
1850
+ # This specifies the number of order shares allocated to each Financial
1851
+ # Advisor managed account. The format of the allocation string is as
1852
+ # follows:
1853
+ # <account_code1>/<number_shares1>,<account_code2>/<number_shares2>,...N
1854
+ # E.g.
1855
+ # To allocate 20 shares of a 100 share order to account 'U101' and the
1856
+ # residual 80 to account 'U203' enter the following share allocation string:
1857
+ # U101/20,U203/80
1858
+ #####################################################################
1859
+ # send deprecated sharesAllocation field
1860
+ flds += [
1861
+ make_field(""), # srv v9 and above
1862
+ make_field(order.discretionaryAmt), # srv v10 and above
1863
+ make_field(order.goodAfterTime), # srv v11 and above
1864
+ make_field(order.goodTillDate), # srv v12 and above
1865
+ make_field(order.faGroup), # srv v13 and above
1866
+ make_field(order.faMethod), # srv v13 and above
1867
+ make_field(order.faPercentage),
1868
+ ] # srv v13 and above
1869
+ if self.serverVersion() < MIN_SERVER_VER_FA_PROFILE_DESUPPORT:
1870
+ flds.append(make_field("")) # send deprecated faProfile field
1871
+
1872
+ if self.serverVersion() >= MIN_SERVER_VER_MODELS_SUPPORT:
1873
+ flds.append(make_field(order.modelCode))
1874
+
1875
+ # institutional short saleslot data (srv v18 and above)
1876
+ flds += [
1877
+ make_field(
1878
+ order.shortSaleSlot
1879
+ ), # 0 for retail, 1 or 2 for institutions
1880
+ make_field(order.designatedLocation),
1881
+ ] # populate only when shortSaleSlot = 2.
1882
+ if self.serverVersion() >= MIN_SERVER_VER_SSHORTX_OLD:
1883
+ flds.append(make_field(order.exemptCode))
1884
+
1885
+ # srv v19 and above fields
1886
+ flds.append(make_field(order.ocaType))
1887
+ # if( self.serverVersion() < 38) {
1888
+ # will never happen
1889
+ # send( /* order.rthOnly */ false)
1890
+ # }
1891
+ flds += [
1892
+ make_field(order.rule80A),
1893
+ make_field(order.settlingFirm),
1894
+ make_field(order.allOrNone),
1895
+ make_field_handle_empty(order.minQty),
1896
+ make_field_handle_empty(order.percentOffset),
1897
+ make_field(False),
1898
+ make_field(False),
1899
+ make_field_handle_empty(UNSET_DOUBLE),
1900
+ make_field(
1901
+ order.auctionStrategy
1902
+ ), # AUCTION_MATCH, AUCTION_IMPROVEMENT, AUCTION_TRANSPARENT
1903
+ make_field_handle_empty(order.startingPrice),
1904
+ make_field_handle_empty(order.stockRefPrice),
1905
+ make_field_handle_empty(order.delta),
1906
+ make_field_handle_empty(order.stockRangeLower),
1907
+ make_field_handle_empty(order.stockRangeUpper),
1908
+ make_field(order.overridePercentageConstraints), # srv v22 and above
1909
+ # Volatility orders (srv v26 and above)
1910
+ make_field_handle_empty(order.volatility),
1911
+ make_field_handle_empty(order.volatilityType),
1912
+ make_field(order.deltaNeutralOrderType), # srv v28 and above
1913
+ make_field_handle_empty(order.deltaNeutralAuxPrice),
1914
+ ] # srv v28 and above
1915
+
1916
+ if (
1917
+ self.serverVersion() >= MIN_SERVER_VER_DELTA_NEUTRAL_CONID
1918
+ and order.deltaNeutralOrderType
1919
+ ):
1920
+ flds += [
1921
+ make_field(order.deltaNeutralConId),
1922
+ make_field(order.deltaNeutralSettlingFirm),
1923
+ make_field(order.deltaNeutralClearingAccount),
1924
+ make_field(order.deltaNeutralClearingIntent),
1925
+ ]
1926
+
1927
+ if (
1928
+ self.serverVersion() >= MIN_SERVER_VER_DELTA_NEUTRAL_OPEN_CLOSE
1929
+ and order.deltaNeutralOrderType
1930
+ ):
1931
+ flds += [
1932
+ make_field(order.deltaNeutralOpenClose),
1933
+ make_field(order.deltaNeutralShortSale),
1934
+ make_field(order.deltaNeutralShortSaleSlot),
1935
+ make_field(order.deltaNeutralDesignatedLocation),
1936
+ ]
1937
+
1938
+ flds += [
1939
+ make_field(order.continuousUpdate),
1940
+ make_field_handle_empty(order.referencePriceType),
1941
+ make_field_handle_empty(order.trailStopPrice),
1942
+ ] # srv v30 and above
1943
+
1944
+ if self.serverVersion() >= MIN_SERVER_VER_TRAILING_PERCENT:
1945
+ flds.append(make_field_handle_empty(order.trailingPercent))
1946
+
1947
+ # SCALE orders
1948
+ if self.serverVersion() >= MIN_SERVER_VER_SCALE_ORDERS2:
1949
+ flds += [
1950
+ make_field_handle_empty(order.scaleInitLevelSize),
1951
+ make_field_handle_empty(order.scaleSubsLevelSize),
1952
+ ]
1953
+ else:
1954
+ # srv v35 and above)
1955
+ flds += [
1956
+ make_field(""), # for not supported scaleNumComponents
1957
+ make_field_handle_empty(order.scaleInitLevelSize),
1958
+ ] # for scaleComponentSize
1959
+
1960
+ flds.append(make_field_handle_empty(order.scalePriceIncrement))
1961
+
1962
+ if (
1963
+ self.serverVersion() >= MIN_SERVER_VER_SCALE_ORDERS3
1964
+ and order.scalePriceIncrement != UNSET_DOUBLE
1965
+ and order.scalePriceIncrement > 0.0
1966
+ ):
1967
+ flds += [
1968
+ make_field_handle_empty(order.scalePriceAdjustValue),
1969
+ make_field_handle_empty(order.scalePriceAdjustInterval),
1970
+ make_field_handle_empty(order.scaleProfitOffset),
1971
+ make_field(order.scaleAutoReset),
1972
+ make_field_handle_empty(order.scaleInitPosition),
1973
+ make_field_handle_empty(order.scaleInitFillQty),
1974
+ make_field(order.scaleRandomPercent),
1975
+ ]
1976
+
1977
+ if self.serverVersion() >= MIN_SERVER_VER_SCALE_TABLE:
1978
+ flds += [
1979
+ make_field(order.scaleTable),
1980
+ make_field(order.activeStartTime),
1981
+ make_field(order.activeStopTime),
1982
+ ]
1983
+
1984
+ # HEDGE orders
1985
+ if self.serverVersion() >= MIN_SERVER_VER_HEDGE_ORDERS:
1986
+ flds.append(make_field(order.hedgeType))
1987
+ if order.hedgeType:
1988
+ flds.append(make_field(order.hedgeParam))
1989
+
1990
+ if self.serverVersion() >= MIN_SERVER_VER_OPT_OUT_SMART_ROUTING:
1991
+ flds.append(make_field(order.optOutSmartRouting))
1992
+
1993
+ if self.serverVersion() >= MIN_SERVER_VER_PTA_ORDERS:
1994
+ flds += [
1995
+ make_field(order.clearingAccount),
1996
+ make_field(order.clearingIntent),
1997
+ ]
1998
+
1999
+ if self.serverVersion() >= MIN_SERVER_VER_NOT_HELD:
2000
+ flds.append(make_field(order.notHeld))
2001
+
2002
+ if self.serverVersion() >= MIN_SERVER_VER_DELTA_NEUTRAL:
2003
+ if contract.deltaNeutralContract:
2004
+ flds += [
2005
+ make_field(True),
2006
+ make_field(contract.deltaNeutralContract.conId),
2007
+ make_field(contract.deltaNeutralContract.delta),
2008
+ make_field(contract.deltaNeutralContract.price),
2009
+ ]
2010
+ else:
2011
+ flds.append(make_field(False))
2012
+
2013
+ if self.serverVersion() >= MIN_SERVER_VER_ALGO_ORDERS:
2014
+ flds.append(make_field(order.algoStrategy))
2015
+ if order.algoStrategy:
2016
+ algoParamsCount = len(order.algoParams) if order.algoParams else 0
2017
+ flds.append(make_field(algoParamsCount))
2018
+ if algoParamsCount > 0:
2019
+ for algoParam in order.algoParams:
2020
+ flds += [
2021
+ make_field(algoParam.tag),
2022
+ make_field(algoParam.value),
2023
+ ]
2024
+
2025
+ if self.serverVersion() >= MIN_SERVER_VER_ALGO_ID:
2026
+ flds.append(make_field(order.algoId))
2027
+
2028
+ flds.append(make_field(order.whatIf)) # srv v36 and above
2029
+
2030
+ # send miscOptions parameter
2031
+ if self.serverVersion() >= MIN_SERVER_VER_LINKING:
2032
+ miscOptionsStr = ""
2033
+ if order.orderMiscOptions:
2034
+ for tagValue in order.orderMiscOptions:
2035
+ miscOptionsStr += str(tagValue)
2036
+ flds.append(make_field(miscOptionsStr))
2037
+
2038
+ if self.serverVersion() >= MIN_SERVER_VER_ORDER_SOLICITED:
2039
+ flds.append(make_field(order.solicited))
2040
+
2041
+ if self.serverVersion() >= MIN_SERVER_VER_RANDOMIZE_SIZE_AND_PRICE:
2042
+ flds += [
2043
+ make_field(order.randomizeSize),
2044
+ make_field(order.randomizePrice),
2045
+ ]
2046
+
2047
+ if self.serverVersion() >= MIN_SERVER_VER_PEGGED_TO_BENCHMARK:
2048
+ if isPegBenchOrder(order.orderType):
2049
+ flds += [
2050
+ make_field(order.referenceContractId),
2051
+ make_field(order.isPeggedChangeAmountDecrease),
2052
+ make_field(order.peggedChangeAmount),
2053
+ make_field(order.referenceChangeAmount),
2054
+ make_field(order.referenceExchangeId),
2055
+ ]
2056
+
2057
+ flds.append(make_field(len(order.conditions)))
2058
+
2059
+ if len(order.conditions) > 0:
2060
+ for cond in order.conditions:
2061
+ flds.append(make_field(cond.type()))
2062
+ flds += cond.make_fields()
2063
+
2064
+ flds += [
2065
+ make_field(order.conditionsIgnoreRth),
2066
+ make_field(order.conditionsCancelOrder),
2067
+ ]
2068
+
2069
+ flds += [
2070
+ make_field(order.adjustedOrderType),
2071
+ make_field(order.triggerPrice),
2072
+ make_field(order.lmtPriceOffset),
2073
+ make_field(order.adjustedStopPrice),
2074
+ make_field(order.adjustedStopLimitPrice),
2075
+ make_field(order.adjustedTrailingAmount),
2076
+ make_field(order.adjustableTrailingUnit),
2077
+ ]
2078
+
2079
+ if self.serverVersion() >= MIN_SERVER_VER_EXT_OPERATOR:
2080
+ flds.append(make_field(order.extOperator))
2081
+
2082
+ if self.serverVersion() >= MIN_SERVER_VER_SOFT_DOLLAR_TIER:
2083
+ flds += [
2084
+ make_field(order.softDollarTier.name),
2085
+ make_field(order.softDollarTier.val),
2086
+ ]
2087
+
2088
+ if self.serverVersion() >= MIN_SERVER_VER_CASH_QTY:
2089
+ flds.append(make_field(order.cashQty))
2090
+
2091
+ if self.serverVersion() >= MIN_SERVER_VER_DECISION_MAKER:
2092
+ flds.append(make_field(order.mifid2DecisionMaker))
2093
+ flds.append(make_field(order.mifid2DecisionAlgo))
2094
+
2095
+ if self.serverVersion() >= MIN_SERVER_VER_MIFID_EXECUTION:
2096
+ flds.append(make_field(order.mifid2ExecutionTrader))
2097
+ flds.append(make_field(order.mifid2ExecutionAlgo))
2098
+
2099
+ if self.serverVersion() >= MIN_SERVER_VER_AUTO_PRICE_FOR_HEDGE:
2100
+ flds.append(make_field(order.dontUseAutoPriceForHedge))
2101
+
2102
+ if self.serverVersion() >= MIN_SERVER_VER_ORDER_CONTAINER:
2103
+ flds.append(make_field(order.isOmsContainer))
2104
+
2105
+ if self.serverVersion() >= MIN_SERVER_VER_D_PEG_ORDERS:
2106
+ flds.append(make_field(order.discretionaryUpToLimitPrice))
2107
+
2108
+ if self.serverVersion() >= MIN_SERVER_VER_PRICE_MGMT_ALGO:
2109
+ flds.append(
2110
+ make_field_handle_empty(
2111
+ UNSET_INTEGER
2112
+ if order.usePriceMgmtAlgo is None
2113
+ else 1
2114
+ if order.usePriceMgmtAlgo
2115
+ else 0
2116
+ )
2117
+ )
2118
+
2119
+ if self.serverVersion() >= MIN_SERVER_VER_DURATION:
2120
+ flds.append(make_field(order.duration))
2121
+
2122
+ if self.serverVersion() >= MIN_SERVER_VER_POST_TO_ATS:
2123
+ flds.append(make_field(order.postToAts))
2124
+
2125
+ if self.serverVersion() >= MIN_SERVER_VER_AUTO_CANCEL_PARENT:
2126
+ flds.append(make_field(order.autoCancelParent))
2127
+
2128
+ if self.serverVersion() >= MIN_SERVER_VER_ADVANCED_ORDER_REJECT:
2129
+ flds.append(make_field(order.advancedErrorOverride))
2130
+
2131
+ if self.serverVersion() >= MIN_SERVER_VER_MANUAL_ORDER_TIME:
2132
+ flds.append(make_field(order.manualOrderTime))
2133
+
2134
+ if self.serverVersion() >= MIN_SERVER_VER_PEGBEST_PEGMID_OFFSETS:
2135
+ sendMidOffsets = False
2136
+ if contract.exchange == "IBKRATS":
2137
+ flds.append(make_field_handle_empty(order.minTradeQty))
2138
+ if isPegBestOrder(order.orderType):
2139
+ flds.append(make_field_handle_empty(order.minCompeteSize))
2140
+ flds.append(make_field_handle_empty(order.competeAgainstBestOffset))
2141
+ if (
2142
+ order.competeAgainstBestOffset
2143
+ == COMPETE_AGAINST_BEST_OFFSET_UP_TO_MID
2144
+ ):
2145
+ sendMidOffsets = True
2146
+ elif isPegMidOrder(order.orderType):
2147
+ sendMidOffsets = True
2148
+ if sendMidOffsets:
2149
+ flds.append(make_field_handle_empty(order.midOffsetAtWhole))
2150
+ flds.append(make_field_handle_empty(order.midOffsetAtHalf))
2151
+
2152
+ if self.serverVersion() >= MIN_SERVER_VER_CUSTOMER_ACCOUNT:
2153
+ flds.append(make_field(order.customerAccount))
2154
+
2155
+ if self.serverVersion() >= MIN_SERVER_VER_PROFESSIONAL_CUSTOMER:
2156
+ flds.append(make_field(order.professionalCustomer))
2157
+
2158
+ if self.serverVersion() >= MIN_SERVER_VER_RFQ_FIELDS and self.serverVersion() < MIN_SERVER_VER_UNDO_RFQ_FIELDS:
2159
+ flds.append(make_field(""))
2160
+ flds.append(make_field(UNSET_INTEGER))
2161
+
2162
+ if self.serverVersion() >= MIN_SERVER_VER_INCLUDE_OVERNIGHT:
2163
+ flds.append(make_field(order.includeOvernight))
2164
+
2165
+ if self.serverVersion() >= MIN_SERVER_VER_CME_TAGGING_FIELDS:
2166
+ flds.append(make_field(order.manualOrderIndicator))
2167
+
2168
+ if self.serverVersion() >= MIN_SERVER_VER_IMBALANCE_ONLY:
2169
+ flds.append(make_field(order.imbalanceOnly))
2170
+
2171
+ msg = "".join(flds)
2172
+
2173
+ except ClientException as ex:
2174
+ self.wrapper.error(orderId, currentTimeMillis(), ex.code, ex.msg + ex.text)
2175
+ return
2176
+
2177
+ self.sendMsg(OUT.PLACE_ORDER, msg)
2178
+
2179
+ def placeOrderProtoBuf(self, placeOrderRequestProto: PlaceOrderRequestProto):
2180
+ serializedString = placeOrderRequestProto.SerializeToString()
2181
+
2182
+ self.sendMsgProtoBuf(OUT.PLACE_ORDER + PROTOBUF_MSG_ID, serializedString)
2183
+
2184
+ def cancelOrder(self, orderId: OrderId, orderCancel: OrderCancel):
2185
+ """Call this function to cancel an order.
2186
+
2187
+ orderId:OrderId - The order ID that was specified previously in the call
2188
+ to placeOrder()"""
2189
+
2190
+ if (self.useProtoBuf(OUT.CANCEL_ORDER)):
2191
+ cancelOrderRequestProto = createCancelOrderRequestProto(orderId, orderCancel)
2192
+ self.cancelOrderProtoBuf(cancelOrderRequestProto)
2193
+ return
2194
+
2195
+ self.logRequest(current_fn_name(), vars())
2196
+
2197
+ if not self.isConnected():
2198
+ self.wrapper.error(NO_VALID_ID, currentTimeMillis(), NOT_CONNECTED.code(), NOT_CONNECTED.msg())
2199
+ return
2200
+
2201
+ if (
2202
+ self.serverVersion() < MIN_SERVER_VER_MANUAL_ORDER_TIME
2203
+ and orderCancel.manualOrderCancelTime
2204
+ ):
2205
+ self.wrapper.error(
2206
+ orderId,
2207
+ currentTimeMillis(),
2208
+ UPDATE_TWS.code(),
2209
+ UPDATE_TWS.msg()
2210
+ + " It does not support manual order cancel time attribute",
2211
+ )
2212
+ return
2213
+
2214
+ if self.serverVersion() < MIN_SERVER_VER_CME_TAGGING_FIELDS and (
2215
+ orderCancel.extOperator != "" or orderCancel.manualOrderIndicator != UNSET_INTEGER
2216
+ ):
2217
+ self.wrapper.error(
2218
+ orderId,
2219
+ currentTimeMillis(),
2220
+ UPDATE_TWS.code(),
2221
+ UPDATE_TWS.msg() + " It does not support ext operator and manual order indicator parameters",
2222
+ )
2223
+ return
2224
+
2225
+ try:
2226
+ VERSION = 1
2227
+
2228
+ flds = []
2229
+ if self.serverVersion() < MIN_SERVER_VER_CME_TAGGING_FIELDS:
2230
+ flds += [make_field(VERSION)]
2231
+ flds += [make_field(orderId)]
2232
+
2233
+ if self.serverVersion() >= MIN_SERVER_VER_MANUAL_ORDER_TIME:
2234
+ flds += [make_field(orderCancel.manualOrderCancelTime)]
2235
+
2236
+ if self.serverVersion() >= MIN_SERVER_VER_RFQ_FIELDS and self.serverVersion() < MIN_SERVER_VER_UNDO_RFQ_FIELDS:
2237
+ flds += [make_field("")]
2238
+ flds += [make_field("")]
2239
+ flds += [make_field(UNSET_INTEGER)]
2240
+
2241
+ if self.serverVersion() >= MIN_SERVER_VER_CME_TAGGING_FIELDS:
2242
+ flds += [make_field(orderCancel.extOperator)]
2243
+ flds += [make_field(orderCancel.manualOrderIndicator)]
2244
+
2245
+ msg = "".join(flds)
2246
+
2247
+ except ClientException as ex:
2248
+ self.wrapper.error(orderId, currentTimeMillis(), ex.code, ex.msg + ex.text)
2249
+ return
2250
+
2251
+ self.sendMsg(OUT.CANCEL_ORDER, msg)
2252
+
2253
+ def cancelOrderProtoBuf(self, cancelOrderRequestProto: CancelOrderRequestProto):
2254
+ serializedString = cancelOrderRequestProto.SerializeToString()
2255
+
2256
+ self.sendMsgProtoBuf(OUT.CANCEL_ORDER + PROTOBUF_MSG_ID, serializedString)
2257
+
2258
+ def reqOpenOrders(self):
2259
+ """Call this function to request the open orders that were
2260
+ placed from this client. Each open order will be fed back through the
2261
+ openOrder() and orderStatus() functions on the EWrapper.
2262
+
2263
+ Note: The client with a clientId of 0 will also receive the TWS-owned
2264
+ open orders. These orders will be associated with the client and a new
2265
+ orderId will be generated. This association will persist over multiple
2266
+ API and TWS sessions."""
2267
+
2268
+ self.logRequest(current_fn_name(), vars())
2269
+
2270
+ if not self.isConnected():
2271
+ self.wrapper.error(NO_VALID_ID, currentTimeMillis(), NOT_CONNECTED.code(), NOT_CONNECTED.msg())
2272
+ return
2273
+
2274
+ VERSION = 1
2275
+
2276
+ msg = make_field(VERSION)
2277
+
2278
+ self.sendMsg(OUT.REQ_OPEN_ORDERS, msg)
2279
+
2280
+ def reqAutoOpenOrders(self, bAutoBind: bool):
2281
+ """Call this function to request that newly created TWS orders
2282
+ be implicitly associated with the client. When a new TWS order is
2283
+ created, the order will be associated with the client, and fed back
2284
+ through the openOrder() and orderStatus() functions on the EWrapper.
2285
+
2286
+ Note: This request can only be made from a client with clientId of 0.
2287
+
2288
+ bAutoBind: If set to TRUE, newly created TWS orders will be implicitly
2289
+ associated with the client. If set to FALSE, no association will be
2290
+ made."""
2291
+
2292
+ self.logRequest(current_fn_name(), vars())
2293
+
2294
+ if not self.isConnected():
2295
+ self.wrapper.error(NO_VALID_ID, currentTimeMillis(), NOT_CONNECTED.code(), NOT_CONNECTED.msg())
2296
+ return
2297
+
2298
+ VERSION = 1
2299
+
2300
+ msg = (
2301
+ make_field(VERSION)
2302
+ + make_field(bAutoBind)
2303
+ )
2304
+
2305
+ self.sendMsg(OUT.REQ_AUTO_OPEN_ORDERS, msg)
2306
+
2307
+ def reqAllOpenOrders(self):
2308
+ """Call this function to request the open orders placed from all
2309
+ clients and also from TWS. Each open order will be fed back through the
2310
+ openOrder() and orderStatus() functions on the EWrapper.
2311
+
2312
+ Note: No association is made between the returned orders and the
2313
+ requesting client."""
2314
+
2315
+ self.logRequest(current_fn_name(), vars())
2316
+
2317
+ if not self.isConnected():
2318
+ self.wrapper.error(NO_VALID_ID, currentTimeMillis(), NOT_CONNECTED.code(), NOT_CONNECTED.msg())
2319
+ return
2320
+
2321
+ VERSION = 1
2322
+
2323
+ msg = make_field(VERSION)
2324
+
2325
+ self.sendMsg(OUT.REQ_ALL_OPEN_ORDERS, msg)
2326
+
2327
+ def reqGlobalCancel(self, orderCancel: OrderCancel):
2328
+ """Use this function to cancel all open orders globally. It
2329
+ cancels both API and TWS open orders.
2330
+
2331
+ If the order was created in TWS, it also gets canceled. If the order
2332
+ was initiated in the API, it also gets canceled."""
2333
+
2334
+ if (self.useProtoBuf(OUT.REQ_GLOBAL_CANCEL)):
2335
+ globalCancelRequestProto = createGlobalCancelRequestProto(orderCancel)
2336
+ self.reqGlobalCancelProtoBuf(globalCancelRequestProto)
2337
+ return
2338
+
2339
+ self.logRequest(current_fn_name(), vars())
2340
+
2341
+ if not self.isConnected():
2342
+ self.wrapper.error(NO_VALID_ID, currentTimeMillis(), NOT_CONNECTED.code(), NOT_CONNECTED.msg())
2343
+ return
2344
+
2345
+ if self.serverVersion() < MIN_SERVER_VER_CME_TAGGING_FIELDS and (
2346
+ orderCancel.extOperator != "" or orderCancel.manualOrderIndicator != UNSET_INTEGER
2347
+ ):
2348
+ self.wrapper.error(
2349
+ NO_VALID_ID,
2350
+ currentTimeMillis(),
2351
+ UPDATE_TWS.code(),
2352
+ UPDATE_TWS.msg() + " It does not support ext operator and manual order indicator parameters",
2353
+ )
2354
+ return
2355
+
2356
+ try:
2357
+ VERSION = 1
2358
+
2359
+ flds = []
2360
+ if self.serverVersion() < MIN_SERVER_VER_CME_TAGGING_FIELDS:
2361
+ flds += [make_field(VERSION)]
2362
+
2363
+ if self.serverVersion() >= MIN_SERVER_VER_CME_TAGGING_FIELDS:
2364
+ flds += [make_field(orderCancel.extOperator)]
2365
+ flds += [make_field(orderCancel.manualOrderIndicator)]
2366
+
2367
+ msg = "".join(flds)
2368
+
2369
+ except ClientException as ex:
2370
+ self.wrapper.error(NO_VALID_ID, currentTimeMillis(), ex.code, ex.msg + ex.text)
2371
+ return
2372
+
2373
+ self.sendMsg(OUT.REQ_GLOBAL_CANCEL, msg)
2374
+
2375
+ def reqGlobalCancelProtoBuf(self, globalCancelRequestProto: GlobalCancelRequestProto):
2376
+ serializedString = globalCancelRequestProto.SerializeToString()
2377
+
2378
+ self.sendMsgProtoBuf(OUT.REQ_GLOBAL_CANCEL + PROTOBUF_MSG_ID, serializedString)
2379
+
2380
+ def reqIds(self, numIds: int):
2381
+ """Call this function to request from TWS the next valid ID that
2382
+ can be used when placing an order. After calling this function, the
2383
+ nextValidId() event will be triggered, and the id returned is that next
2384
+ valid ID. That ID will reflect any autobinding that has occurred (which
2385
+ generates new IDs and increments the next valid ID therein).
2386
+
2387
+ numIds:int - deprecated"""
2388
+
2389
+ self.logRequest(current_fn_name(), vars())
2390
+
2391
+ if not self.isConnected():
2392
+ self.wrapper.error(NO_VALID_ID, currentTimeMillis(), NOT_CONNECTED.code(), NOT_CONNECTED.msg())
2393
+ return
2394
+
2395
+ VERSION = 1
2396
+
2397
+ msg = make_field(VERSION) + make_field(numIds)
2398
+
2399
+ self.sendMsg(OUT.REQ_IDS, msg)
2400
+
2401
+ #########################################################################
2402
+ # Account and Portfolio
2403
+ ########################################################################
2404
+
2405
+ def reqAccountUpdates(self, subscribe: bool, acctCode: str):
2406
+ """Call this function to start getting account values, portfolio,
2407
+ and last update time information via EWrapper.updateAccountValue(),
2408
+ EWrapperi.updatePortfolio() and Wrapper.updateAccountTime().
2409
+
2410
+ subscribe:bool - If set to TRUE, the client will start receiving account
2411
+ and Portfoliolio updates. If set to FALSE, the client will stop
2412
+ receiving this information.
2413
+ acctCode:str -The account code for which to receive account and
2414
+ portfolio updates."""
2415
+
2416
+ self.logRequest(current_fn_name(), vars())
2417
+
2418
+ if not self.isConnected():
2419
+ self.wrapper.error(NO_VALID_ID, currentTimeMillis(), NOT_CONNECTED.code(), NOT_CONNECTED.msg())
2420
+ return
2421
+
2422
+ try:
2423
+ VERSION = 2
2424
+
2425
+ flds = []
2426
+ flds += [
2427
+ make_field(VERSION),
2428
+ make_field(subscribe), # TRUE = subscribe, FALSE = unsubscribe.
2429
+ make_field(acctCode),
2430
+ ] # srv v9 and above, the account code. This will only be used for FA clients
2431
+
2432
+ msg = "".join(flds)
2433
+
2434
+ except ClientException as ex:
2435
+ self.wrapper.error(NO_VALID_ID, currentTimeMillis(), ex.code, ex.msg + ex.text)
2436
+ return
2437
+
2438
+ self.sendMsg(OUT.REQ_ACCT_DATA, msg)
2439
+
2440
+ def reqAccountSummary(self, reqId: int, groupName: str, tags: str):
2441
+ """Call this method to request and keep up to date the data that appears
2442
+ on the TWS Account Window Summary tab. The data is returned by
2443
+ accountSummary().
2444
+
2445
+ Note: This request is designed for an FA managed account but can be
2446
+ used for any multi-account structure.
2447
+
2448
+ reqId:int - The ID of the data request. Ensures that responses are matched
2449
+ to requests If several requests are in process.
2450
+ groupName:str - Set to All to returnrn account summary data for all
2451
+ accounts, or set to a specific Advisor Account Group name that has
2452
+ already been created in TWS Global Configuration.
2453
+ tags:str - A comma-separated list of account tags. Available tags are:
2454
+ accountountType
2455
+ NetLiquidation,
2456
+ TotalCashValue - Total cash including futures pnl
2457
+ SettledCash - For cash accounts, this is the same as
2458
+ TotalCashValue
2459
+ AccruedCash - Net accrued interest
2460
+ BuyingPower - The maximum amount of marginable US stocks the
2461
+ account can buy
2462
+ EquityWithLoanValue - Cash + stocks + bonds + mutual funds
2463
+ PreviousDayEquityWithLoanValue,
2464
+ GrossPositionValue - The sum of the absolute value of all stock
2465
+ and equity option positions
2466
+ RegTEquity,
2467
+ RegTMargin,
2468
+ SMA - Special Memorandum Account
2469
+ InitMarginReq,
2470
+ MaintMarginReq,
2471
+ AvailableFunds,
2472
+ ExcessLiquidity,
2473
+ Cushion - Excess liquidity as a percentage of net liquidation value
2474
+ FullInitMarginReq,
2475
+ FullMaintMarginReq,
2476
+ FullAvailableFunds,
2477
+ FullExcessLiquidity,
2478
+ LookAheadNextChange - Time when look-ahead values take effect
2479
+ LookAheadInitMarginReq,
2480
+ LookAheadMaintMarginReq,
2481
+ LookAheadAvailableFunds,
2482
+ LookAheadExcessLiquidity,
2483
+ HighestSeverity - A measure of how close the account is to liquidation
2484
+ DayTradesRemaining - The Number of Open/Close trades a user
2485
+ could put on before Pattern Day Trading is detected. A value of "-1"
2486
+ means that the user can put on unlimited day trades.
2487
+ Leverage - GrossPositionValue / NetLiquidation
2488
+ $LEDGER - Single flag to relay all cash balance tags*, only in base
2489
+ currency.
2490
+ $LEDGER:CURRENCY - Single flag to relay all cash balance tags*, only in
2491
+ the specified currency.
2492
+ $LEDGER:ALL - Single flag to relay all cash balance tags* in all
2493
+ currencies."""
2494
+
2495
+ self.logRequest(current_fn_name(), vars())
2496
+
2497
+ if not self.isConnected():
2498
+ self.wrapper.error(NO_VALID_ID, currentTimeMillis(), NOT_CONNECTED.code(), NOT_CONNECTED.msg())
2499
+ return
2500
+
2501
+ try:
2502
+ VERSION = 1
2503
+
2504
+ msg = (
2505
+ make_field(VERSION)
2506
+ + make_field(reqId)
2507
+ + make_field(groupName)
2508
+ + make_field(tags)
2509
+ )
2510
+
2511
+ except ClientException as ex:
2512
+ self.wrapper.error(reqId, currentTimeMillis(), ex.code, ex.msg + ex.text)
2513
+ return
2514
+
2515
+ self.sendMsg(OUT.REQ_ACCOUNT_SUMMARY, msg)
2516
+
2517
+ def cancelAccountSummary(self, reqId: int):
2518
+ """Cancels the request for Account Window Summary tab data.
2519
+
2520
+ reqId:int - The ID of the data request being canceled."""
2521
+
2522
+ self.logRequest(current_fn_name(), vars())
2523
+
2524
+ if not self.isConnected():
2525
+ self.wrapper.error(NO_VALID_ID, currentTimeMillis(), NOT_CONNECTED.code(), NOT_CONNECTED.msg())
2526
+ return
2527
+
2528
+ VERSION = 1
2529
+
2530
+ msg = (
2531
+ make_field(VERSION)
2532
+ + make_field(reqId)
2533
+ )
2534
+
2535
+ self.sendMsg(OUT.CANCEL_ACCOUNT_SUMMARY, msg)
2536
+
2537
+ def reqPositions(self):
2538
+ """Requests real-time position data for all accounts."""
2539
+
2540
+ self.logRequest(current_fn_name(), vars())
2541
+
2542
+ if not self.isConnected():
2543
+ self.wrapper.error(NO_VALID_ID, currentTimeMillis(), NOT_CONNECTED.code(), NOT_CONNECTED.msg())
2544
+ return
2545
+
2546
+ if self.serverVersion() < MIN_SERVER_VER_POSITIONS:
2547
+ self.wrapper.error(
2548
+ NO_VALID_ID,
2549
+ currentTimeMillis(),
2550
+ UPDATE_TWS.code(),
2551
+ UPDATE_TWS.msg() + " It does not support positions request.",
2552
+ )
2553
+ return
2554
+
2555
+ VERSION = 1
2556
+
2557
+ msg = make_field(VERSION)
2558
+
2559
+ self.sendMsg(OUT.REQ_POSITIONS, msg)
2560
+
2561
+ def cancelPositions(self):
2562
+ """Cancels real-time position updates."""
2563
+
2564
+ self.logRequest(current_fn_name(), vars())
2565
+
2566
+ if not self.isConnected():
2567
+ self.wrapper.error(NO_VALID_ID, currentTimeMillis(), NOT_CONNECTED.code(), NOT_CONNECTED.msg())
2568
+ return
2569
+
2570
+ if self.serverVersion() < MIN_SERVER_VER_POSITIONS:
2571
+ self.wrapper.error(
2572
+ NO_VALID_ID,
2573
+ currentTimeMillis(),
2574
+ UPDATE_TWS.code(),
2575
+ UPDATE_TWS.msg() + " It does not support positions request.",
2576
+ )
2577
+ return
2578
+
2579
+ VERSION = 1
2580
+
2581
+ msg = make_field(VERSION)
2582
+
2583
+ self.sendMsg(OUT.CANCEL_POSITIONS, msg)
2584
+
2585
+ def reqPositionsMulti(self, reqId: int, account: str, modelCode: str):
2586
+ """Requests positions for account and/or model.
2587
+ Results are delivered via EWrapper.positionMulti() and
2588
+ EWrapper.positionMultiEnd()"""
2589
+
2590
+ self.logRequest(current_fn_name(), vars())
2591
+
2592
+ if not self.isConnected():
2593
+ self.wrapper.error(NO_VALID_ID, currentTimeMillis(), NOT_CONNECTED.code(), NOT_CONNECTED.msg())
2594
+ return
2595
+
2596
+ if self.serverVersion() < MIN_SERVER_VER_MODELS_SUPPORT:
2597
+ self.wrapper.error(
2598
+ NO_VALID_ID,
2599
+ currentTimeMillis(),
2600
+ UPDATE_TWS.code(),
2601
+ UPDATE_TWS.msg() + " It does not support positions multi request.",
2602
+ )
2603
+ return
2604
+
2605
+ try:
2606
+ VERSION = 1
2607
+
2608
+ msg = (
2609
+ make_field(VERSION)
2610
+ + make_field(reqId)
2611
+ + make_field(account)
2612
+ + make_field(modelCode)
2613
+ )
2614
+
2615
+ except ClientException as ex:
2616
+ self.wrapper.error(reqId, currentTimeMillis(), ex.code, ex.msg + ex.text)
2617
+ return
2618
+
2619
+ self.sendMsg(OUT.REQ_POSITIONS_MULTI, msg)
2620
+
2621
+ def cancelPositionsMulti(self, reqId: int):
2622
+ self.logRequest(current_fn_name(), vars())
2623
+
2624
+ if not self.isConnected():
2625
+ self.wrapper.error(NO_VALID_ID, currentTimeMillis(), NOT_CONNECTED.code(), NOT_CONNECTED.msg())
2626
+ return
2627
+
2628
+ if self.serverVersion() < MIN_SERVER_VER_MODELS_SUPPORT:
2629
+ self.wrapper.error(
2630
+ NO_VALID_ID,
2631
+ currentTimeMillis(),
2632
+ UPDATE_TWS.code(),
2633
+ UPDATE_TWS.msg()
2634
+ + " It does not support cancel positions multi request.",
2635
+ )
2636
+ return
2637
+
2638
+ VERSION = 1
2639
+
2640
+ msg = (
2641
+ make_field(VERSION)
2642
+ + make_field(reqId)
2643
+ )
2644
+ self.sendMsg(OUT.CANCEL_POSITIONS_MULTI, msg)
2645
+
2646
+ def reqAccountUpdatesMulti(
2647
+ self, reqId: int, account: str, modelCode: str, ledgerAndNLV: bool
2648
+ ):
2649
+ """Requests account updates for account and/or model."""
2650
+
2651
+ self.logRequest(current_fn_name(), vars())
2652
+
2653
+ if not self.isConnected():
2654
+ self.wrapper.error(NO_VALID_ID, currentTimeMillis(), NOT_CONNECTED.code(), NOT_CONNECTED.msg())
2655
+ return
2656
+
2657
+ if self.serverVersion() < MIN_SERVER_VER_MODELS_SUPPORT:
2658
+ self.wrapper.error(
2659
+ NO_VALID_ID,
2660
+ currentTimeMillis(),
2661
+ UPDATE_TWS.code(),
2662
+ UPDATE_TWS.msg()
2663
+ + " It does not support account updates multi request.",
2664
+ )
2665
+ return
2666
+
2667
+ try:
2668
+ VERSION = 1
2669
+
2670
+ msg = (
2671
+ make_field(VERSION)
2672
+ + make_field(reqId)
2673
+ + make_field(account)
2674
+ + make_field(modelCode)
2675
+ + make_field(ledgerAndNLV)
2676
+ )
2677
+
2678
+ except ClientException as ex:
2679
+ self.wrapper.error(reqId, currentTimeMillis(), ex.code, ex.msg + ex.text)
2680
+ return
2681
+
2682
+ self.sendMsg(OUT.REQ_ACCOUNT_UPDATES_MULTI, msg)
2683
+
2684
+ def cancelAccountUpdatesMulti(self, reqId: int):
2685
+ self.logRequest(current_fn_name(), vars())
2686
+
2687
+ if not self.isConnected():
2688
+ self.wrapper.error(NO_VALID_ID, currentTimeMillis(), NOT_CONNECTED.code(), NOT_CONNECTED.msg())
2689
+ return
2690
+
2691
+ if self.serverVersion() < MIN_SERVER_VER_MODELS_SUPPORT:
2692
+ self.wrapper.error(
2693
+ NO_VALID_ID,
2694
+ currentTimeMillis(),
2695
+ UPDATE_TWS.code(),
2696
+ UPDATE_TWS.msg()
2697
+ + " It does not support cancel account updates multi request.",
2698
+ )
2699
+ return
2700
+
2701
+ VERSION = 1
2702
+
2703
+ msg = (
2704
+ make_field(VERSION)
2705
+ + make_field(reqId)
2706
+ )
2707
+ self.sendMsg(OUT.CANCEL_ACCOUNT_UPDATES_MULTI, msg)
2708
+
2709
+ #########################################################################
2710
+ # Daily PnL
2711
+ #########################################################################
2712
+
2713
+ def reqPnL(self, reqId: int, account: str, modelCode: str):
2714
+ self.logRequest(current_fn_name(), vars())
2715
+
2716
+ if not self.isConnected():
2717
+ self.wrapper.error(NO_VALID_ID, currentTimeMillis(), NOT_CONNECTED.code(), NOT_CONNECTED.msg())
2718
+ return
2719
+
2720
+ if self.serverVersion() < MIN_SERVER_VER_PNL:
2721
+ self.wrapper.error(
2722
+ NO_VALID_ID,
2723
+ currentTimeMillis(),
2724
+ UPDATE_TWS.code(),
2725
+ UPDATE_TWS.msg() + " It does not support PnL request.",
2726
+ )
2727
+ return
2728
+
2729
+ try:
2730
+ msg = (
2731
+ make_field(reqId)
2732
+ + make_field(account)
2733
+ + make_field(modelCode)
2734
+ )
2735
+
2736
+ except ClientException as ex:
2737
+ self.wrapper.error(reqId, currentTimeMillis(), ex.code, ex.msg + ex.text)
2738
+ return
2739
+
2740
+ self.sendMsg(OUT.REQ_PNL, msg)
2741
+
2742
+ def cancelPnL(self, reqId: int):
2743
+ self.logRequest(current_fn_name(), vars())
2744
+
2745
+ if not self.isConnected():
2746
+ self.wrapper.error(NO_VALID_ID, currentTimeMillis(), NOT_CONNECTED.code(), NOT_CONNECTED.msg())
2747
+ return
2748
+
2749
+ if self.serverVersion() < MIN_SERVER_VER_PNL:
2750
+ self.wrapper.error(
2751
+ NO_VALID_ID,
2752
+ currentTimeMillis(),
2753
+ UPDATE_TWS.code(),
2754
+ UPDATE_TWS.msg() + " It does not support PnL request.",
2755
+ )
2756
+ return
2757
+
2758
+ msg = make_field(reqId)
2759
+
2760
+ self.sendMsg(OUT.CANCEL_PNL, msg)
2761
+
2762
+ def reqPnLSingle(self, reqId: int, account: str, modelCode: str, conid: int):
2763
+ self.logRequest(current_fn_name(), vars())
2764
+
2765
+ if not self.isConnected():
2766
+ self.wrapper.error(NO_VALID_ID, currentTimeMillis(), NOT_CONNECTED.code(), NOT_CONNECTED.msg())
2767
+ return
2768
+
2769
+ if self.serverVersion() < MIN_SERVER_VER_PNL:
2770
+ self.wrapper.error(
2771
+ NO_VALID_ID,
2772
+ currentTimeMillis(),
2773
+ UPDATE_TWS.code(),
2774
+ UPDATE_TWS.msg() + " It does not support PnL request.",
2775
+ )
2776
+ return
2777
+
2778
+ try:
2779
+ msg = (
2780
+ make_field(reqId)
2781
+ + make_field(account)
2782
+ + make_field(modelCode)
2783
+ + make_field(conid)
2784
+ )
2785
+
2786
+ except ClientException as ex:
2787
+ self.wrapper.error(reqId, currentTimeMillis(), ex.code, ex.msg + ex.text)
2788
+ return
2789
+
2790
+ self.sendMsg(OUT.REQ_PNL_SINGLE, msg)
2791
+
2792
+ def cancelPnLSingle(self, reqId: int):
2793
+ self.logRequest(current_fn_name(), vars())
2794
+
2795
+ if not self.isConnected():
2796
+ self.wrapper.error(NO_VALID_ID, currentTimeMillis(), NOT_CONNECTED.code(), NOT_CONNECTED.msg())
2797
+ return
2798
+
2799
+ if self.serverVersion() < MIN_SERVER_VER_PNL:
2800
+ self.wrapper.error(
2801
+ NO_VALID_ID,
2802
+ currentTimeMillis(),
2803
+ UPDATE_TWS.code(),
2804
+ UPDATE_TWS.msg() + " It does not support PnL request.",
2805
+ )
2806
+ return
2807
+
2808
+ msg = make_field(reqId)
2809
+
2810
+ self.sendMsg(OUT.CANCEL_PNL_SINGLE, msg)
2811
+
2812
+ #########################################################################
2813
+ # Executions
2814
+ #########################################################################
2815
+
2816
+ def reqExecutions(self, reqId: int, execFilter: ExecutionFilter):
2817
+ """When this function is called, the execution reports that meet the
2818
+ filter criteria are downloaded to the client via the execDetails()
2819
+ function. To view executions beyond the past 24 hours, open the
2820
+ Trade Log in TWS and, while the Trade Log is displayed, request
2821
+ the executions again from the API.
2822
+
2823
+ reqId:int - The ID of the data request. Ensures that responses are
2824
+ matched to requests if several requests are in process.
2825
+ execFilter:ExecutionFilter - This object contains attributes that
2826
+ describe the filter criteria used to determine which execution
2827
+ reports are returned.
2828
+
2829
+ NOTE: Time format must be 'yyyymmdd-hh:mm:ss' Eg: '20030702-14:55'"""
2830
+ if (self.useProtoBuf(OUT.REQ_EXECUTIONS)):
2831
+ executionRequestProto = createExecutionRequestProto(reqId, execFilter)
2832
+ self.reqExecutionsProtoBuf(executionRequestProto)
2833
+ return
2834
+
2835
+ self.logRequest(current_fn_name(), vars())
2836
+
2837
+ if not self.isConnected():
2838
+ self.wrapper.error(NO_VALID_ID, currentTimeMillis(), NOT_CONNECTED.code(), NOT_CONNECTED.msg())
2839
+ return
2840
+
2841
+
2842
+ if self.serverVersion() < MIN_SERVER_VER_PARAMETRIZED_DAYS_OF_EXECUTIONS:
2843
+ if (
2844
+ execFilter.lastNDays != UNSET_INTEGER
2845
+ or execFilter.specificDates is not None
2846
+ ):
2847
+ self.wrapper.error(
2848
+ reqId,
2849
+ currentTimeMillis(),
2850
+ UPDATE_TWS.code(),
2851
+ UPDATE_TWS.msg()
2852
+ + " It does not support last N days and specific dates parameters",
2853
+ )
2854
+ return
2855
+
2856
+ try:
2857
+ VERSION = 3
2858
+
2859
+ # send req open orders msg
2860
+ flds = []
2861
+ flds += [make_field(VERSION)]
2862
+
2863
+ if self.serverVersion() >= MIN_SERVER_VER_EXECUTION_DATA_CHAIN:
2864
+ flds += [
2865
+ make_field(reqId),
2866
+ ]
2867
+
2868
+ # Send the execution rpt filter data (srv v9 and above)
2869
+ flds += [
2870
+ make_field(execFilter.clientId),
2871
+ make_field(execFilter.acctCode),
2872
+ make_field(execFilter.time),
2873
+ make_field(execFilter.symbol),
2874
+ make_field(execFilter.secType),
2875
+ make_field(execFilter.exchange),
2876
+ make_field(execFilter.side),
2877
+ ]
2878
+
2879
+ if self.serverVersion() >= MIN_SERVER_VER_PARAMETRIZED_DAYS_OF_EXECUTIONS:
2880
+ flds += [
2881
+ make_field(execFilter.lastNDays),
2882
+ ]
2883
+ if execFilter.specificDates is not None :
2884
+ flds += [
2885
+ make_field(len(execFilter.specificDates)),
2886
+ ]
2887
+ for specificDate in execFilter.specificDates:
2888
+ flds += [
2889
+ make_field(specificDate),
2890
+ ]
2891
+ else:
2892
+ flds += [
2893
+ make_field(0),
2894
+ ]
2895
+
2896
+ msg = "".join(flds)
2897
+
2898
+ except ClientException as ex:
2899
+ self.wrapper.error(reqId, currentTimeMillis(), ex.code, ex.msg + ex.text)
2900
+ return
2901
+
2902
+ self.sendMsg(OUT.REQ_EXECUTIONS, msg)
2903
+
2904
+ def reqExecutionsProtoBuf(self, executionRequestProto: ExecutionRequestProto):
2905
+ serializedString = executionRequestProto.SerializeToString()
2906
+
2907
+ self.sendMsgProtoBuf(OUT.REQ_EXECUTIONS + PROTOBUF_MSG_ID, serializedString)
2908
+
2909
+ #########################################################################
2910
+ # Contract Details
2911
+ #########################################################################
2912
+
2913
+ def reqContractDetails(self, reqId: int, contract: Contract):
2914
+ """Call this function to download all details for a particular
2915
+ underlying. The contract details will be received via the contractDetails()
2916
+ function on the EWrapper.
2917
+
2918
+ reqId:int - The ID of the data request. Ensures that responses are
2919
+ make_fieldatched to requests if several requests are in process.
2920
+ contract:Contract - The summary description of the contract being looked
2921
+ up."""
2922
+
2923
+ self.logRequest(current_fn_name(), vars())
2924
+
2925
+ if not self.isConnected():
2926
+ self.wrapper.error(NO_VALID_ID, currentTimeMillis(), NOT_CONNECTED.code(), NOT_CONNECTED.msg())
2927
+ return
2928
+
2929
+ if self.serverVersion() < MIN_SERVER_VER_SEC_ID_TYPE:
2930
+ if contract.secIdType or contract.secId:
2931
+ self.wrapper.error(
2932
+ reqId,
2933
+ currentTimeMillis(),
2934
+ UPDATE_TWS.code(),
2935
+ UPDATE_TWS.msg()
2936
+ + " It does not support secIdType and secId parameters.",
2937
+ )
2938
+ return
2939
+
2940
+ if self.serverVersion() < MIN_SERVER_VER_TRADING_CLASS:
2941
+ if contract.tradingClass:
2942
+ self.wrapper.error(
2943
+ reqId,
2944
+ currentTimeMillis(),
2945
+ UPDATE_TWS.code(),
2946
+ UPDATE_TWS.msg()
2947
+ + " It does not support tradingClass parameter in reqContractDetails.",
2948
+ )
2949
+ return
2950
+
2951
+ if self.serverVersion() < MIN_SERVER_VER_LINKING:
2952
+ if contract.primaryExchange:
2953
+ self.wrapper.error(
2954
+ reqId,
2955
+ currentTimeMillis(),
2956
+ UPDATE_TWS.code(),
2957
+ UPDATE_TWS.msg()
2958
+ + " It does not support primaryExchange parameter in reqContractDetails.",
2959
+ )
2960
+ return
2961
+
2962
+ if self.serverVersion() < MIN_SERVER_VER_BOND_ISSUERID:
2963
+ if contract.issuerId:
2964
+ self.wrapper.error(
2965
+ reqId,
2966
+ currentTimeMillis(),
2967
+ UPDATE_TWS.code(),
2968
+ UPDATE_TWS.msg()
2969
+ + " It does not support issuerId parameter in reqContractDetails.",
2970
+ )
2971
+ return
2972
+
2973
+ try:
2974
+ VERSION = 8
2975
+
2976
+ # send req mkt data msg
2977
+ flds = []
2978
+ flds += [make_field(VERSION)]
2979
+
2980
+ if self.serverVersion() >= MIN_SERVER_VER_CONTRACT_DATA_CHAIN:
2981
+ flds += [
2982
+ make_field(reqId),
2983
+ ]
2984
+
2985
+ # send contract fields
2986
+ flds += [
2987
+ make_field(contract.conId), # srv v37 and above
2988
+ make_field(contract.symbol),
2989
+ make_field(contract.secType),
2990
+ make_field(contract.lastTradeDateOrContractMonth),
2991
+ make_field_handle_empty(contract.strike),
2992
+ make_field(contract.right),
2993
+ make_field(contract.multiplier),
2994
+ ] # srv v15 and above
2995
+
2996
+ if self.serverVersion() >= MIN_SERVER_VER_PRIMARYEXCH:
2997
+ flds += [
2998
+ make_field(contract.exchange),
2999
+ make_field(contract.primaryExchange),
3000
+ ]
3001
+ elif self.serverVersion() >= MIN_SERVER_VER_LINKING:
3002
+ if contract.primaryExchange and (
3003
+ contract.exchange == "BEST" or contract.exchange == "SMART"
3004
+ ):
3005
+ flds += [
3006
+ make_field(contract.exchange + ":" + contract.primaryExchange),
3007
+ ]
3008
+ else:
3009
+ flds += [
3010
+ make_field(contract.exchange),
3011
+ ]
3012
+
3013
+ flds += [make_field(contract.currency), make_field(contract.localSymbol)]
3014
+ if self.serverVersion() >= MIN_SERVER_VER_TRADING_CLASS:
3015
+ flds += [
3016
+ make_field(contract.tradingClass),
3017
+ ]
3018
+ flds += [
3019
+ make_field(contract.includeExpired),
3020
+ ] # srv v31 and above
3021
+
3022
+ if self.serverVersion() >= MIN_SERVER_VER_SEC_ID_TYPE:
3023
+ flds += [make_field(contract.secIdType), make_field(contract.secId)]
3024
+
3025
+ if self.serverVersion() >= MIN_SERVER_VER_BOND_ISSUERID:
3026
+ flds += [
3027
+ make_field(contract.issuerId),
3028
+ ]
3029
+
3030
+ msg = "".join(flds)
3031
+
3032
+ except ClientException as ex:
3033
+ self.wrapper.error(reqId, currentTimeMillis(), ex.code, ex.msg + ex.text)
3034
+ return
3035
+
3036
+ self.sendMsg(OUT.REQ_CONTRACT_DATA, msg)
3037
+
3038
+ #########################################################################
3039
+ # Market Depth
3040
+ #########################################################################
3041
+
3042
+ def reqMktDepthExchanges(self):
3043
+ self.logRequest(current_fn_name(), vars())
3044
+
3045
+ if not self.isConnected():
3046
+ self.wrapper.error(NO_VALID_ID, currentTimeMillis(), NOT_CONNECTED.code(), NOT_CONNECTED.msg())
3047
+ return
3048
+
3049
+ if self.serverVersion() < MIN_SERVER_VER_REQ_MKT_DEPTH_EXCHANGES:
3050
+ self.wrapper.error(
3051
+ NO_VALID_ID,
3052
+ currentTimeMillis(),
3053
+ UPDATE_TWS.code(),
3054
+ UPDATE_TWS.msg()
3055
+ + " It does not support market depth exchanges request.",
3056
+ )
3057
+ return
3058
+
3059
+ self.sendMsg(OUT.REQ_MKT_DEPTH_EXCHANGES, "")
3060
+
3061
+ def reqMktDepth(
3062
+ self,
3063
+ reqId: TickerId,
3064
+ contract: Contract,
3065
+ numRows: int,
3066
+ isSmartDepth: bool,
3067
+ mktDepthOptions: TagValueList,
3068
+ ):
3069
+ """Call this function to request market depth for a specific
3070
+ contract. The market depth will be returned by the updateMktDepth() and
3071
+ updateMktDepthL2() events.
3072
+
3073
+ Requests the contract's market depth (order book). Note this request must be
3074
+ direct-routed to an exchange and not smart-routed. The number of simultaneous
3075
+ market depth requests allowed in an account is calculated based on a formula
3076
+ that looks at an accounts' equity, commission and fees, and quote booster packs.
3077
+
3078
+ reqId:TickerId - The ticker id. Must be a unique value. When the market
3079
+ depth data returns, it will be identified by this tag. This is
3080
+ also used when canceling the market depth
3081
+ contract:Contact - This structure contains a description of the contract
3082
+ for which market depth data is being requested.
3083
+ numRows:int - Specifies the numRowsumber of market depth rows to display.
3084
+ isSmartDepth:bool - specifies SMART depth request
3085
+ mktDepthOptions:TagValueList - For internal use only. Use default value
3086
+ XYZ."""
3087
+
3088
+ self.logRequest(current_fn_name(), vars())
3089
+
3090
+ if not self.isConnected():
3091
+ self.wrapper.error(NO_VALID_ID, currentTimeMillis(), NOT_CONNECTED.code(), NOT_CONNECTED.msg())
3092
+ return
3093
+
3094
+ if self.serverVersion() < MIN_SERVER_VER_TRADING_CLASS:
3095
+ if contract.tradingClass or contract.conId > 0:
3096
+ self.wrapper.error(
3097
+ reqId,
3098
+ currentTimeMillis(),
3099
+ UPDATE_TWS.code(),
3100
+ UPDATE_TWS.msg()
3101
+ + " It does not support conId and tradingClass parameters in reqMktDepth.",
3102
+ )
3103
+ return
3104
+
3105
+ if self.serverVersion() < MIN_SERVER_VER_SMART_DEPTH and isSmartDepth:
3106
+ self.wrapper.error(
3107
+ reqId,
3108
+ currentTimeMillis(),
3109
+ UPDATE_TWS.code(),
3110
+ UPDATE_TWS.msg() + " It does not support SMART depth request.",
3111
+ )
3112
+ return
3113
+
3114
+ if (
3115
+ self.serverVersion() < MIN_SERVER_VER_MKT_DEPTH_PRIM_EXCHANGE
3116
+ and contract.primaryExchange
3117
+ ):
3118
+ self.wrapper.error(
3119
+ reqId,
3120
+ currentTimeMillis(),
3121
+ UPDATE_TWS.code(),
3122
+ UPDATE_TWS.msg()
3123
+ + " It does not support primaryExchange parameter in reqMktDepth.",
3124
+ )
3125
+ return
3126
+
3127
+ try:
3128
+ VERSION = 5
3129
+
3130
+ # send req mkt depth msg
3131
+ flds = []
3132
+ flds += [
3133
+ make_field(VERSION),
3134
+ make_field(reqId),
3135
+ ]
3136
+
3137
+ # send contract fields
3138
+ if self.serverVersion() >= MIN_SERVER_VER_TRADING_CLASS:
3139
+ flds += [
3140
+ make_field(contract.conId),
3141
+ ]
3142
+ flds += [
3143
+ make_field(contract.symbol),
3144
+ make_field(contract.secType),
3145
+ make_field(contract.lastTradeDateOrContractMonth),
3146
+ make_field_handle_empty(contract.strike),
3147
+ make_field(contract.right),
3148
+ make_field(contract.multiplier), # srv v15 and above
3149
+ make_field(contract.exchange),
3150
+ ]
3151
+ if self.serverVersion() >= MIN_SERVER_VER_MKT_DEPTH_PRIM_EXCHANGE:
3152
+ flds += [
3153
+ make_field(contract.primaryExchange),
3154
+ ]
3155
+ flds += [make_field(contract.currency), make_field(contract.localSymbol)]
3156
+ if self.serverVersion() >= MIN_SERVER_VER_TRADING_CLASS:
3157
+ flds += [
3158
+ make_field(contract.tradingClass),
3159
+ ]
3160
+
3161
+ flds += [
3162
+ make_field(numRows),
3163
+ ] # srv v19 and above
3164
+
3165
+ if self.serverVersion() >= MIN_SERVER_VER_SMART_DEPTH:
3166
+ flds += [
3167
+ make_field(isSmartDepth),
3168
+ ]
3169
+
3170
+ # send mktDepthOptions parameter
3171
+ if self.serverVersion() >= MIN_SERVER_VER_LINKING:
3172
+ # current doc says this part if for "internal use only" -> won't support it
3173
+ if mktDepthOptions:
3174
+ raise NotImplementedError("not supported")
3175
+ mktDataOptionsStr = ""
3176
+ flds += [
3177
+ make_field(mktDataOptionsStr),
3178
+ ]
3179
+
3180
+ msg = "".join(flds)
3181
+
3182
+ except ClientException as ex:
3183
+ self.wrapper.error(reqId, currentTimeMillis(), ex.code, ex.msg + ex.text)
3184
+ return
3185
+
3186
+ self.sendMsg(OUT.REQ_MKT_DEPTH, msg)
3187
+
3188
+ def cancelMktDepth(self, reqId: TickerId, isSmartDepth: bool):
3189
+ """After calling this function, market depth data for the specified id
3190
+ will stop flowing.
3191
+
3192
+ reqId:TickerId - The ID that was specified in the call to
3193
+ reqMktDepth().
3194
+ isSmartDepth:bool - specifies SMART depth request"""
3195
+
3196
+ self.logRequest(current_fn_name(), vars())
3197
+
3198
+ if not self.isConnected():
3199
+ self.wrapper.error(NO_VALID_ID, currentTimeMillis(), NOT_CONNECTED.code(), NOT_CONNECTED.msg())
3200
+ return
3201
+
3202
+ if self.serverVersion() < MIN_SERVER_VER_SMART_DEPTH and isSmartDepth:
3203
+ self.wrapper.error(
3204
+ reqId,
3205
+ currentTimeMillis(),
3206
+ UPDATE_TWS.code(),
3207
+ UPDATE_TWS.msg() + " It does not support SMART depth cancel.",
3208
+ )
3209
+ return
3210
+
3211
+ VERSION = 1
3212
+
3213
+ # send cancel mkt depth msg
3214
+ flds = []
3215
+ flds += [
3216
+ make_field(VERSION),
3217
+ make_field(reqId),
3218
+ ]
3219
+
3220
+ if self.serverVersion() >= MIN_SERVER_VER_SMART_DEPTH:
3221
+ flds += [make_field(isSmartDepth)]
3222
+
3223
+ msg = "".join(flds)
3224
+
3225
+ self.sendMsg(OUT.CANCEL_MKT_DEPTH, msg)
3226
+
3227
+ #########################################################################
3228
+ # News Bulletins
3229
+ #########################################################################
3230
+
3231
+ def reqNewsBulletins(self, allMsgs: bool):
3232
+ """Call this function to start receiving news bulletins. Each bulletin
3233
+ will be returned by the updateNewsBulletin() event.
3234
+
3235
+ allMsgs:bool - If set to TRUE, returns all the existing bulletins for
3236
+ the currencyent day and any new ones. If set to FALSE, will only
3237
+ return new bulletins."""
3238
+
3239
+ self.logRequest(current_fn_name(), vars())
3240
+
3241
+ if not self.isConnected():
3242
+ self.wrapper.error(NO_VALID_ID, currentTimeMillis(), NOT_CONNECTED.code(), NOT_CONNECTED.msg())
3243
+ return
3244
+
3245
+ VERSION = 1
3246
+
3247
+ msg = (
3248
+ make_field(VERSION)
3249
+ + make_field(allMsgs)
3250
+ )
3251
+
3252
+ self.sendMsg(OUT.REQ_NEWS_BULLETINS, msg)
3253
+
3254
+ def cancelNewsBulletins(self):
3255
+ """Call this function to stop receiving news bulletins."""
3256
+
3257
+ self.logRequest(current_fn_name(), vars())
3258
+
3259
+ if not self.isConnected():
3260
+ self.wrapper.error(NO_VALID_ID, currentTimeMillis(), NOT_CONNECTED.code(), NOT_CONNECTED.msg())
3261
+ return
3262
+
3263
+ VERSION = 1
3264
+
3265
+ msg = make_field(VERSION)
3266
+
3267
+ self.sendMsg(OUT.CANCEL_NEWS_BULLETINS, msg)
3268
+
3269
+ #########################################################################
3270
+ # Financial Advisors
3271
+ #########################################################################
3272
+
3273
+ def reqManagedAccts(self):
3274
+ """Call this function to request the list of managed accounts. The list
3275
+ will be returned by the managedAccounts() function on the EWrapper.
3276
+
3277
+ Note: This request can only be made when connected to a FA managed account."""
3278
+
3279
+ self.logRequest(current_fn_name(), vars())
3280
+
3281
+ if not self.isConnected():
3282
+ self.wrapper.error(NO_VALID_ID, currentTimeMillis(), NOT_CONNECTED.code(), NOT_CONNECTED.msg())
3283
+ return
3284
+
3285
+ VERSION = 1
3286
+
3287
+ msg = make_field(VERSION)
3288
+
3289
+ return self.sendMsg(OUT.REQ_MANAGED_ACCTS, msg)
3290
+
3291
+ def requestFA(self, faData: FaDataType):
3292
+ """Call this function to request FA configuration information from TWS.
3293
+ The data returns in an XML string via a "receiveFA" ActiveX event.
3294
+
3295
+ faData:FaDataType - Specifies the type of Financial Advisor
3296
+ configuration data beingingg requested. Valid values include:
3297
+ 1 = GROUPS
3298
+ 3 = ACCOUNT ALIASES"""
3299
+
3300
+ self.logRequest(current_fn_name(), vars())
3301
+
3302
+ if not self.isConnected():
3303
+ self.wrapper.error(NO_VALID_ID, currentTimeMillis(), NOT_CONNECTED.code(), NOT_CONNECTED.msg())
3304
+ return
3305
+
3306
+ if self.serverVersion() >= MIN_SERVER_VER_FA_PROFILE_DESUPPORT and faData == 2:
3307
+ self.wrapper.error(
3308
+ NO_VALID_ID,
3309
+ currentTimeMillis(),
3310
+ FA_PROFILE_NOT_SUPPORTED.code(),
3311
+ FA_PROFILE_NOT_SUPPORTED.msg(),
3312
+ )
3313
+ return
3314
+
3315
+ VERSION = 1
3316
+
3317
+ msg = make_field(VERSION) + make_field(int(faData))
3318
+
3319
+ return self.sendMsg(OUT.REQ_FA, msg)
3320
+
3321
+ def replaceFA(self, reqId: TickerId, faData: FaDataType, cxml: str):
3322
+ """Call this function to modify FA configuration information from the
3323
+ API. Note that this can also be done manually in TWS itself.
3324
+
3325
+ reqId:TickerId - request id
3326
+ faData:FaDataType - Specifies the type of Financial Advisor
3327
+ configuration data beingingg requested. Valid values include:
3328
+ 1 = GROUPS
3329
+ 3 = ACCOUNT ALIASES
3330
+ cxml: str - The XML string containing the new FA configuration
3331
+ information."""
3332
+
3333
+ self.logRequest(current_fn_name(), vars())
3334
+
3335
+ if not self.isConnected():
3336
+ self.wrapper.error(reqId, currentTimeMillis(), NOT_CONNECTED.code(), NOT_CONNECTED.msg())
3337
+ return
3338
+
3339
+ if self.serverVersion() >= MIN_SERVER_VER_FA_PROFILE_DESUPPORT and faData == 2:
3340
+ self.wrapper.error(reqId, currentTimeMillis(), FA_PROFILE_NOT_SUPPORTED.code(), FA_PROFILE_NOT_SUPPORTED.msg())
3341
+ return
3342
+
3343
+ try:
3344
+ VERSION = 1
3345
+
3346
+ msg = (
3347
+ make_field(VERSION)
3348
+ + make_field(int(faData))
3349
+ + make_field(cxml)
3350
+ )
3351
+
3352
+ if self.serverVersion() >= MIN_SERVER_VER_REPLACE_FA_END:
3353
+ msg += make_field(reqId)
3354
+
3355
+ except ClientException as ex:
3356
+ self.wrapper.error(NO_VALID_ID, currentTimeMillis(), ex.code, ex.msg + ex.text)
3357
+ return
3358
+
3359
+ return self.sendMsg(OUT.REPLACE_FA, msg)
3360
+
3361
+ #########################################################################
3362
+ # Historical Data
3363
+ #########################################################################
3364
+
3365
+ def reqHistoricalData(
3366
+ self,
3367
+ reqId: TickerId,
3368
+ contract: Contract,
3369
+ endDateTime: str,
3370
+ durationStr: str,
3371
+ barSizeSetting: str,
3372
+ whatToShow: str,
3373
+ useRTH: int,
3374
+ formatDate: int,
3375
+ keepUpToDate: bool,
3376
+ chartOptions: TagValueList,
3377
+ ):
3378
+ """Requests contracts' historical data. When requesting historical data, a
3379
+ finishing time and date is required along with a duration string. The
3380
+ resulting bars will be returned in EWrapper.historicalData()
3381
+
3382
+ reqId:TickerId - The id of the request. Must be a unique value. When the
3383
+ market data returns, it whatToShowill be identified by this tag. This is also
3384
+ used when canceling the market data.
3385
+ contract:Contract - This object contains a description of the contract for which
3386
+ market data is being requested.
3387
+ endDateTime:str - Defines a query end date and time at any point during the past 6 mos.
3388
+ Valid values include any date/time within the past six months in the format:
3389
+ yyyymmdd HH:mm:ss ttt
3390
+
3391
+ where "ttt" is the optional time zone.
3392
+ durationStr:str - Set the query duration up to one week, using a time unit
3393
+ of seconds, days or weeks. Valid values include any integer followed by a space
3394
+ and then S (seconds), D (days) or W (week). If no unit is specified, seconds is used.
3395
+ barSizeSetting:str - Specifies the size of the bars that will be returned (within IB/TWS listimits).
3396
+ Valid values include:
3397
+ 1 sec
3398
+ 5 secs
3399
+ 15 secs
3400
+ 30 secs
3401
+ 1 min
3402
+ 2 mins
3403
+ 3 mins
3404
+ 5 mins
3405
+ 15 mins
3406
+ 30 mins
3407
+ 1 hour
3408
+ 1 day
3409
+ whatToShow:str - Determines the nature of data beinging extracted. Valid values include:
3410
+
3411
+ TRADES
3412
+ MIDPOINT
3413
+ BID
3414
+ ASK
3415
+ BID_ASK
3416
+ HISTORICAL_VOLATILITY
3417
+ OPTION_IMPLIED_VOLATILITY
3418
+ SCHEDULE
3419
+ useRTH:int - Determines whether to return all data available during the requested time span,
3420
+ or only data that falls within regular trading hours. Valid values include:
3421
+
3422
+ 0 - all data is returned even where the market in question was outside its
3423
+ regular trading hours.
3424
+ 1 - only data within the regular trading hours is returned, even if the
3425
+ requested time span falls partially or completely outside the RTH.
3426
+ formatDate: int - Determines the date format applied to returned bars. validd values include:
3427
+
3428
+ 1 - dates applying to bars returned in the format: yyyymmdd{space}{space}hh:mm:dd
3429
+ 2 - dates are returned as a long integer specifying the number of seconds since
3430
+ 1/1/1970 GMT.
3431
+ chartOptions:TagValueList - For internal use only. Use default value XYZ."""
3432
+
3433
+ self.logRequest(current_fn_name(), vars())
3434
+
3435
+ if not self.isConnected():
3436
+ self.wrapper.error(reqId, currentTimeMillis(), NOT_CONNECTED.code(), NOT_CONNECTED.msg())
3437
+ return
3438
+
3439
+ if self.serverVersion() < MIN_SERVER_VER_TRADING_CLASS:
3440
+ if contract.tradingClass or contract.conId > 0:
3441
+ self.wrapper.error(
3442
+ reqId,
3443
+ currentTimeMillis(),
3444
+ UPDATE_TWS.code(),
3445
+ UPDATE_TWS.msg()
3446
+ + " It does not support conId and tradingClass parameters in reqHistoricalData.",
3447
+ )
3448
+ return
3449
+
3450
+ if self.serverVersion() < MIN_SERVER_VER_HISTORICAL_SCHEDULE:
3451
+ if whatToShow == "SCHEDULE":
3452
+ self.wrapper.error(
3453
+ reqId,
3454
+ currentTimeMillis(),
3455
+ UPDATE_TWS.code(),
3456
+ UPDATE_TWS.msg()
3457
+ + " It does not support requesting of historical schedule.",
3458
+ )
3459
+ return
3460
+
3461
+ try:
3462
+ VERSION = 6
3463
+
3464
+ # send req mkt data msg
3465
+ flds = []
3466
+
3467
+ if self.serverVersion() < MIN_SERVER_VER_SYNT_REALTIME_BARS:
3468
+ flds += [
3469
+ make_field(VERSION),
3470
+ ]
3471
+
3472
+ flds += [
3473
+ make_field(reqId),
3474
+ ]
3475
+
3476
+ # send contract fields
3477
+ if self.serverVersion() >= MIN_SERVER_VER_TRADING_CLASS:
3478
+ flds += [
3479
+ make_field(contract.conId),
3480
+ ]
3481
+ flds += [
3482
+ make_field(contract.symbol),
3483
+ make_field(contract.secType),
3484
+ make_field(contract.lastTradeDateOrContractMonth),
3485
+ make_field_handle_empty(contract.strike),
3486
+ make_field(contract.right),
3487
+ make_field(contract.multiplier),
3488
+ make_field(contract.exchange),
3489
+ make_field(contract.primaryExchange),
3490
+ make_field(contract.currency),
3491
+ make_field(contract.localSymbol),
3492
+ ]
3493
+ if self.serverVersion() >= MIN_SERVER_VER_TRADING_CLASS:
3494
+ flds += [
3495
+ make_field(contract.tradingClass),
3496
+ ]
3497
+ flds += [
3498
+ make_field(contract.includeExpired), # srv v31 and above
3499
+ make_field(endDateTime), # srv v20 and above
3500
+ make_field(barSizeSetting), # srv v20 and above
3501
+ make_field(durationStr),
3502
+ make_field(useRTH),
3503
+ make_field(whatToShow),
3504
+ make_field(formatDate),
3505
+ ] # srv v16 and above
3506
+
3507
+ # Send combo legs for BAG requests
3508
+ if contract.secType == "BAG":
3509
+ flds += [
3510
+ make_field(len(contract.comboLegs)),
3511
+ ]
3512
+ for comboLeg in contract.comboLegs:
3513
+ flds += [
3514
+ make_field(comboLeg.conId),
3515
+ make_field(comboLeg.ratio),
3516
+ make_field(comboLeg.action),
3517
+ make_field(comboLeg.exchange),
3518
+ ]
3519
+
3520
+ if self.serverVersion() >= MIN_SERVER_VER_SYNT_REALTIME_BARS:
3521
+ flds += [
3522
+ make_field(keepUpToDate),
3523
+ ]
3524
+
3525
+ # send chartOptions parameter
3526
+ if self.serverVersion() >= MIN_SERVER_VER_LINKING:
3527
+ chartOptionsStr = ""
3528
+ if chartOptions:
3529
+ for tagValue in chartOptions:
3530
+ chartOptionsStr += str(tagValue)
3531
+ flds += [
3532
+ make_field(chartOptionsStr),
3533
+ ]
3534
+
3535
+ msg = "".join(flds)
3536
+
3537
+ except ClientException as ex:
3538
+ self.wrapper.error(reqId, currentTimeMillis(), ex.code, ex.msg + ex.text)
3539
+ return
3540
+
3541
+ self.sendMsg(OUT.REQ_HISTORICAL_DATA, msg)
3542
+
3543
+ def cancelHistoricalData(self, reqId: TickerId):
3544
+ """Used if an internet disconnect has occurred or the results of a query
3545
+ are otherwise delayed and the application is no longer interested in receiving
3546
+ the data.
3547
+
3548
+ reqId:TickerId - The ticker ID. Must be a unique value."""
3549
+
3550
+ self.logRequest(current_fn_name(), vars())
3551
+
3552
+ if not self.isConnected():
3553
+ self.wrapper.error(NO_VALID_ID, currentTimeMillis(), NOT_CONNECTED.code(), NOT_CONNECTED.msg())
3554
+ return
3555
+
3556
+ VERSION = 1
3557
+
3558
+ msg = (
3559
+ make_field(VERSION)
3560
+ + make_field(reqId)
3561
+ )
3562
+
3563
+ self.sendMsg(OUT.CANCEL_HISTORICAL_DATA, msg)
3564
+
3565
+ # Note that formatData parameter affects intraday bars only
3566
+ # 1-day bars always return with date in YYYYMMDD format
3567
+
3568
+ def reqHeadTimeStamp(
3569
+ self,
3570
+ reqId: TickerId,
3571
+ contract: Contract,
3572
+ whatToShow: str,
3573
+ useRTH: int,
3574
+ formatDate: int,
3575
+ ):
3576
+ self.logRequest(current_fn_name(), vars())
3577
+
3578
+ if not self.isConnected():
3579
+ self.wrapper.error(NO_VALID_ID, currentTimeMillis(), NOT_CONNECTED.code(), NOT_CONNECTED.msg())
3580
+ return
3581
+
3582
+ if self.serverVersion() < MIN_SERVER_VER_REQ_HEAD_TIMESTAMP:
3583
+ self.wrapper.error(
3584
+ reqId,
3585
+ currentTimeMillis(),
3586
+ UPDATE_TWS.code(),
3587
+ UPDATE_TWS.msg() + " It does not support head time stamp requests.",
3588
+ )
3589
+ return
3590
+
3591
+ try:
3592
+ flds = []
3593
+ flds += [
3594
+ make_field(reqId),
3595
+ make_field(contract.conId),
3596
+ make_field(contract.symbol),
3597
+ make_field(contract.secType),
3598
+ make_field(contract.lastTradeDateOrContractMonth),
3599
+ make_field_handle_empty(contract.strike),
3600
+ make_field(contract.right),
3601
+ make_field(contract.multiplier),
3602
+ make_field(contract.exchange),
3603
+ make_field(contract.primaryExchange),
3604
+ make_field(contract.currency),
3605
+ make_field(contract.localSymbol),
3606
+ make_field(contract.tradingClass),
3607
+ make_field(contract.includeExpired),
3608
+ make_field(useRTH),
3609
+ make_field(whatToShow),
3610
+ make_field(formatDate),
3611
+ ]
3612
+
3613
+ msg = "".join(flds)
3614
+
3615
+ except ClientException as ex:
3616
+ self.wrapper.error(reqId, currentTimeMillis(), ex.code, ex.msg + ex.text)
3617
+ return
3618
+
3619
+ self.sendMsg(OUT.REQ_HEAD_TIMESTAMP, msg)
3620
+
3621
+ def cancelHeadTimeStamp(self, reqId: TickerId):
3622
+ self.logRequest(current_fn_name(), vars())
3623
+
3624
+ if not self.isConnected():
3625
+ self.wrapper.error(NO_VALID_ID, currentTimeMillis(), NOT_CONNECTED.code(), NOT_CONNECTED.msg())
3626
+ return
3627
+
3628
+ if self.serverVersion() < MIN_SERVER_VER_CANCEL_HEADTIMESTAMP:
3629
+ self.wrapper.error(
3630
+ reqId,
3631
+ currentTimeMillis(),
3632
+ UPDATE_TWS.code(),
3633
+ UPDATE_TWS.msg() + " It does not support head time stamp requests.",
3634
+ )
3635
+ return
3636
+
3637
+ flds = []
3638
+ flds += [make_field(reqId)]
3639
+
3640
+ msg = "".join(flds)
3641
+ self.sendMsg(OUT.CANCEL_HEAD_TIMESTAMP, msg)
3642
+
3643
+ def reqHistogramData(
3644
+ self, tickerId: int, contract: Contract, useRTH: bool, timePeriod: str
3645
+ ):
3646
+ self.logRequest(current_fn_name(), vars())
3647
+
3648
+ if not self.isConnected():
3649
+ self.wrapper.error(NO_VALID_ID, currentTimeMillis(), NOT_CONNECTED.code(), NOT_CONNECTED.msg())
3650
+ return
3651
+
3652
+ if self.serverVersion() < MIN_SERVER_VER_REQ_HISTOGRAM:
3653
+ self.wrapper.error(
3654
+ NO_VALID_ID,
3655
+ currentTimeMillis(),
3656
+ UPDATE_TWS.code(),
3657
+ UPDATE_TWS.msg() + " It does not support histogram requests..",
3658
+ )
3659
+ return
3660
+
3661
+ try:
3662
+ flds = []
3663
+ flds += [
3664
+ make_field(tickerId),
3665
+ make_field(contract.conId),
3666
+ make_field(contract.symbol),
3667
+ make_field(contract.secType),
3668
+ make_field(contract.lastTradeDateOrContractMonth),
3669
+ make_field_handle_empty(contract.strike),
3670
+ make_field(contract.right),
3671
+ make_field(contract.multiplier),
3672
+ make_field(contract.exchange),
3673
+ make_field(contract.primaryExchange),
3674
+ make_field(contract.currency),
3675
+ make_field(contract.localSymbol),
3676
+ make_field(contract.tradingClass),
3677
+ make_field(contract.includeExpired),
3678
+ make_field(useRTH),
3679
+ make_field(timePeriod),
3680
+ ]
3681
+
3682
+ msg = "".join(flds)
3683
+
3684
+ except ClientException as ex:
3685
+ self.wrapper.error(tickerId, currentTimeMillis(), ex.code, ex.msg + ex.text)
3686
+ return
3687
+
3688
+ self.sendMsg(OUT.REQ_HISTOGRAM_DATA, msg)
3689
+
3690
+ def cancelHistogramData(self, tickerId: int):
3691
+ self.logRequest(current_fn_name(), vars())
3692
+
3693
+ if not self.isConnected():
3694
+ self.wrapper.error(NO_VALID_ID, currentTimeMillis(), NOT_CONNECTED.code(), NOT_CONNECTED.msg())
3695
+ return
3696
+
3697
+ if self.serverVersion() < MIN_SERVER_VER_REQ_HISTOGRAM:
3698
+ self.wrapper.error(
3699
+ NO_VALID_ID,
3700
+ currentTimeMillis(),
3701
+ UPDATE_TWS.code(),
3702
+ UPDATE_TWS.msg() + " It does not support histogram requests..",
3703
+ )
3704
+ return
3705
+
3706
+ msg = make_field(tickerId)
3707
+
3708
+ self.sendMsg(OUT.CANCEL_HISTOGRAM_DATA, msg)
3709
+
3710
+ def reqHistoricalTicks(
3711
+ self,
3712
+ reqId: int,
3713
+ contract: Contract,
3714
+ startDateTime: str,
3715
+ endDateTime: str,
3716
+ numberOfTicks: int,
3717
+ whatToShow: str,
3718
+ useRth: int,
3719
+ ignoreSize: bool,
3720
+ miscOptions: TagValueList,
3721
+ ):
3722
+ self.logRequest(current_fn_name(), vars())
3723
+
3724
+ if not self.isConnected():
3725
+ self.wrapper.error(NO_VALID_ID, currentTimeMillis(), NOT_CONNECTED.code(), NOT_CONNECTED.msg())
3726
+ return
3727
+
3728
+ if self.serverVersion() < MIN_SERVER_VER_HISTORICAL_TICKS:
3729
+ self.wrapper.error(
3730
+ NO_VALID_ID,
3731
+ currentTimeMillis(),
3732
+ UPDATE_TWS.code(),
3733
+ UPDATE_TWS.msg() + " It does not support historical ticks requests..",
3734
+ )
3735
+ return
3736
+
3737
+ try:
3738
+ flds = []
3739
+ flds += [
3740
+ make_field(reqId),
3741
+ make_field(contract.conId),
3742
+ make_field(contract.symbol),
3743
+ make_field(contract.secType),
3744
+ make_field(contract.lastTradeDateOrContractMonth),
3745
+ make_field_handle_empty(contract.strike),
3746
+ make_field(contract.right),
3747
+ make_field(contract.multiplier),
3748
+ make_field(contract.exchange),
3749
+ make_field(contract.primaryExchange),
3750
+ make_field(contract.currency),
3751
+ make_field(contract.localSymbol),
3752
+ make_field(contract.tradingClass),
3753
+ make_field(contract.includeExpired),
3754
+ make_field(startDateTime),
3755
+ make_field(endDateTime),
3756
+ make_field(numberOfTicks),
3757
+ make_field(whatToShow),
3758
+ make_field(useRth),
3759
+ make_field(ignoreSize),
3760
+ ]
3761
+
3762
+ miscOptionsString = ""
3763
+ if miscOptions:
3764
+ for tagValue in miscOptions:
3765
+ miscOptionsString += str(tagValue)
3766
+ flds += [
3767
+ make_field(miscOptionsString),
3768
+ ]
3769
+
3770
+ msg = "".join(flds)
3771
+
3772
+ except ClientException as ex:
3773
+ self.wrapper.error(reqId, currentTimeMillis(), ex.code, ex.msg + ex.text)
3774
+ return
3775
+
3776
+ self.sendMsg(OUT.REQ_HISTORICAL_TICKS, msg)
3777
+
3778
+ #########################################################################
3779
+ # Market Scanners
3780
+ #########################################################################
3781
+
3782
+ def reqScannerParameters(self):
3783
+ """Requests an XML string that describes all possible scanner queries."""
3784
+
3785
+ self.logRequest(current_fn_name(), vars())
3786
+
3787
+ if not self.isConnected():
3788
+ self.wrapper.error(NO_VALID_ID, currentTimeMillis(), NOT_CONNECTED.code(), NOT_CONNECTED.msg())
3789
+ return
3790
+
3791
+ VERSION = 1
3792
+
3793
+ msg = make_field(VERSION)
3794
+
3795
+ self.sendMsg(OUT.REQ_SCANNER_PARAMETERS, msg)
3796
+
3797
+ def reqScannerSubscription(
3798
+ self,
3799
+ reqId: int,
3800
+ subscription: ScannerSubscription,
3801
+ scannerSubscriptionOptions: TagValueList,
3802
+ scannerSubscriptionFilterOptions: TagValueList,
3803
+ ):
3804
+ """reqId:int - The ticker ID. Must be a unique value.
3805
+ scannerSubscription:ScannerSubscription - This structure contains
3806
+ possible parameters used to filter results.
3807
+ scannerSubscriptionOptions:TagValueList - For internal use only.
3808
+ Use default value XYZ."""
3809
+
3810
+ self.logRequest(current_fn_name(), vars())
3811
+
3812
+ if not self.isConnected():
3813
+ self.wrapper.error(NO_VALID_ID, currentTimeMillis(), NOT_CONNECTED.code(), NOT_CONNECTED.msg())
3814
+ return
3815
+
3816
+ if (
3817
+ self.serverVersion() < MIN_SERVER_VER_SCANNER_GENERIC_OPTS
3818
+ and scannerSubscriptionFilterOptions is not None
3819
+ ):
3820
+ self.wrapper.error(
3821
+ NO_VALID_ID,
3822
+ currentTimeMillis(),
3823
+ UPDATE_TWS.code(),
3824
+ UPDATE_TWS.msg()
3825
+ + " It does not support API scanner subscription generic filter options",
3826
+ )
3827
+ return
3828
+
3829
+ try:
3830
+ VERSION = 4
3831
+
3832
+ flds = []
3833
+
3834
+ if self.serverVersion() < MIN_SERVER_VER_SCANNER_GENERIC_OPTS:
3835
+ flds += [make_field(VERSION)]
3836
+
3837
+ flds += [
3838
+ make_field(reqId),
3839
+ make_field_handle_empty(subscription.numberOfRows),
3840
+ make_field(subscription.instrument),
3841
+ make_field(subscription.locationCode),
3842
+ make_field(subscription.scanCode),
3843
+ make_field_handle_empty(subscription.abovePrice),
3844
+ make_field_handle_empty(subscription.belowPrice),
3845
+ make_field_handle_empty(subscription.aboveVolume),
3846
+ make_field_handle_empty(subscription.marketCapAbove),
3847
+ make_field_handle_empty(subscription.marketCapBelow),
3848
+ make_field(subscription.moodyRatingAbove),
3849
+ make_field(subscription.moodyRatingBelow),
3850
+ make_field(subscription.spRatingAbove),
3851
+ make_field(subscription.spRatingBelow),
3852
+ make_field(subscription.maturityDateAbove),
3853
+ make_field(subscription.maturityDateBelow),
3854
+ make_field_handle_empty(subscription.couponRateAbove),
3855
+ make_field_handle_empty(subscription.couponRateBelow),
3856
+ make_field(subscription.excludeConvertible),
3857
+ make_field_handle_empty(
3858
+ subscription.averageOptionVolumeAbove
3859
+ ), # srv v25 and above
3860
+ make_field(subscription.scannerSettingPairs), # srv v25 and above
3861
+ make_field(subscription.stockTypeFilter),
3862
+ ] # srv v27 and above
3863
+
3864
+ # send scannerSubscriptionFilterOptions parameter
3865
+ if self.serverVersion() >= MIN_SERVER_VER_SCANNER_GENERIC_OPTS:
3866
+ scannerSubscriptionFilterOptionsStr = ""
3867
+ if scannerSubscriptionFilterOptions:
3868
+ for tagValueOpt in scannerSubscriptionFilterOptions:
3869
+ scannerSubscriptionFilterOptionsStr += str(tagValueOpt)
3870
+ flds += [make_field(scannerSubscriptionFilterOptionsStr)]
3871
+
3872
+ # send scannerSubscriptionOptions parameter
3873
+ if self.serverVersion() >= MIN_SERVER_VER_LINKING:
3874
+ scannerSubscriptionOptionsStr = ""
3875
+ if scannerSubscriptionOptions:
3876
+ for tagValueOpt in scannerSubscriptionOptions:
3877
+ scannerSubscriptionOptionsStr += str(tagValueOpt)
3878
+ flds += [
3879
+ make_field(scannerSubscriptionOptionsStr),
3880
+ ]
3881
+
3882
+ msg = "".join(flds)
3883
+
3884
+ except ClientException as ex:
3885
+ self.wrapper.error(reqId, currentTimeMillis(), ex.code, ex.msg + ex.text)
3886
+ return
3887
+
3888
+ self.sendMsg(OUT.REQ_SCANNER_SUBSCRIPTION, msg)
3889
+
3890
+ def cancelScannerSubscription(self, reqId: int):
3891
+ """reqId:int - The ticker ID. Must be a unique value."""
3892
+
3893
+ self.logRequest(current_fn_name(), vars())
3894
+
3895
+ if not self.isConnected():
3896
+ self.wrapper.error(NO_VALID_ID, currentTimeMillis(), NOT_CONNECTED.code(), NOT_CONNECTED.msg())
3897
+ return
3898
+
3899
+ VERSION = 1
3900
+
3901
+ msg = (
3902
+ make_field(VERSION)
3903
+ + make_field(reqId)
3904
+ )
3905
+
3906
+ self.sendMsg(OUT.CANCEL_SCANNER_SUBSCRIPTION, msg)
3907
+
3908
+ #########################################################################
3909
+ # Real Time Bars
3910
+ #########################################################################
3911
+
3912
+ def reqRealTimeBars(
3913
+ self,
3914
+ reqId: TickerId,
3915
+ contract: Contract,
3916
+ barSize: int,
3917
+ whatToShow: str,
3918
+ useRTH: bool,
3919
+ realTimeBarsOptions: TagValueList,
3920
+ ):
3921
+ """Call the reqRealTimeBars() function to start receiving real time bar
3922
+ results through the realtimeBar() EWrapper function.
3923
+
3924
+ reqId:TickerId - The id for the request. Must be a unique value. When the
3925
+ data is received, it will be identified by this id. This is also
3926
+ used when canceling the request.
3927
+ contract:Contract - This object contains a description of the contract
3928
+ for which real time bars are being requested
3929
+ barSize:int - Currently only 5 second bars are supported, if any other
3930
+ value is used, an exception will be thrown.
3931
+ whatToShow:str - Determines the nature of the data extracted. Valid
3932
+ values include:
3933
+ TRADES
3934
+ BID
3935
+ ASK
3936
+ MIDPOINT
3937
+ useRTH:bool - Regular Trading Hours only. Valid values include:
3938
+ 0 = all data available during the time span requested is returned,
3939
+ including time intervals when the market in question was
3940
+ outside of regular trading hours.
3941
+ 1 = only data within the regular trading hours for the product
3942
+ requested is returned, even if the time span falls
3943
+ partially or completely outside.
3944
+ realTimeBarOptions:TagValueList - For internal use only. Use default value XYZ.
3945
+ """
3946
+
3947
+ self.logRequest(current_fn_name(), vars())
3948
+
3949
+ if not self.isConnected():
3950
+ self.wrapper.error(NO_VALID_ID, currentTimeMillis(), NOT_CONNECTED.code(), NOT_CONNECTED.msg())
3951
+ return
3952
+
3953
+ if self.serverVersion() < MIN_SERVER_VER_TRADING_CLASS:
3954
+ if contract.tradingClass:
3955
+ self.wrapper.error(
3956
+ reqId,
3957
+ currentTimeMillis(),
3958
+ UPDATE_TWS.code(),
3959
+ UPDATE_TWS.msg()
3960
+ + " It does not support conId and tradingClass parameter in reqRealTimeBars.",
3961
+ )
3962
+ return
3963
+
3964
+ try:
3965
+ VERSION = 3
3966
+
3967
+ flds = []
3968
+ flds += [
3969
+ make_field(VERSION),
3970
+ make_field(reqId),
3971
+ ]
3972
+
3973
+ # send contract fields
3974
+ if self.serverVersion() >= MIN_SERVER_VER_TRADING_CLASS:
3975
+ flds += [
3976
+ make_field(contract.conId),
3977
+ ]
3978
+ flds += [
3979
+ make_field(contract.symbol),
3980
+ make_field(contract.secType),
3981
+ make_field(contract.lastTradeDateOrContractMonth),
3982
+ make_field_handle_empty(contract.strike),
3983
+ make_field(contract.right),
3984
+ make_field(contract.multiplier),
3985
+ make_field(contract.exchange),
3986
+ make_field(contract.primaryExchange),
3987
+ make_field(contract.currency),
3988
+ make_field(contract.localSymbol),
3989
+ ]
3990
+ if self.serverVersion() >= MIN_SERVER_VER_TRADING_CLASS:
3991
+ flds += [
3992
+ make_field(contract.tradingClass),
3993
+ ]
3994
+ flds += [make_field(barSize), make_field(whatToShow), make_field(useRTH)]
3995
+
3996
+ # send realTimeBarsOptions parameter
3997
+ if self.serverVersion() >= MIN_SERVER_VER_LINKING:
3998
+ realTimeBarsOptionsStr = ""
3999
+ if realTimeBarsOptions:
4000
+ for tagValueOpt in realTimeBarsOptions:
4001
+ realTimeBarsOptionsStr += str(tagValueOpt)
4002
+ flds += [
4003
+ make_field(realTimeBarsOptionsStr),
4004
+ ]
4005
+
4006
+ msg = "".join(flds)
4007
+
4008
+ except ClientException as ex:
4009
+ self.wrapper.error(reqId, currentTimeMillis(), ex.code, ex.msg + ex.text)
4010
+ return
4011
+
4012
+ self.sendMsg(OUT.REQ_REAL_TIME_BARS, msg)
4013
+
4014
+ def cancelRealTimeBars(self, reqId: TickerId):
4015
+ """Call the cancelRealTimeBars() function to stop receiving real time bar results.
4016
+
4017
+ reqId:TickerId - The id that was specified in the call to reqRealTimeBars()."""
4018
+
4019
+ self.logRequest(current_fn_name(), vars())
4020
+
4021
+ if not self.isConnected():
4022
+ self.wrapper.error(reqId, currentTimeMillis(), NOT_CONNECTED.code(), NOT_CONNECTED.msg())
4023
+ return
4024
+
4025
+ VERSION = 1
4026
+
4027
+ # send req mkt data msg
4028
+ flds = []
4029
+ flds += [
4030
+ make_field(VERSION),
4031
+ make_field(reqId),
4032
+ ]
4033
+
4034
+ msg = "".join(flds)
4035
+ self.sendMsg(OUT.CANCEL_REAL_TIME_BARS, msg)
4036
+
4037
+ #########################################################################
4038
+ # Fundamental Data
4039
+ #########################################################################
4040
+
4041
+ def reqFundamentalData(
4042
+ self,
4043
+ reqId: TickerId,
4044
+ contract: Contract,
4045
+ reportType: str,
4046
+ fundamentalDataOptions: TagValueList,
4047
+ ):
4048
+ """Call this function to receive fundamental data for
4049
+ stocks. The appropriate market data subscription must be set up in
4050
+ Account Management before you can receive this data.
4051
+ Fundamental data will be returned at EWrapper.fundamentalData().
4052
+
4053
+ reqFundamentalData() can handle conid specified in the Contract object,
4054
+ but not tradingClass or multiplier. This is because reqFundamentalData()
4055
+ is used only for stocks and stocks do not have a multiplier and
4056
+ trading class.
4057
+
4058
+ reqId:tickerId - The ID of the data request. Ensures that responses are
4059
+ matched to requests if several requests are in process.
4060
+ contract:Contract - This structure contains a description of the
4061
+ contract for which fundamental data is being requested.
4062
+ reportType:str - One of the following XML reports:
4063
+ ReportSnapshot (company overview)
4064
+ ReportsFinSummary (financial summary)
4065
+ ReportRatios (financial ratios)
4066
+ ReportsFinStatements (financial statements)
4067
+ RESC (analyst estimates)"""
4068
+
4069
+ self.logRequest(current_fn_name(), vars())
4070
+
4071
+ if not self.isConnected():
4072
+ self.wrapper.error(NO_VALID_ID, currentTimeMillis(), NOT_CONNECTED.code(), NOT_CONNECTED.msg())
4073
+ return
4074
+
4075
+ try:
4076
+ VERSION = 2
4077
+
4078
+ if self.serverVersion() < MIN_SERVER_VER_FUNDAMENTAL_DATA:
4079
+ self.wrapper.error(
4080
+ NO_VALID_ID,
4081
+ currentTimeMillis(),
4082
+ UPDATE_TWS.code(),
4083
+ UPDATE_TWS.msg()
4084
+ + " It does not support fundamental data request.",
4085
+ )
4086
+ return
4087
+
4088
+ if self.serverVersion() < MIN_SERVER_VER_TRADING_CLASS:
4089
+ self.wrapper.error(
4090
+ NO_VALID_ID,
4091
+ currentTimeMillis(),
4092
+ UPDATE_TWS.code(),
4093
+ UPDATE_TWS.msg()
4094
+ + " It does not support conId parameter in reqFundamentalData.",
4095
+ )
4096
+ return
4097
+
4098
+ flds = []
4099
+ flds += [
4100
+ make_field(VERSION),
4101
+ make_field(reqId),
4102
+ ]
4103
+
4104
+ # send contract fields
4105
+ if self.serverVersion() >= MIN_SERVER_VER_TRADING_CLASS:
4106
+ flds += [
4107
+ make_field(contract.conId),
4108
+ ]
4109
+ flds += [
4110
+ make_field(contract.symbol),
4111
+ make_field(contract.secType),
4112
+ make_field(contract.exchange),
4113
+ make_field(contract.primaryExchange),
4114
+ make_field(contract.currency),
4115
+ make_field(contract.localSymbol),
4116
+ make_field(reportType),
4117
+ ]
4118
+
4119
+ if self.serverVersion() >= MIN_SERVER_VER_LINKING:
4120
+ fundDataOptStr = ""
4121
+ tagValuesCount = (
4122
+ len(fundamentalDataOptions) if fundamentalDataOptions else 0
4123
+ )
4124
+ if fundamentalDataOptions:
4125
+ for fundDataOption in fundamentalDataOptions:
4126
+ fundDataOptStr += str(fundDataOption)
4127
+ flds += [make_field(tagValuesCount), make_field(fundDataOptStr)]
4128
+
4129
+ msg = "".join(flds)
4130
+
4131
+ except ClientException as ex:
4132
+ self.wrapper.error(reqId, currentTimeMillis(), ex.code, ex.msg + ex.text)
4133
+ return
4134
+
4135
+ self.sendMsg(OUT.REQ_FUNDAMENTAL_DATA, msg)
4136
+
4137
+ def cancelFundamentalData(self, reqId: TickerId):
4138
+ """Call this function to stop receiving fundamental data.
4139
+
4140
+ reqId:TickerId - The ID of the data request."""
4141
+
4142
+ self.logRequest(current_fn_name(), vars())
4143
+
4144
+ if not self.isConnected():
4145
+ self.wrapper.error(NO_VALID_ID, currentTimeMillis(), NOT_CONNECTED.code(), NOT_CONNECTED.msg())
4146
+ return
4147
+
4148
+ if self.serverVersion() < MIN_SERVER_VER_FUNDAMENTAL_DATA:
4149
+ self.wrapper.error(
4150
+ NO_VALID_ID,
4151
+ currentTimeMillis(),
4152
+ UPDATE_TWS.code(),
4153
+ UPDATE_TWS.msg() + " It does not support fundamental data request.",
4154
+ )
4155
+ return
4156
+
4157
+ VERSION = 1
4158
+
4159
+ msg = (
4160
+ make_field(VERSION)
4161
+ + make_field(reqId)
4162
+ )
4163
+
4164
+ self.sendMsg(OUT.CANCEL_FUNDAMENTAL_DATA, msg)
4165
+
4166
+ ########################################################################
4167
+ # News
4168
+ #########################################################################
4169
+
4170
+ def reqNewsProviders(self):
4171
+ self.logRequest(current_fn_name(), vars())
4172
+
4173
+ if not self.isConnected():
4174
+ self.wrapper.error(NO_VALID_ID, currentTimeMillis(), NOT_CONNECTED.code(), NOT_CONNECTED.msg())
4175
+ return
4176
+
4177
+ if self.serverVersion() < MIN_SERVER_VER_REQ_NEWS_PROVIDERS:
4178
+ self.wrapper.error(
4179
+ NO_VALID_ID,
4180
+ currentTimeMillis(),
4181
+ UPDATE_TWS.code(),
4182
+ UPDATE_TWS.msg() + " It does not support news providers request.",
4183
+ )
4184
+ return
4185
+
4186
+ self.sendMsg(OUT.REQ_NEWS_PROVIDERS, "")
4187
+
4188
+ def reqNewsArticle(
4189
+ self,
4190
+ reqId: int,
4191
+ providerCode: str,
4192
+ articleId: str,
4193
+ newsArticleOptions: TagValueList,
4194
+ ):
4195
+ self.logRequest(current_fn_name(), vars())
4196
+
4197
+ if not self.isConnected():
4198
+ self.wrapper.error(NO_VALID_ID, currentTimeMillis(), NOT_CONNECTED.code(), NOT_CONNECTED.msg())
4199
+ return
4200
+
4201
+ if self.serverVersion() < MIN_SERVER_VER_REQ_NEWS_ARTICLE:
4202
+ self.wrapper.error(
4203
+ NO_VALID_ID,
4204
+ currentTimeMillis(),
4205
+ UPDATE_TWS.code(),
4206
+ UPDATE_TWS.msg() + " It does not support news article request.",
4207
+ )
4208
+ return
4209
+
4210
+ try:
4211
+ flds = []
4212
+
4213
+ flds += [
4214
+ make_field(reqId),
4215
+ make_field(providerCode),
4216
+ make_field(articleId),
4217
+ ]
4218
+
4219
+ # send newsArticleOptions parameter
4220
+ if self.serverVersion() >= MIN_SERVER_VER_NEWS_QUERY_ORIGINS:
4221
+ newsArticleOptionsStr = ""
4222
+ if newsArticleOptions:
4223
+ for tagValue in newsArticleOptions:
4224
+ newsArticleOptionsStr += str(tagValue)
4225
+ flds += [
4226
+ make_field(newsArticleOptionsStr),
4227
+ ]
4228
+
4229
+ msg = "".join(flds)
4230
+
4231
+ except ClientException as ex:
4232
+ self.wrapper.error(reqId, currentTimeMillis(), ex.code, ex.msg + ex.text)
4233
+ return
4234
+
4235
+ self.sendMsg(OUT.REQ_NEWS_ARTICLE, msg)
4236
+
4237
+ def reqHistoricalNews(
4238
+ self,
4239
+ reqId: int,
4240
+ conId: int,
4241
+ providerCodes: str,
4242
+ startDateTime: str,
4243
+ endDateTime: str,
4244
+ totalResults: int,
4245
+ historicalNewsOptions: TagValueList,
4246
+ ):
4247
+ self.logRequest(current_fn_name(), vars())
4248
+
4249
+ if not self.isConnected():
4250
+ self.wrapper.error(NO_VALID_ID, currentTimeMillis(), NOT_CONNECTED.code(), NOT_CONNECTED.msg())
4251
+ return
4252
+
4253
+ if self.serverVersion() < MIN_SERVER_VER_REQ_HISTORICAL_NEWS:
4254
+ self.wrapper.error(
4255
+ NO_VALID_ID,
4256
+ currentTimeMillis(),
4257
+ UPDATE_TWS.code(),
4258
+ UPDATE_TWS.msg() + " It does not support historical news request.",
4259
+ )
4260
+ return
4261
+
4262
+ try:
4263
+ flds = []
4264
+
4265
+ flds += [
4266
+ make_field(reqId),
4267
+ make_field(conId),
4268
+ make_field(providerCodes),
4269
+ make_field(startDateTime),
4270
+ make_field(endDateTime),
4271
+ make_field(totalResults),
4272
+ ]
4273
+
4274
+ # send historicalNewsOptions parameter
4275
+ if self.serverVersion() >= MIN_SERVER_VER_NEWS_QUERY_ORIGINS:
4276
+ historicalNewsOptionsStr = ""
4277
+ if historicalNewsOptions:
4278
+ for tagValue in historicalNewsOptionsStr:
4279
+ historicalNewsOptionsStr += str(tagValue)
4280
+ flds += [
4281
+ make_field(historicalNewsOptionsStr),
4282
+ ]
4283
+
4284
+ msg = "".join(flds)
4285
+
4286
+ except ClientException as ex:
4287
+ self.wrapper.error(reqId, currentTimeMillis(), ex.code, ex.msg + ex.text)
4288
+ return
4289
+
4290
+ self.sendMsg(OUT.REQ_HISTORICAL_NEWS, msg)
4291
+
4292
+ #########################################################################
4293
+ # Display Groups
4294
+ #########################################################################
4295
+
4296
+ def queryDisplayGroups(self, reqId: int):
4297
+ """
4298
+ API requests used to integrate with TWS color-grouped windows (display groups).
4299
+ TWS color-grouped windows are identified by an integer number.
4300
+ Currently, that number ranges from 1 to 7 and are mapped to specific colors, as indicated in TWS.
4301
+
4302
+ reqId:int - The unique number that will be associated with the
4303
+ response"""
4304
+
4305
+ self.logRequest(current_fn_name(), vars())
4306
+
4307
+ if not self.isConnected():
4308
+ self.wrapper.error(NO_VALID_ID, currentTimeMillis(), NOT_CONNECTED.code(), NOT_CONNECTED.msg())
4309
+ return
4310
+
4311
+ if self.serverVersion() < MIN_SERVER_VER_LINKING:
4312
+ self.wrapper.error(
4313
+ NO_VALID_ID,
4314
+ currentTimeMillis(),
4315
+ UPDATE_TWS.code(),
4316
+ UPDATE_TWS.msg() + " It does not support queryDisplayGroups request.",
4317
+ )
4318
+ return
4319
+
4320
+ VERSION = 1
4321
+
4322
+ msg = (
4323
+ make_field(VERSION)
4324
+ + make_field(reqId)
4325
+ )
4326
+
4327
+ self.sendMsg(OUT.QUERY_DISPLAY_GROUPS, msg)
4328
+
4329
+ def subscribeToGroupEvents(self, reqId: int, groupId: int):
4330
+ """reqId:int - The unique number associated with the notification.
4331
+ groupId:int - The ID of the group, currently it is a number from 1 to 7.
4332
+ This is the display group subscription request sent by the API to TWS."""
4333
+
4334
+ self.logRequest(current_fn_name(), vars())
4335
+
4336
+ if not self.isConnected():
4337
+ self.wrapper.error(NO_VALID_ID, currentTimeMillis(), NOT_CONNECTED.code(), NOT_CONNECTED.msg())
4338
+ return
4339
+
4340
+ if self.serverVersion() < MIN_SERVER_VER_LINKING:
4341
+ self.wrapper.error(
4342
+ NO_VALID_ID,
4343
+ currentTimeMillis(),
4344
+ UPDATE_TWS.code(),
4345
+ UPDATE_TWS.msg()
4346
+ + " It does not support subscribeToGroupEvents request.",
4347
+ )
4348
+ return
4349
+
4350
+ VERSION = 1
4351
+
4352
+ msg = (
4353
+ make_field(VERSION)
4354
+ + make_field(reqId)
4355
+ + make_field(groupId)
4356
+ )
4357
+
4358
+ self.sendMsg(OUT.SUBSCRIBE_TO_GROUP_EVENTS, msg)
4359
+
4360
+ def updateDisplayGroup(self, reqId: int, contractInfo: str):
4361
+ """reqId:int - The requestId specified in subscribeToGroupEvents().
4362
+ contractInfo:str - The encoded value that uniquely represents the
4363
+ contract in IB. Possible values include:
4364
+
4365
+ none = empty selection
4366
+ contractID@exchange - any non-combination contract.
4367
+ Examples: 8314@SMART for IBM SMART; 8314@ARCA for IBM @ARCA.
4368
+ combo = if any combo is selected."""
4369
+
4370
+ self.logRequest(current_fn_name(), vars())
4371
+
4372
+ if not self.isConnected():
4373
+ self.wrapper.error(NO_VALID_ID, currentTimeMillis(), NOT_CONNECTED.code(), NOT_CONNECTED.msg())
4374
+ return
4375
+
4376
+ if self.serverVersion() < MIN_SERVER_VER_LINKING:
4377
+ self.wrapper.error(
4378
+ NO_VALID_ID,
4379
+ currentTimeMillis(),
4380
+ UPDATE_TWS.code(),
4381
+ UPDATE_TWS.msg() + " It does not support updateDisplayGroup request.",
4382
+ )
4383
+ return
4384
+
4385
+ try:
4386
+ VERSION = 1
4387
+
4388
+ msg = (
4389
+ make_field(VERSION)
4390
+ + make_field(reqId)
4391
+ + make_field(contractInfo)
4392
+ )
4393
+
4394
+ except ClientException as ex:
4395
+ self.wrapper.error(NO_VALID_ID, currentTimeMillis(), ex.code, ex.msg + ex.text)
4396
+ return
4397
+
4398
+ self.sendMsg(OUT.UPDATE_DISPLAY_GROUP, msg)
4399
+
4400
+ def unsubscribeFromGroupEvents(self, reqId: int):
4401
+ """reqId:int - The requestId specified in subscribeToGroupEvents()."""
4402
+
4403
+ self.logRequest(current_fn_name(), vars())
4404
+
4405
+ if not self.isConnected():
4406
+ self.wrapper.error(NO_VALID_ID, currentTimeMillis(), NOT_CONNECTED.code(), NOT_CONNECTED.msg())
4407
+ return
4408
+
4409
+ if self.serverVersion() < MIN_SERVER_VER_LINKING:
4410
+ self.wrapper.error(
4411
+ NO_VALID_ID,
4412
+ currentTimeMillis(),
4413
+ UPDATE_TWS.code(),
4414
+ UPDATE_TWS.msg()
4415
+ + " It does not support unsubscribeFromGroupEvents request.",
4416
+ )
4417
+ return
4418
+
4419
+ VERSION = 1
4420
+
4421
+ msg = (
4422
+ make_field(VERSION)
4423
+ + make_field(reqId)
4424
+ )
4425
+
4426
+ self.sendMsg(OUT.UNSUBSCRIBE_FROM_GROUP_EVENTS, msg)
4427
+
4428
+ def verifyRequest(self, apiName: str, apiVersion: str):
4429
+ """For IB's internal purpose. Allows to provide means of verification
4430
+ between the TWS and third party programs."""
4431
+
4432
+ self.logRequest(current_fn_name(), vars())
4433
+
4434
+ if not self.isConnected():
4435
+ self.wrapper.error(NO_VALID_ID, currentTimeMillis(), NOT_CONNECTED.code(), NOT_CONNECTED.msg())
4436
+ return
4437
+
4438
+ if self.serverVersion() < MIN_SERVER_VER_LINKING:
4439
+ self.wrapper.error(
4440
+ NO_VALID_ID,
4441
+ currentTimeMillis(),
4442
+ UPDATE_TWS.code(),
4443
+ UPDATE_TWS.msg() + " It does not support verification request.",
4444
+ )
4445
+ return
4446
+
4447
+ if not self.extraAuth:
4448
+ self.wrapper.error(
4449
+ NO_VALID_ID,
4450
+ currentTimeMillis(),
4451
+ BAD_MESSAGE.code(),
4452
+ BAD_MESSAGE.msg()
4453
+ + " Intent to authenticate needs to be expressed during initial connect request.",
4454
+ )
4455
+ return
4456
+
4457
+ try:
4458
+ VERSION = 1
4459
+
4460
+ msg = (
4461
+ make_field(VERSION)
4462
+ + make_field(apiName)
4463
+ + make_field(apiVersion)
4464
+ )
4465
+
4466
+ except ClientException as ex:
4467
+ self.wrapper.error(NO_VALID_ID, currentTimeMillis(), ex.code, ex.msg + ex.text)
4468
+ return
4469
+
4470
+ self.sendMsg(OUT.VERIFY_REQUEST, msg)
4471
+
4472
+ def verifyMessage(self, apiData: str):
4473
+ """For IB's internal purpose. Allows to provide means of verification
4474
+ between the TWS and third party programs."""
4475
+
4476
+ self.logRequest(current_fn_name(), vars())
4477
+
4478
+ if not self.isConnected():
4479
+ self.wrapper.error(NO_VALID_ID, currentTimeMillis(), NOT_CONNECTED.code(), NOT_CONNECTED.msg())
4480
+ return
4481
+
4482
+ if self.serverVersion() < MIN_SERVER_VER_LINKING:
4483
+ self.wrapper.error(
4484
+ NO_VALID_ID,
4485
+ currentTimeMillis(),
4486
+ UPDATE_TWS.code(),
4487
+ UPDATE_TWS.msg() + " It does not support verification request.",
4488
+ )
4489
+ return
4490
+
4491
+ try:
4492
+ VERSION = 1
4493
+
4494
+ msg = (
4495
+ make_field(VERSION)
4496
+ + make_field(apiData)
4497
+ )
4498
+
4499
+ except ClientException as ex:
4500
+ self.wrapper.error(NO_VALID_ID, currentTimeMillis(), ex.code, ex.msg + ex.text)
4501
+ return
4502
+
4503
+ self.sendMsg(OUT.VERIFY_MESSAGE, msg)
4504
+
4505
+ def verifyAndAuthRequest(self, apiName: str, apiVersion: str, opaqueIsvKey: str):
4506
+ """For IB's internal purpose. Allows to provide means of verification
4507
+ between the TWS and third party programs."""
4508
+
4509
+ self.logRequest(current_fn_name(), vars())
4510
+
4511
+ if not self.isConnected():
4512
+ self.wrapper.error(NO_VALID_ID, currentTimeMillis(), NOT_CONNECTED.code(), NOT_CONNECTED.msg())
4513
+ return
4514
+
4515
+ if self.serverVersion() < MIN_SERVER_VER_LINKING:
4516
+ self.wrapper.error(
4517
+ NO_VALID_ID,
4518
+ currentTimeMillis(),
4519
+ UPDATE_TWS.code(),
4520
+ UPDATE_TWS.msg() + " It does not support verification request.",
4521
+ )
4522
+ return
4523
+
4524
+ if not self.extraAuth:
4525
+ self.wrapper.error(
4526
+ NO_VALID_ID,
4527
+ currentTimeMillis(),
4528
+ BAD_MESSAGE.code(),
4529
+ BAD_MESSAGE.msg()
4530
+ + " Intent to authenticate needs to be expressed during initial connect request.",
4531
+ )
4532
+ return
4533
+
4534
+ try:
4535
+ VERSION = 1
4536
+
4537
+ msg = (
4538
+ make_field(VERSION)
4539
+ + make_field(apiName)
4540
+ + make_field(apiVersion)
4541
+ + make_field(opaqueIsvKey)
4542
+ )
4543
+
4544
+ except ClientException as ex:
4545
+ self.wrapper.error(NO_VALID_ID, currentTimeMillis(), ex.code, ex.msg + ex.text)
4546
+ return
4547
+
4548
+ self.sendMsg(OUT.VERIFY_AND_AUTH_REQUEST, msg)
4549
+
4550
+ def verifyAndAuthMessage(self, apiData: str, xyzResponse: str):
4551
+ """For IB's internal purpose. Allows to provide means of verification
4552
+ between the TWS and third party programs."""
4553
+
4554
+ self.logRequest(current_fn_name(), vars())
4555
+
4556
+ if not self.isConnected():
4557
+ self.wrapper.error(NO_VALID_ID, currentTimeMillis(), NOT_CONNECTED.code(), NOT_CONNECTED.msg())
4558
+ return
4559
+
4560
+ if self.serverVersion() < MIN_SERVER_VER_LINKING:
4561
+ self.wrapper.error(
4562
+ NO_VALID_ID,
4563
+ currentTimeMillis(),
4564
+ UPDATE_TWS.code(),
4565
+ UPDATE_TWS.msg() + " It does not support verification request.",
4566
+ )
4567
+ return
4568
+
4569
+ try:
4570
+ VERSION = 1
4571
+
4572
+ msg = (
4573
+ make_field(VERSION)
4574
+ + make_field(apiData)
4575
+ + make_field(xyzResponse)
4576
+ )
4577
+
4578
+ except ClientException as ex:
4579
+ self.wrapper.error(NO_VALID_ID, currentTimeMillis(), ex.code, ex.msg + ex.text)
4580
+ return
4581
+
4582
+ self.sendMsg(OUT.VERIFY_AND_AUTH_MESSAGE, msg)
4583
+
4584
+ def reqSecDefOptParams(
4585
+ self,
4586
+ reqId: int,
4587
+ underlyingSymbol: str,
4588
+ futFopExchange: str,
4589
+ underlyingSecType: str,
4590
+ underlyingConId: int,
4591
+ ):
4592
+ """Requests security definition option parameters for viewing a
4593
+ contract's option chain reqId the ID chosen for the request
4594
+ underlyingSymbol futFopExchange The exchange on which the returned
4595
+ options are trading. Can be set to the empty string "" for all
4596
+ exchanges. underlyingSecType The type of the underlying security,
4597
+ i.e. STK underlyingConId the contract ID of the underlying security.
4598
+ Response comes via EWrapper.securityDefinitionOptionParameter()"""
4599
+
4600
+ self.logRequest(current_fn_name(), vars())
4601
+
4602
+ if not self.isConnected():
4603
+ self.wrapper.error(NO_VALID_ID, currentTimeMillis(), NOT_CONNECTED.code(), NOT_CONNECTED.msg())
4604
+ return
4605
+
4606
+ if self.serverVersion() < MIN_SERVER_VER_SEC_DEF_OPT_PARAMS_REQ:
4607
+ self.wrapper.error(
4608
+ NO_VALID_ID,
4609
+ currentTimeMillis(),
4610
+ UPDATE_TWS.code(),
4611
+ UPDATE_TWS.msg()
4612
+ + " It does not support security definition option request.",
4613
+ )
4614
+ return
4615
+
4616
+ try:
4617
+ flds = []
4618
+ flds += [
4619
+ make_field(reqId),
4620
+ make_field(underlyingSymbol),
4621
+ make_field(futFopExchange),
4622
+ make_field(underlyingSecType),
4623
+ make_field(underlyingConId),
4624
+ ]
4625
+
4626
+ msg = "".join(flds)
4627
+
4628
+ except ClientException as ex:
4629
+ self.wrapper.error(reqId, currentTimeMillis(), ex.code, ex.msg + ex.text)
4630
+ return
4631
+
4632
+ self.sendMsg(OUT.REQ_SEC_DEF_OPT_PARAMS, msg)
4633
+
4634
+ def reqSoftDollarTiers(self, reqId: int):
4635
+ """Requests pre-defined Soft Dollar Tiers. This is only supported for
4636
+ registered professional advisors and hedge and mutual funds who have
4637
+ configured Soft Dollar Tiers in Account Management."""
4638
+
4639
+ self.logRequest(current_fn_name(), vars())
4640
+
4641
+ if not self.isConnected():
4642
+ self.wrapper.error(NO_VALID_ID, currentTimeMillis(), NOT_CONNECTED.code(), NOT_CONNECTED.msg())
4643
+ return
4644
+
4645
+ msg = make_field(reqId)
4646
+
4647
+ self.sendMsg(OUT.REQ_SOFT_DOLLAR_TIERS, msg)
4648
+
4649
+ def reqFamilyCodes(self):
4650
+ self.logRequest(current_fn_name(), vars())
4651
+
4652
+ if not self.isConnected():
4653
+ self.wrapper.error(NO_VALID_ID, currentTimeMillis(), NOT_CONNECTED.code(), NOT_CONNECTED.msg())
4654
+ return
4655
+
4656
+ if self.serverVersion() < MIN_SERVER_VER_REQ_FAMILY_CODES:
4657
+ self.wrapper.error(
4658
+ NO_VALID_ID,
4659
+ currentTimeMillis(),
4660
+ UPDATE_TWS.code(),
4661
+ UPDATE_TWS.msg() + " It does not support family codes request.",
4662
+ )
4663
+ return
4664
+
4665
+ self.sendMsg(OUT.REQ_FAMILY_CODES, "")
4666
+
4667
+ def reqMatchingSymbols(self, reqId: int, pattern: str):
4668
+ self.logRequest(current_fn_name(), vars())
4669
+
4670
+ if not self.isConnected():
4671
+ self.wrapper.error(NO_VALID_ID, currentTimeMillis(), NOT_CONNECTED.code(), NOT_CONNECTED.msg())
4672
+ return
4673
+
4674
+ if self.serverVersion() < MIN_SERVER_VER_REQ_MATCHING_SYMBOLS:
4675
+ self.wrapper.error(
4676
+ NO_VALID_ID,
4677
+ currentTimeMillis(),
4678
+ UPDATE_TWS.code(),
4679
+ UPDATE_TWS.msg() + " It does not support matching symbols request.",
4680
+ )
4681
+ return
4682
+
4683
+ try:
4684
+ msg = (
4685
+ make_field(reqId)
4686
+ + make_field(pattern)
4687
+ )
4688
+
4689
+ except ClientException as ex:
4690
+ self.wrapper.error(reqId, currentTimeMillis(), ex.code, ex.msg + ex.text)
4691
+ return
4692
+
4693
+ self.sendMsg(OUT.REQ_MATCHING_SYMBOLS, msg)
4694
+
4695
+ def reqCompletedOrders(self, apiOnly: bool):
4696
+ """Call this function to request the completed orders. If apiOnly parameter
4697
+ is true, then only completed orders placed from API are requested.
4698
+ Each completed order will be fed back through the
4699
+ completedOrder() function on the EWrapper."""
4700
+
4701
+ self.logRequest(current_fn_name(), vars())
4702
+
4703
+ if not self.isConnected():
4704
+ self.wrapper.error(NO_VALID_ID, currentTimeMillis(), NOT_CONNECTED.code(), NOT_CONNECTED.msg())
4705
+ return
4706
+
4707
+ msg = make_field(apiOnly)
4708
+
4709
+ self.sendMsg(OUT.REQ_COMPLETED_ORDERS, msg)
4710
+
4711
+ def reqWshMetaData(self, reqId: int):
4712
+ self.logRequest(current_fn_name(), vars())
4713
+
4714
+ if not self.isConnected():
4715
+ self.wrapper.error(NO_VALID_ID, currentTimeMillis(), NOT_CONNECTED.code(), NOT_CONNECTED.msg())
4716
+ return
4717
+
4718
+ if self.serverVersion() < MIN_SERVER_VER_WSHE_CALENDAR:
4719
+ self.wrapper.error(
4720
+ NO_VALID_ID,
4721
+ currentTimeMillis(),
4722
+ UPDATE_TWS.code(),
4723
+ UPDATE_TWS.msg() + " It does not support WSHE Calendar API.",
4724
+ )
4725
+ return
4726
+
4727
+ try:
4728
+ msg = make_field(reqId)
4729
+
4730
+ except ClientException as ex:
4731
+ self.wrapper.error(reqId, currentTimeMillis(), ex.code, ex.msg + ex.text)
4732
+ return
4733
+
4734
+ self.sendMsg(OUT.REQ_WSH_META_DATA, msg)
4735
+
4736
+ def cancelWshMetaData(self, reqId: int):
4737
+ self.logRequest(current_fn_name(), vars())
4738
+
4739
+ if not self.isConnected():
4740
+ self.wrapper.error(NO_VALID_ID, currentTimeMillis(), NOT_CONNECTED.code(), NOT_CONNECTED.msg())
4741
+ return
4742
+
4743
+ if self.serverVersion() < MIN_SERVER_VER_WSHE_CALENDAR:
4744
+ self.wrapper.error(
4745
+ NO_VALID_ID,
4746
+ currentTimeMillis(),
4747
+ UPDATE_TWS.code(),
4748
+ UPDATE_TWS.msg() + " It does not support WSHE Calendar API.",
4749
+ )
4750
+ return
4751
+
4752
+ msg = make_field(reqId)
4753
+
4754
+ self.sendMsg(OUT.CANCEL_WSH_META_DATA, msg)
4755
+
4756
+ def reqWshEventData(
4757
+ self,
4758
+ reqId: int,
4759
+ wshEventData: WshEventData
4760
+ ):
4761
+ self.logRequest(current_fn_name(), vars())
4762
+
4763
+ if not self.isConnected():
4764
+ self.wrapper.error(NO_VALID_ID, currentTimeMillis(), NOT_CONNECTED.code(), NOT_CONNECTED.msg())
4765
+ return
4766
+
4767
+ if self.serverVersion() < MIN_SERVER_VER_WSHE_CALENDAR:
4768
+ self.wrapper.error(
4769
+ NO_VALID_ID,
4770
+ currentTimeMillis(),
4771
+ currentTimeMillis(),
4772
+ UPDATE_TWS.code(),
4773
+ UPDATE_TWS.msg() + " It does not support WSHE Calendar API.",
4774
+ )
4775
+ return
4776
+
4777
+ if self.serverVersion() < MIN_SERVER_VER_WSH_EVENT_DATA_FILTERS:
4778
+ if (
4779
+ wshEventData.filter != ""
4780
+ or wshEventData.fillWatchlist
4781
+ or wshEventData.fillPortfolio
4782
+ or wshEventData.fillCompetitors
4783
+ ):
4784
+ self.wrapper.error(
4785
+ NO_VALID_ID,
4786
+ currentTimeMillis(),
4787
+ UPDATE_TWS.code(),
4788
+ UPDATE_TWS.msg() + " It does not support WSH event data filters.",
4789
+ )
4790
+ return
4791
+
4792
+ if self.serverVersion() < MIN_SERVER_VER_WSH_EVENT_DATA_FILTERS_DATE:
4793
+ if (
4794
+ wshEventData.startDate != ""
4795
+ or wshEventData.endDate != ""
4796
+ or wshEventData.totalLimit != UNSET_INTEGER
4797
+ ):
4798
+ self.wrapper.error(
4799
+ NO_VALID_ID,
4800
+ currentTimeMillis(),
4801
+ UPDATE_TWS.code(),
4802
+ UPDATE_TWS.msg()
4803
+ + " It does not support WSH event data date filters.",
4804
+ )
4805
+ return
4806
+
4807
+ try:
4808
+ flds = [
4809
+ make_field(reqId),
4810
+ make_field(wshEventData.conId),
4811
+ ]
4812
+
4813
+ if self.serverVersion() >= MIN_SERVER_VER_WSH_EVENT_DATA_FILTERS:
4814
+ flds.append(make_field(wshEventData.filter))
4815
+ flds.append(make_field(wshEventData.fillWatchlist))
4816
+ flds.append(make_field(wshEventData.fillPortfolio))
4817
+ flds.append(make_field(wshEventData.fillCompetitors))
4818
+
4819
+ if self.serverVersion() >= MIN_SERVER_VER_WSH_EVENT_DATA_FILTERS_DATE:
4820
+ flds.append(make_field(wshEventData.startDate))
4821
+ flds.append(make_field(wshEventData.endDate))
4822
+ flds.append(make_field(wshEventData.totalLimit))
4823
+
4824
+ msg = "".join(flds)
4825
+
4826
+ except ClientException as ex:
4827
+ self.wrapper.error(reqId, currentTimeMillis(), ex.code, ex.msg + ex.text)
4828
+ return
4829
+
4830
+ self.sendMsg(OUT.REQ_WSH_EVENT_DATA, msg)
4831
+
4832
+ def cancelWshEventData(self, reqId: int):
4833
+ self.logRequest(current_fn_name(), vars())
4834
+
4835
+ if not self.isConnected():
4836
+ self.wrapper.error(NO_VALID_ID, currentTimeMillis(), NOT_CONNECTED.code(), NOT_CONNECTED.msg())
4837
+ return
4838
+
4839
+ if self.serverVersion() < MIN_SERVER_VER_WSHE_CALENDAR:
4840
+ self.wrapper.error(
4841
+ NO_VALID_ID,
4842
+ currentTimeMillis(),
4843
+ UPDATE_TWS.code(),
4844
+ UPDATE_TWS.msg() + " It does not support WSHE Calendar API.",
4845
+ )
4846
+ return
4847
+
4848
+ msg = make_field(reqId)
4849
+
4850
+ self.sendMsg(OUT.CANCEL_WSH_EVENT_DATA, msg)
4851
+
4852
+ def reqUserInfo(self, reqId: int):
4853
+ self.logRequest(current_fn_name(), vars())
4854
+
4855
+ if not self.isConnected():
4856
+ self.wrapper.error(NO_VALID_ID, currentTimeMillis(), NOT_CONNECTED.code(), NOT_CONNECTED.msg())
4857
+ return
4858
+
4859
+ if self.serverVersion() < MIN_SERVER_VER_USER_INFO:
4860
+ self.wrapper.error(
4861
+ NO_VALID_ID,
4862
+ currentTimeMillis(),
4863
+ UPDATE_TWS.code(),
4864
+ UPDATE_TWS.msg() + " It does not support user info requests.",
4865
+ )
4866
+ return
4867
+
4868
+ msg = make_field(reqId)
4869
+
4870
+ self.sendMsg(OUT.REQ_USER_INFO, msg)
4871
+
4872
+ def reqCurrentTimeInMillis(self):
4873
+ """Asks the current system time in milliseconds on the server side."""
4874
+
4875
+ self.logRequest(current_fn_name(), vars())
4876
+
4877
+ if not self.isConnected():
4878
+ self.wrapper.error(NO_VALID_ID, currentTimeMillis(), NOT_CONNECTED.code(), NOT_CONNECTED.msg())
4879
+ return
4880
+
4881
+ if self.serverVersion() < MIN_SERVER_VER_CURRENT_TIME_IN_MILLIS:
4882
+ self.wrapper.error(
4883
+ NO_VALID_ID,
4884
+ currentTimeMillis(),
4885
+ UPDATE_TWS.code(),
4886
+ UPDATE_TWS.msg() + " It does not support current time in millis requests.",
4887
+ )
4888
+ return
4889
+
4890
+ self.sendMsg(OUT.REQ_CURRENT_TIME_IN_MILLIS, "")
4891
+