diff --git a/src/commonMain/kotlin/exchange.dydx.abacus/calculator/AccountCalculator.kt b/src/commonMain/kotlin/exchange.dydx.abacus/calculator/AccountCalculator.kt index d9c128523..7c3b2cd0d 100644 --- a/src/commonMain/kotlin/exchange.dydx.abacus/calculator/AccountCalculator.kt +++ b/src/commonMain/kotlin/exchange.dydx.abacus/calculator/AccountCalculator.kt @@ -5,6 +5,7 @@ import exchange.dydx.abacus.utils.NUM_PARENT_SUBACCOUNTS import exchange.dydx.abacus.utils.ParsingHelper import exchange.dydx.abacus.utils.mutable import exchange.dydx.abacus.utils.safeSet +import exchange.dydx.abacus.output.MarginMode class AccountCalculator(val parser: ParserProtocol, private val useParentSubaccount: Boolean) { private val subaccountCalculator = SubaccountCalculator(parser) @@ -55,6 +56,7 @@ class AccountCalculator(val parser: ParserProtocol, private val useParentSubacco if (subaccountNumbers != null) { val groupedSubaccounts = mutableMapOf() for (subaccountNumber in subaccountNumbers) { + val marginMode = if(subaccountNumber >= NUM_PARENT_SUBACCOUNTS) MarginMode.ISOLATED else MarginMode.CROSS val subaccount = parser.asNativeMap(parser.value(subaccounts, "$subaccountNumber")) ?: break if (subaccountNumber < NUM_PARENT_SUBACCOUNTS) { @@ -76,6 +78,7 @@ class AccountCalculator(val parser: ParserProtocol, private val useParentSubacco mergeChildOpenPositions( parentSubaccount, subaccountNumber, + marginMode, subaccount, childOpenPositions, ) @@ -85,6 +88,7 @@ class AccountCalculator(val parser: ParserProtocol, private val useParentSubacco mergeChildPendingPositions( parentSubaccount, subaccountNumber, + marginMode, subaccount, orders, markets, @@ -93,7 +97,7 @@ class AccountCalculator(val parser: ParserProtocol, private val useParentSubacco parentSubaccount } } - parentSubaccount = mergeOrders(parentSubaccount, subaccount) + parentSubaccount = mergeOrders(parentSubaccount, subaccount, marginMode) parentSubaccount = sumEquity(parentSubaccount, subaccount) groupedSubaccounts["$parentSubaccountNumber"] = parentSubaccount } @@ -108,6 +112,7 @@ class AccountCalculator(val parser: ParserProtocol, private val useParentSubacco private fun mergeChildOpenPositions( parentSubaccount: Map, childSubaccountNumber: Int, + marginMode: MarginMode, childSubaccount: Map, childOpenPositions: Map, ): Map { @@ -121,6 +126,10 @@ class AccountCalculator(val parser: ParserProtocol, private val useParentSubacco "childSubaccountNumber", childSubaccountNumber, ) + modifiedChildOpenPosition?.safeSet( + "marginMode", + marginMode, + ) modifiedChildOpenPosition?.safeSet( "quoteBalance", childSubaccount["quoteBalance"], @@ -145,6 +154,7 @@ class AccountCalculator(val parser: ParserProtocol, private val useParentSubacco private fun mergeChildPendingPositions( parentSubaccount: Map, childSubaccountNumber: Int, + marginMode: MarginMode, childSubaccount: Map, childOrders: Map, markets: Map?, @@ -189,6 +199,14 @@ class AccountCalculator(val parser: ParserProtocol, private val useParentSubacco "orderCount", parser.value(pending, "orderCount"), ) + modifiedPendingPosition?.safeSet( + "childSubaccountNumber", + childSubaccountNumber, + ) + modifiedPendingPosition?.safeSet( + "marginMode", + marginMode, + ) modifiedPendingPosition.safeSet( "quoteBalance", childSubaccount["quoteBalance"], @@ -219,12 +237,13 @@ class AccountCalculator(val parser: ParserProtocol, private val useParentSubacco private fun mergeOrders( parentSubaccount: Map, childSubaccount: Map, + marginMode: MarginMode, ): Map { // Each empty subaccount should have order for one market only // Just in case it has more than one market, we will create // two separate pending positions. - val mergedOrders = ParsingHelper.merge( + var mergedOrders = ParsingHelper.merge( parser.asNativeMap( parser.value(parentSubaccount, "orders"), ), @@ -233,6 +252,15 @@ class AccountCalculator(val parser: ParserProtocol, private val useParentSubacco ), ) + mergedOrders = mergedOrders?.mapValues { (_, value) -> + var modifiedOrder = parser.asMap(value)?.toMutableMap() ?: mutableMapOf() + modifiedOrder?.safeSet( + "marginMode", + marginMode, + ) + modifiedOrder + } + val modifiedParentSubaccount = parentSubaccount.toMutableMap() modifiedParentSubaccount.safeSet( "orders", diff --git a/src/commonMain/kotlin/exchange.dydx.abacus/output/Account.kt b/src/commonMain/kotlin/exchange.dydx.abacus/output/Account.kt index 7432a2a0e..5d0ac12aa 100644 --- a/src/commonMain/kotlin/exchange.dydx.abacus/output/Account.kt +++ b/src/commonMain/kotlin/exchange.dydx.abacus/output/Account.kt @@ -177,6 +177,18 @@ enum class PositionSide(val rawValue: String) { } } +@JsExport +@Serializable +enum class MarginMode(val rawValue: String) { + ISOLATED("ISOLATED"), + CROSS("CROSS"); + + companion object { + operator fun invoke(rawValue: String) = + MarginMode.values().firstOrNull { it.rawValue == rawValue } + } +} + @JsExport @Serializable data class TradeStatesWithPositionSides( @@ -249,10 +261,14 @@ data class SubaccountPosition( val liquidationPrice: TradeStatesWithDoubleValues, val resources: SubaccountPositionResources, val childSubaccountNumber: Int?, + val marginMode: MarginMode?, val freeCollateral: TradeStatesWithDoubleValues, val marginUsage: TradeStatesWithDoubleValues, - val quoteBalance: TradeStatesWithDoubleValues, // available for isolated market position - val equity: TradeStatesWithDoubleValues, // available for isolated market position + /** available for isolated market position */ + val quoteBalance: TradeStatesWithDoubleValues, + /** available for isolated market position */ + val equity: TradeStatesWithDoubleValues, + /** Margin type of the position */ ) { companion object { internal fun create( @@ -361,6 +377,7 @@ data class SubaccountPosition( parser.asMap(data["liquidationPrice"]), ) val childSubaccountNumber = parser.asInt(data["childSubaccountNumber"]) + val marginMode = parser.asString("marginType")?.let{ MarginMode.invoke(it) } val freeCollateral = TradeStatesWithDoubleValues.create( null, parser, @@ -408,7 +425,8 @@ data class SubaccountPosition( existing.freeCollateral !== freeCollateral || existing.marginUsage !== marginUsage || existing.quoteBalance !== quoteBalance || - existing.equity !== equity + existing.equity !== equity || + existing.marginMode !== marginMode ) { val side = positionSide(size) SubaccountPosition( @@ -436,6 +454,7 @@ data class SubaccountPosition( liquidationPrice, resources, childSubaccountNumber, + marginMode, freeCollateral, marginUsage, quoteBalance, @@ -633,7 +652,6 @@ data class SubaccountOrderResources( @JsExport @Serializable data class SubaccountOrder( - val subaccountNumber: Int?, val id: String, val clientId: Int?, val type: OrderType, @@ -660,7 +678,9 @@ data class SubaccountOrder( val reduceOnly: Boolean, val cancelReason: String?, val resources: SubaccountOrderResources, -) { + val subaccountNumber: Int?, + val marginMode: MarginMode?, + ) { companion object { internal fun create( existing: SubaccountOrder?, @@ -670,8 +690,6 @@ data class SubaccountOrder( ): SubaccountOrder? { Logger.d { "creating Account Order\n" } data?.let { - // TODO: Remove default to 0 for subaccountNumber once new indexer response is consumed. Prevents breaking change - val subaccountNumber = parser.asInt(data["subaccountNumber"]) ?: 0 val id = parser.asString(data["id"]) val clientId = parser.asInt(data["clientId"]) val marketId = parser.asString(data["marketId"]) @@ -693,6 +711,9 @@ data class SubaccountOrder( val resources = parser.asMap(data["resources"])?.let { SubaccountOrderResources.create(existing?.resources, parser, it, localizer) } + // TODO: Remove default to 0 for subaccountNumber once new indexer response is consumed. Prevents breaking change + val subaccountNumber = parser.asInt(data["subaccountNumber"]) ?: 0 + val marginMode = parser.asString(data["marginMode"])?.let { MarginMode.invoke(it) } if (id != null && marketId != null && type != null && side != null && status != null && price != null && size != null && resources != null ) { @@ -717,8 +738,7 @@ data class SubaccountOrder( val cancelReason = parser.asString(data["cancelReason"]) return if ( - existing?.subaccountNumber != subaccountNumber || - existing.id != id || + existing?.id != id || existing.clientId != clientId || existing.type !== type || existing.side !== side || @@ -742,10 +762,11 @@ data class SubaccountOrder( existing.postOnly != postOnly || existing.reduceOnly != reduceOnly || existing.cancelReason != cancelReason || - existing.resources !== resources + existing.resources !== resources || + existing.subaccountNumber != subaccountNumber || + existing.marginMode !== marginMode ) { SubaccountOrder( - subaccountNumber, id, clientId, type, @@ -772,7 +793,9 @@ data class SubaccountOrder( reduceOnly, cancelReason, resources, - ) + subaccountNumber, + marginMode, + ) } else { existing } @@ -1338,7 +1361,11 @@ data class Subaccount( parser.asList(data["pendingPositions"]), ) val orders = - orders(parser, existing?.orders, parser.asMap(data["orders"]), localizer) + orders(parser, + existing?.orders, + parser.asMap(data["orders"]), + localizer + ) /* val transfers = AccountTransfers.fromArray( diff --git a/src/commonTest/kotlin/exchange.dydx.abacus/payload/v4/V4ParentSubaccountTests.kt b/src/commonTest/kotlin/exchange.dydx.abacus/payload/v4/V4ParentSubaccountTests.kt index b23fb799a..2b7143b63 100644 --- a/src/commonTest/kotlin/exchange.dydx.abacus/payload/v4/V4ParentSubaccountTests.kt +++ b/src/commonTest/kotlin/exchange.dydx.abacus/payload/v4/V4ParentSubaccountTests.kt @@ -218,6 +218,7 @@ class V4ParentSubaccountTests : V4BaseTests(true) { "current": 7962.44 }, "childSubaccountNumber": 128, + "marginMode": "ISOLATED", "equity": { "current": 829.16 }, @@ -533,6 +534,7 @@ class V4ParentSubaccountTests : V4BaseTests(true) { "current": 1410.69 }, "childSubaccountNumber": 128, + "marginMode": "ISOLATED", "quoteBalance": { "current": 267.89 }, @@ -603,6 +605,7 @@ class V4ParentSubaccountTests : V4BaseTests(true) { "current": 1949.55 }, "childSubaccountNumber": 128, + "marginMode": "ISOLATED", "quoteBalance": { "current": 367.89 },