diff --git a/ibflex/Types.py b/ibflex/Types.py
index 6ab7555..fb8f4b2 100644
--- a/ibflex/Types.py
+++ b/ibflex/Types.py
@@ -479,6 +479,7 @@ class EquitySummaryByReportDateInBase(FlexElement):
marginFinancingChargeAccrualsShort: Optional[decimal.Decimal] = None
cryptoLong: Optional[decimal.Decimal] = None
cryptoShort: Optional[decimal.Decimal] = None
+ liteSurchargeAccruals: Optional[decimal.Decimal] = None
@dataclass(frozen=True)
@@ -709,6 +710,8 @@ class CashReportCurrency(FlexElement):
salesTaxYTD: Optional[decimal.Decimal] = None
salesTaxPaxos: Optional[decimal.Decimal] = None
otherIncome: Optional[decimal.Decimal] = None
+ otherIncomeMTD: Optional[decimal.Decimal] = None
+ otherIncomeYTD: Optional[decimal.Decimal] = None
otherIncomeSec: Optional[decimal.Decimal] = None
otherIncomeCom: Optional[decimal.Decimal] = None
otherFeesMTD: Optional[decimal.Decimal] = None
@@ -1144,7 +1147,8 @@ class Trade(FlexElement):
subCategory: Optional[str] = None
issuerCountryCode: Optional[str] = None
rtn: Optional[str] = None
- initialInvestment: Optional[decimal.Decimal] = None
+ initialInvestment: Optional[bool] = None
+ positionActionID: Optional[str] = None
@dataclass(frozen=True)
@@ -1296,7 +1300,8 @@ class Lot(FlexElement):
issuerCountryCode: Optional[str] = None
relatedTradeID: Optional[str] = None
rtn: Optional[str] = None
- initialInvestment: Optional[decimal.Decimal] = None
+ initialInvestment: Optional[bool] = None
+ positionActionID: Optional[str] = None
@dataclass(frozen=True)
@@ -1385,6 +1390,9 @@ class SymbolSummary(FlexElement):
tradeID: Optional[str] = None
orderID: Optional[decimal.Decimal] = None
execID: Optional[str] = None
+ ibExecID: Optional[str] = None
+ extExecID: Optional[str] = None
+ exchOrderId: Optional[str] = None
brokerageOrderID: Optional[str] = None
orderReference: Optional[str] = None
volatilityOrderLink: Optional[str] = None
@@ -1392,12 +1400,19 @@ class SymbolSummary(FlexElement):
origTradePrice: Optional[decimal.Decimal] = None
origTradeDate: Optional[datetime.date] = None
origTradeID: Optional[str] = None
+ transactionID: Optional[str] = None
# Despite the name, `orderTime` actually contains date/time data.
orderTime: Optional[datetime.datetime] = None
+ openDateTime: Optional[datetime.datetime] = None
+ holdingPeriodDateTime: Optional[datetime.datetime] = None
dateTime: Optional[datetime.datetime] = None
reportDate: Optional[datetime.date] = None
settleDate: Optional[datetime.date] = None
+ settleDateTarget: Optional[datetime.date] = None # expected date of ownership transfer
+ taxes: Optional[decimal.Decimal] = None
tradeDate: Optional[datetime.date] = None
+ tradePrice: Optional[decimal.Decimal] = None
+ tradeMoney: Optional[decimal.Decimal] = None # TradeMoney = Proceeds + Fees + Commissions
exchange: Optional[str] = None
buySell: Optional[enums.BuySell] = None
quantity: Optional[decimal.Decimal] = None
@@ -1427,6 +1442,40 @@ class SymbolSummary(FlexElement):
relatedTradeID: Optional[str] = None
origTransactionID: Optional[str] = None
relatedTransactionID: Optional[str] = None
+ positionActionID: Optional[str] = None
+ changeInPrice: Optional[decimal.Decimal] = None
+ changeInQuantity: Optional[decimal.Decimal] = None
+ closePrice: Optional[decimal.Decimal] = None
+ commodityType: Optional[str] = None
+ cost: Optional[decimal.Decimal] = None
+ deliveryType: Optional[str] = None
+ exchOrderId: Optional[str] = None
+ extExecID: Optional[str] = None
+ fifoPnlRealized: Optional[decimal.Decimal] = None
+ fineness: Optional[decimal.Decimal] = None
+ holdingPeriodDateTime: Optional[datetime.datetime] = None
+ ibCommission: Optional[decimal.Decimal] = None
+ ibCommissionCurrency: Optional[str] = None
+ ibExecID: Optional[str] = None
+ ibOrderID: Optional[str] = None
+ initialInvestment: Optional[bool] = None
+ mtmPnl: Optional[decimal.Decimal] = None
+ netCash: Optional[decimal.Decimal] = None
+ netCashInBase: Optional[decimal.Decimal] = None
+ notes: Optional[str] = None
+ openCloseIndicator: Optional[enums.OpenClose] = None
+ openDateTime: Optional[datetime.datetime] = None
+ origOrderID: Optional[str] = None
+ rtn: Optional[str] = None
+ serialNumber: Optional[str] = None
+ settleDateTarget: Optional[datetime.date] = None
+ taxes: Optional[decimal.Decimal] = None
+ tradeMoney: Optional[decimal.Decimal] = None
+ tradePrice: Optional[decimal.Decimal] = None
+ transactionID: Optional[str] = None
+ weight: Optional[str] = None
+ whenRealized: Optional[datetime.datetime] = None
+ whenReopened: Optional[datetime.datetime] = None
@dataclass(frozen=True)
@@ -1535,7 +1584,8 @@ class AssetSummary(FlexElement):
origTransactionID: Optional[str] = None
relatedTransactionID: Optional[str] = None
rtn: Optional[str] = None
- initialInvestment: Optional[decimal.Decimal] = None
+ initialInvestment: Optional[bool] = None
+ positionActionID: Optional[str] = None
@dataclass(frozen=True)
@@ -1638,12 +1688,13 @@ class Order(FlexElement):
origTransactionID: Optional[str] = None
relatedTransactionID: Optional[str] = None
rtn: Optional[str] = None
- initialInvestment: Optional[decimal.Decimal] = None
+ initialInvestment: Optional[bool] = None
serialNumber: Optional[str] = None
deliveryType: Optional[str] = None
commodityType: Optional[str] = None
fineness: Optional[decimal.Decimal] = None
weight: Optional[str] = None
+ positionActionID: Optional[str] = None
@dataclass(frozen=True)
@@ -1796,7 +1847,7 @@ class OptionEAE(FlexElement):
origTransactionID: Optional[str] = None
relatedTransactionID: Optional[str] = None
rtn: Optional[str] = None
- initialInvestment: Optional[decimal.Decimal] = None
+ initialInvestment: Optional[bool] = None
serialNumber: Optional[str] = None
deliveryType: Optional[str] = None
commodityType: Optional[str] = None
@@ -2122,7 +2173,12 @@ class Transfer(FlexElement):
commodityType: Optional[str] = None
fineness: Optional[decimal.Decimal] = None
weight: Optional[str] = None
-
+ figi: Optional[str] = None
+ settleDate: Optional[datetime.date] = None
+ issuerCountryCode: Optional[str] = None
+ levelOfDetail: Optional[str] = None
+ positionInstructionID: Optional[str] = None
+ positionInstructionSetID: Optional[str] = None
@dataclass(frozen=True)
class UnsettledTransfer(FlexElement):
@@ -2245,6 +2301,11 @@ class CorporateAction(FlexElement):
commodityType: Optional[str] = None
fineness: Optional[decimal.Decimal] = None
weight: Optional[str] = None
+ figi: Optional[str] = None
+ issuerCountryCode: Optional[str] = None
+ costBasis: Optional[decimal.Decimal] = None
+
+
@dataclass(frozen=True)
@@ -2480,7 +2541,7 @@ class SecurityInfo(FlexElement):
origTransactionID: Optional[str] = None
relatedTransactionID: Optional[str] = None
rtn: Optional[str] = None
- initialInvestment: Optional[decimal.Decimal] = None
+ initialInvestment: Optional[bool] = None
serialNumber: Optional[str] = None
deliveryType: Optional[str] = None
commodityType: Optional[str] = None
diff --git a/ibflex/client.py b/ibflex/client.py
index f203e8b..041f53a 100755
--- a/ibflex/client.py
+++ b/ibflex/client.py
@@ -136,8 +136,6 @@ def request_statement(
"""First part of the 2-step download process.
"""
url = url or REQUEST_URL
- ### AKE FIX
- url = 'https://ndcdyn.interactivebrokers.com/portal.flexweb/api/v1/flexQuery'
response = submit_request(url, token, query=query_id)
stmt_access = parse_stmt_response(response)
if isinstance(stmt_access, StatementError):
diff --git a/ibflex/parser.py b/ibflex/parser.py
index af6b270..1bf081a 100755
--- a/ibflex/parser.py
+++ b/ibflex/parser.py
@@ -170,9 +170,11 @@ def parse_element_attr(
# INPUT VALUE PREP FUNCTIONS FOR DATA CONVERTERS
# These are just implementation details for converters and don't need testing.
###############################################################################
-def prep_date(value: str) -> Tuple[int, int, int]:
+def prep_date(value: str) -> Optional[Tuple[int, int, int]]:
"""Returns a tuple of (year, month, day).
"""
+ if value == "MULTI":
+ return None # Summaries have MULTI as date value.
date_format = DATE_FORMATS[len(value)][value.count('/')]
return datetime.datetime.strptime(value, date_format).timetuple()[:3]
@@ -184,9 +186,11 @@ def prep_time(value: str) -> Tuple[int, int, int]:
return datetime.datetime.strptime(value, time_format).timetuple()[3:6]
-def prep_datetime(value: str) -> Tuple[int, ...]:
+def prep_datetime(value: str) -> Optional[Tuple[int, ...]]:
"""Returns a tuple of (year, month, day, hour, minute, second).
"""
+ if value == "MULTI":
+ return None # Summaries have MULTI as date value.
# HACK - some old data has ", " separator instead of ",".
value = value.replace(", ", ",")
@@ -328,8 +332,8 @@ def optional_convert(value):
convert_string = make_optional(make_converter(str, prep=utils.identity_func))
convert_int = make_converter(int, prep=utils.identity_func)
-# IB sends "Y"/"N" for True/False
-convert_bool = make_converter(bool, prep=lambda x: {"Y": True, "N": False}[x])
+# IB sends "Y"/"N" or "Yes"/"No" for True/False
+convert_bool = make_converter(bool, prep=lambda x: {"Y": True, "N": False, "Yes": True, "No": False}[x])
# IB sends numeric data with place delimiters (commas)
convert_decimal = make_converter(
decimal.Decimal,
@@ -463,6 +467,7 @@ def convert_enum(Type, value):
"CNH", # RMB traded in HK
"BASE_SUMMARY", # Fake currency code used in IB NAV/Performance reports
"", # Lot element allows blank currency ?!
+ "RUS", # Russian-related currency code used by IBKR
)
diff --git a/tests/test_parser.py b/tests/test_parser.py
index 92fab3f..31fb665 100644
--- a/tests/test_parser.py
+++ b/tests/test_parser.py
@@ -333,9 +333,11 @@ def testConvertInt(self):
parser.convert_int("")
def testConvertBool(self):
- """ Legal boolean values are 'Y'/'N' """
+ """ Legal boolean values are 'Y'/'N' or 'Yes'/'No' """
self.assertEqual(parser.convert_bool("Y"), True)
self.assertEqual(parser.convert_bool("N"), False)
+ self.assertEqual(parser.convert_bool("Yes"), True)
+ self.assertEqual(parser.convert_bool("No"), False)
# Empty string raises FlexParserError.
with self.assertRaises(parser.FlexParserError):
diff --git a/tests/test_types.py b/tests/test_types.py
index bf53cf4..175eaa5 100644
--- a/tests/test_types.py
+++ b/tests/test_types.py
@@ -1985,5 +1985,49 @@ def testParse(self):
self.assertEqual(instance.tradeID, None)
+class TradeInitialInvestmentTestCase(unittest.TestCase):
+ """Test case for Trade.initialInvestment as boolean field.
+
+ Tests the fix for https://github.com/vroonhof/opensteuerauszug/issues/106
+ where initialInvestment="Yes" was causing parsing errors.
+ """
+ data = ET.fromstring(
+ ('')
+ )
+
+ def testParse(self):
+ instance = parser.parse_data_element(self.data)
+ self.assertIsInstance(instance, Types.Trade)
+ self.assertEqual(instance.accountId, "U123456")
+ self.assertEqual(instance.currency, "USD")
+ self.assertEqual(instance.assetCategory, enums.AssetClass.STOCK)
+ self.assertEqual(instance.symbol, "TEST")
+ self.assertEqual(instance.initialInvestment, True)
+ self.assertEqual(instance.quantity, decimal.Decimal("100"))
+
+
+class EquitySummaryLiteSurchargeAccrualsTestCase(unittest.TestCase):
+ """Test case for EquitySummaryByReportDateInBase.liteSurchargeAccruals field.
+
+ Tests the fix for https://github.com/vroonhof/opensteuerauszug/issues/106
+ where liteSurchargeAccruals attribute was missing.
+ """
+ data = ET.fromstring(
+ ('')
+ )
+
+ def testParse(self):
+ instance = parser.parse_data_element(self.data)
+ self.assertIsInstance(instance, Types.EquitySummaryByReportDateInBase)
+ self.assertEqual(instance.accountId, "U123456")
+ self.assertEqual(instance.reportDate, datetime.date(2024, 1, 1))
+ self.assertEqual(instance.cash, decimal.Decimal("1000.00"))
+ self.assertEqual(instance.total, decimal.Decimal("1000.00"))
+ self.assertEqual(instance.liteSurchargeAccruals, decimal.Decimal("5.50"))
+
+
if __name__ == '__main__':
unittest.main(verbosity=3)