Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: update default target leverage on isolated market orders to be max market leverage #631

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -676,6 +676,19 @@ internal object MarginCalculator {
} ?: return null
}

private fun getMaxMarketLeverageDeprecated(
effectiveImf: Double,
imf: Double,
): Double {
return if (effectiveImf > Numeric.double.ZERO) {
Numeric.double.ONE / effectiveImf
} else if (imf > Numeric.double.ZERO) {
Numeric.double.ONE / imf
} else {
Numeric.double.ONE
}
}

/**
* @description Calculate the amount of collateral to transfer for an isolated margin trade.
* Max leverage is capped at 98% of the the market's max leverage and takes the oraclePrice into account in order to pass collateral checks.
Expand All @@ -685,21 +698,19 @@ internal object MarginCalculator {
market: InternalMarketState?,
subaccount: InternalSubaccountState?
): Double? {
val targetLeverage = trade.targetLeverage ?: 1.0
val side = trade.side ?: return null
val oraclePrice = market?.perpetualMarket?.oraclePrice ?: return null
val price = trade.summary?.price ?: return null
val initialMarginFraction = market.perpetualMarket?.configs?.initialMarginFraction ?: 0.0
val effectiveImf = market.perpetualMarket?.configs?.effectiveInitialMarginFraction ?: 0.0
val maxMarketLeverage = market.perpetualMarket?.configs?.maxMarketLeverage ?: return null
val targetLeverage = trade.targetLeverage ?: maxMarketLeverage
val positionSizeDifference = getPositionSizeDifference(subaccount, trade) ?: return null

return calculateIsolatedMarginTransferAmountFromValues(
targetLeverage = targetLeverage,
side = side.rawValue,
oraclePrice = oraclePrice,
price = price,
initialMarginFraction = initialMarginFraction,
effectiveImf = effectiveImf,
maxMarketLeverage = maxMarketLeverage,
positionSizeDifference = positionSizeDifference,
)
}
Expand All @@ -714,21 +725,22 @@ internal object MarginCalculator {
market: Map<String, Any>?,
subaccount: Map<String, Any>?
): Double? {
val targetLeverage = parser.asDouble(trade["targetLeverage"]) ?: 1.0
val side = parser.asString(parser.value(trade, "side")) ?: return null
val oraclePrice = parser.asDouble(parser.value(market, "oraclePrice")) ?: return null
val price = parser.asDouble(parser.value(trade, "summary.price")) ?: return null
val initialMarginFraction = parser.asDouble(parser.value(market, "configs.initialMarginFraction")) ?: 0.0
val effectiveImf = parser.asDouble(parser.value(market, "configs.effectiveInitialMarginFraction")) ?: 0.0
val initialMarginFraction = parser.asDouble(parser.value(market, "configs.initialMarginFraction")) ?: Numeric.double.ZERO
val effectiveImf = parser.asDouble(parser.value(market, "configs.effectiveInitialMarginFraction")) ?: Numeric.double.ZERO
val maxMarketLeverage = getMaxMarketLeverageDeprecated(effectiveImf = effectiveImf, imf = initialMarginFraction)

val targetLeverage = parser.asDouble(trade["targetLeverage"]) ?: maxMarketLeverage
val positionSizeDifference = getPositionSizeDifferenceDeprecated(parser, subaccount, trade) ?: return null

return calculateIsolatedMarginTransferAmountFromValues(
targetLeverage = targetLeverage,
side = side,
oraclePrice = oraclePrice,
price = price,
initialMarginFraction = initialMarginFraction,
effectiveImf = effectiveImf,
maxMarketLeverage = maxMarketLeverage,
positionSizeDifference = positionSizeDifference,
)
}
Expand All @@ -742,17 +754,15 @@ internal object MarginCalculator {
val side = trade.side?.rawValue ?: return null
val oraclePrice = market.oraclePrice ?: return null
val price = trade.summary?.price ?: return null
val initialMarginFraction = market.configs?.initialMarginFraction ?: 0.0
val effectiveImf = market.configs?.effectiveInitialMarginFraction ?: 0.0
val maxMarketLeverage = market.configs?.maxMarketLeverage ?: return null
val positionSizeDifference = getPositionSizeDifference(subaccount, trade) ?: return null

return calculateIsolatedMarginTransferAmountFromValues(
targetLeverage,
side,
oraclePrice,
price,
initialMarginFraction,
effectiveImf,
maxMarketLeverage,
positionSizeDifference,
)
}
Expand All @@ -762,27 +772,14 @@ internal object MarginCalculator {
side: String,
oraclePrice: Double,
price: Double,
initialMarginFraction: Double,
effectiveImf: Double,
maxMarketLeverage: Double?,
positionSizeDifference: Double,
): Double? {
val maxLeverageForMarket = if (effectiveImf != 0.0) {
1.0 / effectiveImf
} else if (initialMarginFraction != 0.0) {
1.0 / initialMarginFraction
} else {
null
}

val maxLeverageForMarket = maxMarketLeverage ?: Numeric.double.ONE
// Cap targetLeverage to 98% of max leverage
val adjustedTargetLeverage = if (maxLeverageForMarket != null) {
val cappedLeverage = maxLeverageForMarket * MAX_LEVERAGE_BUFFER_PERCENT
min(targetLeverage, cappedLeverage)
} else {
null
}
val adjustedTargetLeverage = min(targetLeverage, maxLeverageForMarket * MAX_LEVERAGE_BUFFER_PERCENT)

return if (adjustedTargetLeverage == 0.0 || adjustedTargetLeverage == null) {
return if (adjustedTargetLeverage == 0.0) {
null
} else {
getTransferAmountFromTargetLeverage(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package exchange.dydx.abacus.calculator

import exchange.dydx.abacus.utils.Numeric
import exchange.dydx.abacus.utils.Parser
import exchange.dydx.abacus.utils.mutable

Expand All @@ -15,6 +16,18 @@ object MarginModeCalculator {
): MutableMap<String, Any>? {
val modified = tradeInput?.mutable() ?: return null
val marketId = parser.asString(tradeInput["marketId"])
val market = parser.asMap(markets?.get(marketId))

val imf = parser.asDouble(parser.value(market, "configs.initialMarginFraction")) ?: Numeric.double.ZERO
val effectiveImf = parser.asDouble(parser.value(market, "configs.effectiveInitialMarginFraction")) ?: Numeric.double.ZERO
val maxMarketLeverage = if (effectiveImf > Numeric.double.ZERO) {
Numeric.double.ONE / effectiveImf
} else if (imf > Numeric.double.ZERO) {
Numeric.double.ONE / imf
} else {
Numeric.double.ONE
}

val existingMarginMode =
MarginCalculator.findExistingMarginModeDeprecated(
parser,
Expand All @@ -36,18 +49,18 @@ object MarginModeCalculator {
subaccountNumber,
)
val existingPositionLeverage = parser.asDouble(parser.value(existingPosition, "leverage.current"))
modified["targetLeverage"] = existingPositionLeverage ?: 1.0
modified["targetLeverage"] = existingPositionLeverage ?: maxMarketLeverage
}
} else {
val marketMarginMode = MarginCalculator.findMarketMarginModeDeprecated(
parser,
parser.asMap(markets?.get(marketId)),
market,
)
when (marketMarginMode) {
"ISOLATED" -> {
modified["marginMode"] = marketMarginMode
if (parser.asDouble(tradeInput["targetLeverage"]) == null) {
modified["targetLeverage"] = 1.0
modified["targetLeverage"] = maxMarketLeverage
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,6 @@ internal class TradeInputCalculator(
val subaccount = if (marginMode == MarginMode.Cross) {
crossMarginSubaccount
} else {
// TODO: incorrect for isolated trades; fix CT-1092
groupedSubaccount
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import exchange.dydx.abacus.output.input.MarginMode
import exchange.dydx.abacus.state.internalstate.InternalAccountState
import exchange.dydx.abacus.state.internalstate.InternalMarketState
import exchange.dydx.abacus.state.internalstate.InternalTradeInputState
import exchange.dydx.abacus.utils.Numeric

internal class TradeInputMarginModeCalculator {
fun updateTradeInputMarginMode(
Expand All @@ -19,6 +20,8 @@ internal class TradeInputMarginModeCalculator {
marketId = tradeInput.marketId,
subaccountNumber = subaccountNumber,
)
val market = markets?.get(tradeInput.marketId)
val maxMarketLeverage = market?.perpetualMarket?.configs?.maxMarketLeverage ?: Numeric.double.ONE

// If there is an existing position or order, we have to use the same margin mode
if (existingMarginMode != null) {
Expand All @@ -30,16 +33,16 @@ internal class TradeInputMarginModeCalculator {
subaccountNumber = subaccountNumber,
)
val existingPositionLeverage = existingPosition?.calculated?.get(CalculationPeriod.current)?.leverage
tradeInput.targetLeverage = existingPositionLeverage ?: 1.0
tradeInput.targetLeverage = if (existingPositionLeverage != null && existingPositionLeverage > Numeric.double.ZERO) existingPositionLeverage else maxMarketLeverage
}
} else {
val marketMarginMode = MarginCalculator.findMarketMarginMode(
market = markets?.get(tradeInput.marketId)?.perpetualMarket,
market = market?.perpetualMarket,
)
when (marketMarginMode) {
MarginMode.Isolated -> {
tradeInput.marginMode = marketMarginMode
tradeInput.targetLeverage = tradeInput.targetLeverage ?: 1.0
tradeInput.targetLeverage = tradeInput.targetLeverage ?: maxMarketLeverage
}

MarginMode.Cross -> {
Expand Down
14 changes: 14 additions & 0 deletions src/commonMain/kotlin/exchange.dydx.abacus/output/Market.kt
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import exchange.dydx.abacus.state.manager.OrderbookGrouping
import exchange.dydx.abacus.utils.IList
import exchange.dydx.abacus.utils.IMap
import exchange.dydx.abacus.utils.Logger
import exchange.dydx.abacus.utils.Numeric
import exchange.dydx.abacus.utils.ParsingHelper
import exchange.dydx.abacus.utils.mutable
import exchange.dydx.abacus.utils.typedSafeSet
Expand Down Expand Up @@ -232,6 +233,19 @@ data class MarketConfigs(
}
}
}

internal val maxMarketLeverage: Double
get() {
val imf = initialMarginFraction ?: Numeric.double.ZERO
val effectiveImf = effectiveInitialMarginFraction ?: Numeric.double.ZERO
return if (effectiveImf > Numeric.double.ZERO) {
Numeric.double.ONE / effectiveImf
} else if (imf > Numeric.double.ZERO) {
Numeric.double.ONE / imf
} else {
Numeric.double.ONE
}
}
}

@JsExport
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,8 +103,11 @@ internal class ClosePositionInputProcessor(
trade.timeInForce = "IOC"
trade.reduceOnly = true

val market = marketSummaryState.markets.get(trade.marketId)
val maxMarketLeverage = market?.perpetualMarket?.configs?.maxMarketLeverage ?: Numeric.double.ONE

val currentPositionLeverage = position.calculated[CalculationPeriod.current]?.leverage?.abs()
trade.targetLeverage = if (currentPositionLeverage != null && currentPositionLeverage > 0) currentPositionLeverage else 1.0
trade.targetLeverage = if (currentPositionLeverage != null && currentPositionLeverage > Numeric.double.ZERO) currentPositionLeverage else maxMarketLeverage

// default full close
trade.sizePercent = 1.0
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import exchange.dydx.abacus.state.internalstate.InternalTradeInputState
import exchange.dydx.abacus.state.internalstate.InternalWalletState
import exchange.dydx.abacus.state.internalstate.safeCreate
import exchange.dydx.abacus.state.model.TradeInputField
import exchange.dydx.abacus.utils.Numeric
import kollections.iListOf

internal interface TradeInputProcessorProtocol {
Expand Down Expand Up @@ -101,6 +102,7 @@ internal class TradeInputProcessor(
initiateMarginModeLeverage(
trade = inputState.trade,
marketState = market,
marketSummaryState = marketSummaryState,
accountState = walletState.account,
marketId = marketId,
subaccountNumber = subaccountNumber,
Expand Down Expand Up @@ -274,6 +276,7 @@ internal class TradeInputProcessor(
private fun initiateMarginModeLeverage(
trade: InternalTradeInputState,
marketState: InternalMarketState?,
marketSummaryState: InternalMarketSummaryState,
accountState: InternalAccountState,
marketId: String,
subaccountNumber: Int,
Expand All @@ -289,26 +292,29 @@ internal class TradeInputProcessor(
marketId = marketId,
subaccountNumber = subaccountNumber,
)
val market = marketSummaryState.markets[marketId]
val maxMarketLeverage = market?.perpetualMarket?.configs?.maxMarketLeverage ?: Numeric.double.ONE

if (existingPosition != null) {
trade.marginMode =
if (subaccount?.equity != null) MarginMode.Isolated else MarginMode.Cross
val currentPositionLeverage =
existingPosition.calculated[CalculationPeriod.current]?.leverage?.abs()
val positionLeverage =
if (currentPositionLeverage != null && currentPositionLeverage > 0) currentPositionLeverage else 1.0
if (currentPositionLeverage != null && currentPositionLeverage > Numeric.double.ZERO) currentPositionLeverage else Numeric.double.ONE
trade.targetLeverage = positionLeverage
} else if (existingOrder != null) {
trade.marginMode =
if (existingOrder.subaccountNumber == subaccountNumber) MarginMode.Cross else MarginMode.Isolated
trade.targetLeverage = 1.0
trade.targetLeverage = maxMarketLeverage
} else {
val marketType = marketState?.perpetualMarket?.configs?.perpetualMarketType
trade.marginMode = when (marketType) {
PerpetualMarketType.CROSS -> MarginMode.Cross
PerpetualMarketType.ISOLATED -> MarginMode.Isolated
else -> null
}
trade.targetLeverage = 1.0
trade.targetLeverage = maxMarketLeverage
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -104,8 +104,18 @@ fun TradingStateMachine.closePosition(
trade["timeInForce"] = "IOC"
trade["reduceOnly"] = true

val market = parser.asNativeMap(parser.value(marketsSummary, "markets.${trade["marketId"]}"))
val imf = parser.asDouble(parser.value(market, "configs.initialMarginFraction")) ?: Numeric.double.ZERO
val effectiveImf = parser.asDouble(parser.value(market, "configs.effectiveInitialMarginFraction")) ?: Numeric.double.ZERO
val maxMarketLeverage = if (effectiveImf > Numeric.double.ZERO) {
Numeric.double.ONE / effectiveImf
} else if (imf > Numeric.double.ZERO) {
Numeric.double.ONE / imf
} else {
Numeric.double.ONE
}
val currentPositionLeverage = parser.asDouble(parser.value(position, "leverage.current"))?.abs()
trade["targetLeverage"] = if (currentPositionLeverage != null && currentPositionLeverage > 0) currentPositionLeverage else 1.0
trade["targetLeverage"] = if (currentPositionLeverage != null && currentPositionLeverage > 0) currentPositionLeverage else maxMarketLeverage

// default full close
trade.safeSet("size.percent", 1.0)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import exchange.dydx.abacus.responses.StateResponse
import exchange.dydx.abacus.responses.cannotModify
import exchange.dydx.abacus.state.changes.Changes
import exchange.dydx.abacus.state.changes.StateChanges
import exchange.dydx.abacus.utils.Numeric
import exchange.dydx.abacus.utils.mutable
import exchange.dydx.abacus.utils.mutableMapOf
import exchange.dydx.abacus.utils.safeSet
Expand Down Expand Up @@ -150,6 +151,16 @@ internal fun TradingStateMachine.tradeInMarket(
marketId,
subaccountNumber,
)
val market = parser.asNativeMap(parser.value(marketsSummary, "markets.$marketId"))
val imf = parser.asDouble(parser.value(market, "configs.initialMarginFraction")) ?: Numeric.double.ZERO
val effectiveImf = parser.asDouble(parser.value(market, "configs.effectiveInitialMarginFraction")) ?: Numeric.double.ZERO
val maxMarketLeverage = if (effectiveImf > Numeric.double.ZERO) {
Numeric.double.ONE / effectiveImf
} else if (imf > Numeric.double.ZERO) {
Numeric.double.ONE / imf
} else {
Numeric.double.ONE
}
if (existingPosition != null) {
it.safeSet("marginMode", if (existingPosition["equity"] != null) MarginMode.Isolated.rawValue else MarginMode.Cross.rawValue)
val currentPositionLeverage = parser.asDouble(parser.value(existingPosition, "leverage.current"))?.abs()
Expand All @@ -158,11 +169,11 @@ internal fun TradingStateMachine.tradeInMarket(
} else if (existingOrder != null) {
val orderMarginMode = if ((parser.asInt(parser.value(existingOrder, "subaccountNumber")) ?: subaccountNumber) == subaccountNumber) MarginMode.Cross.rawValue else MarginMode.Isolated.rawValue
it.safeSet("marginMode", orderMarginMode)
it.safeSet("targetLeverage", 1.0)
it.safeSet("targetLeverage", maxMarketLeverage)
} else {
val marketType = parser.asString(parser.value(marketsSummary, "markets.$marketId.configs.perpetualMarketType"))
it.safeSet("marginMode", MarginMode.invoke(marketType)?.rawValue)
it.safeSet("targetLeverage", 1.0)
it.safeSet("targetLeverage", maxMarketLeverage)
}
}

Expand Down
Loading
Loading