Improve errors and logging
This commit is contained in:
parent
b3e5e96db4
commit
7d44dd8d12
|
@ -68,101 +68,62 @@ const payer = new Account(
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
console.log(`Payer: ${payer.publicKey.toBase58()}`);
|
console.log(`Payer: ${payer.publicKey.toBase58()}`);
|
||||||
|
const rpcEndpoint = process.env.ENDPOINT_URL || config.cluster_urls[cluster];
|
||||||
const connection = new Connection(
|
const connection = new Connection(rpcEndpoint, 'processed' as Commitment);
|
||||||
process.env.ENDPOINT_URL || config.cluster_urls[cluster],
|
|
||||||
'processed' as Commitment,
|
|
||||||
);
|
|
||||||
const client = new MangoClient(connection, mangoProgramId);
|
const client = new MangoClient(connection, mangoProgramId);
|
||||||
|
|
||||||
let mangoSubscriptionId = -1;
|
let mangoSubscriptionId = -1;
|
||||||
let dexSubscriptionId = -1;
|
let dexSubscriptionId = -1;
|
||||||
|
|
||||||
/**
|
|
||||||
* Process trigger orders for one mango account
|
|
||||||
*/
|
|
||||||
async function processTriggerOrders(
|
|
||||||
mangoGroup: MangoGroup,
|
|
||||||
cache: MangoCache,
|
|
||||||
perpMarkets: PerpMarket[],
|
|
||||||
mangoAccount: MangoAccount,
|
|
||||||
) {
|
|
||||||
if (!groupIds) {
|
|
||||||
throw new Error(`Group ${groupName} not found`);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (let i = 0; i < mangoAccount.advancedOrders.length; i++) {
|
|
||||||
const order = mangoAccount.advancedOrders[i];
|
|
||||||
if (!(order.perpTrigger && order.perpTrigger.isActive)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const trigger = order.perpTrigger;
|
|
||||||
const currentPrice = cache.priceCache[trigger.marketIndex].price;
|
|
||||||
const configMarketIndex = groupIds.perpMarkets.findIndex(
|
|
||||||
(pm) => pm.marketIndex === trigger.marketIndex,
|
|
||||||
);
|
|
||||||
if (
|
|
||||||
(trigger.triggerCondition == 'above' &&
|
|
||||||
currentPrice.gt(trigger.triggerPrice)) ||
|
|
||||||
(trigger.triggerCondition == 'below' &&
|
|
||||||
currentPrice.lt(trigger.triggerPrice))
|
|
||||||
) {
|
|
||||||
console.log(
|
|
||||||
`Executing order for account ${mangoAccount.publicKey.toBase58()}`,
|
|
||||||
);
|
|
||||||
await client.executePerpTriggerOrder(
|
|
||||||
mangoGroup,
|
|
||||||
mangoAccount,
|
|
||||||
cache,
|
|
||||||
perpMarkets[configMarketIndex],
|
|
||||||
payer,
|
|
||||||
i,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function main() {
|
async function main() {
|
||||||
if (!groupIds) {
|
if (!groupIds) {
|
||||||
throw new Error(`Group ${groupName} not found`);
|
throw new Error(`Group ${groupName} not found`);
|
||||||
}
|
}
|
||||||
console.log(`Starting liquidator for ${groupName}...`);
|
console.log(`Starting liquidator for ${groupName}...`);
|
||||||
|
console.log(`RPC Endpoint: ${rpcEndpoint}`);
|
||||||
|
|
||||||
const mangoGroup = await client.getMangoGroup(mangoGroupKey);
|
const mangoGroup = await client.getMangoGroup(mangoGroupKey);
|
||||||
let cache = await mangoGroup.loadCache(connection);
|
let cache = await mangoGroup.loadCache(connection);
|
||||||
let liqorMangoAccount: MangoAccount;
|
let liqorMangoAccount: MangoAccount;
|
||||||
|
|
||||||
if (process.env.LIQOR_PK) {
|
try {
|
||||||
liqorMangoAccount = await client.getMangoAccount(
|
if (process.env.LIQOR_PK) {
|
||||||
new PublicKey(process.env.LIQOR_PK),
|
liqorMangoAccount = await client.getMangoAccount(
|
||||||
mangoGroup.dexProgramId,
|
new PublicKey(process.env.LIQOR_PK),
|
||||||
);
|
mangoGroup.dexProgramId,
|
||||||
if (!liqorMangoAccount.owner.equals(payer.publicKey)) {
|
|
||||||
throw new Error('Account not owned by Keypair');
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
const accounts = await client.getMangoAccountsForOwner(
|
|
||||||
mangoGroup,
|
|
||||||
payer.publicKey,
|
|
||||||
true,
|
|
||||||
);
|
|
||||||
if (accounts.length) {
|
|
||||||
accounts.sort((a, b) =>
|
|
||||||
b
|
|
||||||
.computeValue(mangoGroup, cache)
|
|
||||||
.sub(a.computeValue(mangoGroup, cache))
|
|
||||||
.toNumber(),
|
|
||||||
);
|
);
|
||||||
liqorMangoAccount = accounts[0];
|
if (!liqorMangoAccount.owner.equals(payer.publicKey)) {
|
||||||
|
throw new Error('Account not owned by Keypair');
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
throw new Error('No Mango Account found for this Keypair');
|
const accounts = await client.getMangoAccountsForOwner(
|
||||||
|
mangoGroup,
|
||||||
|
payer.publicKey,
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
if (accounts.length) {
|
||||||
|
accounts.sort((a, b) =>
|
||||||
|
b
|
||||||
|
.computeValue(mangoGroup, cache)
|
||||||
|
.sub(a.computeValue(mangoGroup, cache))
|
||||||
|
.toNumber(),
|
||||||
|
);
|
||||||
|
liqorMangoAccount = accounts[0];
|
||||||
|
} else {
|
||||||
|
throw new Error('No Mango Account found for this Keypair');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
} catch (err: any) {
|
||||||
|
console.error(err);
|
||||||
|
throw new Error(`Error loading liqor Mango Account: ${err.message}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(`Liqor Public Key: ${liqorMangoAccount.publicKey.toBase58()}`);
|
console.log(`Liqor Public Key: ${liqorMangoAccount.publicKey.toBase58()}`);
|
||||||
|
|
||||||
let mangoAccounts: MangoAccount[] = [];
|
let mangoAccounts: MangoAccount[] = [];
|
||||||
await refreshAccounts(mangoGroup, mangoAccounts);
|
await refreshAccounts(mangoGroup, mangoAccounts);
|
||||||
watchAccounts(groupIds.mangoProgramId, mangoGroup, mangoAccounts);
|
watchAccounts(groupIds.mangoProgramId, mangoGroup, mangoAccounts);
|
||||||
|
|
||||||
const perpMarkets = await Promise.all(
|
const perpMarkets = await Promise.all(
|
||||||
groupIds.perpMarkets.map((perpMarket) => {
|
groupIds.perpMarkets.map((perpMarket) => {
|
||||||
return mangoGroup.loadPerpMarket(
|
return mangoGroup.loadPerpMarket(
|
||||||
|
@ -199,7 +160,7 @@ async function main() {
|
||||||
const advancedOrders = await getMultipleAccounts(connection, allAOs);
|
const advancedOrders = await getMultipleAccounts(connection, allAOs);
|
||||||
[cache, liqorMangoAccount] = await Promise.all([
|
[cache, liqorMangoAccount] = await Promise.all([
|
||||||
mangoGroup.loadCache(connection),
|
mangoGroup.loadCache(connection),
|
||||||
liqorMangoAccount.reload(connection),
|
liqorMangoAccount.reload(connection, mangoGroup.dexProgramId),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
mangoAccountsWithAOs.forEach((ma, i) => {
|
mangoAccountsWithAOs.forEach((ma, i) => {
|
||||||
|
@ -211,7 +172,7 @@ async function main() {
|
||||||
} else {
|
} else {
|
||||||
[cache, liqorMangoAccount] = await Promise.all([
|
[cache, liqorMangoAccount] = await Promise.all([
|
||||||
mangoGroup.loadCache(connection),
|
mangoGroup.loadCache(connection),
|
||||||
liqorMangoAccount.reload(connection),
|
liqorMangoAccount.reload(connection, mangoGroup.dexProgramId),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -227,11 +188,13 @@ async function main() {
|
||||||
perpMarkets,
|
perpMarkets,
|
||||||
mangoAccount,
|
mangoAccount,
|
||||||
);
|
);
|
||||||
} catch (err) {
|
} catch (err: any) {
|
||||||
console.error(
|
if (!err.message.contains('MangoErrorCode::InvalidParam')) {
|
||||||
`Failed to execute trigger order for ${mangoAccountKeyString}`,
|
console.error(
|
||||||
err,
|
`Failed to execute trigger order for ${mangoAccountKeyString}`,
|
||||||
);
|
err,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -250,13 +213,15 @@ async function main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
const health = mangoAccount.getHealthRatio(mangoGroup, cache, 'Maint');
|
const health = mangoAccount.getHealthRatio(mangoGroup, cache, 'Maint');
|
||||||
|
const accountInfoString = mangoAccount.toPrettyString(
|
||||||
|
groupIds,
|
||||||
|
mangoGroup,
|
||||||
|
cache,
|
||||||
|
);
|
||||||
console.log(
|
console.log(
|
||||||
`Sick account ${mangoAccountKeyString} health ratio: ${health.toString()}`,
|
`Sick account ${mangoAccountKeyString} health ratio: ${health.toString()}\n${accountInfoString}`,
|
||||||
);
|
);
|
||||||
notify(
|
notify(`Sick account\n${accountInfoString}`);
|
||||||
`Sick account ${mangoAccountKeyString} health ratio: ${health.toString()}`,
|
|
||||||
);
|
|
||||||
console.log(mangoAccount.toPrettyString(groupIds, mangoGroup, cache));
|
|
||||||
try {
|
try {
|
||||||
await liquidateAccount(
|
await liquidateAccount(
|
||||||
mangoGroup,
|
mangoGroup,
|
||||||
|
@ -341,14 +306,12 @@ function watchAccounts(
|
||||||
MangoAccountLayout.decode(accountInfo.data),
|
MangoAccountLayout.decode(accountInfo.data),
|
||||||
);
|
);
|
||||||
if (index == -1) {
|
if (index == -1) {
|
||||||
//console.log('New Account');
|
|
||||||
mangoAccounts.push(mangoAccount);
|
mangoAccounts.push(mangoAccount);
|
||||||
} else {
|
} else {
|
||||||
const spotOpenOrdersAccounts =
|
const spotOpenOrdersAccounts =
|
||||||
mangoAccounts[index].spotOpenOrdersAccounts;
|
mangoAccounts[index].spotOpenOrdersAccounts;
|
||||||
mangoAccount.spotOpenOrdersAccounts = spotOpenOrdersAccounts;
|
mangoAccount.spotOpenOrdersAccounts = spotOpenOrdersAccounts;
|
||||||
mangoAccounts[index] = mangoAccount;
|
mangoAccounts[index] = mangoAccount;
|
||||||
//console.log('Updated account ' + accountId.toBase58());
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
'singleGossip',
|
'singleGossip',
|
||||||
|
@ -381,7 +344,6 @@ function watchAccounts(
|
||||||
);
|
);
|
||||||
mangoAccounts[ownerIndex].spotOpenOrdersAccounts[openOrdersIndex] =
|
mangoAccounts[ownerIndex].spotOpenOrdersAccounts[openOrdersIndex] =
|
||||||
openOrders;
|
openOrders;
|
||||||
//console.log('Updated OpenOrders for account ' + mangoAccounts[ownerIndex].publicKey.toBase58());
|
|
||||||
} else {
|
} else {
|
||||||
console.error('Could not match OpenOrdersAccount to MangoAccount');
|
console.error('Could not match OpenOrdersAccount to MangoAccount');
|
||||||
}
|
}
|
||||||
|
@ -436,6 +398,51 @@ async function refreshAccounts(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Process trigger orders for one mango account
|
||||||
|
*/
|
||||||
|
async function processTriggerOrders(
|
||||||
|
mangoGroup: MangoGroup,
|
||||||
|
cache: MangoCache,
|
||||||
|
perpMarkets: PerpMarket[],
|
||||||
|
mangoAccount: MangoAccount,
|
||||||
|
) {
|
||||||
|
if (!groupIds) {
|
||||||
|
throw new Error(`Group ${groupName} not found`);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = 0; i < mangoAccount.advancedOrders.length; i++) {
|
||||||
|
const order = mangoAccount.advancedOrders[i];
|
||||||
|
if (!(order.perpTrigger && order.perpTrigger.isActive)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const trigger = order.perpTrigger;
|
||||||
|
const currentPrice = cache.priceCache[trigger.marketIndex].price;
|
||||||
|
const configMarketIndex = groupIds.perpMarkets.findIndex(
|
||||||
|
(pm) => pm.marketIndex === trigger.marketIndex,
|
||||||
|
);
|
||||||
|
if (
|
||||||
|
(trigger.triggerCondition == 'above' &&
|
||||||
|
currentPrice.gt(trigger.triggerPrice)) ||
|
||||||
|
(trigger.triggerCondition == 'below' &&
|
||||||
|
currentPrice.lt(trigger.triggerPrice))
|
||||||
|
) {
|
||||||
|
console.log(
|
||||||
|
`Executing order for account ${mangoAccount.publicKey.toBase58()}`,
|
||||||
|
);
|
||||||
|
await client.executePerpTriggerOrder(
|
||||||
|
mangoGroup,
|
||||||
|
mangoAccount,
|
||||||
|
cache,
|
||||||
|
perpMarkets[configMarketIndex],
|
||||||
|
payer,
|
||||||
|
i,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function liquidateAccount(
|
async function liquidateAccount(
|
||||||
mangoGroup: MangoGroup,
|
mangoGroup: MangoGroup,
|
||||||
cache: MangoCache,
|
cache: MangoCache,
|
||||||
|
@ -552,6 +559,7 @@ async function liquidateAccount(
|
||||||
liqee.beingLiquidated
|
liqee.beingLiquidated
|
||||||
) {
|
) {
|
||||||
// Send a ForceCancelPerp to reset the being_liquidated flag
|
// Send a ForceCancelPerp to reset the being_liquidated flag
|
||||||
|
console.log('forceCancelAllPerpOrdersInMarket');
|
||||||
await client.forceCancelAllPerpOrdersInMarket(
|
await client.forceCancelAllPerpOrdersInMarket(
|
||||||
mangoGroup,
|
mangoGroup,
|
||||||
liqee,
|
liqee,
|
||||||
|
@ -639,9 +647,11 @@ async function liquidateSpot(
|
||||||
`Liquidating max ${maxLiabTransfer.toString()}/${liqee.getNativeBorrow(
|
`Liquidating max ${maxLiabTransfer.toString()}/${liqee.getNativeBorrow(
|
||||||
liabRootBank,
|
liabRootBank,
|
||||||
minNetIndex,
|
minNetIndex,
|
||||||
)} of liab ${minNetIndex}, asset ${maxNetIndex}`,
|
)} of liab ${groupIds?.tokens[minNetIndex].symbol} for asset ${
|
||||||
|
groupIds?.tokens[maxNetIndex].symbol
|
||||||
|
}`,
|
||||||
);
|
);
|
||||||
console.log(maxNet.toString());
|
|
||||||
if (maxNet.lt(ONE_I80F48) || maxNetIndex == -1) {
|
if (maxNet.lt(ONE_I80F48) || maxNetIndex == -1) {
|
||||||
const highestHealthMarket = perpMarkets
|
const highestHealthMarket = perpMarkets
|
||||||
.map((perpMarket, i) => {
|
.map((perpMarket, i) => {
|
||||||
|
@ -673,7 +683,7 @@ async function liquidateSpot(
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('liquidateTokenAndPerp ' + highestHealthMarket.marketIndex);
|
console.log('liquidateTokenAndPerp', highestHealthMarket.marketIndex);
|
||||||
await client.liquidateTokenAndPerp(
|
await client.liquidateTokenAndPerp(
|
||||||
mangoGroup,
|
mangoGroup,
|
||||||
liqee,
|
liqee,
|
||||||
|
@ -756,11 +766,6 @@ async function liquidatePerps(
|
||||||
const marketIndex = lowestHealthMarket.marketIndex;
|
const marketIndex = lowestHealthMarket.marketIndex;
|
||||||
const perpAccount = liqee.perpAccounts[marketIndex];
|
const perpAccount = liqee.perpAccounts[marketIndex];
|
||||||
const perpMarket = perpMarkets[lowestHealthMarket.i];
|
const perpMarket = perpMarkets[lowestHealthMarket.i];
|
||||||
// const baseRootBank = rootBanks[marketIndex];
|
|
||||||
//
|
|
||||||
// if (!baseRootBank) {
|
|
||||||
// throw new Error(`Base root bank not found for ${marketIndex}`);
|
|
||||||
// }
|
|
||||||
|
|
||||||
if (!perpMarket) {
|
if (!perpMarket) {
|
||||||
throw new Error(`Perp market not found for ${marketIndex}`);
|
throw new Error(`Perp market not found for ${marketIndex}`);
|
||||||
|
@ -805,9 +810,7 @@ async function liquidatePerps(
|
||||||
if (perpAccount.basePosition.isZero()) {
|
if (perpAccount.basePosition.isZero()) {
|
||||||
if (assetRootBank) {
|
if (assetRootBank) {
|
||||||
// we know that since sum of perp healths is negative, lowest perp market must be negative
|
// we know that since sum of perp healths is negative, lowest perp market must be negative
|
||||||
console.log('liquidateTokenAndPerp ' + marketIndex);
|
console.log('liquidateTokenAndPerp', marketIndex);
|
||||||
// maxLiabTransfer
|
|
||||||
let maxLiabTransfer = liqorInitHealth;
|
|
||||||
if (maxNetIndex !== QUOTE_INDEX) {
|
if (maxNetIndex !== QUOTE_INDEX) {
|
||||||
maxLiabTransfer = liqorInitHealth.div(
|
maxLiabTransfer = liqorInitHealth.div(
|
||||||
ONE_I80F48.sub(mangoGroup.spotMarkets[maxNetIndex].initAssetWeight),
|
ONE_I80F48.sub(mangoGroup.spotMarkets[maxNetIndex].initAssetWeight),
|
||||||
|
@ -827,7 +830,7 @@ async function liquidatePerps(
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
console.log('liquidatePerpMarket ' + marketIndex);
|
console.log('liquidatePerpMarket', marketIndex);
|
||||||
|
|
||||||
// technically can be higher because of liquidation fee, but
|
// technically can be higher because of liquidation fee, but
|
||||||
// let's just give ourselves extra room
|
// let's just give ourselves extra room
|
||||||
|
@ -953,7 +956,7 @@ async function balanceTokens(
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
console.log('Cancelling ' + cancelOrdersPromises.length + ' orders');
|
console.log(`Cancelling ${cancelOrdersPromises.length} orders`);
|
||||||
await Promise.all(cancelOrdersPromises);
|
await Promise.all(cancelOrdersPromises);
|
||||||
|
|
||||||
const openOrders = await mangoAccount.loadOpenOrders(
|
const openOrders = await mangoAccount.loadOpenOrders(
|
||||||
|
@ -973,7 +976,7 @@ async function balanceTokens(
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
console.log('Settling on ' + settlePromises.length + ' markets');
|
console.log(`Settling on ${settlePromises.length} markets`);
|
||||||
await Promise.all(settlePromises);
|
await Promise.all(settlePromises);
|
||||||
|
|
||||||
const { diffs, netValues } = getDiffsAndNet(
|
const { diffs, netValues } = getDiffsAndNet(
|
||||||
|
@ -1129,14 +1132,9 @@ async function closePositions(
|
||||||
side == 'sell' ? price.toNumber() * 0.95 : price.toNumber() * 1.05; // TODO: base this on liquidation fee
|
side == 'sell' ? price.toNumber() * 0.95 : price.toNumber() * 1.05; // TODO: base this on liquidation fee
|
||||||
|
|
||||||
console.log(
|
console.log(
|
||||||
side +
|
`${side}ing ${basePositionSize} of ${groupIds?.perpMarkets[i].baseSymbol}-PERP for $${orderPrice}`,
|
||||||
'ing ' +
|
|
||||||
basePositionSize +
|
|
||||||
' of perp ' +
|
|
||||||
i +
|
|
||||||
' for $' +
|
|
||||||
orderPrice,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
await client.placePerpOrder(
|
await client.placePerpOrder(
|
||||||
mangoGroup,
|
mangoGroup,
|
||||||
mangoAccount,
|
mangoAccount,
|
||||||
|
@ -1187,4 +1185,8 @@ function notify(content: string) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
process.on('unhandledException', (err, promise) => {
|
||||||
|
console.error(`Unhandled rejection (promise: ${promise} reason:${err})`);
|
||||||
|
});
|
||||||
|
|
||||||
main();
|
main();
|
||||||
|
|
Loading…
Reference in New Issue