package name.abuchen.portfolio.snapshot.filter;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import name.abuchen.portfolio.model.Account;
import name.abuchen.portfolio.model.AccountTransaction;
import name.abuchen.portfolio.model.Client;
import name.abuchen.portfolio.model.Portfolio;
import name.abuchen.portfolio.model.PortfolioTransaction;
import name.abuchen.portfolio.model.Security;
/**
* Filters the Client to include only transactions related to the given
* portfolios and accounts.
*/
public class PortfolioClientFilter implements ClientFilter
{
private final List<Portfolio> portfolios;
private final List<Account> accounts;
public PortfolioClientFilter(List<Portfolio> portfolios, List<Account> accounts)
{
this.portfolios = portfolios;
this.accounts = accounts;
}
public PortfolioClientFilter(Portfolio portfolio)
{
this(Arrays.asList(portfolio), Collections.emptyList());
}
public PortfolioClientFilter(Portfolio portfolio, Account account)
{
this(Arrays.asList(portfolio), Arrays.asList(account));
}
@Override
public Client filter(Client client)
{
ReadOnlyClient pseudoClient = new ReadOnlyClient(client);
Map<Account, ReadOnlyAccount> account2pseudo = new HashMap<>();
Set<Security> usedSecurities = new HashSet<>();
for (Portfolio portfolio : portfolios)
{
ReadOnlyAccount pseudoAccount = account2pseudo.computeIfAbsent(portfolio.getReferenceAccount(), a -> {
ReadOnlyAccount pa = new ReadOnlyAccount(a);
pseudoClient.internalAddAccount(pa);
return pa;
});
ReadOnlyPortfolio pseudoPortfolio = new ReadOnlyPortfolio(portfolio);
pseudoPortfolio.setReferenceAccount(pseudoAccount);
pseudoClient.internalAddPortfolio(pseudoPortfolio);
adaptPortfolioTransactions(portfolio, pseudoPortfolio, usedSecurities);
if (!accounts.contains(portfolio.getReferenceAccount()))
collectDividends(portfolio, pseudoAccount, usedSecurities);
}
for (Account account : accounts)
{
ReadOnlyAccount pseudoAccount = account2pseudo.computeIfAbsent(account, a -> {
ReadOnlyAccount pa = new ReadOnlyAccount(a);
pseudoClient.internalAddAccount(pa);
return pa;
});
adaptAccountTransactions(account, pseudoAccount, usedSecurities);
}
for (Security security : usedSecurities)
pseudoClient.internalAddSecurity(security);
return pseudoClient;
}
private void adaptPortfolioTransactions(Portfolio portfolio, ReadOnlyPortfolio pseudoPortfolio,
Set<Security> usedSecurities)
{
for (PortfolioTransaction t : portfolio.getTransactions())
{
usedSecurities.add(t.getSecurity());
switch (t.getType())
{
case BUY:
if (accounts.contains(t.getCrossEntry().getCrossOwner(t)))
pseudoPortfolio.internalAddTransaction(t);
else
pseudoPortfolio.internalAddTransaction(
convertTo(t, PortfolioTransaction.Type.DELIVERY_INBOUND));
break;
case TRANSFER_IN:
if (portfolios.contains(t.getCrossEntry().getCrossOwner(t)))
pseudoPortfolio.internalAddTransaction(t);
else
pseudoPortfolio.internalAddTransaction(
convertTo(t, PortfolioTransaction.Type.DELIVERY_INBOUND));
break;
case SELL:
if (accounts.contains(t.getCrossEntry().getCrossOwner(t)))
pseudoPortfolio.internalAddTransaction(t);
else
pseudoPortfolio.internalAddTransaction(
convertTo(t, PortfolioTransaction.Type.DELIVERY_OUTBOUND));
break;
case TRANSFER_OUT:
if (portfolios.contains(t.getCrossEntry().getCrossOwner(t)))
pseudoPortfolio.internalAddTransaction(t);
else
pseudoPortfolio.internalAddTransaction(
convertTo(t, PortfolioTransaction.Type.DELIVERY_OUTBOUND));
break;
case DELIVERY_INBOUND:
case DELIVERY_OUTBOUND:
pseudoPortfolio.internalAddTransaction(t);
break;
default:
throw new UnsupportedOperationException();
}
}
}
private void collectDividends(Portfolio portfolio, ReadOnlyAccount pseudoAccount, Set<Security> usedSecurities)
{
if (portfolio.getReferenceAccount() == null)
return;
for (AccountTransaction t : portfolio.getReferenceAccount().getTransactions()) // NOSONAR
{
if (t.getSecurity() == null)
continue;
if (!usedSecurities.contains(t.getSecurity()))
continue;
switch (t.getType())
{
case TAX_REFUND:
// security must be non-null -> tax refund is relevant for
// performance of security
case DIVIDENDS:
pseudoAccount.internalAddTransaction(t);
pseudoAccount.internalAddTransaction(new AccountTransaction(t.getDate(), t.getCurrencyCode(),
t.getAmount(), null, AccountTransaction.Type.REMOVAL));
break;
case BUY:
case TRANSFER_IN:
case SELL:
case TRANSFER_OUT:
case DEPOSIT:
case REMOVAL:
case INTEREST:
case INTEREST_CHARGE:
case TAXES:
case FEES:
case FEES_REFUND:
// do nothing
break;
default:
throw new UnsupportedOperationException();
}
}
}
private void adaptAccountTransactions(Account account, ReadOnlyAccount pseudoAccount, Set<Security> usedSecurities)
{
for (AccountTransaction t : account.getTransactions())
{
switch (t.getType())
{
case BUY:
if (portfolios.contains(t.getCrossEntry().getCrossOwner(t)))
pseudoAccount.internalAddTransaction(t);
else
pseudoAccount.internalAddTransaction(convertTo(t, AccountTransaction.Type.REMOVAL));
break;
case SELL:
if (portfolios.contains(t.getCrossEntry().getCrossOwner(t)))
pseudoAccount.internalAddTransaction(t);
else
pseudoAccount.internalAddTransaction(convertTo(t, AccountTransaction.Type.DEPOSIT));
break;
case TRANSFER_IN:
if (accounts.contains(t.getCrossEntry().getCrossOwner(t)))
pseudoAccount.internalAddTransaction(t);
else
pseudoAccount.internalAddTransaction(convertTo(t, AccountTransaction.Type.DEPOSIT));
break;
case TRANSFER_OUT:
if (accounts.contains(t.getCrossEntry().getCrossOwner(t)))
pseudoAccount.internalAddTransaction(t);
else
pseudoAccount.internalAddTransaction(convertTo(t, AccountTransaction.Type.REMOVAL));
break;
case DIVIDENDS:
case TAX_REFUND:
if (t.getSecurity() == null || usedSecurities.contains(t.getSecurity()))
pseudoAccount.internalAddTransaction(t);
else
pseudoAccount.internalAddTransaction(convertTo(t, AccountTransaction.Type.DEPOSIT));
break;
case DEPOSIT:
case REMOVAL:
case INTEREST:
case INTEREST_CHARGE:
case TAXES:
case FEES:
case FEES_REFUND:
pseudoAccount.internalAddTransaction(t);
break;
default:
throw new UnsupportedOperationException();
}
}
}
private PortfolioTransaction convertTo(PortfolioTransaction t, PortfolioTransaction.Type type)
{
PortfolioTransaction clone = new PortfolioTransaction();
clone.setType(type);
clone.setDate(t.getDate());
clone.setCurrencyCode(t.getCurrencyCode());
clone.setSecurity(t.getSecurity());
clone.setAmount(t.getAmount());
clone.setShares(t.getShares());
clone.addUnits(t.getUnits());
return clone;
}
private AccountTransaction convertTo(AccountTransaction t, AccountTransaction.Type type)
{
AccountTransaction clone = new AccountTransaction();
clone.setType(type);
clone.setDate(t.getDate());
clone.setCurrencyCode(t.getCurrencyCode());
clone.setSecurity(null); // no security for REMOVAL or DEPOSIT
clone.setAmount(t.getAmount());
clone.setShares(t.getShares());
// do *not* copy units as REMOVAL and DEPOSIT have never units
return clone;
}
}