Tidy up and document TakeBestPricedOfferToSellBsq bot

This commit is contained in:
ghubstan 2022-07-01 16:18:23 -03:00
parent 8456fd9fb6
commit 4133a4c047
No known key found for this signature in database
GPG Key ID: E35592D6800A861E
2 changed files with 66 additions and 17 deletions

View File

@ -32,7 +32,52 @@ import static java.math.RoundingMode.HALF_UP;
import static protobuf.OfferDirection.BUY;
/**
* Bot for buying BSQ with BTC at an attractive (lower) price. The bot receives BSQ for BTC.
* This bot's general use case is to buy BSQ with BTC at a low BTC price. It periodically checks the
* Sell BSQ (Buy BTC) market, and takes a configured maximum number of offers to buy BSQ from you according to criteria
* you define in the bot's configuration file: <b>TakeBestPricedOfferToSellBsq.properties</b> (located in project's
* src/main/resources directory). You will need to replace the default values in the configuration file for your
* use cases.
* <p><br/>
* 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.
* <p>
* Here is one possible use case:
* <pre>
* 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
* </pre>
* <b>Usage</b>
* <p><br/>
* You must encrypt your wallet password before running this bot. If it is not already encrypted, you can use the CLI:
* <pre>
* $ ./bisq-cli --password=xyz --port=9998 setwalletpassword --wallet-password="be careful"
* </pre>
* 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 <String>`. The bot will prompt you for your wallet-password in the console.
* <p><br/>
* You can pass the '--dryrun=true' option to the program to see what offers your bot <i>would take</i> with a given
* configuration. This will help you avoid taking offers by mistake.
* <pre>
* TakeBestPricedOfferToBuyBsq --password=api-password --port=api-port [--dryrun=true|false]
* </pre>
* <p>
* The '--simulate-regtest-payment=true' option is ignored by this bot. Taking a swap triggers execution of the swap.
*
* @see <a href="https://github.com/bisq-network/bisq-api-reference/blob/make-proto-downloader-runnable-from-any-dir/java-examples/src/main/java/bisq/bots/Config.java">bisq.bots.Config.java</a>
*/
@Slf4j
@Getter
@ -99,7 +144,8 @@ public class TakeBestPricedOfferToSellBsq extends AbstractBot {
continue;
}
// Get all available and takeable offers, sorted by price ascending.
// 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();
@ -125,7 +171,6 @@ public class TakeBestPricedOfferToSellBsq extends AbstractBot {
takeCriteria.printOfferAgainstCriteria(cheapestOffer);
});
printDryRunProgress();
runCountdown(log, pollingInterval);
pingDaemon(startTime);
}
@ -134,17 +179,21 @@ public class TakeBestPricedOfferToSellBsq extends AbstractBot {
private void takeOffer(TakeCriteria takeCriteria, OfferInfo offer) {
log.info("Will attempt to take offer '{}'.", offer.getId());
takeCriteria.printOfferAgainstCriteria(offer);
// An encrypted wallet must be unlocked before calling takeoffer and gettrade(s).
// Unlock the wallet for 5 minutes. If the wallet is already unlocked, this request
// will override the timeout of the previous unlock request.
try {
unlockWallet(walletPassword, 300);
} catch (NonFatalException nonFatalException) {
handleNonFatalException(nonFatalException, pollingInterval);
}
if (isDryRun) {
addToOffersTaken(offer);
numOffersTaken++;
maybeShutdownAfterSuccessfulSwap(numOffersTaken, maxTakeOffers);
} else {
// An encrypted wallet must be unlocked before calling takeoffer and gettrade.
// Unlock the wallet for 10 minutes. If the wallet is already unlocked,
// this command will override the timeout of the previous unlock command.
try {
unlockWallet(walletPassword, 600);
printBTCBalances("BTC Balances Before Swap Execution");
printBSQBalances("BSQ Balances Before Swap Execution");
@ -155,13 +204,13 @@ public class TakeBestPricedOfferToSellBsq extends AbstractBot {
printBSQBalances("BSQ Balances After Swap Execution");
numOffersTaken++;
maybeShutdownAfterSuccessfulSwap(numOffersTaken, maxTakeOffers);
} catch (NonFatalException nonFatalException) {
handleNonFatalException(nonFatalException, pollingInterval);
} catch (StatusRuntimeException fatalException) {
handleFatalBsqSwapException(fatalException);
}
}
maybeShutdownAfterSuccessfulSwap(numOffersTaken, maxTakeOffers);
}
private void printBotConfiguration() {
@ -169,6 +218,7 @@ public class TakeBestPricedOfferToSellBsq extends AbstractBot {
configsByLabel.put("Bot OS:", getOSName() + " " + getOSVersion());
var network = getNetwork();
configsByLabel.put("BTC Network:", network);
configsByLabel.put("Dry Run?", isDryRun ? "YES" : "NO");
var isMainnet = network.equalsIgnoreCase("mainnet");
var mainnet30DayAvgBsqPrice = isMainnet ? get30DayAvgBsqPriceInBtc() : null;
configsByLabel.put("My Payment Account:", "");
@ -226,7 +276,6 @@ public class TakeBestPricedOfferToSellBsq extends AbstractBot {
this.targetPrice = calcTargetBsqPrice(maxMarketPriceMargin, avgBsqPrice);
}
/**
* Returns the lowest priced offer passing the filters, or Optional.empty() if not found.
* Max tx fee rate filtering should have passed prior to calling this method.
@ -277,7 +326,7 @@ public class TakeBestPricedOfferToSellBsq extends AbstractBot {
var filterResultsByLabel = new LinkedHashMap<String, Object>();
filterResultsByLabel.put("30-day Avg BSQ trade price:", avgBsqPrice + " BTC");
filterResultsByLabel.put("Target Price (Min):", targetPrice + " BTC");
filterResultsByLabel.put("Target Price (Max):", targetPrice + " BTC");
filterResultsByLabel.put("Offer Price:", offer.getPrice() + " BTC");
filterResultsByLabel.put("Offer maker used same payment method?",
usesSamePaymentMethod.test(offer, getPaymentAccount()));
@ -285,7 +334,7 @@ public class TakeBestPricedOfferToSellBsq extends AbstractBot {
iHavePreferredTradingPeers.get()
? isMakerPreferredTradingPeer.test(offer) ? "YES" : "NO"
: "N/A");
var fixedPriceLabel = format("Is offer's fixed-price (%s) <= bot's minimum price (%s)?",
var fixedPriceLabel = format("Is offer's fixed-price (%s) <= bot's maximum price of (%s)?",
offer.getPrice() + " BTC",
targetPrice + " BTC");
filterResultsByLabel.put(fixedPriceLabel, isFixedPriceLEMaxMarketPriceMargin.test(offer, avgBsqPrice));

View File

@ -1,12 +1,12 @@
# Maximum # of offers to take during one bot session. When reached, bot will shut down (but not the API daemon).
maxTakeOffers=50
maxTakeOffers=5
#
# Maximum distance from 30-day average BSQ trade price.
# Note: all BSQ Swap offers have a fixed-price, but the bot uses a margin (%) of the 30-day price for comparison.
maxMarketPriceMargin=0.00
maxMarketPriceMargin=-1.0
#
# Hard coded 30-day average BSQ trade price, used for development over regtest.
regtest30DayAvgBsqPrice=0.00037
regtest30DayAvgBsqPrice=0.00060
#
# Taker bot's min BTC amount to buy. The candidate BUY BTC offer's amount must be >= minAmount BTC.
minAmount=0.01
@ -24,4 +24,4 @@ maxTxFeeRate=25
preferredTradingPeers=localhost:8888
#
# Offer polling frequency must be >= 1s (1000ms) between each getoffers request.
pollingInterval=30000
pollingInterval=60000