@d8x/perpetuals-sdk
Version:
Node TypeScript SDK for D8X Perpetual Futures
1,026 lines (1,023 loc) • 43.9 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.pmMaxSignedOpenTradeSize = exports.pmFindMaxPersonalTradeSizeAtLeverage = exports.pmFindLiquidationPrice = exports.pmExcessBalance = exports.pmMarginBalance = exports.erfc = exports.decodePriceImpact = exports.extractLvgFeeParams = exports.pmOpenFee = exports.pmExitFee = exports.pmExchangeFee = exports.pmInitialMarginRate = exports.pmMaintenanceMarginRate = exports.entropy = exports.probToPrice = exports.priceToProb = exports.getDepositAmountForPredMktLvgTrade = exports.getDepositAmountForLvgTrade = exports.getNewPositionLeverage = exports.getMaxSignedPositionSize = exports.getMarginRequiredForLeveragedTrade = exports.calculateLiquidationPriceCollateralQuote = exports.calculateLiquidationPriceCollateralQuanto = exports.calculateLiquidationPriceCollateralQuantoConservative = exports.calculateLiquidationPriceCollateralBase = exports.div64x64 = exports.mul64x64 = exports.roundToLotString = exports.countDecimalsOf = exports.floatToDecN = exports.floatToDec18 = exports.floatToABK64x64 = exports.dec18ToFloat = exports.decNToFloat = exports.ABK64x64ToFloat = exports.ABDK29ToFloat = void 0;
const constants_1 = require("./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)
*/
function ABDK29ToFloat(x) {
return Number(x) / 2 ** 29;
}
exports.ABDK29ToFloat = ABDK29ToFloat;
/**
* 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)
*/
function ABK64x64ToFloat(x) {
if (typeof x == "number") {
return x / 2 ** 29;
}
let s = x < 0n ? -1n : 1n;
x = x * s;
let xInt = x / constants_1.ONE_64x64;
let dec18 = 10n ** 18n; // BigNumber.from(10).pow(BigNumber.from(18));
let xDec = x - xInt * constants_1.ONE_64x64;
xDec = (xDec * dec18) / constants_1.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);
}
exports.ABK64x64ToFloat = ABK64x64ToFloat;
/**
*
* @param {BigNumberish} x BigNumber in Dec-N format
* @returns {number} x as a float (number)
*/
function decNToFloat(x, numDec) {
//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);
}
exports.decNToFloat = decNToFloat;
/**
*
* @param {BigNumberish} x BigNumber in Dec18 format
* @returns {number} x as a float (number)
*/
function dec18ToFloat(x) {
//x: BigNumber in Dec18 format to float
x = BigInt(x);
let s = x < 0n ? -1n : 1n;
x = x * s;
let xInt = x / constants_1.DECIMALS;
let xDec = x - xInt * constants_1.DECIMALS;
let k = 18 - xDec.toString().length;
let sPad = "0".repeat(k);
let NumberStr = xInt.toString() + "." + sPad + xDec.toString();
return parseFloat(NumberStr) * Number(s);
}
exports.dec18ToFloat = dec18ToFloat;
/**
* Converts x into ABDK64x64 format
* @param {number} x number (float)
* @returns {bigint} x^64 in big number format
*/
function floatToABK64x64(x) {
// 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 * constants_1.ONE_64x64;
let dec18 = 10n ** 18n; //BigNumber.from(10).pow(BigNumber.from(18));
let xDecBig = (xDec * constants_1.ONE_64x64) / dec18;
return (xIntBig + xDecBig) * BigInt(sg);
}
exports.floatToABK64x64 = floatToABK64x64;
/**
*
* @param {number} x number (float)
* @returns {BigNumber} x as a BigNumber in Dec18 format
*/
function floatToDec18(x) {
// 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 * constants_1.DECIMALS;
return (xIntBig + xDec) * BigInt(sg);
}
exports.floatToDec18 = floatToDec18;
/**
*
* @param {number} x number (float)
* @param {number} decimals number of decimals
* @returns {BigNumber} x as a BigNumber in Dec18 format
*/
function floatToDecN(x, decimals) {
// 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);
}
exports.floatToDecN = floatToDecN;
/**
* 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
*/
function countDecimalsOf(x, precision) {
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;
}
exports.countDecimalsOf = countDecimalsOf;
/**
* 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
*/
function roundToLotString(x, lot, precision = 7) {
// 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);
}
exports.roundToLotString = roundToLotString;
/**
*
* @param {bigint} x
* @param {bigint} y
* @returns {bigint} x * y
*/
function mul64x64(x, y) {
return (x * y) / constants_1.ONE_64x64;
}
exports.mul64x64 = mul64x64;
/**
*
* @param {bigint} x
* @param {bigint} y
* @returns {bigint} x / y
*/
function div64x64(x, y) {
return (x * constants_1.ONE_64x64) / y;
}
exports.div64x64 = div64x64;
/**
* 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
*/
function calculateLiquidationPriceCollateralBase(LockedInValueQC, position, cash_cc, maintenance_margin_rate) {
// 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);
}
exports.calculateLiquidationPriceCollateralBase = calculateLiquidationPriceCollateralBase;
/**
* 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
*/
function calculateLiquidationPriceCollateralQuantoConservative(LockedInValueQC, position, cash_cc, maintenance_margin_rate, S3, Sm) {
// 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;
}
exports.calculateLiquidationPriceCollateralQuantoConservative = calculateLiquidationPriceCollateralQuantoConservative;
/**
* 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
*/
function calculateLiquidationPriceCollateralQuanto(LockedInValueQC, position, cash_cc, maintenance_margin_rate, S3, Sm) {
// 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;
}
exports.calculateLiquidationPriceCollateralQuanto = calculateLiquidationPriceCollateralQuanto;
/**
* 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
*/
function calculateLiquidationPriceCollateralQuote(LockedInValueQC, position, cash_cc, maintenance_margin_rate) {
// 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;
}
exports.calculateLiquidationPriceCollateralQuote = calculateLiquidationPriceCollateralQuote;
/**
*
* @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.
*/
function getMarginRequiredForLeveragedTrade(targetLeverage, currentPosition, currentLockedInValue, tradeAmount, markPrice, indexPriceS2, indexPriceS3, tradePrice, feeRate) {
// 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;
}
exports.getMarginRequiredForLeveragedTrade = getMarginRequiredForLeveragedTrade;
function getMaxSignedPositionSize(marginCollateral, currentPosition, currentLockedInValue, direction, limitPrice, initialMarginRate, feeRate, markPrice, indexPriceS2, indexPriceS3) {
// 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);
}
exports.getMaxSignedPositionSize = getMaxSignedPositionSize;
/**
* 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
*/
function getNewPositionLeverage(tradeAmount, marginCollateral, currentPosition, currentLockedInValue, price, indexPriceS3, markPrice) {
let newPosition = tradeAmount + currentPosition;
let pnlQC = currentPosition * markPrice - currentLockedInValue + tradeAmount * (markPrice - price);
return (Math.abs(newPosition) * markPrice) / (marginCollateral * indexPriceS3 + pnlQC);
}
exports.getNewPositionLeverage = getNewPositionLeverage;
/**
* 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
*/
function getDepositAmountForLvgTrade(pos0, b0, tradeAmnt, targetLvg, price, S3, S2Mark, cmin) {
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);
}
exports.getDepositAmountForLvgTrade = getDepositAmountForLvgTrade;
/**
* 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
*/
function getDepositAmountForPredMktLvgTrade(pos0, b0, c0, tradeAmnt, targetLvg, prob, S3, markProb, imr) {
/**
* 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);
}
exports.getDepositAmountForPredMktLvgTrade = getDepositAmountForPredMktLvgTrade;
function pmMaxLeverage(posSign, markProb, minMarginPerCtrct) {
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]
*/
function priceToProb(px) {
return px - 1;
}
exports.priceToProb = priceToProb;
/**
* Convert a probability to a predictive market price
* @param prob Probability in [0,1]
* @returns Perpetual price
*/
function probToPrice(prob) {
return Math.max(1, Math.min(2, 1 + prob));
}
exports.probToPrice = probToPrice;
// shannon entropy
function entropy(prob) {
if (prob < 1e-15 || prob > 1 - 1e-15) {
return 0;
}
return -prob * Math.log2(prob) - (1 - prob) * Math.log2(1 - prob);
}
exports.entropy = entropy;
/**
* 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, lockedInQC, s2, s3, m) {
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;
function pmMaintenanceMarginRate(position, lockedInQC, sm, m) {
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);
}
}
exports.pmMaintenanceMarginRate = pmMaintenanceMarginRate;
/**
* 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`
*/
function pmInitialMarginRate(posSign, s0, sm, cmin) {
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;
}
exports.pmInitialMarginRate = pmInitialMarginRate;
/**
* 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
*/
function pmExchangeFee(prob, m, tradeAmt, tradeMgnRate) {
// 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);
}
exports.pmExchangeFee = pmExchangeFee;
function pmExitFee(varphi, varphi_0, m_0Exit, mu_m, mu_i, sigt, jump) {
// 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;
}
exports.pmExitFee = pmExitFee;
function pmOpenFee(varphi_0, m_0, mu_m, sigt, jump) {
// 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;
}
exports.pmOpenFee = pmOpenFee;
function firstNonZeroNum(numDec) {
let pos = 0n;
let temp = numDec;
while (temp > 0n) {
temp /= 10n;
pos++;
}
return pos;
}
function decodeUint16Float(num) {
// 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)) * constants_1.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 * constants_1.ONE_64x64) ** Number(exponent1);
if (sgnE == 1n) {
// v = v.div(ABDKMath64x64.pow(10 * ONE_64x64, uint256(exponent)));
v = v / ABK64x64ToFloat(10n * constants_1.ONE_64x64) ** Number(exponent);
}
else {
// v = v.mul(ABDKMath64x64.pow(10 * ONE_64x64, uint256(exponent)));
v = v * ABK64x64ToFloat(10n * constants_1.ONE_64x64) ** Number(exponent);
}
if (sgnNum == 1n) {
// v = v.neg();
v = -v;
}
// console.log({ num, v, fV: floatToABK64x64(v) });
return v;
}
function decodeUint24Float(num) {
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);
}
function extractLvgFeeParams(conf) {
// _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 };
}
exports.extractLvgFeeParams = extractLvgFeeParams;
function decodePriceImpact(amount, params) {
// 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;
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 };
}
exports.decodePriceImpact = decodePriceImpact;
function calcKappa(varphi, varphiLiq, jump) {
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, varphi, jump) {
jump = Math.floor(jump / 0.025) * 0.025;
const jr = jump * (1 - varphi * (1 - kappa ** 2));
return jr;
}
function prdMktLvgFee(kappa, varphi, m0) {
const f = (1 - kappa ** 2) * varphi - (1 - Math.log(varphi) * (1 - kappa ** 2)) * Math.max(m0, varphi);
return f;
}
function erfc(x) {
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;
}
exports.erfc = erfc;
function cdfNormalStd(x) {
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
*/
function pmMarginBalance(pos, s2, s3, ell, mc) {
return (pos * s2) / s3 - ell / s3 + mc;
}
exports.pmMarginBalance = pmMarginBalance;
function pmExcessBalance(pos, s2, s3, ell, mc, m) {
return pmMarginBalance(pos, s2, s3, ell, mc) - pmMarginThresh(pos, ell, s2, s3, m);
}
exports.pmExcessBalance = pmExcessBalance;
/**
*
* @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]
*/
function pmFindLiquidationPrice(pos, s3, ell, mc, baseMarginRate) {
// 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);
}
exports.pmFindLiquidationPrice = pmFindLiquidationPrice;
/**
* 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, currentCashCC, currentPos, currentLockedInQC, limitPrice, Sm, S3) {
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, targetLvg, price, S3, S2Mark) {
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, lvg, walletBalCC, currentCashCC, currentPosition, currentLockedInValue, slippage, S2, Sm, S3) {
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
*/
function pmFindMaxPersonalTradeSizeAtLeverage(dir, lvg, walletBalCC, slippage, currentPosition, currentCashCC, currentLockedInValue, S2, Sm, S3, glblMaxTrade) {
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;
}
exports.pmFindMaxPersonalTradeSizeAtLeverage = pmFindMaxPersonalTradeSizeAtLeverage;
/**
* 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)
*/
function pmMaxSignedOpenTradeSize(long, short, sm, isBuy, mr, ammFundsQC) {
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;
}
exports.pmMaxSignedOpenTradeSize = pmMaxSignedOpenTradeSize;
//# sourceMappingURL=d8XMath.js.map