Skip to content

Commit

Permalink
Create Input for Adjusting Isolated Position's Margin (#326)
Browse files Browse the repository at this point in the history
* draft new Input

* adjustIsolatedMarginInput: state, tests, calculators

* update documentation and nit

* update summary

* spotlessApply

* remove newline

* bump version

* linter get what linter wants

* detekt rule

* fix validation

* force enum usage instead of converting to string

* spotlessApply

* string -> enum

* kotlin enum feedback

* built-in kotlin enum for input field

* bump verison, update test

* Update accessors due to built-in enum change

* update docs
  • Loading branch information
jaredvu authored May 8, 2024
1 parent 037a447 commit 1471edd
Show file tree
Hide file tree
Showing 21 changed files with 1,028 additions and 47 deletions.
2 changes: 1 addition & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ allprojects {
}

group = "exchange.dydx.abacus"
version = "1.7.4"
version = "1.7.5"

repositories {
google()
Expand Down
3 changes: 3 additions & 0 deletions detekt.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ naming:
# /exchange -> /dydx -> /abacus
# didn't seem worth the potential thrash in PRs to fix (feel free to fix if you feel differently)
active: false
MatchingDeclarationName:
# Affects a lot of the TradingStateMachine+_.kt files
active: false

complexity:
CognitiveComplexMethod:
Expand Down
28 changes: 27 additions & 1 deletion docs/API/Actions.md
Original file line number Diff line number Diff line change
Expand Up @@ -240,4 +240,30 @@ Take profit order trigger price's percentage difference from the position's aver

### takeProfitUsdcDiff

Take profit order trigger price's usdc difference from the position's average entry price
Take profit order trigger price's usdc difference from the position's average entry price

# AdjustIsolatedMargin

fun adjustIsolatedMargin(data: String?, type: AdjustIsolatedMarginInputField?): AppStateResponse

The input state is in `response.state.input.adjustIsolatedMargin` as a [AdjustIsolatedMarginInput](../Input/AdjustIsolatedMarginInput.md).

### data

Data input in string format

## AdjustIsolatedMarginInputField

### Type

IsolatedMarginAdjustmentType
Add - Add margin to the child's isolated margin account from the parent's cross margin account
Remove - Remove margin from the child's isolated margin account to the parent's cross margin account

### Amount

Amount of USDC to remove or add

### ChildSubaccountNumber

Subaccount number for the child whose margin is to be adjusted
21 changes: 21 additions & 0 deletions docs/Input/AdjustIsolatedMarginInput.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# AdjustIsolatedMarginInput

data class AdjustIsolatedMarginInput(
 val type: String?, // "ADD" or "REMOVE"
 val amount: Double?,
 val childSubaccountNumber: Int?,
)

## type

ADD - Add margin to the child's isolated margin account from the parent's cross margin account
REMOVE - Remove margin from the child's isolated margin account to the parent's cross margin account

## amount

Amount of USDC to remove or add

## childSubaccountNumber

Subaccount number for the child whose margin is to be adjusted

Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
package exchange.dydx.abacus.calculator

import exchange.dydx.abacus.output.input.IsolatedMarginAdjustmentType
import exchange.dydx.abacus.protocols.ParserProtocol
import exchange.dydx.abacus.utils.Numeric
import exchange.dydx.abacus.utils.mutable
import exchange.dydx.abacus.utils.safeSet

@Suppress("UNCHECKED_CAST")
internal class AdjustIsolatedMarginInputCalculator(val parser: ParserProtocol) {
private val subaccountTransformer = SubaccountTransformer()

internal fun calculate(
state: Map<String, Any>,
parentSubaccountNumber: Int?,
): Map<String, Any> {
val wallet = parser.asNativeMap(state["wallet"])
val isolatedMarginAdjustment = parser.asNativeMap(state["adjustIsolatedMargin"])
val childSubaccountNumber = parser.asInt(isolatedMarginAdjustment?.get("ChildSubaccountNumber"))
val type = parser.asString(isolatedMarginAdjustment?.get("Type"))?.let {
IsolatedMarginAdjustmentType.valueOf(it)
} ?: IsolatedMarginAdjustmentType.Add

return if (wallet != null && isolatedMarginAdjustment != null && type != null) {
val modified = state.mutable()
val parentTransferDelta = getModifiedTransferDelta(isolatedMarginAdjustment, true)
val childTransferDelta = getModifiedTransferDelta(isolatedMarginAdjustment, false)

val walletPostParentSubaccountTransfer =
subaccountTransformer.applyIsolatedMarginAdjustmentToWallet(
wallet,
subaccountNumber = parentSubaccountNumber,
parentTransferDelta,
parser,
"postOrder",
)

val walletPostChildSubaccountTransfer =
subaccountTransformer.applyIsolatedMarginAdjustmentToWallet(
wallet = walletPostParentSubaccountTransfer,
subaccountNumber = childSubaccountNumber,
childTransferDelta,
parser,
"postOrder",
)

val modifiedParentSubaccount = parser.asNativeMap(parser.value(walletPostChildSubaccountTransfer, "accounts.subaccounts.$parentSubaccountNumber"))
val modifiedChildSubaccount = parser.asNativeMap(parser.value(walletPostChildSubaccountTransfer, "accounts.subaccounts.$childSubaccountNumber"))
val modifiedIsolatedMarginAdjustment = finalize(isolatedMarginAdjustment, modifiedParentSubaccount, modifiedChildSubaccount, type)

modified["adjustIsolatedMargin"] = modifiedIsolatedMarginAdjustment
modified["wallet"] = walletPostChildSubaccountTransfer
modified
} else {
state
}
}

private fun getModifiedTransferDelta(
isolatedMarginAdjustment: Map<String, Any>,
isParentSubaccount: Boolean,
): Map<String, Double> {
val type = parser.asString(isolatedMarginAdjustment["Type"])?.let {
IsolatedMarginAdjustmentType.valueOf(it)
} ?: IsolatedMarginAdjustmentType.Add
val amount = parser.asDouble(isolatedMarginAdjustment["Amount"])

when (type) {
IsolatedMarginAdjustmentType.Add -> {
val multiplier =
if (isParentSubaccount) Numeric.double.NEGATIVE else Numeric.double.POSITIVE
val usdcSize = (amount ?: Numeric.double.ZERO) * multiplier

return mapOf(
"usdcSize" to usdcSize,
)
}

IsolatedMarginAdjustmentType.Remove -> {
val multiplier =
if (isParentSubaccount) Numeric.double.POSITIVE else Numeric.double.NEGATIVE
val usdcSize = (amount ?: Numeric.double.ZERO) * multiplier

return mapOf(
"usdcSize" to usdcSize,
)
}
}
}

private fun summaryForType(
parentSubaccount: Map<String, Any>?,
childSubaccount: Map<String, Any>?,
type: IsolatedMarginAdjustmentType,
): Map<String, Any> {
val summary = mutableMapOf<String, Any>()
val crossCollateral = parser.asDouble(parser.value(parentSubaccount, "freeCollateral.postOrder"))
val crossMarginUsage = parser.asDouble(parser.value(parentSubaccount, "marginUsage.postOrder"))
val openPositions = parser.asNativeMap(childSubaccount?.get("openPositions"))
val marketId = openPositions?.keys?.firstOrNull()
val positionMargin = parser.asDouble(parser.value(childSubaccount, "freeCollateral.postOrder"))
val positionLeverage = parser.asDouble(parser.value(childSubaccount, "openPositions.$marketId.leverage.postOrder"))
val liquidationPrice = parser.asDouble(parser.value(childSubaccount, "openPositions.$marketId.liquidationPrice.postOrder"))

when (type) {
IsolatedMarginAdjustmentType.Add -> {
summary.safeSet("crossFreeCollateral", crossCollateral)
summary.safeSet("crossMarginUsage", crossMarginUsage)
summary.safeSet("positionMargin", positionMargin)
summary.safeSet("positionLeverage", positionLeverage)
summary.safeSet("liquidationPrice", liquidationPrice)
}

IsolatedMarginAdjustmentType.Remove -> {
summary.safeSet("crossFreeCollateral", crossCollateral)
summary.safeSet("crossMarginUsage", crossMarginUsage)
summary.safeSet("positionMargin", positionMargin)
summary.safeSet("positionLeverage", positionLeverage)
summary.safeSet("liquidationPrice", liquidationPrice)
}
}

return summary
}

private fun finalize(
isolatedMarginAdjustment: Map<String, Any>,
parentSubaccount: Map<String, Any>?,
childSubaccount: Map<String, Any>?,
type: IsolatedMarginAdjustmentType,
): Map<String, Any> {
val modified = isolatedMarginAdjustment.mutable()
modified.safeSet("summary", summaryForType(parentSubaccount, childSubaccount, type))
return modified
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,26 @@ internal class SubaccountTransformer {
}
}

internal fun applyIsolatedMarginAdjustmentToWallet(
wallet: Map<String, Any>,
subaccountNumber: Int?,
delta: Map<String, Double>,
parser: ParserProtocol,
period: String
): Map<String, Any> {
val key = "account.subaccounts.$subaccountNumber"
val subaccount = parser.asNativeMap(parser.value(wallet, key))

if (subaccount != null) {
val modifiedSubaccount = applyDeltaToSubaccount(subaccount, delta, parser, period)
val modifiedWallet = wallet.mutable()
modifiedWallet.safeSet(key, modifiedSubaccount)
return modifiedWallet
}

return wallet
}

internal fun applyTradeToSubaccount(
subaccount: Map<String, Any>?,
trade: Map<String, Any>,
Expand Down
Loading

0 comments on commit 1471edd

Please sign in to comment.