cleaned up partial liquidator and readme

This commit is contained in:
dd 2021-04-18 10:08:44 -04:00
parent 2427a4974a
commit 682a90915b
2 changed files with 40 additions and 41 deletions

View File

@ -1,6 +1,30 @@
# Mango Liquidator # Mango Liquidator
## Setup Full Liquidator ## Setup Partial Liquidator
Make sure to edit the .env file to look something like this:
```
export CLUSTER="mainnet-beta"
export CLUSTER_URL="https://solana-api.projectserum.com"
export KEYPAIR=~/.config/solana/id.json
export NODE_ENV=production
export TARGETS="0.1 2"
export GROUP_NAME="BTC_ETH_USDT"
export CHECK_INTERVAL="1000.0"
```
TARGETS represents the BTC and ETH amounts the partial liquidator should try to maintain
in the liquidator's wallet. Any excess of that amount in the wallet will be market sold on Serum DEX.
CHECK_INTERVAL is the amount of milliseconds to wait between querying all margin accounts
### Run
```
yarn install
source .env
yarn partialLiquidate
```
## Setup Full Liquidator [DEPRECATED]
Make sure to edit the .env file to look something like this: Make sure to edit the .env file to look something like this:
``` ```
export CLUSTER="mainnet-beta" export CLUSTER="mainnet-beta"
@ -16,25 +40,3 @@ yarn install
source .env source .env
yarn liquidate yarn liquidate
``` ```
## Setup Partial Liquidator
Make sure to edit the .env file to look something like this:
```
export CLUSTER="mainnet-beta"
export CLUSTER_URL="https://solana-api.projectserum.com"
export KEYPAIR=~/.config/solana/id.json
export NODE_ENV=production
export TARGETS="0.1 2"
export GROUP_NAME="BTC_ETH_USDT"
```
TARGETS represents the BTC and ETH amounts the partial liquidator should try to maintain
in the liquidator's wallet
### Run
```
yarn install
source .env
yarn partialLiquidate
```

View File

