ts: fix max serum bid and ask that can be placed by a mango account (#241)
* ts: fix getMaxQuoteForSerum3BidUi and getMaxBaseForSerum3AskUi where the zero amount was not tight enough for binary search 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
d86b3dd757
commit
4090cf407e
|
@ -177,18 +177,52 @@ export class HealthCache {
|
|||
return this.findTokenInfoIndex(bank.tokenIndex);
|
||||
}
|
||||
|
||||
findSerum3InfoIndex(marketIndex: number): number {
|
||||
return this.serum3Infos.findIndex(
|
||||
(serum3Info) => serum3Info.marketIndex === marketIndex,
|
||||
);
|
||||
}
|
||||
|
||||
getOrCreateSerum3InfoIndex(group: Group, serum3Market: Serum3Market): number {
|
||||
const index = this.findSerum3InfoIndex(serum3Market.marketIndex);
|
||||
const baseBank = group.getFirstBankByTokenIndex(
|
||||
serum3Market.baseTokenIndex,
|
||||
);
|
||||
const quoteBank = group.getFirstBankByTokenIndex(
|
||||
serum3Market.quoteTokenIndex,
|
||||
);
|
||||
const baseEntryIndex = this.getOrCreateTokenInfoIndex(baseBank);
|
||||
const quoteEntryIndex = this.getOrCreateTokenInfoIndex(quoteBank);
|
||||
if (index == -1) {
|
||||
this.serum3Infos.push(
|
||||
Serum3Info.emptyFromSerum3Market(
|
||||
serum3Market,
|
||||
baseEntryIndex,
|
||||
quoteEntryIndex,
|
||||
),
|
||||
);
|
||||
}
|
||||
return this.findSerum3InfoIndex(serum3Market.marketIndex);
|
||||
}
|
||||
|
||||
adjustSerum3Reserved(
|
||||
// todo change indices to types from numbers
|
||||
marketIndex: number,
|
||||
baseTokenIndex: number,
|
||||
group: Group,
|
||||
serum3Market: Serum3Market,
|
||||
reservedBaseChange: I80F48,
|
||||
freeBaseChange: I80F48,
|
||||
quoteTokenIndex: number,
|
||||
reservedQuoteChange: I80F48,
|
||||
freeQuoteChange: I80F48,
|
||||
) {
|
||||
const baseEntryIndex = this.findTokenInfoIndex(baseTokenIndex);
|
||||
const quoteEntryIndex = this.findTokenInfoIndex(quoteTokenIndex);
|
||||
const baseBank = group.getFirstBankByTokenIndex(
|
||||
serum3Market.baseTokenIndex,
|
||||
);
|
||||
const quoteBank = group.getFirstBankByTokenIndex(
|
||||
serum3Market.quoteTokenIndex,
|
||||
);
|
||||
|
||||
const baseEntryIndex = this.getOrCreateTokenInfoIndex(baseBank);
|
||||
const quoteEntryIndex = this.getOrCreateTokenInfoIndex(quoteBank);
|
||||
|
||||
const baseEntry = this.tokenInfos[baseEntryIndex];
|
||||
const reservedAmount = reservedBaseChange.mul(baseEntry.oraclePrice);
|
||||
|
@ -203,14 +237,8 @@ export class HealthCache {
|
|||
quoteEntry.balance.iadd(freeQuoteChange.mul(quoteEntry.oraclePrice));
|
||||
|
||||
// Apply it to the serum3 info
|
||||
const serum3Info = this.serum3Infos.find(
|
||||
(serum3Info) => serum3Info.marketIndex === marketIndex,
|
||||
);
|
||||
if (!serum3Info) {
|
||||
throw new Error(
|
||||
`Serum3Info not found for market with index ${marketIndex}`,
|
||||
);
|
||||
}
|
||||
const index = this.getOrCreateSerum3InfoIndex(group, serum3Market);
|
||||
const serum3Info = this.serum3Infos[index];
|
||||
serum3Info.reserved = serum3Info.reserved.add(reservedAmount);
|
||||
}
|
||||
|
||||
|
@ -288,11 +316,10 @@ export class HealthCache {
|
|||
|
||||
// Increase reserved in Serum3Info for quote
|
||||
adjustedCache.adjustSerum3Reserved(
|
||||
serum3Market.marketIndex,
|
||||
serum3Market.baseTokenIndex,
|
||||
group,
|
||||
serum3Market,
|
||||
ZERO_I80F48(),
|
||||
ZERO_I80F48(),
|
||||
serum3Market.quoteTokenIndex,
|
||||
bidNativeQuoteAmount,
|
||||
ZERO_I80F48(),
|
||||
);
|
||||
|
@ -325,11 +352,10 @@ export class HealthCache {
|
|||
|
||||
// Increase reserved in Serum3Info for base
|
||||
adjustedCache.adjustSerum3Reserved(
|
||||
serum3Market.marketIndex,
|
||||
serum3Market.baseTokenIndex,
|
||||
group,
|
||||
serum3Market,
|
||||
askNativeBaseAmount,
|
||||
ZERO_I80F48(),
|
||||
serum3Market.quoteTokenIndex,
|
||||
ZERO_I80F48(),
|
||||
ZERO_I80F48(),
|
||||
);
|
||||
|
@ -558,7 +584,8 @@ export class HealthCache {
|
|||
}
|
||||
|
||||
// Amount which would bring health to 0
|
||||
// amount = max(A_deposits, B_borrows) + init_health / (A_liab_weight - B_asset_weight)
|
||||
// where M = max(A_deposits, B_borrows)
|
||||
// amount = M + (init_health + M * (B_init_liab - A_init_asset)) / (A_init_liab - B_init_asset);
|
||||
// A is what we would be essentially swapping for B
|
||||
// So when its an ask, then base->quote,
|
||||
// and when its a bid, then quote->bid
|
||||
|
@ -567,30 +594,34 @@ export class HealthCache {
|
|||
const quoteBorrows = quote.balance.lt(ZERO_I80F48())
|
||||
? quote.balance.abs()
|
||||
: ZERO_I80F48();
|
||||
zeroAmount = base.balance
|
||||
.max(quoteBorrows)
|
||||
.add(
|
||||
initialHealth.div(
|
||||
const max = base.balance.max(quoteBorrows);
|
||||
zeroAmount = max.add(
|
||||
initialHealth
|
||||
.add(max.mul(quote.initLiabWeight.sub(base.initAssetWeight)))
|
||||
.div(
|
||||
base
|
||||
.liabWeight(HealthType.init)
|
||||
.sub(quote.assetWeight(HealthType.init)),
|
||||
),
|
||||
);
|
||||
);
|
||||
} else {
|
||||
const baseBorrows = base.balance.lt(ZERO_I80F48())
|
||||
? base.balance.abs()
|
||||
: ZERO_I80F48();
|
||||
zeroAmount = quote.balance
|
||||
.max(baseBorrows)
|
||||
.add(
|
||||
initialHealth.div(
|
||||
const max = quote.balance.max(baseBorrows);
|
||||
zeroAmount = max.add(
|
||||
initialHealth
|
||||
.add(max.mul(base.initLiabWeight.sub(quote.initAssetWeight)))
|
||||
.div(
|
||||
quote
|
||||
.liabWeight(HealthType.init)
|
||||
.sub(base.assetWeight(HealthType.init)),
|
||||
),
|
||||
);
|
||||
);
|
||||
}
|
||||
|
||||
const cache = cacheAfterPlacingOrder(zeroAmount);
|
||||
const zeroAmountHealth = cache.health(HealthType.init);
|
||||
const zeroAmountRatio = cache.healthRatio(HealthType.init);
|
||||
|
||||
function cacheAfterPlacingOrder(amount: I80F48) {
|
||||
|
@ -601,11 +632,10 @@ export class HealthCache {
|
|||
: adjustedCache.tokenInfos[quoteIndex].balance.isub(amount);
|
||||
|
||||
adjustedCache.adjustSerum3Reserved(
|
||||
serum3Market.marketIndex,
|
||||
serum3Market.baseTokenIndex,
|
||||
group,
|
||||
serum3Market,
|
||||
side === Serum3Side.ask ? amount.div(base.oraclePrice) : ZERO_I80F48(),
|
||||
ZERO_I80F48(),
|
||||
serum3Market.quoteTokenIndex,
|
||||
side === Serum3Side.bid ? amount.div(quote.oraclePrice) : ZERO_I80F48(),
|
||||
ZERO_I80F48(),
|
||||
);
|
||||
|
@ -732,6 +762,19 @@ export class Serum3Info {
|
|||
);
|
||||
}
|
||||
|
||||
static emptyFromSerum3Market(
|
||||
serum3Market: Serum3Market,
|
||||
baseEntryIndex: number,
|
||||
quoteEntryIndex: number,
|
||||
) {
|
||||
return new Serum3Info(
|
||||
ZERO_I80F48(),
|
||||
baseEntryIndex,
|
||||
quoteEntryIndex,
|
||||
serum3Market.marketIndex,
|
||||
);
|
||||
}
|
||||
|
||||
healthContribution(healthType: HealthType, tokenInfos: TokenInfo[]): I80F48 {
|
||||
const baseInfo = tokenInfos[this.baseIndex];
|
||||
const quoteInfo = tokenInfos[this.quoteIndex];
|
||||
|
|
|
@ -524,7 +524,7 @@ export class MangoAccount {
|
|||
group,
|
||||
serum3Market,
|
||||
Serum3Side.bid,
|
||||
I80F48.fromNumber(3),
|
||||
I80F48.fromNumber(1),
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -566,7 +566,7 @@ export class MangoAccount {
|
|||
group,
|
||||
serum3Market,
|
||||
Serum3Side.ask,
|
||||
I80F48.fromNumber(3),
|
||||
I80F48.fromNumber(1),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@ import fs from 'fs';
|
|||
import { Group } from '../accounts/group';
|
||||
import { I80F48 } from '../accounts/I80F48';
|
||||
import { HealthType, MangoAccount } from '../accounts/mangoAccount';
|
||||
import { Serum3Market } from '../accounts/serum3';
|
||||
import { MangoClient } from '../client';
|
||||
import { MANGO_V4_ID } from '../constants';
|
||||
import { toUiDecimalsForQuote } from '../utils';
|
||||
|
@ -128,6 +129,29 @@ async function debugUser(
|
|||
getMaxSourceForTokenSwapWrapper(srcToken, tgtToken);
|
||||
}
|
||||
}
|
||||
|
||||
function getMaxForSerum3Wrapper(serum3Market: Serum3Market) {
|
||||
// if (serum3Market.name !== 'SOL/USDC') return;
|
||||
console.log(
|
||||
`getMaxQuoteForSerum3BidUi ${serum3Market.name} ` +
|
||||
mangoAccount.getMaxQuoteForSerum3BidUi(
|
||||
group,
|
||||
serum3Market.serumMarketExternal,
|
||||
),
|
||||
);
|
||||
console.log(
|
||||
`getMaxBaseForSerum3AskUi ${serum3Market.name} ` +
|
||||
mangoAccount.getMaxBaseForSerum3AskUi(
|
||||
group,
|
||||
serum3Market.serumMarketExternal,
|
||||
),
|
||||
);
|
||||
}
|
||||
for (const serum3Market of Array.from(
|
||||
group.serum3MarketsMapByExternal.values(),
|
||||
)) {
|
||||
getMaxForSerum3Wrapper(serum3Market);
|
||||
}
|
||||
}
|
||||
|
||||
async function main() {
|
||||
|
@ -177,7 +201,7 @@ async function main() {
|
|||
|
||||
for (const mangoAccount of mangoAccounts) {
|
||||
console.log(`MangoAccount ${mangoAccount.publicKey}`);
|
||||
// if (mangoAccount.name === '2nd Account') {
|
||||
// if (mangoAccount.name === 'PnL Test') {
|
||||
await debugUser(client, group, mangoAccount);
|
||||
// }
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue