From ed09606cc2f7571efadb395836c88873c15ebfaf Mon Sep 17 00:00:00 2001 From: xjcaa <19227506+xjcaa@users.noreply.github.com> Date: Fri, 19 Apr 2024 15:06:31 +1000 Subject: [PATCH] Potential order loss (#387) Co-authored-by: Kelvin Lau --- CHANGELOG.md | 5 ++++ package.json | 2 +- src/idl/zeta.json | 27 ++++++++++++++++++++-- src/program-types.ts | 2 ++ src/risk-utils.ts | 18 ++++++++++++++- src/risk.ts | 49 ++++++++++++++++++++++++++++++++++++---- src/types.ts | 2 ++ src/types/zeta.ts | 54 ++++++++++++++++++++++++++++++++++++++++---- 8 files changed, 146 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1501adf79..9951e8af4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,11 @@ All notable changes to this project will be documented in this file. Version changes are pinned to SDK releases. +## [1.28.0] + +- Add potential order loss to account margining. +- Add 10% oracle deviation protection to orders. + ## [1.26.4] - Respect Exchange.skipRpcConfirmation everywhere. ([#384](https://github.com/zetamarkets/sdk/pull/384)) diff --git a/package.json b/package.json index c264af6a2..c1c8d47ad 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@zetamarkets/sdk", "repository": "https://github.com/zetamarkets/sdk/", - "version": "1.27.3", + "version": "1.28.0", "description": "Zeta SDK", "main": "dist/index.js", "types": "dist/index.d.ts", diff --git a/src/idl/zeta.json b/src/idl/zeta.json index a7a1e7dd4..c9309a153 100644 --- a/src/idl/zeta.json +++ b/src/idl/zeta.json @@ -8174,12 +8174,30 @@ "name": "rebateRebalanceAmount", "type": "u64" }, + { + "name": "potentialOrderLoss", + "type": { + "array": [ + "u64", + 15 + ] + } + }, + { + "name": "potentialOrderLossPadding", + "type": { + "array": [ + "u64", + 10 + ] + } + }, { "name": "padding", "type": { "array": [ "u8", - 1976 + 1776 ] } } @@ -9721,7 +9739,7 @@ "name": "STRK" }, { - "name": "TNSR" + "name": "W" }, { "name": "UNDEFINED" @@ -11255,6 +11273,11 @@ "code": 6169, "name": "InvalidOracleUpdate", "msg": "Invalid oracle update" + }, + { + "code": 6170, + "name": "OrderPriceTooFarFromMarkPrice", + "msg": "Order price too far from mark price" } ] } \ No newline at end of file diff --git a/src/program-types.ts b/src/program-types.ts index 7bb80fb10..63861e266 100644 --- a/src/program-types.ts +++ b/src/program-types.ts @@ -212,6 +212,8 @@ export interface CrossMarginAccount { productLedgersPadding: Array; triggerOrderBits: anchor.BN; rebateRebalanceAmount: anchor.BN; + potentialOrderLoss: Array; + potentialOrderLossPadding: Array; padding: Array; } diff --git a/src/risk-utils.ts b/src/risk-utils.ts index 6c2cd6064..ea55af129 100644 --- a/src/risk-utils.ts +++ b/src/risk-utils.ts @@ -28,7 +28,8 @@ export function collectRiskMaps( mmMap: Map, mmioMap: Map, upnlMap: Map, - unpaidFundingMap: Map + unpaidFundingMap: Map, + potentialOrderLossMap: Map ): Map { let allAssets = assets.allAssets(); let collectedRiskState = new Map(); @@ -40,6 +41,7 @@ export function collectRiskMaps( maintenanceMarginIncludingOrders: mmioMap.get(a), unrealizedPnl: upnlMap.get(a), unpaidFunding: unpaidFundingMap.get(a), + potentialOrderLoss: potentialOrderLossMap.get(a), }); } return collectedRiskState; @@ -326,6 +328,20 @@ export function addFakeCancelToAccount( const nativeOrderSize = convertDecimalToNativeLotSize(order.size); + let totalOrders = + marginAccount.productLedgers[assetIndex].orderState.closingOrders + + marginAccount.productLedgers[assetIndex].orderState.openingOrders[0] + + marginAccount.productLedgers[assetIndex].orderState.openingOrders[1]; + + if (totalOrders == nativeOrderSize) { + marginAccount.potentialOrderLoss[assetIndex] = 0; + } else { + let totalMaxLoss = marginAccount.potentialOrderLoss[assetIndex]; + let maxLossPerLot = totalMaxLoss / totalOrders; + let averageMaxLoss = maxLossPerLot * nativeOrderSize; + marginAccount.potentialOrderLoss[assetIndex] -= averageMaxLoss; + } + const cancelOpening = Math.min( marginAccount.productLedgers[assetIndex].orderState.openingOrders[ bidAskIndex diff --git a/src/risk.ts b/src/risk.ts index 29d624a7c..f9f723840 100644 --- a/src/risk.ts +++ b/src/risk.ts @@ -391,6 +391,23 @@ export class RiskCalculator { } } + public getPotentialOrderLoss( + account: CrossMarginAccount + ): Map { + const i_list = [...Array(constants.ACTIVE_PERP_MARKETS).keys()]; + + const potentialOrderLossMap: Map = new Map(); + for (var i of i_list) { + const asset = assets.indexToAsset(i); + const potentialOrderLoss = convertNativeIntegerToDecimal( + account.potentialOrderLoss[i].toNumber() + ); + potentialOrderLossMap.set(asset, potentialOrderLoss); + } + + return potentialOrderLossMap; + } + /** * Returns the total initial margin requirement for a given account. * This includes initial margin on positions which is used for @@ -675,6 +692,9 @@ export class RiskCalculator { ): types.CrossMarginAccountState { let balance = convertNativeBNToDecimal(marginAccount.balance); let accType = types.ProgramAccountType.CrossMarginAccount; + + let potentialOrderLoss = this.getPotentialOrderLoss(marginAccount); + let unrealizedPnl = this.calculateUnrealizedPnl( marginAccount, accType @@ -703,6 +723,10 @@ export class RiskCalculator { accType ) as Map; + let potentialOrderLossTotal = Array.from( + potentialOrderLoss.values() + ).reduce((a, b) => a + b, 0); + let upnlTotal = Array.from(unrealizedPnl.values()).reduce( (a, b) => a + b, 0 @@ -725,16 +749,28 @@ export class RiskCalculator { let equity: number = balance + upnlTotal + unpaidFundingTotal; let availableBalanceInitial: number = - balance + upnlTotal + unpaidFundingTotal - imTotal; + balance + + upnlTotal + + unpaidFundingTotal - + imTotal - + potentialOrderLossTotal; let availableBalanceWithdrawable: number = balance + Math.min(0, upnlTotal) + unpaidFundingTotal - imSkipConcessionTotal; let availableBalanceMaintenance: number = - balance + upnlTotal + unpaidFundingTotal - mmTotal; + balance + + upnlTotal + + unpaidFundingTotal - + mmTotal - + potentialOrderLossTotal; let availableBalanceMaintenanceIncludingOrders: number = - balance + upnlTotal + unpaidFundingTotal - mmioTotal; + balance + + upnlTotal + + unpaidFundingTotal - + mmioTotal - + potentialOrderLossTotal; return { balance, equity, @@ -748,7 +784,8 @@ export class RiskCalculator { maintenanceMargin, maintenanceMarginIncludingOrders, unrealizedPnl, - unpaidFunding + unpaidFunding, + potentialOrderLoss ), initialMarginTotal: imTotal, initalMarginSkipConcessionTotal: imSkipConcessionTotal, @@ -756,6 +793,7 @@ export class RiskCalculator { maintenanceMarginIncludingOrdersTotal: mmioTotal, unrealizedPnlTotal: upnlTotal, unpaidFundingTotal: unpaidFundingTotal, + potentialOrderLossTotal, }; } @@ -868,7 +906,8 @@ export class RiskCalculator { ).values() ).reduce((a, b) => a + b, 0) + state.unpaidFundingTotal - - state.maintenanceMarginIncludingOrdersTotal; + state.maintenanceMarginIncludingOrdersTotal - + state.potentialOrderLossTotal; let fee = (getFeeBps(isTaker, marginAccount.accountType) / 10000) * tradePrice; diff --git a/src/types.ts b/src/types.ts index 2d64c7834..fa133ad4b 100644 --- a/src/types.ts +++ b/src/types.ts @@ -284,6 +284,7 @@ export interface AssetRiskState { maintenanceMarginIncludingOrders: number; unrealizedPnl: number; unpaidFunding: number; + potentialOrderLoss: number; } export interface CrossMarginAccountState { @@ -300,6 +301,7 @@ export interface CrossMarginAccountState { maintenanceMarginIncludingOrdersTotal: number; unrealizedPnlTotal: number; unpaidFundingTotal: number; + potentialOrderLossTotal: number; } export interface CancelArgs { diff --git a/src/types/zeta.ts b/src/types/zeta.ts index 1d1bdb48f..186e52d36 100644 --- a/src/types/zeta.ts +++ b/src/types/zeta.ts @@ -8174,12 +8174,30 @@ export type Zeta = { "name": "rebateRebalanceAmount", "type": "u64" }, + { + "name": "potentialOrderLoss", + "type": { + "array": [ + "u64", + 15 + ] + } + }, + { + "name": "potentialOrderLossPadding", + "type": { + "array": [ + "u64", + 10 + ] + } + }, { "name": "padding", "type": { "array": [ "u8", - 1976 + 1776 ] } } @@ -9721,7 +9739,7 @@ export type Zeta = { "name": "STRK" }, { - "name": "TNSR" + "name": "W" }, { "name": "UNDEFINED" @@ -11255,6 +11273,11 @@ export type Zeta = { "code": 6169, "name": "InvalidOracleUpdate", "msg": "Invalid oracle update" + }, + { + "code": 6170, + "name": "OrderPriceTooFarFromMarkPrice", + "msg": "Order price too far from mark price" } ] }; @@ -19435,12 +19458,30 @@ export const IDL: Zeta = { "name": "rebateRebalanceAmount", "type": "u64" }, + { + "name": "potentialOrderLoss", + "type": { + "array": [ + "u64", + 15 + ] + } + }, + { + "name": "potentialOrderLossPadding", + "type": { + "array": [ + "u64", + 10 + ] + } + }, { "name": "padding", "type": { "array": [ "u8", - 1976 + 1776 ] } } @@ -20982,7 +21023,7 @@ export const IDL: Zeta = { "name": "STRK" }, { - "name": "TNSR" + "name": "W" }, { "name": "UNDEFINED" @@ -22516,6 +22557,11 @@ export const IDL: Zeta = { "code": 6169, "name": "InvalidOracleUpdate", "msg": "Invalid oracle update" + }, + { + "code": 6170, + "name": "OrderPriceTooFarFromMarkPrice", + "msg": "Order price too far from mark price" } ] };