From b3e5e96db4ea15dc68aa7701330aed6d9f5e4a13 Mon Sep 17 00:00:00 2001 From: Riordan Panayides Date: Thu, 2 Dec 2021 17:54:05 +0000 Subject: [PATCH 01/16] Increase default refreshAccounts interval --- src/liquidator.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/liquidator.ts b/src/liquidator.ts index 76107db..988ecc1 100644 --- a/src/liquidator.ts +++ b/src/liquidator.ts @@ -34,7 +34,7 @@ envExpand(Env.config()); const interval = parseInt(process.env.INTERVAL || '3500'); const refreshAccountsInterval = parseInt( - process.env.INTERVAL_ACCOUNTS || '120000', + process.env.INTERVAL_ACCOUNTS || '600000', ); const refreshWebsocketInterval = parseInt( process.env.INTERVAL_WEBSOCKET || '300000', From 7d44dd8d12e6d31ab36f49b6703b07f742a96a65 Mon Sep 17 00:00:00 2001 From: Riordan Panayides Date: Thu, 2 Dec 2021 18:00:48 +0000 Subject: [PATCH 02/16] Improve errors and logging --- src/liquidator.ts | 218 +++++++++++++++++++++++----------------------- 1 file changed, 110 insertions(+), 108 deletions(-) diff --git a/src/liquidator.ts b/src/liquidator.ts index 988ecc1..f6c2fde 100644 --- a/src/liquidator.ts +++ b/src/liquidator.ts @@ -68,101 +68,62 @@ const payer = new Account( ), ); console.log(`Payer: ${payer.publicKey.toBase58()}`); - -const connection = new Connection( - process.env.ENDPOINT_URL || config.cluster_urls[cluster], - 'processed' as Commitment, -); +const rpcEndpoint = process.env.ENDPOINT_URL || config.cluster_urls[cluster]; +const connection = new Connection(rpcEndpoint, 'processed' as Commitment); const client = new MangoClient(connection, mangoProgramId); let mangoSubscriptionId = -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() { if (!groupIds) { throw new Error(`Group ${groupName} not found`); } console.log(`Starting liquidator for ${groupName}...`); + console.log(`RPC Endpoint: ${rpcEndpoint}`); + const mangoGroup = await client.getMangoGroup(mangoGroupKey); let cache = await mangoGroup.loadCache(connection); let liqorMangoAccount: MangoAccount; - if (process.env.LIQOR_PK) { - liqorMangoAccount = await client.getMangoAccount( - 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(), + try { + if (process.env.LIQOR_PK) { + liqorMangoAccount = await client.getMangoAccount( + new PublicKey(process.env.LIQOR_PK), + mangoGroup.dexProgramId, ); - liqorMangoAccount = accounts[0]; + if (!liqorMangoAccount.owner.equals(payer.publicKey)) { + throw new Error('Account not owned by Keypair'); + } } 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()}`); + let mangoAccounts: MangoAccount[] = []; await refreshAccounts(mangoGroup, mangoAccounts); watchAccounts(groupIds.mangoProgramId, mangoGroup, mangoAccounts); + const perpMarkets = await Promise.all( groupIds.perpMarkets.map((perpMarket) => { return mangoGroup.loadPerpMarket( @@ -199,7 +160,7 @@ async function main() { const advancedOrders = await getMultipleAccounts(connection, allAOs); [cache, liqorMangoAccount] = await Promise.all([ mangoGroup.loadCache(connection), - liqorMangoAccount.reload(connection), + liqorMangoAccount.reload(connection, mangoGroup.dexProgramId), ]); mangoAccountsWithAOs.forEach((ma, i) => { @@ -211,7 +172,7 @@ async function main() { } else { [cache, liqorMangoAccount] = await Promise.all([ mangoGroup.loadCache(connection), - liqorMangoAccount.reload(connection), + liqorMangoAccount.reload(connection, mangoGroup.dexProgramId), ]); } @@ -227,11 +188,13 @@ async function main() { perpMarkets, mangoAccount, ); - } catch (err) { - console.error( - `Failed to execute trigger order for ${mangoAccountKeyString}`, - err, - ); + } catch (err: any) { + if (!err.message.contains('MangoErrorCode::InvalidParam')) { + console.error( + `Failed to execute trigger order for ${mangoAccountKeyString}`, + err, + ); + } } } @@ -250,13 +213,15 @@ async function main() { } const health = mangoAccount.getHealthRatio(mangoGroup, cache, 'Maint'); + const accountInfoString = mangoAccount.toPrettyString( + groupIds, + mangoGroup, + cache, + ); console.log( - `Sick account ${mangoAccountKeyString} health ratio: ${health.toString()}`, + `Sick account ${mangoAccountKeyString} health ratio: ${health.toString()}\n${accountInfoString}`, ); - notify( - `Sick account ${mangoAccountKeyString} health ratio: ${health.toString()}`, - ); - console.log(mangoAccount.toPrettyString(groupIds, mangoGroup, cache)); + notify(`Sick account\n${accountInfoString}`); try { await liquidateAccount( mangoGroup, @@ -341,14 +306,12 @@ function watchAccounts( MangoAccountLayout.decode(accountInfo.data), ); if (index == -1) { - //console.log('New Account'); mangoAccounts.push(mangoAccount); } else { const spotOpenOrdersAccounts = mangoAccounts[index].spotOpenOrdersAccounts; mangoAccount.spotOpenOrdersAccounts = spotOpenOrdersAccounts; mangoAccounts[index] = mangoAccount; - //console.log('Updated account ' + accountId.toBase58()); } }, 'singleGossip', @@ -381,7 +344,6 @@ function watchAccounts( ); mangoAccounts[ownerIndex].spotOpenOrdersAccounts[openOrdersIndex] = openOrders; - //console.log('Updated OpenOrders for account ' + mangoAccounts[ownerIndex].publicKey.toBase58()); } else { 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( mangoGroup: MangoGroup, cache: MangoCache, @@ -552,6 +559,7 @@ async function liquidateAccount( liqee.beingLiquidated ) { // Send a ForceCancelPerp to reset the being_liquidated flag + console.log('forceCancelAllPerpOrdersInMarket'); await client.forceCancelAllPerpOrdersInMarket( mangoGroup, liqee, @@ -639,9 +647,11 @@ async function liquidateSpot( `Liquidating max ${maxLiabTransfer.toString()}/${liqee.getNativeBorrow( liabRootBank, 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) { const highestHealthMarket = perpMarkets .map((perpMarket, i) => { @@ -673,7 +683,7 @@ async function liquidateSpot( ); } - console.log('liquidateTokenAndPerp ' + highestHealthMarket.marketIndex); + console.log('liquidateTokenAndPerp', highestHealthMarket.marketIndex); await client.liquidateTokenAndPerp( mangoGroup, liqee, @@ -756,11 +766,6 @@ async function liquidatePerps( const marketIndex = lowestHealthMarket.marketIndex; const perpAccount = liqee.perpAccounts[marketIndex]; const perpMarket = perpMarkets[lowestHealthMarket.i]; - // const baseRootBank = rootBanks[marketIndex]; - // - // if (!baseRootBank) { - // throw new Error(`Base root bank not found for ${marketIndex}`); - // } if (!perpMarket) { throw new Error(`Perp market not found for ${marketIndex}`); @@ -805,9 +810,7 @@ async function liquidatePerps( if (perpAccount.basePosition.isZero()) { if (assetRootBank) { // we know that since sum of perp healths is negative, lowest perp market must be negative - console.log('liquidateTokenAndPerp ' + marketIndex); - // maxLiabTransfer - let maxLiabTransfer = liqorInitHealth; + console.log('liquidateTokenAndPerp', marketIndex); if (maxNetIndex !== QUOTE_INDEX) { maxLiabTransfer = liqorInitHealth.div( ONE_I80F48.sub(mangoGroup.spotMarkets[maxNetIndex].initAssetWeight), @@ -827,7 +830,7 @@ async function liquidatePerps( ); } } else { - console.log('liquidatePerpMarket ' + marketIndex); + console.log('liquidatePerpMarket', marketIndex); // technically can be higher because of liquidation fee, but // 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); 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); 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 console.log( - side + - 'ing ' + - basePositionSize + - ' of perp ' + - i + - ' for $' + - orderPrice, + `${side}ing ${basePositionSize} of ${groupIds?.perpMarkets[i].baseSymbol}-PERP for $${orderPrice}`, ); + await client.placePerpOrder( mangoGroup, mangoAccount, @@ -1187,4 +1185,8 @@ function notify(content: string) { } } +process.on('unhandledException', (err, promise) => { + console.error(`Unhandled rejection (promise: ${promise} reason:${err})`); +}); + main(); From 9b2ea64d85fa3a8ed63bb47050d0ca64c4f3b680 Mon Sep 17 00:00:00 2001 From: Riordan Panayides Date: Thu, 2 Dec 2021 18:03:18 +0000 Subject: [PATCH 03/16] Add liab limit --- src/liquidator.ts | 34 +++++++++++++++++++++------------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/src/liquidator.ts b/src/liquidator.ts index f6c2fde..ad5cf02 100644 --- a/src/liquidator.ts +++ b/src/liquidator.ts @@ -42,6 +42,10 @@ const refreshWebsocketInterval = parseInt( const checkTriggers = process.env.CHECK_TRIGGERS ? process.env.CHECK_TRIGGERS === 'true' : true; +const liabLimit = I80F48.fromNumber( + Math.min(parseFloat(process.env.LIAB_LIMIT || '0.9'), 1), +); + const config = new Config(IDS); const cluster = (process.env.CLUSTER || 'mainnet') as Cluster; @@ -621,11 +625,13 @@ async function liquidateSpot( ? mangoGroup.spotMarkets[maxNetIndex].initAssetWeight : ONE_I80F48; - const maxLiabTransfer = liqorInitHealth.div( - mangoGroup - .getPriceNative(minNetIndex, cache) - .mul(liabInitLiabWeight.sub(assetInitAssetWeight).abs()), - ); + const maxLiabTransfer = liqorInitHealth + .div( + mangoGroup + .getPriceNative(minNetIndex, cache) + .mul(liabInitLiabWeight.sub(assetInitAssetWeight).abs()), + ) + .mul(liabLimit); if (liqee.isBankrupt) { console.log('Bankrupt account', liqee.publicKey.toBase58()); @@ -676,11 +682,11 @@ async function liquidateSpot( return b.perpHealth.sub(a.perpHealth).toNumber(); })[0]; - let maxLiabTransfer = liqorInitHealth; + let maxLiabTransfer = liqorInitHealth.mul(liabLimit); if (maxNetIndex !== QUOTE_INDEX) { maxLiabTransfer = liqorInitHealth.div( ONE_I80F48.sub(assetInitAssetWeight), - ); + ).mul(liabLimit); } console.log('liquidateTokenAndPerp', highestHealthMarket.marketIndex); @@ -694,7 +700,7 @@ async function liquidateSpot( highestHealthMarket.marketIndex, AssetType.Token, minNetIndex, - liqee.perpAccounts[highestHealthMarket.marketIndex].quotePosition, + maxLiabTransfer, ); } else { await client.liquidateTokenAndToken( @@ -771,8 +777,9 @@ async function liquidatePerps( throw new Error(`Perp market not found for ${marketIndex}`); } - if (liqee.isBankrupt) { - const maxLiabTransfer = perpAccount.quotePosition.abs(); + const liqorInitHealth = liqor.getHealth(mangoGroup, cache, 'Init'); + let maxLiabTransfer = liqorInitHealth.mul(liabLimit); + if (liqee.isBankrupt) { const quoteRootBank = rootBanks[QUOTE_INDEX]; if (quoteRootBank) { // don't do anything it if quote position is zero @@ -814,7 +821,7 @@ async function liquidatePerps( if (maxNetIndex !== QUOTE_INDEX) { maxLiabTransfer = liqorInitHealth.div( ONE_I80F48.sub(mangoGroup.spotMarkets[maxNetIndex].initAssetWeight), - ); + ).mul(liabLimit); } await client.liquidateTokenAndPerp( mangoGroup, @@ -846,6 +853,7 @@ async function liquidatePerps( .div(mangoGroup.getPriceNative(marketIndex, cache)) .div(I80F48.fromI64(perpMarketInfo.baseLotSize)) .floor() + .mul(liabLimit) .toNumber(), ); } else { @@ -855,6 +863,7 @@ async function liquidatePerps( .div(mangoGroup.getPriceNative(marketIndex, cache)) .div(I80F48.fromI64(perpMarketInfo.baseLotSize)) .floor() + .mul(liabLimit) .toNumber(), ).neg(); } @@ -869,10 +878,9 @@ async function liquidatePerps( ); } - await sleep(interval); await liqee.reload(connection, mangoGroup.dexProgramId); if (liqee.isBankrupt) { - const maxLiabTransfer = perpAccount.quotePosition.abs(); + const maxLiabTransfer = liqorInitHealth.mul(liabLimit); const quoteRootBank = rootBanks[QUOTE_INDEX]; if (quoteRootBank) { console.log('resolvePerpBankruptcy', maxLiabTransfer.toString()); From 62bdea83efd847c58d95725f6326f9edc5c9abaa Mon Sep 17 00:00:00 2001 From: Riordan Panayides Date: Thu, 2 Dec 2021 18:04:58 +0000 Subject: [PATCH 04/16] Add retry limit to forceCancelSpotOrders --- src/liquidator.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/liquidator.ts b/src/liquidator.ts index ad5cf02..e67268b 100644 --- a/src/liquidator.ts +++ b/src/liquidator.ts @@ -480,14 +480,14 @@ async function liquidateAccount( throw new Error('Account no longer liquidatable'); } - while (liqee.hasAnySpotOrders()) { + for (let r = 0; r < 5 && liqee.hasAnySpotOrders(); r++) { for (let i = 0; i < mangoGroup.spotMarkets.length; i++) { - const spotMarket = spotMarkets[i]; - const baseRootBank = rootBanks[i]; - const quoteRootBank = rootBanks[QUOTE_INDEX]; + if (liqee.inMarginBasket[i]) { + const spotMarket = spotMarkets[i]; + const baseRootBank = rootBanks[i]; + const quoteRootBank = rootBanks[QUOTE_INDEX]; - if (baseRootBank && quoteRootBank) { - if (liqee.inMarginBasket[i]) { + if (baseRootBank && quoteRootBank) { console.log('forceCancelOrders ', i); await client.forceCancelSpotOrders( mangoGroup, From a31167ae9ce7095f64f2399c66011d18bba6fb81 Mon Sep 17 00:00:00 2001 From: Riordan Panayides Date: Thu, 2 Dec 2021 18:12:46 +0000 Subject: [PATCH 05/16] Fix some bugs, tidy up --- src/liquidator.ts | 109 +++++++++++++++++++--------------------------- 1 file changed, 46 insertions(+), 63 deletions(-) diff --git a/src/liquidator.ts b/src/liquidator.ts index e67268b..d3e5d1f 100644 --- a/src/liquidator.ts +++ b/src/liquidator.ts @@ -473,11 +473,10 @@ async function liquidateAccount( ); }), ); - await sleep(interval * 2); - } - await liqee.reload(connection, mangoGroup.dexProgramId); - if (!liqee.isLiquidatable(mangoGroup, cache)) { - throw new Error('Account no longer liquidatable'); + await liqee.reload(connection, mangoGroup.dexProgramId); + if (!liqee.isLiquidatable(mangoGroup, cache)) { + throw new Error('Account no longer liquidatable'); + } } for (let r = 0; r < 5 && liqee.hasAnySpotOrders(); r++) { @@ -517,45 +516,30 @@ async function liquidateAccount( healthComponents.quote, 'Maint', ); - const initHealths = liqee.getHealthsFromComponents( - mangoGroup, - cache, - healthComponents.spot, - healthComponents.perps, - healthComponents.quote, - 'Init', - ); let shouldLiquidateSpot = false; for (let i = 0; i < mangoGroup.tokens.length; i++) { - shouldLiquidateSpot = liqee.getNet(cache.rootBankCache[i], i).isNeg(); + if (liqee.getNet(cache.rootBankCache[i], i).isNeg()) { + shouldLiquidateSpot = true; + } } - const shouldLiquidatePerps = - maintHealths.perp.lt(ZERO_I80F48) || - (initHealths.perp.lt(ZERO_I80F48) && liqee.beingLiquidated); if (shouldLiquidateSpot) { await liquidateSpot( mangoGroup, cache, - spotMarkets, perpMarkets, rootBanks, liqee, liqor, ); + await liqee.reload(connection, mangoGroup.dexProgramId); + if (!liqee.isLiquidatable(mangoGroup, cache)) { + return; + } } - if (shouldLiquidatePerps) { - await liquidatePerps( - mangoGroup, - cache, - perpMarkets, - rootBanks, - liqee, - liqor, - ); - } + await liquidatePerps(mangoGroup, cache, perpMarkets, rootBanks, liqee, liqor); if ( !shouldLiquidateSpot && @@ -577,7 +561,6 @@ async function liquidateAccount( async function liquidateSpot( mangoGroup: MangoGroup, cache: MangoCache, - spotMarkets: Market[], perpMarkets: PerpMarket[], rootBanks: (RootBank | undefined)[], liqee: MangoAccount, @@ -922,6 +905,40 @@ function getDiffsAndNet( return { diffs, netValues }; } +async function balanceAccount( + mangoGroup: MangoGroup, + mangoAccount: MangoAccount, + mangoCache: MangoCache, + spotMarkets: Market[], + perpMarkets: PerpMarket[], +) { + const { diffs, netValues } = getDiffsAndNet( + mangoGroup, + mangoAccount, + mangoCache, + ); + const tokensUnbalanced = netValues.some( + (nv) => Math.abs(diffs[nv[0]].toNumber()) > spotMarkets[nv[0]].minOrderSize, + ); + const positionsUnbalanced = perpMarkets.some((pm) => { + const index = mangoGroup.getPerpMarketIndex(pm.publicKey); + const perpAccount = mangoAccount.perpAccounts[index]; + const basePositionSize = Math.abs( + pm.baseLotsToNumber(perpAccount.basePosition), + ); + + return basePositionSize != 0 || perpAccount.quotePosition.gt(ZERO_I80F48); + }); + + if (tokensUnbalanced) { + await balanceTokens(mangoGroup, mangoAccount, spotMarkets); + } + + if (positionsUnbalanced) { + await closePositions(mangoGroup, mangoAccount, perpMarkets); + } +} + async function balanceTokens( mangoGroup: MangoGroup, mangoAccount: MangoAccount, @@ -1061,40 +1078,6 @@ async function balanceTokens( } } -async function balanceAccount( - mangoGroup: MangoGroup, - mangoAccount: MangoAccount, - mangoCache: MangoCache, - spotMarkets: Market[], - perpMarkets: PerpMarket[], -) { - const { diffs, netValues } = getDiffsAndNet( - mangoGroup, - mangoAccount, - mangoCache, - ); - const tokensUnbalanced = netValues.some( - (nv) => Math.abs(diffs[nv[0]].toNumber()) > spotMarkets[nv[0]].minOrderSize, - ); - const positionsUnbalanced = perpMarkets.some((pm) => { - const index = mangoGroup.getPerpMarketIndex(pm.publicKey); - const perpAccount = mangoAccount.perpAccounts[index]; - const basePositionSize = Math.abs( - pm.baseLotsToNumber(perpAccount.basePosition), - ); - - return basePositionSize != 0 || perpAccount.quotePosition.gt(ZERO_I80F48); - }); - - if (tokensUnbalanced) { - await balanceTokens(mangoGroup, mangoAccount, spotMarkets); - } - - if (positionsUnbalanced) { - await closePositions(mangoGroup, mangoAccount, perpMarkets); - } -} - async function closePositions( mangoGroup: MangoGroup, mangoAccount: MangoAccount, From e2c128feff8814d5ffd600a62077cb04a5d9e29d Mon Sep 17 00:00:00 2001 From: Riordan Panayides Date: Thu, 2 Dec 2021 18:13:19 +0000 Subject: [PATCH 06/16] Shuffle accounts array on load --- src/liquidator.ts | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/src/liquidator.ts b/src/liquidator.ts index d3e5d1f..0322901 100644 --- a/src/liquidator.ts +++ b/src/liquidator.ts @@ -383,11 +383,14 @@ async function refreshAccounts( try { console.log('Refreshing accounts...'); console.time('getAllMangoAccounts'); - mangoAccounts.splice(0, mangoAccounts.length, ...(await client.getAllMangoAccounts( - mangoGroup, - undefined, - true, - ))); + + mangoAccounts.splice( + 0, + mangoAccounts.length, + ...(await client.getAllMangoAccounts(mangoGroup, undefined, true)), + ); + shuffleArray(mangoAccounts); + console.timeEnd('getAllMangoAccounts'); console.log(`Fetched ${mangoAccounts.length} accounts`); } catch (err) { @@ -1166,6 +1169,13 @@ async function closePositions( } } +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]]; + } +} + function notify(content: string) { if (content && process.env.WEBHOOK_URL) { try { From 7a37705efb398d835491dfbb99de841026e78ee6 Mon Sep 17 00:00:00 2001 From: Riordan Panayides Date: Thu, 2 Dec 2021 18:14:26 +0000 Subject: [PATCH 07/16] Simplify token rebalancing, adjust price for liq fee --- src/liquidator.ts | 90 ++++++++++++++++------------------------------- 1 file changed, 31 insertions(+), 59 deletions(-) diff --git a/src/liquidator.ts b/src/liquidator.ts index 0322901..9379eff 100644 --- a/src/liquidator.ts +++ b/src/liquidator.ts @@ -1017,63 +1017,35 @@ async function balanceTokens( for (let i = 0; i < groupIds!.spotMarkets.length; i++) { const marketIndex = netValues[i][0]; const market = markets[marketIndex]; + const liquidationFee = mangoGroup.spotMarkets[marketIndex].liquidationFee; if (Math.abs(diffs[marketIndex].toNumber()) > market.minOrderSize) { - if (netValues[i][1].gt(ZERO_I80F48)) { - // sell to close - const price = mangoGroup - .getPrice(marketIndex, cache) - .mul(I80F48.fromNumber(0.95)); - console.log( - `Sell to close ${marketIndex} ${Math.abs( - diffs[marketIndex].toNumber(), - )} @ ${price.toString()}`, - ); - await client.placeSpotOrder( - mangoGroup, - mangoAccount, - mangoGroup.mangoCache, - markets[marketIndex], - payer, - 'sell', - price.toNumber(), - Math.abs(diffs[marketIndex].toNumber()), - 'limit', - ); - await client.settleFunds( - mangoGroup, - mangoAccount, - payer, - markets[marketIndex], - ); - } else if (netValues[i][1].lt(ZERO_I80F48)) { - //buy to close - const price = mangoGroup - .getPrice(marketIndex, cache) - .mul(I80F48.fromNumber(1.05)); + const side = netValues[i][1].gt(ZERO_I80F48) ? 'sell' : 'buy'; + const price = mangoGroup + .getPrice(marketIndex, cache) + .mul(ONE_I80F48.sub(liquidationFee)) + .toNumber(); + const quantity = Math.abs(diffs[marketIndex].toNumber()); - console.log( - `Buy to close ${marketIndex} ${Math.abs( - diffs[marketIndex].toNumber(), - )} @ ${price.toString()}`, - ); - await client.placeSpotOrder( - mangoGroup, - mangoAccount, - mangoGroup.mangoCache, - markets[marketIndex], - payer, - 'buy', - price.toNumber(), - Math.abs(diffs[marketIndex].toNumber()), - 'limit', - ); - await client.settleFunds( - mangoGroup, - mangoAccount, - payer, - markets[marketIndex], - ); - } + console.log( + `${side}ing ${quantity} of ${groupIds?.spotMarkets[i].baseSymbol} for $${price}`, + ); + await client.placeSpotOrder( + mangoGroup, + mangoAccount, + mangoGroup.mangoCache, + markets[marketIndex], + payer, + side, + price, + Math.abs(diffs[marketIndex].toNumber()), + 'limit', + ); + await client.settleFunds( + mangoGroup, + mangoAccount, + payer, + markets[marketIndex], + ); } } } catch (err) { @@ -1119,11 +1091,11 @@ async function closePositions( if (basePositionSize != 0) { const side = perpAccount.basePosition.gt(ZERO_BN) ? 'sell' : 'buy'; - // const liquidationFee = - // mangoGroup.perpMarkets[index].liquidationFee.toNumber(); - + const liquidationFee = mangoGroup.perpMarkets[index].liquidationFee; const orderPrice = - side == 'sell' ? price.toNumber() * 0.95 : price.toNumber() * 1.05; // TODO: base this on liquidation fee + side == 'sell' + ? price.mul(ONE_I80F48.sub(liquidationFee)).toNumber() + : price.mul(ONE_I80F48.add(liquidationFee)).toNumber(); console.log( `${side}ing ${basePositionSize} of ${groupIds?.perpMarkets[i].baseSymbol}-PERP for $${orderPrice}`, From fd98dfc3578f2fa9de6a48c8d6ad6da0827c1dd1 Mon Sep 17 00:00:00 2001 From: Riordan Panayides Date: Thu, 2 Dec 2021 18:16:36 +0000 Subject: [PATCH 08/16] Update readme --- .eslintrc.json | 40 ++++++++++++++++++++++++++++++++++++++++ README.md | 12 ++++++++---- 2 files changed, 48 insertions(+), 4 deletions(-) create mode 100644 .eslintrc.json diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 0000000..c1b9024 --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,40 @@ +{ + "parser": "@typescript-eslint/parser", + "plugins": ["@typescript-eslint"], + "extends": [ + "eslint:recommended", + "plugin:@typescript-eslint/recommended", + "prettier" + ], + "env": { + "es6": true, + "browser": true, + "jest": true, + "node": true + }, + "rules": { + "@typescript-eslint/explicit-module-boundary-types": 0, + "@typescript-eslint/ban-ts-comment": 0, + "@typescript-eslint/explicit-function-return-type": 0, + "@typescript-eslint/explicit-member-accessibility": 0, + "@typescript-eslint/indent": 0, + "@typescript-eslint/member-delimiter-style": 0, + "@typescript-eslint/no-empty-function": 0, + "@typescript-eslint/no-explicit-any": 0, + "@typescript-eslint/no-var-requires": 0, + "@typescript-eslint/no-use-before-define": 0, + "@typescript-eslint/no-unused-vars": [ + 2, + { + "argsIgnorePattern": "^_" + } + ] + // "no-console": [ + // 0, + // { + // "allow": ["warn", "error"] + // } + // ] + } + } + \ No newline at end of file diff --git a/README.md b/README.md index f2e2a41..126b38e 100644 --- a/README.md +++ b/README.md @@ -14,17 +14,18 @@ To run the liquidator you will need: | Variable | Default | Description | | -------- | ------- | ----------- | | `CLUSTER` | `mainnet` | The Solana cluster to use | -| `ENDPOINT_URL` | `https://solana-api.projectserum.com` | Your RPC node endpoint | +| `ENDPOINT_URL` | `https://mango.rpcpool.com/946ef7337da3f5b8d3e4a34e7f88` | Your RPC node endpoint | | `KEYPAIR` | `${HOME}/.config/solana/id.json` | The location of your wallet keypair | | `GROUP` | `mainnet.1` | Name of the group in ids.json to run the Liquidator against | | `TARGETS` | `0 0 0 0 0 0 0 0` | Space separated list of the amount of each asset to maintain when rebalancing | | `INTERVAL` | `3500` | Milliseconds to wait before checking for sick accounts | -| `INTERVAL_ACCOUNTS` | `120000` | Milliseconds to wait before reloading all Mango accounts | +| `INTERVAL_ACCOUNTS` | `600000` | Milliseconds to wait before reloading all Mango accounts | | `INTERVAL_WEBSOCKET` | `300000` | Milliseconds to wait before reconnecting to the websocket | -| `LIQOR_PK` | N/A | Liqor Mango account Public Key, by default uses the largest value account owned by the keypair | +| `LIQOR_PK` | N/A | Liqor Mango Account Public Key, by default uses the largest value account owned by the keypair | | `WEBHOOK_URL` | N/A | Discord webhook URL to post liquidation events and errors to | +| `LIAB_LIMIT` | `0.9` | Percentage of your available margin to use when taking on liabs | -You can add these varibles to a `.env` file in the project root to load automatically on liquidator startup. For example: +You can add these variables to a `.env` file in the project root to load automatically on liquidator startup. For example: ```bash ENDPOINT_URL=https://solana-api.projectserum.com KEYPAIR=${HOME}/.config/solana/my-keypair.json @@ -33,6 +34,9 @@ TARGETS=500 0.1 0.75 0 0 0 0 0 ## Rebalancing The liquidator will attempt to close all perp positions, and balance the tokens in the liqor account after each liquidation. By default it will sell all token assets into USDC. You can choose to maintain a certain amount of each asset through this process by editing the value in the `TARGETS` environment variable at the position of the asset. You can find the order of the assets in the 'oracles' property of the group in [ids.json](https://github.com/blockworks-foundation/mango-client-v3/blob/main/src/ids.json#L81) The program will attempt to make buy/sell orders during balancing to maintain this level. +## Advanced Orders Triggering +The liquidator triggers advanced orders for users when their trigger condition is met. Upon successfully triggering the order, the liquidator wallet will receive 100x the transaction fee as a reward. + ## Run ``` yarn liquidator From 604cc420bc64dd1ef7a2552830aed1101bb7773f Mon Sep 17 00:00:00 2001 From: Riordan Panayides Date: Sun, 5 Dec 2021 16:04:03 +0000 Subject: [PATCH 09/16] Fix liquidateSpot bug --- src/liquidator.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/liquidator.ts b/src/liquidator.ts index b6757d0..75da15b 100644 --- a/src/liquidator.ts +++ b/src/liquidator.ts @@ -645,7 +645,7 @@ async function liquidateSpot( }`, ); - if (maxNet.lt(ONE_I80F48) || maxNetIndex == -1) { + if (maxNet.lt(ZERO_I80F48) || maxNetIndex == -1) { const highestHealthMarket = perpMarkets .map((perpMarket, i) => { const marketIndex = mangoGroup.getPerpMarketIndex( From 4071ce5f5f3f408f256c3952fbe69a610908492b Mon Sep 17 00:00:00 2001 From: Riordan Panayides Date: Sun, 5 Dec 2021 19:19:11 +0000 Subject: [PATCH 10/16] Add rebalancing interval --- src/liquidator.ts | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/src/liquidator.ts b/src/liquidator.ts index 75da15b..3417113 100644 --- a/src/liquidator.ts +++ b/src/liquidator.ts @@ -39,12 +39,16 @@ const refreshAccountsInterval = parseInt( const refreshWebsocketInterval = parseInt( process.env.INTERVAL_WEBSOCKET || '300000', ); +const rebalanceInterval = parseInt( + process.env.INTERVAL_WEBSOCKET || '10000', +); const checkTriggers = process.env.CHECK_TRIGGERS ? process.env.CHECK_TRIGGERS === 'true' : true; const liabLimit = I80F48.fromNumber( Math.min(parseFloat(process.env.LIAB_LIMIT || '0.9'), 1), ); +let lastRebalance = Date.now() const config = new Config(IDS); @@ -636,15 +640,6 @@ async function liquidateSpot( await liqee.reload(connection, mangoGroup.dexProgramId); } } else { - console.log( - `Liquidating max ${maxLiabTransfer.toString()}/${liqee.getNativeBorrow( - liabRootBank, - minNetIndex, - )} of liab ${groupIds?.tokens[minNetIndex].symbol} for asset ${ - groupIds?.tokens[maxNetIndex].symbol - }`, - ); - if (maxNet.lt(ZERO_I80F48) || maxNetIndex == -1) { const highestHealthMarket = perpMarkets .map((perpMarket, i) => { @@ -690,6 +685,7 @@ async function liquidateSpot( maxLiabTransfer, ); } else { + console.log('liquidateTokenAndToken', maxNetIndex, minNetIndex); await client.liquidateTokenAndToken( mangoGroup, liqee, @@ -916,6 +912,10 @@ async function balanceAccount( spotMarkets: Market[], perpMarkets: PerpMarket[], ) { + if (Date.now() < lastRebalance + rebalanceInterval) { + return; + } + const { diffs, netValues } = getDiffsAndNet( mangoGroup, mangoAccount, @@ -941,6 +941,8 @@ async function balanceAccount( if (positionsUnbalanced) { await closePositions(mangoGroup, mangoAccount, perpMarkets); } + + lastRebalance = Date.now(); } async function balanceTokens( @@ -1028,7 +1030,7 @@ async function balanceTokens( const quantity = Math.abs(diffs[marketIndex].toNumber()); console.log( - `${side}ing ${quantity} of ${groupIds?.spotMarkets[i].baseSymbol} for $${price}`, + `${side}ing ${quantity} of ${groupIds?.spotMarkets[marketIndex].baseSymbol} for $${price}`, ONE_I80F48.sub(liquidationFee) ); await client.placeSpotOrder( mangoGroup, @@ -1099,7 +1101,7 @@ async function closePositions( : price.mul(ONE_I80F48.add(liquidationFee)).toNumber(); console.log( - `${side}ing ${basePositionSize} of ${groupIds?.perpMarkets[i].baseSymbol}-PERP for $${orderPrice}`, + `${side}ing ${basePositionSize} of ${groupIds?.perpMarkets[index].baseSymbol}-PERP for $${orderPrice}`, ); await client.placePerpOrder( @@ -1159,7 +1161,7 @@ function notify(content: string) { } } -process.on('unhandledException', (err, promise) => { +process.on('unhandledRejection', (err, promise) => { console.error(`Unhandled rejection (promise: ${promise} reason:${err})`); }); From a9d1f7cc60f42268880928ef92903d3a136dcf7d Mon Sep 17 00:00:00 2001 From: Riordan Panayides Date: Sun, 5 Dec 2021 19:19:50 +0000 Subject: [PATCH 11/16] try reloading open orders on account update --- src/liquidator.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/liquidator.ts b/src/liquidator.ts index 3417113..9895c8b 100644 --- a/src/liquidator.ts +++ b/src/liquidator.ts @@ -304,7 +304,7 @@ function watchAccounts( mangoSubscriptionId = connection.onProgramAccountChange( mangoProgramId, - ({ accountId, accountInfo }) => { + async ({ accountId, accountInfo }) => { const index = mangoAccounts.findIndex((account) => account.publicKey.equals(accountId), ); @@ -320,6 +320,7 @@ function watchAccounts( mangoAccounts[index].spotOpenOrdersAccounts; mangoAccount.spotOpenOrdersAccounts = spotOpenOrdersAccounts; mangoAccounts[index] = mangoAccount; + await mangoAccount.loadOpenOrders(connection, mangoGroup.dexProgramId) } }, 'processed', From d99d987b6e35d817f3a6fffab883eeeb8f5d8cff Mon Sep 17 00:00:00 2001 From: Riordan Panayides Date: Sun, 5 Dec 2021 19:22:32 +0000 Subject: [PATCH 12/16] Upgrade client --- yarn.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/yarn.lock b/yarn.lock index b8216f8..fdcd0d6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -31,8 +31,8 @@ regenerator-runtime "^0.13.4" "@blockworks-foundation/mango-client@git+https://github.com/blockworks-foundation/mango-client-v3.git": - version "3.2.9" - resolved "git+https://github.com/blockworks-foundation/mango-client-v3.git#a24f41584cff5a7548e3f9aafb3357036c55317c" + version "3.2.14" + resolved "git+https://github.com/blockworks-foundation/mango-client-v3.git#7fb0f294a6c7cec98348cefbbfc3a725bc10c232" dependencies: "@project-serum/anchor" "^0.16.2" "@project-serum/serum" "0.13.55" From af1df8ddaf94fab2009234e87a21fe29fffa7510 Mon Sep 17 00:00:00 2001 From: Riordan Panayides Date: Sun, 5 Dec 2021 23:07:12 +0000 Subject: [PATCH 13/16] add crank instruction to perp order --- src/liquidator.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/liquidator.ts b/src/liquidator.ts index 9895c8b..7022868 100644 --- a/src/liquidator.ts +++ b/src/liquidator.ts @@ -1100,6 +1100,10 @@ async function closePositions( side == 'sell' ? price.mul(ONE_I80F48.sub(liquidationFee)).toNumber() : price.mul(ONE_I80F48.add(liquidationFee)).toNumber(); + const bookSideInfo = + side == 'sell' + ? await connection.getAccountInfo(perpMarket.bids) + : await connection.getAccountInfo(perpMarket.asks); console.log( `${side}ing ${basePositionSize} of ${groupIds?.perpMarkets[index].baseSymbol}-PERP for $${orderPrice}`, @@ -1115,8 +1119,8 @@ async function closePositions( orderPrice, basePositionSize, 'ioc', - undefined, - undefined, + 0, + bookSideInfo ? bookSideInfo : undefined, true, ); } From 8604faa152b0de0719bda6d1a2dfce89a812f8bf Mon Sep 17 00:00:00 2001 From: Riordan Panayides Date: Mon, 6 Dec 2021 00:43:29 +0000 Subject: [PATCH 14/16] Improve error logging, add rebalance interval --- src/liquidator.ts | 67 ++++++++++++++++++++++++++++------------------- 1 file changed, 40 insertions(+), 27 deletions(-) diff --git a/src/liquidator.ts b/src/liquidator.ts index 7022868..14bed91 100644 --- a/src/liquidator.ts +++ b/src/liquidator.ts @@ -39,16 +39,14 @@ const refreshAccountsInterval = parseInt( const refreshWebsocketInterval = parseInt( process.env.INTERVAL_WEBSOCKET || '300000', ); -const rebalanceInterval = parseInt( - process.env.INTERVAL_WEBSOCKET || '10000', -); +const rebalanceInterval = parseInt(process.env.INTERVAL_WEBSOCKET || '10000'); const checkTriggers = process.env.CHECK_TRIGGERS ? process.env.CHECK_TRIGGERS === 'true' : true; const liabLimit = I80F48.fromNumber( Math.min(parseFloat(process.env.LIAB_LIMIT || '0.9'), 1), ); -let lastRebalance = Date.now() +let lastRebalance = Date.now(); const config = new Config(IDS); @@ -122,8 +120,8 @@ async function main() { } } } catch (err: any) { - console.error(err); - throw new Error(`Error loading liqor Mango Account: ${err.message}`); + console.error(`Error loading liqor Mango Account: ${err}`); + return; } console.log(`Liqor Public Key: ${liqorMangoAccount.publicKey.toBase58()}`); @@ -197,10 +195,19 @@ async function main() { mangoAccount, ); } catch (err: any) { - if (!err.message.contains('MangoErrorCode::InvalidParam')) { + if (err.message.includes('MangoErrorCode::InvalidParam')) { console.error( - `Failed to execute trigger order for ${mangoAccountKeyString}`, - err, + 'Failed to execute trigger order, order already executed', + ); + } else if ( + err.message.includes('MangoErrorCode::TriggerConditionFalse') + ) { + console.error( + 'Failed to execute trigger order, trigger condition was false', + ); + } else { + console.error( + `Failed to execute trigger order for ${mangoAccountKeyString}: ${err}`, ); } } @@ -243,11 +250,9 @@ async function main() { console.log('Liquidated account', mangoAccountKeyString); notify(`Liquidated account ${mangoAccountKeyString}`); - } catch (err) { + } catch (err: any) { console.error( - 'Failed to liquidate account', - mangoAccountKeyString, - err, + `Failed to liquidate account ${mangoAccountKeyString}: ${err}`, ); notify( `Failed to liquidate account ${mangoAccountKeyString}: ${err}`, @@ -320,7 +325,10 @@ function watchAccounts( mangoAccounts[index].spotOpenOrdersAccounts; mangoAccount.spotOpenOrdersAccounts = spotOpenOrdersAccounts; mangoAccounts[index] = mangoAccount; - await mangoAccount.loadOpenOrders(connection, mangoGroup.dexProgramId) + await mangoAccount.loadOpenOrders( + connection, + mangoGroup.dexProgramId, + ); } }, 'processed', @@ -398,8 +406,8 @@ async function refreshAccounts( console.timeEnd('getAllMangoAccounts'); console.log(`Fetched ${mangoAccounts.length} accounts`); - } catch (err) { - console.error('Error reloading accounts', err); + } catch (err: any) { + console.error(`Error reloading accounts: ${err}`); } finally { setTimeout( refreshAccounts, @@ -413,7 +421,7 @@ async function refreshAccounts( /** * Process trigger orders for one mango account */ - async function processTriggerOrders( +async function processTriggerOrders( mangoGroup: MangoGroup, cache: MangoCache, perpMarkets: PerpMarket[], @@ -667,9 +675,9 @@ async function liquidateSpot( let maxLiabTransfer = liqorInitHealth.mul(liabLimit); if (maxNetIndex !== QUOTE_INDEX) { - maxLiabTransfer = liqorInitHealth.div( - ONE_I80F48.sub(assetInitAssetWeight), - ).mul(liabLimit); + maxLiabTransfer = liqorInitHealth + .div(ONE_I80F48.sub(assetInitAssetWeight)) + .mul(liabLimit); } console.log('liquidateTokenAndPerp', highestHealthMarket.marketIndex); @@ -763,7 +771,7 @@ async function liquidatePerps( const liqorInitHealth = liqor.getHealth(mangoGroup, cache, 'Init'); let maxLiabTransfer = liqorInitHealth.mul(liabLimit); - if (liqee.isBankrupt) { + if (liqee.isBankrupt) { const quoteRootBank = rootBanks[QUOTE_INDEX]; if (quoteRootBank) { // don't do anything it if quote position is zero @@ -803,9 +811,13 @@ async function liquidatePerps( // we know that since sum of perp healths is negative, lowest perp market must be negative console.log('liquidateTokenAndPerp', marketIndex); if (maxNetIndex !== QUOTE_INDEX) { - maxLiabTransfer = liqorInitHealth.div( - ONE_I80F48.sub(mangoGroup.spotMarkets[maxNetIndex].initAssetWeight), - ).mul(liabLimit); + maxLiabTransfer = liqorInitHealth + .div( + ONE_I80F48.sub( + mangoGroup.spotMarkets[maxNetIndex].initAssetWeight, + ), + ) + .mul(liabLimit); } await client.liquidateTokenAndPerp( mangoGroup, @@ -1031,7 +1043,8 @@ async function balanceTokens( const quantity = Math.abs(diffs[marketIndex].toNumber()); console.log( - `${side}ing ${quantity} of ${groupIds?.spotMarkets[marketIndex].baseSymbol} for $${price}`, ONE_I80F48.sub(liquidationFee) + `${side}ing ${quantity} of ${groupIds?.spotMarkets[marketIndex].baseSymbol} for $${price}`, + ONE_I80F48.sub(liquidationFee).toString(), ); await client.placeSpotOrder( mangoGroup, @@ -1166,8 +1179,8 @@ function notify(content: string) { } } -process.on('unhandledRejection', (err, promise) => { - console.error(`Unhandled rejection (promise: ${promise} reason:${err})`); +process.on('unhandledRejection', (err) => { + console.error(`Unhandled rejection: ${err})`); }); main(); From 82197ee1d5c3615d8315afbce2c72c01881e22d3 Mon Sep 17 00:00:00 2001 From: Riordan Panayides Date: Mon, 6 Dec 2021 00:55:49 +0000 Subject: [PATCH 15/16] Move ts-node to runtime dependencies, add INTERVAL_REBALANCE to readme --- README.md | 1 + package.json | 4 ++-- src/liquidator.ts | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 126b38e..3c674dc 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,7 @@ To run the liquidator you will need: | `INTERVAL` | `3500` | Milliseconds to wait before checking for sick accounts | | `INTERVAL_ACCOUNTS` | `600000` | Milliseconds to wait before reloading all Mango accounts | | `INTERVAL_WEBSOCKET` | `300000` | Milliseconds to wait before reconnecting to the websocket | +| `INTERVAL_REBALANCE` | `10000` | Milliseconds to wait before reconnecting to the websocket | | `LIQOR_PK` | N/A | Liqor Mango Account Public Key, by default uses the largest value account owned by the keypair | | `WEBHOOK_URL` | N/A | Discord webhook URL to post liquidation events and errors to | | `LIAB_LIMIT` | `0.9` | Percentage of your available margin to use when taking on liabs | diff --git a/package.json b/package.json index 26a8145..fe91009 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,6 @@ "eslint-config-prettier": "^7.2.0", "mocha": "^8.4.0", "prettier": "^2.0.5", - "ts-node": "^9.1.1", "typedoc": "^0.22.5", "typescript": "^4.1.3" }, @@ -49,6 +48,7 @@ "bn.js": "^5.2.0", "buffer-layout": "^1.2.1", "dotenv": "^10.0.0", - "dotenv-expand": "^5.1.0" + "dotenv-expand": "^5.1.0", + "ts-node": "^9.1.1" } } diff --git a/src/liquidator.ts b/src/liquidator.ts index 14bed91..9dfc64d 100644 --- a/src/liquidator.ts +++ b/src/liquidator.ts @@ -39,7 +39,7 @@ const refreshAccountsInterval = parseInt( const refreshWebsocketInterval = parseInt( process.env.INTERVAL_WEBSOCKET || '300000', ); -const rebalanceInterval = parseInt(process.env.INTERVAL_WEBSOCKET || '10000'); +const rebalanceInterval = parseInt(process.env.INTERVAL_REBALANCE || '10000'); const checkTriggers = process.env.CHECK_TRIGGERS ? process.env.CHECK_TRIGGERS === 'true' : true; From bddffb59ea34fd697a14869b0eb988a35d2abcb7 Mon Sep 17 00:00:00 2001 From: Riordan Panayides Date: Mon, 6 Dec 2021 14:37:20 +0000 Subject: [PATCH 16/16] Fix logging --- src/liquidator.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/liquidator.ts b/src/liquidator.ts index 9dfc64d..82a00ac 100644 --- a/src/liquidator.ts +++ b/src/liquidator.ts @@ -329,6 +329,7 @@ function watchAccounts( connection, mangoGroup.dexProgramId, ); + console.log('updated account', mangoAccount.publicKey.toBase58()) } }, 'processed', @@ -1119,7 +1120,7 @@ async function closePositions( : await connection.getAccountInfo(perpMarket.asks); console.log( - `${side}ing ${basePositionSize} of ${groupIds?.perpMarkets[index].baseSymbol}-PERP for $${orderPrice}`, + `${side}ing ${basePositionSize} of ${groupIds?.perpMarkets[i].baseSymbol}-PERP for $${orderPrice}`, ); await client.placePerpOrder(