Skip to content

Commit

Permalink
feat: market close all positions (#660)
Browse files Browse the repository at this point in the history
  • Loading branch information
aforaleka authored Sep 18, 2024
1 parent bb39782 commit 9399502
Show file tree
Hide file tree
Showing 13 changed files with 172 additions and 14 deletions.
2 changes: 1 addition & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ allprojects {
}

group = "exchange.dydx.abacus"
version = "1.11.10"
version = "1.11.11"

repositories {
google()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,7 @@ enum class AnalyticsEvent {
TradePlaceOrderClick,
TradeCancelOrderClick,
TradeCancelAllOrdersClick,
TradeCloseAllPositionsClick,
TradePlaceOrder,
TradeCancelOrder,
TradePlaceOrderSubmissionConfirmed,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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?
Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -87,7 +88,13 @@ data class HumanReadableCancelOrderPayload(
@Serializable
data class HumanReadableCancelAllOrdersPayload(
val marketId: String?,
val payloads: IList<HumanReadableCancelOrderPayload>,
val payloads: List<HumanReadableCancelOrderPayload>,
)

@JsExport
@Serializable
data class HumanReadableCloseAllPositionsPayload(
val payloads: List<HumanReadablePlaceOrderPayload>,
)

@JsExport
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -516,6 +517,10 @@ class AsyncAbacusStateManagerV2(
return adaptor?.cancelAllOrdersPayload(marketId)
}

override fun closeAllPositionsPayload(): HumanReadableCloseAllPositionsPayload? {
return adaptor?.closeAllPositionsPayload()
}

override fun triggerOrdersPayload(): HumanReadableTriggerOrdersPayload? {
return adaptor?.triggerOrdersPayload()
}
Expand Down Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)
}
Expand Down Expand Up @@ -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)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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()
}
Expand Down Expand Up @@ -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)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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()
}
Expand Down Expand Up @@ -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)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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?,
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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,
Expand All @@ -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")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
Loading

0 comments on commit 9399502

Please sign in to comment.