Skip to main content

decrease

This module provides utilities for calculating decrease position amounts, fees, and next position values when closing or reducing trading positions. It handles complex calculations including PnL realization, fee deductions, collateral adjustments, and swap requirements.

Methods​

The decrease trade module exports functions for computing position close amounts, determining full-close conditions, calculating collateral costs, checking margin after a decrease, and projecting the next position state. Import any function directly from @gmx-io/sdk/utils/trade.

getDecreasePositionAmounts​

getDecreasePositionAmounts(p: {
marketInfo: MarketInfo;
collateralToken: TokenData;
isLong: boolean;
position: PositionInfoLoaded | undefined;
closeSizeUsd: bigint;
keepLeverage: boolean;
triggerPrice?: bigint;
fixedAcceptablePriceImpactBps?: bigint;
acceptablePriceImpactBuffer?: number;
userReferralInfo: UserReferralInfo | undefined;
proDiscountFactor?: bigint;
minCollateralUsd: bigint;
minPositionSizeUsd: bigint;
uiFeeFactor: bigint;
isLimit?: boolean;
limitPrice?: bigint;
triggerOrderType?: DecreasePositionAmounts["triggerOrderType"];
isSetAcceptablePriceImpactEnabled: boolean;
receiveToken?: TokenData;
forceDecreaseSwapType?: DecreasePositionSwapType;
isTwap?: boolean;
}): DecreasePositionAmounts

Computes a complete DecreasePositionAmounts result for a Market Decrease, Limit Decrease, Stop-Loss, or Take-Profit order. The result includes all pricing, fee, PnL, collateral, and receive-amount fields needed to build the on-chain order.

When position is undefined or has zero size, only the fee and price fields are computed; amounts relating to PnL and collateral are left at zero.

When receiveToken is omitted, it defaults to collateralToken. forceDecreaseSwapType replaces the swap type the function would otherwise pick. proDiscountFactor applies the pro-tier discount to the position fee.

A call with no triggerOrderType and no isTwap: true counts as a Market Decrease, and these rules apply to it only:

  • When the pool can't perform the profit swap, the profit stays in the PnL token.
  • When the profit stays in the PnL token, costs come from collateral first and then from that profit.
  • With keepLeverage, the collateral withdrawal can't go below zero, and its output is reduced by priceImpactDiffUsd.
  • A swap from the collateral token to the PnL token applies to the collateral output only.

Pass isTwap: true for a TWAP decrease so these rules don't apply to it.

In the result, positionFeeUsd, feeDiscountUsd, and borrowingFeeUsd are plain USD values for every decrease type, and unpaidCostUsd is the cost that neither collateral nor profit could cover. For every decrease type, a profit swap to the collateral token calculates its price impact on the pool after the profit payout.

import { getDecreasePositionAmounts } from "@gmx-io/sdk/utils/trade";

const decreaseAmounts = getDecreasePositionAmounts({
marketInfo,
collateralToken: usdcToken,
isLong: true,
position: currentPosition,
closeSizeUsd: 1000n * 10n ** 30n, // close $1000 of position
keepLeverage: false,
userReferralInfo: undefined,
minCollateralUsd: 5000n * 10n ** 30n,
minPositionSizeUsd: 1000n * 10n ** 30n,
uiFeeFactor: 0n,
isSetAcceptablePriceImpactEnabled: true,
});

console.log("Receive token amount:", decreaseAmounts.receiveTokenAmount);
console.log("Receive USD:", decreaseAmounts.receiveUsd);
console.log("Realized PnL:", decreaseAmounts.realizedPnl);
console.log("Position fee:", decreaseAmounts.positionFeeUsd);
console.log("Acceptable price:", decreaseAmounts.acceptablePrice);

getIsFullClose​

getIsFullClose(p: {
position: PositionInfoLoaded;
sizeDeltaUsd: bigint;
indexPrice: bigint;
remainingCollateralUsd: bigint;
minCollateralUsd: bigint;
minPositionSizeUsd: bigint;
}): boolean

