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

v1.7.75: Add buffer and use oraclePrice to determine collateral movement #445

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
2 changes: 1 addition & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ allprojects {
}

group = "exchange.dydx.abacus"
version = "1.7.74"
version = "1.7.75"

repositories {
google()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ class AccountTransformer() {
private val subaccountTransformer = SubaccountTransformer()
internal fun applyTradeToAccount(
account: Map<String, Any>?,
subaccountNumber: Int?,
subaccountNumber: Int,
trade: Map<String, Any>,
market: Map<String, Any>?,
parser: ParserProtocol,
Expand All @@ -22,14 +22,14 @@ class AccountTransformer() {
subaccountNumber ?: 0,
trade,
)
val subaccount = parser.asNativeMap(
parser.value(
account,
"subaccounts.$subaccountNumber",
),
) ?: mapOf()
if (subaccountNumber == childSubaccountNumber) {
// CROSS
val subaccount = parser.asNativeMap(
parser.value(
account,
"subaccounts.$subaccountNumber",
),
) ?: mapOf()
val modifiedSubaccount =
subaccountTransformer.applyTradeToSubaccount(
subaccount,
Expand All @@ -41,18 +41,28 @@ class AccountTransformer() {
modified.safeSet("subaccounts.$subaccountNumber", modifiedSubaccount)
return modified
} else {
val transferAmount = calculateIsolatedMarginTransferAmount(
parser,
trade,
) ?: 0.0

val subaccount = parser.asNativeMap(
val childSubaccount = parser.asNativeMap(
parser.value(
account,
"subaccounts.$subaccountNumber",
"subaccounts.$childSubaccountNumber",
),
) ?: mapOf()

val transferAmount = if (MarginModeCalculator.getShouldTransferCollateral(
parser,
subaccount = childSubaccount,
tradeInput = trade,
)
) {
MarginModeCalculator.calculateIsolatedMarginTransferAmount(
parser,
trade,
market,
) ?: 0.0
} else {
0.0
}

val modifiedSubaccount =
subaccountTransformer.applyTransferToSubaccount(
subaccount,
Expand All @@ -62,13 +72,6 @@ class AccountTransformer() {
)
modified.safeSet("subaccounts.$subaccountNumber", modifiedSubaccount)

val childSubaccount = parser.asNativeMap(
parser.value(
account,
"subaccounts.$childSubaccountNumber",
),
) ?: mapOf()

val modifiedChildSubaccount =
subaccountTransformer.applyTradeToSubaccount(
childSubaccount,
Expand All @@ -82,19 +85,4 @@ class AccountTransformer() {
return modified
}
}

fun calculateIsolatedMarginTransferAmount(
parser: ParserProtocol,
trade: Map<String, Any>,
): Double? {
val marketOrderUsdcSize = parser.asDouble(parser.value(trade, "marketOrder.usdcSize"))
val targetLeverage = parser.asDouble(trade["targetLeverage"]) ?: 1.0

return if (targetLeverage == 0.0) {
null
} else {
val usdcSize = marketOrderUsdcSize ?: parser.asDouble(parser.value(trade, "size.usdcSize")) ?: return null
usdcSize / targetLeverage
}
}
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
package exchange.dydx.abacus.calculator

import abs
import exchange.dydx.abacus.protocols.ParserProtocol
import exchange.dydx.abacus.utils.MAX_LEVERAGE_BUFFER_PERCENT
import exchange.dydx.abacus.utils.MAX_SUBACCOUNT_NUMBER
import exchange.dydx.abacus.utils.NUM_PARENT_SUBACCOUNTS
import kollections.iListOf
import kotlin.math.min

internal object MarginModeCalculator {
fun findExistingPosition(
Expand Down Expand Up @@ -187,4 +190,88 @@ internal object MarginModeCalculator {
// User has reached the maximum number of childSubaccounts for their current parentSubaccount
error("No available subaccount number")
}

private fun getIsIncreasingPositionSize(
parser: ParserProtocol,
subaccount: Map<String, Any>?,
tradeInput: Map<String, Any>?,
): Boolean {
val marketId = parser.asString(tradeInput?.get("marketId")) ?: return true
val position = parser.asNativeMap(parser.value(subaccount, "openPositions.$marketId"))
val currentSize = parser.asDouble(parser.value(position, "size.current")) ?: 0.0
val postOrderSize = parser.asDouble(parser.value(position, "size.postOrder")) ?: 0.0
return postOrderSize.abs() > currentSize.abs()
}

/**
* @description Determine if collateral should be transferred for an isolated margin trade
*/
fun getShouldTransferCollateral(
parser: ParserProtocol,
subaccount: Map<String, Any>?,
tradeInput: Map<String, Any>?,
): Boolean {
val isIncreasingPositionSize = getIsIncreasingPositionSize(parser, subaccount, tradeInput)
val isIsolatedMarginOrder = parser.asString(tradeInput?.get("marginMode")) == "ISOLATED"
val isReduceOnly = parser.asBool(tradeInput?.get("reduceOnly")) ?: false

return isIncreasingPositionSize && isIsolatedMarginOrder && !isReduceOnly
}

private fun getTransferAmountFromTargetLeverage(
askPrice: Double,
oraclePrice: Double,
size: Double,
targetLeverage: Double,
): Double {
if (targetLeverage == 0.0) {
return 0.0
}

return (oraclePrice * size) / targetLeverage + (askPrice - oraclePrice) * size
}

/**
* @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.
*/
fun calculateIsolatedMarginTransferAmount(
parser: ParserProtocol,
trade: Map<String, Any>,
market: Map<String, Any>?,
): Double? {
val targetLeverage = parser.asDouble(trade["targetLeverage"]) ?: 1.0
val size = parser.asDouble(parser.value(trade, "size.size"))?.abs() ?: return null
val oraclePrice = parser.asDouble(parser.value(market, "oraclePrice")) ?: return null
val askPrice = 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 maxLeverageForMarket = if (effectiveImf != 0.0) {
1.0 / effectiveImf
} else if (initialMarginFraction != 0.0) {
1.0 / initialMarginFraction
} else {
null
}

// Cap targetLeverage to 98% of max leverage
val adjustedTargetLeverage = if (maxLeverageForMarket != null) {
val cappedLeverage = maxLeverageForMarket * MAX_LEVERAGE_BUFFER_PERCENT
min(targetLeverage, cappedLeverage)
} else {
null
}

return if (adjustedTargetLeverage == 0.0 || adjustedTargetLeverage == null) {
null
} else {
getTransferAmountFromTargetLeverage(
askPrice,
oraclePrice,
size,
targetLeverage = adjustedTargetLeverage,
)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import exchange.dydx.abacus.calculator.SlippageConstants.TAKE_PROFIT_MARKET_ORDE
import exchange.dydx.abacus.output.input.MarginMode
import exchange.dydx.abacus.protocols.ParserProtocol
import exchange.dydx.abacus.state.manager.EnvironmentFeatureFlags
import exchange.dydx.abacus.utils.NUM_PARENT_SUBACCOUNTS
import exchange.dydx.abacus.utils.Numeric
import exchange.dydx.abacus.utils.QUANTUM_MULTIPLIER
import exchange.dydx.abacus.utils.Rounder
Expand Down Expand Up @@ -52,7 +53,7 @@ internal class TradeInputCalculator(

internal fun calculate(
state: Map<String, Any>,
subaccountNumber: Int?,
subaccountNumber: Int,
input: String?,
): Map<String, Any> {
val account = parser.asNativeMap(state["account"])
Expand Down Expand Up @@ -402,6 +403,7 @@ internal class TradeInputCalculator(
if (subaccount == null || market == null) {
return null
}

val marginMode = parser.asString(parser.value(trade, "marginMode"))
val marketId = parser.asString(market["id"]) ?: return null
val position = parser.asNativeMap(
Expand All @@ -410,18 +412,17 @@ internal class TradeInputCalculator(
"openPositions.$marketId",
),
)

if (position != null) {
when (marginMode) {
"ISOLATED" -> {
// TODO: When the collateral of child subaccounts is implemented, return from here. The below code is the CROSS implementation.
val currentNotionalTotal = parser.asDouble(parser.value(position, "notionalTotal.current"))
val postOrderNotionalTotal = parser.asDouble(parser.value(position, "notionalTotal.postOrder"))
val mmf = parser.asDouble(parser.value(market, "configs.maintenanceMarginFraction"))
if (currentNotionalTotal != null && mmf != null) {
if (postOrderNotionalTotal != null) {
return postOrderNotionalTotal.times(mmf)
val currentEquity = parser.asDouble(parser.value(position, "equity.current"))
val postOrderEquity = parser.asDouble(parser.value(position, "equity.postOrder"))
if (currentEquity != null) {
if (postOrderEquity != null) {
return postOrderEquity
}
return currentNotionalTotal.times(mmf)
return currentEquity
}
}

Expand Down Expand Up @@ -455,7 +456,14 @@ internal class TradeInputCalculator(
market: Map<String, Any>?
): Double? {
if (subaccount == null || market == null) return null
// TODO: When Child Subaccounts are added to Subaccount data class, return the child subaccount's leverage
val subaccountNumber = parser.asInt(subaccount["subaccountNumber"]) ?: return null

if (subaccountNumber >= NUM_PARENT_SUBACCOUNTS) {
val currentLeverage = parser.asDouble(parser.value(subaccount, "leverage.current"))
val postOrderLeverage = parser.asDouble(parser.value(subaccount, "leverage.postOrder"))
return postOrderLeverage ?: currentLeverage
}

val marketId = parser.asString(market["id"])
val position = parser.asNativeMap(
parser.value(
Expand Down Expand Up @@ -1765,6 +1773,25 @@ internal class TradeInputCalculator(

else -> {}
}

// Calculate isolated margin transfer amount
if (MarginModeCalculator.getShouldTransferCollateral(
parser,
subaccount,
trade,
)
) {
val isolatedMarginTransferAmount = MarginModeCalculator.calculateIsolatedMarginTransferAmount(
parser,
trade,
market,
)

summary.safeSet("isolatedMarginTransferAmount", isolatedMarginTransferAmount)
} else {
summary.safeSet("isolatedMarginTransferAmount", null)
}

return summary
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,7 @@ data class TradeInputSummary(
val filled: Boolean,
val positionMargin: Double?,
val positionLeverage: Double?,
val isolatedMarginTransferAmount: Double?,
) {
companion object {
internal fun create(
Expand All @@ -330,6 +331,8 @@ data class TradeInputSummary(
val filled = parser.asBool(data["filled"]) ?: false
val positionMargin = parser.asDouble(data["positionMargin"])
val positionLeverage = parser.asDouble(data["positionLeverage"])
val isolatedMarginTransferAmount =
parser.asDouble(data["isolatedMarginTransferAmount"])

return if (
existing?.price != price ||
Expand All @@ -341,7 +344,8 @@ data class TradeInputSummary(
existing?.total != total ||
existing?.positionMargin != positionMargin ||
existing?.positionLeverage != positionLeverage ||
existing?.filled != filled
existing?.filled != filled ||
existing.isolatedMarginTransferAmount != isolatedMarginTransferAmount
) {
TradeInputSummary(
price,
Expand All @@ -355,6 +359,7 @@ data class TradeInputSummary(
filled,
positionMargin,
positionLeverage,
isolatedMarginTransferAmount,
)
} else {
existing
Expand Down
Loading
Loading