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

Automatically cancel trigger orders if position has been closed/flipped #452

Merged
merged 3 commits into from
Jun 17, 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 @@ -51,7 +51,7 @@ allprojects {
}

group = "exchange.dydx.abacus"
version = "1.7.78"
version = "1.7.79"

repositories {
google()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ interface AsyncAbacusStateManagerProtocol {

// Commit changes with params
fun faucet(amount: Double, callback: TransactionCallback)
fun cancelOrder(orderId: String, callback: TransactionCallback)
fun cancelOrder(orderId: String, callback: TransactionCallback, isOrphanedTriggerOrder: Boolean)

// 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 @@ -592,9 +592,9 @@ class AsyncAbacusStateManagerV2(
}
}

override fun cancelOrder(orderId: String, callback: TransactionCallback) {
override fun cancelOrder(orderId: String, callback: TransactionCallback, isOrphanedTriggerOrder: Boolean) {
try {
adaptor?.cancelOrder(orderId, callback)
adaptor?.cancelOrder(orderId, callback, isOrphanedTriggerOrder)
} catch (e: Exception) {
val error = V4TransactionErrors.error(null, e.toString())
callback(false, error, null)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -581,8 +581,8 @@ internal class StateManagerAdaptorV2(
accounts.stopWatchingLastOrder()
}

internal fun cancelOrder(orderId: String, callback: TransactionCallback) {
accounts.cancelOrder(orderId, callback)
internal fun cancelOrder(orderId: String, callback: TransactionCallback, isOrphanedTriggerOrder: Boolean) {
accounts.cancelOrder(orderId, callback, isOrphanedTriggerOrder)
}

internal fun orderCanceled(orderId: String) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1121,8 +1121,8 @@ internal fun AccountSupervisor.faucet(amount: Double, callback: TransactionCallb
subaccount?.faucet(amount, callback)
}

internal fun AccountSupervisor.cancelOrder(orderId: String, callback: TransactionCallback) {
subaccount?.cancelOrder(orderId, callback)
internal fun AccountSupervisor.cancelOrder(orderId: String, callback: TransactionCallback, isOrphanedTriggerOrder: Boolean) {
subaccount?.cancelOrder(orderId, callback, isOrphanedTriggerOrder)
}

internal fun AccountSupervisor.orderCanceled(orderId: String) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -333,8 +333,8 @@ internal fun AccountsSupervisor.faucet(amount: Double, callback: TransactionCall
account?.faucet(amount, callback)
}

internal fun AccountsSupervisor.cancelOrder(orderId: String, callback: TransactionCallback) {
account?.cancelOrder(orderId, callback)
internal fun AccountsSupervisor.cancelOrder(orderId: String, callback: TransactionCallback, isOrphanedTriggerOrder: Boolean) {
account?.cancelOrder(orderId, callback, isOrphanedTriggerOrder)
}

internal fun AccountsSupervisor.orderCanceled(orderId: String) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@ package exchange.dydx.abacus.state.v2.supervisor
import abs
import exchange.dydx.abacus.calculator.TriggerOrdersConstants.TRIGGER_ORDER_DEFAULT_DURATION_DAYS
import exchange.dydx.abacus.output.Notification
import exchange.dydx.abacus.output.PositionSide
import exchange.dydx.abacus.output.SubaccountOrder
import exchange.dydx.abacus.output.TransferRecordType
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
Expand Down Expand Up @@ -395,6 +397,38 @@ internal class SubaccountSupervisor(
}
}

private var cancelingOrphanedTriggerOrders = mutableSetOf<String>()

private fun cancelTriggerOrder(orderId: String) {
cancelingOrphanedTriggerOrders.add(orderId)
cancelOrder(orderId, { _, _, _ -> cancelingOrphanedTriggerOrders.remove(orderId) }, true)
}

private fun cancelTriggerOrdersWithClosedOrFlippedPositions() {
val subaccount = stateMachine.state?.subaccount(subaccountNumber) ?: return
val cancelableTriggerOrders = subaccount.orders?.filter { order ->
val isConditionalOrder = order.orderFlags == 32
val isReduceOnly = order.reduceOnly
val isActiveOrder =
(order.status === OrderStatus.untriggered || order.status === OrderStatus.open)
isConditionalOrder && isReduceOnly && isActiveOrder
} ?: return

cancelableTriggerOrders.forEach { order ->
if (order.id !in cancelingOrphanedTriggerOrders) {
val marketPosition = subaccount.openPositions?.find { position -> position.id === order.marketId }
val hasPositionFlippedOrClosed = marketPosition?.let { position ->
when (position.side.current) {
PositionSide.LONG -> order.side == OrderSide.buy
PositionSide.SHORT -> order.side == OrderSide.sell
else -> true
}
} ?: true
if (hasPositionFlippedOrClosed) cancelTriggerOrder(order.id)
}
}
}

private fun fromSlTpDialogParams(fromSlTpDialog: Boolean): IMap<String, Any> {
return iMapOf(
"fromSlTpDialog" to fromSlTpDialog,
Expand Down Expand Up @@ -778,7 +812,7 @@ internal class SubaccountSupervisor(
payload: HumanReadableCancelOrderPayload,
analyticsPayload: IMap<String, Any>?,
uiClickTimeMs: Double,
isTriggerOrder: Boolean = false,
fromSlTpDialog: Boolean = false,
): HumanReadableCancelOrderPayload {
val clientId = payload.clientId
val string = Json.encodeToString(payload)
Expand All @@ -801,7 +835,7 @@ internal class SubaccountSupervisor(
subaccountNumber,
clientId,
submitTimeMs,
fromSlTpDialog = isTriggerOrder,
fromSlTpDialog,
),
)
}
Expand All @@ -820,7 +854,7 @@ internal class SubaccountSupervisor(
helper.send(
error,
callback,
if (isTriggerOrder) {
if (fromSlTpDialog) {
HumanReadableTriggerOrdersPayload(
marketId,
positionSize,
Expand Down Expand Up @@ -863,15 +897,15 @@ internal class SubaccountSupervisor(
return submitPlaceOrder(callback, payload, analyticsPayload, uiClickTimeMs)
}

internal fun cancelOrder(orderId: String, callback: TransactionCallback): HumanReadableCancelOrderPayload {
internal fun cancelOrder(orderId: String, callback: TransactionCallback, isOrphanedTriggerOrder: Boolean = false): HumanReadableCancelOrderPayload {
val payload = cancelOrderPayload(orderId)
val subaccount = stateMachine.state?.subaccount(subaccountNumber)
val existingOrder = subaccount?.orders?.firstOrNull { it.id == orderId } ?: throw ParsingException(
ParsingErrorType.MissingRequiredData,
"no existing order to be cancelled for $orderId",
)
val marketId = existingOrder.marketId
val analyticsPayload = analyticsUtils.cancelOrderAnalyticsPayload(payload, existingOrder, fromSlTpDialog = false)
val analyticsPayload = analyticsUtils.cancelOrderAnalyticsPayload(payload, existingOrder, fromSlTpDialog = false, isOrphanedTriggerOrder)
val uiClickTimeMs = trackOrderClick(analyticsPayload, AnalyticsEvent.TradeCancelOrderClick)

return submitCancelOrder(orderId, marketId, callback, payload, analyticsPayload, uiClickTimeMs)
Expand Down Expand Up @@ -1424,6 +1458,7 @@ internal class SubaccountSupervisor(
}
if (changes.changes.contains(Changes.subaccount)) {
parseOrdersToMatchPlaceOrdersAndCancelOrders()
cancelTriggerOrdersWithClosedOrFlippedPositions()
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -179,9 +179,10 @@ class AnalyticsUtils {
payload: HumanReadableCancelOrderPayload,
existingOrder: SubaccountOrder?,
fromSlTpDialog: Boolean? = false,
isOrphanedTriggerOrder: Boolean = false,
): IMap<String, Any>? {
return ParsingHelper.merge(
formatCancelOrderPayload(payload, fromSlTpDialog),
formatCancelOrderPayload(payload, fromSlTpDialog, isOrphanedTriggerOrder),
if (existingOrder != null) formatOrder(existingOrder) else mapOf(),
)?.toIMap()
}
Expand All @@ -194,9 +195,11 @@ class AnalyticsUtils {
private fun formatCancelOrderPayload(
payload: HumanReadableCancelOrderPayload,
fromSlTpDialog: Boolean? = false,
isOrphanedTriggerOrder: Boolean = false,
): IMap<String, Any>? {
return iMapOf(
"fromSlTpDialog" to fromSlTpDialog,
"isAutomaticallyCanceledByFrontend" to isOrphanedTriggerOrder,
"subaccountNumber" to payload.subaccountNumber,
"clientId" to payload.clientId,
"orderId" to payload.orderId,
Expand Down
Loading