Returns true when the decrease must be treated as a full close. This happens when:

  • The remaining position size would be < $1 after the decrease, or
  • A loss would leave the remaining collateral below the minimum factor threshold, and the remaining collateral + PnL is below minCollateralUsd or the remaining size is below minPositionSizeUsd.
import { getIsFullClose } from "@gmx-io/sdk/utils/trade";

const isFullClose = getIsFullClose({
position: currentPosition,
sizeDeltaUsd: 500n * 10n ** 30n,
indexPrice: currentPosition.markPrice,
remainingCollateralUsd: 100n * 10n ** 30n,
minCollateralUsd: 5000n * 10n ** 30n,
minPositionSizeUsd: 1000n * 10n ** 30n,
});

getMinCollateralUsdForLeverage​

getMinCollateralUsdForLeverage(position: PositionInfoLoaded, openInterestDelta: bigint): bigint

Returns the minimum collateral required to keep the current position's leverage within the market's allowed range, given an open interest change.

import { getMinCollateralUsdForLeverage } from "@gmx-io/sdk/utils/trade";

const minCollateral = getMinCollateralUsdForLeverage(
currentPosition,
-1000n * 10n ** 30n // OI decreasing by $1000
);

payForCollateralCost​

payForCollateralCost(p: {
initialCostUsd: bigint;
collateralToken: TokenData;
collateralPrice: bigint;
outputAmount: bigint;
remainingCollateralAmount: bigint;
}): {
outputAmount: bigint;
remainingCollateralAmount: bigint;
paidOutputAmount: bigint;
paidRemainingCollateralAmount: bigint;
unpaidCostAmount: bigint;
}

Deducts initialCostUsd from outputAmount first, then from remainingCollateralAmount. Returns the updated balances, the amounts paid from each source, and unpaidCostAmount, the part of the cost neither source could cover.

import { payForCollateralCost } from "@gmx-io/sdk/utils/trade";

const result = payForCollateralCost({
initialCostUsd: 100n * 10n ** 30n,
collateralToken: usdcToken,
collateralPrice: 1n * 10n ** 30n, // $1 per USDC
outputAmount: 50_000_000n, // 50 USDC (6 decimals)
remainingCollateralAmount: 1_000_000_000n,
});
// result.paidOutputAmount — deducted from output
// result.paidRemainingCollateralAmount — deducted from remaining collateral
// result.unpaidCostAmount — not covered by either source

estimateCollateralCost​

estimateCollateralCost(baseUsd: bigint, collateralToken: TokenData, collateralPrice: bigint): {
amount: bigint;
usd: bigint;
}

Converts baseUsd to the collateral token's amount using collateralToken.prices.minPrice, then converts back to USD using collateralPrice. Used to estimate costs denominated in the collateral token.

import { estimateCollateralCost } from "@gmx-io/sdk/utils/trade";

const { amount, usd } = estimateCollateralCost(
50n * 10n ** 30n, // $50
usdcToken,
1n * 10n ** 30n // $1 per USDC
);
// amount: token units of the cost
// usd: cost in USD at collateralPrice

getTotalFeesUsdForDecrease​

getTotalFeesUsdForDecrease(p: {
positionFeeUsd: bigint;
borrowingFeeUsd: bigint;
fundingFeeUsd: bigint;
swapProfitFeeUsd: bigint;
swapUiFeeUsd: bigint;
uiFeeUsd: bigint;
pnlUsd: bigint;
totalPendingImpactDeltaUsd: bigint;
}): bigint

Returns the total cost that must be covered by the position's output. Only negative contributions are counted: a negative pnlUsd (loss) and a negative totalPendingImpactDeltaUsd (adverse price impact) are added as absolute values.

import { getTotalFeesUsdForDecrease } from "@gmx-io/sdk/utils/trade";

const totalCost = getTotalFeesUsdForDecrease({
positionFeeUsd: 10n * 10n ** 30n,
borrowingFeeUsd: 5n * 10n ** 30n,
fundingFeeUsd: 2n * 10n ** 30n,
swapProfitFeeUsd: 1n * 10n ** 30n,
swapUiFeeUsd: 0n,
uiFeeUsd: 0n,
pnlUsd: -10n * 10n ** 30n, // $10 loss is added to total cost
totalPendingImpactDeltaUsd: -5n * 10n ** 30n, // negative impact is added
});
// totalCost = 10 + 5 + 2 + 1 + 10 + 5 = $33

