From cbf4f78ee3649c733e6ef1543786fff6db0cbf13 Mon Sep 17 00:00:00 2001 From: Lorenz Bauer Date: Tue, 21 Oct 2025 18:36:38 +0100 Subject: [PATCH 1/3] IBFlex: Refactor extractDate to work on all transaction types Use a single method to extract a LocalDateTime from a transaction element. This improves the result of some imports by adding LocalTime when there previously wasn't one. --- .../ibflex/IBFlexStatementExtractorTest.java | 18 ++-- .../ibflex/IBFlexStatementExtractor.java | 98 +++++++++++++------ 2 files changed, 77 insertions(+), 39 deletions(-) diff --git a/name.abuchen.portfolio.tests/src/name/abuchen/portfolio/datatransfer/ibflex/IBFlexStatementExtractorTest.java b/name.abuchen.portfolio.tests/src/name/abuchen/portfolio/datatransfer/ibflex/IBFlexStatementExtractorTest.java index 12a97b1e8a..2b43a67ce7 100644 --- a/name.abuchen.portfolio.tests/src/name/abuchen/portfolio/datatransfer/ibflex/IBFlexStatementExtractorTest.java +++ b/name.abuchen.portfolio.tests/src/name/abuchen/portfolio/datatransfer/ibflex/IBFlexStatementExtractorTest.java @@ -1955,7 +1955,7 @@ public void testIBFlexStatementFile12() throws IOException // assert transaction AccountTransaction transaction = (AccountTransaction) item.getSubject(); assertThat(transaction.getType(), is(AccountTransaction.Type.TAX_REFUND)); - assertThat(transaction.getDateTime(), is(LocalDateTime.parse("2019-01-31T00:00"))); + assertThat(transaction.getDateTime(), is(LocalDateTime.parse("2019-01-31T20:20"))); assertThat(transaction.getMonetaryAmount(), is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(1.81)))); assertNull(transaction.getSource()); assertThat(transaction.getNote(), is("XXXXXUSD")); @@ -1968,7 +1968,7 @@ public void testIBFlexStatementFile12() throws IOException // assert transaction transaction = (AccountTransaction) item.getSubject(); assertThat(transaction.getType(), is(AccountTransaction.Type.TAXES)); - assertThat(transaction.getDateTime(), is(LocalDateTime.parse("2019-01-31T00:00"))); + assertThat(transaction.getDateTime(), is(LocalDateTime.parse("2019-01-31T20:20"))); assertThat(transaction.getMonetaryAmount(), is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(0.49)))); assertNull(transaction.getSource()); assertThat(transaction.getNote(), is("XXXXXUSD")); @@ -1981,7 +1981,7 @@ public void testIBFlexStatementFile12() throws IOException // assert transaction transaction = (AccountTransaction) item.getSubject(); assertThat(transaction.getType(), is(AccountTransaction.Type.TAXES)); - assertThat(transaction.getDateTime(), is(LocalDateTime.parse("2020-01-02T00:00"))); + assertThat(transaction.getDateTime(), is(LocalDateTime.parse("2020-01-02T20:20"))); assertThat(transaction.getMonetaryAmount(), is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(3.55)))); assertNull(transaction.getSource()); assertThat(transaction.getNote(), is("XXXXXUSD")); @@ -1995,7 +1995,7 @@ public void testIBFlexStatementFile12() throws IOException assertThat(transaction.getType(), is(AccountTransaction.Type.DIVIDENDS)); - assertThat(transaction.getDateTime(), is(LocalDateTime.parse("2019-12-31T00:00"))); + assertThat(transaction.getDateTime(), is(LocalDateTime.parse("2019-12-31T20:20"))); assertThat(transaction.getShares(), is(Values.Share.factorize(0))); assertNull(transaction.getSource()); assertThat(transaction.getNote(), is("XXXXXCAD")); @@ -2016,7 +2016,7 @@ public void testIBFlexStatementFile12() throws IOException assertThat(transaction.getType(), is(AccountTransaction.Type.DIVIDENDS)); - assertThat(transaction.getDateTime(), is(LocalDateTime.parse("2020-01-15T00:00"))); + assertThat(transaction.getDateTime(), is(LocalDateTime.parse("2020-01-15T20:20"))); assertThat(transaction.getShares(), is(Values.Share.factorize(0))); assertNull(transaction.getSource()); assertThat(transaction.getNote(), is("XXXXXCAD")); @@ -2037,7 +2037,7 @@ public void testIBFlexStatementFile12() throws IOException assertThat(transaction.getType(), is(AccountTransaction.Type.DIVIDENDS)); - assertThat(transaction.getDateTime(), is(LocalDateTime.parse("2020-01-02T00:00"))); + assertThat(transaction.getDateTime(), is(LocalDateTime.parse("2020-01-02T20:20"))); assertThat(transaction.getShares(), is(Values.Share.factorize(0))); assertNull(transaction.getSource()); assertThat(transaction.getNote(), is("XXXXXUSD")); @@ -2058,7 +2058,7 @@ public void testIBFlexStatementFile12() throws IOException assertThat(transaction.getType(), is(AccountTransaction.Type.DIVIDENDS)); - assertThat(transaction.getDateTime(), is(LocalDateTime.parse("2020-01-02T00:00"))); + assertThat(transaction.getDateTime(), is(LocalDateTime.parse("2020-01-02T20:20"))); assertThat(transaction.getShares(), is(Values.Share.factorize(0))); assertNull(transaction.getSource()); assertNull(transaction.getNote()); @@ -2082,7 +2082,7 @@ public void testIBFlexStatementFile12() throws IOException // assert transaction transaction = (AccountTransaction) item.getSubject(); assertThat(transaction.getType(), is(AccountTransaction.Type.FEES)); - assertThat(transaction.getDateTime(), is(LocalDateTime.parse("2020-01-03T00:00"))); + assertThat(transaction.getDateTime(), is(LocalDateTime.parse("2020-01-03T17:59:42"))); assertThat(transaction.getMonetaryAmount(), is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(1.34)))); assertNull(transaction.getSource()); assertThat(transaction.getNote(), is("XXXXXUSD")); @@ -2092,7 +2092,7 @@ public void testIBFlexStatementFile12() throws IOException // assert transaction transaction = (AccountTransaction) item.getSubject(); assertThat(transaction.getType(), is(AccountTransaction.Type.FEES)); - assertThat(transaction.getDateTime(), is(LocalDateTime.parse("2020-01-03T00:00"))); + assertThat(transaction.getDateTime(), is(LocalDateTime.parse("2020-01-03T17:59:42"))); assertThat(transaction.getMonetaryAmount(), is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(1.34)))); assertNull(transaction.getSource()); assertThat(transaction.getNote(), is("XXXXXUSD")); diff --git a/name.abuchen.portfolio/src/name/abuchen/portfolio/datatransfer/ibflex/IBFlexStatementExtractor.java b/name.abuchen.portfolio/src/name/abuchen/portfolio/datatransfer/ibflex/IBFlexStatementExtractor.java index 1b7cd1ef46..3ac8a80f00 100644 --- a/name.abuchen.portfolio/src/name/abuchen/portfolio/datatransfer/ibflex/IBFlexStatementExtractor.java +++ b/name.abuchen.portfolio/src/name/abuchen/portfolio/datatransfer/ibflex/IBFlexStatementExtractor.java @@ -10,6 +10,10 @@ import java.text.MessageFormat; import java.time.LocalDate; import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeFormatterBuilder; +import java.time.format.DateTimeParseException; +import java.time.temporal.ChronoField; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -17,6 +21,7 @@ import java.util.HashMap; import java.util.HashSet; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.Optional; import java.util.Set; @@ -84,6 +89,29 @@ public class IBFlexStatementExtractor implements Extractor // Map to store exchange mappings from Interactive Broker to Yahoo private Map exchanges = new HashMap<>(); + // dateTime attribute formats by tag: + // + // | Tag | Format Pattern(s) | + // |-----------------|-----------------------------------------| + // | Trade | YYYYMMDD;HHMMSS | + // | FxTransaction | YYYYMMDD;HHMMSS | + // | CorporateAction | YYYY-MM-DD, HH:MM:SS OR YYYYMMDD;HHMMSS | + // | CashTransaction | YYYY-MM-DD OR YYYYMMDD[;HHMMSS] | + private static final DateTimeFormatter[] DATE_TIME_FORMATTER = { + createFormatter("yyyyMMdd[;HHmmss]"), // + createFormatter("yyyy-MM-dd[, HH:mm:ss]"), // + }; + + private static DateTimeFormatter createFormatter(String pattern) + { + return new DateTimeFormatterBuilder() + .appendPattern(pattern) + .parseDefaulting(ChronoField.HOUR_OF_DAY, 0) + .parseDefaulting(ChronoField.MINUTE_OF_HOUR, 0) + .parseDefaulting(ChronoField.SECOND_OF_MINUTE, 0) + .toFormatter(Locale.US); + } + /** * Constructs an IBFlexStatementExtractor with the given client. * Initializes the list of securities and exchange mappings. @@ -222,19 +250,7 @@ private class IBFlexStatementExtractorResult private Consumer buildAccountTransaction = element -> { AccountTransaction accountTransaction = new AccountTransaction(); - // @formatter:off - // New Format dateTime has now also Time [YYYYMMDD;HHMMSS], I cut - // Date from string [YYYYMMDD] - // - // Checks for old format [YYYY-MM-DD, HH:MM:SS], too. Quapla 11.1.20 - // Changed from dateTime to reportDate + Check for old Data-Formats, - // Quapla 14.2.20 - // @formatter:on - String dateTime = !element.getAttribute("reportDate").isEmpty() ? element.getAttribute("reportDate") - : element.getAttribute("dateTime"); - - dateTime = dateTime.length() == 15 ? dateTime.substring(0, 8) : dateTime; - accountTransaction.setDateTime(ExtractorUtils.asDate(dateTime)); + accountTransaction.setDateTime(extractDate(element)); // Set amount Money amount = Money.of(asCurrencyCode(element.getAttribute("currency")), @@ -787,36 +803,58 @@ private void setAmount(Element element, Transaction transaction, Money amount, b * If possible, set "tradeDate" with "tradeTime" as the correct trading * date of the transaction. *

