@d8x/perpetuals-sdk
Version:
Node TypeScript SDK for D8X Perpetual Futures
778 lines • 32.2 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.pmFindMaxPersonalTradeSizeAtLeverage = exports.pmFindLiquidationPrice = exports.pmExcessBalance = exports.pmMarginBalance = exports.pmExchangeFee = exports.expectedLoss = exports.pmInitialMarginRate = exports.pmMaintenanceMarginRate = exports.entropy = exports.probToPrice = exports.priceToProb = 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 {boolean} isPredMkt - true if prediction market
* @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, isPredMkt) {
let pnl = (tradeAmnt * (S2Mark - price)) / S3;
let S2MarkBefore = S2Mark;
if (isPredMkt) {
// adjust mark price to 'probability'
S2Mark = S2Mark - 1;
S2MarkBefore = S2Mark;
if (pos0 < 0) {
S2MarkBefore = 1 - S2Mark;
}
if (pos0 + tradeAmnt < 0) {
S2Mark = 1 - S2Mark;
}
}
if (targetLvg == 0) {
// use current leverage
targetLvg = (Math.abs(pos0) * S2MarkBefore) / S3 / b0;
}
let b = (Math.abs(pos0 + tradeAmnt) * S2Mark) / S3 / targetLvg;
return -(b0 + pnl - b);
}
exports.getDepositAmountForLvgTrade = getDepositAmountForLvgTrade;
/**
* 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 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 s2 mark price
* @param s3 collateral to quote conversion
* @param m base margin rate
* @returns required margin balance
*/
function pmMarginThresh(pos, s2, s3, m = 0.18) {
let p = s2 - 1;
if (pos < 0) {
p = 1 - p;
}
const h = entropy(p);
const tau = m + (0.4 - m) * h;
return (Math.abs(pos) * p * tau) / s3;
}
/**
* Maintenance margin rate for prediction markets.
* @param posSign sign of position in base currency (can be signed position or -1, 1)
* @param sm mark-price (=1+p)
* @param m max margin rate from fInitialMarginRate
* @returns margin rate to be applied (Math.abs(pos) * p * tau) / s3;
*/
function pmMaintenanceMarginRate(posSign, sm, m = 0.18) {
let p = sm - 1;
if (posSign < 0) {
p = 1 - p;
}
const h = entropy(p);
return m + (0.4 - m) * h;
}
exports.pmMaintenanceMarginRate = pmMaintenanceMarginRate;
/**
* Maintenance margin rate for prediction markets.
* @param posSign sign of position in base currency (can be signed position or -1, 1)
* @param sm mark-price (=1+p)
* @param m max margin rate from fMaintenanceMarginRate
* @returns margin rate to be applied (Math.abs(pos) * p * tau) / s3;
*/
function pmInitialMarginRate(posSign, sm, m = 0.2) {
let p = sm - 1;
if (posSign < 0) {
p = 1 - p;
}
const h = entropy(p);
return m + (0.5 - m) * h;
}
exports.pmInitialMarginRate = pmInitialMarginRate;
/**
* Calculate the expected loss for a prediction market trade used for
* prediction market fees
* @param p probability derived from mark price (long)
* @param m maximal maintenance rate from which we defer the actual maintenance margin rate
* @param totLong total long in base currency
* @param totShort total short
* @param tradeAmt signed trade amount, can be zero
* @param tradeMgnRate margin rate of the trader
* @returns expected loss in dollars
*/
function expectedLoss(p, m, totLong, totShort, tradeAmt, tradeMgnRate) {
// maintenance margin rate
m = (0.4 - m) * entropy(p) + m;
let dlm = 0;
let dl = 0;
let dsm = 0;
let ds = 0;
if (tradeAmt > 0) {
dlm = p * tradeAmt * tradeMgnRate;
dl = tradeAmt;
}
else if (tradeAmt < 0) {
dsm = (1 - p) * Math.abs(tradeAmt) * tradeMgnRate;
ds = Math.abs(tradeAmt);
}
const a = dl + totLong - m * totShort - dsm;
const b = ds + totShort - m * totLong - dlm;
return p * (1 - p) * Math.max(0, a + b);
}
exports.expectedLoss = expectedLoss;
/**
* Equivalent to
* const el0 = expectedLoss(prob, m, totLong, totShort, 0, 0);
* const el1 = expectedLoss(prob, m, totLong, totShort, tradeAmt, tradeMgnRate)
* const fee = (el1 - el0) / Math.abs(tradeAmt);
* @param p 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
*/
function expectedLossImpact(p, m, tradeAmt, tradeMgnRate) {
m = (0.4 - m) * entropy(p) + m;
let dlm = 0;
let dl = 0;
let dsm = 0;
let ds = 0;
if (tradeAmt > 0) {
dlm = p * tradeAmt * tradeMgnRate;
dl = tradeAmt;
}
else if (tradeAmt < 0) {
dsm = (1 - p) * Math.abs(tradeAmt) * tradeMgnRate;
ds = Math.abs(tradeAmt);
}
//long: p * (1 - p) max(0, dl-dlm) = p * (1 - p) max(0, tradeAmt - p * tradeAmt * tradeMgnRate)
const a = dl - dsm;
const b = ds - dlm;
return p * (1 - p) * Math.max(0, a + b);
}
/**
* 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) {
/*
equivalent:
const el0 = expectedLoss(prob, m, totLong, totShort, 0, 0);
const el1 = expectedLoss(prob, m, totLong, totShort, tradeAmt, tradeMgnRate);
const fee = (el1 - el0) / Math.abs(tradeAmt);
*/
let fee = expectedLossImpact(prob, m, tradeAmt, tradeMgnRate) / Math.abs(tradeAmt);
return Math.max(fee, 0.001);
}
exports.pmExchangeFee = pmExchangeFee;
/**
* 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, s2, s3, m);
}
exports.pmExcessBalance = pmExcessBalance;
// finds the liquidation price for prediction markets
// using Newton's algorithm
function pmFindLiquidationPrice(pos, s3, ell, mc, baseMarginRate, s2Start = 0.5) {
const delta_s = 0.01;
let s = 100;
let s_new = s2Start;
while (Math.abs(s_new - s) > 0.01) {
s = s_new;
const f = Math.pow(pmExcessBalance(pos, s, s3, ell, mc, baseMarginRate), 2);
const ds = (Math.pow(pmExcessBalance(pos, s + delta_s, s3, ell, mc, baseMarginRate), 2) - f) / delta_s;
s_new = s - f / ds;
if (s_new < 1) {
return 1;
}
if (s_new > 2) {
return 2;
}
}
return s;
}
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 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) {
//determine deposit amount for given leverage
const limitPrice = S2 * (1 + Math.sign(tradeAmt) * slippage);
const depositFromWallet = pmGetDepositAmtForLvgTrade(tradeAmt, lvg, limitPrice, S3, Sm);
const m0 = 0.18;
//leverage fee
let p0 = Sm - 1;
if (tradeAmt < 0) {
p0 = 2 - Sm; //=1-(Sm-1)
}
const feeCc = (Math.abs(tradeAmt) * pmExchangeFee(p0, m0, 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;
if (pos < 0) {
p = 2 - Sm;
}
const h = entropy(p);
const tau = m0 + (0.5 - m0) * h;
const thresh = Math.abs(pos) * p * tau;
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;
//# sourceMappingURL=d8XMath.js.map