package name.abuchen.portfolio.snapshot.security; import java.util.ArrayList; import java.util.List; import name.abuchen.portfolio.model.AccountTransaction; import name.abuchen.portfolio.model.PortfolioTransaction; import name.abuchen.portfolio.model.Transaction.Unit; import name.abuchen.portfolio.money.CurrencyConverter; import name.abuchen.portfolio.money.Money; /* package */class CostCalculation extends Calculation { private static class LineItem { public long shares; public long grossAmount; public long netAmount; public LineItem(long shares, long grossAmount, long netAmount) { this.shares = shares; this.grossAmount = grossAmount; this.netAmount = netAmount; } } private List<LineItem> fifo = new ArrayList<>(); private long fees; private long taxes; @Override public void visit(CurrencyConverter converter, DividendInitialTransaction t) { long amount = converter.convert(t.getDate(), t.getMonetaryAmount()).getAmount(); fifo.add(new LineItem(t.getPosition().getShares(), amount, amount)); } @Override public void visit(CurrencyConverter converter, PortfolioTransaction t) { fees += t.getUnitSum(Unit.Type.FEE, converter).getAmount(); taxes += t.getUnitSum(Unit.Type.TAX, converter).getAmount(); switch (t.getType()) { case BUY: case DELIVERY_INBOUND: long grossAmount = t.getMonetaryAmount(converter).getAmount(); long netAmount = t.getGrossValue(converter).getAmount(); fifo.add(new LineItem(t.getShares(), grossAmount, netAmount)); break; case SELL: case DELIVERY_OUTBOUND: long sold = t.getShares(); for (LineItem entry : fifo) { if (entry.shares == 0) continue; if (sold <= 0) break; long n = Math.min(sold, entry.shares); entry.grossAmount -= Math.round(n * entry.grossAmount / entry.shares); entry.netAmount -= Math.round(n * entry.netAmount / entry.shares); entry.shares -= n; sold -= n; } if (sold > 0) { // FIXME Oops. More sold than bought. Report error? Ignore? } break; case TRANSFER_IN: case TRANSFER_OUT: // do nothing break; default: throw new UnsupportedOperationException(); } } @Override public void visit(CurrencyConverter converter, AccountTransaction t) { if (t.getType() == AccountTransaction.Type.TAX_REFUND) taxes -= converter.convert(t.getDate(), t.getMonetaryAmount()).getAmount(); } @Override public void visit(CurrencyConverter converter, DividendTransaction t) { taxes += t.getUnitSum(Unit.Type.TAX, converter).getAmount(); t.setFifoCost(getFifoCost()); t.setTotalShares(getSharesHeld()); } /** * gross investment */ public Money getFifoCost() { long cost = 0; for (LineItem entry : fifo) cost += entry.grossAmount; return Money.of(getTermCurrency(), cost); } /** * net investment, i.e. without fees and taxes */ public Money getNetFifoCost() { long cost = 0; for (LineItem entry : fifo) cost += entry.netAmount; return Money.of(getTermCurrency(), cost); } public long getSharesHeld() { long shares = 0; for (LineItem entry : fifo) shares += entry.shares; return shares; } public Money getFees() { return Money.of(getTermCurrency(), fees); } public Money getTaxes() { return Money.of(getTermCurrency(), taxes); } }