2021-07-22 08:52:16 -07:00
|
|
|
import * as Env from 'dotenv';
|
|
|
|
Env.config();
|
2021-04-06 17:43:51 -07:00
|
|
|
import {
|
|
|
|
findLargestTokenAccountForOwner,
|
|
|
|
getMultipleAccounts,
|
|
|
|
IDS,
|
2021-04-09 13:18:06 -07:00
|
|
|
MangoClient,
|
2021-06-11 12:53:17 -07:00
|
|
|
MangoGroup,
|
|
|
|
MarginAccount,
|
2021-04-09 13:18:06 -07:00
|
|
|
nativeToUi,
|
|
|
|
NUM_MARKETS,
|
|
|
|
NUM_TOKENS,
|
|
|
|
parseTokenAccount,
|
2021-04-18 07:08:44 -07:00
|
|
|
parseTokenAccountData,
|
|
|
|
tokenToDecimals,
|
2021-04-06 17:43:51 -07:00
|
|
|
} from '@blockworks-foundation/mango-client';
|
2021-06-11 12:53:17 -07:00
|
|
|
import {
|
|
|
|
Account,
|
|
|
|
Commitment,
|
|
|
|
Connection,
|
|
|
|
PublicKey,
|
|
|
|
Transaction,
|
|
|
|
} from '@solana/web3.js';
|
2021-04-06 17:43:51 -07:00
|
|
|
import { homedir } from 'os';
|
2022-08-02 05:06:13 -07:00
|
|
|
import * as fs from 'fs';
|
2021-04-06 17:43:51 -07:00
|
|
|
import { notify, sleep } from './utils';
|
2021-04-09 13:18:06 -07:00
|
|
|
import { Market, OpenOrders } from '@project-serum/serum';
|
2021-04-06 17:43:51 -07:00
|
|
|
import {
|
|
|
|
makeForceCancelOrdersInstruction,
|
|
|
|
makePartialLiquidateInstruction,
|
|
|
|
} from '@blockworks-foundation/mango-client/lib/instruction';
|
2021-04-09 13:18:06 -07:00
|
|
|
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
|
|
|
|
Convert excess into quote currency
|
|
|
|
*/
|
|
|
|
async function balanceWallets(
|
|
|
|
connection: Connection,
|
|
|
|
mangoGroup: MangoGroup,
|
|
|
|
prices: number[],
|
|
|
|
markets: Market[],
|
|
|
|
liqor: Account,
|
|
|
|
liqorWallets: PublicKey[],
|
|
|
|
liqorValuesUi: number[],
|
2021-04-09 14:37:10 -07:00
|
|
|
liqorOpenOrdersKeys: PublicKey[],
|
2021-06-11 12:53:17 -07:00
|
|
|
targets: number[],
|
2021-04-09 13:18:06 -07:00
|
|
|
) {
|
2021-05-13 19:10:46 -07:00
|
|
|
// Retrieve orders from order book by owner
|
2021-06-11 12:53:17 -07:00
|
|
|
const liqorOrders = await Promise.all(
|
|
|
|
markets.map((m) => m.loadOrdersForOwner(connection, liqor.publicKey)),
|
|
|
|
);
|
2021-05-13 19:10:46 -07:00
|
|
|
|
|
|
|
// Cancel all
|
2021-06-11 12:53:17 -07:00
|
|
|
const cancelTransactions: Promise<string>[] = [];
|
2021-05-13 19:10:46 -07:00
|
|
|
for (let i = 0; i < NUM_MARKETS; i++) {
|
|
|
|
for (let order of liqorOrders[i]) {
|
2021-06-11 12:53:17 -07:00
|
|
|
console.log(
|
|
|
|
`Cancelling liqor order on market ${i} size=${order.size} price=${order.price}`,
|
|
|
|
);
|
2021-05-13 19:10:46 -07:00
|
|
|
cancelTransactions.push(markets[i].cancelOrder(connection, liqor, order));
|
|
|
|
}
|
|
|
|
}
|
2021-06-11 12:53:17 -07:00
|
|
|
await Promise.all(cancelTransactions);
|
2021-05-13 19:10:46 -07:00
|
|
|
|
|
|
|
// Load open orders accounts
|
2021-06-11 12:53:17 -07:00
|
|
|
const liqorOpenOrders = await Promise.all(
|
|
|
|
liqorOpenOrdersKeys.map((pk) =>
|
|
|
|
OpenOrders.load(connection, pk, mangoGroup.dexProgramId),
|
|
|
|
),
|
|
|
|
);
|
2021-05-13 19:10:46 -07:00
|
|
|
|
|
|
|
// Settle all
|
2021-06-11 12:53:17 -07:00
|
|
|
const settleTransactions: Promise<string>[] = [];
|
2021-04-09 13:18:06 -07:00
|
|
|
for (let i = 0; i < NUM_MARKETS; i++) {
|
2021-06-11 12:53:17 -07:00
|
|
|
const oo = liqorOpenOrders[i];
|
|
|
|
if (
|
2021-06-22 06:25:17 -07:00
|
|
|
parseFloat(oo.quoteTokenTotal.toString()) +
|
|
|
|
parseFloat(oo['referrerRebatesAccrued'].toString()) >
|
|
|
|
0 ||
|
2021-06-11 12:53:17 -07:00
|
|
|
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],
|
|
|
|
),
|
|
|
|
);
|
2021-04-09 13:18:06 -07:00
|
|
|
}
|
|
|
|
}
|
2021-06-11 12:53:17 -07:00
|
|
|
await Promise.all(settleTransactions);
|
2021-04-09 13:18:06 -07:00
|
|
|
|
2021-05-13 19:10:46 -07:00
|
|
|
// Account wallets should instantly update
|
2021-06-11 12:53:17 -07:00
|
|
|
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][] = [];
|
2021-04-09 13:18:06 -07:00
|
|
|
// Go to each base currency and see if it's above or below target
|
|
|
|
for (let i = 0; i < NUM_TOKENS - 1; i++) {
|
2021-06-11 12:53:17 -07:00
|
|
|
const diff = liqorValuesUi[i] - targets[i];
|
|
|
|
diffs.push(diff);
|
|
|
|
netValues.push([i, diff * prices[i]]);
|
2021-04-09 13:18:06 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
// Sort in decreasing order so you sell first then buy
|
2021-06-11 12:53:17 -07:00
|
|
|
netValues.sort((a, b) => b[1] - a[1]);
|
2021-04-09 13:18:06 -07:00
|
|
|
for (let i = 0; i < NUM_TOKENS - 1; i++) {
|
2021-06-11 12:53:17 -07:00
|
|
|
const marketIndex = netValues[i][0];
|
|
|
|
const market = markets[marketIndex];
|
|
|
|
const tokenDecimals = mangoGroup.getTokenDecimals(marketIndex);
|
|
|
|
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
|
2021-04-09 13:18:06 -07:00
|
|
|
if (size === 0) {
|
2021-06-11 12:53:17 -07:00
|
|
|
continue;
|
2021-04-09 13:18:06 -07:00
|
|
|
}
|
2021-06-11 12:53:17 -07:00
|
|
|
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
|
|
|
|
});
|
2021-04-18 07:08:44 -07:00
|
|
|
// TODO add a SettleFunds instruction to this transaction
|
2021-06-11 12:53:17 -07:00
|
|
|
console.log(`Place order successful: ${txid}; Settling funds`);
|
|
|
|
await market.settleFunds(
|
2021-04-09 13:18:06 -07:00
|
|
|
connection,
|
2021-06-11 12:53:17 -07:00
|
|
|
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],
|
|
|
|
);
|
2021-04-09 13:18:06 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2021-04-06 17:43:51 -07:00
|
|
|
|
2021-06-22 05:53:51 -07:00
|
|
|
function shuffleArray(array) {
|
|
|
|
for (let i = array.length - 1; i > 0; i--) {
|
|
|
|
const j = Math.floor(Math.random() * (i + 1));
|
|
|
|
[array[i], array[j]] = [array[j], array[i]];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-06 17:43:51 -07:00
|
|
|
async function runPartialLiquidator() {
|
2021-06-11 12:53:17 -07:00
|
|
|
const client = new MangoClient();
|
|
|
|
const cluster = process.env.CLUSTER || 'mainnet-beta';
|
|
|
|
const group_name = process.env.GROUP_NAME || 'BTC_ETH_SOL_SRM_USDC';
|
|
|
|
const clusterUrl = process.env.CLUSTER_URL || IDS.cluster_urls[cluster];
|
|
|
|
const targetsStr = process.env.TARGETS || '0.1 2.0 100.0 1000.0';
|
|
|
|
const checkInterval = parseFloat(process.env.CHECK_INTERVAL || '1000.0');
|
|
|
|
const targets = targetsStr.split(' ').map((s) => parseFloat(s));
|
|
|
|
const connection = new Connection(clusterUrl, 'singleGossip');
|
2021-04-06 17:43:51 -07:00
|
|
|
|
|
|
|
// The address of the Mango Program on the blockchain
|
2021-06-11 12:53:17 -07:00
|
|
|
const programId = new PublicKey(IDS[cluster].mango_program_id);
|
2021-04-06 17:43:51 -07:00
|
|
|
|
|
|
|
// The address of the serum dex program on the blockchain: https://github.com/project-serum/serum-dex
|
2021-06-11 12:53:17 -07:00
|
|
|
const dexProgramId = new PublicKey(IDS[cluster].dex_program_id);
|
2021-04-06 17:43:51 -07:00
|
|
|
|
|
|
|
// Address of the MangoGroup
|
2021-06-11 12:53:17 -07:00
|
|
|
const mangoGroupPk = new PublicKey(
|
|
|
|
IDS[cluster].mango_groups[group_name].mango_group_pk,
|
|
|
|
);
|
2021-04-06 17:43:51 -07:00
|
|
|
|
|
|
|
// liquidator's keypair
|
2021-06-11 12:53:17 -07:00
|
|
|
const keyPairPath =
|
|
|
|
process.env.KEYPAIR || homedir() + '/.config/solana/id.json';
|
|
|
|
const payer = new Account(JSON.parse(fs.readFileSync(keyPairPath, 'utf-8')));
|
2021-04-06 17:43:51 -07:00
|
|
|
|
2021-04-09 14:37:10 -07:00
|
|
|
notify(`partial liquidator launched cluster=${cluster} group=${group_name}`);
|
2021-04-06 17:43:51 -07:00
|
|
|
|
2021-06-11 12:53:17 -07:00
|
|
|
let mangoGroup = await client.getMangoGroup(connection, mangoGroupPk);
|
2021-04-06 17:43:51 -07:00
|
|
|
|
2021-06-11 12:53:17 -07:00
|
|
|
const tokenWallets = await Promise.all(
|
|
|
|
mangoGroup.tokens.map((mint) =>
|
|
|
|
findLargestTokenAccountForOwner(connection, payer.publicKey, mint).then(
|
|
|
|
(response) => response.publicKey,
|
|
|
|
),
|
|
|
|
),
|
|
|
|
);
|
2021-04-06 17:43:51 -07:00
|
|
|
|
|
|
|
// load all markets
|
2021-06-11 12:53:17 -07:00
|
|
|
const markets = await Promise.all(
|
|
|
|
mangoGroup.spotMarkets.map((pk) =>
|
|
|
|
Market.load(
|
|
|
|
connection,
|
|
|
|
pk,
|
|
|
|
{ skipPreflight: true, commitment: 'singleGossip' },
|
|
|
|
dexProgramId,
|
|
|
|
),
|
|
|
|
),
|
|
|
|
);
|
2021-04-06 17:43:51 -07:00
|
|
|
// 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
|
2021-06-11 12:53:17 -07:00
|
|
|
const liqorOpenOrdersKeys: PublicKey[] = [];
|
2021-04-09 13:18:06 -07:00
|
|
|
|
|
|
|
for (let i = 0; i < NUM_MARKETS; i++) {
|
2021-06-11 12:53:17 -07:00
|
|
|
let openOrdersAccounts: OpenOrders[] = await markets[
|
|
|
|
i
|
|
|
|
].findOpenOrdersAccountsForOwner(connection, payer.publicKey);
|
|
|
|
if (openOrdersAccounts.length) {
|
|
|
|
liqorOpenOrdersKeys.push(openOrdersAccounts[0].publicKey);
|
2021-04-30 05:49:48 -07:00
|
|
|
} else {
|
2021-06-11 12:53:17 -07:00
|
|
|
console.log(
|
|
|
|
`No OpenOrders account found for market ${markets[
|
|
|
|
i
|
|
|
|
].publicKey.toBase58()}`,
|
|
|
|
);
|
2021-04-30 05:49:48 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-06-11 12:53:17 -07:00
|
|
|
if (liqorOpenOrdersKeys.length != NUM_MARKETS) {
|
|
|
|
console.log(
|
|
|
|
'Warning: Missing OpenOrders accounts. Wallet balancing has been disabled.',
|
|
|
|
);
|
2021-04-09 13:18:06 -07:00
|
|
|
}
|
|
|
|
|
2021-06-11 12:53:17 -07:00
|
|
|
const cancelLimit = 5;
|
2021-04-06 17:43:51 -07:00
|
|
|
while (true) {
|
|
|
|
try {
|
2021-06-11 12:53:17 -07:00
|
|
|
mangoGroup = await client.getMangoGroup(connection, mangoGroupPk);
|
|
|
|
let marginAccounts = process.env.FILTER_ACCOUNTS
|
|
|
|
? await client.getAllMarginAccountsWithBorrows(
|
|
|
|
connection,
|
|
|
|
programId,
|
|
|
|
mangoGroup,
|
|
|
|
)
|
|
|
|
: await client.getAllMarginAccounts(connection, programId, mangoGroup);
|
2021-04-06 17:43:51 -07:00
|
|
|
|
2021-06-22 05:53:51 -07:00
|
|
|
shuffleArray(marginAccounts);
|
2021-06-11 12:53:17 -07:00
|
|
|
let [prices, vaultAccs, liqorAccs] = await Promise.all([
|
2021-04-09 13:18:06 -07:00
|
|
|
mangoGroup.getPrices(connection),
|
|
|
|
getMultipleAccounts(connection, mangoGroup.vaults),
|
2021-06-11 12:53:17 -07:00
|
|
|
getMultipleAccounts(
|
|
|
|
connection,
|
|
|
|
tokenWallets,
|
|
|
|
'processed' as Commitment,
|
|
|
|
),
|
|
|
|
]);
|
|
|
|
|
|
|
|
const vaultValues = vaultAccs.map((a, i) =>
|
|
|
|
nativeToUi(
|
|
|
|
parseTokenAccountData(a.accountInfo.data).amount,
|
|
|
|
mangoGroup.mintDecimals[i],
|
|
|
|
),
|
|
|
|
);
|
2021-04-09 13:18:06 -07:00
|
|
|
const liqorTokenValues = liqorAccs.map(
|
2021-06-11 12:53:17 -07:00
|
|
|
(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);
|
2021-04-06 17:43:51 -07:00
|
|
|
|
|
|
|
// FIXME: added bias to collRatio to allow other liquidators to step in for testing
|
2021-06-11 12:53:17 -07:00
|
|
|
let coll_bias = 0;
|
2021-04-06 17:43:51 -07:00
|
|
|
if (process.env.COLL_BIAS) {
|
2021-06-11 12:53:17 -07:00
|
|
|
coll_bias = parseFloat(process.env.COLL_BIAS);
|
2021-04-06 17:43:51 -07:00
|
|
|
}
|
|
|
|
|
2021-04-18 07:08:44 -07:00
|
|
|
let maxBorrAcc: MarginAccount | undefined = undefined;
|
2021-04-06 17:43:51 -07:00
|
|
|
let maxBorrVal = 0;
|
2021-05-13 19:10:46 -07:00
|
|
|
let minCollAcc: MarginAccount | undefined = undefined;
|
|
|
|
let minCollVal = 99999;
|
2021-06-22 05:53:51 -07:00
|
|
|
|
2021-06-11 12:53:17 -07:00
|
|
|
for (let ma of marginAccounts) {
|
|
|
|
// parallelize this if possible
|
2021-04-06 17:43:51 -07:00
|
|
|
|
2021-06-11 12:53:17 -07:00
|
|
|
let description = '';
|
2021-04-14 14:41:15 -07:00
|
|
|
try {
|
2021-06-11 12:53:17 -07:00
|
|
|
const assetsVal = ma.getAssetsVal(mangoGroup, prices);
|
|
|
|
const liabsVal = ma.getLiabsVal(mangoGroup, prices);
|
2021-04-14 14:41:15 -07:00
|
|
|
if (liabsVal > maxBorrVal) {
|
2021-06-11 12:53:17 -07:00
|
|
|
maxBorrVal = liabsVal;
|
|
|
|
maxBorrAcc = ma;
|
2021-04-14 14:41:15 -07:00
|
|
|
}
|
2021-04-06 17:43:51 -07:00
|
|
|
|
2021-06-22 06:15:00 -07:00
|
|
|
if (liabsVal < 0.1) {
|
2021-06-11 12:53:17 -07:00
|
|
|
// too small of an account; number precision may cause errors
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
let collRatio = assetsVal / liabsVal;
|
|
|
|
if (collRatio < minCollVal) {
|
|
|
|
minCollVal = collRatio;
|
|
|
|
minCollAcc = ma;
|
2021-04-14 14:41:15 -07:00
|
|
|
}
|
|
|
|
if (!ma.beingLiquidated) {
|
2021-06-11 12:53:17 -07:00
|
|
|
const deficit = liabsVal * mangoGroup.initCollRatio - assetsVal;
|
2021-06-22 06:55:48 -07:00
|
|
|
if (deficit < 0.01) {
|
2021-06-11 12:53:17 -07:00
|
|
|
// too small of an account; number precision may cause errors
|
|
|
|
continue;
|
2021-05-13 19:10:46 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
if (collRatio + coll_bias >= mangoGroup.maintCollRatio) {
|
2021-06-11 12:53:17 -07:00
|
|
|
continue;
|
2021-05-13 19:10:46 -07:00
|
|
|
}
|
2021-04-14 14:41:15 -07:00
|
|
|
}
|
2021-06-11 12:53:17 -07:00
|
|
|
description = ma.toPrettyString(mangoGroup, prices);
|
|
|
|
console.log(
|
|
|
|
`Liquidatable\n${description}\nbeingLiquidated: ${ma.beingLiquidated}`,
|
|
|
|
);
|
|
|
|
notify(
|
|
|
|
`Liquidatable\n${description}\nbeingLiquidated: ${ma.beingLiquidated}`,
|
|
|
|
);
|
2021-04-14 14:41:15 -07:00
|
|
|
|
|
|
|
// find the market with the most value in OpenOrdersAccount
|
2021-06-11 12:53:17 -07:00
|
|
|
let maxMarketIndex = -1;
|
|
|
|
let maxMarketVal = 0;
|
2021-04-14 14:41:15 -07:00
|
|
|
for (let i = 0; i < NUM_MARKETS; i++) {
|
2021-06-11 12:53:17 -07:00
|
|
|
const openOrdersAccount = ma.openOrdersAccounts[i];
|
2021-04-14 14:41:15 -07:00
|
|
|
if (openOrdersAccount === undefined) {
|
2021-06-11 12:53:17 -07:00
|
|
|
continue;
|
2021-04-06 17:43:51 -07:00
|
|
|
}
|
2021-06-11 12:53:17 -07:00
|
|
|
const marketVal =
|
|
|
|
openOrdersAccount.quoteTokenTotal.toNumber() +
|
2021-06-22 06:25:17 -07:00
|
|
|
openOrdersAccount['referrerRebatesAccrued'].toNumber() +
|
2021-06-11 12:53:17 -07:00
|
|
|
openOrdersAccount.baseTokenTotal.toNumber() * prices[i];
|
2021-04-14 14:41:15 -07:00
|
|
|
if (marketVal > maxMarketVal) {
|
2021-06-11 12:53:17 -07:00
|
|
|
maxMarketIndex = i;
|
|
|
|
maxMarketVal = marketVal;
|
2021-04-14 14:41:15 -07:00
|
|
|
}
|
|
|
|
}
|
2021-06-11 12:53:17 -07:00
|
|
|
|
|
|
|
// Cancel orders on the market with the most value in open orders
|
|
|
|
const transaction = new Transaction();
|
2021-04-14 14:41:15 -07:00
|
|
|
if (maxMarketIndex !== -1) {
|
|
|
|
// force cancel orders on this particular market
|
2021-06-11 12:53:17 -07:00
|
|
|
const spotMarket = markets[maxMarketIndex];
|
|
|
|
const [bids, asks] = await Promise.all([
|
|
|
|
spotMarket.loadBids(connection),
|
|
|
|
spotMarket.loadAsks(connection),
|
|
|
|
]);
|
|
|
|
const openOrdersAccount = ma.openOrdersAccounts[maxMarketIndex];
|
2021-04-14 14:41:15 -07:00
|
|
|
if (openOrdersAccount === undefined) {
|
2021-06-11 12:53:17 -07:00
|
|
|
console.log('error state');
|
|
|
|
continue;
|
2021-04-14 14:41:15 -07:00
|
|
|
}
|
2021-06-11 12:53:17 -07:00
|
|
|
let numOrders = spotMarket.filterForOpenOrders(bids, asks, [
|
|
|
|
openOrdersAccount,
|
|
|
|
]).length;
|
2021-04-14 14:41:15 -07:00
|
|
|
const dexSigner = await PublicKey.createProgramAddress(
|
|
|
|
[
|
|
|
|
spotMarket.publicKey.toBuffer(),
|
2021-06-11 12:53:17 -07:00
|
|
|
spotMarket['_decoded'].vaultSignerNonce.toArrayLike(
|
|
|
|
Buffer,
|
|
|
|
'le',
|
|
|
|
8,
|
|
|
|
),
|
2021-04-14 14:41:15 -07:00
|
|
|
],
|
2021-06-11 12:53:17 -07:00
|
|
|
spotMarket.programId,
|
|
|
|
);
|
2021-04-14 14:41:15 -07:00
|
|
|
|
|
|
|
for (let i = 0; i < 10; i++) {
|
|
|
|
const instruction = makeForceCancelOrdersInstruction(
|
|
|
|
programId,
|
|
|
|
mangoGroup.publicKey,
|
|
|
|
payer.publicKey,
|
|
|
|
ma.publicKey,
|
|
|
|
mangoGroup.vaults[maxMarketIndex],
|
2021-06-11 12:53:17 -07:00
|
|
|
mangoGroup.vaults[NUM_TOKENS - 1],
|
2021-04-14 14:41:15 -07:00
|
|
|
spotMarket.publicKey,
|
|
|
|
spotMarket.bidsAddress,
|
|
|
|
spotMarket.asksAddress,
|
|
|
|
mangoGroup.signerKey,
|
|
|
|
spotMarket['_decoded'].eventQueue,
|
|
|
|
spotMarket['_decoded'].baseVault,
|
|
|
|
spotMarket['_decoded'].quoteVault,
|
|
|
|
dexSigner,
|
|
|
|
spotMarket.programId,
|
|
|
|
ma.openOrders,
|
|
|
|
mangoGroup.oracles,
|
2021-06-11 12:53:17 -07:00
|
|
|
new BN(cancelLimit),
|
|
|
|
);
|
|
|
|
transaction.add(instruction);
|
|
|
|
numOrders -= cancelLimit;
|
2021-04-14 14:41:15 -07:00
|
|
|
if (numOrders <= 0) {
|
2021-10-15 12:13:21 -07:00
|
|
|
await client.sendTransaction(connection, transaction, payer, []);
|
2021-06-11 12:53:17 -07:00
|
|
|
break;
|
2021-04-07 17:54:06 -07:00
|
|
|
}
|
2021-04-06 17:43:51 -07:00
|
|
|
}
|
2021-04-14 14:41:15 -07:00
|
|
|
}
|
2021-04-06 17:43:51 -07:00
|
|
|
|
2021-06-11 12:53:17 -07:00
|
|
|
// Find the market with the highest borrows and lowest assets
|
|
|
|
const deposits = ma.getAssets(mangoGroup);
|
|
|
|
const borrows = ma.getLiabs(mangoGroup);
|
|
|
|
let minNet = 0;
|
|
|
|
let minNetIndex = -1;
|
|
|
|
let maxNet = 0;
|
|
|
|
let maxNetIndex = NUM_TOKENS - 1;
|
2021-04-14 14:41:15 -07:00
|
|
|
for (let i = 0; i < NUM_TOKENS; i++) {
|
2021-06-11 12:53:17 -07:00
|
|
|
const netDeposit = (deposits[i] - borrows[i]) * prices[i];
|
2021-04-14 14:41:15 -07:00
|
|
|
if (netDeposit < minNet) {
|
2021-06-11 12:53:17 -07:00
|
|
|
minNet = netDeposit;
|
|
|
|
minNetIndex = i;
|
2021-04-14 14:41:15 -07:00
|
|
|
} else if (netDeposit > maxNet) {
|
2021-06-11 12:53:17 -07:00
|
|
|
maxNet = netDeposit;
|
|
|
|
maxNetIndex = i;
|
2021-04-06 17:43:51 -07:00
|
|
|
}
|
2021-04-14 14:41:15 -07:00
|
|
|
}
|
2021-04-06 17:43:51 -07:00
|
|
|
|
2021-06-22 06:33:17 -07:00
|
|
|
if (minNetIndex === -1) {
|
|
|
|
// In this case, send a random token account that is not maxNetIndex
|
|
|
|
minNetIndex = (maxNetIndex + 1) % NUM_TOKENS;
|
2021-06-22 06:14:15 -07:00
|
|
|
}
|
2021-06-11 12:53:17 -07:00
|
|
|
|
2021-08-26 15:52:27 -07:00
|
|
|
|
|
|
|
const liqTx = new Transaction();
|
|
|
|
|
|
|
|
liqTx.add(
|
2021-06-22 06:33:17 -07:00
|
|
|
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],
|
|
|
|
),
|
|
|
|
);
|
|
|
|
|
2021-08-26 15:52:27 -07:00
|
|
|
await client.sendTransaction(connection, liqTx, payer, []);
|
2021-06-11 12:53:17 -07:00
|
|
|
await sleep(2000);
|
|
|
|
ma = await client.getMarginAccount(
|
|
|
|
connection,
|
2021-04-14 14:41:15 -07:00
|
|
|
ma.publicKey,
|
2021-06-11 12:53:17 -07:00
|
|
|
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
|
2021-04-14 14:41:15 -07:00
|
|
|
} catch (e) {
|
|
|
|
if (!e.timeout) {
|
2021-06-11 12:53:17 -07:00
|
|
|
throw e;
|
2021-04-14 14:41:15 -07:00
|
|
|
} else {
|
|
|
|
notify(`unknown error: ${e}`);
|
|
|
|
console.error(e);
|
2021-04-06 17:43:51 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2021-04-09 13:18:06 -07:00
|
|
|
|
2021-06-11 12:53:17 -07:00
|
|
|
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,
|
|
|
|
);
|
2021-04-30 05:49:48 -07:00
|
|
|
} else {
|
2021-06-11 12:53:17 -07:00
|
|
|
console.log(
|
|
|
|
'Could not balance wallets due to missing OpenOrders account',
|
|
|
|
);
|
2021-04-30 05:49:48 -07:00
|
|
|
}
|
2021-04-06 17:43:51 -07:00
|
|
|
} catch (e) {
|
|
|
|
notify(`unknown error: ${e}`);
|
|
|
|
console.error(e);
|
|
|
|
} finally {
|
2021-06-11 12:53:17 -07:00
|
|
|
await sleep(checkInterval);
|
2021-04-06 17:43:51 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-06-11 12:53:17 -07:00
|
|
|
runPartialLiquidator();
|