Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: market close all positions #660

Merged
merged 3 commits into from
Sep 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

don't we have another helper for this somewhere already? @jaredvu

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
Loading