@@ -392,15 +392,15 @@ private class IBFlexStatementExtractorResult
392392 case "Payment In Lieu Of Dividends" :
393393 // Set the Symbol
394394 if (element .getAttribute ("symbol" ).length () > 0 )
395- accountTransaction .setSecurity (this .getOrCreateSecurity (element , true ));
395+ accountTransaction .setSecurity (this .getOrCreateSecurity (element , true , false ));
396396
397397 accountTransaction .setType (AccountTransaction .Type .DIVIDENDS );
398398 this .calculateShares (accountTransaction , element );
399399 break ;
400400 case "Withholding Tax" :
401401 // Set the Symbol
402402 if (element .getAttribute ("symbol" ).length () > 0 )
403- accountTransaction .setSecurity (this .getOrCreateSecurity (element , true ));
403+ accountTransaction .setSecurity (this .getOrCreateSecurity (element , true , false ));
404404
405405 // Positive amount are a tax refund
406406 if (Math .signum (Double .parseDouble (element .getAttribute ("amount" ))) == -1 )
@@ -613,7 +613,7 @@ private class IBFlexStatementExtractorResult
613613 portfolioTransaction .setDate (extractDate (element ));
614614
615615 // Set security before amount so that setAmount can detect currency mismatches in all cases
616- portfolioTransaction .setSecurity (this .getOrCreateSecurity (element , true ));
616+ portfolioTransaction .setSecurity (this .getOrCreateSecurity (element , true , true ));
617617
618618 // @formatter:off
619619 // Set amount and check if the element contains the "netCash"
@@ -692,7 +692,7 @@ else if (Messages.MsgErrorOrderCancellationUnsupported.equals(portfolioTransacti
692692 double qty = Math .abs (Double .parseDouble (element .getAttribute ("quantity" )));
693693 portfolioTransaction .setShares (Values .Share .factorize (qty ));
694694
695- portfolioTransaction .setSecurity (this .getOrCreateSecurity (element , true ));
695+ portfolioTransaction .setSecurity (this .getOrCreateSecurity (element , true , true ));
696696
697697 portfolioTransaction .setMonetaryAmount (proceeds );
698698
@@ -714,7 +714,7 @@ else if (Messages.MsgErrorOrderCancellationUnsupported.equals(portfolioTransacti
714714 Double qty = Math .abs (Double .parseDouble (element .getAttribute ("quantity" )));
715715 portfolioTransaction .setShares (Math .round (qty .doubleValue () * Values .Share .factor ()));
716716
717- portfolioTransaction .setSecurity (this .getOrCreateSecurity (element , true ));
717+ portfolioTransaction .setSecurity (this .getOrCreateSecurity (element , true , true ));
718718 portfolioTransaction .setNote (element .getAttribute ("description" ));
719719
720720 portfolioTransaction .setMonetaryAmount (proceeds );
@@ -1070,6 +1070,27 @@ else if (series.getBaseCurrency().equals(toCurrency) && series.getTermCurrency()
10701070 return null ;
10711071 }
10721072
1073+ /**
1074+ * Checks if two currencies are compatible for matching purposes.
1075+ * Currencies are compatible if they are equal or if one is a major/minor
1076+ * unit of the other (e.g., GBP and GBX, ILS and ILA, ZAR and ZAC).
1077+ *
1078+ * @param currency1 First currency code
1079+ * @param currency2 Second currency code
1080+ * @return true if currencies are compatible
1081+ */
1082+ private boolean isCurrencyCompatible (String currency1 , String currency2 )
1083+ {
1084+ if (currency1 == null || currency2 == null )
1085+ return false ;
1086+
1087+ if (currency1 .equals (currency2 ))
1088+ return true ;
1089+
1090+ // Check if there's a fixed exchange rate between them (major/minor unit relationship)
1091+ return getUnitExchangeRate (currency1 , currency2 ) != null ;
1092+ }
1093+
10731094 /**
10741095 * @formatter:off
10751096 * Imports model objects from the statement based on the specified type using the provided handling function.
@@ -1199,11 +1220,13 @@ public void addError(Exception e)
11991220 * Looks up a Security in the model or creates a new one if it does not yet exist.
12001221 * It uses the IB ContractID (conID) for the WKN, tries to degrade if conID or ISIN are not available.
12011222 *
1202- * @param element The XML element containing information about the security.
1203- * @param doCreate A flag indicating whether to create a new Security if not found.
1204- * @return The found or created Security object.
1223+ * @param element The XML element containing information about the security.
1224+ * @param doCreate A flag indicating whether to create a new Security if not found.
1225+ * @param strictCurrencyMatch If true (for trades), only match on ISIN if currency also matches.
1226+ * If false (for dividends), allow ISIN match regardless of currency.
1227+ * @return The found or created Security object.
12051228 */
1206- private Security getOrCreateSecurity (Element element , boolean doCreate )
1229+ private Security getOrCreateSecurity (Element element , boolean doCreate , boolean strictCurrencyMatch )
12071230 {
12081231 // Lookup the Exchange Suffix for Yahoo
12091232 Optional <String > tickerSymbol = Optional .ofNullable (element .getAttribute ("symbol" ));
@@ -1268,6 +1291,7 @@ private Security getOrCreateSecurity(Element element, boolean doCreate)
12681291 }
12691292
12701293 Security matchingSecurity = null ;
1294+ Security matchingTickerSecurity = null ;
12711295
12721296 for (Security security : allSecurities )
12731297 {
@@ -1276,18 +1300,33 @@ private Security getOrCreateSecurity(Element element, boolean doCreate)
12761300 return security ;
12771301
12781302 if (!isin .isEmpty () && isin .equals (security .getIsin ()))
1279- if (currency . equals ( security .getCurrencyCode ()))
1303+ if (isCurrencyCompatible ( currency , security .getCurrencyCode ()))
12801304 return security ;
1281- else
1305+ else if (! strictCurrencyMatch )
12821306 matchingSecurity = security ;
12831307
1308+ // Only match by ticker symbol if CONID and ISIN don't conflict
12841309 if (computedTickerSymbol .isPresent () && computedTickerSymbol .get ().equals (security .getTickerSymbol ()))
1285- return security ;
1310+ {
1311+ // Don't match by ticker if CONID or ISIN conflict
1312+ boolean conidConflicts = conid != null && conid .length () > 0
1313+ && security .getWkn () != null && security .getWkn ().length () > 0
1314+ && !conid .equals (security .getWkn ());
1315+ boolean isinConflicts = !isin .isEmpty ()
1316+ && security .getIsin () != null && security .getIsin ().length () > 0
1317+ && !isin .equals (security .getIsin ());
1318+
1319+ if (!conidConflicts && !isinConflicts )
1320+ matchingTickerSecurity = security ;
1321+ }
12861322 }
12871323
12881324 if (matchingSecurity != null )
12891325 return matchingSecurity ;
12901326
1327+ if (matchingTickerSecurity != null )
1328+ return matchingTickerSecurity ;
1329+
12911330 if (!doCreate )
12921331 return null ;
12931332
0 commit comments