@ -3,16 +3,15 @@ import {
getMultipleAccounts, getMultipleAccounts,
IDS, IDS,
MangoClient, MangoClient,
MangoGroup, MangoGroup, MarginAccount,
MarginAccount,
nativeToUi, nativeToUi,
NUM_MARKETS, NUM_MARKETS,
NUM_TOKENS, NUM_TOKENS,
parseTokenAccount, parseTokenAccount,
parseTokenAccountData, tokenToDecimals, parseTokenAccountData,
uiToNative, tokenToDecimals,
} from '@blockworks-foundation/mango-client'; } from '@blockworks-foundation/mango-client';
import { Account, Connection, PublicKey, Transaction, TransactionSignature } from '@solana/web3.js'; import { Account, Connection, PublicKey, Transaction } from '@solana/web3.js';
import { homedir } from 'os'; import { homedir } from 'os';
import fs from 'fs'; import fs from 'fs';
import { notify, sleep } from './utils'; import { notify, sleep } from './utils';
@ -41,17 +40,15 @@ async function balanceWallets(
targets: number[] targets: number[]
) { ) {
const liqorOpenOrders = await Promise.all(liqorOpenOrdersKeys.map((pk) => OpenOrders.load(connection, pk, mangoGroup.dexProgramId))) const liqorOpenOrders = await Promise.all(liqorOpenOrdersKeys.map((pk) => OpenOrders.load(connection, pk, mangoGroup.dexProgramId)))
let updateWallets = false
for (let i = 0; i < NUM_MARKETS; i++) { for (let i = 0; i < NUM_MARKETS; i++) {
const oo = liqorOpenOrders[i] const oo = liqorOpenOrders[i]
if (parseFloat(oo.quoteTokenTotal.toString()) > 0 || parseFloat(oo.baseTokenTotal.toString()) > 0) { if (parseFloat(oo.quoteTokenTotal.toString()) > 0 || parseFloat(oo.baseTokenTotal.toString()) > 0) {
console.log(`Settling funds on liqor wallet ${i}`) console.log(`Settling funds on liqor wallet ${i}`)
await markets[i].settleFunds(connection, liqor, oo, liqorWallets[i], liqorWallets[NUM_TOKENS-1]) await markets[i].settleFunds(connection, liqor, oo, liqorWallets[i], liqorWallets[NUM_TOKENS-1])
updateWallets = true
} }
} }
await sleep(1000) await sleep(1000) // Wait for account wallets to update
const liqorWalletAccounts = await getMultipleAccounts(connection, liqorWallets) const liqorWalletAccounts = await getMultipleAccounts(connection, liqorWallets)
liqorValuesUi = liqorWalletAccounts.map( liqorValuesUi = liqorWalletAccounts.map(
(a, i) => nativeToUi(parseTokenAccountData(a.accountInfo.data).amount, mangoGroup.mintDecimals[i]) (a, i) => nativeToUi(parseTokenAccountData(a.accountInfo.data).amount, mangoGroup.mintDecimals[i])
@ -91,9 +88,10 @@ async function balanceWallets(
size, size,
orderType: 'ioc', orderType: 'ioc',
openOrdersAddressKey: liqorOpenOrdersKeys[marketIndex], openOrdersAddressKey: liqorOpenOrdersKeys[marketIndex],
feeDiscountPubkey: null feeDiscountPubkey: null // TODO find liqor's SRM fee account
} }
) )
// TODO add a SettleFunds instruction to this transaction
console.log(`Place order successful: ${txid}; Settling funds`) console.log(`Place order successful: ${txid}; Settling funds`)
await market.settleFunds(connection, liqor, liqorOpenOrders[marketIndex], liqorWallets[marketIndex], liqorWallets[NUM_TOKENS-1]) await market.settleFunds(connection, liqor, liqorOpenOrders[marketIndex], liqorWallets[marketIndex], liqorWallets[NUM_TOKENS-1])
@ -127,6 +125,7 @@ async function runPartialLiquidator() {
const group_name = process.env.GROUP_NAME || 'BTC_ETH_USDT' const group_name = process.env.GROUP_NAME || 'BTC_ETH_USDT'
const clusterUrl = process.env.CLUSTER_URL || IDS.cluster_urls[cluster] const clusterUrl = process.env.CLUSTER_URL || IDS.cluster_urls[cluster]
const targetsStr = process.env.TARGETS || "0.1 2" const targetsStr = process.env.TARGETS || "0.1 2"
const checkInterval = parseFloat(process.env.CHECK_INTERVAL || "1000.0")
const targets = targetsStr.split(' ').map((s) => parseFloat(s)) const targets = targetsStr.split(' ').map((s) => parseFloat(s))
const connection = new Connection(clusterUrl, 'singleGossip') const connection = new Connection(clusterUrl, 'singleGossip')
@ -159,10 +158,8 @@ async function runPartialLiquidator() {
const markets = await Promise.all(mangoGroup.spotMarkets.map( const markets = await Promise.all(mangoGroup.spotMarkets.map(
(pk) => Market.load(connection, pk, {skipPreflight: true, commitment: 'singleGossip'}, dexProgramId) (pk) => Market.load(connection, pk, {skipPreflight: true, commitment: 'singleGossip'}, dexProgramId)
)) ))
const sleepTime = 1000
// TODO handle failures in any of the steps // TODO handle failures in any of the steps
// Find a way to get all margin accounts without querying fresh--get incremental updates to margin accounts // Find a way to get all margin accounts without querying fresh--get incremental updates to margin accounts
const liqorOpenOrdersKeys: PublicKey[] = [] const liqorOpenOrdersKeys: PublicKey[] = []
for (let i = 0; i < NUM_MARKETS; i++) { for (let i = 0; i < NUM_MARKETS; i++) {
@ -192,6 +189,7 @@ async function runPartialLiquidator() {
(a, i) => nativeToUi(parseTokenAccountData(a.accountInfo.data).amount, mangoGroup.mintDecimals[i]) (a, i) => nativeToUi(parseTokenAccountData(a.accountInfo.data).amount, mangoGroup.mintDecimals[i])
) )
console.log(prices)
console.log(vaultValues) console.log(vaultValues)
console.log(liqorTokenUi) console.log(liqorTokenUi)
@ -201,18 +199,17 @@ async function runPartialLiquidator() {
coll_bias = parseFloat(process.env.COLL_BIAS) coll_bias = parseFloat(process.env.COLL_BIAS)
} }
let maxBorrAcc = "" let maxBorrAcc: MarginAccount | undefined = undefined;
let maxBorrVal = 0; let maxBorrVal = 0;
for (let ma of marginAccounts) { // parallelize this if possible for (let ma of marginAccounts) { // parallelize this if possible
let liquidated = false
let description = '' let description = ''
try { try {
const assetsVal = ma.getAssetsVal(mangoGroup, prices) const assetsVal = ma.getAssetsVal(mangoGroup, prices)
const liabsVal = ma.getLiabsVal(mangoGroup, prices) const liabsVal = ma.getLiabsVal(mangoGroup, prices)
if (liabsVal > maxBorrVal) { if (liabsVal > maxBorrVal) {
maxBorrVal = liabsVal maxBorrVal = liabsVal
maxBorrAcc = ma.publicKey.toBase58() maxBorrAcc = ma
} }
if (liabsVal < 0.1) { // too small of an account; number precision may cause errors if (liabsVal < 0.1) { // too small of an account; number precision may cause errors
@ -334,8 +331,6 @@ async function runPartialLiquidator() {
ma = await client.getMarginAccount(connection, ma.publicKey, dexProgramId) ma = await client.getMarginAccount(connection, ma.publicKey, dexProgramId)
console.log(`Successful partial liquidation\n${ma.toPrettyString(mangoGroup, prices)}\nbeingLiquidated: ${ma.beingLiquidated}`) console.log(`Successful partial liquidation\n${ma.toPrettyString(mangoGroup, prices)}\nbeingLiquidated: ${ma.beingLiquidated}`)
notify(`Successful partial liquidation\n${ma.toPrettyString(mangoGroup, prices)}\nbeingLiquidated: ${ma.beingLiquidated}`) notify(`Successful partial liquidation\n${ma.toPrettyString(mangoGroup, prices)}\nbeingLiquidated: ${ma.beingLiquidated}`)
liquidated = true
break
} catch (e) { } catch (e) {
if (!e.timeout) { if (!e.timeout) {
throw e throw e
@ -346,14 +341,16 @@ async function runPartialLiquidator() {
} }
} }
console.log(`Max Borrow Account: ${maxBorrAcc} | Max Borrow Val: ${maxBorrVal}`) const maxBorrAccPk = maxBorrAcc ? maxBorrAcc.publicKey.toBase58() : ""
const maxBorrAccCr = maxBorrAcc ? maxBorrAcc.getCollateralRatio(mangoGroup, prices) : 0
console.log(`Max Borrow Account: ${maxBorrAccPk} | Max Borrow Val: ${maxBorrVal} | CR: ${maxBorrAccCr}`)
await balanceWallets(connection, mangoGroup, prices, markets, payer, tokenWallets, liqorTokenUi, liqorOpenOrdersKeys, targets) await balanceWallets(connection, mangoGroup, prices, markets, payer, tokenWallets, liqorTokenUi, liqorOpenOrdersKeys, targets)
} catch (e) { } catch (e) {
notify(`unknown error: ${e}`); notify(`unknown error: ${e}`);
console.error(e); console.error(e);
} finally { } finally {
await sleep(sleepTime) await sleep(checkInterval)
} }
} }