Skip to content

Commit

Permalink
Remove outliers when calculating BSQ rate
Browse files Browse the repository at this point in the history
Add percentage for upper and lower threshold for outlier detection.
  • Loading branch information
chimp1984 committed Oct 25, 2020
1 parent 55c663a commit 8bfe9b8
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 @@ -1576,6 +1576,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 8bfe9b8

Please sign in to comment.