Mc/perp liq price 2 (#625)
* perp position liquidation price calculator Signed-off-by: microwavedcola1 <microwavedcola@gmail.com> * refactor Signed-off-by: microwavedcola1 <microwavedcola@gmail.com> * ui method Signed-off-by: microwavedcola1 <microwavedcola@gmail.com> * Fixes from review Signed-off-by: microwavedcola1 <microwavedcola@gmail.com> --------- Signed-off-by: microwavedcola1 <microwavedcola@gmail.com>
This commit is contained in:
parent
18e39bc197
commit
0e180ed380
|
@ -1,23 +1,20 @@
|
|||
import { AnchorProvider, Wallet } from '@coral-xyz/anchor';
|
||||
import { Cluster, Connection, Keypair, PublicKey } from '@solana/web3.js';
|
||||
import fs from 'fs';
|
||||
import cloneDeep from 'lodash/cloneDeep';
|
||||
import { Group } from '../../src/accounts/group';
|
||||
import { HealthCache } from '../../src/accounts/healthCache';
|
||||
import { HealthType, MangoAccount } from '../../src/accounts/mangoAccount';
|
||||
import { PerpMarket } from '../../src/accounts/perp';
|
||||
import { Serum3Market } from '../../src/accounts/serum3';
|
||||
import { MangoClient } from '../../src/client';
|
||||
import { MANGO_V4_ID } from '../../src/constants';
|
||||
import { ZERO_I80F48 } from '../../src/numbers/I80F48';
|
||||
import { toUiDecimalsForQuote } from '../../src/utils';
|
||||
|
||||
const CLUSTER_URL =
|
||||
process.env.CLUSTER_URL_OVERRIDE || process.env.MB_CLUSTER_URL;
|
||||
const PAYER_KEYPAIR =
|
||||
process.env.PAYER_KEYPAIR_OVERRIDE || process.env.MB_PAYER_KEYPAIR;
|
||||
const USER_KEYPAIR =
|
||||
process.env.USER_KEYPAIR_OVERRIDE || process.env.MB_PAYER_KEYPAIR;
|
||||
const GROUP_NUM = Number(process.env.GROUP_NUM || 0);
|
||||
const MANGO_ACCOUNT_PK = process.env.MANGO_ACCOUNT_PK;
|
||||
const MANGO_ACCOUNT_PK = new PublicKey(
|
||||
process.env.MANGO_ACCOUNT_PK || PublicKey.default.toBase58(),
|
||||
);
|
||||
const CLUSTER: Cluster =
|
||||
(process.env.CLUSTER_OVERRIDE as Cluster) || 'mainnet-beta';
|
||||
|
||||
|
@ -222,57 +219,82 @@ async function debugUser(
|
|||
)) {
|
||||
getMaxForSerum3Wrapper(serum3Market);
|
||||
}
|
||||
|
||||
// Liquidation price for perp positions
|
||||
for (const pp of mangoAccount.perpActive()) {
|
||||
const pm = group.getPerpMarketByMarketIndex(pp.marketIndex);
|
||||
const health = toUiDecimalsForQuote(
|
||||
mangoAccount.getHealth(group, HealthType.maint),
|
||||
);
|
||||
|
||||
if (
|
||||
// pp.getNotionalValueUi(pm) > 1000 &&
|
||||
// !(pp.getNotionalValueUi(pm) < health && pp.getBasePosition(pm).isPos())
|
||||
// eslint-disable-next-line no-constant-condition
|
||||
true
|
||||
) {
|
||||
const lp = await pp.getLiquidationPrice(group, mangoAccount);
|
||||
if (lp.lt(ZERO_I80F48())) {
|
||||
continue;
|
||||
}
|
||||
const lpUi = group
|
||||
.getPerpMarketByMarketIndex(pp.marketIndex)
|
||||
.priceNativeToUi(lp.toNumber());
|
||||
|
||||
const gClone: Group = cloneDeep(group);
|
||||
gClone.getPerpMarketByMarketIndex(pm.perpMarketIndex)._price = lp;
|
||||
|
||||
const simHealth = toUiDecimalsForQuote(
|
||||
mangoAccount.getHealth(gClone, HealthType.maint),
|
||||
);
|
||||
|
||||
console.log(
|
||||
` - ${pm.name}, health: ${health.toLocaleString()}, side: ${
|
||||
pp.getBasePosition(pm).isPos() ? 'LONG' : 'SHORT'
|
||||
}, notional: ${pp
|
||||
.getNotionalValueUi(pm)
|
||||
.toLocaleString()}, liq price: ${lpUi.toLocaleString()}, sim health: ${simHealth.toLocaleString()}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function main(): Promise<void> {
|
||||
const options = AnchorProvider.defaultOptions();
|
||||
const connection = new Connection(CLUSTER_URL!, options);
|
||||
|
||||
const admin = Keypair.fromSecretKey(
|
||||
Buffer.from(JSON.parse(fs.readFileSync(PAYER_KEYPAIR!, 'utf-8'))),
|
||||
);
|
||||
console.log(`Admin ${admin.publicKey.toBase58()}`);
|
||||
|
||||
const adminWallet = new Wallet(admin);
|
||||
const adminProvider = new AnchorProvider(connection, adminWallet, options);
|
||||
const client = MangoClient.connect(
|
||||
adminProvider,
|
||||
CLUSTER,
|
||||
MANGO_V4_ID[CLUSTER],
|
||||
{
|
||||
const wallet = new Wallet(new Keypair());
|
||||
const provider = new AnchorProvider(connection, wallet, options);
|
||||
const client = MangoClient.connect(provider, CLUSTER, MANGO_V4_ID[CLUSTER], {
|
||||
idsSource: 'api',
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
const group = await client.getGroupForCreator(admin.publicKey, GROUP_NUM);
|
||||
|
||||
for (const keypair of [USER_KEYPAIR!]) {
|
||||
console.log();
|
||||
const user = Keypair.fromSecretKey(
|
||||
Buffer.from(JSON.parse(fs.readFileSync(keypair, 'utf-8'))),
|
||||
const group = await client.getGroup(
|
||||
new PublicKey('78b8f4cGCwmZ9ysPFMWLaLTkkaYnUjwMJYStWe5RTSSX'),
|
||||
);
|
||||
const userWallet = new Wallet(user);
|
||||
console.log(`User ${userWallet.publicKey.toBase58()}`);
|
||||
|
||||
const mangoAccounts = await client.getAllMangoAccounts(group, true);
|
||||
mangoAccounts.sort((a, b) => b.getEquity(group).cmp(a.getEquity(group)));
|
||||
|
||||
for (const mangoAccount of mangoAccounts) {
|
||||
if (
|
||||
!MANGO_ACCOUNT_PK ||
|
||||
mangoAccount.publicKey.equals(new PublicKey(MANGO_ACCOUNT_PK))
|
||||
true &&
|
||||
(MANGO_ACCOUNT_PK!.equals(PublicKey.default) ||
|
||||
// For specific account
|
||||
mangoAccount.publicKey.equals(new PublicKey(MANGO_ACCOUNT_PK!))) &&
|
||||
// Only interesting perp liq price candidates
|
||||
mangoAccount.perpActive().length > 0 &&
|
||||
mangoAccount
|
||||
.perpActive()
|
||||
.filter((pp) =>
|
||||
pp
|
||||
.getBasePosition(group.getPerpMarketByMarketIndex(pp.marketIndex))
|
||||
.gt(ZERO_I80F48()),
|
||||
).length > 0
|
||||
) {
|
||||
// console.log();
|
||||
console.log(
|
||||
`${mangoAccount.publicKey
|
||||
.toBase58()
|
||||
.padStart(48)}, health ${toUiDecimalsForQuote(
|
||||
mangoAccount.getHealth(group, HealthType.maint),
|
||||
).toFixed(2)}, ${toUiDecimalsForQuote(
|
||||
mangoAccount.getHealth(group, HealthType.init),
|
||||
).toFixed(2)}`,
|
||||
`account https://app.mango.markets/?address=${mangoAccount.publicKey}`,
|
||||
);
|
||||
// await debugUser(client, group, mangoAccount);
|
||||
}
|
||||
await debugUser(client, group, mangoAccount);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -654,7 +654,7 @@ export class HealthCache {
|
|||
const rightValue = fun(right);
|
||||
|
||||
// console.log(
|
||||
// ` - binaryApproximationSearch left ${left.toLocaleString()}, leftValue ${leftValue.toLocaleString()}, right ${right.toLocaleString()}, rightValue ${rightValue.toLocaleString()}, targetValue ${targetValue.toLocaleString()}`,
|
||||
// ` - binaryApproximationSearch left ${left.toLocaleString()}, leftValue ${leftValue.toLocaleString()}, right ${right.toLocaleString()}, rightValue ${rightValue.toLocaleString()}, targetValue ${targetValue.toLocaleString()}, minStep ${minStep}`,
|
||||
// );
|
||||
|
||||
if (
|
||||
|
@ -1159,6 +1159,52 @@ export class HealthCache {
|
|||
|
||||
return baseLots.floor();
|
||||
}
|
||||
|
||||
public getPerpPositionLiquidationPrice(
|
||||
group: Group,
|
||||
mangoAccount: MangoAccount,
|
||||
perpPosition: PerpPosition,
|
||||
): I80F48 | null {
|
||||
const hc = HealthCache.fromMangoAccount(group, mangoAccount);
|
||||
const perpMarket = group.getPerpMarketByMarketIndex(
|
||||
perpPosition.marketIndex,
|
||||
);
|
||||
|
||||
function healthAfterPriceChange(newPrice: I80F48): I80F48 {
|
||||
const gClone = cloneDeep(group);
|
||||
gClone.getPerpMarketByMarketIndex(perpMarket.perpMarketIndex)._price =
|
||||
newPrice;
|
||||
const hc = HealthCache.fromMangoAccount(gClone, mangoAccount);
|
||||
return hc.health(HealthType.maint);
|
||||
}
|
||||
|
||||
if (perpPosition.getBasePosition(perpMarket).isPos()) {
|
||||
const zero = ZERO_I80F48();
|
||||
const healthAtPriceZero = healthAfterPriceChange(zero);
|
||||
if (healthAtPriceZero.gt(ZERO_I80F48())) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return HealthCache.binaryApproximationSearch(
|
||||
zero,
|
||||
healthAtPriceZero,
|
||||
perpMarket.price,
|
||||
ZERO_I80F48(),
|
||||
perpMarket.priceLotsToNative(new BN(1)),
|
||||
healthAfterPriceChange,
|
||||
);
|
||||
}
|
||||
|
||||
const price1000x = perpMarket.price.mul(I80F48.fromNumber(1000));
|
||||
return HealthCache.binaryApproximationSearch(
|
||||
perpMarket.price,
|
||||
hc.health(HealthType.maint),
|
||||
price1000x,
|
||||
ZERO_I80F48(),
|
||||
perpMarket.priceLotsToNative(new BN(1)),
|
||||
healthAfterPriceChange,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export class Prices {
|
||||
|
|
|
@ -1432,6 +1432,29 @@ export class PerpPosition {
|
|||
);
|
||||
}
|
||||
|
||||
public getLiquidationPrice(
|
||||
group: Group,
|
||||
mangoAccount: MangoAccount,
|
||||
): I80F48 | null {
|
||||
if (this.basePositionLots.eq(new BN(0))) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return HealthCache.fromMangoAccount(
|
||||
group,
|
||||
mangoAccount,
|
||||
).getPerpPositionLiquidationPrice(group, mangoAccount, this);
|
||||
}
|
||||
|
||||
public getLiquidationPriceUi(
|
||||
group: Group,
|
||||
mangoAccount: MangoAccount,
|
||||
): number | null {
|
||||
const pm = group.getPerpMarketByMarketIndex(this.marketIndex);
|
||||
const lp = this.getLiquidationPrice(group, mangoAccount);
|
||||
return lp == null ? null : pm.priceNativeToUi(lp.toNumber());
|
||||
}
|
||||
|
||||
public getBreakEvenPrice(perpMarket: PerpMarket): I80F48 {
|
||||
if (perpMarket.perpMarketIndex !== this.marketIndex) {
|
||||
throw new Error("PerpPosition doesn't belong to the given market!");
|
||||
|
|
|
@ -436,6 +436,10 @@ export class PerpMarket {
|
|||
return toNative(uiQuote, QUOTE_DECIMALS).div(this.quoteLotSize);
|
||||
}
|
||||
|
||||
public priceLotsToNative(price: BN): I80F48 {
|
||||
return I80F48.fromI64(price.mul(this.quoteLotSize).div(this.baseLotSize));
|
||||
}
|
||||
|
||||
public priceLotsToUi(price: BN): number {
|
||||
return parseFloat(price.toString()) * this.priceLotsToUiConverter;
|
||||
}
|
||||
|
|
|
@ -232,6 +232,10 @@ export function ONE_I80F48(): I80F48 {
|
|||
return I80F48.fromNumber(1);
|
||||
}
|
||||
|
||||
export function MINUS_ONE_I80F48(): I80F48 {
|
||||
return I80F48.fromNumber(-1);
|
||||
}
|
||||
|
||||
export function ZERO_I80F48(): I80F48 {
|
||||
return I80F48.fromNumber(0);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue