Skip to content

Commit

Permalink
Merge pull request #4706 from chimp1984/remove-outliers-at-bsq-rate
Browse files Browse the repository at this point in the history
Remove outliers when calculating BSQ rate
  • Loading branch information
sqrrm authored Oct 31, 2020
2 parents f3c62d3 + 8bfe9b8 commit 89620a0
Show file tree
Hide file tree
Showing 7 changed files with 95 additions and 22 deletions.
7 changes: 7 additions & 0 deletions core/src/main/java/bisq/core/user/Preferences.java
Original file line number Diff line number Diff line change
Expand Up @@ -416,6 +416,11 @@ public void setTacAcceptedV120(boolean tacAccepted) {
requestPersistence();
}

public void setBsqAverageTrimThreshold(double bsqAverageTrimThreshold) {
prefPayload.setBsqAverageTrimThreshold(bsqAverageTrimThreshold);
requestPersistence();
}

public Optional<AutoConfirmSettings> findAutoConfirmSettings(String currencyCode) {
return prefPayload.getAutoConfirmSettingsList().stream()
.filter(e -> e.getCurrencyCode().equals(currencyCode))
Expand Down Expand Up @@ -1034,6 +1039,8 @@ private interface ExcludesDelegateMethods {

void setTacAcceptedV120(boolean tacAccepted);

void setBsqAverageTrimThreshold(double bsqAverageTrimThreshold);

void setAutoConfirmSettings(AutoConfirmSettings autoConfirmSettings);
}
}
7 changes: 5 additions & 2 deletions core/src/main/java/bisq/core/user/PreferencesPayload.java
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ public final class PreferencesPayload implements PersistableEnvelope {
private double buyerSecurityDepositAsPercentForCrypto = getDefaultBuyerSecurityDepositAsPercent();
private int blockNotifyPort;
private boolean tacAcceptedV120;
private double bsqAverageTrimThreshold = 0.05;

// Added at 1.3.8
private List<AutoConfirmSettings> autoConfirmSettingsList = new ArrayList<>();
Expand Down Expand Up @@ -188,9 +189,10 @@ public Message toProtoMessage() {
.setBuyerSecurityDepositAsPercentForCrypto(buyerSecurityDepositAsPercentForCrypto)
.setBlockNotifyPort(blockNotifyPort)
.setTacAcceptedV120(tacAcceptedV120)
.setBsqAverageTrimThreshold(bsqAverageTrimThreshold)
.addAllAutoConfirmSettings(autoConfirmSettingsList.stream()
.map(autoConfirmSettings -> ((protobuf.AutoConfirmSettings) autoConfirmSettings.toProtoMessage()))
.collect(Collectors.toList()));
.map(autoConfirmSettings -> ((protobuf.AutoConfirmSettings) autoConfirmSettings.toProtoMessage()))
.collect(Collectors.toList()));

Optional.ofNullable(backupDirectory).ifPresent(builder::setBackupDirectory);
Optional.ofNullable(preferredTradeCurrency).ifPresent(e -> builder.setPreferredTradeCurrency((protobuf.TradeCurrency) e.toProtoMessage()));
Expand Down Expand Up @@ -280,6 +282,7 @@ public static PreferencesPayload fromProto(protobuf.PreferencesPayload proto, Co
proto.getBuyerSecurityDepositAsPercentForCrypto(),
proto.getBlockNotifyPort(),
proto.getTacAcceptedV120(),
proto.getBsqAverageTrimThreshold(),
proto.getAutoConfirmSettingsList().isEmpty() ? new ArrayList<>() :
new ArrayList<>(proto.getAutoConfirmSettingsList().stream()
.map(AutoConfirmSettings::fromProto)
Expand Down
1 change: 1 addition & 0 deletions core/src/main/resources/i18n/displayStrings.properties
Original file line number Diff line number Diff line change
Expand Up @@ -1184,6 +1184,7 @@ setting.preferences.general=General preferences
setting.preferences.explorer=Bitcoin Explorer
setting.preferences.explorer.bsq=Bisq Explorer
setting.preferences.deviation=Max. deviation from market price
setting.preferences.bsqAverageTrimThreshold=Outlier threshold for BSQ rate
setting.preferences.avoidStandbyMode=Avoid standby mode
setting.preferences.autoConfirmXMR=XMR auto-confirm
setting.preferences.autoConfirmEnabled=Enabled
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import bisq.desktop.common.view.ActivatableView;
import bisq.desktop.common.view.FxmlView;
import bisq.desktop.components.TextFieldWithIcon;
import bisq.desktop.util.AxisInlierUtils;

import bisq.core.dao.DaoFacade;
import bisq.core.dao.state.DaoStateListener;
Expand Down Expand Up @@ -112,19 +113,21 @@ public class BsqDashboardView extends ActivatableView<GridPane, Void> implements
private Label marketPriceLabel;

private Coin availableAmount;

private int gridRow = 0;
double percentToTrim = 5;
double howManyStdDevsConstituteOutlier = 10;


///////////////////////////////////////////////////////////////////////////////////////////
// Constructor, lifecycle
///////////////////////////////////////////////////////////////////////////////////////////

@Inject
public BsqDashboardView(DaoFacade daoFacade,
TradeStatisticsManager tradeStatisticsManager,
PriceFeedService priceFeedService,
Preferences preferences,
BsqFormatter bsqFormatter) {
TradeStatisticsManager tradeStatisticsManager,
PriceFeedService priceFeedService,
Preferences preferences,
BsqFormatter bsqFormatter) {
this.daoFacade = daoFacade;
this.tradeStatisticsManager = tradeStatisticsManager;
this.priceFeedService = priceFeedService;
Expand All @@ -134,7 +137,6 @@ public BsqDashboardView(DaoFacade daoFacade,

@Override
public void initialize() {

ADJUSTERS.put(DAY, TemporalAdjusters.ofDateAdjuster(d -> d));

createKPIs();
Expand Down Expand Up @@ -368,15 +370,24 @@ private void updateAveragePriceFields(TextField field90, TextFieldWithIcon field
}

private long updateAveragePriceField(TextField textField, int days, boolean isUSDField) {
double percentToTrim = Math.max(0, Math.min(49, preferences.getBsqAverageTrimThreshold() * 100));
Date pastXDays = getPastDate(days);
List<TradeStatistics3> bsqTradePastXDays = tradeStatisticsManager.getObservableTradeStatisticsSet().stream()
List<TradeStatistics3> bsqAllTradePastXDays = tradeStatisticsManager.getObservableTradeStatisticsSet().stream()
.filter(e -> e.getCurrency().equals("BSQ"))
.filter(e -> e.getDate().after(pastXDays))
.collect(Collectors.toList());
List<TradeStatistics3> usdTradePastXDays = tradeStatisticsManager.getObservableTradeStatisticsSet().stream()
List<TradeStatistics3> bsqTradePastXDays = percentToTrim > 0 ?
removeOutliers(bsqAllTradePastXDays, percentToTrim) :
bsqAllTradePastXDays;

List<TradeStatistics3> usdAllTradePastXDays = tradeStatisticsManager.getObservableTradeStatisticsSet().stream()
.filter(e -> e.getCurrency().equals("USD"))
.filter(e -> e.getDate().after(pastXDays))
.collect(Collectors.toList());
List<TradeStatistics3> usdTradePastXDays = percentToTrim > 0 ?
removeOutliers(usdAllTradePastXDays, percentToTrim) :
usdAllTradePastXDays;

long average = isUSDField ? getUSDAverage(bsqTradePastXDays, usdTradePastXDays) :
getBTCAverage(bsqTradePastXDays);
Price avgPrice = isUSDField ? Price.valueOf("USD", average) :
Expand All @@ -390,11 +401,26 @@ private long updateAveragePriceField(TextField textField, int days, boolean isUS
return average;
}

private long getBTCAverage(List<TradeStatistics3> bsqList) {
private List<TradeStatistics3> removeOutliers(List<TradeStatistics3> list, double percentToTrim) {
List<Double> yValues = list.stream()
.filter(TradeStatistics3::isValid)
.map(e -> (double) e.getPrice())
.collect(Collectors.toList());

Tuple2<Double, Double> tuple = AxisInlierUtils.findInlierRange(yValues, percentToTrim, howManyStdDevsConstituteOutlier);
double lowerBound = tuple.first;
double upperBound = tuple.second;
return list.stream()
.filter(e -> e.getPrice() > lowerBound)
.filter(e -> e.getPrice() < upperBound)
.collect(Collectors.toList());
}

private long getBTCAverage(List<TradeStatistics3> list) {
long accumulatedVolume = 0;
long accumulatedAmount = 0;

for (TradeStatistics3 item : bsqList) {
for (TradeStatistics3 item : list) {
accumulatedVolume += item.getTradeVolume().getValue();
accumulatedAmount += item.getTradeAmount().getValue(); // Amount of BTC traded
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -148,11 +148,11 @@ public class PreferencesView extends ActivatableViewAndModel<GridPane, Preferenc
private ObservableList<CryptoCurrency> cryptoCurrencies;
private ObservableList<CryptoCurrency> allCryptoCurrencies;
private ObservableList<TradeCurrency> tradeCurrencies;
private InputTextField deviationInputTextField;
private ChangeListener<String> deviationListener, ignoreTradersListListener, ignoreDustThresholdListener,
private InputTextField deviationInputTextField, bsqAverageTrimThresholdTextField;
private ChangeListener<String> deviationListener, bsqAverageTrimThresholdListener, ignoreTradersListListener, ignoreDustThresholdListener,
rpcUserListener, rpcPwListener, blockNotifyPortListener,
autoConfTradeLimitListener, autoConfServiceAddressListener;
private ChangeListener<Boolean> deviationFocusedListener;
private ChangeListener<Boolean> deviationFocusedListener, bsqAverageTrimThresholdFocusedListener;
private ChangeListener<Boolean> useCustomFeeCheckboxListener;
private ChangeListener<Number> transactionFeeChangeListener;
private final boolean daoOptionsSet;
Expand Down Expand Up @@ -318,7 +318,6 @@ private void initializeGeneralOptions() {
// deviation
deviationInputTextField = addInputTextField(root, ++gridRow,
Res.get("setting.preferences.deviation"));

deviationListener = (observable, oldValue, newValue) -> {
try {
double value = ParsingUtils.parsePercentStringToDouble(newValue);
Expand All @@ -327,16 +326,16 @@ private void initializeGeneralOptions() {
preferences.setMaxPriceDistanceInPercent(value);
} else {
new Popup().warning(Res.get("setting.preferences.deviationToLarge", maxDeviation * 100)).show();
UserThread.runAfter(() -> deviationInputTextField.setText(FormattingUtils.formatPercentagePrice(preferences.getMaxPriceDistanceInPercent())), 100, TimeUnit.MILLISECONDS);
UserThread.runAfter(() -> deviationInputTextField.setText(FormattingUtils.formatToPercentWithSymbol(preferences.getMaxPriceDistanceInPercent())), 100, TimeUnit.MILLISECONDS);
}
} catch (NumberFormatException t) {
log.error("Exception at parseDouble deviation: " + t.toString());
UserThread.runAfter(() -> deviationInputTextField.setText(FormattingUtils.formatPercentagePrice(preferences.getMaxPriceDistanceInPercent())), 100, TimeUnit.MILLISECONDS);
UserThread.runAfter(() -> deviationInputTextField.setText(FormattingUtils.formatToPercentWithSymbol(preferences.getMaxPriceDistanceInPercent())), 100, TimeUnit.MILLISECONDS);
}
};
deviationFocusedListener = (observable1, oldValue1, newValue1) -> {
if (oldValue1 && !newValue1)
UserThread.runAfter(() -> deviationInputTextField.setText(FormattingUtils.formatPercentagePrice(preferences.getMaxPriceDistanceInPercent())), 100, TimeUnit.MILLISECONDS);
UserThread.runAfter(() -> deviationInputTextField.setText(FormattingUtils.formatToPercentWithSymbol(preferences.getMaxPriceDistanceInPercent())), 100, TimeUnit.MILLISECONDS);
};

// ignoreTraders
Expand Down Expand Up @@ -617,7 +616,7 @@ private void initializeDisplayOptions() {
}

private void initializeDaoOptions() {
daoOptionsTitledGroupBg = addTitledGroupBg(root, ++gridRow, 3, Res.get("setting.preferences.daoOptions"), Layout.GROUP_DISTANCE);
daoOptionsTitledGroupBg = addTitledGroupBg(root, ++gridRow, 4, Res.get("setting.preferences.daoOptions"), Layout.GROUP_DISTANCE);
resyncDaoFromResourcesButton = addButton(root, gridRow, Res.get("setting.preferences.dao.resyncFromResources.label"), Layout.TWICE_FIRST_ROW_AND_GROUP_DISTANCE);
resyncDaoFromResourcesButton.setMaxWidth(Double.MAX_VALUE);
GridPane.setHgrow(resyncDaoFromResourcesButton, Priority.ALWAYS);
Expand All @@ -626,6 +625,36 @@ private void initializeDaoOptions() {
resyncDaoFromGenesisButton.setMaxWidth(Double.MAX_VALUE);
GridPane.setHgrow(resyncDaoFromGenesisButton, Priority.ALWAYS);

bsqAverageTrimThresholdTextField = addInputTextField(root, ++gridRow,
Res.get("setting.preferences.bsqAverageTrimThreshold"));
bsqAverageTrimThresholdTextField.setText(FormattingUtils.formatToPercentWithSymbol(preferences.getBsqAverageTrimThreshold()));

bsqAverageTrimThresholdListener = (observable, oldValue, newValue) -> {
try {
double value = ParsingUtils.parsePercentStringToDouble(newValue);
double maxValue = 0.49;
checkArgument(value >= 0, "Input must be positive");
if (value <= maxValue) {
preferences.setBsqAverageTrimThreshold(value);
} else {
new Popup().warning(Res.get("setting.preferences.deviationToLarge",
maxValue * 100)).show();
UserThread.runAfter(() -> bsqAverageTrimThresholdTextField.setText(FormattingUtils.formatToPercentWithSymbol(
preferences.getBsqAverageTrimThreshold())), 100, TimeUnit.MILLISECONDS);
}
} catch (NumberFormatException t) {
log.error("Exception: " + t.toString());
UserThread.runAfter(() -> bsqAverageTrimThresholdTextField.setText(FormattingUtils.formatToPercentWithSymbol(
preferences.getBsqAverageTrimThreshold())), 100, TimeUnit.MILLISECONDS);
}
};
bsqAverageTrimThresholdFocusedListener = (observable1, oldValue1, newValue1) -> {
if (oldValue1 && !newValue1)
UserThread.runAfter(() -> bsqAverageTrimThresholdTextField.setText(FormattingUtils.formatToPercentWithSymbol(
preferences.getBsqAverageTrimThreshold())), 100, TimeUnit.MILLISECONDS);
};


isDaoFullNodeToggleButton = addSlideToggleButton(root, ++gridRow, Res.get("setting.preferences.dao.isDaoFullNode"));
rpcUserTextField = addInputTextField(root, ++gridRow, Res.get("setting.preferences.dao.rpcUser"));
rpcUserTextField.setVisible(false);
Expand Down Expand Up @@ -861,7 +890,7 @@ public BlockChainExplorer fromString(String string) {
});
bsqBlockChainExplorerComboBox.setOnAction(e -> preferences.setBsqBlockChainExplorer(bsqBlockChainExplorerComboBox.getSelectionModel().getSelectedItem()));

deviationInputTextField.setText(FormattingUtils.formatPercentagePrice(preferences.getMaxPriceDistanceInPercent()));
deviationInputTextField.setText(FormattingUtils.formatToPercentWithSymbol(preferences.getMaxPriceDistanceInPercent()));
deviationInputTextField.textProperty().addListener(deviationListener);
deviationInputTextField.focusedProperty().addListener(deviationFocusedListener);

Expand Down Expand Up @@ -950,6 +979,10 @@ private void activateDisplayPreferences() {
private void activateDaoPreferences() {
boolean daoFullNode = preferences.isDaoFullNode();
isDaoFullNodeToggleButton.setSelected(daoFullNode);

bsqAverageTrimThresholdTextField.textProperty().addListener(bsqAverageTrimThresholdListener);
bsqAverageTrimThresholdTextField.focusedProperty().addListener(bsqAverageTrimThresholdFocusedListener);

String rpcUser = preferences.getRpcUser();
String rpcPw = preferences.getRpcPw();
int blockNotifyPort = preferences.getBlockNotifyPort();
Expand Down Expand Up @@ -1091,6 +1124,8 @@ private void deactivateDisplayPreferences() {
private void deactivateDaoPreferences() {
resyncDaoFromResourcesButton.setOnAction(null);
resyncDaoFromGenesisButton.setOnAction(null);
bsqAverageTrimThresholdTextField.textProperty().removeListener(bsqAverageTrimThresholdListener);
bsqAverageTrimThresholdTextField.focusedProperty().removeListener(bsqAverageTrimThresholdFocusedListener);
isDaoFullNodeToggleButton.setOnAction(null);
rpcUserTextField.textProperty().removeListener(rpcUserListener);
rpcPwTextField.textProperty().removeListener(rpcPwListener);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ private static List<Double> extractYValues(ObservableList<? extends XYChart.Data
/* Finds the minimum and maximum inlier values. The returned values may be NaN.
* See `computeInlierThreshold` for the definition of inlier.
*/
private static Tuple2<Double, Double> findInlierRange(
public static Tuple2<Double, Double> findInlierRange(
List<Double> yValues,
double percentToTrim,
double howManyStdDevsConstituteOutlier
Expand Down
1 change: 1 addition & 0 deletions proto/src/main/proto/pb.proto
Original file line number Diff line number Diff line change
Expand Up @@ -1581,6 +1581,7 @@ message PreferencesPayload {
int32 css_theme = 54;
bool tac_accepted_v120 = 55;
repeated AutoConfirmSettings auto_confirm_settings = 56;
double bsq_average_trim_threshold = 57;
}

message AutoConfirmSettings {
Expand Down

0 comments on commit 89620a0

Please sign in to comment.