From 6aa4ecfb18ca34eb1eb42c6ba0d640888d99160d Mon Sep 17 00:00:00 2001 From: Rui <102453770+ruixhuang@users.noreply.github.com> Date: Fri, 8 Nov 2024 11:13:50 -0800 Subject: [PATCH] TRCL-3706 Static Typing: Fixing isolated margin position update (#741) --- build.gradle.kts | 2 +- .../calculator/V2/SubaccountCalculatorV2.kt | 2 +- .../functional/vault/Vault.kt | 2 +- .../output/account/Subaccount.kt | 47 ++++++++-- .../account/SubaccountPendingPosition.kt | 94 ++++++++++++++----- .../processor/input/TradeInputProcessor.kt | 2 +- .../processor/markets/MarketProcessor.kt | 2 +- .../wallet/account/SubaccountProcessor.kt | 11 ++- .../state/internalstate/InternalState.kt | 4 - .../BlockRewardNotificationProvider.kt | 3 +- v4_abacus.podspec | 2 +- 11 files changed, 128 insertions(+), 43 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 5afb9dc23..cd4830bfa 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -52,7 +52,7 @@ allprojects { } group = "exchange.dydx.abacus" -version = "1.13.18" +version = "1.13.19" repositories { google() diff --git a/src/commonMain/kotlin/exchange.dydx.abacus/calculator/V2/SubaccountCalculatorV2.kt b/src/commonMain/kotlin/exchange.dydx.abacus/calculator/V2/SubaccountCalculatorV2.kt index 970230c6b..ef125c7b8 100644 --- a/src/commonMain/kotlin/exchange.dydx.abacus/calculator/V2/SubaccountCalculatorV2.kt +++ b/src/commonMain/kotlin/exchange.dydx.abacus/calculator/V2/SubaccountCalculatorV2.kt @@ -419,7 +419,7 @@ internal class SubaccountCalculatorV2( val marginMode = position.marginMode when (marginMode) { MarginMode.Isolated -> { - val equity = subaccount.equity + val equity = subaccount.calculated[period]?.equity calculated.marginValue = equity } diff --git a/src/commonMain/kotlin/exchange.dydx.abacus/functional/vault/Vault.kt b/src/commonMain/kotlin/exchange.dydx.abacus/functional/vault/Vault.kt index b38362362..aa0d2fb86 100644 --- a/src/commonMain/kotlin/exchange.dydx.abacus/functional/vault/Vault.kt +++ b/src/commonMain/kotlin/exchange.dydx.abacus/functional/vault/Vault.kt @@ -259,13 +259,13 @@ object VaultCalculator { val assetPositionsMap = assetPosition?.let { mapOf((it.symbol ?: "") to it) } val subaccount = subaccountCalculator.calculate( subaccount = InternalSubaccountState( - equity = equity ?: 0.0, assetPositions = assetPositionsMap, openPositions = perpetualPosition?.let { mapOf((it.market ?: "") to it) }, subaccountNumber = 0, calculated = mutableMapOf( CalculationPeriod.current to InternalSubaccountCalculated( + equity = equity ?: 0.0, quoteBalance = subaccountCalculator.calculateQuoteBalance( assetPositionsMap, ), diff --git a/src/commonMain/kotlin/exchange.dydx.abacus/output/account/Subaccount.kt b/src/commonMain/kotlin/exchange.dydx.abacus/output/account/Subaccount.kt index 88641f4dc..88c1acd29 100644 --- a/src/commonMain/kotlin/exchange.dydx.abacus/output/account/Subaccount.kt +++ b/src/commonMain/kotlin/exchange.dydx.abacus/output/account/Subaccount.kt @@ -5,6 +5,7 @@ import exchange.dydx.abacus.output.TradeStatesWithDoubleValues import exchange.dydx.abacus.processor.base.ComparisonOrder import exchange.dydx.abacus.protocols.LocalizerProtocol import exchange.dydx.abacus.protocols.ParserProtocol +import exchange.dydx.abacus.state.internalstate.InternalPerpetualPendingPosition import exchange.dydx.abacus.state.internalstate.InternalPerpetualPosition import exchange.dydx.abacus.state.internalstate.InternalSubaccountState import exchange.dydx.abacus.utils.IList @@ -242,11 +243,19 @@ data class Subaccount( ) } - val pendingPositions = pendingPositions( - existing?.pendingPositions, - parser, - parser.asList(data?.get("pendingPositions")), - ) + val pendingPositions = if (staticTyping) { + createPendingPositions( + existing = existing?.pendingPositions, + parser = parser, + pendingPositions = internalState?.pendingPositions, + ) + } else { + pendingPositionsDeprecated( + existing?.pendingPositions, + parser, + parser.asList(data?.get("pendingPositions")), + ) + } val orders = if (staticTyping) { internalState?.orders?.toIList() @@ -387,7 +396,32 @@ data class Subaccount( )?.toIList() } - private fun pendingPositions( + private fun createPendingPositions( + existing: IList?, + parser: ParserProtocol, + pendingPositions: List?, + ): IList? { + val newEntries: MutableList = mutableListOf() + for (position in pendingPositions ?: emptyList()) { + val pendingPosition = SubaccountPendingPosition.create( + existing = null, + parser = parser, + data = emptyMap(), + internalState = position, + ) + if (pendingPosition != null) { + newEntries.add(pendingPosition) + } + } + + return if (newEntries != existing) { + newEntries.toIList() + } else { + existing + } + } + + private fun pendingPositionsDeprecated( existing: IList?, parser: ParserProtocol, data: List<*>?, @@ -402,6 +436,7 @@ data class Subaccount( obj as? SubaccountPendingPosition, parser, parser.asMap(itemData), + internalState = null, ) }, true)?.toIList() } diff --git a/src/commonMain/kotlin/exchange.dydx.abacus/output/account/SubaccountPendingPosition.kt b/src/commonMain/kotlin/exchange.dydx.abacus/output/account/SubaccountPendingPosition.kt index 72b97cd64..0cc0086c3 100644 --- a/src/commonMain/kotlin/exchange.dydx.abacus/output/account/SubaccountPendingPosition.kt +++ b/src/commonMain/kotlin/exchange.dydx.abacus/output/account/SubaccountPendingPosition.kt @@ -1,7 +1,9 @@ package exchange.dydx.abacus.output.account +import exchange.dydx.abacus.calculator.CalculationPeriod import exchange.dydx.abacus.output.TradeStatesWithDoubleValues import exchange.dydx.abacus.protocols.ParserProtocol +import exchange.dydx.abacus.state.internalstate.InternalPerpetualPendingPosition import exchange.dydx.abacus.utils.Logger import kollections.JsExport import kotlinx.serialization.Serializable @@ -23,32 +25,33 @@ data class SubaccountPendingPosition( existing: SubaccountPendingPosition?, parser: ParserProtocol, data: Map?, + internalState: InternalPerpetualPendingPosition?, ): SubaccountPendingPosition? { - Logger.d { "creating Account Pending Position\n" } - data?.let { - val assetId = parser.asString(data["assetId"]) ?: return null - val displayId = parser.asString(data["displayId"]) ?: return null - val marketId = parser.asString(data["marketId"]) ?: return null - val firstOrderId = parser.asString(data["firstOrderId"]) ?: return null - val orderCount = parser.asInt(data["orderCount"]) ?: return null - val freeCollateral = TradeStatesWithDoubleValues.create( - null, - parser, - parser.asMap(data["freeCollateral"]), + if (internalState != null) { + Logger.d { "Account Pending Position from internal state\n" } + val assetId = internalState.assetId ?: return null + val displayId = internalState.displayId ?: return null + val marketId = internalState.marketId ?: return null + val firstOrderId = internalState.firstOrderId ?: return null + val orderCount = internalState.orderCount ?: return null + val freeCollateral = TradeStatesWithDoubleValues( + internalState.calculated[CalculationPeriod.current]?.freeCollateral, + internalState.calculated[CalculationPeriod.post]?.freeCollateral, + internalState.calculated[CalculationPeriod.settled]?.freeCollateral, ) - val quoteBalance = TradeStatesWithDoubleValues.create( - null, - parser, - parser.asMap(data["quoteBalance"]), + val quoteBalance = TradeStatesWithDoubleValues( + internalState.calculated[CalculationPeriod.current]?.quoteBalance, + internalState.calculated[CalculationPeriod.post]?.quoteBalance, + internalState.calculated[CalculationPeriod.settled]?.quoteBalance, ) - val equity = TradeStatesWithDoubleValues.create( - null, - parser, - parser.asMap(data["equity"]), + val equity = TradeStatesWithDoubleValues( + internalState.calculated[CalculationPeriod.current]?.equity, + internalState.calculated[CalculationPeriod.post]?.equity, + internalState.calculated[CalculationPeriod.settled]?.equity, ) return if (existing?.assetId != assetId || - existing?.displayId != displayId || + existing.displayId != displayId || existing.marketId != marketId || existing.firstOrderId != firstOrderId || existing.orderCount != orderCount || @@ -69,9 +72,56 @@ data class SubaccountPendingPosition( } else { existing } + } else { + Logger.d { "creating Account Pending Position\n" } + data?.let { + val assetId = parser.asString(data["assetId"]) ?: return null + val displayId = parser.asString(data["displayId"]) ?: return null + val marketId = parser.asString(data["marketId"]) ?: return null + val firstOrderId = parser.asString(data["firstOrderId"]) ?: return null + val orderCount = parser.asInt(data["orderCount"]) ?: return null + val freeCollateral = TradeStatesWithDoubleValues.create( + null, + parser, + parser.asMap(data["freeCollateral"]), + ) + val quoteBalance = TradeStatesWithDoubleValues.create( + null, + parser, + parser.asMap(data["quoteBalance"]), + ) + val equity = TradeStatesWithDoubleValues.create( + null, + parser, + parser.asMap(data["equity"]), + ) + + return if (existing?.assetId != assetId || + existing?.displayId != displayId || + existing.marketId != marketId || + existing.firstOrderId != firstOrderId || + existing.orderCount != orderCount || + existing.freeCollateral !== freeCollateral || + existing.quoteBalance !== quoteBalance || + existing.equity !== equity + ) { + SubaccountPendingPosition( + assetId = assetId, + displayId = displayId, + marketId = marketId, + firstOrderId = firstOrderId, + orderCount = orderCount, + freeCollateral = freeCollateral, + quoteBalance = quoteBalance, + equity = equity, + ) + } else { + existing + } + } + Logger.d { "Account Pending Position not valid" } + return null } - Logger.d { "Account Pending Position not valid" } - return null } private fun positionSide(size: TradeStatesWithDoubleValues): TradeStatesWithPositionSides { diff --git a/src/commonMain/kotlin/exchange.dydx.abacus/processor/input/TradeInputProcessor.kt b/src/commonMain/kotlin/exchange.dydx.abacus/processor/input/TradeInputProcessor.kt index f6120b77f..81ff834f0 100644 --- a/src/commonMain/kotlin/exchange.dydx.abacus/processor/input/TradeInputProcessor.kt +++ b/src/commonMain/kotlin/exchange.dydx.abacus/processor/input/TradeInputProcessor.kt @@ -317,7 +317,7 @@ internal class TradeInputProcessor( if (existingPosition != null) { trade.marginMode = - if (subaccount?.equity != null) MarginMode.Isolated else MarginMode.Cross + if (subaccount?.calculated?.get(CalculationPeriod.current)?.equity != null) MarginMode.Isolated else MarginMode.Cross val currentPositionLeverage = existingPosition.calculated[CalculationPeriod.current]?.leverage?.abs() val positionLeverage = diff --git a/src/commonMain/kotlin/exchange.dydx.abacus/processor/markets/MarketProcessor.kt b/src/commonMain/kotlin/exchange.dydx.abacus/processor/markets/MarketProcessor.kt index 5da487db2..9c51a3dfc 100644 --- a/src/commonMain/kotlin/exchange.dydx.abacus/processor/markets/MarketProcessor.kt +++ b/src/commonMain/kotlin/exchange.dydx.abacus/processor/markets/MarketProcessor.kt @@ -184,9 +184,9 @@ internal class MarketProcessor( payload: List, period: IndexerSparklineTimePeriod, ): PerpetualMarket? { + cachedIndexerSparklines[marketId] = payload.mapNotNull { parser.asDouble(it) }.reversed() when (period) { IndexerSparklineTimePeriod.ONEDAY -> { - cachedIndexerSparklines[marketId] = payload.mapNotNull { parser.asDouble(it) }.reversed() return createPerpetualMarket(marketId) } IndexerSparklineTimePeriod.SEVENDAYS -> { diff --git a/src/commonMain/kotlin/exchange.dydx.abacus/processor/wallet/account/SubaccountProcessor.kt b/src/commonMain/kotlin/exchange.dydx.abacus/processor/wallet/account/SubaccountProcessor.kt index 3c256ade5..037a3dc38 100644 --- a/src/commonMain/kotlin/exchange.dydx.abacus/processor/wallet/account/SubaccountProcessor.kt +++ b/src/commonMain/kotlin/exchange.dydx.abacus/processor/wallet/account/SubaccountProcessor.kt @@ -120,8 +120,8 @@ internal open class SubaccountProcessor( ) val subaccountCalculated = state.calculated[CalculationPeriod.current] ?: InternalSubaccountCalculated() - state.calculated[CalculationPeriod.current] = subaccountCalculated subaccountCalculated.quoteBalance = subaccountCalculator.calculateQuoteBalance(state.assetPositions) + state.calculated[CalculationPeriod.current] = subaccountCalculated val fills = parser.asTypedList(content["fills"]) state = processFills( @@ -231,8 +231,11 @@ internal open class SubaccountProcessor( existing.subaccountNumber = subaccountNumber existing.address = payload.address - existing.equity = parser.asDouble(payload.equity) - existing.freeCollateral = parser.asDouble(payload.freeCollateral) + val calculated = existing.calculated[CalculationPeriod.current] ?: InternalSubaccountCalculated() + calculated.equity = parser.asDouble(payload.equity) + calculated.freeCollateral = parser.asDouble(payload.freeCollateral) + existing.calculated[CalculationPeriod.current] = calculated + existing.marginEnabled = payload.marginEnabled existing.updatedAtHeight = payload.updatedAtHeight existing.latestProcessedBlockHeight = payload.latestProcessedBlockHeight @@ -251,8 +254,8 @@ internal open class SubaccountProcessor( } val subaccountCalculated = existing.calculated[CalculationPeriod.current] ?: InternalSubaccountCalculated() - existing.calculated[CalculationPeriod.current] = subaccountCalculated subaccountCalculated.quoteBalance = subaccountCalculator.calculateQuoteBalance(existing.assetPositions) + existing.calculated[CalculationPeriod.current] = subaccountCalculated return existing } diff --git a/src/commonMain/kotlin/exchange.dydx.abacus/state/internalstate/InternalState.kt b/src/commonMain/kotlin/exchange.dydx.abacus/state/internalstate/InternalState.kt index 836140ad6..b6bf3da14 100644 --- a/src/commonMain/kotlin/exchange.dydx.abacus/state/internalstate/InternalState.kt +++ b/src/commonMain/kotlin/exchange.dydx.abacus/state/internalstate/InternalState.kt @@ -305,8 +305,6 @@ internal data class InternalSubaccountState( var assetPositions: Map? = null, var subaccountNumber: Int, var address: String? = null, - var equity: Double? = null, - var freeCollateral: Double? = null, var marginEnabled: Boolean? = null, var updatedAtHeight: String? = null, var latestProcessedBlockHeight: String? = null, @@ -332,8 +330,6 @@ internal data class InternalSubaccountState( assetPositions = assetPositions?.map { it.key to it.value.copy() }?.toMap(), subaccountNumber = subaccountNumber, address = address, - equity = equity, - freeCollateral = freeCollateral, marginEnabled = marginEnabled, updatedAtHeight = updatedAtHeight, latestProcessedBlockHeight = latestProcessedBlockHeight, diff --git a/src/commonMain/kotlin/exchange.dydx.abacus/state/manager/notification/providers/BlockRewardNotificationProvider.kt b/src/commonMain/kotlin/exchange.dydx.abacus/state/manager/notification/providers/BlockRewardNotificationProvider.kt index 3bd2dfefa..5cc4e71a7 100644 --- a/src/commonMain/kotlin/exchange.dydx.abacus/state/manager/notification/providers/BlockRewardNotificationProvider.kt +++ b/src/commonMain/kotlin/exchange.dydx.abacus/state/manager/notification/providers/BlockRewardNotificationProvider.kt @@ -1,5 +1,6 @@ package exchange.dydx.abacus.state.manager.notification.providers +import com.ionspin.kotlin.bignum.decimal.toBigDecimal import exchange.dydx.abacus.output.Notification import exchange.dydx.abacus.output.NotificationPriority import exchange.dydx.abacus.output.NotificationType @@ -52,7 +53,7 @@ class BlockRewardNotificationProvider( token: String, ): Notification? { val blockHeight = blockReward.createdAtHeight - val blockRewardAmount = blockReward.tradingReward + val blockRewardAmount = blockReward.tradingReward.toBigDecimal().toPlainString() val params = iMapOf( "BLOCK_REWARD_HEIGHT" to blockHeight, "TOKEN_NAME" to token, diff --git a/v4_abacus.podspec b/v4_abacus.podspec index bac082fdb..39564ff53 100644 --- a/v4_abacus.podspec +++ b/v4_abacus.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |spec| spec.name = 'v4_abacus' - spec.version = '1.13.18' + spec.version = '1.13.19' spec.homepage = 'https://github.com/dydxprotocol/v4-abacus' spec.source = { :http=> ''} spec.authors = ''