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.
- ibapi/__init__.py +16 -0
- ibapi/account_summary_tags.py +70 -0
- ibapi/client.py +4891 -0
- ibapi/client_utils.py +412 -0
- ibapi/comm.py +100 -0
- ibapi/commission_and_fees_report.py +31 -0
- ibapi/common.py +315 -0
- ibapi/connection.py +126 -0
- ibapi/const.py +12 -0
- ibapi/contract.py +265 -0
- ibapi/decoder.py +1711 -0
- ibapi/decoder_utils.py +420 -0
- ibapi/enum_implem.py +20 -0
- ibapi/errors.py +43 -0
- ibapi/execution.py +95 -0
- ibapi/ineligibility_reason.py +18 -0
- ibapi/message.py +186 -0
- ibapi/news.py +13 -0
- ibapi/object_implem.py +12 -0
- ibapi/order.py +263 -0
- ibapi/order_cancel.py +21 -0
- ibapi/order_condition.py +289 -0
- ibapi/order_state.py +117 -0
- ibapi/orderdecoder.py +539 -0
- ibapi/protobuf/CancelOrderRequest_pb2.py +38 -0
- ibapi/protobuf/ComboLeg_pb2.py +37 -0
- ibapi/protobuf/Contract_pb2.py +39 -0
- ibapi/protobuf/DeltaNeutralContract_pb2.py +37 -0
- ibapi/protobuf/ErrorMessage_pb2.py +37 -0
- ibapi/protobuf/ExecutionDetailsEnd_pb2.py +37 -0
- ibapi/protobuf/ExecutionDetails_pb2.py +39 -0
- ibapi/protobuf/ExecutionFilter_pb2.py +37 -0
- ibapi/protobuf/ExecutionRequest_pb2.py +38 -0
- ibapi/protobuf/Execution_pb2.py +37 -0
- ibapi/protobuf/GlobalCancelRequest_pb2.py +38 -0
- ibapi/protobuf/OpenOrder_pb2.py +40 -0
- ibapi/protobuf/OpenOrdersEnd_pb2.py +37 -0
- ibapi/protobuf/OrderAllocation_pb2.py +37 -0
- ibapi/protobuf/OrderCancel_pb2.py +37 -0
- ibapi/protobuf/OrderCondition_pb2.py +37 -0
- ibapi/protobuf/OrderState_pb2.py +38 -0
- ibapi/protobuf/OrderStatus_pb2.py +37 -0
- ibapi/protobuf/Order_pb2.py +51 -0
- ibapi/protobuf/PlaceOrderRequest_pb2.py +39 -0
- ibapi/protobuf/SoftDollarTier_pb2.py +37 -0
- ibapi/reader.py +49 -0
- ibapi/scanner.py +77 -0
- ibapi/server_versions.py +159 -0
- ibapi/softdollartier.py +16 -0
- ibapi/tag_value.py +22 -0
- ibapi/ticktype.py +117 -0
- ibapi/utils.py +233 -0
- ibapi/wrapper.py +885 -0
- ibapi_stable-10.37.2.dist-info/METADATA +136 -0
- ibapi_stable-10.37.2.dist-info/RECORD +57 -0
- ibapi_stable-10.37.2.dist-info/WHEEL +5 -0
- 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
|
+
|