Send partial liquidate instruction just to force settle borrow
This commit is contained in:
parent
39b5ab3b4f
commit
1c16de7171
540
src/partial.ts
540
src/partial.ts
|
@ -3,7 +3,8 @@ import {
|
|||
getMultipleAccounts,
|
||||
IDS,
|
||||
MangoClient,
|
||||
MangoGroup, MarginAccount,
|
||||
MangoGroup,
|
||||
MarginAccount,
|
||||
nativeToUi,
|
||||
NUM_MARKETS,
|
||||
NUM_TOKENS,
|
||||
|
@ -11,7 +12,13 @@ import {
|
|||
parseTokenAccountData,
|
||||
tokenToDecimals,
|
||||
} from '@blockworks-foundation/mango-client';
|
||||
import { Account, Commitment, Connection, PublicKey, Transaction } from '@solana/web3.js';
|
||||
import {
|
||||
Account,
|
||||
Commitment,
|
||||
Connection,
|
||||
PublicKey,
|
||||
Transaction,
|
||||
} from '@solana/web3.js';
|
||||
import { homedir } from 'os';
|
||||
import fs from 'fs';
|
||||
import { notify, sleep } from './utils';
|
||||
|
@ -22,7 +29,6 @@ import {
|
|||
} from '@blockworks-foundation/mango-client/lib/instruction';
|
||||
import BN = require('bn.js');
|
||||
|
||||
|
||||
/*
|
||||
After a liquidation, the amounts in each wallet become unbalanced
|
||||
Make sure to sell or buy quantities different from the target on base currencies
|
||||
|
@ -37,269 +43,348 @@ async function balanceWallets(
|
|||
liqorWallets: PublicKey[],
|
||||
liqorValuesUi: number[],
|
||||
liqorOpenOrdersKeys: PublicKey[],
|
||||
targets: number[]
|
||||
targets: number[],
|
||||
) {
|
||||
|
||||
// Retrieve orders from order book by owner
|
||||
const liqorOrders = await Promise.all(markets.map((m) =>
|
||||
m.loadOrdersForOwner(connection, liqor.publicKey)));
|
||||
const liqorOrders = await Promise.all(
|
||||
markets.map((m) => m.loadOrdersForOwner(connection, liqor.publicKey)),
|
||||
);
|
||||
|
||||
// Cancel all
|
||||
const cancelTransactions: Promise<string>[] = []
|
||||
const cancelTransactions: Promise<string>[] = [];
|
||||
for (let i = 0; i < NUM_MARKETS; i++) {
|
||||
for (let order of liqorOrders[i]) {
|
||||
console.log(`Cancelling liqor order on market ${i} size=${order.size} price=${order.price}`)
|
||||
console.log(
|
||||
`Cancelling liqor order on market ${i} size=${order.size} price=${order.price}`,
|
||||
);
|
||||
cancelTransactions.push(markets[i].cancelOrder(connection, liqor, order));
|
||||
}
|
||||
}
|
||||
await Promise.all(cancelTransactions)
|
||||
await Promise.all(cancelTransactions);
|
||||
|
||||
// Load open orders accounts
|
||||
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),
|
||||
),
|
||||
);
|
||||
|
||||
// Settle all
|
||||
const settleTransactions: Promise<string>[] = []
|
||||
const settleTransactions: Promise<string>[] = [];
|
||||
for (let i = 0; i < NUM_MARKETS; i++) {
|
||||
const oo = liqorOpenOrders[i]
|
||||
if (parseFloat(oo.quoteTokenTotal.toString()) > 0 || parseFloat(oo.baseTokenTotal.toString()) > 0) {
|
||||
console.log(`Settling funds to liqor wallet ${i} quote:${oo.quoteTokenTotal.toString()} base:${oo.baseTokenTotal.toString()}`)
|
||||
settleTransactions.push(markets[i].settleFunds(connection, liqor, oo, liqorWallets[i], liqorWallets[NUM_TOKENS-1]))
|
||||
const oo = liqorOpenOrders[i];
|
||||
if (
|
||||
parseFloat(oo.quoteTokenTotal.toString()) > 0 ||
|
||||
parseFloat(oo.baseTokenTotal.toString()) > 0
|
||||
) {
|
||||
console.log(
|
||||
`Settling funds to liqor wallet ${i} quote:${oo.quoteTokenTotal.toString()} base:${oo.baseTokenTotal.toString()}`,
|
||||
);
|
||||
settleTransactions.push(
|
||||
markets[i].settleFunds(
|
||||
connection,
|
||||
liqor,
|
||||
oo,
|
||||
liqorWallets[i],
|
||||
liqorWallets[NUM_TOKENS - 1],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
await Promise.all(settleTransactions)
|
||||
await Promise.all(settleTransactions);
|
||||
|
||||
// Account wallets should instantly update
|
||||
const liqorWalletAccounts = await getMultipleAccounts(connection, liqorWallets, 'processed' as Commitment)
|
||||
liqorValuesUi = liqorWalletAccounts.map(
|
||||
(a, i) => nativeToUi(parseTokenAccountData(a.accountInfo.data).amount, mangoGroup.mintDecimals[i])
|
||||
)
|
||||
const liqorWalletAccounts = await getMultipleAccounts(
|
||||
connection,
|
||||
liqorWallets,
|
||||
'processed' as Commitment,
|
||||
);
|
||||
liqorValuesUi = liqorWalletAccounts.map((a, i) =>
|
||||
nativeToUi(
|
||||
parseTokenAccountData(a.accountInfo.data).amount,
|
||||
mangoGroup.mintDecimals[i],
|
||||
),
|
||||
);
|
||||
|
||||
const diffs: number[] = []
|
||||
const netValues: [number, number][] = []
|
||||
const diffs: number[] = [];
|
||||
const netValues: [number, number][] = [];
|
||||
// Go to each base currency and see if it's above or below target
|
||||
for (let i = 0; i < NUM_TOKENS - 1; i++) {
|
||||
const diff = liqorValuesUi[i] - targets[i]
|
||||
diffs.push(diff)
|
||||
netValues.push([i, diff * prices[i]])
|
||||
const diff = liqorValuesUi[i] - targets[i];
|
||||
diffs.push(diff);
|
||||
netValues.push([i, diff * prices[i]]);
|
||||
}
|
||||
|
||||
// Sort in decreasing order so you sell first then buy
|
||||
netValues.sort((a, b) => (b[1] - a[1]))
|
||||
netValues.sort((a, b) => b[1] - a[1]);
|
||||
for (let i = 0; i < NUM_TOKENS - 1; i++) {
|
||||
const marketIndex = netValues[i][0]
|
||||
const market = markets[marketIndex]
|
||||
const tokenDecimals = tokenToDecimals[marketIndex === 0 ? 'BTC' : 'ETH'] // TODO make this mapping allow arbitrary mango groups
|
||||
const tokenDecimalAdj = Math.pow(10, tokenDecimals)
|
||||
if (netValues[i][1] > 0) { // sell to close
|
||||
const price = prices[marketIndex] * 0.95
|
||||
const size = Math.floor(diffs[marketIndex] * tokenDecimalAdj) / tokenDecimalAdj // round down the size
|
||||
const marketIndex = netValues[i][0];
|
||||
const market = markets[marketIndex];
|
||||
const tokenDecimals = tokenToDecimals[marketIndex === 0 ? 'BTC' : 'ETH']; // TODO make this mapping allow arbitrary mango groups
|
||||
const tokenDecimalAdj = Math.pow(10, tokenDecimals);
|
||||
if (netValues[i][1] > 0) {
|
||||
// sell to close
|
||||
const price = prices[marketIndex] * 0.95;
|
||||
const size =
|
||||
Math.floor(diffs[marketIndex] * tokenDecimalAdj) / tokenDecimalAdj; // round down the size
|
||||
if (size === 0) {
|
||||
continue
|
||||
continue;
|
||||
}
|
||||
console.log(`Sell to close ${marketIndex} ${size} @ ${price}`)
|
||||
let txid = await market.placeOrder(
|
||||
connection,
|
||||
{
|
||||
owner: liqor,
|
||||
payer: liqorWallets[marketIndex],
|
||||
side: 'sell',
|
||||
price,
|
||||
size,
|
||||
orderType: 'ioc',
|
||||
openOrdersAddressKey: liqorOpenOrdersKeys[marketIndex],
|
||||
feeDiscountPubkey: null // TODO find liqor's SRM fee account
|
||||
}
|
||||
)
|
||||
console.log(`Sell to close ${marketIndex} ${size} @ ${price}`);
|
||||
let txid = await market.placeOrder(connection, {
|
||||
owner: liqor,
|
||||
payer: liqorWallets[marketIndex],
|
||||
side: 'sell',
|
||||
price,
|
||||
size,
|
||||
orderType: 'ioc',
|
||||
openOrdersAddressKey: liqorOpenOrdersKeys[marketIndex],
|
||||
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`)
|
||||
await market.settleFunds(connection, liqor, liqorOpenOrders[marketIndex], liqorWallets[marketIndex], liqorWallets[NUM_TOKENS-1])
|
||||
|
||||
} else if (netValues[i][1] < 0) { // buy to close
|
||||
const price = prices[marketIndex] * 1.05 // buy at up to 5% higher than oracle price
|
||||
const size = Math.ceil(-diffs[marketIndex] * tokenDecimalAdj) / tokenDecimalAdj
|
||||
|
||||
console.log(`Buy to close ${marketIndex} ${size} @ ${price}`)
|
||||
let txid = await market.placeOrder(
|
||||
console.log(`Place order successful: ${txid}; Settling funds`);
|
||||
await market.settleFunds(
|
||||
connection,
|
||||
{
|
||||
owner: liqor,
|
||||
payer: liqorWallets[NUM_TOKENS-1],
|
||||
side: 'buy',
|
||||
price,
|
||||
size,
|
||||
orderType: 'ioc',
|
||||
openOrdersAddressKey: liqorOpenOrdersKeys[marketIndex],
|
||||
feeDiscountPubkey: null
|
||||
}
|
||||
)
|
||||
console.log(`Place order successful: ${txid}; Settling funds`)
|
||||
await market.settleFunds(connection, liqor, liqorOpenOrders[marketIndex], liqorWallets[marketIndex], liqorWallets[NUM_TOKENS-1])
|
||||
liqor,
|
||||
liqorOpenOrders[marketIndex],
|
||||
liqorWallets[marketIndex],
|
||||
liqorWallets[NUM_TOKENS - 1],
|
||||
);
|
||||
} else if (netValues[i][1] < 0) {
|
||||
// buy to close
|
||||
const price = prices[marketIndex] * 1.05; // buy at up to 5% higher than oracle price
|
||||
const size =
|
||||
Math.ceil(-diffs[marketIndex] * tokenDecimalAdj) / tokenDecimalAdj;
|
||||
|
||||
console.log(`Buy to close ${marketIndex} ${size} @ ${price}`);
|
||||
let txid = await market.placeOrder(connection, {
|
||||
owner: liqor,
|
||||
payer: liqorWallets[NUM_TOKENS - 1],
|
||||
side: 'buy',
|
||||
price,
|
||||
size,
|
||||
orderType: 'ioc',
|
||||
openOrdersAddressKey: liqorOpenOrdersKeys[marketIndex],
|
||||
feeDiscountPubkey: null,
|
||||
});
|
||||
console.log(`Place order successful: ${txid}; Settling funds`);
|
||||
await market.settleFunds(
|
||||
connection,
|
||||
liqor,
|
||||
liqorOpenOrders[marketIndex],
|
||||
liqorWallets[marketIndex],
|
||||
liqorWallets[NUM_TOKENS - 1],
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function runPartialLiquidator() {
|
||||
const client = new MangoClient()
|
||||
const cluster = process.env.CLUSTER || 'mainnet-beta'
|
||||
const group_name = process.env.GROUP_NAME || 'BTC_ETH_USDT'
|
||||
const clusterUrl = process.env.CLUSTER_URL || IDS.cluster_urls[cluster]
|
||||
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 connection = new Connection(clusterUrl, 'singleGossip')
|
||||
const client = new MangoClient();
|
||||
const cluster = process.env.CLUSTER || 'mainnet-beta';
|
||||
const group_name = process.env.GROUP_NAME || 'BTC_ETH_USDT';
|
||||
const clusterUrl = process.env.CLUSTER_URL || IDS.cluster_urls[cluster];
|
||||
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 connection = new Connection(clusterUrl, 'singleGossip');
|
||||
|
||||
// The address of the Mango Program on the blockchain
|
||||
const programId = new PublicKey(IDS[cluster].mango_program_id)
|
||||
const programId = new PublicKey(IDS[cluster].mango_program_id);
|
||||
|
||||
// The address of the serum dex program on the blockchain: https://github.com/project-serum/serum-dex
|
||||
const dexProgramId = new PublicKey(IDS[cluster].dex_program_id)
|
||||
const dexProgramId = new PublicKey(IDS[cluster].dex_program_id);
|
||||
|
||||
// Address of the MangoGroup
|
||||
const mangoGroupPk = new PublicKey(IDS[cluster].mango_groups[group_name].mango_group_pk)
|
||||
const mangoGroupPk = new PublicKey(
|
||||
IDS[cluster].mango_groups[group_name].mango_group_pk,
|
||||
);
|
||||
|
||||
// liquidator's keypair
|
||||
const keyPairPath = process.env.KEYPAIR || homedir() + '/.config/solana/id.json'
|
||||
const payer = new Account(JSON.parse(fs.readFileSync(keyPairPath, 'utf-8')))
|
||||
const keyPairPath =
|
||||
process.env.KEYPAIR || homedir() + '/.config/solana/id.json';
|
||||
const payer = new Account(JSON.parse(fs.readFileSync(keyPairPath, 'utf-8')));
|
||||
|
||||
notify(`partial liquidator launched cluster=${cluster} group=${group_name}`);
|
||||
|
||||
let mangoGroup = await client.getMangoGroup(connection, mangoGroupPk)
|
||||
let mangoGroup = await client.getMangoGroup(connection, mangoGroupPk);
|
||||
|
||||
const tokenWallets = (await Promise.all(
|
||||
mangoGroup.tokens.map(
|
||||
(mint) => findLargestTokenAccountForOwner(connection, payer.publicKey, mint).then(
|
||||
(response) => response.publicKey
|
||||
)
|
||||
)
|
||||
))
|
||||
const tokenWallets = await Promise.all(
|
||||
mangoGroup.tokens.map((mint) =>
|
||||
findLargestTokenAccountForOwner(connection, payer.publicKey, mint).then(
|
||||
(response) => response.publicKey,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
// load all markets
|
||||
const markets = await Promise.all(mangoGroup.spotMarkets.map(
|
||||
(pk) => Market.load(connection, pk, {skipPreflight: true, commitment: 'singleGossip'}, dexProgramId)
|
||||
))
|
||||
const markets = await Promise.all(
|
||||
mangoGroup.spotMarkets.map((pk) =>
|
||||
Market.load(
|
||||
connection,
|
||||
pk,
|
||||
{ skipPreflight: true, commitment: 'singleGossip' },
|
||||
dexProgramId,
|
||||
),
|
||||
),
|
||||
);
|
||||
// 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
|
||||
const liqorOpenOrdersKeys: PublicKey[] = []
|
||||
const liqorOpenOrdersKeys: PublicKey[] = [];
|
||||
|
||||
for (let i = 0; i < NUM_MARKETS; i++) {
|
||||
let openOrdersAccounts: OpenOrders[] = await markets[i].findOpenOrdersAccountsForOwner(connection, payer.publicKey)
|
||||
if(openOrdersAccounts.length) {
|
||||
liqorOpenOrdersKeys.push(openOrdersAccounts[0].publicKey)
|
||||
let openOrdersAccounts: OpenOrders[] = await markets[
|
||||
i
|
||||
].findOpenOrdersAccountsForOwner(connection, payer.publicKey);
|
||||
if (openOrdersAccounts.length) {
|
||||
liqorOpenOrdersKeys.push(openOrdersAccounts[0].publicKey);
|
||||
} else {
|
||||
console.log(`No OpenOrders account found for market ${markets[i].publicKey.toBase58()}`)
|
||||
console.log(
|
||||
`No OpenOrders account found for market ${markets[
|
||||
i
|
||||
].publicKey.toBase58()}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if(liqorOpenOrdersKeys.length != NUM_MARKETS) {
|
||||
console.log('Warning: Missing OpenOrders accounts. Wallet balancing has been disabled.')
|
||||
if (liqorOpenOrdersKeys.length != NUM_MARKETS) {
|
||||
console.log(
|
||||
'Warning: Missing OpenOrders accounts. Wallet balancing has been disabled.',
|
||||
);
|
||||
}
|
||||
|
||||
const cancelLimit = 5
|
||||
const cancelLimit = 5;
|
||||
while (true) {
|
||||
try {
|
||||
mangoGroup = await client.getMangoGroup(connection, mangoGroupPk)
|
||||
mangoGroup = await client.getMangoGroup(connection, mangoGroupPk);
|
||||
|
||||
let [marginAccounts, prices, vaultAccs, liqorAccs] = await Promise.all([
|
||||
client.getAllMarginAccounts(connection, programId, mangoGroup),
|
||||
mangoGroup.getPrices(connection),
|
||||
getMultipleAccounts(connection, mangoGroup.vaults),
|
||||
getMultipleAccounts(connection, tokenWallets, 'processed' as Commitment),
|
||||
])
|
||||
getMultipleAccounts(
|
||||
connection,
|
||||
tokenWallets,
|
||||
'processed' as Commitment,
|
||||
),
|
||||
]);
|
||||
|
||||
const vaultValues = vaultAccs.map(
|
||||
(a, i) => nativeToUi(parseTokenAccountData(a.accountInfo.data).amount, mangoGroup.mintDecimals[i])
|
||||
)
|
||||
const vaultValues = vaultAccs.map((a, i) =>
|
||||
nativeToUi(
|
||||
parseTokenAccountData(a.accountInfo.data).amount,
|
||||
mangoGroup.mintDecimals[i],
|
||||
),
|
||||
);
|
||||
const liqorTokenValues = liqorAccs.map(
|
||||
(a) => parseTokenAccount(a.accountInfo.data).amount
|
||||
)
|
||||
const liqorTokenUi = liqorAccs.map(
|
||||
(a, i) => nativeToUi(parseTokenAccountData(a.accountInfo.data).amount, mangoGroup.mintDecimals[i])
|
||||
)
|
||||
(a) => parseTokenAccount(a.accountInfo.data).amount,
|
||||
);
|
||||
const liqorTokenUi = liqorAccs.map((a, i) =>
|
||||
nativeToUi(
|
||||
parseTokenAccountData(a.accountInfo.data).amount,
|
||||
mangoGroup.mintDecimals[i],
|
||||
),
|
||||
);
|
||||
|
||||
console.log(prices)
|
||||
console.log(vaultValues)
|
||||
console.log(liqorTokenUi)
|
||||
console.log(prices);
|
||||
console.log(vaultValues);
|
||||
console.log(liqorTokenUi);
|
||||
|
||||
// FIXME: added bias to collRatio to allow other liquidators to step in for testing
|
||||
let coll_bias = 0
|
||||
let coll_bias = 0;
|
||||
if (process.env.COLL_BIAS) {
|
||||
coll_bias = parseFloat(process.env.COLL_BIAS)
|
||||
coll_bias = parseFloat(process.env.COLL_BIAS);
|
||||
}
|
||||
|
||||
let maxBorrAcc: MarginAccount | undefined = undefined;
|
||||
let maxBorrVal = 0;
|
||||
let minCollAcc: MarginAccount | undefined = undefined;
|
||||
let minCollVal = 99999;
|
||||
for (let ma of marginAccounts) { // parallelize this if possible
|
||||
for (let ma of marginAccounts) {
|
||||
// parallelize this if possible
|
||||
|
||||
let description = ''
|
||||
let description = '';
|
||||
try {
|
||||
const assetsVal = ma.getAssetsVal(mangoGroup, prices)
|
||||
const liabsVal = ma.getLiabsVal(mangoGroup, prices)
|
||||
const assetsVal = ma.getAssetsVal(mangoGroup, prices);
|
||||
const liabsVal = ma.getLiabsVal(mangoGroup, prices);
|
||||
if (liabsVal > maxBorrVal) {
|
||||
maxBorrVal = liabsVal
|
||||
maxBorrAcc = ma
|
||||
maxBorrVal = liabsVal;
|
||||
maxBorrAcc = ma;
|
||||
}
|
||||
|
||||
if (liabsVal < 0.1) { // too small of an account; number precision may cause errors
|
||||
continue
|
||||
if (liabsVal < 0.1) {
|
||||
// too small of an account; number precision may cause errors
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!ma.beingLiquidated) {
|
||||
let collRatio = (assetsVal / liabsVal)
|
||||
let collRatio = assetsVal / liabsVal;
|
||||
|
||||
const deficit = liabsVal * mangoGroup.initCollRatio - assetsVal
|
||||
if (deficit < 0.1) { // too small of an account; number precision may cause errors
|
||||
continue
|
||||
const deficit = liabsVal * mangoGroup.initCollRatio - assetsVal;
|
||||
if (deficit < 0.1) {
|
||||
// too small of an account; number precision may cause errors
|
||||
continue;
|
||||
}
|
||||
|
||||
if (collRatio < minCollVal)
|
||||
{
|
||||
minCollVal = collRatio
|
||||
minCollAcc = ma
|
||||
if (collRatio < minCollVal) {
|
||||
minCollVal = collRatio;
|
||||
minCollAcc = ma;
|
||||
}
|
||||
|
||||
if (collRatio + coll_bias >= mangoGroup.maintCollRatio) {
|
||||
continue
|
||||
continue;
|
||||
}
|
||||
|
||||
}
|
||||
description = ma.toPrettyString(mangoGroup, prices)
|
||||
console.log(`Liquidatable\n${description}\nbeingLiquidated: ${ma.beingLiquidated}`)
|
||||
notify(`Liquidatable\n${description}\nbeingLiquidated: ${ma.beingLiquidated}`)
|
||||
description = ma.toPrettyString(mangoGroup, prices);
|
||||
console.log(
|
||||
`Liquidatable\n${description}\nbeingLiquidated: ${ma.beingLiquidated}`,
|
||||
);
|
||||
notify(
|
||||
`Liquidatable\n${description}\nbeingLiquidated: ${ma.beingLiquidated}`,
|
||||
);
|
||||
|
||||
// find the market with the most value in OpenOrdersAccount
|
||||
let maxMarketIndex = -1
|
||||
let maxMarketVal = 0
|
||||
let maxMarketIndex = -1;
|
||||
let maxMarketVal = 0;
|
||||
for (let i = 0; i < NUM_MARKETS; i++) {
|
||||
const openOrdersAccount = ma.openOrdersAccounts[i]
|
||||
const openOrdersAccount = ma.openOrdersAccounts[i];
|
||||
if (openOrdersAccount === undefined) {
|
||||
continue
|
||||
continue;
|
||||
}
|
||||
const marketVal = openOrdersAccount.quoteTokenTotal.toNumber() + openOrdersAccount.baseTokenTotal.toNumber() * prices[i]
|
||||
const marketVal =
|
||||
openOrdersAccount.quoteTokenTotal.toNumber() +
|
||||
openOrdersAccount.baseTokenTotal.toNumber() * prices[i];
|
||||
if (marketVal > maxMarketVal) {
|
||||
maxMarketIndex = i
|
||||
maxMarketVal = marketVal
|
||||
maxMarketIndex = i;
|
||||
maxMarketVal = marketVal;
|
||||
}
|
||||
}
|
||||
const transaction = new Transaction()
|
||||
const transaction = new Transaction();
|
||||
if (maxMarketIndex !== -1) {
|
||||
// force cancel orders on this particular market
|
||||
const spotMarket = markets[maxMarketIndex]
|
||||
const [bids, asks] = await Promise.all([spotMarket.loadBids(connection), spotMarket.loadAsks(connection)])
|
||||
const openOrdersAccount = ma.openOrdersAccounts[maxMarketIndex]
|
||||
const spotMarket = markets[maxMarketIndex];
|
||||
const [bids, asks] = await Promise.all([
|
||||
spotMarket.loadBids(connection),
|
||||
spotMarket.loadAsks(connection),
|
||||
]);
|
||||
const openOrdersAccount = ma.openOrdersAccounts[maxMarketIndex];
|
||||
if (openOrdersAccount === undefined) {
|
||||
console.log('error state')
|
||||
continue
|
||||
console.log('error state');
|
||||
continue;
|
||||
}
|
||||
let numOrders = spotMarket.filterForOpenOrders(bids, asks, [openOrdersAccount]).length
|
||||
let numOrders = spotMarket.filterForOpenOrders(bids, asks, [
|
||||
openOrdersAccount,
|
||||
]).length;
|
||||
const dexSigner = await PublicKey.createProgramAddress(
|
||||
[
|
||||
spotMarket.publicKey.toBuffer(),
|
||||
spotMarket['_decoded'].vaultSignerNonce.toArrayLike(Buffer, 'le', 8)
|
||||
spotMarket['_decoded'].vaultSignerNonce.toArrayLike(
|
||||
Buffer,
|
||||
'le',
|
||||
8,
|
||||
),
|
||||
],
|
||||
spotMarket.programId
|
||||
)
|
||||
spotMarket.programId,
|
||||
);
|
||||
|
||||
for (let i = 0; i < 10; i++) {
|
||||
const instruction = makeForceCancelOrdersInstruction(
|
||||
|
@ -308,7 +393,7 @@ async function runPartialLiquidator() {
|
|||
payer.publicKey,
|
||||
ma.publicKey,
|
||||
mangoGroup.vaults[maxMarketIndex],
|
||||
mangoGroup.vaults[NUM_TOKENS-1],
|
||||
mangoGroup.vaults[NUM_TOKENS - 1],
|
||||
spotMarket.publicKey,
|
||||
spotMarket.bidsAddress,
|
||||
spotMarket.asksAddress,
|
||||
|
@ -320,58 +405,78 @@ async function runPartialLiquidator() {
|
|||
spotMarket.programId,
|
||||
ma.openOrders,
|
||||
mangoGroup.oracles,
|
||||
new BN(cancelLimit)
|
||||
)
|
||||
transaction.add(instruction)
|
||||
numOrders -= cancelLimit
|
||||
new BN(cancelLimit),
|
||||
);
|
||||
transaction.add(instruction);
|
||||
numOrders -= cancelLimit;
|
||||
if (numOrders <= 0) {
|
||||
break
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Find the market with the highest borrows and lowest deposits
|
||||
const deposits = ma.getAssets(mangoGroup)
|
||||
const borrows = ma.getLiabs(mangoGroup)
|
||||
let minNet = 0
|
||||
let minNetIndex = -1
|
||||
let maxNet = 0
|
||||
let maxNetIndex = NUM_TOKENS-1
|
||||
const deposits = ma.getAssets(mangoGroup);
|
||||
const borrows = ma.getLiabs(mangoGroup);
|
||||
let minNet = 0;
|
||||
let minNetIndex = -1;
|
||||
let maxNet = 0;
|
||||
let maxNetIndex = NUM_TOKENS - 1;
|
||||
for (let i = 0; i < NUM_TOKENS; i++) {
|
||||
const netDeposit = (deposits[i] - borrows[i]) * prices[i]
|
||||
const netDeposit = (deposits[i] - borrows[i]) * prices[i];
|
||||
if (netDeposit < minNet) {
|
||||
minNet = netDeposit
|
||||
minNetIndex = i
|
||||
minNet = netDeposit;
|
||||
minNetIndex = i;
|
||||
} else if (netDeposit > maxNet) {
|
||||
maxNet = netDeposit
|
||||
maxNetIndex = i
|
||||
maxNet = netDeposit;
|
||||
maxNetIndex = i;
|
||||
}
|
||||
}
|
||||
|
||||
transaction.add(makePartialLiquidateInstruction(
|
||||
programId,
|
||||
mangoGroup.publicKey,
|
||||
payer.publicKey,
|
||||
liqorAccs[minNetIndex].publicKey,
|
||||
liqorAccs[maxNetIndex].publicKey,
|
||||
ma.publicKey,
|
||||
mangoGroup.vaults[minNetIndex],
|
||||
mangoGroup.vaults[maxNetIndex],
|
||||
mangoGroup.signerKey,
|
||||
ma.openOrders,
|
||||
mangoGroup.oracles,
|
||||
liqorTokenValues[minNetIndex]
|
||||
))
|
||||
if (minNetIndex === -1) {
|
||||
// pick random index not maxNetIndex to force a settle borrows
|
||||
minNetIndex = (maxNetIndex + 1) % NUM_TOKENS;
|
||||
}
|
||||
transaction.add(
|
||||
makePartialLiquidateInstruction(
|
||||
programId,
|
||||
mangoGroup.publicKey,
|
||||
payer.publicKey,
|
||||
liqorAccs[minNetIndex].publicKey,
|
||||
liqorAccs[maxNetIndex].publicKey,
|
||||
ma.publicKey,
|
||||
mangoGroup.vaults[minNetIndex],
|
||||
mangoGroup.vaults[maxNetIndex],
|
||||
mangoGroup.signerKey,
|
||||
ma.openOrders,
|
||||
mangoGroup.oracles,
|
||||
liqorTokenValues[minNetIndex],
|
||||
),
|
||||
);
|
||||
|
||||
await client.sendTransaction(connection, transaction, payer, [])
|
||||
await sleep(2000)
|
||||
ma = await client.getMarginAccount(connection, ma.publicKey, dexProgramId)
|
||||
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}`)
|
||||
break // This is so wallets get balanced
|
||||
await client.sendTransaction(connection, transaction, payer, []);
|
||||
await sleep(2000);
|
||||
ma = await client.getMarginAccount(
|
||||
connection,
|
||||
ma.publicKey,
|
||||
dexProgramId,
|
||||
);
|
||||
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}`,
|
||||
);
|
||||
break; // This is so wallets get balanced
|
||||
} catch (e) {
|
||||
if (!e.timeout) {
|
||||
throw e
|
||||
throw e;
|
||||
} else {
|
||||
notify(`unknown error: ${e}`);
|
||||
console.error(e);
|
||||
|
@ -379,28 +484,45 @@ async function runPartialLiquidator() {
|
|||
}
|
||||
}
|
||||
|
||||
const maxBorrAccPk = maxBorrAcc ? maxBorrAcc.publicKey.toBase58() : ""
|
||||
const maxBorrAccCr = maxBorrAcc ? maxBorrAcc.getCollateralRatio(mangoGroup, prices) : 0
|
||||
const minCollAccPk = minCollAcc ? minCollAcc.publicKey.toBase58() : ""
|
||||
const minCollBorrVal = minCollAcc ? minCollAcc.getLiabsVal(mangoGroup, prices) : 0
|
||||
const maxBorrAccPk = maxBorrAcc ? maxBorrAcc.publicKey.toBase58() : '';
|
||||
const maxBorrAccCr = maxBorrAcc
|
||||
? maxBorrAcc.getCollateralRatio(mangoGroup, prices)
|
||||
: 0;
|
||||
const minCollAccPk = minCollAcc ? minCollAcc.publicKey.toBase58() : '';
|
||||
const minCollBorrVal = minCollAcc
|
||||
? minCollAcc.getLiabsVal(mangoGroup, prices)
|
||||
: 0;
|
||||
|
||||
console.log(`Max Borrow Account: ${maxBorrAccPk} | Borrow Val: ${maxBorrVal} | CR: ${maxBorrAccCr}`)
|
||||
console.log(`Min Coll-Ratio Account: ${minCollAccPk} | Borrow Val: ${minCollBorrVal} | CR: ${minCollVal}`)
|
||||
if(liqorOpenOrdersKeys.length == NUM_MARKETS) {
|
||||
await balanceWallets(connection, mangoGroup, prices, markets, payer, tokenWallets, liqorTokenUi, liqorOpenOrdersKeys, targets)
|
||||
console.log(
|
||||
`Max Borrow Account: ${maxBorrAccPk} | Borrow Val: ${maxBorrVal} | CR: ${maxBorrAccCr}`,
|
||||
);
|
||||
console.log(
|
||||
`Min Coll-Ratio Account: ${minCollAccPk} | Borrow Val: ${minCollBorrVal} | CR: ${minCollVal}`,
|
||||
);
|
||||
if (liqorOpenOrdersKeys.length == NUM_MARKETS) {
|
||||
await balanceWallets(
|
||||
connection,
|
||||
mangoGroup,
|
||||
prices,
|
||||
markets,
|
||||
payer,
|
||||
tokenWallets,
|
||||
liqorTokenUi,
|
||||
liqorOpenOrdersKeys,
|
||||
targets,
|
||||
);
|
||||
} else {
|
||||
console.log('Could not balance wallets due to missing OpenOrders account')
|
||||
console.log(
|
||||
'Could not balance wallets due to missing OpenOrders account',
|
||||
);
|
||||
}
|
||||
|
||||
} catch (e) {
|
||||
notify(`unknown error: ${e}`);
|
||||
console.error(e);
|
||||
} finally {
|
||||
await sleep(checkInterval)
|
||||
await sleep(checkInterval);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
runPartialLiquidator()
|
||||
|
||||
runPartialLiquidator();
|
||||
|
|
Loading…
Reference in New Issue