"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.HealthType = exports.TokenConditionalSwapDto = exports.TokenConditionalSwap = exports.TokenConditionalSwapIntention = exports.TokenConditionalSwapDisplayPriceStyle = exports.PerpOoDto = exports.PerpOo = exports.PerpPositionDto = exports.PerpPosition = exports.Serum3PositionDto = exports.Serum3Orders = exports.TokenPositionDto = exports.TokenPosition = exports.MangoAccount = void 0;
const anchor_1 = require("@coral-xyz/anchor");
const bytes_1 = require("@coral-xyz/anchor/dist/cjs/utils/bytes");
const market_1 = require("@project-serum/serum/lib/market");
const web3_js_1 = require("@solana/web3.js");
const on_demand_1 = require("@switchboard-xyz/on-demand");
const constants_1 = require("../constants");
const I80F48_1 = require("../numbers/I80F48");
const utils_1 = require("../utils");
const healthCache_1 = require("./healthCache");
const oracle_1 = require("./oracle");
const perp_1 = require("./perp");
const serum3_1 = require("./serum3");
class MangoAccount {
    publicKey;
    group;
    owner;
    delegate;
    accountNum;
    beingLiquidated;
    inHealthRegion;
    netDeposits;
    perpSpotTransfers;
    healthRegionBeginInitHealth;
    frozenUntil;
    buybackFeesAccruedCurrent;
    buybackFeesAccruedPrevious;
    buybackFeesExpiryTimestamp;
    sequenceNumber;
    headerVersion;
    lastCollateralFeeCharge;
    serum3OosMapByMarketIndex;
    name;
    tokens;
    serum3;
    perps;
    perpOpenOrders;
    tokenConditionalSwaps;
    static from(publicKey, obj) {
        return new MangoAccount(publicKey, obj.group, obj.owner, obj.name, obj.delegate, obj.accountNum, obj.beingLiquidated == 1, obj.inHealthRegion == 1, obj.netDeposits, obj.perpSpotTransfers, obj.healthRegionBeginInitHealth, obj.frozenUntil, obj.buybackFeesAccruedCurrent, obj.buybackFeesAccruedPrevious, obj.buybackFeesExpiryTimestamp, obj.sequenceNumber, obj.headerVersion, obj.lastCollateralFeeCharge, obj.tokens, obj.serum3, obj.perps, obj.perpOpenOrders, obj.tokenConditionalSwaps, new Map());
    }
    constructor(publicKey, group, owner, name, delegate, accountNum, beingLiquidated, inHealthRegion, netDeposits, perpSpotTransfers, healthRegionBeginInitHealth, frozenUntil, buybackFeesAccruedCurrent, buybackFeesAccruedPrevious, buybackFeesExpiryTimestamp, sequenceNumber, headerVersion, lastCollateralFeeCharge, tokens, serum3, perps, perpOpenOrders, tokenConditionalSwaps, serum3OosMapByMarketIndex) {
        this.publicKey = publicKey;
        this.group = group;
        this.owner = owner;
        this.delegate = delegate;
        this.accountNum = accountNum;
        this.beingLiquidated = beingLiquidated;
        this.inHealthRegion = inHealthRegion;
        this.netDeposits = netDeposits;
        this.perpSpotTransfers = perpSpotTransfers;
        this.healthRegionBeginInitHealth = healthRegionBeginInitHealth;
        this.frozenUntil = frozenUntil;
        this.buybackFeesAccruedCurrent = buybackFeesAccruedCurrent;
        this.buybackFeesAccruedPrevious = buybackFeesAccruedPrevious;
        this.buybackFeesExpiryTimestamp = buybackFeesExpiryTimestamp;
        this.sequenceNumber = sequenceNumber;
        this.headerVersion = headerVersion;
        this.lastCollateralFeeCharge = lastCollateralFeeCharge;
        this.serum3OosMapByMarketIndex = serum3OosMapByMarketIndex;
        this.name = bytes_1.utf8.decode(new Uint8Array(name)).split('\x00')[0];
        this.tokens = tokens.map((dto) => TokenPosition.from(dto));
        this.serum3 = serum3.map((dto) => Serum3Orders.from(dto));
        this.perps = perps.map((dto) => PerpPosition.from(dto));
        this.perpOpenOrders = perpOpenOrders.map((dto) => PerpOo.from(dto));
        this.tokenConditionalSwaps = tokenConditionalSwaps.map((dto) => TokenConditionalSwap.from(dto));
    }
    async reload(client) {
        const mangoAccount = await client.getMangoAccount(this.publicKey);
        await mangoAccount.reloadSerum3OpenOrders(client);
        Object.assign(this, mangoAccount);
        return mangoAccount;
    }
    async reloadWithSlot(client) {
        const resp = await client.getMangoAccountWithSlot(this.publicKey);
        await resp?.value.reloadSerum3OpenOrders(client);
        Object.assign(this, resp?.value);
        return { value: resp.value, slot: resp.slot };
    }
    async reloadSerum3OpenOrders(client) {
        const serum3Active = this.serum3Active();
        if (!serum3Active.length)
            return this;
        const ais = await client.program.provider.connection.getMultipleAccountsInfo(serum3Active.map((serum3) => serum3.openOrders));
        this.serum3OosMapByMarketIndex = new Map(Array.from(ais.map((ai, i) => {
            if (!ai) {
                throw new Error(`Undefined AI for open orders ${serum3Active[i].openOrders} and market ${serum3Active[i].marketIndex}!`);
            }
            const oo = market_1.OpenOrders.fromAccountInfo(serum3Active[i].openOrders, ai, constants_1.OPENBOOK_PROGRAM_ID[client.cluster]);
            return [serum3Active[i].marketIndex, oo];
        })));
        return this;
    }
    loadSerum3OpenOrders(serum3OosMapByOo) {
        const serum3Active = this.serum3Active();
        if (!serum3Active.length)
            return;
        this.serum3OosMapByMarketIndex = new Map(Array.from(serum3Active.map((mangoOo) => {
            const oo = serum3OosMapByOo.get(mangoOo.openOrders.toBase58());
            if (!oo) {
                throw new Error(`Undefined open orders for ${mangoOo.openOrders}`);
            }
            return [mangoOo.marketIndex, oo];
        })));
    }
    isDelegate(client) {
        return this.delegate.equals(client.program.provider.wallet.publicKey);
    }
    isOperational() {
        return this.frozenUntil.lt(new anchor_1.BN(Date.now() / 1000));
    }
    async tokenPositionsForNotConfidentOrStaleOracles(client, group) {
        const nowSlot = await client.connection.getSlot();
        return this.tokensActive()
            .map((tp) => group.getFirstBankByTokenIndex(tp.tokenIndex))
            .filter((bank) => bank.isOracleStaleOrUnconfident(nowSlot));
    }
    tokensActive() {
        return this.tokens.filter((token) => token.isActive());
    }
    serum3Active() {
        return this.serum3.filter((serum3) => serum3.isActive());
    }
    tokenConditionalSwapsActive() {
        return this.tokenConditionalSwaps.filter((tcs) => tcs.isConfigured);
    }
    perpPositionExistsForMarket(perpMarket) {
        return this.perps.some((pp) => pp.isActive() && pp.marketIndex == perpMarket.perpMarketIndex);
    }
    perpOrderExistsForMarket(perpMarket) {
        return this.perpOpenOrders.some((poo) => poo.isActive() && poo.orderMarket == perpMarket.perpMarketIndex);
    }
    perpActive() {
        return this.perps.filter((perp) => perp.isActive());
    }
    perpOrdersActive() {
        return this.perpOpenOrders.filter((oo) => oo.orderMarket !== PerpOo.OrderMarketUnset);
    }
    getToken(tokenIndex) {
        return this.tokens.find((ta) => ta.tokenIndex == tokenIndex);
    }
    getSerum3Account(marketIndex) {
        return this.serum3.find((sa) => sa.marketIndex == marketIndex);
    }
    getPerpPosition(perpMarketIndex) {
        return this.perps.find((pp) => pp.marketIndex == perpMarketIndex);
    }
    getPerpPositionUi(group, perpMarketIndex, useEventQueue) {
        const pp = this.perps.find((pp) => pp.marketIndex == perpMarketIndex);
        if (!pp) {
            throw new Error(`No position found for PerpMarket ${perpMarketIndex}!`);
        }
        const perpMarket = group.getPerpMarketByMarketIndex(perpMarketIndex);
        return pp.getBasePositionUi(perpMarket, useEventQueue);
    }
    getSerum3OoAccount(marketIndex) {
        const oo = this.serum3OosMapByMarketIndex.get(marketIndex);
        if (!oo) {
            throw new Error(`Open orders account not loaded for market with marketIndex ${marketIndex}!`);
        }
        return oo;
    }
    // How to navigate
    // * if a function is returning a I80F48, then usually the return value is in native quote or native token, unless specified
    // * if a function is returning a number, then usually the return value is in ui tokens, unless specified
    // * functions try to be explicit by having native or ui in the name to better reflect the value
    // * some values might appear unexpected large or small, usually the doc contains a "note"
    /**
     *
     * @param bank
     * @returns native balance for a token, is signed
     */
    getTokenBalance(bank) {
        const tp = this.getToken(bank.tokenIndex);
        return tp ? tp.balance(bank) : (0, I80F48_1.ZERO_I80F48)();
    }
    // TODO: once perp quote is merged, also add in the settle token balance if relevant
    getEffectiveTokenBalance(group, bank) {
        const tp = this.getToken(bank.tokenIndex);
        if (tp) {
            const bal = tp.balance(bank);
            for (const serum3Market of Array.from(group.serum3MarketsMapByMarketIndex.values())) {
                const oo = this.serum3OosMapByMarketIndex.get(serum3Market.marketIndex);
                if (serum3Market.baseTokenIndex == bank.tokenIndex && oo) {
                    bal.add(I80F48_1.I80F48.fromI64(oo.baseTokenFree));
                }
                if (serum3Market.quoteTokenIndex == bank.tokenIndex && oo) {
                    bal.add(I80F48_1.I80F48.fromI64(oo.quoteTokenFree));
                }
            }
            return bal;
        }
        return (0, I80F48_1.ZERO_I80F48)();
    }
    /**
     *
     * @param bank
     * @returns native deposits for a token, 0 if position has borrows
     */
    getTokenDeposits(bank) {
        const tp = this.getToken(bank.tokenIndex);
        return tp ? tp.deposits(bank) : (0, I80F48_1.ZERO_I80F48)();
    }
    /**
     *
     * @param bank
     * @returns native borrows for a token, 0 if position has deposits
     */
    getTokenBorrows(bank) {
        const tp = this.getToken(bank.tokenIndex);
        return tp ? tp.borrows(bank) : (0, I80F48_1.ZERO_I80F48)();
    }
    /**
     *
     * @param bank
     * @returns UI balance for a token, is signed
     */
    getTokenBalanceUi(bank) {
        const tp = this.getToken(bank.tokenIndex);
        return tp ? tp.balanceUi(bank) : 0;
    }
    /**
     *
     * @param bank
     * @returns UI deposits for a token, 0 or more
     */
    getTokenDepositsUi(bank) {
        const ta = this.getToken(bank.tokenIndex);
        return ta ? ta.depositsUi(bank) : 0;
    }
    /**
     *
     * @param bank
     * @returns UI borrows for a token, 0 or less
     */
    getTokenBorrowsUi(bank) {
        const ta = this.getToken(bank.tokenIndex);
        return ta ? ta.borrowsUi(bank) : 0;
    }
    /**
     * Health, see health.rs or https://docs.mango.markets/mango-markets/health-overview
     * @param healthType
     * @returns raw health number, in native quote
     */
    getHealth(group, healthType) {
        const hc = healthCache_1.HealthCache.fromMangoAccount(group, this);
        return hc.health(healthType);
    }
    getHealthContributionPerAssetUi(group, healthType) {
        const hc = healthCache_1.HealthCache.fromMangoAccount(group, this);
        return hc.healthContributionPerAssetUi(group, healthType);
    }
    perpMaxSettle(group, perpMarketSettleTokenIndex) {
        const hc = healthCache_1.HealthCache.fromMangoAccount(group, this);
        return hc.perpMaxSettle(perpMarketSettleTokenIndex);
    }
    /**
     * Health ratio, which is computed so `100 * (assets-liabs)/liabs`
     * Note: health ratio is technically ∞ if liabs are 0
     * @param healthType
     * @returns health ratio, in percentage form
     */
    getHealthRatio(group, healthType) {
        const hc = healthCache_1.HealthCache.fromMangoAccount(group, this);
        return hc.healthRatio(healthType);
    }
    /**
     * Health ratio
     * @param healthType
     * @returns health ratio, in percentage form, capped to 100
     */
    getHealthRatioUi(group, healthType) {
        const ratio = this.getHealthRatio(group, healthType).toNumber();
        return ratio > 100 ? 100 : Math.trunc(ratio);
    }
    /**
     * Sum of all the assets i.e. token deposits, borrows, total assets in spot open orders, and perps positions.
     * @returns equity, in native quote
     */
    getEquity(group) {
        const tokensMap = new Map();
        for (const tp of this.tokensActive()) {
            const bank = group.getFirstBankByTokenIndex(tp.tokenIndex);
            tokensMap.set(tp.tokenIndex, tp.balance(bank).mul(bank.price));
        }
        for (const sp of this.serum3Active()) {
            const oo = this.getSerum3OoAccount(sp.marketIndex);
            const baseBank = group.getFirstBankByTokenIndex(sp.baseTokenIndex);
            tokensMap
                .get(baseBank.tokenIndex)
                .iadd(I80F48_1.I80F48.fromI64(oo.baseTokenTotal).mul(baseBank.price));
            const quoteBank = group.getFirstBankByTokenIndex(sp.quoteTokenIndex);
            tokensMap
                .get(baseBank.tokenIndex)
                .iadd(I80F48_1.I80F48.fromI64(oo.quoteTokenTotal).mul(quoteBank.price));
        }
        const tokenEquity = Array.from(tokensMap.values()).reduce((a, b) => a.add(b), (0, I80F48_1.ZERO_I80F48)());
        const perpEquity = this.perpActive().reduce((a, b) => a.add(b.getEquity(group.getPerpMarketByMarketIndex(b.marketIndex))), (0, I80F48_1.ZERO_I80F48)());
        return tokenEquity.add(perpEquity);
    }
    /**
     * The amount of native quote you could withdraw against your existing assets.
     * @returns collateral value, in native quote
     */
    getCollateralValue(group) {
        return this.getHealth(group, HealthType.init);
    }
    /**
     * Sum of all positive assets.
     * @returns assets, in native quote
     */
    getAssetsValue(group, healthType) {
        const hc = healthCache_1.HealthCache.fromMangoAccount(group, this);
        return hc.healthAssetsAndLiabs(healthType, false).assets;
    }
    /**
     * Sum of all negative assets.
     * @returns liabs, in native quote
     */
    getLiabsValue(group) {
        const hc = healthCache_1.HealthCache.fromMangoAccount(group, this);
        return hc.healthAssetsAndLiabs(undefined, false).liabs;
    }
    /**
     * @returns Overall PNL, in native quote
     * PNL is defined here as spot value + serum3 open orders value + perp value - net deposits value (evaluated at native quote price at the time of the deposit/withdraw)
     * spot value + serum3 open orders value + perp value is returned by getEquity (open orders values are added to spot token values implicitly)
     */
    getPnl(group) {
        return this.getEquity(group)?.add(I80F48_1.I80F48.fromI64(this.netDeposits).mul(I80F48_1.I80F48.fromNumber(-1)));
    }
    /**
     * @returns token cumulative interest, in native token units. Sum of deposit and borrow interest.
     * Caveat: This will only return cumulative interest since the tokenPosition was last opened.
     * If the tokenPosition was closed and reopened multiple times it is necessary to add this result to
     * cumulative interest at each of the prior tokenPosition closings (from mango API) to get the all time
     * cumulative interest.
     */
    getCumulativeInterest(bank) {
        const token = this.getToken(bank.tokenIndex);
        if (token === undefined) {
            // tokenPosition does not exist on mangoAccount so no cumulative interest
            return 0;
        }
        else {
            if (token.indexedPosition.isPos()) {
                const interest = bank.depositIndex
                    .sub(token.previousIndex)
                    .mul(token.indexedPosition)
                    .toNumber();
                return (interest +
                    token.cumulativeDepositInterest +
                    token.cumulativeBorrowInterest);
            }
            else {
                const interest = bank.borrowIndex
                    .sub(token.previousIndex)
                    .mul(token.indexedPosition)
                    .toNumber();
                return (interest +
                    token.cumulativeDepositInterest +
                    token.cumulativeBorrowInterest);
            }
        }
    }
    /**
     * The amount of given native token you can withdraw including borrows, considering all existing assets as collateral.
     * @returns amount of given native token you can borrow, considering all existing assets as collateral, in native token
     *
     * TODO: take into account net_borrow_limit and min_vault_to_deposits_ratio
     * TODO: see max_borrow_for_health_fn
     */
    getMaxWithdrawWithBorrowForToken(group, mintPk) {
        const tokenBank = group.getFirstBankByMint(mintPk);
        const loanOriginationFactor = (0, I80F48_1.ONE_I80F48)().add(tokenBank.loanOriginationFeeRate);
        const maxBorrowUtilization = I80F48_1.I80F48.fromNumber(1 - tokenBank.minVaultToDepositsRatio);
        const tp = this.getToken(tokenBank.tokenIndex);
        const healthCache = healthCache_1.HealthCache.fromMangoAccount(group, this);
        const tokenInfoIndex = healthCache.getOrCreateTokenInfoIndex(tokenBank);
        const initHealth = healthCache.health(HealthType.init);
        // Cannot withdraw if init health is below 0
        if (initHealth.lte((0, I80F48_1.ZERO_I80F48)())) {
            return (0, I80F48_1.ZERO_I80F48)();
        }
        // Step 1: Since withdraws can change the asset weight scaling and borrows will
        // change the liab weight scaling, we use a binary search to find something
        // close to the true maximum value.
        // To do that, we first get an upper bound that the search can start with.
        const existingTokenDeposits = tp ? tp.deposits(tokenBank) : (0, I80F48_1.ZERO_I80F48)();
        const lowerBoundBorrowHealthFactor = tokenBank
            .getLiabPrice()
            .mul(tokenBank.scaledInitLiabWeight(tokenBank.getLiabPrice()));
        const upperBound = existingTokenDeposits.add(initHealth.div(lowerBoundBorrowHealthFactor));
        // Step 2: Find the maximum withdraw amount
        const mutTokenBank = (0, utils_1.deepClone)(tokenBank);
        const mutHealthCache = (0, utils_1.deepClone)(healthCache);
        const invalidHealthValue = (0, I80F48_1.MAX_I80F48)().div(I80F48_1.I80F48.fromNumber(2)).neg();
        function healthAfterWithdraw(amount) {
            const withdrawOfDepositsAmount = amount.min(existingTokenDeposits);
            const borrowAmount = amount.sub(withdrawOfDepositsAmount);
            // Take care of loan origination fee
            const borrowCost = borrowAmount.mul(loanOriginationFactor);
            // Update the account's token position
            const mutTi = mutHealthCache.tokenInfos[tokenInfoIndex];
            const startTi = healthCache.tokenInfos[tokenInfoIndex];
            mutTi.balanceSpot = startTi.balanceSpot
                .sub(withdrawOfDepositsAmount)
                .sub(borrowCost);
            // Update the bank and the scaled weights
            mutTokenBank.indexedDeposits = tokenBank.indexedDeposits.sub(withdrawOfDepositsAmount.div(tokenBank.depositIndex));
            mutTokenBank.indexedBorrows = tokenBank.indexedBorrows.add(borrowCost.div(tokenBank.borrowIndex));
            if (mutTokenBank.nativeBorrows().gt(mutTokenBank.nativeDeposits())) {
                return invalidHealthValue;
            }
            if (borrowAmount.isPos()) {
                if (mutTokenBank
                    .nativeBorrows()
                    .gt(mutTokenBank.nativeDeposits().mul(maxBorrowUtilization))) {
                    return invalidHealthValue;
                }
            }
            mutTi.initScaledAssetWeight = mutTokenBank.scaledInitAssetWeight(tokenBank.getAssetPrice());
            mutTi.initScaledLiabWeight = mutTokenBank.scaledInitLiabWeight(tokenBank.getLiabPrice());
            return mutHealthCache.health(HealthType.init);
        }
        // Withdrawing one token will change health by at least this much.
        // We use this to define a good stopping criterion for the search.
        const minHealthChangePerNative = tokenBank
            .getAssetPrice()
            .mul(tokenBank.scaledInitAssetWeight(tokenBank.getAssetPrice()));
        let amount = healthCache_1.HealthCache.binaryApproximationSearch((0, I80F48_1.ZERO_I80F48)(), initHealth, upperBound, I80F48_1.I80F48.fromNumber(0.5).mul(minHealthChangePerNative).min(initHealth), I80F48_1.I80F48.fromNumber(0.5), healthAfterWithdraw, {
            maxIterations: 100,
            targetError: I80F48_1.I80F48.fromNumber(0.2)
                .mul(minHealthChangePerNative)
                .toNumber(),
        });
        // Step 3: Only full tokens can be withdrawn, do the rounding and
        // check if withdrawing one-native more would also be fine
        amount = amount.floor();
        const amountPlusOne = amount.add((0, I80F48_1.ONE_I80F48)());
        if (!healthAfterWithdraw(amountPlusOne).isNeg()) {
            amount = amountPlusOne;
        }
        // Step 4: No borrows on no-borrow tokens
        if (tokenBank.areBorrowsReduceOnly()) {
            amount = amount.min(existingTokenDeposits);
        }
        // Step 5: also limit by vault funds
        const vaultAmount = group.vaultAmountsMap.get(tokenBank.vault.toBase58());
        if (!vaultAmount) {
            throw new Error(`No vault amount found for ${tokenBank.name} vault ${tokenBank.vault}!`);
        }
        const vaultLimit = I80F48_1.I80F48.fromU64(vaultAmount);
        return amount.min(vaultLimit).max((0, I80F48_1.ZERO_I80F48)());
    }
    getMaxWithdrawWithBorrowForTokenUi(group, mintPk) {
        const maxWithdrawWithBorrow = this.getMaxWithdrawWithBorrowForToken(group, mintPk);
        return (0, utils_1.toUiDecimals)(maxWithdrawWithBorrow, group.getMintDecimals(mintPk));
    }
    calculateEquivalentSourceAmount(sourceBank, targetBank, targetRemainingDepositLimit) {
        return I80F48_1.I80F48.fromI64(targetRemainingDepositLimit).mul(targetBank.price.div(sourceBank.price));
    }
    /**
     * The max amount of given source ui token you can swap to a target token.
     * @returns max amount of given source ui token you can swap to a target token, in ui token
     */
    getMaxSourceUiForTokenSwap(group, sourceMintPk, targetMintPk, slippageAndFeesFactor = 1) {
        if (sourceMintPk.equals(targetMintPk)) {
            return 0;
        }
        const sourceBank = group.getFirstBankByMint(sourceMintPk);
        const targetBank = group.getFirstBankByMint(targetMintPk);
        const targetRemainingDepositLimit = targetBank.getRemainingDepositLimit();
        const hc = healthCache_1.HealthCache.fromMangoAccount(group, this);
        let maxSource = hc.getMaxSwapSource(sourceBank, targetBank, I80F48_1.I80F48.fromNumber(slippageAndFeesFactor *
            ((sourceBank.uiPrice / targetBank.uiPrice) *
                Math.pow(10, targetBank.mintDecimals - sourceBank.mintDecimals))));
        const sourceBalance = this.getEffectiveTokenBalance(group, sourceBank);
        const maxWithdrawNative = sourceBank.getMaxWithdraw(group.getTokenVaultBalanceByMint(sourceBank.mint), sourceBalance);
        maxSource = maxSource.min(maxWithdrawNative);
        if (targetRemainingDepositLimit) {
            const equivalentSourceAmount = this.calculateEquivalentSourceAmount(sourceBank, targetBank, targetRemainingDepositLimit);
            maxSource = maxSource.min(equivalentSourceAmount);
        }
        // Apply max swap fee
        const maxSwapFeeRate = I80F48_1.I80F48.fromNumber(Math.max(sourceBank.flashLoanSwapFeeRate, targetBank.flashLoanSwapFeeRate));
        maxSource = maxSource.div((0, I80F48_1.ONE_I80F48)().add(maxSwapFeeRate));
        return (0, utils_1.toUiDecimals)(maxSource, group.getMintDecimals(sourceMintPk));
    }
    /**
     * Simulates new health ratio after applying tokenChanges to the token positions.
     * Note: token changes are expected in ui amounts
     *
     * e.g. useful to simulate health after a potential swap.
     * Note: health ratio is technically ∞ if liabs are 0
     * @returns health ratio, in percentage form
     */
    simHealthRatioWithTokenPositionUiChanges(group, uiTokenChanges, healthType = HealthType.init) {
        const nativeTokenChanges = uiTokenChanges.map((tokenChange) => {
            return {
                nativeTokenAmount: (0, utils_1.toNativeI80F48)(tokenChange.uiTokenAmount, group.getMintDecimals(tokenChange.mintPk)),
                mintPk: tokenChange.mintPk,
            };
        });
        const hc = healthCache_1.HealthCache.fromMangoAccount(group, this);
        return hc
            .simHealthRatioWithTokenPositionChanges(group, nativeTokenChanges, healthType)
            .toNumber();
    }
    async loadSerum3OpenOrdersAccounts(client) {
        const openOrderPks = this.serum3Active().map((s) => s.openOrders);
        if (!openOrderPks.length)
            return [];
        const response = await client.program.provider.connection.getMultipleAccountsInfo(openOrderPks);
        const accounts = response.filter((a) => Boolean(a));
        return accounts.map((acc, index) => {
            return market_1.OpenOrders.fromAccountInfo(this.serum3[index].openOrders, acc, constants_1.OPENBOOK_PROGRAM_ID[client.cluster]);
        });
    }
    async loadSerum3OpenOrdersForMarket(client, group, externalMarketPk) {
        const serum3Market = group.getSerum3MarketByExternalMarket(externalMarketPk);
        const serum3OO = this.serum3Active().find((s) => s.marketIndex === serum3Market.marketIndex);
        if (!serum3OO) {
            throw new Error(`No open orders account found for ${externalMarketPk}`);
        }
        const serum3MarketExternal = group.serum3ExternalMarketsMap.get(externalMarketPk.toBase58());
        const [bidsInfo, asksInfo] = await client.program.provider.connection.getMultipleAccountsInfo([
            serum3MarketExternal.bidsAddress,
            serum3MarketExternal.asksAddress,
        ]);
        if (!bidsInfo) {
            throw new Error(`Undefined bidsInfo for serum3Market with externalMarket ${externalMarketPk.toString()}`);
        }
        if (!asksInfo) {
            throw new Error(`Undefined asksInfo for serum3Market with externalMarket ${externalMarketPk.toString()}`);
        }
        const bids = market_1.Orderbook.decode(serum3MarketExternal, bidsInfo.data);
        const asks = market_1.Orderbook.decode(serum3MarketExternal, asksInfo.data);
        return [...bids, ...asks].filter((o) => o.openOrdersAddress.equals(serum3OO.openOrders));
    }
    /**
     * TODO REWORK, know to break in binary search, also make work for limit orders
     *
     * @param group
     * @param externalMarketPk
     * @returns maximum ui quote which can be traded at oracle price for base token given current health
     */
    getMaxQuoteForSerum3BidUi(group, externalMarketPk) {
        const serum3Market = group.getSerum3MarketByExternalMarket(externalMarketPk);
        const baseBank = group.getFirstBankByTokenIndex(serum3Market.baseTokenIndex);
        const quoteBank = group.getFirstBankByTokenIndex(serum3Market.quoteTokenIndex);
        const targetRemainingDepositLimit = baseBank.getRemainingDepositLimit();
        const hc = healthCache_1.HealthCache.fromMangoAccount(group, this);
        const nativeAmount = hc.getMaxSerum3OrderForHealthRatio(baseBank, quoteBank, serum3Market, serum3_1.Serum3Side.bid, I80F48_1.I80F48.fromNumber(2));
        let quoteAmount = nativeAmount.div(quoteBank.price);
        const quoteBalance = this.getEffectiveTokenBalance(group, quoteBank);
        const maxWithdrawNative = quoteBank.getMaxWithdraw(group.getTokenVaultBalanceByMint(quoteBank.mint), quoteBalance);
        quoteAmount = quoteAmount.min(maxWithdrawNative);
        if (targetRemainingDepositLimit) {
            const equivalentSourceAmount = this.calculateEquivalentSourceAmount(quoteBank, baseBank, targetRemainingDepositLimit);
            quoteAmount = quoteAmount.min(equivalentSourceAmount);
        }
        quoteAmount = quoteAmount.div((0, I80F48_1.ONE_I80F48)().add(I80F48_1.I80F48.fromNumber(serum3Market.getFeeRates(true))));
        return (0, utils_1.toUiDecimals)(quoteAmount, quoteBank.mintDecimals);
    }
    /**
     * TODO REWORK, know to break in binary search, also make work for limit orders
     * @param group
     * @param externalMarketPk
     * @returns maximum ui base which can be traded at oracle price for quote token given current health
     */
    getMaxBaseForSerum3AskUi(group, externalMarketPk) {
        const serum3Market = group.getSerum3MarketByExternalMarket(externalMarketPk);
        const baseBank = group.getFirstBankByTokenIndex(serum3Market.baseTokenIndex);
        const quoteBank = group.getFirstBankByTokenIndex(serum3Market.quoteTokenIndex);
        const targetRemainingDepositLimit = quoteBank.getRemainingDepositLimit();
        const hc = healthCache_1.HealthCache.fromMangoAccount(group, this);
        const nativeAmount = hc.getMaxSerum3OrderForHealthRatio(baseBank, quoteBank, serum3Market, serum3_1.Serum3Side.ask, I80F48_1.I80F48.fromNumber(2));
        let baseAmount = nativeAmount.div(baseBank.price);
        const baseBalance = this.getEffectiveTokenBalance(group, baseBank);
        const maxWithdrawNative = baseBank.getMaxWithdraw(group.getTokenVaultBalanceByMint(baseBank.mint), baseBalance);
        baseAmount = baseAmount.min(maxWithdrawNative);
        if (targetRemainingDepositLimit) {
            const equivalentSourceAmount = this.calculateEquivalentSourceAmount(baseBank, quoteBank, targetRemainingDepositLimit);
            baseAmount = baseAmount.min(equivalentSourceAmount);
        }
        baseAmount = baseAmount.div((0, I80F48_1.ONE_I80F48)().add(I80F48_1.I80F48.fromNumber(serum3Market.getFeeRates(true))));
        return (0, utils_1.toUiDecimals)(baseAmount, baseBank.mintDecimals);
    }
    /**
     *
     * @param group
     * @param uiQuoteAmount
     * @param externalMarketPk
     * @param healthType
     * @returns health ratio after a bid with uiQuoteAmount is placed
     */
    simHealthRatioWithSerum3BidUiChanges(group, uiQuoteAmount, externalMarketPk, healthType = HealthType.init) {
        const serum3Market = group.getSerum3MarketByExternalMarket(externalMarketPk);
        const baseBank = group.getFirstBankByTokenIndex(serum3Market.baseTokenIndex);
        const quoteBank = group.getFirstBankByTokenIndex(serum3Market.quoteTokenIndex);
        const hc = healthCache_1.HealthCache.fromMangoAccount(group, this);
        return hc
            .simHealthRatioWithSerum3BidChanges(baseBank, quoteBank, (0, utils_1.toNativeI80F48)(uiQuoteAmount, group.getFirstBankByTokenIndex(serum3Market.quoteTokenIndex)
            .mintDecimals), serum3Market, healthType)
            .toNumber();
    }
    /**
     *
     * @param group
     * @param uiBaseAmount
     * @param externalMarketPk
     * @param healthType
     * @returns health ratio after an ask with uiBaseAmount is placed
     */
    simHealthRatioWithSerum3AskUiChanges(group, uiBaseAmount, externalMarketPk, healthType = HealthType.init) {
        const serum3Market = group.getSerum3MarketByExternalMarket(externalMarketPk);
        const baseBank = group.getFirstBankByTokenIndex(serum3Market.baseTokenIndex);
        const quoteBank = group.getFirstBankByTokenIndex(serum3Market.quoteTokenIndex);
        const hc = healthCache_1.HealthCache.fromMangoAccount(group, this);
        return hc
            .simHealthRatioWithSerum3AskChanges(baseBank, quoteBank, (0, utils_1.toNativeI80F48)(uiBaseAmount, group.getFirstBankByTokenIndex(serum3Market.baseTokenIndex)
            .mintDecimals), serum3Market, healthType)
            .toNumber();
    }
    // TODO: don't send a settle instruction if there's nothing to settle
    async serum3SettleFundsForAllMarkets(client, group) {
        // Future: collect ixs, batch them, and send them in fewer txs
        return await Promise.all(this.serum3Active().map((s) => {
            const serum3Market = group.getSerum3MarketByMarketIndex(s.marketIndex);
            return client.serum3SettleFunds(group, this, serum3Market.serumMarketExternal);
        }));
    }
    // TODO: cancel until all are cancelled
    async serum3CancelAllOrdersForAllMarkets(client, group) {
        // Future: collect ixs, batch them, and send them in in fewer txs
        return await Promise.all(this.serum3Active().map((s) => {
            const serum3Market = group.getSerum3MarketByMarketIndex(s.marketIndex);
            return client.serum3CancelAllOrders(group, this, serum3Market.serumMarketExternal);
        }));
    }
    /**
     * TODO: also think about limit orders
     *
     * The max ui quote you can place a market/ioc bid on the market,
     * price is the ui price at which you think the order would materialiase.
     * @param group
     * @param perpMarketName
     * @returns maximum ui quote which can be traded at oracle price for quote token given current health
     */
    getMaxQuoteForPerpBidUi(group, perpMarketIndex) {
        const perpMarket = group.getPerpMarketByMarketIndex(perpMarketIndex);
        const hc = healthCache_1.HealthCache.fromMangoAccount(group, this);
        const baseLots = hc.getMaxPerpForHealthRatio(perpMarket, perpMarket.price, perp_1.PerpOrderSide.bid, I80F48_1.I80F48.fromNumber(2));
        const nativeBase = baseLots.mul(I80F48_1.I80F48.fromI64(perpMarket.baseLotSize));
        const nativeQuote = nativeBase.mul(perpMarket.price);
        return (0, utils_1.toUiDecimalsForQuote)(nativeQuote);
    }
    /**
     * TODO: also think about limit orders
     *
     * The max ui base you can place a market/ioc ask on the market,
     * price is the ui price at which you think the order would materialiase.
     * @param group
     * @param perpMarketName
     * @param uiPrice ui price at which ask would be placed at
     * @returns max ui base ask
     */
    getMaxBaseForPerpAskUi(group, perpMarketIndex) {
        const perpMarket = group.getPerpMarketByMarketIndex(perpMarketIndex);
        const hc = healthCache_1.HealthCache.fromMangoAccount(group, this);
        const baseLots = hc.getMaxPerpForHealthRatio(perpMarket, perpMarket.price, perp_1.PerpOrderSide.ask, I80F48_1.I80F48.fromNumber(2));
        return perpMarket.baseLotsToUi(new anchor_1.BN(baseLots.toString()));
    }
    simHealthRatioWithPerpBidUiChanges(group, perpMarketIndex, size, healthType = HealthType.init) {
        const perpMarket = group.getPerpMarketByMarketIndex(perpMarketIndex);
        const pp = this.getPerpPosition(perpMarket.perpMarketIndex);
        const hc = healthCache_1.HealthCache.fromMangoAccount(group, this);
        return hc
            .simHealthRatioWithPerpOrderChanges(perpMarket, pp
            ? pp
            : PerpPosition.emptyFromPerpMarketIndex(perpMarket.perpMarketIndex), perp_1.PerpOrderSide.bid, perpMarket.uiBaseToLots(size), perpMarket.price, healthType)
            .toNumber();
    }
    simHealthRatioWithPerpAskUiChanges(group, perpMarketIndex, size, healthType = HealthType.init) {
        const perpMarket = group.getPerpMarketByMarketIndex(perpMarketIndex);
        const pp = this.getPerpPosition(perpMarket.perpMarketIndex);
        const hc = healthCache_1.HealthCache.fromMangoAccount(group, this);
        return hc
            .simHealthRatioWithPerpOrderChanges(perpMarket, pp
            ? pp
            : PerpPosition.emptyFromPerpMarketIndex(perpMarket.perpMarketIndex), perp_1.PerpOrderSide.ask, perpMarket.uiBaseToLots(size), perpMarket.price, healthType)
            .toNumber();
    }
    async loadPerpOpenOrdersForMarket(client, group, perpMarketIndex, forceReload) {
        const perpMarket = group.getPerpMarketByMarketIndex(perpMarketIndex);
        const [bids, asks] = await Promise.all([
            perpMarket.loadBids(client, forceReload),
            perpMarket.loadAsks(client, forceReload),
        ]);
        return [...bids.items(), ...asks.items()].filter((order) => order.owner.equals(this.publicKey));
    }
    getBuybackFeesAccrued() {
        return this.buybackFeesAccruedCurrent.add(this.buybackFeesAccruedPrevious);
    }
    getBuybackFeesAccruedUi() {
        return (0, utils_1.toUiDecimalsForQuote)(this.getBuybackFeesAccrued());
    }
    getMaxFeesBuyback(group) {
        const mngoBalanceValueWithBonus = new anchor_1.BN(this.getTokenBalance(group.getFirstBankForMngo())
            .mul(group.getFirstBankForMngo().price)
            .mul(I80F48_1.I80F48.fromNumber(group.buybackFeesMngoBonusFactor))
            .floor()
            .toNumber());
        return anchor_1.BN.max(anchor_1.BN.min(this.getBuybackFeesAccrued(), mngoBalanceValueWithBonus), new anchor_1.BN(0));
    }
    getMaxFeesBuybackUi(group) {
        return (0, utils_1.toUiDecimalsForQuote)(this.getMaxFeesBuyback(group));
    }
    async buildIxsForStaleOrUnconfidentOracles(client, group) {
        return await this.buildIxsForStaleOrUnconfidentOraclesInternal(group, await client.buildHealthRemainingAccounts(group, [this]));
    }
    async buildIxsForStaleOrUnconfidentOraclesInternal(group, relevantOraclesToAUserAction) {
        const promises = [];
        for (const oracle of relevantOraclesToAUserAction) {
            const bank = group.getFirstBankByOracle(oracle);
            if (!bank.isOracleStaleOrUnconfident(group.groupLastUpdatedSlot)) {
                continue;
            }
            const pullFeed = new on_demand_1.PullFeed(oracle_1.sbOnDemandProgram, new web3_js_1.PublicKey(oracle));
            promises.push(pullFeed.fetchUpdateIx({
                numSignatures: 2 /* TODO: This needs to be computed dynamically */,
            }));
        }
        return (await Promise.all(promises)).map((item) => item[0]);
    }
    toString(group, onlyTokens = false) {
        let res = 'MangoAccount';
        res = res + '\n pk: ' + this.publicKey.toString();
        res = res + '\n name: ' + this.name;
        res = res + '\n accountNum: ' + this.accountNum;
        res = res + '\n owner: ' + this.owner;
        res = res + '\n delegate: ' + this.delegate;
        res =
            res +
                `\n max token slots ${this.tokens.length}, max serum3 slots ${this.serum3.length}, max perp slots ${this.perps.length}, max perp oo slots ${this.perpOpenOrders.length}`;
        res =
            this.tokensActive().length > 0
                ? res +
                    '\n tokens:' +
                    JSON.stringify(this.tokens
                        .filter((token, i) => token.isActive())
                        .map((token, i) => token.toString(group, i)), null, 4)
                : res + '';
        if (onlyTokens) {
            return res;
        }
        res =
            this.serum3Active().length > 0
                ? res + '\n serum:' + JSON.stringify(this.serum3Active(), null, 4)
                : res + '';
        res =
            this.perpActive().length > 0
                ? res +
                    '\n perps:' +
                    JSON.stringify(this.perpActive().map((p) => p.toString(group?.getPerpMarketByMarketIndex(p.marketIndex))), null, 4)
                : res + '';
        res =
            this.perpOrdersActive().length > 0
                ? res +
                    '\n perps oo:' +
                    JSON.stringify(this.perpOrdersActive(), null, 4)
                : res + '';
        return res;
    }
}
exports.MangoAccount = MangoAccount;
class TokenPosition {
    indexedPosition;
    tokenIndex;
    inUseCount;
    previousIndex;
    cumulativeDepositInterest;
    cumulativeBorrowInterest;
    static TokenIndexUnset = 65535;
    static from(dto) {
        return new TokenPosition(I80F48_1.I80F48.from(dto.indexedPosition), dto.tokenIndex, dto.inUseCount, I80F48_1.I80F48.from(dto.previousIndex), dto.cumulativeDepositInterest, dto.cumulativeBorrowInterest);
    }
    constructor(indexedPosition, tokenIndex, inUseCount, previousIndex, cumulativeDepositInterest, cumulativeBorrowInterest) {
        this.indexedPosition = indexedPosition;
        this.tokenIndex = tokenIndex;
        this.inUseCount = inUseCount;
        this.previousIndex = previousIndex;
        this.cumulativeDepositInterest = cumulativeDepositInterest;
        this.cumulativeBorrowInterest = cumulativeBorrowInterest;
    }
    isActive() {
        return this.tokenIndex !== TokenPosition.TokenIndexUnset;
    }
    /**
     *
     * @param bank
     * @returns native balance
     */
    balance(bank) {
        if (this.indexedPosition.isPos()) {
            return bank.depositIndex.mul(this.indexedPosition);
        }
        else {
            return bank.borrowIndex.mul(this.indexedPosition);
        }
    }
    /**
     *
     * @param bank
     * @returns native deposits, 0 if position has borrows
     */
    deposits(bank) {
        if (this.indexedPosition && this.indexedPosition.lt((0, I80F48_1.ZERO_I80F48)())) {
            return (0, I80F48_1.ZERO_I80F48)();
        }
        return this.balance(bank);
    }
    /**
     *
     * @param bank
     * @returns native borrows, 0 if position has deposits
     */
    borrows(bank) {
        if (this.indexedPosition && this.indexedPosition.gt((0, I80F48_1.ZERO_I80F48)())) {
            return (0, I80F48_1.ZERO_I80F48)();
        }
        return this.balance(bank).abs();
    }
    /**
     * @param bank
     * @returns UI balance, is signed
     */
    balanceUi(bank) {
        return (0, utils_1.toUiDecimals)(this.balance(bank), bank.mintDecimals);
    }
    /**
     * @param bank
     * @returns UI deposits, 0 if position has borrows
     */
    depositsUi(bank) {
        return (0, utils_1.toUiDecimals)(this.deposits(bank), bank.mintDecimals);
    }
    /**
     * @param bank
     * @returns UI borrows, 0 if position has deposits
     */
    borrowsUi(bank) {
        return (0, utils_1.toUiDecimals)(this.borrows(bank), bank.mintDecimals);
    }
    toString(group, index) {
        let extra = '';
        if (group) {
            const bank = group.getFirstBankByTokenIndex(this.tokenIndex);
            if (bank) {
                const native = this.balance(bank);
                extra += ', native: ' + native.toNumber();
                extra += ', ui: ' + this.balanceUi(bank);
                extra += ', tokenName: ' + bank.name;
            }
        }
        return ((index !== undefined ? 'index: ' + index : '') +
            ', tokenIndex: ' +
            this.tokenIndex +
            ', inUseCount: ' +
            this.inUseCount +
            ', indexedValue: ' +
            this.indexedPosition.toNumber() +
            extra);
    }
}
exports.TokenPosition = TokenPosition;
class TokenPositionDto {
    indexedPosition;
    tokenIndex;
    inUseCount;
    reserved;
    previousIndex;
    cumulativeDepositInterest;
    cumulativeBorrowInterest;
    constructor(indexedPosition, tokenIndex, inUseCount, reserved, previousIndex, cumulativeDepositInterest, cumulativeBorrowInterest) {
        this.indexedPosition = indexedPosition;
        this.tokenIndex = tokenIndex;
        this.inUseCount = inUseCount;
        this.reserved = reserved;
        this.previousIndex = previousIndex;
        this.cumulativeDepositInterest = cumulativeDepositInterest;
        this.cumulativeBorrowInterest = cumulativeBorrowInterest;
    }
}
exports.TokenPositionDto = TokenPositionDto;
class Serum3Orders {
    openOrders;
    marketIndex;
    baseTokenIndex;
    quoteTokenIndex;
    highestPlacedBidInv;
    lowestPlacedAsk;
    static Serum3MarketIndexUnset = 65535;
    static from(dto) {
        return new Serum3Orders(dto.openOrders, dto.marketIndex, dto.baseTokenIndex, dto.quoteTokenIndex, dto.highestPlacedBidInv, dto.lowestPlacedAsk);
    }
    constructor(openOrders, marketIndex, baseTokenIndex, quoteTokenIndex, highestPlacedBidInv, lowestPlacedAsk) {
        this.openOrders = openOrders;
        this.marketIndex = marketIndex;
        this.baseTokenIndex = baseTokenIndex;
        this.quoteTokenIndex = quoteTokenIndex;
        this.highestPlacedBidInv = highestPlacedBidInv;
        this.lowestPlacedAsk = lowestPlacedAsk;
    }
    isActive() {
        return this.marketIndex !== Serum3Orders.Serum3MarketIndexUnset;
    }
}
exports.Serum3Orders = Serum3Orders;
class Serum3PositionDto {
    openOrders;
    marketIndex;
    baseBorrowsWithoutFee;
    quoteBorrowsWithoutFee;
    baseTokenIndex;
    quoteTokenIndex;
    highestPlacedBidInv;
    lowestPlacedAsk;
    reserved;
    constructor(openOrders, marketIndex, baseBorrowsWithoutFee, quoteBorrowsWithoutFee, baseTokenIndex, quoteTokenIndex, highestPlacedBidInv, lowestPlacedAsk, 
    // public baseDepositsReserved: BN,
    // public quoteDepositsReserved: BN,
    reserved) {
        this.openOrders = openOrders;
        this.marketIndex = marketIndex;
        this.baseBorrowsWithoutFee = baseBorrowsWithoutFee;
        this.quoteBorrowsWithoutFee = quoteBorrowsWithoutFee;
        this.baseTokenIndex = baseTokenIndex;
        this.quoteTokenIndex = quoteTokenIndex;
        this.highestPlacedBidInv = highestPlacedBidInv;
        this.lowestPlacedAsk = lowestPlacedAsk;
        this.reserved = reserved;
    }
}
exports.Serum3PositionDto = Serum3PositionDto;
class PerpPosition {
    marketIndex;
    settlePnlLimitWindow;
    settlePnlLimitSettledInCurrentWindowNative;
    basePositionLots;
    quotePositionNative;
    quoteRunningNative;
    longSettledFunding;
    shortSettledFunding;
    bidsBaseLots;
    asksBaseLots;
    takerBaseLots;
    takerQuoteLots;
    cumulativeLongFunding;
    cumulativeShortFunding;
    makerVolume;
    takerVolume;
    perpSpotTransfers;
    avgEntryPricePerBaseLot;
    deprecatedRealizedTradePnlNative;
    oneshotSettlePnlAllowance;
    recurringSettlePnlAllowance;
    realizedPnlForPositionNative;
    static PerpMarketIndexUnset = 65535;
    static from(dto) {
        return new PerpPosition(dto.marketIndex, dto.settlePnlLimitWindow, dto.settlePnlLimitSettledInCurrentWindowNative, dto.basePositionLots, I80F48_1.I80F48.from(dto.quotePositionNative), dto.quoteRunningNative, I80F48_1.I80F48.from(dto.longSettledFunding), I80F48_1.I80F48.from(dto.shortSettledFunding), dto.bidsBaseLots, dto.asksBaseLots, dto.takerBaseLots, dto.takerQuoteLots, dto.cumulativeLongFunding, dto.cumulativeShortFunding, dto.makerVolume, dto.takerVolume, dto.perpSpotTransfers, dto.avgEntryPricePerBaseLot, I80F48_1.I80F48.from(dto.deprecatedRealizedTradePnlNative), I80F48_1.I80F48.from(dto.oneshotSettlePnlAllowance), dto.recurringSettlePnlAllowance, I80F48_1.I80F48.from(dto.realizedPnlForPositionNative));
    }
    static emptyFromPerpMarketIndex(perpMarketIndex) {
        return new PerpPosition(perpMarketIndex, 0, new anchor_1.BN(0), new anchor_1.BN(0), (0, I80F48_1.ZERO_I80F48)(), new anchor_1.BN(0), (0, I80F48_1.ZERO_I80F48)(), (0, I80F48_1.ZERO_I80F48)(), new anchor_1.BN(0), new anchor_1.BN(0), new anchor_1.BN(0), new anchor_1.BN(0), 0, 0, new anchor_1.BN(0), new anchor_1.BN(0), new anchor_1.BN(0), 0, (0, I80F48_1.ZERO_I80F48)(), (0, I80F48_1.ZERO_I80F48)(), new anchor_1.BN(0), (0, I80F48_1.ZERO_I80F48)());
    }
    constructor(marketIndex, settlePnlLimitWindow, settlePnlLimitSettledInCurrentWindowNative, basePositionLots, quotePositionNative, quoteRunningNative, longSettledFunding, shortSettledFunding, bidsBaseLots, asksBaseLots, takerBaseLots, takerQuoteLots, cumulativeLongFunding, cumulativeShortFunding, makerVolume, takerVolume, perpSpotTransfers, avgEntryPricePerBaseLot, deprecatedRealizedTradePnlNative, oneshotSettlePnlAllowance, recurringSettlePnlAllowance, realizedPnlForPositionNative) {
        this.marketIndex = marketIndex;
        this.settlePnlLimitWindow = settlePnlLimitWindow;
        this.settlePnlLimitSettledInCurrentWindowNative = settlePnlLimitSettledInCurrentWindowNative;
        this.basePositionLots = basePositionLots;
        this.quotePositionNative = quotePositionNative;
        this.quoteRunningNative = quoteRunningNative;
        this.longSettledFunding = longSettledFunding;
        this.shortSettledFunding = shortSettledFunding;
        this.bidsBaseLots = bidsBaseLots;
        this.asksBaseLots = asksBaseLots;
        this.takerBaseLots = takerBaseLots;
        this.takerQuoteLots = takerQuoteLots;
        this.cumulativeLongFunding = cumulativeLongFunding;
        this.cumulativeShortFunding = cumulativeShortFunding;
        this.makerVolume = makerVolume;
        this.takerVolume = takerVolume;
        this.perpSpotTransfers = perpSpotTransfers;
        this.avgEntryPricePerBaseLot = avgEntryPricePerBaseLot;
        this.deprecatedRealizedTradePnlNative = deprecatedRealizedTradePnlNative;
        this.oneshotSettlePnlAllowance = oneshotSettlePnlAllowance;
        this.recurringSettlePnlAllowance = recurringSettlePnlAllowance;
        this.realizedPnlForPositionNative = realizedPnlForPositionNative;
    }
    isActive() {
        return this.marketIndex !== PerpPosition.PerpMarketIndexUnset;
    }
    getBasePosition(perpMarket) {
        return I80F48_1.I80F48.fromI64(this.basePositionLots.mul(perpMarket.baseLotSize));
    }
    getBasePositionUi(perpMarket, useEventQueue) {
        if (perpMarket.perpMarketIndex !== this.marketIndex) {
            throw new Error("PerpPosition doesn't belong to the given market!");
        }
        return perpMarket.baseLotsToUi(useEventQueue
            ? this.basePositionLots.add(this.takerBaseLots)
            : this.basePositionLots);
    }
    getQuotePositionUi(perpMarket, useEventQueue) {
        if (perpMarket.perpMarketIndex !== this.marketIndex) {
            throw new Error("PerpPosition doesn't belong to the given market!");
        }
        const quotePositionUi = (0, utils_1.toUiDecimalsForQuote)(this.quotePositionNative);
        return useEventQueue
            ? quotePositionUi + perpMarket.quoteLotsToUi(this.takerQuoteLots)
            : quotePositionUi;
    }
    getNotionalValueUi(perpMarket, useEventQueue) {
        return (this.getBasePositionUi(perpMarket, useEventQueue) * perpMarket.uiPrice);
    }
    getUnsettledFunding(perpMarket) {
        if (perpMarket.perpMarketIndex !== this.marketIndex) {
            throw new Error("PerpPosition doesn't belong to the given market!");
        }
        if (this.basePositionLots.gt(new anchor_1.BN(0))) {
            return perpMarket.longFunding
                .sub(this.longSettledFunding)
                .mul(I80F48_1.I80F48.fromI64(this.basePositionLots));
        }
        else if (this.basePositionLots.lt(new anchor_1.BN(0))) {
            return perpMarket.shortFunding
                .sub(this.shortSettledFunding)
                .mul(I80F48_1.I80F48.fromI64(this.basePositionLots));
        }
        return (0, I80F48_1.ZERO_I80F48)();
    }
    getUnsettledFundingUi(perpMarket) {
        return (0, utils_1.toUiDecimalsForQuote)(this.getUnsettledFunding(perpMarket));
    }
    /**
     * @returns perp position cumulative funding, in quote token units.
     * If the user paid $1 in funding for a short position, this would be -1e6.
     * Caveat: This will only return cumulative interest since the perp position was last opened.
     * If the perp position was closed and reopened multiple times it is necessary to add this result to
     * cumulative funding at each of the prior perp position closings (from mango API) to get the all time
     * cumulative funding.
     */
    getCumulativeFunding(perpMarket) {
        const funding = this.getUnsettledFunding(perpMarket).toNumber();
        let cumulativeLongFunding = this.cumulativeLongFunding;
        let cumulativeShortFunding = this.cumulativeShortFunding;
        if (this.basePositionLots.toNumber() > 0) {
            cumulativeLongFunding += funding;
        }
        else {
            cumulativeShortFunding -= funding;
        }
        return {
            cumulativeLongFunding: cumulativeLongFunding,
            cumulativeShortFunding: cumulativeShortFunding,
        };
    }
    /**
     * @returns perp position cumulative funding.
     * Caveat: This will only return cumulative interest since the perp position was last opened.
     */
    getCumulativeFundingUi(perpMarket) {
        if (perpMarket.perpMarketIndex !== this.marketIndex) {
            throw new Error("PerpPosition doesn't belong to the given market!");
        }
        const cumulativeFunding = this.getCumulativeFunding(perpMarket);
        return (-1 * (0, utils_1.toUiDecimalsForQuote)(cumulativeFunding.cumulativeLongFunding) +
            (0, utils_1.toUiDecimalsForQuote)(cumulativeFunding.cumulativeShortFunding));
    }
    getEquity(perpMarket) {
        if (perpMarket.perpMarketIndex !== this.marketIndex) {
            throw new Error("PerpPosition doesn't belong to the given market!");
        }
        const lotsToQuote = I80F48_1.I80F48.fromI64(perpMarket.baseLotSize).mul(perpMarket.price);
        const baseLots = I80F48_1.I80F48.fromI64(this.basePositionLots.add(this.takerBaseLots));
        const unsettledFunding = this.getUnsettledFunding(perpMarket);
        const takerQuote = I80F48_1.I80F48.fromI64(new anchor_1.BN(this.takerQuoteLots).mul(perpMarket.quoteLotSize));
        const quoteCurrent = this.quotePositionNative
            .sub(unsettledFunding)
            .add(takerQuote);
        return baseLots.mul(lotsToQuote).add(quoteCurrent);
    }
    getEquityUi(perpMarket) {
        if (perpMarket.perpMarketIndex !== this.marketIndex) {
            throw new Error("PerpPosition doesn't belong to the given market!");
        }
        return (0, utils_1.toUiDecimalsForQuote)(this.getEquity(perpMarket));
    }
    hasOpenOrders() {
        const zero = new anchor_1.BN(0);
        return (!this.asksBaseLots.eq(zero) ||
            !this.bidsBaseLots.eq(zero) ||
            !this.takerBaseLots.eq(zero) ||
            !this.takerQuoteLots.eq(zero));
    }
    getAverageEntryPrice(perpMarket) {
        return I80F48_1.I80F48.fromNumber(this.avgEntryPricePerBaseLot).div(I80F48_1.I80F48.fromI64(perpMarket.baseLotSize));
    }
    getAverageEntryPriceUi(perpMarket) {
        return perpMarket.priceNativeToUi(this.getAverageEntryPrice(perpMarket).toNumber());
    }
    getLiquidationPrice(group, mangoAccount) {
        if (this.basePositionLots.eq(new anchor_1.BN(0))) {
            return null;
        }
        return healthCache_1.HealthCache.fromMangoAccount(group, mangoAccount).getPerpPositionLiquidationPrice(group, mangoAccount, this);
    }
    getLiquidationPriceUi(group, mangoAccount) {
        const pm = group.getPerpMarketByMarketIndex(this.marketIndex);
        const lp = this.getLiquidationPrice(group, mangoAccount);
        return lp == null ? null : pm.priceNativeToUi(lp.toNumber());
    }
    getBreakEvenPrice(perpMarket) {
        if (perpMarket.perpMarketIndex !== this.marketIndex) {
            throw new Error("PerpPosition doesn't belong to the given market!");
        }
        if (this.basePositionLots.eq(new anchor_1.BN(0))) {
            return (0, I80F48_1.ZERO_I80F48)();
        }
        return I80F48_1.I80F48.fromI64(this.quoteRunningNative)
            .sub(this.getUnsettledFunding(perpMarket))
            .neg()
            .div(I80F48_1.I80F48.fromI64(this.basePositionLots.mul(perpMarket.baseLotSize)));
    }
    getBreakEvenPriceUi(perpMarket) {
        return perpMarket.priceNativeToUi(this.getBreakEvenPrice(perpMarket).toNumber());
    }
    canSettlePnl(group, perpMarket, account) {
        return !this.getSettleablePnl(group, perpMarket, account).eq((0, I80F48_1.ZERO_I80F48)());
    }
    updateSettleLimit(perpMarket) {
        if (perpMarket.perpMarketIndex !== this.marketIndex) {
            throw new Error("PerpPosition doesn't belong to the given market!");
        }
        const windowSize = perpMarket.settlePnlLimitWindowSizeTs;
        const windowStart = new anchor_1.BN(this.settlePnlLimitWindow).mul(windowSize);
        const windowEnd = windowStart.add(windowSize);
        const nowTs = new anchor_1.BN(Date.now() / 1000);
        const newWindow = nowTs.gte(windowEnd) || nowTs.lt(windowStart);
        if (newWindow) {
            this.settlePnlLimitWindow = nowTs.div(windowSize).toNumber();
            this.settlePnlLimitSettledInCurrentWindowNative = new anchor_1.BN(0);
        }
    }
    availableSettleLimit(perpMarket) {
        if (perpMarket.perpMarketIndex !== this.marketIndex) {
            throw new Error("PerpPosition doesn't belong to the given market!");
        }
        if (perpMarket.settlePnlLimitFactor < 0) {
            return [(0, constants_1.RUST_I64_MIN)(), (0, constants_1.RUST_I64_MAX)()];
        }
        const baseNative = I80F48_1.I80F48.fromI64(this.basePositionLots.mul(perpMarket.baseLotSize)).abs();
        const positionValue = I80F48_1.I80F48.fromNumber(perpMarket.stablePriceModel.stablePrice)
            .mul(baseNative)
            .toNumber();
        const unrealized = new anchor_1.BN(perpMarket.settlePnlLimitFactor * positionValue);
        let maxPnl = unrealized.add(this.recurringSettlePnlAllowance.abs());
        let minPnl = maxPnl.neg();
        const oneshot = this.oneshotSettlePnlAllowance;
        if (!oneshot.isNeg()) {
            maxPnl = maxPnl.add(new anchor_1.BN(oneshot.ceil().toNumber()));
        }
        else {
            minPnl = minPnl.add(new anchor_1.BN(oneshot.floor().toNumber()));
        }
        const used = new anchor_1.BN(this.settlePnlLimitSettledInCurrentWindowNative.toNumber());
        const availableMin = anchor_1.BN.min(minPnl.sub(used), new anchor_1.BN(0));
        const availableMax = anchor_1.BN.max(maxPnl.sub(used), new anchor_1.BN(0));
        return [availableMin, availableMax];
    }
    applyPnlSettleLimit(pnl, perpMarket) {
        if (perpMarket.perpMarketIndex !== this.marketIndex) {
            throw new Error("PerpPosition doesn't belong to the given market!");
        }
        if (perpMarket.settlePnlLimitFactor < 0) {
            return pnl;
        }
        const [minPnl, maxPnl] = this.availableSettleLimit(perpMarket);
        if (pnl.lt((0, I80F48_1.ZERO_I80F48)())) {
            return pnl.max(I80F48_1.I80F48.fromI64(minPnl));
        }
        else {
            return pnl.min(I80F48_1.I80F48.fromI64(maxPnl));
        }
    }
    getUnsettledPnl(perpMarket) {
        if (perpMarket.perpMarketIndex !== this.marketIndex) {
            throw new Error("PerpPosition doesn't belong to the given market!");
        }
        return this.quotePositionNative.add(this.getBasePosition(perpMarket).mul(perpMarket.price));
    }
    getUnsettledPnlUi(perpMarket) {
        return (0, utils_1.toUiDecimalsForQuote)(this.getUnsettledPnl(perpMarket));
    }
    getSettleablePnl(group, perpMarket, account) {
        if (perpMarket.perpMarketIndex !== this.marketIndex) {
            throw new Error("PerpPosition doesn't belong to the given market!");
        }
        this.updateSettleLimit(perpMarket);
        const perpMaxSettle = account.perpMaxSettle(group, perpMarket.settleTokenIndex);
        const limitedUnsettled = this.applyPnlSettleLimit(this.getUnsettledPnl(perpMarket), perpMarket);
        if (limitedUnsettled.lt((0, I80F48_1.ZERO_I80F48)())) {
            return limitedUnsettled.max(perpMaxSettle.max((0, I80F48_1.ZERO_I80F48)()).neg());
        }
        return limitedUnsettled;
    }
    getSettleablePnlUi(group, perpMarket, account) {
        return (0, utils_1.toUiDecimalsForQuote)(this.getSettleablePnl(group, perpMarket, account));
    }
    cumulativePnlOverPositionLifetimeUi(perpMarket) {
        if (perpMarket.perpMarketIndex !== this.marketIndex) {
            throw new Error("PerpPosition doesn't belong to the given market!");
        }
        const priceChange = perpMarket.price.sub(this.getAverageEntryPrice(perpMarket));
        return (0, utils_1.toUiDecimalsForQuote)(this.realizedPnlForPositionNative.add(this.getBasePosition(perpMarket).mul(priceChange)));
    }
    getUnRealizedPnlUi(perpMarket) {
        if (perpMarket.perpMarketIndex !== this.marketIndex) {
            throw new Error("PerpPosition doesn't belong to the given market!");
        }
        const priceChange = perpMarket.price.sub(this.getAverageEntryPrice(perpMarket));
        return (0, utils_1.toUiDecimalsForQuote)(this.getBasePosition(perpMarket).mul(priceChange));
    }
    getRealizedPnlUi() {
        return (0, utils_1.toUiDecimalsForQuote)(this.realizedPnlForPositionNative);
    }
    toString(perpMarket) {
        return perpMarket
            ? 'market - ' +
                perpMarket.name +
                ', basePositionLots - ' +
                perpMarket.baseLotsToUi(this.basePositionLots) +
                ', quotePositive - ' +
                (0, utils_1.toUiDecimalsForQuote)(this.quotePositionNative.toNumber()) +
                ', bidsBaseLots - ' +
                perpMarket.baseLotsToUi(this.bidsBaseLots) +
                ', asksBaseLots - ' +
                perpMarket.baseLotsToUi(this.asksBaseLots) +
                ', takerBaseLots - ' +
                perpMarket.baseLotsToUi(this.takerBaseLots) +
                ', takerQuoteLots - ' +
                perpMarket.quoteLotsToUi(this.takerQuoteLots) +
                ', unsettled pnl - ' +
                this.getUnsettledPnlUi(perpMarket).toString() +
                ', average entry price ui - ' +
                this.getAverageEntryPriceUi(perpMarket).toString() +
                ', notional value ui - ' +
                this.getNotionalValueUi(perpMarket).toString() +
                ', cumulative pnl over position lifetime ui - ' +
                this.cumulativePnlOverPositionLifetimeUi(perpMarket).toString() +
                ', oneshot settleable native ui - ' +
                (0, utils_1.toUiDecimalsForQuote)(this.oneshotSettlePnlAllowance) +
                ', recurring settleable native ui - ' +
                (0, utils_1.toUiDecimalsForQuote)(this.recurringSettlePnlAllowance) +
                ', cumulative long funding ui - ' +
                (0, utils_1.toUiDecimalsForQuote)(this.cumulativeLongFunding) +
                ', cumulative short funding ui - ' +
                (0, utils_1.toUiDecimalsForQuote)(this.cumulativeShortFunding)
            : '';
    }
}
exports.PerpPosition = PerpPosition;
class PerpPositionDto {
    marketIndex;
    settlePnlLimitWindow;
    settlePnlLimitSettledInCurrentWindowNative;
    basePositionLots;
    quotePositionNative;
    quoteRunningNative;
    longSettledFunding;
    shortSettledFunding;
    bidsBaseLots;
    asksBaseLots;
    takerBaseLots;
    takerQuoteLots;
    cumulativeLongFunding;
    cumulativeShortFunding;
    makerVolume;
    takerVolume;
    perpSpotTransfers;
    avgEntryPricePerBaseLot;
    deprecatedRealizedTradePnlNative;
    oneshotSettlePnlAllowance;
    recurringSettlePnlAllowance;
    realizedPnlForPositionNative;
    constructor(marketIndex, settlePnlLimitWindow, settlePnlLimitSettledInCurrentWindowNative, basePositionLots, quotePositionNative, quoteRunningNative, longSettledFunding, shortSettledFunding, bidsBaseLots, asksBaseLots, takerBaseLots, takerQuoteLots, cumulativeLongFunding, cumulativeShortFunding, makerVolume, takerVolume, perpSpotTransfers, avgEntryPricePerBaseLot, deprecatedRealizedTradePnlNative, oneshotSettlePnlAllowance, recurringSettlePnlAllowance, realizedPnlForPositionNative) {
        this.marketIndex = marketIndex;
        this.settlePnlLimitWindow = settlePnlLimitWindow;
        this.settlePnlLimitSettledInCurrentWindowNative = settlePnlLimitSettledInCurrentWindowNative;
        this.basePositionLots = basePositionLots;
        this.quotePositionNative = quotePositionNative;
        this.quoteRunningNative = quoteRunningNative;
        this.longSettledFunding = longSettledFunding;
        this.shortSettledFunding = shortSettledFunding;
        this.bidsBaseLots = bidsBaseLots;
        this.asksBaseLots = asksBaseLots;
        this.takerBaseLots = takerBaseLots;
        this.takerQuoteLots = takerQuoteLots;
        this.cumulativeLongFunding = cumulativeLongFunding;
        this.cumulativeShortFunding = cumulativeShortFunding;
        this.makerVolume = makerVolume;
        this.takerVolume = takerVolume;
        this.perpSpotTransfers = perpSpotTransfers;
        this.avgEntryPricePerBaseLot = avgEntryPricePerBaseLot;
        this.deprecatedRealizedTradePnlNative = deprecatedRealizedTradePnlNative;
        this.oneshotSettlePnlAllowance = oneshotSettlePnlAllowance;
        this.recurringSettlePnlAllowance = recurringSettlePnlAllowance;
        this.realizedPnlForPositionNative = realizedPnlForPositionNative;
    }
}
exports.PerpPositionDto = PerpPositionDto;
class PerpOo {
    sideAndTree;
    orderMarket;
    clientId;
    id;
    static OrderMarketUnset = 65535;
    static from(dto) {
        return new PerpOo(dto.sideAndTree, dto.market, dto.clientId, dto.id);
    }
    constructor(sideAndTree, orderMarket, clientId, id) {
        this.sideAndTree = sideAndTree;
        this.orderMarket = orderMarket;
        this.clientId = clientId;
        this.id = id;
    }
    isActive() {
        return this.orderMarket !== PerpOo.OrderMarketUnset;
    }
}
exports.PerpOo = PerpOo;
class PerpOoDto {
    sideAndTree;
    market;
    clientId;
    id;
    constructor(sideAndTree, market, clientId, id) {
        this.sideAndTree = sideAndTree;
        this.market = market;
        this.clientId = clientId;
        this.id = id;
    }
}
exports.PerpOoDto = PerpOoDto;
// eslint-disable-next-line @typescript-eslint/no-namespace
var TokenConditionalSwapDisplayPriceStyle;
(function (TokenConditionalSwapDisplayPriceStyle) {
    TokenConditionalSwapDisplayPriceStyle.sellTokenPerBuyToken = { sellTokenPerBuyToken: {} };
    TokenConditionalSwapDisplayPriceStyle.buyTokenPerSellToken = { buyTokenPerSellToken: {} };
})(TokenConditionalSwapDisplayPriceStyle || (exports.TokenConditionalSwapDisplayPriceStyle = TokenConditionalSwapDisplayPriceStyle = {}));
// eslint-disable-next-line @typescript-eslint/no-namespace
var TokenConditionalSwapIntention;
(function (TokenConditionalSwapIntention) {
    TokenConditionalSwapIntention.unknown = { unknown: {} };
    TokenConditionalSwapIntention.stopLoss = { stopLoss: {} };
    TokenConditionalSwapIntention.takeProfit = { takeProfit: {} };
})(TokenConditionalSwapIntention || (exports.TokenConditionalSwapIntention = TokenConditionalSwapIntention = {}));
function tokenConditionalSwapIntentionFromDto(intention) {
    switch (intention) {
        case 0:
            return TokenConditionalSwapIntention.unknown;
        case 1:
            return TokenConditionalSwapIntention.stopLoss;
        case 2:
            return TokenConditionalSwapIntention.takeProfit;
        default:
            throw new Error(`unexpected token conditional swap intention: ${intention}`);
    }
}
class TokenConditionalSwap {
    id;
    maxBuy;
    maxSell;
    bought;
    sold;
    expiryTimestamp;
    priceLowerLimit;
    priceUpperLimit;
    pricePremiumRate;
    takerFeeRate;
    makerFeeRate;
    buyTokenIndex;
    sellTokenIndex;
    isConfigured;
    allowCreatingDeposits;
    allowCreatingBorrows;
    priceDisplayStyle;
    intention;
    static from(dto) {
        return new TokenConditionalSwap(dto.id, dto.maxBuy, dto.maxSell, dto.bought, dto.sold, dto.expiryTimestamp, dto.priceLowerLimit, dto.priceUpperLimit, dto.pricePremiumRate, dto.takerFeeRate, dto.makerFeeRate, dto.buyTokenIndex, dto.sellTokenIndex, dto.isConfigured == 1, dto.allowCreatingDeposits == 1, dto.allowCreatingBorrows == 1, dto.displayPriceStyle == 0
            ? TokenConditionalSwapDisplayPriceStyle.sellTokenPerBuyToken
            : TokenConditionalSwapDisplayPriceStyle.buyTokenPerSellToken, tokenConditionalSwapIntentionFromDto(dto.intention));
    }
    constructor(id, maxBuy, maxSell, bought, sold, expiryTimestamp, priceLowerLimit, priceUpperLimit, pricePremiumRate, takerFeeRate, makerFeeRate, buyTokenIndex, sellTokenIndex, isConfigured, allowCreatingDeposits, allowCreatingBorrows, priceDisplayStyle, intention) {
        this.id = id;
        this.maxBuy = maxBuy;
        this.maxSell = maxSell;
        this.bought = bought;
        this.sold = sold;
        this.expiryTimestamp = expiryTimestamp;
        this.priceLowerLimit = priceLowerLimit;
        this.priceUpperLimit = priceUpperLimit;
        this.pricePremiumRate = pricePremiumRate;
        this.takerFeeRate = takerFeeRate;
        this.makerFeeRate = makerFeeRate;
        this.buyTokenIndex = buyTokenIndex;
        this.sellTokenIndex = sellTokenIndex;
        this.isConfigured = isConfigured;
        this.allowCreatingDeposits = allowCreatingDeposits;
        this.allowCreatingBorrows = allowCreatingBorrows;
        this.priceDisplayStyle = priceDisplayStyle;
        this.intention = intention;
    }
    getMaxBuyUi(group) {
        const buyBank = this.getBuyToken(group);
        return (0, utils_1.toUiDecimals)(this.maxBuy, buyBank.mintDecimals);
    }
    getMaxSellUi(group) {
        const sellBank = this.getSellToken(group);
        return (0, utils_1.toUiDecimals)(this.maxSell, sellBank.mintDecimals);
    }
    getBoughtUi(group) {
        const buyBank = this.getBuyToken(group);
        return (0, utils_1.toUiDecimals)(this.bought, buyBank.mintDecimals);
    }
    getSoldUi(group) {
        const sellBank = this.getSellToken(group);
        return (0, utils_1.toUiDecimals)(this.sold, sellBank.mintDecimals);
    }
    getExpiryTimestampInEpochSeconds() {
        return this.expiryTimestamp.toNumber();
    }
    priceLimitToUi(group, sellTokenPerBuyTokenNative) {
        const buyBank = this.getBuyToken(group);
        const sellBank = this.getSellToken(group);
        const sellTokenPerBuyTokenUi = (0, utils_1.toUiSellPerBuyTokenPrice)(sellTokenPerBuyTokenNative, sellBank, buyBank);
        // Below are workarounds to know when to show an inverted price in ui
        // We want to identify if the pair user is wanting to trade is
        // buytoken/selltoken or selltoken/buytoken
        // Buy limit / close short
        if (this.priceDisplayStyle ==
            TokenConditionalSwapDisplayPriceStyle.sellTokenPerBuyToken) {
            return (0, utils_1.roundTo5)(sellTokenPerBuyTokenUi);
        }
        // Stop loss / take profit
        const buyTokenPerSellTokenUi = 1 / sellTokenPerBuyTokenUi;
        return (0, utils_1.roundTo5)(buyTokenPerSellTokenUi);
    }
    getPriceLowerLimitUi(group) {
        return this.priceLimitToUi(group, this.priceLowerLimit);
    }
    getPriceUpperLimitUi(group) {
        return this.priceLimitToUi(group, this.priceUpperLimit);
    }
    getThresholdPriceUi(group) {
        const buyBank = this.getBuyToken(group);
        const sellBank = this.getSellToken(group);
        const a = (0, utils_1.toUiSellPerBuyTokenPrice)(this.priceLowerLimit, sellBank, buyBank);
        const b = (0, utils_1.toUiSellPerBuyTokenPrice)(this.priceUpperLimit, sellBank, buyBank);
        const o = buyBank.uiPrice / sellBank.uiPrice;
        // Choose the price closest to oracle
        if (Math.abs(o - a) < Math.abs(o - b)) {
            return this.getPriceLowerLimitUi(group);
        }
        return this.getPriceUpperLimitUi(group);
    }
    getCurrentPairPriceUi(group) {
        const buyBank = this.getBuyToken(group);
        const sellBank = this.getSellToken(group);
        const sellTokenPerBuyTokenUi = (0, utils_1.toUiSellPerBuyTokenPrice)(buyBank.price.div(sellBank.price).toNumber(), sellBank, buyBank);
        // Below are workarounds to know when to show an inverted price in ui
        // We want to identify if the pair user is wanting to trade is
        // buytoken/selltoken or selltoken/buytoken
        // Buy limit / close short
        if (this.priceDisplayStyle ==
            TokenConditionalSwapDisplayPriceStyle.sellTokenPerBuyToken) {
            return (0, utils_1.roundTo5)(sellTokenPerBuyTokenUi);
        }
        // Stop loss / take profit
        const buyTokenPerSellTokenUi = 1 / sellTokenPerBuyTokenUi;
        return (0, utils_1.roundTo5)(buyTokenPerSellTokenUi);
    }
    // in percent
    getPricePremium() {
        return this.pricePremiumRate * 100;
    }
    getCurrentlySuggestedPremium(group) {
        const buyBank = this.getBuyToken(group);
        const sellBank = this.getSellToken(group);
        return TokenConditionalSwap.computePremium(group, buyBank, sellBank, this.maxBuy, this.maxSell, this.getMaxBuyUi(group), this.getMaxSellUi(group));
    }
    static computePremium(group, buyBank, sellBank, maxBuy, maxSell, maxBuyUi, maxSellUi) {
        const buyAmountInUsd = maxBuy != utils_1.U64_MAX_BN
            ? maxBuyUi * buyBank.uiPrice
            : Number.MAX_SAFE_INTEGER;
        const sellAmountInUsd = maxSell != utils_1.U64_MAX_BN
            ? maxSellUi * sellBank.uiPrice
            : Number.MAX_SAFE_INTEGER;
        // Used for computing optimal premium
        let liqorTcsChunkSizeInUsd = Math.min(buyAmountInUsd, sellAmountInUsd);
        if (liqorTcsChunkSizeInUsd > 5000) {
            liqorTcsChunkSizeInUsd = 5000;
        }
        // For small TCS swaps, reduce chunk size to 1000 USD
        else {
            liqorTcsChunkSizeInUsd = 1000;
        }
        const buyTokenPriceImpact = group.getPriceImpactByTokenIndex(buyBank.tokenIndex, liqorTcsChunkSizeInUsd);
        const sellTokenPriceImpact = group.getPriceImpactByTokenIndex(sellBank.tokenIndex, liqorTcsChunkSizeInUsd);
        if (buyTokenPriceImpact <= 0 || sellTokenPriceImpact <= 0) {
            throw new Error(`Error compitong slippage/premium for token conditional swap!`);
        }
        return (((1 + buyTokenPriceImpact / 100) * (1 + sellTokenPriceImpact / 100) - 1) *
            100);
    }
    getBuyToken(group) {
        return group.getFirstBankByTokenIndex(this.buyTokenIndex);
    }
    getSellToken(group) {
        return group.getFirstBankByTokenIndex(this.sellTokenIndex);
    }
    getAllowCreatingDeposits() {
        return this.allowCreatingDeposits;
    }
    getAllowCreatingBorrows() {
        return this.allowCreatingBorrows;
    }
    toString(group) {
        return `${group.getFirstBankByTokenIndex(this.buyTokenIndex).name +
            '/' +
            group.getFirstBankByTokenIndex(this.sellTokenIndex).name} , getMaxBuy ${this.getMaxBuyUi(group)}, getMaxSell ${this.getMaxSellUi(group)}, bought ${this.getBoughtUi(group)}, sold ${this.getSoldUi(group)}, getPriceLowerLimitUi ${this.getPriceLowerLimitUi(group)},  getPriceUpperLimitUi ${this.getPriceUpperLimitUi(group)}, getCurrentPairPriceUi ${this.getCurrentPairPriceUi(group)}, getThresholdPriceUi ${this.getThresholdPriceUi(group)}, getPricePremium ${this.getPricePremium()}, expiry ${this.expiryTimestamp.toString()}`;
    }
}
exports.TokenConditionalSwap = TokenConditionalSwap;
class TokenConditionalSwapDto {
    id;
    maxBuy;
    maxSell;
    bought;
    sold;
    expiryTimestamp;
    priceLowerLimit;
    priceUpperLimit;
    pricePremiumRate;
    takerFeeRate;
    makerFeeRate;
    buyTokenIndex;
    sellTokenIndex;
    isConfigured;
    allowCreatingDeposits;
    allowCreatingBorrows;
    displayPriceStyle;
    intention;
    constructor(id, maxBuy, maxSell, bought, sold, expiryTimestamp, priceLowerLimit, priceUpperLimit, pricePremiumRate, takerFeeRate, makerFeeRate, buyTokenIndex, sellTokenIndex, isConfigured, allowCreatingDeposits, allowCreatingBorrows, displayPriceStyle, intention) {
        this.id = id;
        this.maxBuy = maxBuy;
        this.maxSell = maxSell;
        this.bought = bought;
        this.sold = sold;
        this.expiryTimestamp = expiryTimestamp;
        this.priceLowerLimit = priceLowerLimit;
        this.priceUpperLimit = priceUpperLimit;
        this.pricePremiumRate = pricePremiumRate;
        this.takerFeeRate = takerFeeRate;
        this.makerFeeRate = makerFeeRate;
        this.buyTokenIndex = buyTokenIndex;
        this.sellTokenIndex = sellTokenIndex;
        this.isConfigured = isConfigured;
        this.allowCreatingDeposits = allowCreatingDeposits;
        this.allowCreatingBorrows = allowCreatingBorrows;
        this.displayPriceStyle = displayPriceStyle;
        this.intention = intention;
    }
}
exports.TokenConditionalSwapDto = TokenConditionalSwapDto;
class HealthType {
    static maint = { maint: {} };
    static init = { init: {} };
    static liquidationEnd = { liquidationEnd: {} };
}
exports.HealthType = HealthType;