- * If "tradeTime" is not present, then check if "tradeDate" and - * "dateTime" are the same date, then set "dateTime" as the trading day. - * + * If "tradeDate" is not present, use "dateTime" if possible. + * * @return the extracted date or null if not available */ private LocalDateTime extractDate(Element element) { - if (!element.hasAttribute("tradeDate")) - return null; + String dateTime = ""; - if (element.hasAttribute("tradeTime")) - { - return ExtractorUtils.asDate(element.getAttribute("tradeDate"), element.getAttribute("tradeTime")); - } - else if (element.hasAttribute("dateTime")) + // Prefer tradeDate over dateTime for tags. + if (element.hasAttribute("tradeDate")) { - if (element.getAttribute("tradeDate").equals(element.getAttribute("dateTime").substring(0, 8))) + dateTime = element.getAttribute("tradeDate"); + + if (element.hasAttribute("tradeTime")) { - return ExtractorUtils.asDate(element.getAttribute("tradeDate"), - element.getAttribute("dateTime").substring(9, 15)); + dateTime += ";" + element.getAttribute("tradeTime"); } - else + else if (element.hasAttribute("dateTime") + && dateTime.equals(element.getAttribute("dateTime").substring(0, 8))) { - return ExtractorUtils.asDate(element.getAttribute("tradeDate")); + dateTime = element.getAttribute("dateTime"); } } - else + + // Prefer reportDate over dateTime for tags. + if (dateTime.isEmpty() && element.hasAttribute("reportDate")) { - return ExtractorUtils.asDate(element.getAttribute("tradeDate")); + dateTime = element.getAttribute("reportDate"); } + + // All other tags. + if (dateTime.isEmpty() && element.hasAttribute("dateTime")) + { + dateTime = element.getAttribute("dateTime"); + } + + if (!dateTime.isEmpty()) + { + for (DateTimeFormatter formatter : DATE_TIME_FORMATTER) + { + try + { + return LocalDateTime.parse(dateTime, formatter); + } + catch (DateTimeParseException ignore) + { + continue; + } + } + } + + return null; } /** From 20f5503f044ecdd8a0d67577fab322d215f7e51b Mon Sep 17 00:00:00 2001 From: Lorenz Bauer Date: Tue, 21 Oct 2025 18:06:24 +0100 Subject: [PATCH 2/3] IBFlex: Improve handling of currency conversions The existing exchange rate calculations are spread all over the place and are not entirely consistent. For example, fxRateToBase can only safely be used if the accountCurrency is known. Improve this by using fxRateToBase correctly and by parsing ConversionRate elements contained in the IB XML. This allows doing cross currency conversions even if the accountCurrency is not known. --- .../ibflex/IBFlexStatementExtractor.java | 125 ++++++++++++++---- 1 file changed, 102 insertions(+), 23 deletions(-) diff --git a/name.abuchen.portfolio/src/name/abuchen/portfolio/datatransfer/ibflex/IBFlexStatementExtractor.java b/name.abuchen.portfolio/src/name/abuchen/portfolio/datatransfer/ibflex/IBFlexStatementExtractor.java index 3ac8a80f00..6dd7544039 100644 --- a/name.abuchen.portfolio/src/name/abuchen/portfolio/datatransfer/ibflex/IBFlexStatementExtractor.java +++ b/name.abuchen.portfolio/src/name/abuchen/portfolio/datatransfer/ibflex/IBFlexStatementExtractor.java @@ -112,6 +112,9 @@ private static DateTimeFormatter createFormatter(String pattern) .toFormatter(Locale.US); } + // Map to store currency conversion rates by (date, "fromCurrency-toCurrency") + private Map, BigDecimal> conversionRates = new HashMap<>(); + /** * Constructs an IBFlexStatementExtractor with the given client. * Initializes the list of securities and exchange mappings. @@ -225,6 +228,24 @@ private class IBFlexStatementExtractorResult private List results = new ArrayList<>(); private String accountCurrency = null; + /** + * Processes ConversionRate elements to build currency conversion rate mapping. + * Stores exchange rates from each currency to the account base currency. + */ + private Consumer buildConversionRates = element -> { + String reportDate = element.getAttribute("reportDate"); + String toCurrency = element.getAttribute("toCurrency"); + String fromCurrency = element.getAttribute("fromCurrency"); + String rateStr = element.getAttribute("rate"); + + if (!rateStr.equals("-1")) + { + BigDecimal rate = asExchangeRate(rateStr); + Pair key = new Pair<>(reportDate, fromCurrency + "-" + toCurrency); + conversionRates.put(key, rate); + } + }; + /** * Builds account information based on the provided XML element. Extracts the currency * attribute from the element, converts it to a currency code, and sets the corresponding @@ -710,12 +731,10 @@ private Unit createUnit(Element element, Unit.Type unitType, Money amount) } else { - // Calculate the FX rate to the base currency - BigDecimal fxRateToBase = element.getAttribute("fxRateToBase").isEmpty() ? BigDecimal.ONE - : asExchangeRate(element.getAttribute("fxRateToBase")); + BigDecimal exchangeRate = getExchangeRate(element, amount.getCurrencyCode(), accountCurrency); // Calculate the inverse rate - BigDecimal inverseRate = BigDecimal.ONE.divide(fxRateToBase, 10, RoundingMode.HALF_DOWN); + BigDecimal inverseRate = BigDecimal.ONE.divide(exchangeRate, 10, RoundingMode.HALF_DOWN); // Convert the amount to the IB account currency using the // inverse rate @@ -723,7 +742,7 @@ private Unit createUnit(Element element, Unit.Type unitType, Money amount) .divide(inverseRate, Values.MC).setScale(0, RoundingMode.HALF_UP).longValue()); // Create a Unit with FX amount, original amount, and FX rate - unit = new Unit(unitType, fxAmount, amount, fxRateToBase); + unit = new Unit(unitType, fxAmount, amount, exchangeRate); } return unit; @@ -753,17 +772,16 @@ private void setAmount(Element element, Transaction transaction, Money amount, b { if (accountCurrency != null && !accountCurrency.equals(amount.getCurrencyCode())) { - BigDecimal fxRateToBase = element.getAttribute("fxRateToBase").isEmpty() ? BigDecimal.ONE - : asExchangeRate(element.getAttribute("fxRateToBase")); + BigDecimal exchangeRate = getExchangeRate(element, amount.getCurrencyCode(), accountCurrency); Money fxAmount = Money.of(accountCurrency, BigDecimal.valueOf(amount.getAmount()) - .multiply(fxRateToBase).setScale(0, RoundingMode.HALF_UP).longValue()); + .multiply(exchangeRate).setScale(0, RoundingMode.HALF_UP).longValue()); transaction.setMonetaryAmount(fxAmount); if (addUnit) { - Unit grossValue = new Unit(Unit.Type.GROSS_VALUE, fxAmount, amount, fxRateToBase); + Unit grossValue = new Unit(Unit.Type.GROSS_VALUE, fxAmount, amount, exchangeRate); transaction.addUnit(grossValue); } } @@ -776,23 +794,19 @@ private void setAmount(Element element, Transaction transaction, Money amount, b // @formatter:off // If the transaction currency is different from the security currency (as stored in PP) // we need to supply the gross value in the security currency. - // - // We assume that the security currency is the same that IB - // thinks of as base currency for this transaction (fxRateToBase). // @formatter:on - BigDecimal fxRateToBase = element.getAttribute("fxRateToBase").isEmpty() ? BigDecimal.ONE - : asExchangeRate(element.getAttribute("fxRateToBase")); + BigDecimal exchangeRate = getExchangeRate(element, amount.getCurrencyCode(), transaction.getSecurity().getCurrencyCode()); - BigDecimal inverseRate = BigDecimal.ONE.divide(fxRateToBase, 10, RoundingMode.HALF_DOWN); + if (exchangeRate != null) { + BigDecimal inverseRate = BigDecimal.ONE.divide(exchangeRate, 10, RoundingMode.HALF_DOWN); - Money fxAmount = Money.of(transaction.getSecurity().getCurrencyCode(), - BigDecimal.valueOf(amount.getAmount()).divide(inverseRate, Values.MC) - .setScale(0, RoundingMode.HALF_UP).longValue()); + Money fxAmount = Money.of(transaction.getSecurity().getCurrencyCode(), + BigDecimal.valueOf(amount.getAmount()).divide(inverseRate, Values.MC) + .setScale(0, RoundingMode.HALF_UP).longValue()); - transaction.setMonetaryAmount(amount); - - Unit grossValue = new Unit(Unit.Type.GROSS_VALUE, amount, fxAmount, inverseRate); - transaction.addUnit(grossValue); + Unit grossValue = new Unit(Unit.Type.GROSS_VALUE, amount, fxAmount, inverseRate); + transaction.addUnit(grossValue); + } } } } @@ -882,6 +896,68 @@ private String extractNote(Element element) return note.length() > 0 ? note.toString() : null; } + /** + * Gets the exchange rate to go from fromCurrency to toCurrency. + * + * @param element The XML element containing transaction details + * @param fromCurrency The currency to convert from + * @param toCurrency The currency to convert to + * @return The exchange rate, or null if it cannot be determined + */ + private BigDecimal getExchangeRate(Element element, String fromCurrency, String toCurrency) + { + if (fromCurrency.equals(toCurrency)) + return BigDecimal.ONE; + + LocalDateTime dateTime = extractDate(element); + if (dateTime == null) + { + return null; + } + + String dateStr = dateTime.format(DateTimeFormatter.ofPattern("yyyyMMdd")); + + // Attempt a direct lookup in the rates table. + Pair key = new Pair<>(dateStr, fromCurrency + "-" + toCurrency); + BigDecimal rate = conversionRates.get(key); + if (rate != null) + { + return rate; + } + + key = new Pair<>(dateStr, toCurrency + "-" + fromCurrency); + rate = conversionRates.get(key); + if (rate != null) + return ExchangeRate.inverse(rate); + + // Fall back to using accountCurrency as an intermediate. + if (accountCurrency != null) + { + if (toCurrency.equals(accountCurrency) && element.hasAttribute("fxRateToBase")) + { + // Avoid cross rate if possible by using fxRateToBase from the + // transaction element itself. + return asExchangeRate(element.getAttribute("fxRateToBase")); + } + + // Attempt to calculate cross rate via accountCurrency. No use + // in trying a different intermediate currency, it seems like + // toCurrency is only ever the account's base. + Pair fromKey = new Pair<>(dateStr, fromCurrency + "-" + accountCurrency); + Pair toKey = new Pair<>(dateStr, toCurrency + "-" + accountCurrency); + + BigDecimal fromRate = conversionRates.get(fromKey); + BigDecimal toRate = conversionRates.get(toKey); + + if (fromRate != null && toRate != null) + { + return fromRate.divide(toRate, 10, RoundingMode.HALF_DOWN); + } + } + + return null; + } + /** * @formatter:off * Imports model objects from the document based on the specified type using the provided handling function. @@ -930,6 +1006,9 @@ public void parseDocument(Document doc) if (document == null) return; + // Process conversion rates first + importModelObjects("ConversionRate", buildConversionRates); + // Import AccountInformation importModelObjects("AccountInformation", buildAccountInformation); @@ -948,7 +1027,7 @@ public void parseDocument(Document doc) // Process all SalesTaxes importModelObjects("SalesTax", buildSalesTaxTransaction); - // TODO: Process all FxTransactions and ConversionRates + // TODO: Process all FxTransactions } public void addError(Exception e) From 4d4b293114d16c2646d07fd98d91f29f1f44de15 Mon Sep 17 00:00:00 2001 From: Lorenz Bauer Date: Tue, 21 Oct 2025 18:12:09 +0100 Subject: [PATCH 3/3] IBFlex: Remove most currency conversions The IBFlex importer currently has a set of historically grown heuristics around currency conversions: 1. DIVIDEND and TAX transactions are converted to account base currency in buildAccountTransaction() 2. BUY and SELL transactions are converted to account base currency in buildPortfolioTransaction() As far as I can tell, Interactive Brokers doesn't actually work the way that these heuristics assume. - There are no automatic currency conversions, the platform creates forex balances on the fly. - Performing a trade in a foreign currency requires a balance in that currency. - Dividends are distributed in the "base" currency of a security. - fxRateToBase attributes give the exchange rate to the base currency of the account. It is possible to get into a situation where the internal currency of a security doesn't match the listed currency. For example by buying a security denominated in USD on a German exchange and then transferring this to Interactive Brokers. The current heuristics make it impossible to import transactions in this case. Fix this by removing most currency conversions outright. Users will have to maintain additional deposit accounts in the correct currency and then manually track forex conversions. We still create GROSS_VALUE units with forex amounts for securities where the internal currency doesn't match the listed currency. Closes https://github.com/portfolio-performance/portfolio/issues/4978 --- .../ibflex/IBFlexStatementExtractorTest.java | 452 +++--- .../ibflex/testIBFlexStatementFile25.xml | 1232 +++++++++++++++++ .../ibflex/IBFlexStatementExtractor.java | 155 +-- 3 files changed, 1504 insertions(+), 335 deletions(-) create mode 100644 name.abuchen.portfolio.tests/src/name/abuchen/portfolio/datatransfer/ibflex/testIBFlexStatementFile25.xml diff --git a/name.abuchen.portfolio.tests/src/name/abuchen/portfolio/datatransfer/ibflex/IBFlexStatementExtractorTest.java b/name.abuchen.portfolio.tests/src/name/abuchen/portfolio/datatransfer/ibflex/IBFlexStatementExtractorTest.java index 2b43a67ce7..d9285c698f 100644 --- a/name.abuchen.portfolio.tests/src/name/abuchen/portfolio/datatransfer/ibflex/IBFlexStatementExtractorTest.java +++ b/name.abuchen.portfolio.tests/src/name/abuchen/portfolio/datatransfer/ibflex/IBFlexStatementExtractorTest.java @@ -679,7 +679,7 @@ public void testIBFlexStatementFile02() throws IOException assertThat(buySellTransactions.size(), is(9)); assertThat(accountTransactions.size(), is(3)); assertThat(results.size(), is(20)); - new AssertImportActions().check(results, CurrencyUnit.EUR); + new AssertImportActions().check(results, CurrencyUnit.EUR, CurrencyUnit.USD); // check security Security security1 = results.stream().filter(SecurityItem.class::isInstance).findFirst() @@ -767,19 +767,17 @@ public void testIBFlexStatementFile02() throws IOException assertThat(entry.getNote(), is("Trade-ID: 1908991475")); assertThat(entry.getPortfolioTransaction().getMonetaryAmount(), - is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(4185.05)))); + is(Money.of(CurrencyUnit.USD, Values.Amount.factorize(5000.00)))); assertThat(entry.getPortfolioTransaction().getGrossValue(), - is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(4183.38)))); + is(Money.of(CurrencyUnit.USD, Values.Amount.factorize(4998.00)))); assertThat(entry.getPortfolioTransaction().getUnitSum(Unit.Type.TAX), - is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(0.00)))); + is(Money.of(CurrencyUnit.USD, Values.Amount.factorize(0.00)))); assertThat(entry.getPortfolioTransaction().getUnitSum(Unit.Type.FEE), - is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(1.67)))); + is(Money.of(CurrencyUnit.USD, Values.Amount.factorize(2.00)))); assertThat(entry.getPortfolioTransaction().getGrossPricePerShare(), - is(Quote.of(CurrencyUnit.EUR, Values.Quote.factorize(41.8338)))); + is(Quote.of(CurrencyUnit.USD, Values.Quote.factorize(49.98)))); - Unit grossValueUnit = entry.getPortfolioTransaction().getUnit(Unit.Type.GROSS_VALUE) - .orElseThrow(IllegalArgumentException::new); - assertThat(grossValueUnit.getForex(), is(Money.of(CurrencyUnit.USD, Values.Amount.factorize(5000.00)))); + assertThat(entry.getPortfolioTransaction().getUnit(Unit.Type.GROSS_VALUE).isPresent(), is(false)); // check 2nd buy sell transaction entry = (BuySellEntry) results.stream().filter(BuySellEntryItem.class::isInstance).skip(1).findFirst() @@ -794,19 +792,17 @@ public void testIBFlexStatementFile02() throws IOException assertThat(entry.getNote(), is("Trade-ID: 1902533101")); assertThat(entry.getPortfolioTransaction().getMonetaryAmount(), - is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(41.17)))); + is(Money.of(CurrencyUnit.USD, Values.Amount.factorize(49.50)))); assertThat(entry.getPortfolioTransaction().getGrossValue(), - is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(44.08)))); + is(Money.of(CurrencyUnit.USD, Values.Amount.factorize(53.00)))); assertThat(entry.getPortfolioTransaction().getUnitSum(Unit.Type.TAX), - is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(0.00)))); + is(Money.of(CurrencyUnit.USD, Values.Amount.factorize(0.00)))); assertThat(entry.getPortfolioTransaction().getUnitSum(Unit.Type.FEE), - is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(2.91)))); + is(Money.of(CurrencyUnit.USD, Values.Amount.factorize(3.50)))); assertThat(entry.getPortfolioTransaction().getGrossPricePerShare(), - is(Quote.of(CurrencyUnit.EUR, Values.Quote.factorize(0.4408)))); + is(Quote.of(CurrencyUnit.USD, Values.Quote.factorize(0.53)))); - grossValueUnit = entry.getPortfolioTransaction().getUnit(Unit.Type.GROSS_VALUE) - .orElseThrow(IllegalArgumentException::new); - assertThat(grossValueUnit.getForex(), is(Money.of(CurrencyUnit.USD, Values.Amount.factorize(53.00)))); + assertThat(entry.getPortfolioTransaction().getUnit(Unit.Type.GROSS_VALUE).isPresent(), is(false)); // check 3th buy sell (Amount = 0,00) transaction entry = (BuySellEntry) results.stream().filter(BuySellEntryItem.class::isInstance).skip(2).findFirst() @@ -821,19 +817,17 @@ public void testIBFlexStatementFile02() throws IOException assertThat(entry.getNote(), is("Trade-ID: 1908991474")); assertThat(entry.getPortfolioTransaction().getMonetaryAmount(), - is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(0.00)))); + is(Money.of(CurrencyUnit.USD, Values.Amount.factorize(0.00)))); assertThat(entry.getPortfolioTransaction().getGrossValue(), - is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(0.00)))); + is(Money.of(CurrencyUnit.USD, Values.Amount.factorize(0.00)))); assertThat(entry.getPortfolioTransaction().getUnitSum(Unit.Type.TAX), - is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(0.00)))); + is(Money.of(CurrencyUnit.USD, Values.Amount.factorize(0.00)))); assertThat(entry.getPortfolioTransaction().getUnitSum(Unit.Type.FEE), - is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(0.00)))); + is(Money.of(CurrencyUnit.USD, Values.Amount.factorize(0.00)))); assertThat(entry.getPortfolioTransaction().getGrossPricePerShare(), - is(Quote.of(CurrencyUnit.EUR, Values.Quote.factorize(0.00)))); + is(Quote.of(CurrencyUnit.USD, Values.Quote.factorize(0.00)))); - grossValueUnit = entry.getPortfolioTransaction().getUnit(Unit.Type.GROSS_VALUE) - .orElseThrow(IllegalArgumentException::new); - assertThat(grossValueUnit.getForex(), is(Money.of(CurrencyUnit.USD, Values.Amount.factorize(0.00)))); + assertThat(entry.getPortfolioTransaction().getUnit(Unit.Type.GROSS_VALUE).isPresent(), is(false)); // check cancellation (Storno) 3rd buy sell transaction BuySellEntry cancellation = (BuySellEntry) results.stream() // @@ -854,19 +848,17 @@ public void testIBFlexStatementFile02() throws IOException assertThat(cancellation.getNote(), is("Trade-ID: 1908991474")); assertThat(cancellation.getPortfolioTransaction().getMonetaryAmount(), - is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(0.00)))); + is(Money.of(CurrencyUnit.USD, Values.Amount.factorize(0.00)))); assertThat(cancellation.getPortfolioTransaction().getGrossValue(), - is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(0.00)))); + is(Money.of(CurrencyUnit.USD, Values.Amount.factorize(0.00)))); assertThat(cancellation.getPortfolioTransaction().getUnitSum(Unit.Type.TAX), - is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(0.00)))); + is(Money.of(CurrencyUnit.USD, Values.Amount.factorize(0.00)))); assertThat(cancellation.getPortfolioTransaction().getUnitSum(Unit.Type.FEE), - is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(0.00)))); + is(Money.of(CurrencyUnit.USD, Values.Amount.factorize(0.00)))); assertThat(cancellation.getPortfolioTransaction().getGrossPricePerShare(), - is(Quote.of(CurrencyUnit.EUR, Values.Quote.factorize(0.00)))); + is(Quote.of(CurrencyUnit.USD, Values.Quote.factorize(0.00)))); - grossValueUnit = cancellation.getPortfolioTransaction().getUnit(Unit.Type.GROSS_VALUE) - .orElseThrow(IllegalArgumentException::new); - assertThat(grossValueUnit.getForex(), is(Money.of(CurrencyUnit.USD, Values.Amount.factorize(0.00)))); + assertThat(entry.getPortfolioTransaction().getUnit(Unit.Type.GROSS_VALUE).isPresent(), is(false)); // check 4th buy sell transaction entry = (BuySellEntry) results.stream().filter(BuySellEntryItem.class::isInstance).skip(3).findFirst() @@ -881,19 +873,17 @@ public void testIBFlexStatementFile02() throws IOException assertThat(entry.getNote(), is("Trade-ID: 1910911677")); assertThat(entry.getPortfolioTransaction().getMonetaryAmount(), - is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(42.94)))); + is(Money.of(CurrencyUnit.USD, Values.Amount.factorize(51.50)))); assertThat(entry.getPortfolioTransaction().getGrossValue(), - is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(45.86)))); + is(Money.of(CurrencyUnit.USD, Values.Amount.factorize(55.00)))); assertThat(entry.getPortfolioTransaction().getUnitSum(Unit.Type.TAX), - is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(0.00)))); + is(Money.of(CurrencyUnit.USD, Values.Amount.factorize(0.00)))); assertThat(entry.getPortfolioTransaction().getUnitSum(Unit.Type.FEE), - is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(2.92)))); + is(Money.of(CurrencyUnit.USD, Values.Amount.factorize(3.50)))); assertThat(entry.getPortfolioTransaction().getGrossPricePerShare(), - is(Quote.of(CurrencyUnit.EUR, Values.Quote.factorize(0.4586)))); + is(Quote.of(CurrencyUnit.USD, Values.Quote.factorize(0.55)))); - grossValueUnit = entry.getPortfolioTransaction().getUnit(Unit.Type.GROSS_VALUE) - .orElseThrow(IllegalArgumentException::new); - assertThat(grossValueUnit.getForex(), is(Money.of(CurrencyUnit.USD, Values.Amount.factorize(55.01)))); + assertThat(entry.getPortfolioTransaction().getUnit(Unit.Type.GROSS_VALUE).isPresent(), is(false)); // check 5th buy sell transaction entry = (BuySellEntry) results.stream().filter(BuySellEntryItem.class::isInstance).skip(4).findFirst() @@ -908,19 +898,18 @@ public void testIBFlexStatementFile02() throws IOException assertThat(entry.getNote(), is("Trade-ID: 2107408815 | Transaction-ID: 9004815263")); assertThat(entry.getPortfolioTransaction().getMonetaryAmount(), - is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(578.32)))); + is(Money.of(CurrencyUnit.USD, Values.Amount.factorize(690.64)))); assertThat(entry.getPortfolioTransaction().getGrossValue(), - is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(577.78)))); + is(Money.of(CurrencyUnit.USD, Values.Amount.factorize(690.00)))); assertThat(entry.getPortfolioTransaction().getUnitSum(Unit.Type.TAX), - is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(0.00)))); + is(Money.of(CurrencyUnit.USD, Values.Amount.factorize(0.00)))); assertThat(entry.getPortfolioTransaction().getUnitSum(Unit.Type.FEE), - is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(0.54)))); + is(Money.of(CurrencyUnit.USD, Values.Amount.factorize(0.64)))); assertThat(entry.getPortfolioTransaction().getGrossPricePerShare(), - is(Quote.of(CurrencyUnit.EUR, Values.Quote.factorize(5.7778)))); + is(Quote.of(CurrencyUnit.USD, Values.Quote.factorize(6.90)))); - grossValueUnit = entry.getPortfolioTransaction().getUnit(Unit.Type.GROSS_VALUE) - .orElseThrow(IllegalArgumentException::new); - assertThat(grossValueUnit.getForex(), is(Money.of(CurrencyUnit.USD, Values.Amount.factorize(690.64)))); + assertThat(entry.getPortfolioTransaction().getUnit(Unit.Type.GROSS_VALUE).isPresent(), is(false)); + assertThat(entry.getPortfolioTransaction().getUnit(Unit.Type.GROSS_VALUE).isPresent(), is(false)); // check 6th buy sell transaction entry = (BuySellEntry) results.stream().filter(BuySellEntryItem.class::isInstance).skip(5).findFirst() @@ -1025,15 +1014,14 @@ public void testIBFlexStatementFile02() throws IOException assertNull(transaction.getSource()); assertThat(transaction.getNote(), is("ORCL(US68389X1054) CASH DIVIDEND 0.19000000 USD PER SHARE - US TAX")); - assertThat(transaction.getMonetaryAmount(), is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(13.67)))); - assertThat(transaction.getGrossValue(), is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(16.08)))); + assertThat(transaction.getMonetaryAmount(), is(Money.of(CurrencyUnit.USD, Values.Amount.factorize(16.15)))); + assertThat(transaction.getGrossValue(), is(Money.of(CurrencyUnit.USD, Values.Amount.factorize(19.00)))); assertThat(transaction.getUnitSum(Unit.Type.TAX), - is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(2.41)))); + is(Money.of(CurrencyUnit.USD, Values.Amount.factorize(2.85)))); assertThat(transaction.getUnitSum(Unit.Type.FEE), - is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(0.00)))); + is(Money.of(CurrencyUnit.USD, Values.Amount.factorize(0.00)))); - grossValueUnit = transaction.getUnit(Unit.Type.GROSS_VALUE).orElseThrow(IllegalArgumentException::new); - assertThat(grossValueUnit.getForex(), is(Money.of(CurrencyUnit.USD, Values.Amount.factorize(19.00)))); + assertThat(entry.getPortfolioTransaction().getUnit(Unit.Type.GROSS_VALUE).isPresent(), is(false)); // check transaction // get transactions @@ -1128,7 +1116,7 @@ public void testIBFlexStatementFile04() throws IOException assertThat(buySellTransactions.size(), is(1)); assertThat(accountTransactions.size(), is(0)); assertThat(results.size(), is(2)); - new AssertImportActions().check(results, "CHF"); + new AssertImportActions().check(results, "CHF", CurrencyUnit.USD); // check security Security security = results.stream().filter(SecurityItem.class::isInstance).findFirst() @@ -1153,19 +1141,17 @@ public void testIBFlexStatementFile04() throws IOException assertThat(entry.getNote(), is("Trade-ID: 1111111111")); assertThat(entry.getPortfolioTransaction().getMonetaryAmount(), - is(Money.of("CHF", Values.Amount.factorize(775.80)))); + is(Money.of(CurrencyUnit.USD, Values.Amount.factorize(844.95)))); assertThat(entry.getPortfolioTransaction().getGrossValue(), - is(Money.of("CHF", Values.Amount.factorize(775.50)))); + is(Money.of(CurrencyUnit.USD, Values.Amount.factorize(844.62)))); assertThat(entry.getPortfolioTransaction().getUnitSum(Unit.Type.TAX), - is(Money.of("CHF", Values.Amount.factorize(0.00)))); + is(Money.of(CurrencyUnit.USD, Values.Amount.factorize(0.00)))); assertThat(entry.getPortfolioTransaction().getUnitSum(Unit.Type.FEE), - is(Money.of("CHF", Values.Amount.factorize(0.30)))); + is(Money.of(CurrencyUnit.USD, Values.Amount.factorize(0.33)))); assertThat(entry.getPortfolioTransaction().getGrossPricePerShare(), - is(Quote.of("CHF", Values.Quote.factorize(55.39285714)))); + is(Quote.of(CurrencyUnit.USD, Values.Quote.factorize(60.33)))); - Unit grossValueUnit = entry.getPortfolioTransaction().getUnit(Unit.Type.GROSS_VALUE) - .orElseThrow(IllegalArgumentException::new); - assertThat(grossValueUnit.getForex(), is(Money.of(CurrencyUnit.USD, Values.Amount.factorize(844.95)))); + assertThat(entry.getPortfolioTransaction().getUnit(Unit.Type.GROSS_VALUE).isPresent(), is(false)); } @Test @@ -1319,7 +1305,7 @@ public void testIBFlexStatementFile06() throws IOException assertThat(buySellTransactions.size(), is(2)); assertThat(accountTransactions.size(), is(2)); assertThat(results.size(), is(6)); - new AssertImportActions().check(results, CurrencyUnit.EUR); + new AssertImportActions().check(results, CurrencyUnit.EUR, CurrencyUnit.USD); // check security Security security1 = results.stream().filter(SecurityItem.class::isInstance).findFirst() @@ -1376,19 +1362,17 @@ public void testIBFlexStatementFile06() throws IOException assertThat(entry.getNote(), is("Trade-ID: 3004185992 | Transaction-ID: 13346288726")); assertThat(entry.getPortfolioTransaction().getMonetaryAmount(), - is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(2433.96)))); + is(Money.of(CurrencyUnit.USD, Values.Amount.factorize(2870.00)))); assertThat(entry.getPortfolioTransaction().getGrossValue(), - is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(2429.72)))); + is(Money.of(CurrencyUnit.USD, Values.Amount.factorize(2865.00)))); assertThat(entry.getPortfolioTransaction().getUnitSum(Unit.Type.TAX), - is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(0.00)))); + is(Money.of(CurrencyUnit.USD, Values.Amount.factorize(0.00)))); assertThat(entry.getPortfolioTransaction().getUnitSum(Unit.Type.FEE), - is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(4.24)))); + is(Money.of(CurrencyUnit.USD, Values.Amount.factorize(5.00)))); assertThat(entry.getPortfolioTransaction().getGrossPricePerShare(), - is(Quote.of(CurrencyUnit.EUR, Values.Quote.factorize(97.1888)))); + is(Quote.of(CurrencyUnit.USD, Values.Quote.factorize(114.60)))); - Unit grossValueUnit = entry.getPortfolioTransaction().getUnit(Unit.Type.GROSS_VALUE) - .orElseThrow(IllegalArgumentException::new); - assertThat(grossValueUnit.getForex(), is(Money.of(CurrencyUnit.USD, Values.Amount.factorize(2870.00)))); + assertThat(entry.getPortfolioTransaction().getUnit(Unit.Type.GROSS_VALUE).isPresent(), is(false)); // check 1st dividends transaction AccountTransaction transaction = (AccountTransaction) results.stream().filter(TransactionItem.class::isInstance) @@ -1401,12 +1385,12 @@ public void testIBFlexStatementFile06() throws IOException assertNull(transaction.getSource()); assertThat(transaction.getNote(), is("Transaction-ID: 8765764573")); - assertThat(transaction.getMonetaryAmount(), is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(7.74)))); - assertThat(transaction.getGrossValue(), is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(7.74)))); + assertThat(transaction.getMonetaryAmount(), is(Money.of(CurrencyUnit.USD, Values.Amount.factorize(9.52)))); + assertThat(transaction.getGrossValue(), is(Money.of(CurrencyUnit.USD, Values.Amount.factorize(9.52)))); assertThat(transaction.getUnitSum(Unit.Type.TAX), - is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(0.00)))); + is(Money.of(CurrencyUnit.USD, Values.Amount.factorize(0.00)))); assertThat(transaction.getUnitSum(Unit.Type.FEE), - is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(0.00)))); + is(Money.of(CurrencyUnit.USD, Values.Amount.factorize(0.00)))); // check 2nd dividends transaction transaction = (AccountTransaction) results.stream().filter(TransactionItem.class::isInstance).skip(1) @@ -1419,15 +1403,15 @@ public void testIBFlexStatementFile06() throws IOException assertNull(transaction.getSource()); assertThat(transaction.getNote(), is("Transaction-ID: 13713058125")); - assertThat(transaction.getMonetaryAmount(), is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(8.04)))); - assertThat(transaction.getGrossValue(), is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(8.04)))); + assertThat(transaction.getMonetaryAmount(), is(Money.of(CurrencyUnit.USD, Values.Amount.factorize(9.50)))); + assertThat(transaction.getGrossValue(), is(Money.of(CurrencyUnit.USD, Values.Amount.factorize(9.50)))); assertThat(transaction.getUnitSum(Unit.Type.TAX), - is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(0.00)))); + is(Money.of(CurrencyUnit.USD, Values.Amount.factorize(0.00)))); assertThat(transaction.getUnitSum(Unit.Type.FEE), - is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(0.00)))); + is(Money.of(CurrencyUnit.USD, Values.Amount.factorize(0.00)))); + + assertThat(entry.getPortfolioTransaction().getUnit(Unit.Type.GROSS_VALUE).isPresent(), is(false)); - grossValueUnit = transaction.getUnit(Unit.Type.GROSS_VALUE).orElseThrow(IllegalArgumentException::new); - assertThat(grossValueUnit.getForex(), is(Money.of(CurrencyUnit.USD, Values.Amount.factorize(9.50)))); } @Test @@ -1676,7 +1660,7 @@ public void testIBFlexStatementFile10() throws IOException assertThat(buySellTransactions.size(), is(1)); assertThat(accountTransactions.size(), is(0)); assertThat(results.size(), is(3)); - new AssertImportActions().check(results, "EUR", "USD"); + new AssertImportActions().check(results, CurrencyUnit.EUR, CurrencyUnit.USD); // check security Security security = results.stream().filter(SecurityItem.class::isInstance).findFirst() @@ -1701,19 +1685,17 @@ public void testIBFlexStatementFile10() throws IOException assertThat(entry.getNote(), is("Trade-ID: 116815359 | Transaction-ID: 415451625")); assertThat(entry.getPortfolioTransaction().getMonetaryAmount(), - is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(132.81)))); + is(Money.of(CurrencyUnit.USD, Values.Amount.factorize(140.83)))); assertThat(entry.getPortfolioTransaction().getGrossValue(), - is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(132.50)))); + is(Money.of(CurrencyUnit.USD, Values.Amount.factorize(140.50)))); assertThat(entry.getPortfolioTransaction().getUnitSum(Unit.Type.TAX), - is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(0.00)))); + is(Money.of(CurrencyUnit.USD, Values.Amount.factorize(0.00)))); assertThat(entry.getPortfolioTransaction().getUnitSum(Unit.Type.FEE), - is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(0.31)))); + is(Money.of(CurrencyUnit.USD, Values.Amount.factorize(0.33)))); assertThat(entry.getPortfolioTransaction().getGrossPricePerShare(), - is(Quote.of(CurrencyUnit.EUR, Values.Quote.factorize(13.25)))); + is(Quote.of(CurrencyUnit.USD, Values.Quote.factorize(14.05)))); - Unit grossValueUnit = entry.getPortfolioTransaction().getUnit(Unit.Type.GROSS_VALUE) - .orElseThrow(IllegalArgumentException::new); - assertThat(grossValueUnit.getForex(), is(Money.of(CurrencyUnit.USD, Values.Amount.factorize(140.83)))); + assertThat(entry.getPortfolioTransaction().getUnit(Unit.Type.GROSS_VALUE).isPresent(), is(false)); // check 1st account transfer assertThat(results, hasItem(outboundCash( // @@ -1752,7 +1734,7 @@ public void testIBFlexStatementFile11() throws IOException assertThat(buySellTransactions.size(), is(1)); assertThat(accountTransactions.size(), is(0)); assertThat(results.size(), is(2)); - new AssertImportActions().check(results, CurrencyUnit.EUR); + new AssertImportActions().check(results, CurrencyUnit.USD); // check security Security security = results.stream().filter(SecurityItem.class::isInstance).findFirst() @@ -1777,19 +1759,17 @@ public void testIBFlexStatementFile11() throws IOException assertNull(entry.getNote()); assertThat(entry.getPortfolioTransaction().getMonetaryAmount(), - is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(344.56)))); + is(Money.of(CurrencyUnit.USD, Values.Amount.factorize(350.17)))); assertThat(entry.getPortfolioTransaction().getGrossValue(), - is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(344.39)))); + is(Money.of(CurrencyUnit.USD, Values.Amount.factorize(350.00)))); assertThat(entry.getPortfolioTransaction().getUnitSum(Unit.Type.TAX), - is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(0.00)))); + is(Money.of(CurrencyUnit.USD, Values.Amount.factorize(0.00)))); assertThat(entry.getPortfolioTransaction().getUnitSum(Unit.Type.FEE), - is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(0.17)))); + is(Money.of(CurrencyUnit.USD, Values.Amount.factorize(0.17)))); assertThat(entry.getPortfolioTransaction().getGrossPricePerShare(), - is(Quote.of(CurrencyUnit.EUR, Values.Quote.factorize(24.59928571)))); + is(Quote.of(CurrencyUnit.USD, Values.Quote.factorize(25.00)))); - Unit grossValueUnit = entry.getPortfolioTransaction().getUnit(Unit.Type.GROSS_VALUE) - .orElseThrow(IllegalArgumentException::new); - assertThat(grossValueUnit.getForex(), is(Money.of(CurrencyUnit.USD, Values.Amount.factorize(350.17)))); + assertThat(entry.getPortfolioTransaction().getUnit(Unit.Type.GROSS_VALUE).isPresent(), is(false)); } @Test @@ -1816,7 +1796,7 @@ public void testIBFlexStatementFile12() throws IOException assertThat(buySellTransactions.size(), is(4)); assertThat(accountTransactions.size(), is(11)); assertThat(results.size(), is(17)); - new AssertImportActions().check(results, CurrencyUnit.EUR); + new AssertImportActions().check(results, CurrencyUnit.EUR, CurrencyUnit.USD, "CAD"); // check security Security security1 = results.stream().filter(SecurityItem.class::isInstance).findFirst() @@ -1850,19 +1830,17 @@ public void testIBFlexStatementFile12() throws IOException assertNull(entry.getNote()); assertThat(entry.getPortfolioTransaction().getMonetaryAmount(), - is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(848.65)))); + is(Money.of("CAD", Values.Amount.factorize(1234.00)))); assertThat(entry.getPortfolioTransaction().getGrossValue(), - is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(845.90)))); + is(Money.of("CAD", Values.Amount.factorize(1230.00)))); assertThat(entry.getPortfolioTransaction().getUnitSum(Unit.Type.TAX), - is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(0.00)))); + is(Money.of("CAD", Values.Amount.factorize(0.00)))); assertThat(entry.getPortfolioTransaction().getUnitSum(Unit.Type.FEE), - is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(2.75)))); + is(Money.of("CAD", Values.Amount.factorize(4.00)))); assertThat(entry.getPortfolioTransaction().getGrossPricePerShare(), - is(Quote.of(CurrencyUnit.EUR, Values.Quote.factorize(2.11475)))); + is(Quote.of("CAD", Values.Quote.factorize(3.075)))); - Unit grossValueUnit = entry.getPortfolioTransaction().getUnit(Unit.Type.GROSS_VALUE) - .orElseThrow(IllegalArgumentException::new); - assertThat(grossValueUnit.getForex(), is(Money.of("CAD", Values.Amount.factorize(1234.00)))); + assertThat(entry.getPortfolioTransaction().getUnit(Unit.Type.GROSS_VALUE).isPresent(), is(false)); // check 2nd buy sell transaction entry = (BuySellEntry) results.stream().filter(BuySellEntryItem.class::isInstance).skip(1).findFirst() @@ -1877,19 +1855,18 @@ public void testIBFlexStatementFile12() throws IOException assertNull(entry.getNote()); assertThat(entry.getPortfolioTransaction().getMonetaryAmount(), - is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(5203.94)))); + is(Money.of(CurrencyUnit.USD, Values.Amount.factorize(5814.00)))); assertThat(entry.getPortfolioTransaction().getGrossValue(), - is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(5202.15)))); + is(Money.of(CurrencyUnit.USD, Values.Amount.factorize(5812.00)))); assertThat(entry.getPortfolioTransaction().getUnitSum(Unit.Type.TAX), - is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(0.00)))); + is(Money.of(CurrencyUnit.USD, Values.Amount.factorize(0.00)))); assertThat(entry.getPortfolioTransaction().getUnitSum(Unit.Type.FEE), - is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(1.79)))); + is(Money.of(CurrencyUnit.USD, Values.Amount.factorize(2.00)))); assertThat(entry.getPortfolioTransaction().getGrossPricePerShare(), - is(Quote.of(CurrencyUnit.EUR, Values.Quote.factorize(26.01075)))); + is(Quote.of(CurrencyUnit.USD, Values.Quote.factorize(29.06)))); + + assertThat(entry.getPortfolioTransaction().getUnit(Unit.Type.GROSS_VALUE).isPresent(), is(false)); - grossValueUnit = entry.getPortfolioTransaction().getUnit(Unit.Type.GROSS_VALUE) - .orElseThrow(IllegalArgumentException::new); - assertThat(grossValueUnit.getForex(), is(Money.of(CurrencyUnit.USD, Values.Amount.factorize(5814.00)))); // check 3rd buy sell transaction entry = (BuySellEntry) results.stream().filter(BuySellEntryItem.class::isInstance).skip(2).findFirst() @@ -1904,19 +1881,17 @@ public void testIBFlexStatementFile12() throws IOException assertNull(entry.getNote()); assertThat(entry.getPortfolioTransaction().getMonetaryAmount(), - is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(4093.22)))); + is(Money.of(CurrencyUnit.USD, Values.Amount.factorize(4545.50)))); assertThat(entry.getPortfolioTransaction().getGrossValue(), - is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(4091.42)))); + is(Money.of(CurrencyUnit.USD, Values.Amount.factorize(4543.50)))); assertThat(entry.getPortfolioTransaction().getUnitSum(Unit.Type.TAX), - is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(0.00)))); + is(Money.of(CurrencyUnit.USD, Values.Amount.factorize(0.00)))); assertThat(entry.getPortfolioTransaction().getUnitSum(Unit.Type.FEE), - is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(1.80)))); + is(Money.of(CurrencyUnit.USD, Values.Amount.factorize(2.00)))); assertThat(entry.getPortfolioTransaction().getGrossPricePerShare(), - is(Quote.of(CurrencyUnit.EUR, Values.Quote.factorize(116.8977143)))); + is(Quote.of(CurrencyUnit.USD, Values.Quote.factorize(129.8142857)))); - grossValueUnit = entry.getPortfolioTransaction().getUnit(Unit.Type.GROSS_VALUE) - .orElseThrow(IllegalArgumentException::new); - assertThat(grossValueUnit.getForex(), is(Money.of(CurrencyUnit.USD, Values.Amount.factorize(4545.50)))); + assertThat(entry.getPortfolioTransaction().getUnit(Unit.Type.GROSS_VALUE).isPresent(), is(false)); // check 4th buy sell transaction entry = (BuySellEntry) results.stream().filter(BuySellEntryItem.class::isInstance).skip(3).findFirst() @@ -1931,19 +1906,18 @@ public void testIBFlexStatementFile12() throws IOException assertNull(entry.getNote()); assertThat(entry.getPortfolioTransaction().getMonetaryAmount(), - is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(1349.34)))); + is(Money.of(CurrencyUnit.USD, Values.Amount.factorize(1504.50)))); assertThat(entry.getPortfolioTransaction().getGrossValue(), - is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(1347.55)))); + is(Money.of(CurrencyUnit.USD, Values.Amount.factorize(1502.50)))); assertThat(entry.getPortfolioTransaction().getUnitSum(Unit.Type.TAX), - is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(0.00)))); + is(Money.of(CurrencyUnit.USD, Values.Amount.factorize(0.00)))); assertThat(entry.getPortfolioTransaction().getUnitSum(Unit.Type.FEE), - is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(1.79)))); + is(Money.of(CurrencyUnit.USD, Values.Amount.factorize(2.00)))); assertThat(entry.getPortfolioTransaction().getGrossPricePerShare(), - is(Quote.of(CurrencyUnit.EUR, Values.Quote.factorize(26.951)))); + is(Quote.of(CurrencyUnit.USD, Values.Quote.factorize(30.05)))); + + assertThat(entry.getPortfolioTransaction().getUnit(Unit.Type.GROSS_VALUE).isPresent(), is(false)); - grossValueUnit = entry.getPortfolioTransaction().getUnit(Unit.Type.GROSS_VALUE) - .orElseThrow(IllegalArgumentException::new); - assertThat(grossValueUnit.getForex(), is(Money.of(CurrencyUnit.USD, Values.Amount.factorize(1504.50)))); // check transaction // get transactions @@ -1956,12 +1930,11 @@ public void testIBFlexStatementFile12() throws IOException AccountTransaction transaction = (AccountTransaction) item.getSubject(); assertThat(transaction.getType(), is(AccountTransaction.Type.TAX_REFUND)); assertThat(transaction.getDateTime(), is(LocalDateTime.parse("2019-01-31T20:20"))); - assertThat(transaction.getMonetaryAmount(), is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(1.81)))); + assertThat(transaction.getMonetaryAmount(), is(Money.of(CurrencyUnit.USD, Values.Amount.factorize(1.99)))); assertNull(transaction.getSource()); assertThat(transaction.getNote(), is("XXXXXUSD")); - grossValueUnit = transaction.getUnit(Unit.Type.GROSS_VALUE).orElseThrow(IllegalArgumentException::new); - assertThat(grossValueUnit.getForex(), is(Money.of(CurrencyUnit.USD, Values.Amount.factorize(1.99)))); + assertThat(transaction.getUnit(Unit.Type.GROSS_VALUE).isPresent(), is(false)); item = iter.next(); @@ -1969,12 +1942,11 @@ public void testIBFlexStatementFile12() throws IOException transaction = (AccountTransaction) item.getSubject(); assertThat(transaction.getType(), is(AccountTransaction.Type.TAXES)); assertThat(transaction.getDateTime(), is(LocalDateTime.parse("2019-01-31T20:20"))); - assertThat(transaction.getMonetaryAmount(), is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(0.49)))); + assertThat(transaction.getMonetaryAmount(), is(Money.of(CurrencyUnit.USD, Values.Amount.factorize(0.54)))); assertNull(transaction.getSource()); assertThat(transaction.getNote(), is("XXXXXUSD")); - grossValueUnit = transaction.getUnit(Unit.Type.GROSS_VALUE).orElseThrow(IllegalArgumentException::new); - assertThat(grossValueUnit.getForex(), is(Money.of(CurrencyUnit.USD, Values.Amount.factorize(0.54)))); + assertThat(transaction.getUnit(Unit.Type.GROSS_VALUE).isPresent(), is(false)); item = iter.next(); @@ -1982,12 +1954,11 @@ public void testIBFlexStatementFile12() throws IOException transaction = (AccountTransaction) item.getSubject(); assertThat(transaction.getType(), is(AccountTransaction.Type.TAXES)); assertThat(transaction.getDateTime(), is(LocalDateTime.parse("2020-01-02T20:20"))); - assertThat(transaction.getMonetaryAmount(), is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(3.55)))); + assertThat(transaction.getMonetaryAmount(), is(Money.of(CurrencyUnit.USD, Values.Amount.factorize(3.97)))); assertNull(transaction.getSource()); assertThat(transaction.getNote(), is("XXXXXUSD")); - grossValueUnit = transaction.getUnit(Unit.Type.GROSS_VALUE).orElseThrow(IllegalArgumentException::new); - assertThat(grossValueUnit.getForex(), is(Money.of(CurrencyUnit.USD, Values.Amount.factorize(3.97)))); + assertThat(transaction.getUnit(Unit.Type.GROSS_VALUE).isPresent(), is(false)); // check 1st dividends transaction transaction = (AccountTransaction) results.stream().filter(TransactionItem.class::isInstance).skip(3) @@ -2000,15 +1971,14 @@ public void testIBFlexStatementFile12() throws IOException assertNull(transaction.getSource()); assertThat(transaction.getNote(), is("XXXXXCAD")); - assertThat(transaction.getMonetaryAmount(), is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(26.95)))); - assertThat(transaction.getGrossValue(), is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(31.71)))); + assertThat(transaction.getMonetaryAmount(), is(Money.of("CAD", Values.Amount.factorize(39.10)))); + assertThat(transaction.getGrossValue(), is(Money.of("CAD", Values.Amount.factorize(46.00)))); assertThat(transaction.getUnitSum(Unit.Type.TAX), - is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(4.76)))); + is(Money.of("CAD", Values.Amount.factorize(6.90)))); assertThat(transaction.getUnitSum(Unit.Type.FEE), - is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(0.00)))); + is(Money.of("CAD", Values.Amount.factorize(0.00)))); - grossValueUnit = transaction.getUnit(Unit.Type.GROSS_VALUE).orElseThrow(IllegalArgumentException::new); - assertThat(grossValueUnit.getForex(), is(Money.of("CAD", Values.Amount.factorize(46.00)))); + assertThat(transaction.getUnit(Unit.Type.GROSS_VALUE).isPresent(), is(false)); // check 2nd dividends transaction transaction = (AccountTransaction) results.stream().filter(TransactionItem.class::isInstance).skip(4) @@ -2021,15 +1991,14 @@ public void testIBFlexStatementFile12() throws IOException assertNull(transaction.getSource()); assertThat(transaction.getNote(), is("XXXXXCAD")); - assertThat(transaction.getMonetaryAmount(), is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(23.24)))); - assertThat(transaction.getGrossValue(), is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(29.44)))); + assertThat(transaction.getMonetaryAmount(), is(Money.of("CAD", Values.Amount.factorize(33.75)))); + assertThat(transaction.getGrossValue(), is(Money.of("CAD", Values.Amount.factorize(42.75)))); assertThat(transaction.getUnitSum(Unit.Type.TAX), - is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(6.20)))); + is(Money.of("CAD", Values.Amount.factorize(9.00)))); assertThat(transaction.getUnitSum(Unit.Type.FEE), - is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(0.00)))); + is(Money.of("CAD", Values.Amount.factorize(0.00)))); - grossValueUnit = transaction.getUnit(Unit.Type.GROSS_VALUE).orElseThrow(IllegalArgumentException::new); - assertThat(grossValueUnit.getForex(), is(Money.of("CAD", Values.Amount.factorize(42.75)))); + assertThat(transaction.getUnit(Unit.Type.GROSS_VALUE).isPresent(), is(false)); // check 3rd dividends transaction transaction = (AccountTransaction) results.stream().filter(TransactionItem.class::isInstance).skip(5) @@ -2042,15 +2011,14 @@ public void testIBFlexStatementFile12() throws IOException assertNull(transaction.getSource()); assertThat(transaction.getNote(), is("XXXXXUSD")); - assertThat(transaction.getMonetaryAmount(), is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(43.51)))); - assertThat(transaction.getGrossValue(), is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(51.19)))); + assertThat(transaction.getMonetaryAmount(), is(Money.of(CurrencyUnit.USD, Values.Amount.factorize(48.61)))); + assertThat(transaction.getGrossValue(), is(Money.of(CurrencyUnit.USD, Values.Amount.factorize(57.19)))); assertThat(transaction.getUnitSum(Unit.Type.TAX), - is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(7.68)))); + is(Money.of(CurrencyUnit.USD, Values.Amount.factorize(8.58)))); assertThat(transaction.getUnitSum(Unit.Type.FEE), - is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(0.00)))); + is(Money.of(CurrencyUnit.USD, Values.Amount.factorize(0.00)))); - grossValueUnit = transaction.getUnit(Unit.Type.GROSS_VALUE).orElseThrow(IllegalArgumentException::new); - assertThat(grossValueUnit.getForex(), is(Money.of(CurrencyUnit.USD, Values.Amount.factorize(57.19)))); + assertThat(transaction.getUnit(Unit.Type.GROSS_VALUE).isPresent(), is(false)); // check 4th dividends transaction transaction = (AccountTransaction) results.stream().filter(TransactionItem.class::isInstance).skip(6) @@ -2063,15 +2031,14 @@ public void testIBFlexStatementFile12() throws IOException assertNull(transaction.getSource()); assertNull(transaction.getNote()); - assertThat(transaction.getMonetaryAmount(), is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(23.72)))); - assertThat(transaction.getGrossValue(), is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(23.72)))); + assertThat(transaction.getMonetaryAmount(), is(Money.of(CurrencyUnit.USD, Values.Amount.factorize(26.50)))); + assertThat(transaction.getGrossValue(), is(Money.of(CurrencyUnit.USD, Values.Amount.factorize(26.50)))); assertThat(transaction.getUnitSum(Unit.Type.TAX), - is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(0.00)))); + is(Money.of(CurrencyUnit.USD, Values.Amount.factorize(0.00)))); assertThat(transaction.getUnitSum(Unit.Type.FEE), - is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(0.00)))); + is(Money.of(CurrencyUnit.USD, Values.Amount.factorize(0.00)))); - grossValueUnit = transaction.getUnit(Unit.Type.GROSS_VALUE).orElseThrow(IllegalArgumentException::new); - assertThat(grossValueUnit.getForex(), is(Money.of(CurrencyUnit.USD, Values.Amount.factorize(26.50)))); + assertThat(transaction.getUnit(Unit.Type.GROSS_VALUE).isPresent(), is(false)); // check transaction // get transactions @@ -2113,7 +2080,7 @@ public void testIBFlexStatementFile12() throws IOException transaction = (AccountTransaction) item.getSubject(); assertThat(transaction.getType(), is(AccountTransaction.Type.INTEREST_CHARGE)); assertThat(transaction.getDateTime(), is(LocalDateTime.parse("2020-01-06T00:00"))); - assertThat(transaction.getMonetaryAmount(), is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(8.92)))); + assertThat(transaction.getMonetaryAmount(), is(Money.of(CurrencyUnit.USD, Values.Amount.factorize(9.99)))); assertNull(transaction.getSource()); assertThat(transaction.getNote(), is("USD DEBIT INT FOR DEC-2019")); } @@ -2976,6 +2943,7 @@ public void testIBFlexStatementFile20() throws IOException assertThat(buySellTransactions.size(), is(1)); assertThat(accountTransactions.size(), is(2)); assertThat(results.size(), is(5)); + new AssertImportActions().check(results, CurrencyUnit.USD, "CAD"); // check security Security security1 = results.stream().filter(SecurityItem.class::isInstance).findFirst() @@ -3009,19 +2977,17 @@ public void testIBFlexStatementFile20() throws IOException assertThat(entry.getNote(), is("Trade-ID: 486028469 | Transaction-ID: 1619968617")); assertThat(entry.getPortfolioTransaction().getMonetaryAmount(), - is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(2754.96)))); + is(Money.of(CurrencyUnit.USD, Values.Amount.factorize(2995.20)))); assertThat(entry.getPortfolioTransaction().getGrossValue(), - is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(2753.12)))); + is(Money.of(CurrencyUnit.USD, Values.Amount.factorize(2993.20)))); assertThat(entry.getPortfolioTransaction().getUnitSum(Unit.Type.TAX), - is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(0.00)))); + is(Money.of(CurrencyUnit.USD, Values.Amount.factorize(0.00)))); assertThat(entry.getPortfolioTransaction().getUnitSum(Unit.Type.FEE), - is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(1.84)))); + is(Money.of(CurrencyUnit.USD, Values.Amount.factorize(2.00)))); assertThat(entry.getPortfolioTransaction().getGrossPricePerShare(), - is(Quote.of(CurrencyUnit.EUR, Values.Quote.factorize(17.53579618)))); + is(Quote.of(CurrencyUnit.USD, Values.Quote.factorize(19.06496815)))); - Unit grossValueUnit = entry.getPortfolioTransaction().getUnit(Unit.Type.GROSS_VALUE) - .orElseThrow(IllegalArgumentException::new); - assertThat(grossValueUnit.getForex(), is(Money.of(CurrencyUnit.USD, Values.Amount.factorize(2995.20)))); + assertThat(entry.getPortfolioTransaction().getUnit(Unit.Type.GROSS_VALUE).isPresent(), is(false)); // check 1st tax refund transaction AccountTransaction transaction = (AccountTransaction) results.stream().filter(TransactionItem.class::isInstance) @@ -3034,15 +3000,14 @@ public void testIBFlexStatementFile20() throws IOException assertNull(transaction.getSource()); assertThat(entry.getNote(), is("Trade-ID: 486028469 | Transaction-ID: 1619968617")); - assertThat(transaction.getMonetaryAmount(), is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(21.02)))); - assertThat(transaction.getGrossValue(), is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(21.02)))); + assertThat(transaction.getMonetaryAmount(), is(Money.of("CAD", Values.Amount.factorize(28.50)))); + assertThat(transaction.getGrossValue(), is(Money.of("CAD", Values.Amount.factorize(28.50)))); assertThat(transaction.getUnitSum(Unit.Type.TAX), - is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(0.00)))); + is(Money.of("CAD", Values.Amount.factorize(0.00)))); assertThat(transaction.getUnitSum(Unit.Type.FEE), - is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(0.00)))); + is(Money.of("CAD", Values.Amount.factorize(0.00)))); - grossValueUnit = transaction.getUnit(Unit.Type.GROSS_VALUE).orElseThrow(IllegalArgumentException::new); - assertThat(grossValueUnit.getForex(), is(Money.of("CAD", Values.Amount.factorize(28.50)))); + assertThat(entry.getPortfolioTransaction().getUnit(Unit.Type.GROSS_VALUE).isPresent(), is(false)); // check 1st tax refund transaction transaction = (AccountTransaction) results.stream().filter(TransactionItem.class::isInstance).skip(1) @@ -3055,15 +3020,15 @@ public void testIBFlexStatementFile20() throws IOException assertNull(transaction.getSource()); assertThat(entry.getNote(), is("Trade-ID: 486028469 | Transaction-ID: 1619968617")); - assertThat(transaction.getMonetaryAmount(), is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(21.02)))); - assertThat(transaction.getGrossValue(), is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(21.02)))); + assertThat(transaction.getMonetaryAmount(), is(Money.of("CAD", Values.Amount.factorize(28.50)))); + assertThat(transaction.getGrossValue(), is(Money.of("CAD", Values.Amount.factorize(28.50)))); assertThat(transaction.getUnitSum(Unit.Type.TAX), - is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(0.00)))); + is(Money.of("CAD", Values.Amount.factorize(0.00)))); assertThat(transaction.getUnitSum(Unit.Type.FEE), - is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(0.00)))); + is(Money.of("CAD", Values.Amount.factorize(0.00)))); + + assertThat(entry.getPortfolioTransaction().getUnit(Unit.Type.GROSS_VALUE).isPresent(), is(false)); - grossValueUnit = transaction.getUnit(Unit.Type.GROSS_VALUE).orElseThrow(IllegalArgumentException::new); - assertThat(grossValueUnit.getForex(), is(Money.of("CAD", Values.Amount.factorize(28.50)))); } @Test @@ -3252,15 +3217,14 @@ public void testIBFlexStatementFile23() throws IOException assertThat(transaction.getShares(), is(Values.Share.factorize(0))); assertNull(transaction.getSource()); - assertThat(transaction.getMonetaryAmount(), is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(8.28)))); - assertThat(transaction.getGrossValue(), is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(8.28)))); + assertThat(transaction.getMonetaryAmount(), is(Money.of(CurrencyUnit.USD, Values.Amount.factorize(8.97)))); + assertThat(transaction.getGrossValue(), is(Money.of(CurrencyUnit.USD, Values.Amount.factorize(8.97)))); assertThat(transaction.getUnitSum(Unit.Type.TAX), - is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(0.00)))); + is(Money.of(CurrencyUnit.USD, Values.Amount.factorize(0.00)))); assertThat(transaction.getUnitSum(Unit.Type.FEE), - is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(0.00)))); + is(Money.of(CurrencyUnit.USD, Values.Amount.factorize(0.00)))); - Unit grossValueUnit = transaction.getUnit(Unit.Type.GROSS_VALUE).orElseThrow(IllegalArgumentException::new); - assertThat(grossValueUnit.getForex(), is(Money.of("USD", Values.Amount.factorize(8.97)))); + assertThat(transaction.getUnit(Unit.Type.GROSS_VALUE).isPresent(), is(false)); // check 1st dividends transaction transaction = (AccountTransaction) results.stream().filter(TransactionItem.class::isInstance).skip(1) @@ -3273,15 +3237,14 @@ public void testIBFlexStatementFile23() throws IOException assertNull(transaction.getSource()); assertThat(transaction.getNote(), is("Transaction-ID: 11136056417")); - assertThat(transaction.getMonetaryAmount(), is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(55.03)))); - assertThat(transaction.getGrossValue(), is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(55.03)))); + assertThat(transaction.getMonetaryAmount(), is(Money.of(CurrencyUnit.USD, Values.Amount.factorize(61.13)))); + assertThat(transaction.getGrossValue(), is(Money.of(CurrencyUnit.USD, Values.Amount.factorize(61.13)))); assertThat(transaction.getUnitSum(Unit.Type.TAX), - is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(0.00)))); + is(Money.of(CurrencyUnit.USD, Values.Amount.factorize(0.00)))); assertThat(transaction.getUnitSum(Unit.Type.FEE), - is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(0.00)))); + is(Money.of(CurrencyUnit.USD, Values.Amount.factorize(0.00)))); - grossValueUnit = transaction.getUnit(Unit.Type.GROSS_VALUE).orElseThrow(IllegalArgumentException::new); - assertThat(grossValueUnit.getForex(), is(Money.of(CurrencyUnit.USD, Values.Amount.factorize(61.13)))); + assertThat(transaction.getUnit(Unit.Type.GROSS_VALUE).isPresent(), is(false)); // check 1st buy sell transaction BuySellEntry entry = (BuySellEntry) results.stream().filter(BuySellEntryItem.class::isInstance).findFirst() @@ -3339,4 +3302,93 @@ public void testIBFlexStatementFile24() throws IOException hasAmount("USD", 245.92), hasGrossValue("USD", 245.92), // hasTaxes("USD", 0.00), hasFees("USD", 0.00)))); } + + @Test + public void testIBFlexStatementFile25() throws IOException + { + var client = new Client(); + + var vwrl = new Security("Vanguard MSCI World", "EUR"); + vwrl.setTickerSymbol("VWRL"); + client.addSecurity(vwrl); + + var euns = new Security("EUNS", "EUR"); + euns.setTickerSymbol("EUNS"); + client.addSecurity(vwrl); + + var test = new Security("TEST", "GBP"); + test.setTickerSymbol("TEST"); + client.addSecurity(test); + + var test2 = new Security("TEST2", "EUR"); + test2.setTickerSymbol("TEST2"); + client.addSecurity(test2); + + var extractor = new IBFlexStatementExtractor(client); + + var activityStatement = getClass().getResourceAsStream("testIBFlexStatementFile25.xml"); + var tempFile = createTempFile(activityStatement); + + var errors = new ArrayList(); + + var results = extractor.extract(Collections.singletonList(tempFile), errors); + assertThat(errors, empty()); + assertThat(countSecurities(results), is(4L)); + assertThat(countBuySell(results), is(0L)); + assertThat(countAccountTransactions(results), is(4L)); + new AssertImportActions().check(results, CurrencyUnit.USD, CurrencyUnit.EUR, "GBP"); + + // Check first two cash transactions (dividends) + var iter = results.stream().filter(TransactionItem.class::isInstance).iterator(); + + // EUNS dividend, no GROSS_VALUE. + var item1 = iter.next(); + var transaction1 = (AccountTransaction) item1.getSubject(); + assertThat(transaction1.getType(), is(AccountTransaction.Type.DIVIDENDS)); + assertThat(transaction1.getDateTime(), is(LocalDateTime.parse("2025-01-29T00:00"))); + // assertThat(transaction1.getDateTime(), + // is(LocalDateTime.parse("2025-01-29T20:20"))); + assertThat(transaction1.getMonetaryAmount(), is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(13.51)))); + assertThat(transaction1.getSecurity().getTickerSymbol(), is("EUNS")); + + assertThat(transaction1.getUnit(Unit.Type.GROSS_VALUE).isPresent(), is(false)); + + // TEST dividend (EUR converted to GBP) + var item2 = iter.next(); + var transaction2 = (AccountTransaction) item2.getSubject(); + assertThat(transaction2.getType(), is(AccountTransaction.Type.DIVIDENDS)); + assertThat(transaction2.getDateTime(), is(LocalDateTime.parse("2025-01-29T00:00"))); + // assertThat(transaction1.getDateTime(), + // is(LocalDateTime.parse("2025-01-29T20:20"))); + assertThat(transaction2.getMonetaryAmount(), is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(13.51)))); + assertThat(transaction2.getSecurity().getTickerSymbol(), is("TEST")); + + var grossValue2 = transaction2.getUnit(Unit.Type.GROSS_VALUE).orElseThrow(); + assertThat(grossValue2.getAmount(), is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(13.51)))); + assertThat(grossValue2.getForex(), is(Money.of("GBP", Values.Amount.factorize(11.31)))); + + // VWRL dividend (USD converted to EUR via cross rate) + var item3 = iter.next(); + var transaction3 = (AccountTransaction) item3.getSubject(); + assertThat(transaction3.getType(), is(AccountTransaction.Type.DIVIDENDS)); + assertThat(transaction3.getDateTime(), is(LocalDateTime.parse("2024-12-30T00:00"))); + assertThat(transaction3.getMonetaryAmount(), is(Money.of(CurrencyUnit.USD, Values.Amount.factorize(4.46619)))); + assertThat(transaction3.getSecurity().getTickerSymbol(), is("VWRL")); + + var grossValue3 = transaction3.getUnit(Unit.Type.GROSS_VALUE).orElseThrow(); + assertThat(grossValue3.getAmount(), is(Money.of(CurrencyUnit.USD, Values.Amount.factorize(4.47)))); + assertThat(grossValue3.getForex(), is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(4.30)))); + + // TEST2 dividend (GBP converted to EUR) + var item4 = iter.next(); + var transaction4 = (AccountTransaction) item4.getSubject(); + assertThat(transaction4.getType(), is(AccountTransaction.Type.DIVIDENDS)); + assertThat(transaction4.getDateTime(), is(LocalDateTime.parse("2024-12-30T00:00"))); + assertThat(transaction4.getMonetaryAmount(), is(Money.of("GBP", Values.Amount.factorize(4.46619)))); + assertThat(transaction4.getSecurity().getTickerSymbol(), is("TEST2")); + + var grossValue4 = transaction4.getUnit(Unit.Type.GROSS_VALUE).orElseThrow(); + assertThat(grossValue4.getAmount(), is(Money.of("GBP", Values.Amount.factorize(4.47)))); + assertThat(grossValue4.getForex(), is(Money.of(CurrencyUnit.EUR, Values.Amount.factorize(5.39)))); + } } diff --git a/name.abuchen.portfolio.tests/src/name/abuchen/portfolio/datatransfer/ibflex/testIBFlexStatementFile25.xml b/name.abuchen.portfolio.tests/src/name/abuchen/portfolio/datatransfer/ibflex/testIBFlexStatementFile25.xml new file mode 100644 index 0000000000..016f7c8cd3 --- /dev/null +++ b/name.abuchen.portfolio.tests/src/name/abuchen/portfolio/datatransfer/ibflex/testIBFlexStatementFile25.xml @@ -0,0 +1,1232 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/name.abuchen.portfolio/src/name/abuchen/portfolio/datatransfer/ibflex/IBFlexStatementExtractor.java b/name.abuchen.portfolio/src/name/abuchen/portfolio/datatransfer/ibflex/IBFlexStatementExtractor.java index 6dd7544039..af7d7a6b34 100644 --- a/name.abuchen.portfolio/src/name/abuchen/portfolio/datatransfer/ibflex/IBFlexStatementExtractor.java +++ b/name.abuchen.portfolio/src/name/abuchen/portfolio/datatransfer/ibflex/IBFlexStatementExtractor.java @@ -329,54 +329,6 @@ private class IBFlexStatementExtractorResult throw new IllegalArgumentException("unsupported type '" + type + "'"); } - // @formatter:off - // If the account currency differs from transaction currency - // convert currency, if there is a matching security with the account currency - // @formatter:on - if ((AccountTransaction.Type.DIVIDENDS.equals(accountTransaction.getType()) - || AccountTransaction.Type.TAXES.equals(accountTransaction.getType())) - && (this.accountCurrency != null && !this.accountCurrency.equals(amount.getCurrencyCode()))) - { - // matching isin or wkn & base currency - boolean foundIsinBase = false; - - // matching isin & transaction currency - boolean foundIsinTransaction = false; - - // matching conid (wkn) & transaction currency - boolean foundWknTransaction = false; - - for (Security s : allSecurities) - { - // Find security with same isin or conid (wkn) and currency - if (element.getAttribute("isin").length() > 0 && element.getAttribute("isin").equals(s.getIsin())) - { - if (amount.getCurrencyCode().equals(s.getCurrencyCode())) - foundIsinTransaction = true; - else if (this.accountCurrency.equals(s.getCurrencyCode())) - foundIsinBase = true; - } - else if (element.getAttribute("conid").length() > 0 && element.getAttribute("conid").equals(s.getWkn())) - { - if (amount.getCurrencyCode().equals(s.getCurrencyCode())) - foundWknTransaction = true; - else if (this.accountCurrency.equals(s.getCurrencyCode())) - foundIsinBase = true; - } - } - - // If the transaction currency is not found but the base - // currency is found, and there is an exchange rate, convert the - // amount. - if ((!foundIsinTransaction || !foundWknTransaction) && foundIsinBase && element.getAttribute("fxRateToBase").length() > 0) - { - BigDecimal fxRateToBase = asExchangeRate(element.getAttribute("fxRateToBase")); - - amount = Money.of(accountCurrency, BigDecimal.valueOf(amount.getAmount()).multiply(fxRateToBase) - .setScale(0, RoundingMode.HALF_UP).longValue()); - } - } - // Set the amount in the transaction setAmount(element, accountTransaction, amount); @@ -572,14 +524,14 @@ else if (this.accountCurrency.equals(s.getCurrencyCode())) Money amount = Money.of(asCurrencyCode(element.getAttribute("currency")), asAmount(element.getAttribute("netCash"))); setAmount(element, portfolioTransaction.getPortfolioTransaction(), amount); - setAmount(element, portfolioTransaction.getAccountTransaction(), amount, false); + setAmount(element, portfolioTransaction.getAccountTransaction(), amount); } else { Money amount = Money.of(asCurrencyCode(element.getAttribute("currency")), asAmount(element.getAttribute("cost"))); setAmount(element, portfolioTransaction.getPortfolioTransaction(), amount); - setAmount(element, portfolioTransaction.getAccountTransaction(), amount, false); + setAmount(element, portfolioTransaction.getAccountTransaction(), amount); } // Set share quantity @@ -589,12 +541,12 @@ else if (this.accountCurrency.equals(s.getCurrencyCode())) // Set fees Money fees = Money.of(asCurrencyCode(element.getAttribute("ibCommissionCurrency")), asAmount(element.getAttribute("ibCommission"))); - Unit feeUnit = createUnit(element, Unit.Type.FEE, fees); + Unit feeUnit = new Unit(Unit.Type.FEE, fees); portfolioTransaction.getPortfolioTransaction().addUnit(feeUnit); // Set taxes Money taxes = Money.of(asCurrencyCode(element.getAttribute("currency")), asAmount(element.getAttribute("taxes"))); - Unit taxUnit = createUnit(element, Unit.Type.TAX, taxes); + Unit taxUnit = new Unit(Unit.Type.TAX, taxes); portfolioTransaction.getPortfolioTransaction().addUnit(taxUnit); portfolioTransaction.setSecurity(this.getOrCreateSecurity(element, true)); @@ -711,102 +663,35 @@ else if (Messages.MsgErrorOrderCancellationUnsupported.equals(portfolioTransacti results.add(new TransactionItem(accountTransaction)); }; - /** - * Creates a Unit based on the specified element, unit type, and monetary amount. - * - * @param element The element containing FX rate information. - * @param unitType The type of the Unit to be created. - * @param amount The original monetary amount. - * @return The created Unit. - */ - private Unit createUnit(Element element, Unit.Type unitType, Money amount) - { - Unit unit; - - // Check if the IB account currency is null or matches the amount - // currency - if (accountCurrency == null || accountCurrency.equals(amount.getCurrencyCode())) - { - unit = new Unit(unitType, amount); - } - else - { - BigDecimal exchangeRate = getExchangeRate(element, amount.getCurrencyCode(), accountCurrency); - - // Calculate the inverse rate - BigDecimal inverseRate = BigDecimal.ONE.divide(exchangeRate, 10, RoundingMode.HALF_DOWN); - - // Convert the amount to the IB account currency using the - // inverse rate - Money fxAmount = Money.of(accountCurrency, BigDecimal.valueOf(amount.getAmount()) - .divide(inverseRate, Values.MC).setScale(0, RoundingMode.HALF_UP).longValue()); - - // Create a Unit with FX amount, original amount, and FX rate - unit = new Unit(unitType, fxAmount, amount, exchangeRate); - } - - return unit; - } - - /** - * Sets the specified monetary amount on the given transaction, with an option to include in the portfolio transaction. - * - * @param element The XML element containing transaction details. - * @param transaction The transaction object to update with the amount. - * @param amount The monetary amount to set on the transaction. - */ - private void setAmount(Element element, Transaction transaction, Money amount) - { - setAmount(element, transaction, amount, true); - } - /** * Sets the specified monetary amount on the given transaction, with an option to include in the portfolio transaction. * * @param element The XML element containing transaction details. * @param transaction The transaction object to update with the amount. * @param amount The monetary amount to set on the transaction. - * @param addUnit A flag indicating whether to add a Unit to the transaction. */ - private void setAmount(Element element, Transaction transaction, Money amount, boolean addUnit) + private void setAmount(Element element, Transaction transaction, Money amount) { - if (accountCurrency != null && !accountCurrency.equals(amount.getCurrencyCode())) - { - BigDecimal exchangeRate = getExchangeRate(element, amount.getCurrencyCode(), accountCurrency); - - Money fxAmount = Money.of(accountCurrency, BigDecimal.valueOf(amount.getAmount()) - .multiply(exchangeRate).setScale(0, RoundingMode.HALF_UP).longValue()); + transaction.setMonetaryAmount(amount); - transaction.setMonetaryAmount(fxAmount); - - if (addUnit) - { - Unit grossValue = new Unit(Unit.Type.GROSS_VALUE, fxAmount, amount, exchangeRate); - transaction.addUnit(grossValue); - } - } - else + if (transaction.getSecurity() != null + && !transaction.getSecurity().getCurrencyCode().equals(amount.getCurrencyCode())) { - transaction.setMonetaryAmount(amount); + // Transaction currency differs from security currency - create GROSS_VALUE unit + BigDecimal exchangeRate = getExchangeRate(element, amount.getCurrencyCode(), + transaction.getSecurity().getCurrencyCode()); - if (addUnit && transaction.getSecurity() != null && !transaction.getSecurity().getCurrencyCode().equals(amount.getCurrencyCode())) + // Some old testcases contain neither accountCurrency nor fx rates. + // Don't add a GROSS_VALUE in that case. + if (exchangeRate != null) { - // @formatter:off - // If the transaction currency is different from the security currency (as stored in PP) - // we need to supply the gross value in the security currency. - // @formatter:on - BigDecimal exchangeRate = getExchangeRate(element, amount.getCurrencyCode(), transaction.getSecurity().getCurrencyCode()); - - if (exchangeRate != null) { - BigDecimal inverseRate = BigDecimal.ONE.divide(exchangeRate, 10, RoundingMode.HALF_DOWN); + exchangeRate = ExchangeRate.inverse(exchangeRate); + Money fxAmount = Money.of(transaction.getSecurity().getCurrencyCode(), + BigDecimal.valueOf(amount.getAmount()).divide(exchangeRate, Values.MC) + .setScale(0, RoundingMode.HALF_UP).longValue()); - Money fxAmount = Money.of(transaction.getSecurity().getCurrencyCode(), - BigDecimal.valueOf(amount.getAmount()).divide(inverseRate, Values.MC) - .setScale(0, RoundingMode.HALF_UP).longValue()); - - Unit grossValue = new Unit(Unit.Type.GROSS_VALUE, amount, fxAmount, inverseRate); - transaction.addUnit(grossValue); - } + Unit grossValue = new Unit(Unit.Type.GROSS_VALUE, amount, fxAmount, exchangeRate); + transaction.addUnit(grossValue); } } }