diff --git a/name.abuchen.portfolio.tests/src/name/abuchen/portfolio/datatransfer/IBActivityStatementWithForeignDividendNoAccountInfo.xml b/name.abuchen.portfolio.tests/src/name/abuchen/portfolio/datatransfer/IBActivityStatementWithForeignDividendNoAccountInfo.xml
new file mode 100644
index 0000000000..e9a86ca359
--- /dev/null
+++ b/name.abuchen.portfolio.tests/src/name/abuchen/portfolio/datatransfer/IBActivityStatementWithForeignDividendNoAccountInfo.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/name.abuchen.portfolio.tests/src/name/abuchen/portfolio/datatransfer/IBFlexStatementExtractorWithForeignDividendNoAccountInfoTest.java b/name.abuchen.portfolio.tests/src/name/abuchen/portfolio/datatransfer/IBFlexStatementExtractorWithForeignDividendNoAccountInfoTest.java
new file mode 100644
index 0000000000..fd7b13e199
--- /dev/null
+++ b/name.abuchen.portfolio.tests/src/name/abuchen/portfolio/datatransfer/IBFlexStatementExtractorWithForeignDividendNoAccountInfoTest.java
@@ -0,0 +1,161 @@
+package name.abuchen.portfolio.datatransfer;
+
+import static org.hamcrest.CoreMatchers.instanceOf;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.notNullValue;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.time.LocalDateTime;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Optional;
+import java.util.stream.Collectors;
+
+import org.apache.pdfbox.io.IOUtils;
+import org.junit.Test;
+
+import name.abuchen.portfolio.datatransfer.Extractor.BuySellEntryItem;
+import name.abuchen.portfolio.datatransfer.Extractor.Item;
+import name.abuchen.portfolio.datatransfer.Extractor.SecurityItem;
+import name.abuchen.portfolio.datatransfer.Extractor.TransactionItem;
+import name.abuchen.portfolio.model.AccountTransaction;
+import name.abuchen.portfolio.model.BuySellEntry;
+import name.abuchen.portfolio.model.Client;
+import name.abuchen.portfolio.model.PortfolioTransaction;
+import name.abuchen.portfolio.model.Security;
+import name.abuchen.portfolio.model.Transaction.Unit;
+import name.abuchen.portfolio.money.CurrencyUnit;
+import name.abuchen.portfolio.money.Money;
+import name.abuchen.portfolio.money.Quote;
+import name.abuchen.portfolio.money.Values;
+
+@SuppressWarnings("nls")
+public class IBFlexStatementExtractorWithForeignDividendNoAccountInfoTest
+{
+
+ private List- runExtractor(List errors) throws IOException
+ {
+ Client client = new Client();
+ // We add two securities to the client with EUR as currency, both will
+ // receive dividends in USD.
+ Security security = new Security("3M CO. already defined", CurrencyUnit.EUR);
+ security.setIsin("US88579Y1010");
+ client.addSecurity(security);
+
+ security = new Security("CDW CORP/DE already defined", CurrencyUnit.EUR);
+ security.setIsin("US12514G1085");
+ client.addSecurity(security);
+
+ InputStream activityStatement = getClass()
+ .getResourceAsStream("IBActivityStatementWithForeignDividendNoAccountInfo.xml");
+ Extractor.InputFile tempFile = createTempFile(activityStatement);
+ IBFlexStatementExtractor extractor = new IBFlexStatementExtractor(client);
+
+ return extractor.extract(Collections.singletonList(tempFile), errors);
+ }
+
+ @Test
+ public void testIBAcitvityStatement() throws IOException
+ {
+ List errors = new ArrayList<>();
+ List
- results = runExtractor(errors);
+ assertThat(errors.isEmpty(), is(true));
+ int numSecurity = 0; // The two securities are already present in the
+ // client.
+ int numBuySell = 2;
+ int numTransactions = 2;
+
+ results.stream().filter(i -> !(i instanceof SecurityItem))
+ .forEach(i -> assertThat(i.getAmount(), notNullValue()));
+
+ List securityItems = results.stream().filter(SecurityItem.class::isInstance)
+ .collect(Collectors.toList());
+
+ assertThat(securityItems.size(), is(numSecurity));
+
+ List buySellTransactions = results.stream().filter(BuySellEntryItem.class::isInstance)
+ .collect(Collectors.toList());
+
+ assertThat(buySellTransactions.size(), is(numBuySell));
+
+ List accountTransactions = results.stream().filter(TransactionItem.class::isInstance)
+ .collect(Collectors.toList());
+
+ assertThat(accountTransactions.size(), is(numTransactions));
+
+ assertThat(results.size(), is(numSecurity + numBuySell + numTransactions));
+
+ assertFirstBuySell(results.stream().filter(BuySellEntryItem.class::isInstance).findFirst());
+ assertFirstTransaction(results.stream().filter(TransactionItem.class::isInstance).findFirst());
+ assertSecondTransaction(results.stream().filter(TransactionItem.class::isInstance).skip(1).findFirst());
+ }
+
+ private void assertFirstBuySell(Optional
- item)
+ {
+ assertThat(item.isPresent(), is(true));
+ assertThat(item.orElseThrow().getSubject(), instanceOf(BuySellEntry.class));
+ BuySellEntry entry = (BuySellEntry) item.orElseThrow().getSubject();
+
+ assertThat(entry.getPortfolioTransaction().getType(), is(PortfolioTransaction.Type.BUY));
+ assertThat(entry.getAccountTransaction().getType(), is(AccountTransaction.Type.BUY));
+
+ assertThat(entry.getPortfolioTransaction().getSecurity().getName(), is("3M CO. already defined"));
+ assertThat(entry.getPortfolioTransaction().getMonetaryAmount(), is(Money.of("EUR", 1275_25L)));
+ assertThat(entry.getPortfolioTransaction().getDateTime(), is(LocalDateTime.parse("2018-02-09T11:19")));
+ assertThat(entry.getPortfolioTransaction().getShares(), is(Values.Share.factorize(7)));
+ assertThat(entry.getPortfolioTransaction().getUnitSum(Unit.Type.FEE), is(Money.of("EUR", 5_80L)));
+
+ assertThat(entry.getPortfolioTransaction().getGrossPricePerShare(),
+ is(Quote.of("EUR", Values.Quote.factorize(181.35))));
+ }
+
+ private void assertFirstTransaction(Optional
- item)
+ {
+ assertThat(item.isPresent(), is(true));
+ assertThat(item.orElseThrow().getSubject(), instanceOf(AccountTransaction.class));
+ AccountTransaction entry = (AccountTransaction) item.orElseThrow().getSubject();
+
+ assertThat(entry.getType(), is(AccountTransaction.Type.DIVIDENDS));
+
+ assertThat(entry.getSecurity().getName(), is("3M CO. already defined"));
+ assertThat(entry.getSecurity().getIsin(), is("US88579Y1010"));
+ assertThat(entry.getMonetaryAmount(), is(Money.of("USD", 9_52L)));
+ assertThat(entry.getCurrencyCode(), is("USD"));
+ assertThat(entry.getSecurity().getCurrencyCode(), is("EUR"));
+ Unit grossValue = entry.getUnit(Unit.Type.GROSS_VALUE).orElseThrow();
+ assertThat(grossValue.getForex(), is(Money.of("EUR", 7_74L)));
+ assertThat(grossValue.getAmount(), is(Money.of("USD", 9_52L)));
+ }
+
+ private void assertSecondTransaction(Optional
- item)
+ {
+ assertThat(item.isPresent(), is(true));
+ assertThat(item.orElseThrow().getSubject(), instanceOf(AccountTransaction.class));
+ AccountTransaction entry = (AccountTransaction) item.orElseThrow().getSubject();
+
+ assertThat(entry.getType(), is(AccountTransaction.Type.DIVIDENDS));
+
+ assertThat(entry.getSecurity().getName(), is("CDW CORP/DE already defined"));
+ assertThat(entry.getSecurity().getIsin(), is("US12514G1085"));
+ assertThat(entry.getMonetaryAmount(), is(Money.of("USD", 9_50L)));
+ assertThat(entry.getCurrencyCode(), is("USD"));
+ assertThat(entry.getSecurity().getCurrencyCode(), is("EUR"));
+ Unit grossValue = entry.getUnit(Unit.Type.GROSS_VALUE).orElseThrow();
+ assertThat(grossValue.getForex(), is(Money.of("EUR", 8_04L)));
+ assertThat(grossValue.getAmount(), is(Money.of("USD", 9_50L)));
+ }
+
+ private Extractor.InputFile createTempFile(InputStream input) throws IOException
+ {
+ File tempFile = File.createTempFile("iBFlexStatementExtractorTest", null);
+ FileOutputStream fos = new FileOutputStream(tempFile);
+
+ IOUtils.copy(input, fos);
+ return new Extractor.InputFile(tempFile);
+ }
+}
diff --git a/name.abuchen.portfolio/src/name/abuchen/portfolio/datatransfer/IBFlexStatementExtractor.java b/name.abuchen.portfolio/src/name/abuchen/portfolio/datatransfer/IBFlexStatementExtractor.java
index 433d7acba9..1b3b67ac46 100644
--- a/name.abuchen.portfolio/src/name/abuchen/portfolio/datatransfer/IBFlexStatementExtractor.java
+++ b/name.abuchen.portfolio/src/name/abuchen/portfolio/datatransfer/IBFlexStatementExtractor.java
@@ -512,10 +512,9 @@ private void setAmount(Element element, Transaction transaction, Double amount,
{
fxRateToBase = new BigDecimal(1);
}
- BigDecimal inverseRate = BigDecimal.ONE.divide(fxRateToBase, 10, RoundingMode.HALF_DOWN);
BigDecimal baseCurrencyMoney = BigDecimal.valueOf(amount.doubleValue() * Values.Amount.factor())
- .divide(inverseRate, RoundingMode.HALF_DOWN);
+ .multiply(fxRateToBase);
transaction.setAmount(Math.round(baseCurrencyMoney.doubleValue()));
transaction.setCurrencyCode(ibAccountCurrency);
if (addUnit)
@@ -532,6 +531,45 @@ private void setAmount(Element element, Transaction transaction, Double amount,
{
transaction.setAmount(Math.round(amount.doubleValue() * Values.Amount.factor()));
transaction.setCurrencyCode(currency);
+
+ if (addUnit && transaction.getSecurity() != null
+ && !transaction.getSecurity().getCurrencyCode().equals(currency))
+ {
+ // 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).
+ String fxRateToBaseString = element.getAttribute("fxRateToBase");
+ BigDecimal fxRateToBase;
+ if (fxRateToBaseString != null && !fxRateToBaseString.isEmpty())
+ {
+ fxRateToBase = BigDecimal.valueOf(Double.parseDouble(fxRateToBaseString));
+ }
+ else
+ {
+ fxRateToBase = new BigDecimal(1);
+ }
+ // To back out the amount in the security currency we could
+ // multiply with fxRateToBase. Instead, we calculate the
+ // inverse rate and divide by it as we need to supply the
+ // inverse rate for the gross value below (which converts
+ // from security currency to original
+ // transaction currency).
+ BigDecimal inverseRate = BigDecimal.ONE.divide(fxRateToBase, 10, RoundingMode.HALF_DOWN);
+
+ BigDecimal securityCurrencyMoney = BigDecimal.valueOf(amount.doubleValue() * Values.Amount.factor())
+ .divide(inverseRate, RoundingMode.HALF_DOWN);
+
+ // Gross value with conversion information for the security
+ // currency.
+ Unit grossValue = new Unit(Unit.Type.GROSS_VALUE, transaction.getMonetaryAmount(),
+ Money.of(transaction.getSecurity().getCurrencyCode(),
+ Math.round(securityCurrencyMoney.doubleValue())),
+ inverseRate);
+ transaction.addUnit(grossValue);
+ }
+
}
}