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:
microwavedcola1 2022-09-23 09:34:08 +02:00 committed by microwavedcola1
parent d86b3dd757
commit 4090cf407e
3 changed files with 103 additions and 36 deletions

View File

@ -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];

View File

@ -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),
);
}

View File

@ -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);
// }
}