Skip to content

Commit b2d0efb

Browse files
Improvement: IBFlex stricter security matching
- If existing security has an ISIN, it must match - Currency must match the existing security (trades only) - Currency matching supports major/minor units
1 parent 7ecfd10 commit b2d0efb

File tree

4 files changed

+190
-13
lines changed

4 files changed

+190
-13
lines changed

name.abuchen.portfolio.tests/src/name/abuchen/portfolio/datatransfer/ibflex/IBFlexStatementExtractorTest.java

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3679,4 +3679,112 @@ public void testIBFlexStatementFile28() throws IOException
36793679
BigDecimal eqqqTolerance = new BigDecimal("0.0000001"); // Same tolerance as HIDR test
36803680
assertThat(eqqqUnit.getExchangeRate().subtract(eqqqExpectedRate).abs().compareTo(eqqqTolerance) < 0, is(true));
36813681
}
3682+
3683+
@Test
3684+
public void testIBFlexStatementFile29() throws IOException
3685+
{
3686+
IBFlexStatementExtractor extractor = new IBFlexStatementExtractor(new Client());
3687+
3688+
InputStream activityStatement = getClass().getResourceAsStream("testIBFlexStatementFile29.xml");
3689+
Extractor.InputFile tempFile = createTempFile(activityStatement);
3690+
3691+
List<Exception> errors = new ArrayList<>();
3692+
3693+
List<Item> results = extractor.extract(Collections.singletonList(tempFile), errors);
3694+
3695+
assertThat(errors, empty());
3696+
3697+
List<Item> securityItems = results.stream().filter(SecurityItem.class::isInstance) //
3698+
.collect(Collectors.toList());
3699+
3700+
// Should have 6 securities total:
3701+
// 1. GCM CAD (CA38501D2041) - Case 1 base trade
3702+
// 2. GCM EUR (CA38501D2041) - Case 1 trade (same ISIN, different currency - NEW security)
3703+
// 3. GCM CAD (CA38501D2042) - Case 2 trade (same symbol, different ISIN - NEW security)
3704+
// 4. GCM EUR (CA38501D2043) - Case 2 dividend (different ISIN from Case 2 trade - NEW security)
3705+
// 5. UUU CAD (no ISIN) - Case 3 base trade
3706+
// 6. UUU EUR (no ISIN) - Case 3 trade (same symbol, different currency - NEW security)
3707+
assertThat("Should create 6 distinct securities", securityItems.size(), is(6));
3708+
3709+
// Verify Case 1: Same ISIN, different currency creates separate securities
3710+
Security gcmCad = securityItems.stream() //
3711+
.map(item -> ((SecurityItem) item).getSecurity()) //
3712+
.filter(s -> "CA38501D2041".equals(s.getIsin()) && "CAD".equals(s.getCurrencyCode())) //
3713+
.findFirst() //
3714+
.orElseThrow(() -> new AssertionError("GCM CAD security not found"));
3715+
assertThat(gcmCad.getTickerSymbol(), is("GCM.TO"));
3716+
3717+
Security gcmEur = securityItems.stream() //
3718+
.map(item -> ((SecurityItem) item).getSecurity()) //
3719+
.filter(s -> "CA38501D2041".equals(s.getIsin()) && "EUR".equals(s.getCurrencyCode())) //
3720+
.findFirst() //
3721+
.orElseThrow(() -> new AssertionError("GCM EUR security not found"));
3722+
assertThat(gcmEur.getTickerSymbol(), is("GCM.TO"));
3723+
assertThat("Same ISIN but different currencies should be separate securities", gcmCad.getUUID(), not(is(gcmEur.getUUID())));
3724+
3725+
// Verify Case 2: Same symbol, different ISIN creates separate securities
3726+
Security gcmCad2042 = securityItems.stream() //
3727+
.map(item -> ((SecurityItem) item).getSecurity()) //
3728+
.filter(s -> "CA38501D2042".equals(s.getIsin())) //
3729+
.findFirst() //
3730+
.orElseThrow(() -> new AssertionError("GCM CA38501D2042 security not found"));
3731+
assertThat(gcmCad2042.getTickerSymbol(), is("GCM.TO"));
3732+
3733+
Security gcmEur2043 = securityItems.stream() //
3734+
.map(item -> ((SecurityItem) item).getSecurity()) //
3735+
.filter(s -> "CA38501D2043".equals(s.getIsin())) //
3736+
.findFirst() //
3737+
.orElseThrow(() -> new AssertionError("GCM CA38501D2043 security not found"));
3738+
assertThat(gcmEur2043.getTickerSymbol(), is("GCM.TO"));
3739+
assertThat("Same symbol but different ISINs should be separate securities", gcmCad2042.getUUID(), not(is(gcmEur2043.getUUID())));
3740+
3741+
// Verify Case 3: Same symbol, no ISIN, different currency creates separate securities
3742+
Security uuuCad = securityItems.stream() //
3743+
.map(item -> ((SecurityItem) item).getSecurity()) //
3744+
.filter(s -> (s.getIsin() == null || s.getIsin().isEmpty()) && "UUU.TO".equals(s.getTickerSymbol()) && "CAD".equals(s.getCurrencyCode())) //
3745+
.findFirst() //
3746+
.orElseThrow(() -> new AssertionError("UUU CAD security not found"));
3747+
assertThat(uuuCad.getTickerSymbol(), is("UUU.TO"));
3748+
3749+
Security uuuEur = securityItems.stream() //
3750+
.map(item -> ((SecurityItem) item).getSecurity()) //
3751+
.filter(s -> (s.getIsin() == null || s.getIsin().isEmpty()) && "UUU.TO".equals(s.getTickerSymbol()) && "EUR".equals(s.getCurrencyCode())) //
3752+
.findFirst() //
3753+
.orElseThrow(() -> new AssertionError("UUU EUR security not found"));
3754+
assertThat(uuuEur.getTickerSymbol(), is("UUU.TO"));
3755+
assertThat("Same symbol but different currencies should be separate securities", uuuCad.getUUID(), not(is(uuuEur.getUUID())));
3756+
3757+
// Verify dividends are associated with correct securities
3758+
List<AccountTransaction> dividends = results.stream() //
3759+
.filter(TransactionItem.class::isInstance) //
3760+
.map(item -> (AccountTransaction) ((TransactionItem) item).getSubject()) //
3761+
.filter(t -> t.getType() == AccountTransaction.Type.DIVIDENDS) //
3762+
.collect(Collectors.toList());
3763+
3764+
assertThat("Should have 3 dividend transactions", dividends.size(), is(3));
3765+
3766+
// Verify Case 1 dividend: ISIN match with non-strict currency (should match one of the Case 1 securities)
3767+
AccountTransaction case1Dividend = dividends.stream() //
3768+
.filter(t -> "CA38501D2041".equals(t.getSecurity().getIsin()) && "USD".equals(t.getCurrencyCode())) //
3769+
.findFirst() //
3770+
.orElseThrow(() -> new AssertionError("Case 1 USD dividend not found"));
3771+
assertThat("Case 1 dividend should match one of the Case 1 securities", case1Dividend.getSecurity().getIsin(), is("CA38501D2041"));
3772+
3773+
// Verify Case 2 dividend: Creates new security (different ISIN)
3774+
AccountTransaction case2Dividend = dividends.stream() //
3775+
.filter(t -> "CA38501D2043".equals(t.getSecurity().getIsin())) //
3776+
.findFirst() //
3777+
.orElseThrow(() -> new AssertionError("Case 2 EUR dividend not found"));
3778+
assertThat(case2Dividend.getCurrencyCode(), is("EUR"));
3779+
assertThat(case2Dividend.getSecurity().getTickerSymbol(), is("GCM.TO"));
3780+
3781+
// Verify Case 3 dividend: Ticker match with no ISIN (should match one of the Case 3 securities)
3782+
AccountTransaction case3Dividend = dividends.stream() //
3783+
.filter(t -> (t.getSecurity().getIsin() == null || t.getSecurity().getIsin().isEmpty())
3784+
&& "UUU.TO".equals(t.getSecurity().getTickerSymbol())
3785+
&& "AUD".equals(t.getCurrencyCode())) //
3786+
.findFirst() //
3787+
.orElseThrow(() -> new AssertionError("Case 3 AUD dividend not found"));
3788+
assertThat("Case 3 dividend should match one of the Case 3 securities", case3Dividend.getSecurity().getTickerSymbol(), is("UUU.TO"));
3789+
}
36823790
}

name.abuchen.portfolio.tests/src/name/abuchen/portfolio/datatransfer/ibflex/testIBFlexStatementFile01.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@
4646
<CashTransaction accountId="U123456" acctAlias="" currency="CHF" assetCategory="" fxRateToBase="1" symbol="" description="BALANCE OF MONTHLY MINIMUM FEE FOR MAR 2013" conid="" securityID="" securityIDType="" cusip="" isin="" underlyingConid="" underlyingSymbol="" issuer="" multiplier="0" strike="" expiry="" putCall="" dateTime="2013-04-03" amount="-9.49" type="Other Fees" tradeID="" code="" />
4747
<CashTransaction accountId="U123456" acctAlias="" currency="CAD" assetCategory="" fxRateToBase="0.91249" symbol="" description="CAD DEBIT INT FOR JAN-2013" conid="" securityID="" securityIDType="" cusip="" isin="" underlyingConid="" underlyingSymbol="" issuer="" multiplier="0" strike="" expiry="" putCall="" dateTime="2013-02-05" amount="1.23" type="Broker Interest Received" tradeID="" code="" />
4848
<CashTransaction accountId="U123456" acctAlias="" currency="CAD" assetCategory="" fxRateToBase="0.91249" symbol="" description="CAD DEBIT INT FOR JAN-2013" conid="" securityID="" securityIDType="" cusip="" isin="" underlyingConid="" underlyingSymbol="" issuer="" multiplier="0" strike="" expiry="" putCall="" dateTime="2013-02-05" amount="-15.17" type="Broker Interest Paid" tradeID="" code="" />
49-
<CashTransaction accountId="U123456" acctAlias="" currency="USD" assetCategory="" fxRateToBase="0.83701" symbol="GCM.TO" description="GCM.TO CASH DIVIDEND USD 0.6900000000 - US TAX" conid="6691" securityID="" securityIDType="" cusip="" isin="" underlyingConid="" underlyingSymbol="" issuer="" multiplier="0" strike="" expiry="" putCall="" dateTime="2017-09-15" amount="-2.07" type="Withholding Tax" tradeID="" code="" />
49+
<CashTransaction accountId="U123456" acctAlias="" currency="USD" assetCategory="" fxRateToBase="0.83701" symbol="GCM.TO" description="GCM.TO CASH DIVIDEND USD 0.6900000000 - US TAX" conid="80845553" securityID="" securityIDType="" cusip="" isin="" underlyingConid="" underlyingSymbol="" issuer="" multiplier="0" strike="" expiry="" putCall="" dateTime="2017-09-15" amount="-2.07" type="Withholding Tax" tradeID="" code="" />
5050
<CashTransaction accountId="U123456" acctAlias="" currency="USD" assetCategory="" fxRateToBase="1" symbol="" description="NP SECURITIES AND FUTURES VALUE BUNDLE FOR APR 2017" conid="" securityID="" securityIDType="" cusip="" isin="" underlyingConid="" underlyingSymbol="" issuer="" multiplier="0" strike="" expiry="" putCall="" dateTime="2017-05-03" amount="-9.18" type="Other Fees" tradeID="" code="" />
5151
<CashTransaction accountId="U123456" acctAlias="" currency="USD" assetCategory="" fxRateToBase="1" symbol="" description="NP SECURITIES AND FUTURES VALUE BUNDLE FOR APR 2017" conid="" securityID="" securityIDType="" cusip="" isin="" underlyingConid="" underlyingSymbol="" issuer="" multiplier="0" strike="" expiry="" putCall="" dateTime="2017-05-03" amount="9.18" type="Other Fees" tradeID="" code="" />
5252
</CashTransactions>
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<FlexQueryResponse queryName="AccountActivities" type="AF">
2+
<FlexStatements count="1">
3+
<FlexStatement accountId="U123456" fromDate="20240101" toDate="20241231" period="" whenGenerated="20240101 12:00:00">
4+
<Trades>
5+
<!-- Base security 1 with ISIN -->
6+
<Trade accountId="U123456" acctAlias="" currency="CAD" assetCategory="STK" fxRateToBase="0.93099" symbol="GCM" description="GRAN COLOMBIA GOLD CORP" conid="" securityID="CA38501D2041" securityIDType="ISIN" cusip="" isin="CA38501D2041" underlyingConid="" underlyingSymbol="" issuer="" multiplier="1" strike="" expiry="" putCall="" tradeID="855937427" reportDate="20130401" tradeDate="20130401" tradeTime="093406" settleDateTarget="20130404" transactionType="ExchTrade" exchange="TSE" quantity="5000" tradePrice="0.27" tradeMoney="1350" proceeds="-1350" taxes="0" ibCommission="-6.75" ibCommissionCurrency="CAD" closePrice="0.275" openCloseIndicator="O" notes="P;" cost="1356.75" fifoPnlRealized="0" mtmPnl="25" origTradePrice="0" origTradeDate="" origTradeID="" origOrderID="0" clearingFirmID="" transactionID="3452745495" buySell="BUY" ibOrderID="437137993" ibExecID="0000d514.5159662d.01.01" brokerageOrderID="" orderReference="" volatilityOrderLink="" orderPlacementTime="" exchOrderId="N/A" extExecID="14.GCM.B" orderTime="20130401;070007" openDateTime="--" holdingPeriodDateTime="--" whenRealized="--" whenReopened="--" levelOfDetail="EXECUTION" changeInPrice="0" changeInQuantity="0" netCash="-1356.75" orderType="LMT" traderID="" />
7+
<!-- Base security 2 without ISIN -->
8+
<Trade accountId="U123456" acctAlias="" currency="CAD" assetCategory="STK" fxRateToBase="0.93194" symbol="UUU" description="URANIUM ONE INC." conid="" securityID="CA91701P1053" securityIDType="ISIN" cusip="" isin="" underlyingConid="" underlyingSymbol="" issuer="" multiplier="1" strike="" expiry="" putCall="" tradeID="813598116" reportDate="20130102" tradeDate="20130102" tradeTime="151236" settleDateTarget="20130107" transactionType="ExchTrade" exchange="TSE" quantity="100" tradePrice="2.31" tradeMoney="231" proceeds="-231" taxes="0" ibCommission="-1" ibCommissionCurrency="CAD" closePrice="2.36" openCloseIndicator="O" notes="P;" cost="232" fifoPnlRealized="0" mtmPnl="5" origTradePrice="0" origTradeDate="" origTradeID="" origOrderID="0" clearingFirmID="" transactionID="3277427053" buySell="BUY" ibOrderID="415875524" ibExecID="0000d514.50e42ded.01.01" brokerageOrderID="" orderReference="" volatilityOrderLink="" orderPlacementTime="" exchOrderId="N/A" extExecID="5173.UUU.B" orderTime="20130102;115456" openDateTime="--" holdingPeriodDateTime="--" whenRealized="--" whenReopened="--" levelOfDetail="EXECUTION" changeInPrice="0" changeInQuantity="0" netCash="-232" orderType="LMT" traderID="" />
9+
10+
<!-- Case 1: Same ISIN, different currency - Should create new security -->
11+
<Trade accountId="U123456" acctAlias="" currency="EUR" assetCategory="STK" fxRateToBase="0.93099" symbol="GCM" description="GRAN COLOMBIA GOLD CORP" conid="" securityID="CA38501D2041" securityIDType="ISIN" cusip="" isin="CA38501D2041" underlyingConid="" underlyingSymbol="" issuer="" multiplier="1" strike="" expiry="" putCall="" tradeID="855937427" reportDate="20130401" tradeDate="20130401" tradeTime="093406" settleDateTarget="20130404" transactionType="ExchTrade" exchange="TSE" quantity="5000" tradePrice="0.27" tradeMoney="1350" proceeds="-1350" taxes="0" ibCommission="-6.75" ibCommissionCurrency="EUR" closePrice="0.275" openCloseIndicator="O" notes="P;" cost="1356.75" fifoPnlRealized="0" mtmPnl="25" origTradePrice="0" origTradeDate="" origTradeID="" origOrderID="0" clearingFirmID="" transactionID="3452745495" buySell="BUY" ibOrderID="437137993" ibExecID="0000d514.5159662d.01.01" brokerageOrderID="" orderReference="" volatilityOrderLink="" orderPlacementTime="" exchOrderId="N/A" extExecID="14.GCM.B" orderTime="20130401;070007" openDateTime="--" holdingPeriodDateTime="--" whenRealized="--" whenReopened="--" levelOfDetail="EXECUTION" changeInPrice="0" changeInQuantity="0" netCash="-1356.75" orderType="LMT" traderID="" />
12+
<!-- Case 2: Same symbol, different ISIN - Should create new security -->
13+
<Trade accountId="U123456" acctAlias="" currency="CAD" assetCategory="STK" fxRateToBase="0.93099" symbol="GCM" description="GRAN COLOMBIA GOLD CORP" conid="" securityID="CA38501D2042" securityIDType="ISIN" cusip="" isin="CA38501D2042" underlyingConid="" underlyingSymbol="" issuer="" multiplier="1" strike="" expiry="" putCall="" tradeID="855937427" reportDate="20130401" tradeDate="20130401" tradeTime="093406" settleDateTarget="20130404" transactionType="ExchTrade" exchange="TSE" quantity="5000" tradePrice="0.27" tradeMoney="1350" proceeds="-1350" taxes="0" ibCommission="-6.75" ibCommissionCurrency="CAD" closePrice="0.275" openCloseIndicator="O" notes="P;" cost="1356.75" fifoPnlRealized="0" mtmPnl="25" origTradePrice="0" origTradeDate="" origTradeID="" origOrderID="0" clearingFirmID="" transactionID="3452745495" buySell="BUY" ibOrderID="437137993" ibExecID="0000d514.5159662d.01.01" brokerageOrderID="" orderReference="" volatilityOrderLink="" orderPlacementTime="" exchOrderId="N/A" extExecID="14.GCM.B" orderTime="20130401;070007" openDateTime="--" holdingPeriodDateTime="--" whenRealized="--" whenReopened="--" levelOfDetail="EXECUTION" changeInPrice="0" changeInQuantity="0" netCash="-1356.75" orderType="LMT" traderID="" />
14+
<!-- Case 3: No ISIN, same symbol, different currency - Should create new security -->
15+
<Trade accountId="U123456" acctAlias="" currency="EUR" assetCategory="STK" fxRateToBase="0.93194" symbol="UUU" description="URANIUM ONE INC." conid="" securityID="CA91701P1053" securityIDType="ISIN" cusip="" isin="" underlyingConid="" underlyingSymbol="" issuer="" multiplier="1" strike="" expiry="" putCall="" tradeID="813598116" reportDate="20130102" tradeDate="20130102" tradeTime="151236" settleDateTarget="20130107" transactionType="ExchTrade" exchange="TSE" quantity="100" tradePrice="2.31" tradeMoney="231" proceeds="-231" taxes="0" ibCommission="-1" ibCommissionCurrency="EUR" closePrice="2.36" openCloseIndicator="O" notes="P;" cost="232" fifoPnlRealized="0" mtmPnl="5" origTradePrice="0" origTradeDate="" origTradeID="" origOrderID="0" clearingFirmID="" transactionID="3277427053" buySell="BUY" ibOrderID="415875524" ibExecID="0000d514.50e42ded.01.01" brokerageOrderID="" orderReference="" volatilityOrderLink="" orderPlacementTime="" exchOrderId="N/A" extExecID="5173.UUU.B" orderTime="20130102;115456" openDateTime="--" holdingPeriodDateTime="--" whenRealized="--" whenReopened="--" levelOfDetail="EXECUTION" changeInPrice="0" changeInQuantity="0" netCash="-232" orderType="LMT" traderID="" />
16+
</Trades>
17+
<CashTransactions>
18+
<!-- Case 1: Same ISIN, different currency - Should match base security 1 -->
19+
<CashTransaction accountId="U123456" acctAlias="" currency="USD" assetCategory="STK" fxRateToBase="0.93194" symbol="GCM" description="GRAN COLOMBIA GOLD CORP" conid="" securityID="CA38501D2041" securityIDType="ISIN" cusip="" isin="CA38501D2041" exchange="TSE" amount="10" type="Dividends" dateTime="20130401" reportDate="20130401" />
20+
<!-- Case 2: Same symbol, different ISIN - Should create new security -->
21+
<CashTransaction accountId="U123456" acctAlias="" currency="EUR" assetCategory="STK" fxRateToBase="0.93194" symbol="GCM" description="GRAN COLOMBIA GOLD CORP" conid="" securityID="CA38501D2043" securityIDType="ISIN" cusip="" isin="CA38501D2043" exchange="TSE" amount="10" type="Dividends" dateTime="20130401" reportDate="20130401" />
22+
<!-- Case 3: No ISIN, same symbol, different currency - Should match base security 2 -->
23+
<CashTransaction accountId="U123456" acctAlias="" currency="AUD" assetCategory="STK" fxRateToBase="0.93194" symbol="UUU" description="URANIUM ONE INC." conid="" securityID="CA91701P1053" securityIDType="ISIN" cusip="" isin="" exchange="TSE" amount="10" type="Dividends" dateTime="20130401" reportDate="20130401" />
24+
</CashTransactions>
25+
</FlexStatement>
26+
</FlexStatements>
27+
</FlexQueryResponse>

0 commit comments

Comments
 (0)