/*
* This file is part of Bisq.
*
* Bisq 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.
*
* Bisq 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 Bisq. If not, see
* After the maximum number of BSQ swap offers have been taken (good to start with 1), the bot will shut down. The API
* daemon will not be shut down because swaps do not require additional payment related steps taken outside Bisq, or
* in the GUI.
*
* Here is one possible use case: *
* Take 5 BSQ swap offers to sell BSQ for BTC, priced no higher than -1.00% below the 30-day average BSQ price if: * * the offer's BTC amount is between 0.10 and 0.25 BTC * the offer maker is one of two preferred trading peers * the current transaction mining fee rate is less than or equal 25 sats / byte * * The bot configurations for these rules are set in TakeBestPricedOfferToSellBsq.properties as follows: * * maxTakeOffers=5 * minMarketPriceMargin=-1.00 * minAmount=0.10 * maxAmount=0.25 * preferredTradingPeers=preferred-address-1.onion:9999,preferred-address-2.onion:9999 * maxTxFeeRate=25 ** Usage *
* You must encrypt your wallet password before running this bot. If it is not already encrypted, you can use the CLI:
*
* $ ./bisq-cli --password=xyz --port=9998 setwalletpassword --wallet-password="be careful" ** There are some {@link bisq.bots.Config program options} common to all the Java bot examples, passed on the command * line. The only one you must provide (no default value) is your API daemon's password option: * `--password
* You can pass the '--dryrun=true' option to the program to see what offers your bot would take with a given
* configuration. This will help you avoid taking offers by mistake.
*
* TakeBestPricedOfferToBuyBsq --password=api-password --port=api-port [--dryrun=true|false] **
* The '--simulate-regtest-payment=true' option is ignored by this bot. Taking a swap triggers execution of the swap.
*
* @see bisq.bots.Config.java
*/
@Slf4j
@Getter
public class TakeBestPricedOfferToSellBsq extends AbstractBot {
// Taker bot's default BSQ payment account trading currency code.
private static final String CURRENCY_CODE = "BSQ";
// Config file: resources/TakeBestPricedOfferToSellBsq.properties.
private final Properties configFile;
// Taker bot's default BSQ Swap payment account.
private final PaymentAccount paymentAccount;
// Taker bot's max market price margin. A takeable BSQ Swap offer's fixed-price must be <= maxMarketPriceMargin (%).
// Note: all BSQ Swap offers have a fixed-price, but the bot uses a margin (%) of the 30-day price for comparison.
private final BigDecimal maxMarketPriceMargin;
// Hard coded 30-day average BSQ trade price, used for development over regtest (ignored when running on mainnet).
private final BigDecimal regtest30DayAvgBsqPrice;
// Taker bot's minimum BTC amount to trade. A takeable offer's amount must be >= minAmount BTC.
private final BigDecimal minAmount;
// Taker bot's maximum BTC amount to trade. A takeable offer's amount must be <= maxAmount BTC.
private final BigDecimal maxAmount;
// Taker bot's max acceptable transaction fee rate.
private final long maxTxFeeRate;
// Maximum # of offers to take during one bot session (shut down bot after taking N swap offers).
private final int maxTakeOffers;
// Offer polling frequency must be > 1000 ms between each getoffers request.
private final long pollingInterval;
// The # of offers taken during the bot session (since startup).
private int numOffersTaken = 0;
public TakeBestPricedOfferToSellBsq(String[] args) {
super(args);
pingDaemon(new Date().getTime()); // Shut down now if API daemon is not available.
this.configFile = loadConfigFile();
this.paymentAccount = getBsqSwapPaymentAccount();
this.maxMarketPriceMargin = new BigDecimal(configFile.getProperty("maxMarketPriceMargin"))
.setScale(2, HALF_UP);
this.regtest30DayAvgBsqPrice = new BigDecimal(configFile.getProperty("regtest30DayAvgBsqPrice"))
.setScale(8, HALF_UP);
this.minAmount = new BigDecimal(configFile.getProperty("minAmount"));
this.maxAmount = new BigDecimal(configFile.getProperty("maxAmount"));
this.maxTxFeeRate = Long.parseLong(configFile.getProperty("maxTxFeeRate"));
this.maxTakeOffers = Integer.parseInt(configFile.getProperty("maxTakeOffers"));
loadPreferredOnionAddresses.accept(configFile, preferredTradingPeers);
this.pollingInterval = Long.parseLong(configFile.getProperty("pollingInterval"));
}
/**
* Checks for the most attractive BSQ Swap offer to take every {@link #pollingInterval} ms.
* Will only terminate when manually shut down, or a fatal gRPC StatusRuntimeException is thrown.
*/
@Override
public void run() {
var startTime = new Date().getTime();
validateWalletPassword(walletPassword);
validatePollingInterval(pollingInterval);
printBotConfiguration();
while (!isShutdown) {
if (!isBisqNetworkTxFeeRateLowEnough.test(maxTxFeeRate)) {
runCountdown(log, pollingInterval);
continue;
}
// Get all available buy BTC with BSQ offers, sorted by price ascending.
// The list contains only fixed-priced offers.
var offers = getOffers(BUY.name(), CURRENCY_CODE).stream()
.filter(o -> !isAlreadyTaken.test(o))
.toList();
if (offers.isEmpty()) {
log.info("No takeable offers found.");
runCountdown(log, pollingInterval);
continue;
}
// Define criteria for taking an offer, based on conf file.
TakeBestPricedOfferToSellBsq.TakeCriteria takeCriteria = new TakeBestPricedOfferToSellBsq.TakeCriteria();
takeCriteria.printCriteriaSummary();
takeCriteria.printOffersAgainstCriteria(offers);
// Find takeable offer based on criteria.
Optional