mirror of
https://github.com/haveno-dex/haveno.git
synced 2025-08-02 19:56:23 -04:00
trigger price handled as string, same as price
This commit is contained in:
parent
9d4ec0a532
commit
c2f5adac9b
23 changed files with 46 additions and 54 deletions
|
@ -432,7 +432,7 @@ public class CoreApi {
|
|||
long amountAsLong,
|
||||
long minAmountAsLong,
|
||||
double buyerSecurityDeposit,
|
||||
long triggerPrice,
|
||||
String triggerPriceAsString,
|
||||
String paymentAccountId,
|
||||
Consumer<Offer> resultHandler,
|
||||
ErrorMessageHandler errorMessageHandler) {
|
||||
|
@ -444,7 +444,7 @@ public class CoreApi {
|
|||
amountAsLong,
|
||||
minAmountAsLong,
|
||||
buyerSecurityDeposit,
|
||||
triggerPrice,
|
||||
triggerPriceAsString,
|
||||
paymentAccountId,
|
||||
resultHandler,
|
||||
errorMessageHandler);
|
||||
|
|
|
@ -30,7 +30,7 @@ import bisq.core.offer.OpenOffer;
|
|||
import bisq.core.offer.OpenOfferManager;
|
||||
import bisq.core.payment.PaymentAccount;
|
||||
import bisq.core.user.User;
|
||||
|
||||
import bisq.core.util.PriceUtil;
|
||||
import bisq.common.crypto.KeyRing;
|
||||
import bisq.common.handlers.ErrorMessageHandler;
|
||||
import org.bitcoinj.core.Coin;
|
||||
|
@ -176,7 +176,7 @@ class CoreOffersService {
|
|||
for (Offer offer : offers) {
|
||||
for (String keyImage : offer.getOfferPayload().getReserveTxKeyImages()) {
|
||||
if (!allKeyImages.add(keyImage)) {
|
||||
log.warn("Key image {} belongs to another offer, removing offer {}", keyImage, offer.getId());
|
||||
log.warn("Key image {} belongs to another offer, removing offer {}", keyImage, offer.getId()); // TODO (woodser): this is list, not set, so not checking for duplicates
|
||||
unreservedOffers.add(offer);
|
||||
}
|
||||
}
|
||||
|
@ -220,7 +220,7 @@ class CoreOffersService {
|
|||
long amountAsLong,
|
||||
long minAmountAsLong,
|
||||
double buyerSecurityDeposit,
|
||||
long triggerPrice,
|
||||
String triggerPriceAsString,
|
||||
String paymentAccountId,
|
||||
Consumer<Offer> resultHandler,
|
||||
ErrorMessageHandler errorMessageHandler) {
|
||||
|
@ -257,7 +257,7 @@ class CoreOffersService {
|
|||
//noinspection ConstantConditions
|
||||
placeOffer(offer,
|
||||
buyerSecurityDeposit,
|
||||
triggerPrice,
|
||||
triggerPriceAsString,
|
||||
useSavingsWallet,
|
||||
transaction -> resultHandler.accept(offer),
|
||||
errorMessageHandler);
|
||||
|
@ -309,14 +309,15 @@ class CoreOffersService {
|
|||
|
||||
private void placeOffer(Offer offer,
|
||||
double buyerSecurityDeposit,
|
||||
long triggerPrice,
|
||||
String triggerPriceAsString,
|
||||
boolean useSavingsWallet,
|
||||
Consumer<Transaction> resultHandler,
|
||||
ErrorMessageHandler errorMessageHandler) {
|
||||
long triggerPriceAsLong = PriceUtil.getMarketPriceAsLong(triggerPriceAsString, offer.getCurrencyCode());
|
||||
openOfferManager.placeOffer(offer,
|
||||
buyerSecurityDeposit,
|
||||
useSavingsWallet,
|
||||
triggerPrice,
|
||||
triggerPriceAsLong,
|
||||
resultHandler::accept,
|
||||
errorMessageHandler);
|
||||
}
|
||||
|
|
|
@ -75,6 +75,8 @@ public class ValidateOffer extends Task<PlaceOfferModel> {
|
|||
|
||||
checkArgument(offer.getDate().getTime() > 0,
|
||||
"Date must not be 0. date=" + offer.getDate().toString());
|
||||
|
||||
System.out.println("OFFER PRICE: " + offer.getPrice());
|
||||
|
||||
checkNotNull(offer.getCurrencyCode(), "Currency is null");
|
||||
checkNotNull(offer.getDirection(), "Direction is null");
|
||||
|
|
210
core/src/main/java/bisq/core/util/PriceUtil.java
Normal file
210
core/src/main/java/bisq/core/util/PriceUtil.java
Normal file
|
@ -0,0 +1,210 @@
|
|||
/*
|
||||
* This file is part of Haveno.
|
||||
*
|
||||
* Haveno 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.
|
||||
*
|
||||
* Haveno 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 Haveno. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package bisq.core.util;
|
||||
|
||||
import bisq.core.util.validation.AltcoinValidator;
|
||||
import bisq.core.util.validation.FiatPriceValidator;
|
||||
import bisq.core.util.validation.MonetaryValidator;
|
||||
|
||||
import bisq.core.locale.CurrencyUtil;
|
||||
import bisq.core.locale.Res;
|
||||
import bisq.core.monetary.Altcoin;
|
||||
import bisq.core.monetary.Price;
|
||||
import bisq.core.offer.Offer;
|
||||
import bisq.core.offer.OfferPayload;
|
||||
import bisq.core.provider.price.MarketPrice;
|
||||
import bisq.core.provider.price.PriceFeedService;
|
||||
import bisq.core.trade.statistics.TradeStatisticsManager;
|
||||
import bisq.core.user.Preferences;
|
||||
import bisq.core.util.validation.InputValidator;
|
||||
|
||||
import bisq.common.util.MathUtils;
|
||||
|
||||
import org.bitcoinj.utils.Fiat;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import java.util.Optional;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
@Slf4j
|
||||
@Singleton
|
||||
public class PriceUtil {
|
||||
private final PriceFeedService priceFeedService;
|
||||
|
||||
@Inject
|
||||
public PriceUtil(PriceFeedService priceFeedService,
|
||||
TradeStatisticsManager tradeStatisticsManager,
|
||||
Preferences preferences) {
|
||||
this.priceFeedService = priceFeedService;
|
||||
}
|
||||
|
||||
public static MonetaryValidator getPriceValidator(boolean isFiatCurrency) {
|
||||
return isFiatCurrency ?
|
||||
new FiatPriceValidator() :
|
||||
new AltcoinValidator();
|
||||
}
|
||||
|
||||
public static InputValidator.ValidationResult isTriggerPriceValid(String triggerPriceAsString,
|
||||
Price price,
|
||||
boolean isSellOffer,
|
||||
boolean isFiatCurrency) {
|
||||
if (triggerPriceAsString == null || triggerPriceAsString.isEmpty()) {
|
||||
return new InputValidator.ValidationResult(true);
|
||||
}
|
||||
|
||||
InputValidator.ValidationResult result = getPriceValidator(isFiatCurrency).validate(triggerPriceAsString);
|
||||
if (!result.isValid) {
|
||||
return result;
|
||||
}
|
||||
|
||||
long triggerPriceAsLong = PriceUtil.getMarketPriceAsLong(triggerPriceAsString, price.getCurrencyCode());
|
||||
long priceAsLong = price.getValue();
|
||||
String priceAsString = FormattingUtils.formatPrice(price);
|
||||
if ((isSellOffer && isFiatCurrency) || (!isSellOffer && !isFiatCurrency)) {
|
||||
if (triggerPriceAsLong >= priceAsLong) {
|
||||
return new InputValidator.ValidationResult(false,
|
||||
Res.get("createOffer.triggerPrice.invalid.tooHigh", priceAsString));
|
||||
} else {
|
||||
return new InputValidator.ValidationResult(true);
|
||||
}
|
||||
} else {
|
||||
if (triggerPriceAsLong <= priceAsLong) {
|
||||
return new InputValidator.ValidationResult(false,
|
||||
Res.get("createOffer.triggerPrice.invalid.tooLow", priceAsString));
|
||||
} else {
|
||||
return new InputValidator.ValidationResult(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static Price marketPriceToPrice(MarketPrice marketPrice) {
|
||||
String currencyCode = marketPrice.getCurrencyCode();
|
||||
double priceAsDouble = marketPrice.getPrice();
|
||||
int precision = CurrencyUtil.isCryptoCurrency(currencyCode) ?
|
||||
Altcoin.SMALLEST_UNIT_EXPONENT :
|
||||
Fiat.SMALLEST_UNIT_EXPONENT;
|
||||
double scaled = MathUtils.scaleUpByPowerOf10(priceAsDouble, precision);
|
||||
long roundedToLong = MathUtils.roundDoubleToLong(scaled);
|
||||
return Price.valueOf(currencyCode, roundedToLong);
|
||||
}
|
||||
|
||||
public boolean hasMarketPrice(Offer offer) {
|
||||
String currencyCode = offer.getCurrencyCode();
|
||||
checkNotNull(priceFeedService, "priceFeed must not be null");
|
||||
MarketPrice marketPrice = priceFeedService.getMarketPrice(currencyCode);
|
||||
Price price = offer.getPrice();
|
||||
return price != null && marketPrice != null && marketPrice.isRecentExternalPriceAvailable();
|
||||
}
|
||||
|
||||
public Optional<Double> getMarketBasedPrice(Offer offer,
|
||||
OfferPayload.Direction direction) {
|
||||
if (offer.isUseMarketBasedPrice()) {
|
||||
return Optional.of(offer.getMarketPriceMargin());
|
||||
}
|
||||
|
||||
if (!hasMarketPrice(offer)) {
|
||||
log.trace("We don't have a market price. " +
|
||||
"That case could only happen if you don't have a price feed.");
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
String currencyCode = offer.getCurrencyCode();
|
||||
checkNotNull(priceFeedService, "priceFeed must not be null");
|
||||
MarketPrice marketPrice = priceFeedService.getMarketPrice(currencyCode);
|
||||
double marketPriceAsDouble = checkNotNull(marketPrice).getPrice();
|
||||
return calculatePercentage(offer, marketPriceAsDouble, direction);
|
||||
}
|
||||
|
||||
public Optional<Double> calculatePercentage(Offer offer,
|
||||
double marketPrice,
|
||||
OfferPayload.Direction direction) {
|
||||
// If the offer did not use % price we calculate % from current market price
|
||||
String currencyCode = offer.getCurrencyCode();
|
||||
Price price = offer.getPrice();
|
||||
int precision = CurrencyUtil.isCryptoCurrency(currencyCode) ?
|
||||
Altcoin.SMALLEST_UNIT_EXPONENT :
|
||||
Fiat.SMALLEST_UNIT_EXPONENT;
|
||||
long priceAsLong = checkNotNull(price).getValue();
|
||||
double scaled = MathUtils.scaleDownByPowerOf10(priceAsLong, precision);
|
||||
double value;
|
||||
if (direction == OfferPayload.Direction.SELL) {
|
||||
if (CurrencyUtil.isFiatCurrency(currencyCode)) {
|
||||
if (marketPrice == 0) {
|
||||
return Optional.empty();
|
||||
}
|
||||
value = 1 - scaled / marketPrice;
|
||||
} else {
|
||||
if (marketPrice == 1) {
|
||||
return Optional.empty();
|
||||
}
|
||||
value = scaled / marketPrice - 1;
|
||||
}
|
||||
} else {
|
||||
if (CurrencyUtil.isFiatCurrency(currencyCode)) {
|
||||
if (marketPrice == 1) {
|
||||
return Optional.empty();
|
||||
}
|
||||
value = scaled / marketPrice - 1;
|
||||
} else {
|
||||
if (marketPrice == 0) {
|
||||
return Optional.empty();
|
||||
}
|
||||
value = 1 - scaled / marketPrice;
|
||||
}
|
||||
}
|
||||
return Optional.of(value);
|
||||
}
|
||||
|
||||
public static long getMarketPriceAsLong(String inputValue, String currencyCode) {
|
||||
if (inputValue == null || inputValue.isEmpty() || currencyCode == null) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
try {
|
||||
int precision = getMarketPricePrecision(currencyCode);
|
||||
String stringValue = reformatMarketPrice(inputValue, currencyCode);
|
||||
return ParsingUtils.parsePriceStringToLong(currencyCode, stringValue, precision);
|
||||
} catch (Throwable t) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
public static String reformatMarketPrice(String inputValue, String currencyCode) {
|
||||
if (inputValue == null || inputValue.isEmpty() || currencyCode == null) {
|
||||
return "";
|
||||
}
|
||||
|
||||
double priceAsDouble = ParsingUtils.parseNumberStringToDouble(inputValue);
|
||||
int precision = getMarketPricePrecision(currencyCode);
|
||||
return FormattingUtils.formatRoundedDoubleWithPrecision(priceAsDouble, precision);
|
||||
}
|
||||
|
||||
public static String formatMarketPrice(long price, String currencyCode) {
|
||||
int marketPricePrecision = getMarketPricePrecision(currencyCode);
|
||||
double scaled = MathUtils.scaleDownByPowerOf10(price, marketPricePrecision);
|
||||
return FormattingUtils.formatMarketPrice(scaled, marketPricePrecision);
|
||||
}
|
||||
|
||||
public static int getMarketPricePrecision(String currencyCode) {
|
||||
return CurrencyUtil.isCryptoCurrency(currencyCode) ?
|
||||
Altcoin.SMALLEST_UNIT_EXPONENT : Fiat.SMALLEST_UNIT_EXPONENT;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* This file is part of Haveno.
|
||||
*
|
||||
* Haveno 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.
|
||||
*
|
||||
* Haveno 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 Haveno. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package bisq.core.util.validation;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
public class AltcoinValidator extends MonetaryValidator {
|
||||
@Override
|
||||
protected double getMinValue() {
|
||||
return 0.00000001;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected double getMaxValue() {
|
||||
// hard to say what the max value should be with altcoins
|
||||
return 100_000_000;
|
||||
}
|
||||
|
||||
@Inject
|
||||
public AltcoinValidator() {
|
||||
}
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
* This file is part of Haveno.
|
||||
*
|
||||
* Haveno 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.
|
||||
*
|
||||
* Haveno 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 Haveno. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package bisq.core.util.validation;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
public class FiatPriceValidator extends MonetaryValidator {
|
||||
@Override
|
||||
protected double getMinValue() {
|
||||
return 0.0001;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected double getMaxValue() {
|
||||
// Hard to say what the max value should be (zimbabwe dollar....)?
|
||||
// Lets set it to Double.MAX_VALUE until we find some reasonable number
|
||||
return Double.MAX_VALUE;
|
||||
}
|
||||
|
||||
@Inject
|
||||
public FiatPriceValidator() {
|
||||
}
|
||||
}
|
|
@ -0,0 +1,68 @@
|
|||
/*
|
||||
* This file is part of Haveno.
|
||||
*
|
||||
* Haveno 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.
|
||||
*
|
||||
* Haveno 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 Haveno. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package bisq.core.util.validation;
|
||||
|
||||
import bisq.core.locale.Res;
|
||||
import javax.inject.Inject;
|
||||
|
||||
public abstract class MonetaryValidator extends NumberValidator {
|
||||
|
||||
protected abstract double getMinValue();
|
||||
|
||||
@SuppressWarnings("SameReturnValue")
|
||||
protected abstract double getMaxValue();
|
||||
|
||||
@Inject
|
||||
public MonetaryValidator() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public ValidationResult validate(String input) {
|
||||
ValidationResult result = validateIfNotEmpty(input);
|
||||
if (result.isValid) {
|
||||
input = cleanInput(input);
|
||||
result = validateIfNumber(input);
|
||||
}
|
||||
|
||||
if (result.isValid) {
|
||||
result = result.andValidation(input,
|
||||
this::validateIfNotZero,
|
||||
this::validateIfNotNegative,
|
||||
this::validateIfNotExceedsMinValue,
|
||||
this::validateIfNotExceedsMaxValue);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
protected ValidationResult validateIfNotExceedsMinValue(String input) {
|
||||
double d = Double.parseDouble(input);
|
||||
if (d < getMinValue())
|
||||
return new ValidationResult(false, Res.get("validation.fiat.toSmall"));
|
||||
else
|
||||
return new ValidationResult(true);
|
||||
}
|
||||
|
||||
protected ValidationResult validateIfNotExceedsMaxValue(String input) {
|
||||
double d = Double.parseDouble(input);
|
||||
if (d > getMaxValue())
|
||||
return new ValidationResult(false, Res.get("validation.fiat.toLarge"));
|
||||
else
|
||||
return new ValidationResult(true);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,58 @@
|
|||
/*
|
||||
* This file is part of Haveno.
|
||||
*
|
||||
* Haveno 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.
|
||||
*
|
||||
* Haveno 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 Haveno. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package bisq.core.util.validation;
|
||||
|
||||
import bisq.core.locale.Res;
|
||||
import bisq.core.util.ParsingUtils;
|
||||
|
||||
/**
|
||||
* NumberValidator for validating basic number values.
|
||||
* Localisation not supported at the moment
|
||||
* The decimal mark can be either "." or ",". Thousand separators are not supported yet,
|
||||
* but might be added alter with Local support.
|
||||
*/
|
||||
public abstract class NumberValidator extends InputValidator {
|
||||
|
||||
protected String cleanInput(String input) {
|
||||
return ParsingUtils.convertCharsForNumber(input);
|
||||
}
|
||||
|
||||
protected ValidationResult validateIfNumber(String input) {
|
||||
try {
|
||||
//noinspection ResultOfMethodCallIgnored
|
||||
Double.parseDouble(input);
|
||||
return new ValidationResult(true);
|
||||
} catch (Exception e) {
|
||||
return new ValidationResult(false, Res.get("validation.NaN"));
|
||||
}
|
||||
}
|
||||
|
||||
protected ValidationResult validateIfNotZero(String input) {
|
||||
if (Double.parseDouble(input) == 0)
|
||||
return new ValidationResult(false, Res.get("validation.zero"));
|
||||
else
|
||||
return new ValidationResult(true);
|
||||
}
|
||||
|
||||
protected ValidationResult validateIfNotNegative(String input) {
|
||||
if (Double.parseDouble(input) < 0)
|
||||
return new ValidationResult(false, Res.get("validation.negative"));
|
||||
else
|
||||
return new ValidationResult(true);
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue