ibflex2 1.0.0__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.
ibflex/__init__.py ADDED
@@ -0,0 +1,19 @@
1
+ from . import enums
2
+ from .enums import *
3
+ from . import Types
4
+ from .Types import *
5
+ from . import parser
6
+ from .parser import parse
7
+ from . import utils
8
+ from . import client
9
+
10
+ from .__version__ import (
11
+ __title__,
12
+ __description__,
13
+ __url__,
14
+ __version__,
15
+ __author__,
16
+ __author_email__,
17
+ __license__,
18
+ __copyright__,
19
+ )
ibflex/__version__.py ADDED
@@ -0,0 +1,26 @@
1
+ """
2
+ Get a job, save your money, listen to Jane
3
+ Everybody knows umbrellas cost more in the rain
4
+ All the news is bad
5
+ Is there any other kind?
6
+ Everybody's talking at the same time
7
+
8
+ It's hard times for some
9
+ For others it's sweet
10
+ Someone makes money when there's blood in the street
11
+ Don't take any lip
12
+ Stay in line
13
+ Everybody's talking at the same time
14
+ """
15
+ __title__ = "ibflex2"
16
+ __description__ = (
17
+ "Parse Interactive Brokers Flex XML reports and convert to Python types (maintained fork)"
18
+ ),
19
+ __url__ = "https://github.com/robcohen/ibflex2"
20
+ __upstream_url__ = "https://github.com/csingley/ibflex"
21
+ __version__ = "1.0.0"
22
+ __author__ = "Christopher Singley"
23
+ __author_email__ = "csingley@gmail.com"
24
+ __maintainer__ = "robcohen"
25
+ __license__ = "MIT"
26
+ __copyright__ = "Copyright 2017 Christopher Singley"
ibflex/client.py ADDED
@@ -0,0 +1,259 @@
1
+ # coding: utf-8
2
+ """
3
+ Download Flex queries without logging into Account Management web page.
4
+
5
+ https://www.interactivebrokers.com/en/software/am/am/reports/flex_web_service_version_3.htm
6
+ """
7
+ # stdlib imports
8
+ from dataclasses import dataclass
9
+ import xml.etree.ElementTree as ET
10
+ from datetime import datetime
11
+ import time
12
+ from typing import Union, Optional
13
+
14
+
15
+ # 3rd party imports
16
+ import requests
17
+
18
+
19
+ ###############################################################################
20
+ # SERVICE LOCATIONS
21
+ ###############################################################################
22
+ FLEX_URL = 'https://gdcdyn.interactivebrokers.com/Universal/servlet/'
23
+ REQUEST_URL = FLEX_URL + 'FlexStatementService.SendRequest'
24
+ STMT_URL = FLEX_URL + 'FlexStatementService.GetStatement'
25
+
26
+
27
+ ###############################################################################
28
+ # ERRORS
29
+ ###############################################################################
30
+ ERRORS = [
31
+ ("1003", "Statement is not available."),
32
+ ("1004", "Statement is incomplete at this time. Please try again shortly."),
33
+ ("1005", "Settlement data is not ready at this time. Please try again shortly."),
34
+ ("1006", "FIFO P/L data is not ready at this time. Please try again shortly."),
35
+ ("1007", "MTM P/L data is not ready at this time. Please try again shortly."),
36
+ ("1008", "MTM and FIFO P/L data is not ready at this time. Please try again shortly."),
37
+ ("1009", "The server is under heavy load. Statement could not be generated at this time. Please try again shortly."),
38
+ ("1010", "Legacy Flex Queries are no longer supported. Please convert over to Activity Flex."),
39
+ ("1011", "Service account is inactive."),
40
+ ("1012", "Token has expired."),
41
+ ("1013", "IP restriction."),
42
+ ("1014", "Query is invalid."),
43
+ ("1015", "Token is invalid."),
44
+ ("1016", "Account in invalid."),
45
+ ("1017", "Reference code is invalid."),
46
+ ("1018", "Too many requests have been made from this token. Please try again shortly."),
47
+ ("1019", "Statement generation in progress. Please try again shortly."),
48
+ ("1020", "Invalid request or unable to validate request."),
49
+ ("1021", "Statement could not be retrieved at this time. Please try again shortly."),
50
+ ]
51
+ ERROR_CODES, ERROR_MSGS = zip(*ERRORS)
52
+
53
+ SERVER_BUSY = ("1009", "1019", )
54
+ CLIENT_THROTTLED = ("1018", )
55
+
56
+
57
+ class IbflexClientError(Exception):
58
+ """ Base class for Exceptions defined in this module """
59
+
60
+
61
+ class BadResponseError(IbflexClientError):
62
+ """
63
+ Exception raised for malformed Flex response.
64
+ """
65
+ def __init__(self, response: requests.Response):
66
+ self.response = response
67
+ super(BadResponseError, self).__init__(response.content)
68
+
69
+
70
+ class StatementGenerationTimeout(IbflexClientError):
71
+ """ Exception raised when the Flex server says it is generating the response,
72
+ but does not finish generating the statement in a timely fashion.
73
+ """
74
+
75
+
76
+ class ResponseCodeError(IbflexClientError):
77
+ """
78
+ Exception raised when Flex server returns a response with an error code.
79
+ """
80
+ def __init__(self, code: str, msg: str):
81
+ self.code = code
82
+ self.msg = msg
83
+ super(ResponseCodeError, self).__init__(f"Code={code}: {msg}")
84
+
85
+
86
+ ###############################################################################
87
+ # FlexStatementResponse TYPES
88
+ ###############################################################################
89
+ @dataclass(frozen=True)
90
+ class StatementAccess:
91
+ timestamp: datetime
92
+ ReferenceCode: str
93
+ Url: str
94
+
95
+
96
+ @dataclass(frozen=True)
97
+ class StatementError:
98
+ timestamp: datetime
99
+ ErrorCode: str
100
+ ErrorMessage: str
101
+
102
+
103
+ ###############################################################################
104
+ # FUNCTIONS
105
+ ###############################################################################
106
+ def download(token: str, query_id: str, max_tries: Optional[int] = 5) -> bytes:
107
+ """2-step FlexQueryReport download process.
108
+
109
+ Args:
110
+ token: Current access token from Reports > Settings > FlexWeb Service.
111
+ query_id: Flex Query ID from
112
+ Reports > Flex Queries > Custom Flex Queries > Configure.
113
+ """
114
+ stmt_access = request_statement(token, query_id)
115
+ status = 0
116
+ tries = 0
117
+ while status is not True:
118
+ time.sleep(status)
119
+ tries += 1
120
+ response = submit_request(
121
+ url=stmt_access.Url or STMT_URL,
122
+ token=token,
123
+ query=stmt_access.ReferenceCode,
124
+ )
125
+ status = check_statement_response(response)
126
+ if max_tries and tries > max_tries:
127
+ raise StatementGenerationTimeout(
128
+ "Exceeded max number of tries while attempting download"
129
+ )
130
+ return response.content
131
+
132
+
133
+ def request_statement(
134
+ token: str, query_id: str, url: Optional[str] = None
135
+ ) -> StatementAccess:
136
+ """First part of the 2-step download process.
137
+ """
138
+ url = url or REQUEST_URL
139
+ ### AKE FIX
140
+ url = 'https://ndcdyn.interactivebrokers.com/portal.flexweb/api/v1/flexQuery'
141
+ response = submit_request(url, token, query=query_id)
142
+ stmt_access = parse_stmt_response(response)
143
+ if isinstance(stmt_access, StatementError):
144
+ raise ResponseCodeError(
145
+ stmt_access.ErrorCode,
146
+ stmt_access.ErrorMessage,
147
+ )
148
+ return stmt_access
149
+
150
+
151
+ def submit_request(url: str, token: str, query: str) -> requests.Response:
152
+ """Post a query to an API access point, along with an authentication token.
153
+
154
+ Retry with a progressive timeout window.
155
+ """
156
+ MAX_REQUESTS = 3
157
+ TIMEOUT_INCREMENT = 5
158
+
159
+ response = None
160
+ req_count = 1
161
+ while (not response):
162
+ try:
163
+ response = requests.get(
164
+ url,
165
+ params={"v": "3", "t": token, "q": query},
166
+ headers={"user-agent": "Java"},
167
+ timeout=req_count * TIMEOUT_INCREMENT,
168
+ )
169
+ except requests.exceptions.Timeout:
170
+ if req_count >= MAX_REQUESTS:
171
+ raise
172
+ else:
173
+ print("Request Timeout, re-sending...")
174
+ req_count += 1
175
+
176
+ return response
177
+
178
+
179
+ def parse_stmt_response(
180
+ response: requests.Response
181
+ ) -> Union[StatementAccess, StatementError]:
182
+ """Read 1st step response; parse into StatementAccess or StatementError.
183
+ """
184
+ try:
185
+ elem = ET.fromstring(response.content)
186
+ assert elem.tag == 'FlexStatementResponse'
187
+
188
+ timestamp = elem.attrib['timestamp']
189
+ # Convert "EST"/"EDT" to UTC offset so datetime.strptime can understand
190
+ tz = {'EST': '-0500', 'EDT': '-0400'}[timestamp[-3:]]
191
+ timestamp = timestamp[:-3] + tz
192
+ datetime_ = datetime.strptime(timestamp, '%d %B, %Y %I:%M %p %z')
193
+
194
+ data = {child.tag: child.text for child in elem}
195
+ status = data.pop("Status")
196
+ assert status in {"Success", "Fail", "Warn"}
197
+ Type = {
198
+ "Success": StatementAccess,
199
+ "Fail": StatementError,
200
+ "Warn": StatementError,
201
+ }[status]
202
+ return Type(timestamp=datetime_, **data) # type: ignore
203
+ except Exception:
204
+ raise BadResponseError(response=response)
205
+
206
+
207
+ def check_statement_response(response: requests.Response) -> Union[bool, int]:
208
+ """Validate response received from 2nd step of download.
209
+
210
+ Returns:
211
+ True if `response` contains a FlexQueryResponse.
212
+ Retry delay (seconds) if `response` is an error indicating that
213
+ 'please try again shortly' means 'within several seconds'.
214
+
215
+ Raises:
216
+ ResponseCodeError if `response` is any other kind of error.
217
+ BadResponseError if we can't parse `response`.
218
+ """
219
+ # FlexQueryResponses can be massive; avoid parsing them.
220
+ resp_str = str(response.content)
221
+ if 'FlexQueryResponse' in resp_str:
222
+ return True
223
+ elif 'FlexStatementResponse' in resp_str:
224
+ try:
225
+ error = parse_stmt_response(response)
226
+ assert isinstance(error, StatementError)
227
+ except Exception:
228
+ raise BadResponseError(response)
229
+ if error.ErrorCode in SERVER_BUSY:
230
+ # Statement generation in progress. Please try again shortly.
231
+ return 5
232
+ elif error.ErrorCode in CLIENT_THROTTLED:
233
+ # Too many requests have been made from this token. Please try again shortly.
234
+ return 10
235
+ else:
236
+ raise ResponseCodeError(error.ErrorCode, error.ErrorMessage)
237
+ else:
238
+ raise BadResponseError(response)
239
+
240
+
241
+ ##############################################################################
242
+ # CLI SCRIPT
243
+ ###############################################################################
244
+ def main():
245
+ from argparse import ArgumentParser
246
+ description = 'Download Flex brokerage statement from Interactive Brokers'
247
+ argparser = ArgumentParser(description=description)
248
+ argparser.add_argument('--token', '-t', required=True,
249
+ help='Current Flex Web Service token')
250
+ argparser.add_argument('--query', '-q', required=True,
251
+ help='Flex Query ID#')
252
+ args = argparser.parse_args()
253
+
254
+ statement = download(args.token, args.query)
255
+ print(statement.decode())
256
+
257
+
258
+ if __name__ == '__main__':
259
+ main()
ibflex/enums.py ADDED
@@ -0,0 +1,295 @@
1
+ # coding: utf-8
2
+ """Enumerated values (Python enum.Enum subclasses) used to define ibflex.Types.
3
+
4
+ Values are the text sent by IB in XML element attribute.
5
+ Names keep the convention of using UPPERCASE for Enums.
6
+
7
+ When creating a new Enum subclass, be sure to add it to ENUMS
8
+ and EnumType (as Optional type) at the end of the file.
9
+ """
10
+
11
+ __all__ = [
12
+ "CashAction",
13
+ "Code",
14
+ "AssetClass",
15
+ "TradeType",
16
+ "BuySell",
17
+ "OpenClose",
18
+ "OrderType",
19
+ "Reorg",
20
+ "OptionAction",
21
+ "LongShort",
22
+ "TransferType",
23
+ "ToFrom",
24
+ "InOut",
25
+ "DeliveredReceived",
26
+ "ENUMS",
27
+ "EnumType",
28
+ "PutCall"
29
+ ]
30
+
31
+ import enum
32
+ import typing
33
+
34
+
35
+ @enum.unique
36
+ class CashAction(str, enum.Enum):
37
+ DEPOSITWITHDRAW = "Deposits & Withdrawals"
38
+ BROKERINTPAID = "Broker Interest Paid"
39
+ BROKERINTRCVD = "Broker Interest Received"
40
+ WHTAX = "Withholding Tax"
41
+ BONDINTRCVD = "Bond Interest Received"
42
+ BONDINTPAID = "Bond Interest Paid"
43
+ FEES = "Other Fees"
44
+ DIVIDEND = "Dividends"
45
+ PAYMENTINLIEU = "Payment In Lieu Of Dividends"
46
+ COMMADJ = "Commission Adjustments"
47
+ ADVISORFEES = "Advisor Fees"
48
+
49
+ @enum.unique
50
+ class Code(str, enum.Enum):
51
+ """Used for both `code` and `notes` attributes.
52
+ """
53
+ ASSIGNMENT = "A"
54
+ AUTOEXERCISE = "AEx" # Automatic exercise for dividend-related recommendation
55
+ AUTOFX = "AFx" # AutoFX conversion resulting from trading
56
+ ADJUSTMENT = "Adj" # Adjustment
57
+ ALLOCATION = "Al" # Allocation
58
+ AWAY = "Aw" # Away Trade
59
+ BUYIN = "B" # Automatic Buy-in
60
+ BORROW = "Bo" # Direct Borrow
61
+ CLOSING = "C" # Closing Trade
62
+ CASHDELIVERY = "CD" # Cash Delivery
63
+ COMPLEX = "CP" # Complex Position
64
+ CANCEL = "Ca" # Cancelled
65
+ CORRECT = "Co" # Corrected Trade
66
+ CROSSING = "Cx" # Part or all of this transaction was a Crossing executed as dual agent by IB for two IB customers
67
+ DUAL = "D" # IB acted as Dual Agent, UNIQUE TO TRADE CONFIRM REPORT
68
+ ETF = "ETF" # ETF Creation/Redemption
69
+ EXPIRED = "Ep" # Resulted from an Expired Position
70
+ EXERCISE = "Ex" # Exercise
71
+ GUARANTEED = "G" # Trade in Guaranteed Account Segment
72
+ HIGHESTCOST = "HC" # Highest Cost tax lot-matching method
73
+ HFINVESTMENT = "HFI" # Investment Transferred to Hedge Fund
74
+ HFREDEMPTION = "HFR" # Redemption from Hedge Fund
75
+ INTERNAL = "I" # Internal Transfer
76
+ AFFILIATE = "IA" # This transaction was executed against an IB affiliate
77
+ INVESTOR = "INV" # Investment Transfer from Investor
78
+ MARGINLOW = "L" # Ordered by IB (Margin Violation)
79
+ WASHSALE = "LD" # Adjusted by Loss Disallowed from Wash Sale
80
+ LIFO = "LI" # Last In, First Out (LIFO) tax lot-matching method
81
+ LTCG = "LT" # Long-term P/L
82
+ LOAN = "Lo" # Direct Loan
83
+ MANUAL = "M" # Entered manually by IB
84
+ MANUALEXERCISE = "MEx" # Manual exercise for dividend-related recommendation
85
+ MAXLOSS = "ML" # Maximize Losses tax basis election
86
+ MAXLTCG = "MLG" # Maximize Long-Term Gain tax lot-matching method
87
+ MINLTCG = "MLL" # Maximize Long-Term Loss tax lot-matching method
88
+ MAXSTCG = "MSG" # Maximize Short-Term Gain tax lot-matching method
89
+ MINSTCG = "MSL" # Maximize Short-Term Loss tax lot-matching method
90
+ OPENING = "O" # Opening Trade
91
+ PARTIAL = "P" # Partial Execution
92
+ FRACRISKLESSPRINCIPAL = "RP" # IB acted as riskless principal for the fractional share portion of this trade
93
+ FRACPRINCIPAL = "FP" # IB acted as principal for the fractional share portion of this trade
94
+ PRICEIMPROVEMENT = "PI" # Price Improvement
95
+ POSTACCRUAL = "Po" # Interest or Dividend Accrual Posting
96
+ PRINCIPAL = "Pr" # Part or all of this transaction was executed by the Exchange as a Crossing by IB against an IB affiliate and is therefore classified as a Principal and not an agency trade
97
+ REINVESTMENT = "R" # Dividend Reinvestment
98
+ REDEMPTION = "RED" # Redemption to Investor
99
+ REVERSE = "Re" # Interest or Dividend Accrual Reversal
100
+ REIMBURSEMENT = "RI" # Reimbursement
101
+ SOLICITEDIB = "SI" # This order was solicited by Interactive Brokers
102
+ SPECIFICLOT = "SL" # Specific Lot tax lot-matching method
103
+ SOLICITEDOTHER = "SO" # This order was marked as solicited by your Introducing Broker
104
+ SHORTENEDSETTLEMENT = "SS" # Customer designated this trade for shortened settlement and so is subject to execution at prices above the prevailing market
105
+ STCG = "ST" # Short-term P/L
106
+ STOCKYIELD = "SY" # Positions that may be eligible for Stock Yield.
107
+ TRANSFER = "T" # Transfer
108
+
109
+
110
+ @enum.unique
111
+ class AssetClass(str, enum.Enum):
112
+ CASH = "CASH"
113
+ BILL = "BILL"
114
+ BOND = "BOND"
115
+ STOCK = "STK"
116
+ OPTION = "OPT"
117
+ WARRANT = "WAR"
118
+ FUTURE = "FUT"
119
+ FUTUREOPTION = "FOP"
120
+ CFD = "CFD"
121
+ FOREXCFD = "FXCFD"
122
+ CRYPTOCURRENCY = "CRYPTO"
123
+ STRUCTUREDPRODUCTS = "IOPT"
124
+ METALS = "CMDTY"
125
+ OPTIONSONFUTURES = "FSFOP"
126
+ OPTIONSFUTURESSTYLE = "FSOPT"
127
+ MUTUALFUND = "FUND"
128
+
129
+
130
+ @enum.unique
131
+ class TradeType(str, enum.Enum):
132
+ EXCHTRADE = "ExchTrade"
133
+ TRADECANCEL = "TradeCancel"
134
+ FRACSHARE = "FracShare"
135
+ FRACSHARECANCEL = "FracShareCancel"
136
+ TRADECORRECT = "TradeCorrect"
137
+ BOOKTRADE = "BookTrade"
138
+ DVPTRADE = "DvpTrade"
139
+
140
+
141
+ @enum.unique
142
+ class BuySell(str, enum.Enum):
143
+ BUY = "BUY"
144
+ CANCELBUY = "BUY (Ca.)"
145
+ SELL = "SELL"
146
+ CANCELSELL = "SELL (Ca.)"
147
+
148
+
149
+ @enum.unique
150
+ class OpenClose(str, enum.Enum):
151
+ OPEN = "O"
152
+ CLOSE = "C"
153
+ OPENCLOSE = "C;O"
154
+ UNKNOWN = "-"
155
+
156
+
157
+ @enum.unique
158
+ class OrderType(str, enum.Enum):
159
+ LIMIT = "LMT"
160
+ MARKET = "MKT"
161
+ STOP = "STP"
162
+ STOPLIMIT = "STPLMT"
163
+ MARKETONCLOSE = "MOC"
164
+ LIMITONCLOSE = "LOC"
165
+ # MULTIPLE is not an actual IB order type. It is a catch-all value to use when an Order has an orderType like "LMT;MKT".
166
+ # This way OrderType can remian a enum and not be a Tuple.
167
+ MULTIPLE = "MULTIPLE"
168
+ TRAILLMT = "TRAILLMT"
169
+ MIDPX = "MIDPX"
170
+ TRAIL = "TRAIL"
171
+ REL = "REL"
172
+ MIT = "MIT"
173
+
174
+ @enum.unique
175
+ class Reorg(str, enum.Enum):
176
+ BONDCONVERSION = "BC"
177
+ BONDMATURITY = "BM"
178
+ CONTRACTSOULTE = "CA"
179
+ CONTRACTCONSOLIDATION = "CC"
180
+ CASHDIV = "CD"
181
+ CHOICEDIV = "CH"
182
+ CONVERTIBLEISSUE = "CI"
183
+ CONTRACTSPINOFF = "CO"
184
+ COUPONPAYMENT = "CP"
185
+ CONTRACTSPLIT = "CS"
186
+ CFDTERMINATION = "CT"
187
+ DIVRIGHTSISSUE = "DI"
188
+ DELISTWORTHLESS = "DW"
189
+ EXPIREDIVRIGHT = "ED"
190
+ FEEALLOCATION = "FA"
191
+ FORWARDSPLITISSUE = "FI"
192
+ FORWARDSPLIT = "FS"
193
+ GENERICVOLUNTARY = "GV"
194
+ CHOICEDIVDELIVERY = "HD"
195
+ CHOICEDIVISSUE = "HI"
196
+ ISSUECHANGE = "IC"
197
+ ASSETPURCHASE = "OR"
198
+ PURCHASEISSUE = "PI"
199
+ PROXYVOTE = "PV"
200
+ RIGHTSISSUE = "RI"
201
+ REVERSESPLIT = "RS"
202
+ STOCKDIV = "SD"
203
+ SPINOFF = "SO"
204
+ SUBSCRIBERIGHTS = "SR"
205
+ MERGER = "TC"
206
+ TENDERISSUE = "TI"
207
+ TENDER = "TO"
208
+ TBILLMATURITY = "TM"
209
+
210
+
211
+ @enum.unique
212
+ class OptionAction(str, enum.Enum):
213
+ ASSIGN = "Assignment"
214
+ EXERCISE = "Exercise"
215
+ EXPIRE = "Expiration"
216
+ SELL = "Sell"
217
+ BUY = "Buy"
218
+ CASHSETTLEMENT = "Cash Settlement"
219
+
220
+
221
+ @enum.unique
222
+ class LongShort(str, enum.Enum):
223
+ LONG = "Long"
224
+ SHORT = "Short"
225
+
226
+
227
+ @enum.unique
228
+ class ToFrom(str, enum.Enum):
229
+ TO = "To"
230
+ FROM = "From"
231
+
232
+
233
+ @enum.unique
234
+ class TransferType(str, enum.Enum):
235
+ INTERNAL = "INTERNAL"
236
+ ACATS = "ACATS"
237
+ ATON = "ATON"
238
+ FOP = "FOP"
239
+
240
+
241
+ @enum.unique
242
+ class InOut(str, enum.Enum):
243
+ IN = "IN"
244
+ OUT = "OUT"
245
+
246
+
247
+ @enum.unique
248
+ class DeliveredReceived(str, enum.Enum):
249
+ DELIVERED = "Delivered"
250
+ RECEIVED = "Received"
251
+
252
+
253
+ @enum.unique
254
+ class PutCall(str, enum.Enum):
255
+ PUT = "P"
256
+ CALL = "C"
257
+
258
+
259
+ ENUMS = [
260
+ CashAction,
261
+ Code,
262
+ AssetClass,
263
+ TradeType,
264
+ BuySell,
265
+ OpenClose,
266
+ OrderType,
267
+ Reorg,
268
+ OptionAction,
269
+ LongShort,
270
+ ToFrom,
271
+ TransferType,
272
+ InOut,
273
+ DeliveredReceived,
274
+ PutCall,
275
+ ]
276
+ """Used by ibflex.parser.ATTRIB_CONVERTERS"""
277
+
278
+ EnumType = typing.Union[
279
+ typing.Optional[CashAction],
280
+ typing.Optional[Code],
281
+ typing.Optional[AssetClass],
282
+ typing.Optional[TradeType],
283
+ typing.Optional[BuySell],
284
+ typing.Optional[OpenClose],
285
+ typing.Optional[OrderType],
286
+ typing.Optional[Reorg],
287
+ typing.Optional[OptionAction],
288
+ typing.Optional[LongShort],
289
+ typing.Optional[ToFrom],
290
+ typing.Optional[TransferType],
291
+ typing.Optional[InOut],
292
+ typing.Optional[DeliveredReceived],
293
+ typing.Optional[PutCall],
294
+ ]
295
+ """Used by ibflex.parser.DataType"""