getNextPositionValuesForDecreaseTrade​

getNextPositionValuesForDecreaseTrade(p: {
existingPosition?: PositionInfo;
marketInfo: MarketInfo;
collateralToken: TokenData;
sizeDeltaUsd: bigint;
sizeDeltaInTokens: bigint;
realizedPnl: bigint;
estimatedPnl: bigint;
collateralDeltaUsd: bigint;
collateralDeltaAmount: bigint;
payedRemainingCollateralUsd: bigint;
payedRemainingCollateralAmount: bigint;
proportionalPendingImpactDeltaUsd: bigint;
showPnlInLeverage: boolean;
isLong: boolean;
minCollateralUsd: bigint;
userReferralInfo: UserReferralInfo | undefined;
priceImpactDiffUsd?: bigint;
}): NextPositionValues

Computes the projected position state after a decrease: remaining size, collateral, leverage, PnL percentage, and liquidation price. Use this to populate UI previews before order submission. Pass priceImpactDiffUsd to reduce the projected collateral withdrawal by it.

import { getNextPositionValuesForDecreaseTrade } from "@gmx-io/sdk/utils/trade";

const nextValues = getNextPositionValuesForDecreaseTrade({
existingPosition: currentPosition,
marketInfo,
collateralToken: usdcToken,
sizeDeltaUsd: decreaseAmounts.sizeDeltaUsd,
sizeDeltaInTokens: decreaseAmounts.sizeDeltaInTokens,
realizedPnl: decreaseAmounts.realizedPnl,
estimatedPnl: decreaseAmounts.estimatedPnl,
collateralDeltaUsd: decreaseAmounts.collateralDeltaUsd,
collateralDeltaAmount: decreaseAmounts.collateralDeltaAmount,
payedRemainingCollateralUsd: decreaseAmounts.payedRemainingCollateralUsd,
payedRemainingCollateralAmount: decreaseAmounts.payedRemainingCollateralAmount,
proportionalPendingImpactDeltaUsd: decreaseAmounts.proportionalPendingImpactDeltaUsd,
showPnlInLeverage: true,
isLong: true,
minCollateralUsd: 5000n * 10n ** 30n,
userReferralInfo: undefined,
});

console.log("Next size:", nextValues.nextSizeUsd);
console.log("Next leverage:", nextValues.nextLeverage);
console.log("Next liq price:", nextValues.nextLiqPrice);

getMaxWithdrawAmount​

getMaxWithdrawAmount(p: {
position: PositionInfoLoaded;
minCollateralUsd: bigint | undefined;
collateralPrice: bigint | undefined;
collateralDecimals: number | undefined;
userReferralInfo: UserReferralInfo | undefined;
proDiscountFactor?: bigint;
}): bigint

Returns the most collateral you can withdraw from a position, in collateral token units. The result is the lowest of three limits: the collateral above the position's minimum required collateral, the collateral above the minimum for leverage after pending fees and PnL, and the collateral above the minimum for the market's maximum allowed leverage. It returns 0n when any limit is reached.

import { getMaxWithdrawAmount } from "@gmx-io/sdk/utils/trade";

const maxWithdrawAmount = getMaxWithdrawAmount({
position: currentPosition,
minCollateralUsd: 5000n * 10n ** 30n,
collateralPrice: usdcToken.prices.minPrice,
collateralDecimals: usdcToken.decimals,
userReferralInfo: undefined,
});

getDecreaseResultingPositionMarginState​

getDecreaseResultingPositionMarginState(p: {
marketInfo: MarketInfo;
collateralToken: TokenData;
isLong: boolean;
position: {
sizeInUsd: bigint;
sizeInTokens: bigint;
collateralAmount: bigint;
pendingImpactAmount: bigint;
};
sizeDeltaUsd: bigint;
sizeDeltaInTokens: bigint;
collateralDeltaAmount: bigint;
payedRemainingCollateralAmount: bigint;
priceImpactDiffUsd: bigint;
minCollateralUsd: bigint;
minPositionSizeUsd: bigint;
userReferralInfo: UserReferralInfo | undefined;
proDiscountFactor?: bigint;
indexPriceForEvaluation?: bigint;
}): DecreasePositionMarginState | undefined

