/*
* This file is part of Bitsquare.
*
* Bitsquare is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bitsquare is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bitsquare. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.gui.main.market.trades;
import io.bitsquare.common.UserThread;
import io.bitsquare.common.util.MathUtils;
import io.bitsquare.gui.common.view.ActivatableViewAndModel;
import io.bitsquare.gui.common.view.FxmlView;
import io.bitsquare.gui.main.market.trades.charts.price.CandleStickChart;
import io.bitsquare.gui.main.market.trades.charts.volume.VolumeChart;
import io.bitsquare.gui.util.BSFormatter;
import io.bitsquare.gui.util.CurrencyListItem;
import io.bitsquare.gui.util.GUIUtil;
import io.bitsquare.locale.BSResources;
import io.bitsquare.locale.CurrencyUtil;
import io.bitsquare.trade.statistics.TradeStatistics;
import java.util.Date;
import java.util.concurrent.TimeUnit;
import javafx.beans.property.ReadOnlyObjectWrapper;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.beans.value.ChangeListener;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.transformation.SortedList;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.XYChart;
import javafx.scene.control.*;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Pane;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import javafx.util.Callback;
import javafx.util.StringConverter;
import javax.inject.Inject;
import org.bitcoinj.core.Coin;
import org.bitcoinj.utils.Fiat;
import org.fxmisc.easybind.EasyBind;
import org.fxmisc.easybind.Subscription;
import org.fxmisc.easybind.monadic.MonadicBinding;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@FxmlView
public class TradesChartsView extends ActivatableViewAndModel<VBox, TradesChartsViewModel> {
private static final Logger log = LoggerFactory.getLogger(TradesChartsView.class);
private final BSFormatter formatter;
private TableView<TradeStatistics> tableView;
private ComboBox<CurrencyListItem> currencyComboBox;
private VolumeChart volumeChart;
private CandleStickChart priceChart;
private NumberAxis priceAxisX, priceAxisY, volumeAxisY, volumeAxisX;
private XYChart.Series<Number, Number> priceSeries;
private XYChart.Series<Number, Number> volumeSeries;
private ChangeListener<Number> priceAxisYWidthListener;
private ChangeListener<Number> volumeAxisYWidthListener;
private double priceAxisYWidth;
private double volumeAxisYWidth;
private final StringProperty priceColumnLabel = new SimpleStringProperty();
private ChangeListener<Toggle> timeUnitChangeListener;
private ToggleGroup toggleGroup;
private final ListChangeListener<XYChart.Data<Number, Number>> itemsChangeListener;
private SortedList<TradeStatistics> sortedList;
private Label nrOfTradeStatisticsLabel;
private ListChangeListener<TradeStatistics> tradeStatisticsByCurrencyListener;
private ChangeListener<Number> selectedTabIndexListener;
private SingleSelectionModel<Tab> tabPaneSelectionModel;
private TableColumn<TradeStatistics, TradeStatistics> priceColumn, volumeColumn, marketColumn;
private MonadicBinding<Void> currencySelectionBinding;
private Subscription currencySelectionSubscriber;
///////////////////////////////////////////////////////////////////////////////////////////
// Constructor, lifecycle
///////////////////////////////////////////////////////////////////////////////////////////
@Inject
public TradesChartsView(TradesChartsViewModel model, BSFormatter formatter) {
super(model);
this.formatter = formatter;
// Need to render on next frame as otherwise there are issues in the chart rendering
itemsChangeListener = c -> UserThread.execute(this::updateChartData);
}
@Override
public void initialize() {
HBox toolBox = getToolBox();
createCharts();
createTable();
nrOfTradeStatisticsLabel = new Label("");
nrOfTradeStatisticsLabel.setId("num-offers");
nrOfTradeStatisticsLabel.setPadding(new Insets(-5, 0, -10, 5));
root.getChildren().addAll(toolBox, priceChart, volumeChart, tableView, nrOfTradeStatisticsLabel);
timeUnitChangeListener = (observable, oldValue, newValue) -> {
if (newValue != null) {
model.setTickUnit((TradesChartsViewModel.TickUnit) newValue.getUserData());
priceAxisX.setTickLabelFormatter(getTimeAxisStringConverter());
volumeAxisX.setTickLabelFormatter(getTimeAxisStringConverter());
}
};
priceAxisYWidthListener = (observable, oldValue, newValue) -> {
priceAxisYWidth = (double) newValue;
layoutChart();
};
volumeAxisYWidthListener = (observable, oldValue, newValue) -> {
volumeAxisYWidth = (double) newValue;
layoutChart();
};
tradeStatisticsByCurrencyListener = c -> nrOfTradeStatisticsLabel.setText("Trades: " + model.tradeStatisticsByCurrency.size());
}
@Override
protected void activate() {
// root.getParent() is null at initialize
tabPaneSelectionModel = GUIUtil.getParentOfType(root, TabPane.class).getSelectionModel();
selectedTabIndexListener = (observable, oldValue, newValue) -> model.setSelectedTabIndex((int) newValue);
model.setSelectedTabIndex(tabPaneSelectionModel.getSelectedIndex());
tabPaneSelectionModel.selectedIndexProperty().addListener(selectedTabIndexListener);
currencyComboBox.setItems(model.getCurrencyListItems());
currencyComboBox.setVisibleRowCount(25);
if (model.showAllTradeCurrenciesProperty.get())
currencyComboBox.getSelectionModel().select(0);
else if (model.getSelectedCurrencyListItem().isPresent())
currencyComboBox.getSelectionModel().select(model.getSelectedCurrencyListItem().get());
currencyComboBox.setOnAction(e -> {
CurrencyListItem selectedItem = currencyComboBox.getSelectionModel().getSelectedItem();
if (selectedItem != null)
model.onSetTradeCurrency(selectedItem.tradeCurrency);
});
toggleGroup.getToggles().get(model.tickUnit.ordinal()).setSelected(true);
model.priceItems.addListener(itemsChangeListener);
toggleGroup.selectedToggleProperty().addListener(timeUnitChangeListener);
priceAxisY.widthProperty().addListener(priceAxisYWidthListener);
volumeAxisY.widthProperty().addListener(volumeAxisYWidthListener);
model.tradeStatisticsByCurrency.addListener(tradeStatisticsByCurrencyListener);
priceAxisY.labelProperty().bind(priceColumnLabel);
priceColumn.textProperty().bind(priceColumnLabel);
currencySelectionBinding = EasyBind.combine(
model.showAllTradeCurrenciesProperty, model.selectedTradeCurrencyProperty,
(showAll, selectedTradeCurrency) -> {
priceChart.setVisible(!showAll);
priceChart.setManaged(!showAll);
priceColumn.setSortable(!showAll);
if (showAll) {
volumeColumn.setText("Amount");
priceColumnLabel.set("Price");
if (!tableView.getColumns().contains(marketColumn))
tableView.getColumns().add(1, marketColumn);
} else {
priceSeries.setName(selectedTradeCurrency.getName());
String code = selectedTradeCurrency.getCode();
volumeColumn.setText("Amount in " + code);
priceColumnLabel.set(formatter.getPriceWithCurrencyCode(code));
if (tableView.getColumns().contains(marketColumn))
tableView.getColumns().remove(marketColumn);
}
return null;
});
currencySelectionSubscriber = currencySelectionBinding.subscribe((observable, oldValue, newValue) -> {
});
sortedList = new SortedList<>(model.tradeStatisticsByCurrency);
sortedList.comparatorProperty().bind(tableView.comparatorProperty());
tableView.setItems(sortedList);
priceChart.setAnimated(model.preferences.getUseAnimations());
volumeChart.setAnimated(model.preferences.getUseAnimations());
priceAxisX.setTickLabelFormatter(getTimeAxisStringConverter());
volumeAxisX.setTickLabelFormatter(getTimeAxisStringConverter());
nrOfTradeStatisticsLabel.setText("Trades: " + model.tradeStatisticsByCurrency.size());
UserThread.runAfter(this::updateChartData, 100, TimeUnit.MILLISECONDS);
}
@Override
protected void deactivate() {
currencyComboBox.setOnAction(null);
tabPaneSelectionModel.selectedIndexProperty().removeListener(selectedTabIndexListener);
model.priceItems.removeListener(itemsChangeListener);
toggleGroup.selectedToggleProperty().removeListener(timeUnitChangeListener);
priceAxisY.widthProperty().removeListener(priceAxisYWidthListener);
volumeAxisY.widthProperty().removeListener(volumeAxisYWidthListener);
model.tradeStatisticsByCurrency.removeListener(tradeStatisticsByCurrencyListener);
priceAxisY.labelProperty().unbind();
priceColumn.textProperty().unbind();
currencySelectionSubscriber.unsubscribe();
sortedList.comparatorProperty().unbind();
priceSeries.getData().clear();
priceChart.getData().clear();
}
///////////////////////////////////////////////////////////////////////////////////////////
// Chart
///////////////////////////////////////////////////////////////////////////////////////////
private void createCharts() {
priceSeries = new XYChart.Series<>();
priceAxisX = new NumberAxis(0, model.maxTicks + 1, 1);
priceAxisX.setTickUnit(1);
priceAxisX.setMinorTickCount(0);
priceAxisX.setForceZeroInRange(false);
priceAxisX.setTickLabelFormatter(getTimeAxisStringConverter());
priceAxisY = new NumberAxis();
priceAxisY.setForceZeroInRange(false);
priceAxisY.setAutoRanging(true);
priceAxisY.setTickLabelFormatter(new StringConverter<Number>() {
@Override
public String toString(Number object) {
if (CurrencyUtil.isCryptoCurrency(model.getCurrencyCode())) {
final double value = MathUtils.scaleDownByPowerOf10((double) object, 8);
return formatter.formatRoundedDoubleWithPrecision(value, 8);
} else
return formatter.formatPrice(Fiat.valueOf(model.getCurrencyCode(), MathUtils.doubleToLong((double) object)));
}
@Override
public Number fromString(String string) {
return null;
}
});
priceChart = new CandleStickChart(priceAxisX, priceAxisY, new StringConverter<Number>() {
@Override
public String toString(Number object) {
if (CurrencyUtil.isCryptoCurrency(model.getCurrencyCode())) {
final double value = MathUtils.scaleDownByPowerOf10((long) object, 8);
return formatter.formatRoundedDoubleWithPrecision(value, 8);
} else {
return formatter.formatPrice(Fiat.valueOf(model.getCurrencyCode(), (long) object));
}
}
@Override
public Number fromString(String string) {
return null;
}
});
priceChart.setMinHeight(198);
priceChart.setPrefHeight(198);
priceChart.setMaxHeight(300);
priceChart.setLegendVisible(false);
priceChart.setData(FXCollections.observableArrayList(priceSeries));
volumeSeries = new XYChart.Series<>();
volumeAxisX = new NumberAxis(0, model.maxTicks + 1, 1);
volumeAxisX.setTickUnit(1);
volumeAxisX.setMinorTickCount(0);
volumeAxisX.setForceZeroInRange(false);
volumeAxisX.setTickLabelFormatter(getTimeAxisStringConverter());
volumeAxisY = new NumberAxis();
volumeAxisY.setForceZeroInRange(true);
volumeAxisY.setAutoRanging(true);
volumeAxisY.setLabel("Volume in BTC");
volumeAxisY.setTickLabelFormatter(new StringConverter<Number>() {
@Override
public String toString(Number object) {
return formatter.formatCoin(Coin.valueOf(MathUtils.doubleToLong((double) object)));
}
@Override
public Number fromString(String string) {
return null;
}
});
volumeChart = new VolumeChart(volumeAxisX, volumeAxisY, new StringConverter<Number>() {
@Override
public String toString(Number object) {
return formatter.formatCoinWithCode(Coin.valueOf((long) object));
}
@Override
public Number fromString(String string) {
return null;
}
});
volumeChart.setData(FXCollections.observableArrayList(volumeSeries));
volumeChart.setMinHeight(148);
volumeChart.setPrefHeight(148);
volumeChart.setMaxHeight(200);
volumeChart.setLegendVisible(false);
}
private void updateChartData() {
volumeSeries.getData().setAll(model.volumeItems);
// At price chart we need to set the priceSeries new otherwise the lines are not rendered correctly
// TODO should be fixed in candle chart
priceSeries.getData().clear();
priceSeries = new XYChart.Series<>();
priceSeries.getData().setAll(model.priceItems);
priceChart.getData().clear();
priceChart.setData(FXCollections.observableArrayList(priceSeries));
}
private void layoutChart() {
UserThread.execute(() -> {
if (volumeAxisYWidth > priceAxisYWidth) {
priceChart.setPadding(new Insets(0, 0, 0, volumeAxisYWidth - priceAxisYWidth));
volumeChart.setPadding(new Insets(0, 0, 0, 0));
} else if (volumeAxisYWidth < priceAxisYWidth) {
priceChart.setPadding(new Insets(0, 0, 0, 0));
volumeChart.setPadding(new Insets(0, 0, 0, priceAxisYWidth - volumeAxisYWidth));
}
});
}
@NotNull
private StringConverter<Number> getTimeAxisStringConverter() {
return new StringConverter<Number>() {
@Override
public String toString(Number object) {
long index = MathUtils.doubleToLong((double) object);
long time = model.getTimeFromTickIndex(index);
if (model.tickUnit.ordinal() <= TradesChartsViewModel.TickUnit.DAY.ordinal())
return index % 4 == 0 ? formatter.formatDate(new Date(time)) : "";
else
return index % 3 == 0 ? formatter.formatTime(new Date(time)) : "";
}
@Override
public Number fromString(String string) {
return null;
}
};
}
///////////////////////////////////////////////////////////////////////////////////////////
// CurrencyComboBox
///////////////////////////////////////////////////////////////////////////////////////////
private HBox getToolBox() {
Label currencyLabel = new Label("Currency:");
currencyLabel.setPadding(new Insets(0, 4, 0, 0));
currencyComboBox = new ComboBox<>();
currencyComboBox.setPromptText("Select currency");
currencyComboBox.setConverter(GUIUtil.getCurrencyListItemConverter("trades", model.preferences));
Pane spacer = new Pane();
HBox.setHgrow(spacer, Priority.ALWAYS);
Label label = new Label("Interval:");
label.setPadding(new Insets(0, 4, 0, 0));
toggleGroup = new ToggleGroup();
ToggleButton year = getToggleButton("Year", TradesChartsViewModel.TickUnit.YEAR, toggleGroup, "toggle-left");
ToggleButton month = getToggleButton("Month", TradesChartsViewModel.TickUnit.MONTH, toggleGroup, "toggle-left");
ToggleButton week = getToggleButton("Week", TradesChartsViewModel.TickUnit.WEEK, toggleGroup, "toggle-center");
ToggleButton day = getToggleButton("Day", TradesChartsViewModel.TickUnit.DAY, toggleGroup, "toggle-center");
ToggleButton hour = getToggleButton("Hour", TradesChartsViewModel.TickUnit.HOUR, toggleGroup, "toggle-center");
ToggleButton minute10 = getToggleButton("10 Minutes", TradesChartsViewModel.TickUnit.MINUTE_10, toggleGroup, "toggle-center");
HBox hBox = new HBox();
hBox.setSpacing(0);
hBox.setPadding(new Insets(5, 9, -10, 10));
hBox.setAlignment(Pos.CENTER_LEFT);
hBox.getChildren().addAll(currencyLabel, currencyComboBox, spacer, label, year, month, week, day, hour, minute10);
return hBox;
}
private ToggleButton getToggleButton(String label, TradesChartsViewModel.TickUnit tickUnit, ToggleGroup toggleGroup, String style) {
ToggleButton toggleButton = new ToggleButton(label);
toggleButton.setPadding(new Insets(0, 5, 0, 5));
toggleButton.setUserData(tickUnit);
toggleButton.setToggleGroup(toggleGroup);
toggleButton.setId(style);
return toggleButton;
}
///////////////////////////////////////////////////////////////////////////////////////////
// Table
///////////////////////////////////////////////////////////////////////////////////////////
private void createTable() {
tableView = new TableView<>();
tableView.setMinHeight(140);
tableView.setPrefHeight(140);
VBox.setVgrow(tableView, Priority.ALWAYS);
// date
TableColumn<TradeStatistics, TradeStatistics> dateColumn = new TableColumn<TradeStatistics, TradeStatistics>("Date/Time") {
{
setMinWidth(190);
setMaxWidth(190);
}
};
dateColumn.setCellValueFactory((tradeStatistics) -> new ReadOnlyObjectWrapper<>(tradeStatistics.getValue()));
dateColumn.setCellFactory(
new Callback<TableColumn<TradeStatistics, TradeStatistics>, TableCell<TradeStatistics,
TradeStatistics>>() {
@Override
public TableCell<TradeStatistics, TradeStatistics> call(
TableColumn<TradeStatistics, TradeStatistics> column) {
return new TableCell<TradeStatistics, TradeStatistics>() {
@Override
public void updateItem(final TradeStatistics item, boolean empty) {
super.updateItem(item, empty);
if (item != null)
setText(formatter.formatDateTime(item.getTradeDate()));
else
setText("");
}
};
}
});
dateColumn.setComparator((o1, o2) -> o1.getTradeDate().compareTo(o2.getTradeDate()));
tableView.getColumns().add(dateColumn);
// market
marketColumn = new TableColumn<TradeStatistics, TradeStatistics>("Market") {
{
setMinWidth(130);
setMaxWidth(130);
}
};
marketColumn.setCellValueFactory((tradeStatistics) -> new ReadOnlyObjectWrapper<>(tradeStatistics.getValue()));
marketColumn.setCellFactory(
new Callback<TableColumn<TradeStatistics, TradeStatistics>, TableCell<TradeStatistics,
TradeStatistics>>() {
@Override
public TableCell<TradeStatistics, TradeStatistics> call(
TableColumn<TradeStatistics, TradeStatistics> column) {
return new TableCell<TradeStatistics, TradeStatistics>() {
@Override
public void updateItem(final TradeStatistics item, boolean empty) {
super.updateItem(item, empty);
if (item != null)
setText(formatter.getCurrencyPair(item.currency));
else
setText("");
}
};
}
});
marketColumn.setComparator((o1, o2) -> o1.getTradeDate().compareTo(o2.getTradeDate()));
tableView.getColumns().add(marketColumn);
// price
priceColumn = new TableColumn<>();
priceColumn.setCellValueFactory((tradeStatistics) -> new ReadOnlyObjectWrapper<>(tradeStatistics.getValue()));
priceColumn.setCellFactory(
new Callback<TableColumn<TradeStatistics, TradeStatistics>, TableCell<TradeStatistics,
TradeStatistics>>() {
@Override
public TableCell<TradeStatistics, TradeStatistics> call(
TableColumn<TradeStatistics, TradeStatistics> column) {
return new TableCell<TradeStatistics, TradeStatistics>() {
@Override
public void updateItem(final TradeStatistics item, boolean empty) {
super.updateItem(item, empty);
if (item != null)
setText(formatter.formatPrice(item.getTradePrice()));
else
setText("");
}
};
}
});
priceColumn.setComparator((o1, o2) -> o1.getTradePrice().compareTo(o2.getTradePrice()));
tableView.getColumns().add(priceColumn);
// amount
TableColumn<TradeStatistics, TradeStatistics> amountColumn = new TableColumn<>("Amount in BTC");
amountColumn.setCellValueFactory((tradeStatistics) -> new ReadOnlyObjectWrapper<>(tradeStatistics.getValue()));
amountColumn.setCellFactory(
new Callback<TableColumn<TradeStatistics, TradeStatistics>, TableCell<TradeStatistics,
TradeStatistics>>() {
@Override
public TableCell<TradeStatistics, TradeStatistics> call(
TableColumn<TradeStatistics, TradeStatistics> column) {
return new TableCell<TradeStatistics, TradeStatistics>() {
@Override
public void updateItem(final TradeStatistics item, boolean empty) {
super.updateItem(item, empty);
if (item != null)
setText(formatter.formatCoinWithCode(item.getTradeAmount()));
else
setText("");
}
};
}
});
amountColumn.setComparator((o1, o2) -> o1.getTradeAmount().compareTo(o2.getTradeAmount()));
tableView.getColumns().add(amountColumn);
// volume
volumeColumn = new TableColumn<>();
volumeColumn.setCellValueFactory((tradeStatistics) -> new ReadOnlyObjectWrapper<>(tradeStatistics.getValue()));
volumeColumn.setCellFactory(
new Callback<TableColumn<TradeStatistics, TradeStatistics>, TableCell<TradeStatistics,
TradeStatistics>>() {
@Override
public TableCell<TradeStatistics, TradeStatistics> call(
TableColumn<TradeStatistics, TradeStatistics> column) {
return new TableCell<TradeStatistics, TradeStatistics>() {
@Override
public void updateItem(final TradeStatistics item, boolean empty) {
super.updateItem(item, empty);
if (item != null)
setText(model.showAllTradeCurrenciesProperty.get() ?
formatter.formatVolumeWithCode(item.getTradeVolume()) :
formatter.formatVolume(item.getTradeVolume()));
else
setText("");
}
};
}
});
volumeColumn.setComparator((o1, o2) -> {
final Fiat tradeVolume1 = o1.getTradeVolume();
final Fiat tradeVolume2 = o2.getTradeVolume();
return tradeVolume1 != null && tradeVolume2 != null ? tradeVolume1.compareTo(tradeVolume2) : 0;
});
tableView.getColumns().add(volumeColumn);
// paymentMethod
TableColumn<TradeStatistics, TradeStatistics> paymentMethodColumn = new TableColumn<>("Payment method");
paymentMethodColumn.setCellValueFactory((tradeStatistics) -> new ReadOnlyObjectWrapper<>(tradeStatistics.getValue()));
paymentMethodColumn.setCellFactory(
new Callback<TableColumn<TradeStatistics, TradeStatistics>, TableCell<TradeStatistics,
TradeStatistics>>() {
@Override
public TableCell<TradeStatistics, TradeStatistics> call(
TableColumn<TradeStatistics, TradeStatistics> column) {
return new TableCell<TradeStatistics, TradeStatistics>() {
@Override
public void updateItem(final TradeStatistics item, boolean empty) {
super.updateItem(item, empty);
if (item != null)
setText(getPaymentMethodLabel(item));
else
setText("");
}
};
}
});
paymentMethodColumn.setComparator((o1, o2) -> getPaymentMethodLabel(o1).compareTo(getPaymentMethodLabel(o2)));
tableView.getColumns().add(paymentMethodColumn);
// direction
TableColumn<TradeStatistics, TradeStatistics> directionColumn = new TableColumn<>("Trade type");
directionColumn.setCellValueFactory((tradeStatistics) -> new ReadOnlyObjectWrapper<>(tradeStatistics.getValue()));
directionColumn.setCellFactory(
new Callback<TableColumn<TradeStatistics, TradeStatistics>, TableCell<TradeStatistics,
TradeStatistics>>() {
@Override
public TableCell<TradeStatistics, TradeStatistics> call(
TableColumn<TradeStatistics, TradeStatistics> column) {
return new TableCell<TradeStatistics, TradeStatistics>() {
@Override
public void updateItem(final TradeStatistics item, boolean empty) {
super.updateItem(item, empty);
if (item != null)
setText(getDirectionLabel(item));
else
setText("");
}
};
}
});
directionColumn.setComparator((o1, o2) -> getDirectionLabel(o1).compareTo(getDirectionLabel(o2)));
tableView.getColumns().add(directionColumn);
tableView.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
Label placeholder = new Label("There is no data available");
placeholder.setWrapText(true);
tableView.setPlaceholder(placeholder);
dateColumn.setSortType(TableColumn.SortType.DESCENDING);
tableView.getSortOrder().add(dateColumn);
}
@NotNull
private String getDirectionLabel(TradeStatistics item) {
return formatter.getDirectionWithCode(item.direction, item.currency);
}
@NotNull
private String getPaymentMethodLabel(TradeStatistics item) {
return BSResources.get(item.paymentMethod);
}
}