package name.abuchen.portfolio.ui.views.taxonomy;
import java.time.LocalDate;
import javax.inject.Inject;
import org.eclipse.jface.action.IMenuManager;
import org.eclipse.jface.viewers.ColumnLabelProvider;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.widgets.Display;
import name.abuchen.portfolio.model.Classification;
import name.abuchen.portfolio.model.Security;
import name.abuchen.portfolio.model.SecurityPrice;
import name.abuchen.portfolio.money.Money;
import name.abuchen.portfolio.money.Values;
import name.abuchen.portfolio.snapshot.AssetPosition;
import name.abuchen.portfolio.ui.Images;
import name.abuchen.portfolio.ui.Messages;
import name.abuchen.portfolio.ui.util.SimpleAction;
import name.abuchen.portfolio.ui.util.viewers.Column;
import name.abuchen.portfolio.ui.util.viewers.SharesLabelProvider;
import name.abuchen.portfolio.ui.util.viewers.ShowHideColumnHelper;
import name.abuchen.portfolio.ui.util.viewers.ValueEditingSupport;
public class ReBalancingViewer extends AbstractNodeTreeViewer
{
@Inject
public ReBalancingViewer(TaxonomyModel model, TaxonomyNodeRenderer renderer)
{
super(model, renderer);
}
@Override
protected String readExpansionState()
{
return getModel().getExpansionStateRebalancing();
}
@Override
protected void storeExpansionState(String expanded)
{
getModel().setExpansionStateRebalancing(expanded);
}
@Override
protected void addColumns(ShowHideColumnHelper support)
{
addDimensionColumn(support);
addDesiredAllocationColumn(support);
Column column = new Column("targetvalue", Messages.ColumnTargetValue, SWT.RIGHT, 100); //$NON-NLS-1$
column.setLabelProvider(new ColumnLabelProvider()
{
@Override
public String getText(Object element)
{
TaxonomyNode node = (TaxonomyNode) element;
return node.isClassification() ? Values.Money.format(node.getTarget(), getModel().getCurrencyCode())
: null;
}
});
support.addColumn(column);
addActualColumns(support);
column = new Column("delta%", Messages.ColumnDeltaPercent, SWT.RIGHT, 60); //$NON-NLS-1$
column.setLabelProvider(new ColumnLabelProvider()
{
@Override
public String getText(Object element)
{
TaxonomyNode node = (TaxonomyNode) element;
if (node.getTarget() == null)
return null;
return Values.Percent.format(
((double) node.getActual().getAmount() / (double) node.getTarget().getAmount()) - 1);
}
@Override
public Color getForeground(Object element)
{
TaxonomyNode node = (TaxonomyNode) element;
if (node.getTarget() == null)
return null;
return Display.getCurrent().getSystemColor(node.getActual().isGreaterOrEqualThan(node.getTarget())
? SWT.COLOR_DARK_GREEN : SWT.COLOR_DARK_RED);
}
});
support.addColumn(column);
column = new Column("delta", Messages.ColumnDeltaValue, SWT.RIGHT, 100); //$NON-NLS-1$
column.setLabelProvider(new ColumnLabelProvider()
{
@Override
public String getText(Object element)
{
TaxonomyNode node = (TaxonomyNode) element;
if (node.getTarget() == null)
return null;
return Values.Money.format(node.getActual().subtract(node.getTarget()), getModel().getCurrencyCode());
}
@Override
public Color getForeground(Object element)
{
TaxonomyNode node = (TaxonomyNode) element;
if (node.getTarget() == null)
return null;
return Display.getCurrent().getSystemColor(node.getActual().isGreaterOrEqualThan(node.getTarget())
? SWT.COLOR_DARK_GREEN : SWT.COLOR_DARK_RED);
}
});
support.addColumn(column);
column = new Column("quote", Messages.ColumnQuote, SWT.RIGHT, 60); //$NON-NLS-1$
column.setLabelProvider(new ColumnLabelProvider()
{
@Override
public String getText(Object element)
{
TaxonomyNode node = (TaxonomyNode) element;
Security security = node.getBackingSecurity();
if (security == null || security.getCurrencyCode() == null)
return null;
SecurityPrice price = security.getSecurityPrice(LocalDate.now());
return Values.Quote.format(security.getCurrencyCode(), price.getValue(), getModel().getCurrencyCode());
}
});
support.addColumn(column);
column = new Column("shares", Messages.ColumnSharesOwned, SWT.RIGHT, 60); //$NON-NLS-1$
column.setLabelProvider(new SharesLabelProvider()
{
@Override
public Long getValue(Object element)
{
TaxonomyNode node = (TaxonomyNode) element;
Security security = node.getBackingSecurity();
if (security == null || security.getCurrencyCode() == null)
return null;
AssetPosition position = getModel().getClientSnapshot().getPositionsByVehicle().get(security);
if (position == null)
return null;
return Math.round(position.getPosition().getShares() * node.getWeight()
/ (double) Classification.ONE_HUNDRED_PERCENT);
}
});
column.setVisible(false);
support.addColumn(column);
column = new Column("deltashares", Messages.ColumnDeltaShares, SWT.RIGHT, 100); //$NON-NLS-1$
column.setLabelProvider(new ColumnLabelProvider()
{
@Override
public String getText(Object element)
{
TaxonomyNode node = (TaxonomyNode) element;
// no delta shares for unassigned securities
if (node.getParent() != null && node.getParent().isUnassignedCategory())
return null;
Security security = node.getBackingSecurity();
if (security == null || security.getCurrencyCode() == null)
return null;
String priceCurrency = security.getCurrencyCode();
long price = security.getSecurityPrice(LocalDate.now()).getValue();
long weightedPrice = Math.round(node.getWeight() * price / (double) Classification.ONE_HUNDRED_PERCENT);
if (weightedPrice == 0L)
return Values.Share.format(0L);
String deltaCurrency = node.getActual().getCurrencyCode();
long delta = node.getParent().getTarget().getAmount() - node.getParent().getActual().getAmount();
// if currency of the data (here: deltaCurrency) does not match
// the currency of the security (here: priceCurrency), convert
// delta in order to know how many shares need to bought or sold
if (!deltaCurrency.equals(priceCurrency))
{
delta = getModel().getCurrencyConverter().with(priceCurrency)
.convert(LocalDate.now(), Money.of(deltaCurrency, delta)).getAmount();
}
long shares = Math
.round(delta * Values.Share.divider() * Values.Quote.dividerToMoney() / weightedPrice);
return Values.Share.format(shares);
}
});
support.addColumn(column);
addAdditionalColumns(support);
}
private void addDesiredAllocationColumn(ShowHideColumnHelper support)
{
Column column = new Column("desiredAllocation", Messages.ColumnDesiredAllocation, SWT.RIGHT, 70); //$NON-NLS-1$
column.setDescription(Messages.ColumnDesiredAllocation_Description);
column.setLabelProvider(new ColumnLabelProvider()
{
@Override
public String getText(Object element)
{
TaxonomyNode node = (TaxonomyNode) element;
return node.isClassification() ? Values.Weight.format(node.getWeight())
: node.isUnassignedCategory() ? Messages.LabelNotAvailable : null;
}
@Override
public Color getForeground(Object element)
{
TaxonomyNode node = (TaxonomyNode) element;
return node.isClassification() && getModel().hasWeightError(node)
? Display.getDefault().getSystemColor(SWT.COLOR_BLACK) : null;
}
@Override
public Color getBackground(Object element)
{
TaxonomyNode node = (TaxonomyNode) element;
return node.isClassification() && getModel().hasWeightError(node) ? getWarningColor() : null;
}
@Override
public Image getImage(Object element)
{
TaxonomyNode node = (TaxonomyNode) element;
return node.isClassification() && getModel().hasWeightError(node) ? Images.QUICKFIX.image() : null;
}
});
new ValueEditingSupport(TaxonomyNode.class, "weight", Values.Weight) //$NON-NLS-1$
{
@Override
public boolean canEdit(Object element)
{
TaxonomyNode node = (TaxonomyNode) element;
if (node.isAssignment() || node.isUnassignedCategory())
return false;
return super.canEdit(element);
}
}.addListener(this::onWeightModified).attachTo(column);
support.addColumn(column);
}
@Override
protected void fillContextMenu(IMenuManager manager)
{
super.fillContextMenu(manager);
final IStructuredSelection selection = getNodeViewer().getStructuredSelection();
if (selection.isEmpty() || selection.size() > 1)
return;
final TaxonomyNode node = (TaxonomyNode) selection.getFirstElement();
if (node == null || node.isUnassignedCategory())
return;
if (node.isClassification() && getModel().hasWeightError(node))
{
manager.appendToGroup(MENU_GROUP_CUSTOM_ACTIONS,
new SimpleAction(Messages.MenuTaxonomyWeightFix, a -> doFixClassificationWeights(node)));
}
}
private void doFixClassificationWeights(TaxonomyNode node)
{
Classification classification = node.getClassification();
if (node.isUnassignedCategory())
{
classification.setWeight(0);
}
else if (node.isRoot() || node.getParent().isRoot())
{
classification.setWeight(Classification.ONE_HUNDRED_PERCENT);
}
else
{
classification.setWeight(0);
int weight = Math.max(0,
Classification.ONE_HUNDRED_PERCENT - classification.getParent().getChildrenWeight());
classification.setWeight(weight);
}
onTaxnomyNodeEdited(node);
}
}