cleaned up partial liquidator and readme
This commit is contained in:
parent
2427a4974a
commit
682a90915b
48
README.md
48
README.md
|
@ -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
|
|
||||||
```
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue