|
16 | 16 | import name.abuchen.portfolio.model.PortfolioTransaction; |
17 | 17 | import name.abuchen.portfolio.model.PortfolioTransaction.Type; |
18 | 18 | import name.abuchen.portfolio.model.Security; |
| 19 | +import name.abuchen.portfolio.model.SecurityPrice; |
19 | 20 | import name.abuchen.portfolio.money.CurrencyConverter; |
| 21 | +import name.abuchen.portfolio.money.Values; |
20 | 22 | import name.abuchen.portfolio.snapshot.security.BaseSecurityPerformanceRecord.Periodicity; |
21 | 23 |
|
22 | 24 | @SuppressWarnings("nls") |
@@ -196,4 +198,61 @@ public void rateOfReturnCalculationTest() |
196 | 198 |
|
197 | 199 | assertEquals(0.1, dividends.getRateOfReturnPerYear(), 0.0); |
198 | 200 | } |
| 201 | + |
| 202 | + @Test |
| 203 | + public void rateOfReturnWithCurrencyMismatchBugTest() |
| 204 | + { |
| 205 | + // Test case that catches the currency mismatch bug: |
| 206 | + // When transaction currency (USD) differs from term currency (EUR), |
| 207 | + // the bug divides raw USD amount by converted EUR cost without proper conversion. |
| 208 | + // |
| 209 | + // Purchase: 10 shares at 100 USD per share = 1000 USD total |
| 210 | + // Dividend: 50 USD (5 USD per share) on 2015-01-15 |
| 211 | + // |
| 212 | + // Using TestCurrencyConverter with EUR term currency: |
| 213 | + // Exchange rate on 2015-01-15: ~1.1708 EUR per USD (from test data) |
| 214 | + // - Purchase cost in EUR: 1000 USD * 1.1708 = 1170.8 EUR |
| 215 | + // - Dividend in EUR: 50 USD * 1.1708 = 58.54 EUR |
| 216 | + // - Correct rate: 58.54 / 1170.8 = 0.05 (5%) |
| 217 | + // |
| 218 | + // BUG: Code uses raw dividend amount (5000 in stored format, USD) divided by |
| 219 | + // converted cost (in EUR), which gives wrong result |
| 220 | + // With buggy code: ~0.0588 (wrong - dividing USD by EUR) |
| 221 | + // With fixed code: ~0.05 (correct - both in EUR) |
| 222 | + |
| 223 | + Security testSecurity = new Security("Test Security", "USD"); |
| 224 | + testSecurity.addPrice(new SecurityPrice(LocalDateTime.of(2015, 1, 15, 0, 0).toLocalDate(), |
| 225 | + Values.Quote.factorize(100))); |
| 226 | + |
| 227 | + List<CalculationLineItem> transactions = new ArrayList<>(); |
| 228 | + |
| 229 | + // Purchase: 10 shares at 100 USD = 1000 USD |
| 230 | + transactions.add(CalculationLineItem.of(new Portfolio(), new PortfolioTransaction( |
| 231 | + LocalDateTime.of(2015, 1, 14, 12, 0), testSecurity.getCurrencyCode(), |
| 232 | + Values.Amount.factorize(1000), testSecurity, Values.Share.factorize(10), Type.BUY, 0L, 0L))); |
| 233 | + |
| 234 | + // Dividend: 50 USD (5 USD per share) |
| 235 | + AccountTransaction dividend = new AccountTransaction(); |
| 236 | + dividend.setType(AccountTransaction.Type.DIVIDENDS); |
| 237 | + dividend.setSecurity(testSecurity); |
| 238 | + dividend.setDateTime(LocalDateTime.of(2015, 1, 15, 12, 0)); |
| 239 | + dividend.setAmount(Values.Amount.factorize(50)); |
| 240 | + dividend.setShares(Values.Share.factorize(10)); |
| 241 | + dividend.setCurrencyCode(testSecurity.getCurrencyCode()); |
| 242 | + |
| 243 | + transactions.add(CalculationLineItem.of(new Account(), dividend)); |
| 244 | + |
| 245 | + @SuppressWarnings("unused") |
| 246 | + CostCalculation cost = Calculation.perform(CostCalculation.class, converter, testSecurity, transactions); |
| 247 | + DividendCalculation dividends = Calculation.perform(DividendCalculation.class, converter, testSecurity, |
| 248 | + transactions); |
| 249 | + |
| 250 | + // Expected: 50 USD / 1000 USD = 0.05 (5%) |
| 251 | + // After currency conversion to EUR: ~58.54 EUR / ~1170.8 EUR = 0.05 |
| 252 | + // With bug: Uses raw USD amount / converted EUR cost = ~0.0588 (wrong!) |
| 253 | + // With fix: Uses converted dividend / converted cost = ~0.05 (correct) |
| 254 | + // This test will FAIL with the buggy code (~0.0588) and PASS with the fix (~0.05) |
| 255 | + // Tolerance is tight (0.005) to catch the bug: 0.0588 - 0.05 = 0.0088 > 0.005 |
| 256 | + assertEquals(0.05, dividends.getRateOfReturnPerYear(), 0.005); |
| 257 | + } |
199 | 258 | } |
0 commit comments