Checks whether the position a decrease leaves passes the margin checks the contracts run during a decrease. Use it for a Market Decrease, or for a collateral withdrawal that leaves the position open. Pass indexPriceForEvaluation to evaluate at a price other than the current one. The parameter type is exported as DecreaseResultingPositionMarginStateParams.

The function first checks the collateral left after the withdrawal against the minimum for the market's open interest and, together with the remaining PnL, against minCollateralUsd. If that check fails:

  • With sizeDeltaUsd of 0n, it returns isLiquidatable: true with the reason UnableToWithdrawCollateral.
  • With a size change, it continues as if the collateral stayed in the position. When a withdrawal was requested, it sets isCollateralWithdrawalCancelled: true, because the contracts would cancel it.

It then runs getResultingPositionMarginState on the resulting position with shouldValidateMinCollateralUsd: false.

DecreasePositionMarginState is PositionMarginState plus isCollateralWithdrawalCancelled. The function returns undefined when the check doesn't apply: a full close, a position with no size, a remaining size or collateral below the minimums, or a price of 0n or less. undefined doesn't mean the position passes.

import { getDecreaseResultingPositionMarginState } from "@gmx-io/sdk/utils/trade";

const marginState = getDecreaseResultingPositionMarginState({
marketInfo,
collateralToken: usdcToken,
isLong: true,
position: currentPosition,
sizeDeltaUsd: decreaseAmounts.sizeDeltaUsd,
sizeDeltaInTokens: decreaseAmounts.sizeDeltaInTokens,
collateralDeltaAmount: decreaseAmounts.collateralDeltaAmount,
payedRemainingCollateralAmount: decreaseAmounts.payedRemainingCollateralAmount,
priceImpactDiffUsd: decreaseAmounts.priceImpactDiffUsd,
minCollateralUsd: 5000n * 10n ** 30n,
minPositionSizeUsd: 1000n * 10n ** 30n,
userReferralInfo: undefined,
});

if (marginState?.isLiquidatable) {
console.log("Expected to fail at execution:", marginState.reason);
}

getTriggerDecreaseRemainingPositionMarginState​

getTriggerDecreaseRemainingPositionMarginState(p: {
marketInfo: MarketInfo;
collateralToken: TokenData;
isLong: boolean;
position: DecreaseResultingPositionMarginStateParams["position"];
decreaseAmounts: DecreasePositionAmounts;
collateralDeltaAmount: bigint;
minCollateralUsd: bigint;
minPositionSizeUsd: bigint;
userReferralInfo: UserReferralInfo | undefined;
proDiscountFactor?: bigint;
}): DecreasePositionMarginState | undefined

Runs the same checks for a Stop-Loss or Take-Profit at its trigger price. Pass the order's amounts at that price as decreaseAmounts, and the collateral withdrawal the order stores as collateralDeltaAmount. It returns undefined for a full close or when decreaseAmounts has no trigger price. The parameter type is exported as TriggerDecreaseRemainingPositionMarginStateParams.

getIsProfitKeptInPnlToken​

getIsProfitKeptInPnlToken(p: {
pnlToken: TokenData;
collateralToken: TokenData;
decreaseSwapType: DecreasePositionSwapType;
isProfitSwapReverted?: boolean;
}): boolean

Returns true when the PnL token and collateral token differ and the profit isn't swapped to the collateral token: either the decrease has no profit swap, or isProfitSwapReverted is true.

getCollateralDeltaAmountAfterPriceImpactDiff​

getCollateralDeltaAmountAfterPriceImpactDiff(p: {
collateralDeltaAmount: bigint;
priceImpactDiffUsd: bigint;
collateralToken: TokenData;
}): bigint

Returns the collateral withdrawal reduced by priceImpactDiffUsd, converted at the collateral token's minimum price. The result never goes below 0n. When collateralDeltaAmount or priceImpactDiffUsd is 0n or less, it returns collateralDeltaAmount unchanged.