trigger price handled as string, same as price

This commit is contained in:
woodser 2022-04-26 15:19:20 -04:00
parent 9d4ec0a532
commit c2f5adac9b
23 changed files with 46 additions and 54 deletions

View file

@ -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);

View file

@ -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);
}

View file

@ -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");

View 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;
}
}

View file

@ -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() {
}
}

View file

@ -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() {
}
}

View file

@ -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);
}
}

View file

@ -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);
}
}