UNPKG

@d8x/perpetuals-sdk

Version:

Node TypeScript SDK for D8X Perpetual Futures

1,237 lines (1,149 loc) 41.9 kB
import { BigNumberish } from "ethers"; import { DECIMALS, ONE_64x64 } from "./constants"; /** * @module d8xMath */ /** * Convert ABK64x64/2^35 bigint-format to float. * Divide by 2^64 to get a float, but it's already "divided" by 2^35, * so there's only 2^29 left * @param {BigNumber|number} x number in ABDK-format/2^35 * @returns {number} x/2^64 in number-format (float) */ export function ABDK29ToFloat(x: bigint | number): number { return Number(x) / 2 ** 29; } /** * Convert ABK64x64 bigint-format to float. * Result = x/2^64 if big number, x/2^29 if number * @param {BigNumberish|number} x number in ABDK-format or 2^29 * @returns {number} x/2^64 in number-format (float) */ export function ABK64x64ToFloat(x: bigint | number): number { if (typeof x == "number") { return x / 2 ** 29; } let s = x < 0n ? -1n : 1n; x = x * s; let xInt = x / ONE_64x64; let dec18 = 10n ** 18n; // BigNumber.from(10).pow(BigNumber.from(18)); let xDec = x - xInt * ONE_64x64; xDec = (xDec * dec18) / ONE_64x64; let k = 18 - xDec.toString().length; // console.assert(k >= 0); let sPad = "0".repeat(k); let NumberStr = xInt.toString() + "." + sPad + xDec.toString(); return parseFloat(NumberStr) * Number(s); } /** * * @param {BigNumberish} x BigNumber in Dec-N format * @returns {number} x as a float (number) */ export function decNToFloat(x: BigNumberish, numDec: BigNumberish): number { //x: BigNumber in DecN format to float const DECIMALS = 10n ** BigInt(numDec); // BigNumber.from(10).pow(BigNumber.from(numDec)); x = BigInt(x); numDec = BigInt(numDec); let s = x < 0n ? -1n : 1n; x = x * s; let xInt = x / DECIMALS; let xDec = x - xInt * DECIMALS; let k = Number(numDec) - xDec.toString().length; let sPad = "0".repeat(k); let NumberStr = xInt.toString() + "." + sPad + xDec.toString(); return parseFloat(NumberStr) * Number(s); } /** * * @param {BigNumberish} x BigNumber in Dec18 format * @returns {number} x as a float (number) */ export function dec18ToFloat(x: BigNumberish): number { //x: BigNumber in Dec18 format to float x = BigInt(x); let s = x < 0n ? -1n : 1n; x = x * s; let xInt = x / DECIMALS; let xDec = x - xInt * DECIMALS; let k = 18 - xDec.toString().length; let sPad = "0".repeat(k); let NumberStr = xInt.toString() + "." + sPad + xDec.toString(); return parseFloat(NumberStr) * Number(s); } /** * Converts x into ABDK64x64 format * @param {number} x number (float) * @returns {bigint} x^64 in big number format */ export function floatToABK64x64(x: number): bigint { // convert float to ABK64x64 bigint-format // Create string from number with 18 decimals if (x === 0) { return 0n; } let sg = Math.sign(x); x = Math.abs(x); let strX = Number(x).toFixed(18); const arrX = strX.split("."); let xInt = BigInt(arrX[0]); let xDec = BigInt(arrX[1]); let xIntBig = xInt * ONE_64x64; let dec18 = 10n ** 18n; //BigNumber.from(10).pow(BigNumber.from(18)); let xDecBig = (xDec * ONE_64x64) / dec18; return (xIntBig + xDecBig) * BigInt(sg); } /** * * @param {number} x number (float) * @returns {BigNumber} x as a BigNumber in Dec18 format */ export function floatToDec18(x: number): bigint { // float number to dec 18 if (x === 0) { return 0n; } let sg = Math.sign(x); x = Math.abs(x); let strX = x.toFixed(18); const arrX = strX.split("."); let xInt = BigInt(arrX[0]); let xDec = BigInt(arrX[1]); let xIntBig = xInt * DECIMALS; return (xIntBig + xDec) * BigInt(sg); } /** * * @param {number} x number (float) * @param {number} decimals number of decimals * @returns {BigNumber} x as a BigNumber in Dec18 format */ export function floatToDecN(x: number, decimals: number): bigint { // float number to dec 18 if (x === 0) { return 0n; } let sg = Math.sign(x); x = Math.abs(x); let strX = x.toFixed(decimals); const arrX = strX.split("."); let xInt = BigInt(arrX[0]); let xDec = BigInt(arrX[1]); let xIntBig = xInt * 10n ** BigInt(decimals); return (xIntBig + xDec) * BigInt(sg); } /** * 9 are rounded up regardless of precision, e.g, 0.1899000 at precision 6 results in 3 * @param {number} x * @param {number} precision * @returns number of decimals */ export function countDecimalsOf(x: number, precision: number): number { let decimalPart = x - Math.floor(x); if (decimalPart == 0) { return 0; } let decimalPartStr = decimalPart.toFixed(precision); // remove trailing zeros let c = decimalPartStr.charAt(decimalPartStr.length - 1); while (c == "0") { decimalPartStr = decimalPartStr.substring(0, decimalPartStr.length - 1); c = decimalPartStr.charAt(decimalPartStr.length - 1); } // remove trailing 9 c = decimalPartStr.charAt(decimalPartStr.length - 1); while (c == "9") { decimalPartStr = decimalPartStr.substring(0, decimalPartStr.length - 1); c = decimalPartStr.charAt(decimalPartStr.length - 1); } return decimalPartStr.length > 2 ? decimalPartStr.length - 2 : 0; } /** * Round a number to a given lot size and return a string formated * to for this lot-size * @param {number} x number to round * @param {number} lot lot size (could be 'uneven' such as 0.019999999 instead of 0.02) * @param {number} precision optional lot size precision (e.g. if 0.01999 should be 0.02 then precision could be 5) * @returns formated number string */ export function roundToLotString(x: number, lot: number, precision: number = 7): string { // round lot to precision let lotRounded = Math.round(lot / 10 ** -precision) * 10 ** -precision; let v = Math.round(x / lotRounded) * lotRounded; // number of digits of rounded lot let numDig = countDecimalsOf(lotRounded, precision); return v.toFixed(numDig); } /** * * @param {bigint} x * @param {bigint} y * @returns {bigint} x * y */ export function mul64x64(x: bigint, y: bigint): bigint { return (x * y) / ONE_64x64; } /** * * @param {bigint} x * @param {bigint} y * @returns {bigint} x / y */ export function div64x64(x: bigint, y: bigint): bigint { return (x * ONE_64x64) / y; } /** * Determine the liquidation price * @param {number} LockedInValueQC - trader locked in value in quote currency * @param {number} position - trader position in base currency * @param {number} cash_cc - trader available margin cash in collateral currency * @param {number} maintenance_margin_rate - maintenance margin ratio * @param {number} S3 - collateral to quote conversion (=S2 if base-collateral, =1 if quuote collateral, = index S3 if quanto) * @returns {number} Amount to be deposited to have the given leverage when trading into position pos */ export function calculateLiquidationPriceCollateralBase( LockedInValueQC: number, position: number, cash_cc: number, maintenance_margin_rate: number ): number { // correct only if markprice = spot price // m_r <= (Sm * Pi - L + cash * S3) / (Sm * |Pi|) // -> Sm * (Pi + cash - m_r|Pi|) => L return LockedInValueQC / (position - maintenance_margin_rate * Math.abs(position) + cash_cc); } /** * Determine the liquidation price * @param {number} LockedInValueQC - trader locked in value in quote currency * @param {number} position - trader position in base currency * @param {number} cash_cc - trader available margin cash in collateral currency * @param {number} maintenance_margin_rate - maintenance margin ratio * @param {number} S3 - collateral to quote conversion (=S2 if base-collateral, =1 if quuote collateral, = index S3 if quanto) * @param {number} Sm - mark price * @returns {number} Amount to be deposited to have the given leverage when trading into position pos */ export function calculateLiquidationPriceCollateralQuantoConservative( LockedInValueQC: number, position: number, cash_cc: number, maintenance_margin_rate: number, S3: number, Sm: number ): number { // correct only if markprice = spot price and S3 co-moves with Sm // m_r = (Sm * Pi - L + cash * S3) / (Sm * |Pi|) // m_r = [Sm * Pi - L + cash * S3(0) * (1 + sign(Pi) (Sm / Sm(0) - 1)] / (Sm * |Pi|) // -> Sm * (m_r |Pi| - Pi - cash * S3(0) * sign(Pi) / Sm(0)) = - L + cash * S3(0) * (1 - sign(Pi)) let numerator = -LockedInValueQC + cash_cc * S3 * (1 - Math.sign(position)); let denominator = maintenance_margin_rate * Math.abs(position) - position - (cash_cc * S3 * Math.sign(position)) / Sm; return numerator / denominator; } /** * Determine the liquidation price for quanto -- assuming quanto currency value remains constant * See calculateLiquidationPriceCollateralQuantoConservative for a more conservative version * @param {number} LockedInValueQC - trader locked in value in quote currency * @param {number} position - trader position in base currency * @param {number} cash_cc - trader available margin cash in collateral currency * @param {number} maintenance_margin_rate - maintenance margin ratio * @param {number} S3 - collateral to quote conversion (=S2 if base-collateral, =1 if quuote collateral, = index S3 if quanto) * @param {number} Sm - mark price * @returns {number} Amount to be deposited to have the given leverage when trading into position pos */ export function calculateLiquidationPriceCollateralQuanto( LockedInValueQC: number, position: number, cash_cc: number, maintenance_margin_rate: number, S3: number, Sm: number ): number { // correct only if markprice = spot price and S3 co-moves with Sm // m_r = (Sm * Pi - L + cash * S3) / (Sm * |Pi|) // -> Sm (m_r * |position| - position) = -L + cash *S3 // -> Sm = Sm = (-L + cash * S3)/(m_r * |position| - position) let numerator = -LockedInValueQC + cash_cc * S3; let denominator = maintenance_margin_rate * Math.abs(position) - position; return numerator / denominator; } /** * Determine the liquidation price * @param {number} LockedInValueQC - trader locked in value in quote currency * @param {number} position - trader position in base currency * @param {number} cash_cc - trader available margin cash in collateral currency * @param {number} maintenance_margin_rate - maintenance margin ratio * @param {number} S3 - collateral to quote conversion (=S2 if base-collateral, =1 if quuote collateral, = index S3 if quanto) * @returns {number} Amount to be deposited to have the given leverage when trading into position pos */ export function calculateLiquidationPriceCollateralQuote( LockedInValueQC: number, position: number, cash_cc: number, maintenance_margin_rate: number ): number { // m_r = (Sm * Pi - L + cash ) / (Sm * |Pi|) // -> Sm * (m_r |Pi| - Pi) = - L + cash let numerator = -LockedInValueQC + cash_cc; let denominator = maintenance_margin_rate * Math.abs(position) - position; return numerator / denominator; } /** * * @param targetLeverage Leverage of the resulting position. It must be positive unless the resulting position is closed. * @param currentPosition Current position size, in base currency, signed. * @param currentLockedInValue Current locked in value, average entry price times position size, in quote currency. * @param tradeAmount Trade amount, in base currency, signed. * @param markPrice Mark price, positive. * @param indexPriceS2 Index price, positive. * @param indexPriceS3 Collateral index price, positive. * @param tradePrice Expected price to trade tradeAmount. * @param feeRate * @returns {number} Total collateral amount needed for the new position to have he desired leverage. */ export function getMarginRequiredForLeveragedTrade( targetLeverage: number | undefined, currentPosition: number, currentLockedInValue: number, tradeAmount: number, markPrice: number, indexPriceS2: number, indexPriceS3: number, tradePrice: number, feeRate: number ): number { // we solve for margin in: // |new position| * Sm / leverage + fee rate * |trade amount| * S2 = margin * S3 + current position * Sm - L + trade amount * (Sm - trade price) // --> M S3 = |P'|Sm/L + FeeQC - PnL + (P'-P)(Price - Sm) = pos value / leverage + fees + price impact - pnl let isClosing = currentPosition != 0 && currentPosition * tradeAmount < 0 && currentPosition * (currentPosition + tradeAmount) >= 0; let feesCC = (feeRate * Math.abs(tradeAmount) * indexPriceS2) / indexPriceS3; let collRequired = feesCC; if (!isClosing) { if (targetLeverage == undefined || targetLeverage <= 0) { throw Error("opening trades must have positive leverage"); } // unrealized pnl (could be + or -) - price impact premium (+) let pnlQC = currentPosition * markPrice - currentLockedInValue - tradeAmount * (tradePrice - markPrice); collRequired += Math.max(0, (Math.abs(currentPosition + tradeAmount) * markPrice) / targetLeverage - pnlQC) / indexPriceS3; } return collRequired; } export function getMaxSignedPositionSize( marginCollateral: number, currentPosition: number, currentLockedInValue: number, direction: number, limitPrice: number, initialMarginRate: number, feeRate: number, markPrice: number, indexPriceS2: number, indexPriceS3: number ): number { // we solve for new position in: // |new position| * Sm / leverage + fee rate * |trade amount| * S2 = margin * S3 + current position * Sm - L + trade amount * (Sm - entry price) // |trade amount| = (new position - current position) * direction let numerator = marginCollateral * indexPriceS3 + currentPosition * markPrice - currentLockedInValue - Math.abs(currentPosition) * markPrice * initialMarginRate; let denominator = markPrice * initialMarginRate + feeRate * indexPriceS2 + Math.max(0, direction * (limitPrice - markPrice)); return currentPosition + (numerator > 0 ? direction * (numerator / denominator) : 0); } /** * Compute the leverage resulting from a trade * @param tradeAmount Amount to trade, in base currency, signed * @param marginCollateral Amount of cash in the margin account, in collateral currency * @param currentPosition Position size before the trade * @param currentLockedInValue Locked-in value before the trade * @param price Price charged to trade tradeAmount * @param indexPriceS3 Spot price of the collateral currency when the trade happens * @param markPrice Mark price of the index when the trade happens * @returns Leverage of the resulting position */ export function getNewPositionLeverage( tradeAmount: number, marginCollateral: number, currentPosition: number, currentLockedInValue: number, price: number, indexPriceS3: number, markPrice: number ): number { let newPosition = tradeAmount + currentPosition; let pnlQC = currentPosition * markPrice - currentLockedInValue + tradeAmount * (markPrice - price); return (Math.abs(newPosition) * markPrice) / (marginCollateral * indexPriceS3 + pnlQC); } /** * Determine amount to be deposited into margin account so that the given leverage * is obtained when trading a position pos (trade amount = position) * Does NOT include fees * Smart contract equivalent: calcMarginForTargetLeverage(..., _ignorePosBalance = false & balance = b0) * @param {number} pos0 - current position * @param {number} b0 - current balance * @param {number} tradeAmnt - amount to trade * @param {number} targetLvg - target leverage * @param {number} price - price to trade amount 'tradeAmnt' * @param {number} S3 - collateral to quote conversion (=S2 if base-collateral, =1 if quote collateral, = index S3 if quanto) * @param {number} S2Mark - mark price * @param {number} cmin - Absolute minimum margin per contract, only for pred markets * @returns {number} Amount to be deposited to have the given leverage when trading into position pos before fees */ export function getDepositAmountForLvgTrade( pos0: number, b0: number, tradeAmnt: number, targetLvg: number, price: number, S3: number, S2Mark: number, cmin: number | undefined ) { if (cmin && cmin > 0) { // TODO: c0? if (b0 != 0) { console.log("b0 != 0"); } return getDepositAmountForPredMktLvgTrade(pos0, b0, 0, tradeAmnt, targetLvg, price - 1, S3, S2Mark - 1, cmin); } let pnl = (tradeAmnt * (S2Mark - price)) / S3; if (targetLvg == 0) { // use current leverage targetLvg = (Math.abs(pos0) * S2Mark) / S3 / b0; } let b = (Math.abs(pos0 + tradeAmnt) * S2Mark) / S3 / targetLvg; return -(b0 + pnl - b); } /** * Determine amount to be deposited into margin account so that the given leverage * is obtained when opening a prediction market position * Does NOT include fees, but accounts for a possible non-zero current position * Smart contract equivalent: getDepositAmountForPredMktLvgPosition * @param {number} pos0 - current position * @param {number} b0 - current balance * @param {number} c0 - current available cash * @param {number} tradeAmnt - amount to trade * @param {number} targetLvg - target leverage * @param {number} prob - prob to trade amount 'tradeAmnt' * @param {number} S3 - collateral to quote conversion (=S2 if base-collateral, =1 if quote collateral, = index S3 if quanto) * @param {number} markProb - mark prob * @param {number} imr - minimum absolute margin per contract (fInitialMarginRate) * @returns {number} Amount to be deposited to have the given leverage when trading into position pos before fees */ export function getDepositAmountForPredMktLvgTrade( pos0: number, b0: number, c0: number, tradeAmnt: number, targetLvg: number, prob: number, S3: number, markProb: number, imr: number ) { /** * Smart contract implementation: // find smallest x such that: // bal * s3 >= pos value / lvg // where: // pos value / lvg = |pos| * R(pm, sign(pos)) * margin rate // pos = pos0 + k // cash = cash0 + x // ell = ell0 + px * k // bal * s3 = cash * s3 + pos * sm - ell // = bal0 * s3 + x * s3 + k * (sm - px) // subject to: // x >= 0 // cash * s3 >= |pos| * min(cmin, prob(sign(pos))) // k * (sm - px) <= 0 a.s. // (positive pnl does not contribute, i.e. ignore px better than mark) // solution: // bal0 * s3 + x * s3 >= pos value / lvg + (k * (px - sm))_+ = v * s3 // --> // x >= v + (cash0 - bal0)_+ - cash0 = v - min(bal0, cash0) // = pos value / lvg/ s3 + (k * (px - sm))_+ / s3 - min (bal0, cash0) // = A + B - C // x >= |pos| * min(cmin, prob(sign(pos))) / s3 - cash0 // x >= 0 // init x = A = pos value / lvg / s3 int128 fNewPos = _fPosition0.add(_fTradeAmount); int128 v = ( fNewPos > 0 ? fNewPos.mul(_fMarkProb) : fNewPos.neg().mul(ONE_64x64.sub(_fMarkProb)) ).mul(_fMarginRate).div(_fS3); // + B = max(0,k * (px - sm)) / s3 { int128 fPnL = _fTradeAmount.mul(_fMarkProb.sub(_fTradeProb)); if (fPnL < 0) { v = v.sub(fPnL.div(_fS3)); // pnl < 0 -> increase v } } // - C = - min(bal0, cash0) = - Equity { int128 equity = _fCash0CC < _fBalance0 ? _fCash0CC : _fBalance0; v = v.sub(equity); // equity can be used / must be covered if negative } return v > 0 ? v : int128(0); */ const newPos = pos0 + tradeAmnt; const posProb = newPos > 0 ? markProb : 1 - markProb; // R(pm, sign(new pos)) const maxLvg = pmMaxLeverage(newPos, markProb, imr); targetLvg = targetLvg > maxLvg ? maxLvg : targetLvg; const posValue = (Math.abs(newPos) * posProb) / S3; const tradeLoss = Math.max(0, tradeAmnt * (prob - markProb)) / S3; const curEquity = Math.min(c0, b0); return Math.max(posValue / targetLvg + tradeLoss - curEquity, 0); } function pmMaxLeverage(posSign: number, markProb: number, minMarginPerCtrct: number) { return Math.round(100 * (posSign > 0 ? markProb / minMarginPerCtrct : (1 - markProb) / minMarginPerCtrct)) / 100; } /** * Convert a perpetual price to probability (predtictive markets) * @param px Perpetual price * @returns Probability in [0,1] */ export function priceToProb(px: number) { return px - 1; } /** * Convert a probability to a predictive market price * @param prob Probability in [0,1] * @returns Perpetual price */ export function probToPrice(prob: number) { return Math.max(1, Math.min(2, 1 + prob)); } // shannon entropy export function entropy(prob: number) { if (prob < 1e-15 || prob > 1 - 1e-15) { return 0; } return -prob * Math.log2(prob) - (1 - prob) * Math.log2(1 - prob); } /** * Maintenance margin requirement for prediction markets * @param pos signed position * @param lockedInQC locked in value * @param s2 mark price * @param s3 collateral to quote conversion * @param m base margin rate * @returns required margin balance */ function pmMarginThresh(pos: number, lockedInQC: number, s2: number, s3: number, m: number): number { return (pmMaintenanceMarginRate(pos, lockedInQC, s2, m) * Math.abs(pos)) / s3; } /** * Maintenance margin rate for prediction markets. * @param position signed position in base currency * @param lockedInQC locked in value, p or 1-p times number of contracts * @param sm mark-price (=1+p) * @param m absolute maintenance buffer per contract (mu_m, fMaintenanceMarginRate) * @returns {number} The margin rate to be applied: (Math.abs(pos) * p * tau) / s3 */ pmExchangeFee; export function pmMaintenanceMarginRate(position: number, lockedInQC: number, sm: number, m: number): number { let pm = sm - 1; let entryP = position == 0 ? pm : Math.abs(lockedInQC / position) - 1; if (position < 0) { pm = 1 - pm; entryP = 1 - entryP; } const L = Math.max(entryP - pm, 0); if (position == 0) { return Math.min(m + L, entryP) / pm; } else { const balAtLiq = Math.min(m + L, entryP) * Math.abs(position) + (position * sm - lockedInQC); return balAtLiq / pm / Math.abs(position); } } /** * Initial margin rate for prediction markets. * @param posSign sign of position in base currency (can be signed position or -1, 1) * @param s0 trade price * @param sm mark-price (=1+p) * @param cmin Absolute min margin saved as `fInitialMarginRate` * @returns {number} The margin rate to be applied: `(Math.abs(pos) * p * tau) / s3` */ export function pmInitialMarginRate(posSign: number, s0: number, sm: number, cmin: number): number { let pm = sm - 1; let p0 = s0 - 1; if (posSign < 0) { pm = 1 - pm; // R(p_mark, sign(pos)) p0 = 1 - p0; // R(p_entry, sign(pos)) } // mu0 = max(Rm/lvg, min(Rm, cmin)) // balance = (mu0 * |k| + k *(sm - s0)) / s3 // pos value = |k| * Rm / s3 // at max init lvg: Rm/lvg = min(cmin, Rm) // --> margin rate = (mu0 + Rm - R0) / Rm const mu0 = Math.min(pm, cmin) + Math.max(0, p0 - pm); return (mu0 + pm - p0) / pm; } /** * Exchange fee as a rate for prediction markets * For opening trades only * @param prob long probability * @param m max maintenance margin rate (0.18) * @param tradeAmt trade amount in base currency * @param tradeMgnRate margin rate for this trade * @returns dollar fee relative to tradeAmt */ export function pmExchangeFee(prob: number, m: number, tradeAmt: number, tradeMgnRate: number): number { // TODO: port contract logic here const [kappa, es] = [0, 0]; prob = tradeAmt > 0 ? prob : 1 - prob; let fee = prob * (1 - kappa); const scaledLvg = prob * tradeMgnRate * (1 - fee); fee = fee * (1 - prob) - scaledLvg + es; return Math.max(fee, 0.001); } export function pmExitFee( varphi: number, varphi_0: number, m_0Exit: number, mu_m: number, mu_i: number, sigt: number, jump: number ) { // We implement equation (10) from leveraged binary markets paper - full closure fee only ! // pLiq and kappa for existing position, varphi is the entry price of the existing position const pLiq = varphi + mu_m - m_0Exit; const kappa = calcKappa(varphi_0, pLiq, jump); let fee = -prdMktLvgFee(kappa, varphi, m_0Exit); return fee; } export function pmOpenFee(varphi_0: number, m_0: number, mu_m: number, sigt: number, jump: number) { // eq (4) leveraged prediction markets paper: const varphiLiq = varphi_0 + mu_m - m_0; const kappa = calcKappa(varphi_0, varphiLiq, jump); //console.log("[pmOpenFee] kappa:", kappa, "varphiLiq:", varphiLiq, "m_0:", m_0); let fee = prdMktLvgFee(kappa, varphi_0, m_0); const es = calcJumpRisk(kappa, varphi_0, jump); fee = fee + es; if (fee < 0.001) { fee = 0.001; } return fee; } function firstNonZeroNum(numDec: bigint) { let pos = 0n; let temp = numDec; while (temp > 0n) { temp /= 10n; pos++; } return pos; } function decodeUint16Float(num: bigint) { // uint16 sgnNum = num >> 15; const sgnNum = BigInt.asUintN(16, num >> 15n); // uint16 sgnE = (num >> 14) & 1; const sgnE = BigInt.asUintN(16, (num >> 14n) & 1n); // uint16 val = (num >> 4) & ((2 ** 10) - 1); const val = BigInt.asUintN(16, (num >> 4n) & (2n ** 10n - 1n)); // uint16 exponent = num & ((2 ** 4) - 1); const exponent = BigInt.asUintN(16, num & (2n ** 4n - 1n)); // //convert val abcde to normalized form a.bcde // int128 v = int128(uint128(val)) * ONE_64x64; let v = ABK64x64ToFloat(BigInt.asIntN(128, BigInt.asUintN(128, val)) * ONE_64x64); // uint256 exponent1 = first_nonzeronum(val); let exponent1 = firstNonZeroNum(val); if (exponent1 > 0n) { exponent1 -= 1n; } // v = v.div(ABDKMath64x64.pow(10 * ONE_64x64, exponent1)); v = v / ABK64x64ToFloat(10n * ONE_64x64) ** Number(exponent1); if (sgnE == 1n) { // v = v.div(ABDKMath64x64.pow(10 * ONE_64x64, uint256(exponent))); v = v / ABK64x64ToFloat(10n * ONE_64x64) ** Number(exponent); } else { // v = v.mul(ABDKMath64x64.pow(10 * ONE_64x64, uint256(exponent))); v = v * ABK64x64ToFloat(10n * ONE_64x64) ** Number(exponent); } if (sgnNum == 1n) { // v = v.neg(); v = -v; } // console.log({ num, v, fV: floatToABK64x64(v) }); return v; } function decodeUint24Float(num: number | bigint) { const n = BigInt(num); const ONE_64x64 = 2n ** 64n; // Extract sign/exponent flag from bit 22 const sgnE = n >> 22n; // Extract 20-bit value from bits 2-21 const val = (n >> 2n) & ((1n << 21n) - 1n); // Extract 3-bit exponent from bits 0-2 const exponent = n & ((1n << 2n) - 1n); // v = val * ONE_64x64 let v = BigInt.asIntN(128, val * ONE_64x64); // Normalize: find first non-zero digit position and adjust let exponent1 = firstNonZeroNum(val); // Don't decrement to preserve precision // if (exponent1 > 0n) exponent1--; if (exponent1 > 0n) { const pow10exp1 = 10n ** exponent1 * ONE_64x64; v = (v + ONE_64x64) / pow10exp1; } // Apply exponent based on sign flag if (sgnE == 0n) { const pow10exp = 10n ** exponent * ONE_64x64; v = (v * ONE_64x64) / pow10exp; } else { const pow10exp = 10n ** exponent * ONE_64x64; v = (v * pow10exp) / ONE_64x64; } return ABK64x64ToFloat(v); } export function extractLvgFeeParams(conf: bigint) { // _param == int128(odinvalue >> 8) const param = BigInt.asIntN(128, conf >> 8n); // uint64(uint128(_param)) const enc = BigInt.asUintN(128, BigInt.asUintN(128, param)); // uint64 st = enc >> 43; //top 4 bits const st = BigInt.asUintN(64, enc >> 43n); // uint64 j = enc & ((uint64(1) << 45) - 1); const j = BigInt.asUintN(64, enc & ((1n << 45n) - 1n)); // jump = int128(int64(j)) * int128(922337203685477580); // times 0.025 in ABDK // 461168601842738790 = 0.025 * 2^64 const JUMP_MULTIPLIER = 461168601842738790n; // 0.025 in ABDK const jumpABDK = BigInt.asIntN(128, BigInt.asIntN(64, j)) * JUMP_MULTIPLIER; const jump = ABK64x64ToFloat(jumpABDK); // sigT = _decodeUint24Float(uint32(st & ((1 << 32) - 1))); const sigtEncoded = BigInt.asUintN(32, st & ((1n << 32n) - 1n)); const sigt = decodeUint24Float(Number(sigtEncoded)); //console.log("[extractLvgFeeParams] jump:", jump, "sigt:", sigt); return { jump, sigt }; } export function decodePriceImpact(amount: bigint, params: bigint) { // uint32 params; // if (_amount > 0) { // params = uint32(_params & ((2 ** 32) - 1)); // } else { // params = uint32(_params >> 32); // } // console.log({ params2sided: params }); params = BigInt.asUintN( 32, amount > 0n ? BigInt.asUintN(32, params) & BigInt.asUintN(32, 2n ** 32n - 1n) : BigInt.asUintN(32, params) >> 32n ); // console.log({ amountSign: amount > 0n, params }); // int128 a = decodeUint16Float(uint16(params >> 16)); // int128 m = decodeUint16Float(uint16(params & ((2 ** 16) - 1))); // int128 l = a.add(_amount.abs().mul(m)); const a = decodeUint16Float(BigInt.asUintN(16, params >> 16n)); const m = decodeUint16Float(BigInt.asUintN(16, params & (2n ** 16n - 1n))); const l = a + Math.abs(ABK64x64ToFloat(amount)) * m; // if (l < 0x200000000000000000) { // // here if impact is not close to overflow // return _amount < 0 ? -l.exp() : l.exp(); // } let dp: number; if (l < ABK64x64ToFloat(BigInt("0x200000000000000000"))) { dp = amount < 0n ? -Math.exp(l) : Math.exp(l); } else { dp = (amount < 0n ? -1 : 1) * ABK64x64ToFloat(BigInt("0x40000000000000000000000000000000")); } // // return a very big number // return // _amount < 0 // ? -int128(0x40000000000000000000000000000000) // : int128(0x40000000000000000000000000000000); return { a, m, l, dp }; } function calcKappa(varphi: number, varphiLiq: number, jump: number): number { const varphiBar = 1 - varphi; const varphiBar_star = 1 - varphiLiq; const date = new Date(); const time = date.getTime() / 1000; const jumpSqrtTime = jump * Math.sqrt(time); const kappa = Math.sqrt(2) * (1 - cdfNormalStd((varphiBar - varphiBar_star) / jumpSqrtTime)); return kappa; } function calcJumpRisk(kappa: number, varphi: number, jump: number): number { jump = Math.floor(jump / 0.025) * 0.025; const jr = jump * (1 - varphi * (1 - kappa ** 2)); return jr; } function prdMktLvgFee(kappa: number, varphi: number, m0: number): number { const f = (1 - kappa ** 2) * varphi - (1 - Math.log(varphi) * (1 - kappa ** 2)) * Math.max(m0, varphi); return f; } export function erfc(x: number): number { if (x < -10) { return 0; } if (x > 10) { return 1; } const z = Math.abs(x); const t = 1 / (1 + z / 2); // prettier-ignore const r = t * Math.exp(-z * z - 1.26551223 + t * (1.00002368 + t * (0.37409196 + t * (0.09678418 + t * (-0.18628806 + t * (0.27886807 + t * (-1.13520398 + t * (1.48851587 + t * (-0.82215223 + t * 0.17087277))))))))); return x >= 0 ? r : 2 - r; } function cdfNormalStd(x: number) { return 0.5 * erfc(-x / Math.sqrt(2)); } /** * Margin balance for prediction markets * @param pos signed position * @param s2 mark price * @param s3 collateral to quote conversion * @param ell locked in value * @param mc margin cash in collateral currency * @returns current margin balance */ export function pmMarginBalance(pos: number, s2: number, s3: number, ell: number, mc: number): number { return (pos * s2) / s3 - ell / s3 + mc; } export function pmExcessBalance(pos: number, s2: number, s3: number, ell: number, mc: number, m: number): number { return pmMarginBalance(pos, s2, s3, ell, mc) - pmMarginThresh(pos, ell, s2, s3, m); } /** * * @param pos Signed position size * @param s3 Collateral to quote conversion at spot * @param ell Locked-in value * @param mc Margin collateral * @param baseMarginRate Maintenance margin per contract (mu_m) * @param sm Mark price at entry * @returns {number} Liquidation price as a probability in the range [0, 1] */ export function pmFindLiquidationPrice( pos: number, s3: number, ell: number, mc: number, baseMarginRate: number ): number { // liq <--> (A) c / |k| < R0 && (B) E < |k| * mu_m // if not (A), return 0 (long) or 1 (short) [no liq] // else, solve for pm: // E = c - |k| max(0, s * (p0 - pm)) = |k| * mu_m // if c/|k| < mu_m: // any number would do --> return 1 (long) or 0 (short) // else: // pm = p0 - s * (c/|k| - mu_m) const p0 = Math.abs(ell / pos) - 1; const R0 = pos > 0 ? p0 : 1 - p0; const excessPerCtrct = (mc * s3) / Math.abs(pos) - baseMarginRate; // c/|k| - mu_m, mu_m < CMINUS if (mc * s3 > R0 * Math.abs(pos)) { // c > |k| R(p0, s) --> no liquidation return probToPrice(pos > 0 ? 0.0001 : 0.9999); } if (excessPerCtrct < 0) { // already underwater return probToPrice(pos > 0 ? 0.9999 : 0.0001); } return probToPrice(pos > 0 ? p0 - excessPerCtrct : p0 + excessPerCtrct); } /** * Calculate the excess margin defined as * excess := margin balance - trading fee - initial margin threshold * for the given trade and position * @param tradeAmt * @param currentCashCC * @param currentPos * @param currentLockedInQC * @param limitPrice * @param Sm * @param S3 * @returns excess margin as defined above */ function excessMargin( tradeAmt: number, currentCashCC: number, currentPos: number, currentLockedInQC: number, limitPrice: number, Sm: number, S3: number ): number { const m = 0.18; //max maintenance margin rate const m0 = 0.2; //max initial margin rate const pos = currentPos + tradeAmt; let p = Sm - 1; if (pos < 0) { p = 2 - Sm; //=1-(Sm-1) } const h = entropy(p); const tau = m0 + (0.5 - m0) * h; const thresh = Math.abs(pos) * p * tau; const b0 = currentCashCC + Math.abs(currentPos) * Sm - currentLockedInQC + Math.max(0, tradeAmt * (Sm - limitPrice)); // b0 + margin - fee > threshold // margin = threshold - b0 + fee const fee_cc = pmExchangeFee(p, m, tradeAmt, tau) / S3; // missing: referral rebate return b0 / S3 - thresh / S3 - fee_cc; } /** * Internal function to find the deposit amount required * for a given trade amount and target leverage * @param tradeAmt * @param targetLvg * @param price * @param S3 * @param S2Mark * @returns deposit amount */ function pmGetDepositAmtForLvgTrade( tradeAmt: number, targetLvg: number, price: number, S3: number, S2Mark: number ): number { const cmin = 0.05; // refer to main contract function for this: return getDepositAmountForPredMktLvgTrade(0, 0, 0, tradeAmt, targetLvg, price - 1, S3, S2Mark - 1, cmin); // const pnl = (tradeAmt * (S2Mark - price)) / S3; // let p = S2Mark - 1; // if (tradeAmt < 0) { // p = 1 - p; // } // const b = (Math.abs(tradeAmt) * p) / S3 / targetLvg; // const amt = -(pnl - b); // // check: // //bal = amt+pnl // //pos_val = (np.abs(trade_amt) * p) / S3 // //lvg = pos_val/bal // //assert(np.abs(lvg-targetLvg)<0.1) // return amt; } /** * Internal function to calculate cash over initial margin rate * after a trade of size tradeAmt in prediction markets * @param tradeAmt * @param lvg * @param walletBalCC * @param currentCashCC * @param currentPosition * @param currentLockedInValue * @param slippage * @param S2 * @param Sm * @param S3 * @param totLong * @param totShort * @returns excess cash */ function pmExcessCashAtLvg( tradeAmt: number, lvg: number, walletBalCC: number, currentCashCC: number, currentPosition: number, currentLockedInValue: number, slippage: number, S2: number, Sm: number, S3: number ): number { const cmin = 0.05; const mu_m = 0.01; const maxLvg = pmMaxLeverage(currentPosition + tradeAmt, Sm - 1, cmin); lvg = lvg < maxLvg ? lvg : maxLvg; //determine deposit amount for given leverage const limitPrice = S2 * (1 + Math.sign(tradeAmt) * slippage); const depositFromWallet = pmGetDepositAmtForLvgTrade(tradeAmt, lvg, limitPrice, S3, Sm); //leverage fee let p0 = Sm - 1; if (tradeAmt < 0) { p0 = 2 - Sm; //=1-(Sm-1) } const feeCc = (Math.abs(tradeAmt) * pmExchangeFee(p0, mu_m, tradeAmt, 1 / lvg)) / S3; //excess cash let exc = walletBalCC - depositFromWallet - feeCc; if (exc < 0) { return exc; } // margin balance let pos = currentPosition + tradeAmt; let p = Sm - 1; let entryP = limitPrice - 1; if (pos < 0) { p = 1 - Sm; entryP = 1 - entryP; } const mu0 = p / lvg + Math.max(0, entryP - p); const thresh = Math.abs(pos) * mu0; const b0 = depositFromWallet + currentCashCC + Math.abs(currentPosition) * Sm - currentLockedInValue + Math.max(0, tradeAmt * (Sm - limitPrice)); // b0 - fee > threshold // b0 - fee - threshold > 0 // extra_cash = b0 - fee - threshold // missing: referral rebate const bal = b0 / S3 - thresh / S3; exc = exc + bal; return exc; } /** * Find maximal *affordable* trade size (short dir=-1 or long dir=1) for prediction * markets at provided leverage and incorporating the current position * and wallet balance. * Factors in lot size and global max short/long, factors in opening/closing position * @param dir direction of trade (-1 sell, 1 buy) * @param lvg leverage of the trade * @param walletBalCC wallet balance of the trader (collateral currency) * @param slippage slippage percent used to estimate a traded price * @param currentPosition position in base currency of the trader * @param currentCashCC this is the cash available net of unpaid funding (often called available cash) * @param currentLockedInValue average entry price * signed position size in base currency, in margin account * @param S2 current index price of the form 1+p (regardless whether short or long) * @param Sm current mark price (not just the mark price index but including the ema-premium from the contract) * @param S3 current collateral to quote index price * @param glblMaxTrade global max short or long order size that we retreive, e.g., from position risk (sign irrelevant) * based on long: (*ℓ+n) * (1-p) - m (1-p) s = F → n = (F+m*(1-p)*s)/(1-p)-ℓ* * short: (s+n)*p - m p *ℓ* = F →n = (F+m*p**ℓ*)/p-s * @returns max *signed* trade size */ export function pmFindMaxPersonalTradeSizeAtLeverage( dir: number, lvg: number, walletBalCC: number, slippage: number, currentPosition: number, currentCashCC: number, currentLockedInValue: number, S2: number, Sm: number, S3: number, glblMaxTrade: number ): number { if (dir < 0) { dir = -1; } else { dir = 1; } const lot = 10; const deltaS = 1; //for derivative const f0 = pmExcessCashAtLvg( dir * deltaS, lvg, walletBalCC, currentCashCC, currentPosition, currentLockedInValue, slippage, S2, Sm, S3 ); if (f0 < 0) { // no trade possible return 0; } // numerically find maximal trade size let sNew = dir * lot * 10; let s = 2 * sNew; while (true) { let count = 0; while (Math.abs(sNew - s) > 1 && count < 100) { s = sNew; const f = pmExcessCashAtLvg( s, lvg, walletBalCC, currentCashCC, currentPosition, currentLockedInValue, slippage, S2, Sm, S3 ) ** 2; const f2 = pmExcessCashAtLvg( s + deltaS, lvg, walletBalCC, currentCashCC, currentPosition, currentLockedInValue, slippage, S2, Sm, S3 ) ** 2; let ds = (f2 - f) / deltaS; if (ds == 0) { sNew = s + Math.random() * deltaS - deltaS / 2; } else { sNew = s - f / ds; } count += 1; } if (count < 100) { break; } // Newton algorithm failed, // choose new starting value if (dir > 0) { sNew = Math.random() * (glblMaxTrade - currentPosition); } else { sNew = -Math.random() * (Math.abs(glblMaxTrade) + currentPosition); } } // round trade size down to lot sNew = Math.sign(sNew) * Math.floor(Math.abs(sNew) / lot) * lot; // Cases // 1) trading in closing direction // |sNew| >= |currPosition| -> // - if we are flipping, global max applies // - we should at least have currPos because we can always close // max(min(|glblMaxTrade|, |sNew|), |currPos|) // note: closing positions do not add any margin. If liquidations work correctly, // the starting position is at least at maintenance margin and after reducing the // size (which only costs trading fee from the margin), the new position is (without fees) // above maintenance margin. Therefore assuming closing is always affordable // 2) trading in opening dir // min(|glblMaxTrade|,|sNew|) // 2) ensure trade maximal trade sNew does not exceed // the contract limits // if opening trade, adhere to glblMaxTrade sNew = dir * Math.min(Math.abs(sNew), Math.abs(glblMaxTrade)); // 1) // We are trading into closing direction. // So we need to make sure that glblMaxTrade did // not shrink our sNew beyond the position size (per note |sNew|>|currentPosition| when closing) const isClosingDir = currentPosition != 0 && currentPosition * dir < 0; if (isClosingDir) { return dir * Math.max(Math.abs(sNew), Math.abs(currentPosition)); } return sNew; } /** * See PerpetualTradeLogic::_getMaxSignedOpenPredMktTradeSize * @param long Long open OI * @param short Short open OI * @param sm Mark price (>1) * @param isBuy True if trade is long * @param mr Margin threshold per contract for liquidation (mu_m) */ export function pmMaxSignedOpenTradeSize( long: number, short: number, sm: number, isBuy: boolean, mr: number, ammFundsQC: number ) { if (sm <= 1 || sm >= 2) { // closed return 0; } let p = !isBuy ? sm - 1 : 2 - sm; p = p < 0.01 ? 0.01 : p > 0.99 ? 0.99 : p; // same cap as contract return isBuy ? (ammFundsQC + mr * short) / p - long : -(ammFundsQC + mr * long) / p + short; }