diff --git a/build.gradle.kts b/build.gradle.kts index d9cf16fe9..df558d49b 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -52,7 +52,7 @@ allprojects { } group = "exchange.dydx.abacus" -version = "1.11.10" +version = "1.11.11" repositories { google() diff --git a/src/commonMain/kotlin/exchange.dydx.abacus/protocols/PublicProtocols.kt b/src/commonMain/kotlin/exchange.dydx.abacus/protocols/PublicProtocols.kt index f6c73d000..8ac8b256a 100644 --- a/src/commonMain/kotlin/exchange.dydx.abacus/protocols/PublicProtocols.kt +++ b/src/commonMain/kotlin/exchange.dydx.abacus/protocols/PublicProtocols.kt @@ -204,6 +204,7 @@ enum class AnalyticsEvent { TradePlaceOrderClick, TradeCancelOrderClick, TradeCancelAllOrdersClick, + TradeCloseAllPositionsClick, TradePlaceOrder, TradeCancelOrder, TradePlaceOrderSubmissionConfirmed, diff --git a/src/commonMain/kotlin/exchange.dydx.abacus/state/manager/AsyncAbacusStateManagerProtocol.kt b/src/commonMain/kotlin/exchange.dydx.abacus/state/manager/AsyncAbacusStateManagerProtocol.kt index 141d5ddcc..76ae9413f 100644 --- a/src/commonMain/kotlin/exchange.dydx.abacus/state/manager/AsyncAbacusStateManagerProtocol.kt +++ b/src/commonMain/kotlin/exchange.dydx.abacus/state/manager/AsyncAbacusStateManagerProtocol.kt @@ -66,6 +66,7 @@ interface AsyncAbacusStateManagerProtocol { // these functions provide payload fun placeOrderPayload(): HumanReadablePlaceOrderPayload? fun closePositionPayload(): HumanReadablePlaceOrderPayload? + fun closeAllPositionsPayload(): HumanReadableCloseAllPositionsPayload? fun triggerOrdersPayload(): HumanReadableTriggerOrdersPayload? fun cancelOrderPayload(orderId: String): HumanReadableCancelOrderPayload? fun cancelAllOrdersPayload(marketId: String?): HumanReadableCancelAllOrdersPayload? @@ -87,6 +88,7 @@ interface AsyncAbacusStateManagerProtocol { fun faucet(amount: Double, callback: TransactionCallback) fun cancelOrder(orderId: String, callback: TransactionCallback) fun cancelAllOrders(marketId: String?, callback: TransactionCallback) + fun closeAllPositions(callback: TransactionCallback) // Bridge functions. // If client is not using cancelOrder function, it should call orderCanceled function with diff --git a/src/commonMain/kotlin/exchange.dydx.abacus/state/manager/utils/Payloads.kt b/src/commonMain/kotlin/exchange.dydx.abacus/state/manager/utils/Payloads.kt index 6c8ddef58..320c2b833 100644 --- a/src/commonMain/kotlin/exchange.dydx.abacus/state/manager/utils/Payloads.kt +++ b/src/commonMain/kotlin/exchange.dydx.abacus/state/manager/utils/Payloads.kt @@ -3,6 +3,7 @@ package exchange.dydx.abacus.state.manager import exchange.dydx.abacus.output.input.OrderStatus import exchange.dydx.abacus.utils.IList import kollections.JsExport +import kollections.List import kotlinx.serialization.Serializable internal data class Subaccount( @@ -87,7 +88,13 @@ data class HumanReadableCancelOrderPayload( @Serializable data class HumanReadableCancelAllOrdersPayload( val marketId: String?, - val payloads: IList, + val payloads: List, +) + +@JsExport +@Serializable +data class HumanReadableCloseAllPositionsPayload( + val payloads: List, ) @JsExport diff --git a/src/commonMain/kotlin/exchange.dydx.abacus/state/v2/manager/AsyncAbacusStateManagerV2.kt b/src/commonMain/kotlin/exchange.dydx.abacus/state/v2/manager/AsyncAbacusStateManagerV2.kt index 78b9e200f..fbc414c2a 100644 --- a/src/commonMain/kotlin/exchange.dydx.abacus/state/v2/manager/AsyncAbacusStateManagerV2.kt +++ b/src/commonMain/kotlin/exchange.dydx.abacus/state/v2/manager/AsyncAbacusStateManagerV2.kt @@ -26,6 +26,7 @@ import exchange.dydx.abacus.state.manager.HistoricalPnlPeriod import exchange.dydx.abacus.state.manager.HistoricalTradingRewardsPeriod import exchange.dydx.abacus.state.manager.HumanReadableCancelAllOrdersPayload import exchange.dydx.abacus.state.manager.HumanReadableCancelOrderPayload +import exchange.dydx.abacus.state.manager.HumanReadableCloseAllPositionsPayload import exchange.dydx.abacus.state.manager.HumanReadableDepositPayload import exchange.dydx.abacus.state.manager.HumanReadablePlaceOrderPayload import exchange.dydx.abacus.state.manager.HumanReadableSubaccountTransferPayload @@ -516,6 +517,10 @@ class AsyncAbacusStateManagerV2( return adaptor?.cancelAllOrdersPayload(marketId) } + override fun closeAllPositionsPayload(): HumanReadableCloseAllPositionsPayload? { + return adaptor?.closeAllPositionsPayload() + } + override fun triggerOrdersPayload(): HumanReadableTriggerOrdersPayload? { return adaptor?.triggerOrdersPayload() } @@ -625,6 +630,15 @@ class AsyncAbacusStateManagerV2( } } + override fun closeAllPositions(callback: TransactionCallback) { + try { + adaptor?.closeAllPositions(callback) + } catch (e: Exception) { + val error = V4TransactionErrors.error(null, e.toString()) + callback(false, error, null) + } + } + override fun triggerCompliance(action: ComplianceAction, callback: TransactionCallback) { try { adaptor?.triggerCompliance(action, callback) diff --git a/src/commonMain/kotlin/exchange.dydx.abacus/state/v2/manager/StateManagerAdaptorV2.kt b/src/commonMain/kotlin/exchange.dydx.abacus/state/v2/manager/StateManagerAdaptorV2.kt index 62c3411fa..0c78bcd25 100644 --- a/src/commonMain/kotlin/exchange.dydx.abacus/state/v2/manager/StateManagerAdaptorV2.kt +++ b/src/commonMain/kotlin/exchange.dydx.abacus/state/v2/manager/StateManagerAdaptorV2.kt @@ -28,6 +28,7 @@ import exchange.dydx.abacus.state.manager.HistoricalPnlPeriod import exchange.dydx.abacus.state.manager.HistoricalTradingRewardsPeriod import exchange.dydx.abacus.state.manager.HumanReadableCancelAllOrdersPayload import exchange.dydx.abacus.state.manager.HumanReadableCancelOrderPayload +import exchange.dydx.abacus.state.manager.HumanReadableCloseAllPositionsPayload import exchange.dydx.abacus.state.manager.HumanReadableDepositPayload import exchange.dydx.abacus.state.manager.HumanReadablePlaceOrderPayload import exchange.dydx.abacus.state.manager.HumanReadableSubaccountTransferPayload @@ -62,6 +63,8 @@ import exchange.dydx.abacus.state.v2.supervisor.cancelAllOrders import exchange.dydx.abacus.state.v2.supervisor.cancelAllOrdersPayload import exchange.dydx.abacus.state.v2.supervisor.cancelOrder import exchange.dydx.abacus.state.v2.supervisor.cancelOrderPayload +import exchange.dydx.abacus.state.v2.supervisor.closeAllPositions +import exchange.dydx.abacus.state.v2.supervisor.closeAllPositionsPayload import exchange.dydx.abacus.state.v2.supervisor.closePosition import exchange.dydx.abacus.state.v2.supervisor.closePositionPayload import exchange.dydx.abacus.state.v2.supervisor.commitAdjustIsolatedMargin @@ -546,6 +549,10 @@ internal class StateManagerAdaptorV2( return accounts.cancelAllOrdersPayload(marketId) } + internal fun closeAllPositionsPayload(): HumanReadableCloseAllPositionsPayload? { + return accounts.closeAllPositionsPayload(currentHeight) + } + internal fun triggerOrdersPayload(): HumanReadableTriggerOrdersPayload? { return accounts.triggerOrdersPayload(currentHeight) } @@ -608,6 +615,10 @@ internal class StateManagerAdaptorV2( accounts.cancelAllOrders(marketId, callback) } + internal fun closeAllPositions(callback: TransactionCallback) { + accounts.closeAllPositions(currentHeight, callback) + } + internal fun orderCanceled(orderId: String) { accounts.orderCanceled(orderId) } diff --git a/src/commonMain/kotlin/exchange.dydx.abacus/state/v2/supervisor/AccountSupervisor.kt b/src/commonMain/kotlin/exchange.dydx.abacus/state/v2/supervisor/AccountSupervisor.kt index 22c9340a1..9118726ab 100644 --- a/src/commonMain/kotlin/exchange.dydx.abacus/state/v2/supervisor/AccountSupervisor.kt +++ b/src/commonMain/kotlin/exchange.dydx.abacus/state/v2/supervisor/AccountSupervisor.kt @@ -23,6 +23,7 @@ import exchange.dydx.abacus.state.manager.BlockAndTime import exchange.dydx.abacus.state.manager.HistoricalTradingRewardsPeriod import exchange.dydx.abacus.state.manager.HumanReadableCancelAllOrdersPayload import exchange.dydx.abacus.state.manager.HumanReadableCancelOrderPayload +import exchange.dydx.abacus.state.manager.HumanReadableCloseAllPositionsPayload import exchange.dydx.abacus.state.manager.HumanReadableDepositPayload import exchange.dydx.abacus.state.manager.HumanReadablePlaceOrderPayload import exchange.dydx.abacus.state.manager.HumanReadableSubaccountTransferPayload @@ -1154,6 +1155,10 @@ internal fun AccountSupervisor.cancelAllOrdersPayload(marketId: String?): HumanR return subaccount?.cancelAllOrdersPayload(marketId) } +internal fun AccountSupervisor.closeAllPositionsPayload(currentHeight: Int?): HumanReadableCloseAllPositionsPayload? { + return subaccount?.closeAllPositionsPayload(currentHeight) +} + internal fun AccountSupervisor.depositPayload(): HumanReadableDepositPayload? { return subaccount?.depositPayload() } @@ -1209,6 +1214,10 @@ internal fun AccountSupervisor.cancelAllOrders(marketId: String?, callback: Tran subaccount?.cancelAllOrders(marketId, callback) } +internal fun AccountSupervisor.closeAllPositions(currentHeight: Int?, callback: TransactionCallback) { + subaccount?.closeAllPositions(currentHeight, callback) +} + internal fun AccountSupervisor.orderCanceled(orderId: String) { subaccount?.orderCanceled(orderId) } diff --git a/src/commonMain/kotlin/exchange.dydx.abacus/state/v2/supervisor/AccountsSupervisor.kt b/src/commonMain/kotlin/exchange.dydx.abacus/state/v2/supervisor/AccountsSupervisor.kt index 0046d3fec..9b33a07e9 100644 --- a/src/commonMain/kotlin/exchange.dydx.abacus/state/v2/supervisor/AccountsSupervisor.kt +++ b/src/commonMain/kotlin/exchange.dydx.abacus/state/v2/supervisor/AccountsSupervisor.kt @@ -17,6 +17,7 @@ import exchange.dydx.abacus.state.manager.HistoricalPnlPeriod import exchange.dydx.abacus.state.manager.HistoricalTradingRewardsPeriod import exchange.dydx.abacus.state.manager.HumanReadableCancelAllOrdersPayload import exchange.dydx.abacus.state.manager.HumanReadableCancelOrderPayload +import exchange.dydx.abacus.state.manager.HumanReadableCloseAllPositionsPayload import exchange.dydx.abacus.state.manager.HumanReadableDepositPayload import exchange.dydx.abacus.state.manager.HumanReadablePlaceOrderPayload import exchange.dydx.abacus.state.manager.HumanReadableSubaccountTransferPayload @@ -295,6 +296,10 @@ internal fun AccountsSupervisor.cancelAllOrdersPayload(marketId: String?): Human return account?.cancelAllOrdersPayload(marketId) } +internal fun AccountsSupervisor.closeAllPositionsPayload(currentHeight: Int?): HumanReadableCloseAllPositionsPayload? { + return account?.closeAllPositionsPayload(currentHeight) +} + internal fun AccountsSupervisor.depositPayload(): HumanReadableDepositPayload? { return account?.depositPayload() } @@ -361,6 +366,10 @@ internal fun AccountsSupervisor.cancelAllOrders(marketId: String?, callback: Tra account?.cancelAllOrders(marketId, callback) } +internal fun AccountsSupervisor.closeAllPositions(currentHeight: Int?, callback: TransactionCallback) { + account?.closeAllPositions(currentHeight, callback) +} + internal fun AccountsSupervisor.orderCanceled(orderId: String) { account?.orderCanceled(orderId) } diff --git a/src/commonMain/kotlin/exchange.dydx.abacus/state/v2/supervisor/SubaccountSupervisor.kt b/src/commonMain/kotlin/exchange.dydx.abacus/state/v2/supervisor/SubaccountSupervisor.kt index 66ea4f279..6632e52d0 100644 --- a/src/commonMain/kotlin/exchange.dydx.abacus/state/v2/supervisor/SubaccountSupervisor.kt +++ b/src/commonMain/kotlin/exchange.dydx.abacus/state/v2/supervisor/SubaccountSupervisor.kt @@ -16,6 +16,7 @@ import exchange.dydx.abacus.state.manager.BlockAndTime import exchange.dydx.abacus.state.manager.FaucetRecord import exchange.dydx.abacus.state.manager.HumanReadableCancelAllOrdersPayload import exchange.dydx.abacus.state.manager.HumanReadableCancelOrderPayload +import exchange.dydx.abacus.state.manager.HumanReadableCloseAllPositionsPayload import exchange.dydx.abacus.state.manager.HumanReadableDepositPayload import exchange.dydx.abacus.state.manager.HumanReadableFaucetPayload import exchange.dydx.abacus.state.manager.HumanReadablePlaceOrderPayload @@ -334,6 +335,10 @@ internal class SubaccountSupervisor( return payloadProvider.cancelAllOrdersPayload(marketId) } + internal fun closeAllPositionsPayload(currentHeight: Int?): HumanReadableCloseAllPositionsPayload { + return payloadProvider.closeAllPositionsPayload(currentHeight) + } + fun trade( data: String?, type: TradeInputField?, @@ -435,6 +440,10 @@ internal class SubaccountSupervisor( return transactionSupervisor.cancelAllOrders(marketId, callback) } + internal fun closeAllPositions(currentHeight: Int?, callback: TransactionCallback): HumanReadableCloseAllPositionsPayload { + return transactionSupervisor.closeAllPositions(currentHeight, callback) + } + internal fun commitTriggerOrders( currentHeight: Int?, callback: TransactionCallback diff --git a/src/commonMain/kotlin/exchange.dydx.abacus/state/v2/supervisor/SubaccountTransactionPayloadProvider.kt b/src/commonMain/kotlin/exchange.dydx.abacus/state/v2/supervisor/SubaccountTransactionPayloadProvider.kt index 5e0f3a7e8..d7e97c6dc 100644 --- a/src/commonMain/kotlin/exchange.dydx.abacus/state/v2/supervisor/SubaccountTransactionPayloadProvider.kt +++ b/src/commonMain/kotlin/exchange.dydx.abacus/state/v2/supervisor/SubaccountTransactionPayloadProvider.kt @@ -2,16 +2,20 @@ package exchange.dydx.abacus.state.v2.supervisor import abs import exchange.dydx.abacus.calculator.MarginCalculator +import exchange.dydx.abacus.calculator.SlippageConstants.MARKET_ORDER_MAX_SLIPPAGE import exchange.dydx.abacus.calculator.TriggerOrdersConstants.TRIGGER_ORDER_DEFAULT_DURATION_DAYS import exchange.dydx.abacus.output.account.SubaccountOrder +import exchange.dydx.abacus.output.account.SubaccountPosition import exchange.dydx.abacus.output.input.IsolatedMarginAdjustmentType import exchange.dydx.abacus.output.input.MarginMode +import exchange.dydx.abacus.output.input.OrderSide import exchange.dydx.abacus.output.input.OrderStatus import exchange.dydx.abacus.output.input.OrderType import exchange.dydx.abacus.output.input.TradeInputGoodUntil import exchange.dydx.abacus.output.input.TriggerOrder import exchange.dydx.abacus.state.manager.HumanReadableCancelAllOrdersPayload import exchange.dydx.abacus.state.manager.HumanReadableCancelOrderPayload +import exchange.dydx.abacus.state.manager.HumanReadableCloseAllPositionsPayload import exchange.dydx.abacus.state.manager.HumanReadableDepositPayload import exchange.dydx.abacus.state.manager.HumanReadablePlaceOrderPayload import exchange.dydx.abacus.state.manager.HumanReadableSubaccountTransferPayload @@ -23,6 +27,7 @@ import exchange.dydx.abacus.state.model.TradingStateMachine import exchange.dydx.abacus.utils.LIMIT_CLOSE_ORDER_DEFAULT_DURATION_DAYS import exchange.dydx.abacus.utils.MAX_SUBACCOUNT_NUMBER import exchange.dydx.abacus.utils.NUM_PARENT_SUBACCOUNTS +import exchange.dydx.abacus.utils.Numeric import exchange.dydx.abacus.utils.SHORT_TERM_ORDER_DURATION import kollections.iEmptyList import kollections.iListOf @@ -46,6 +51,9 @@ internal interface SubaccountTransactionPayloadProviderProtocol { @Throws(Exception::class) fun closePositionPayload(currentHeight: Int?): HumanReadablePlaceOrderPayload + @Throws(Exception::class) + fun closeAllPositionsPayload(currentHeight: Int?): HumanReadableCloseAllPositionsPayload + @Throws(Exception::class) fun adjustIsolatedMarginPayload(): HumanReadableSubaccountTransferPayload @@ -191,6 +199,16 @@ internal class SubaccountTransactionPayloadProvider( ) } + @Throws(Exception::class) + override fun closeAllPositionsPayload(currentHeight: Int?): HumanReadableCloseAllPositionsPayload { + val subaccount = stateMachine.state?.subaccount(subaccountNumber) ?: throw Exception("subaccount is null") + val orderPayloads = subaccount.openPositions?.map { marketFullClosePositionPayload(it, currentHeight) }?.toIList() ?: iEmptyList() + + return HumanReadableCloseAllPositionsPayload( + payloads = orderPayloads, + ) + } + override fun transferPayloadForIsolatedMarginTrade(orderPayload: HumanReadablePlaceOrderPayload): HumanReadableSubaccountTransferPayload? { val trade = stateMachine.state?.input?.trade ?: return null val childSubaccountNumber = orderPayload.subaccountNumber @@ -268,6 +286,19 @@ internal class SubaccountTransactionPayloadProvider( return HumanReadableTriggerOrdersPayload(marketId, positionSize, placeOrderPayloads, cancelOrderPayloads) } + private fun subaccountNumberForPosition(marketId: String): Int { + return if (stateMachine.staticTyping) { + stateMachine.internalState.wallet.account.groupedSubaccounts[subaccountNumber]?.openPositions?.get(marketId)?.childSubaccountNumber ?: subaccountNumber + } else { + helper.parser.asInt( + helper.parser.value( + stateMachine.data, + "wallet.account.groupedSubaccounts.$subaccountNumber.openPositions.$marketId.childSubaccountNumber", + ), + ) ?: subaccountNumber + } + } + @Throws(Exception::class) override fun closePositionPayload(currentHeight: Int?): HumanReadablePlaceOrderPayload { val closePosition = stateMachine.state?.input?.closePosition @@ -288,17 +319,7 @@ internal class SubaccountTransactionPayloadProvider( val goodTilTimeInSeconds = if (isLimitClose) (limitCloseDuration / 1.seconds).toInt() else null val goodTilBlock = if (isLimitClose) null else currentHeight?.plus(SHORT_TERM_ORDER_DURATION) val marketInfo = marketInfo(marketId) - val subaccountNumberForPosition = - if (stateMachine.staticTyping) { - stateMachine.internalState.wallet.account.groupedSubaccounts[subaccountNumber]?.openPositions?.get(marketId)?.childSubaccountNumber ?: subaccountNumber - } else { - helper.parser.asInt( - helper.parser.value( - stateMachine.data, - "wallet.account.groupedSubaccounts.$subaccountNumber.openPositions.$marketId.childSubaccountNumber", - ), - ) ?: subaccountNumber - } + val subaccountNumberForPosition = subaccountNumberForPosition(marketId) return HumanReadablePlaceOrderPayload( subaccountNumber = subaccountNumberForPosition, @@ -321,6 +342,48 @@ internal class SubaccountTransactionPayloadProvider( ) } + private fun marketFullClosePositionPayload(position: SubaccountPosition, currentHeight: Int?): HumanReadablePlaceOrderPayload { + val marketId = position.id + val clientId = ClientId.generate() + val subaccountNumberForPosition = subaccountNumberForPosition(marketId) + val positionSize = position.size.current ?: throw Exception("size is null") + val side = if (positionSize > Numeric.double.ZERO) OrderSide.Sell.rawValue else OrderSide.Buy.rawValue + val oraclePrice = stateMachine.state?.market(marketId)?.oraclePrice ?: throw Exception("missing oraclePrice") + val price = if (side == OrderSide.Buy.rawValue) { + oraclePrice * (Numeric.double.ONE + MARKET_ORDER_MAX_SLIPPAGE) + } else { + oraclePrice * (Numeric.double.ONE - MARKET_ORDER_MAX_SLIPPAGE) + } + val size = positionSize.abs() + val type = "MARKET" + val reduceOnly = true + val postOnly = false + val timeInForce = "IOC" + val execution = "DEFAULT" + val goodTilBlock = currentHeight?.plus(SHORT_TERM_ORDER_DURATION) + val marketInfo = marketInfo(marketId) + + return HumanReadablePlaceOrderPayload( + subaccountNumber = subaccountNumberForPosition, + marketId = marketId, + clientId = clientId, + type = type, + side = side, + price = price, + triggerPrice = null, + size = size, + sizeInput = null, + reduceOnly = reduceOnly, + postOnly = postOnly, + timeInForce = timeInForce, + execution = execution, + goodTilTimeInSeconds = null, + goodTilBlock = goodTilBlock, + marketInfo = marketInfo, + currentHeight = currentHeight, + ) + } + @Throws(Exception::class) override fun adjustIsolatedMarginPayload(): HumanReadableSubaccountTransferPayload { val isolatedMarginAdjustment = stateMachine.state?.input?.adjustIsolatedMargin ?: error("AdjustIsolatedMarginInput is null") diff --git a/src/commonMain/kotlin/exchange.dydx.abacus/state/v2/supervisor/SubaccountTransactionSupervisor.kt b/src/commonMain/kotlin/exchange.dydx.abacus/state/v2/supervisor/SubaccountTransactionSupervisor.kt index 711da2531..7f412f212 100644 --- a/src/commonMain/kotlin/exchange.dydx.abacus/state/v2/supervisor/SubaccountTransactionSupervisor.kt +++ b/src/commonMain/kotlin/exchange.dydx.abacus/state/v2/supervisor/SubaccountTransactionSupervisor.kt @@ -16,6 +16,7 @@ import exchange.dydx.abacus.state.manager.CancelOrderRecord import exchange.dydx.abacus.state.manager.FaucetRecord import exchange.dydx.abacus.state.manager.HumanReadableCancelAllOrdersPayload import exchange.dydx.abacus.state.manager.HumanReadableCancelOrderPayload +import exchange.dydx.abacus.state.manager.HumanReadableCloseAllPositionsPayload import exchange.dydx.abacus.state.manager.HumanReadablePlaceOrderPayload import exchange.dydx.abacus.state.manager.HumanReadableSubaccountTransferPayload import exchange.dydx.abacus.state.manager.HumanReadableTriggerOrdersPayload @@ -181,6 +182,23 @@ internal class SubaccountTransactionSupervisor( return payload } + internal fun closeAllPositions(currentHeight: Int?, callback: TransactionCallback): HumanReadableCloseAllPositionsPayload { + val payload = payloadProvider.closeAllPositionsPayload(currentHeight) + payload.payloads.forEach { closePositionPayload -> + val marketId = closePositionPayload.marketId + val midMarketPrice = stateMachine.state?.marketOrderbook(marketId)?.midPrice + val analyticsPayload = analyticsUtils.placeOrderAnalyticsPayload( + payload = closePositionPayload, + midMarketPrice = midMarketPrice, + fromSlTpDialog = false, + isClosePosition = true, + ) + val uiClickTimeMs = transactionTracker.trackOrderClick(analyticsPayload, AnalyticsEvent.TradeCloseAllPositionsClick) + submitPlaceOrder(callback, closePositionPayload, analyticsPayload, uiClickTimeMs, false) + } + return payload + } + internal fun commitTriggerOrders( currentHeight: Int?, callback: TransactionCallback diff --git a/src/commonTest/kotlin/exchange.dydx.abacus/app/manager/v2/V4TransactionTests.kt b/src/commonTest/kotlin/exchange.dydx.abacus/app/manager/v2/V4TransactionTests.kt index cb250dc22..2e00cf28d 100644 --- a/src/commonTest/kotlin/exchange.dydx.abacus/app/manager/v2/V4TransactionTests.kt +++ b/src/commonTest/kotlin/exchange.dydx.abacus/app/manager/v2/V4TransactionTests.kt @@ -252,6 +252,21 @@ class V4TransactionTests : NetworkTests() { assertEquals(4, cancelPayloads.size) } + @Test + fun testCloseAllPositions() { + setStateMachineConnected(stateManager) + testWebSocket?.simulateReceived(mock.accountsChannel.v4_parent_subaccounts_subscribed_with_trigger_orders_and_open_positions) + + var orderPlacedCallCount = 0 + val callback: TransactionCallback = { _, _, _ -> orderPlacedCallCount++ } + val closePositionPayloads = testChain!!.placeOrderPayloads + + subaccountSupervisor?.closeAllPositions(0, callback) + assertTransactionQueueEmpty() + // there are 3 open positions + assertEquals(3, closePositionPayloads.size) + } + @Test fun testCancelTriggerOrdersWithClosedOrFlippedPositions() { setStateMachineConnected(stateManager) diff --git a/v4_abacus.podspec b/v4_abacus.podspec index 1e65fa773..6b628d8c5 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.11.10' + spec.version = '1.11.11' spec.homepage = 'https://github.com/dydxprotocol/v4-abacus' spec.source = { :http=> ''} spec.authors = ''