Skip to content

Commit

Permalink
Remove equity tier validation (dydxprotocol#397)
Browse files Browse the repository at this point in the history
Co-authored-by: mobile-build-bot-git <[email protected]>
  • Loading branch information
2 people authored and yogurtandjam committed May 31, 2024
1 parent d43db09 commit f2d688a
Show file tree
Hide file tree
Showing 4 changed files with 15 additions and 365 deletions.
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.35"
version = "1.7.36"

repositories {
google()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
package exchange.dydx.abacus.validator
import abs
import exchange.dydx.abacus.output.input.OrderSide
import exchange.dydx.abacus.output.input.OrderStatus
import exchange.dydx.abacus.output.input.OrderTimeInForce
import exchange.dydx.abacus.output.input.OrderType
import exchange.dydx.abacus.protocols.LocalizerProtocol
import exchange.dydx.abacus.protocols.ParserProtocol
Expand All @@ -11,8 +9,6 @@ import exchange.dydx.abacus.state.manager.BlockAndTime
import exchange.dydx.abacus.state.manager.V4Environment
import exchange.dydx.abacus.state.model.TriggerOrdersInputField
import exchange.dydx.abacus.utils.Rounder
import exchange.dydx.abacus.validator.trade.EquityTier
import exchange.dydx.abacus.validator.trade.SubaccountLimitConstants.MAX_NUM_OPEN_UNTRIGGERED_ORDERS

enum class RelativeToPrice(val rawValue: String) {
ABOVE("ABOVE"),
Expand Down Expand Up @@ -47,7 +43,8 @@ internal class TriggerOrdersInputValidator(

val marketId = parser.asString(transaction["marketId"]) ?: return null
val market = parser.asNativeMap(markets?.get(marketId))
val position = parser.asNativeMap(parser.value(subaccount, "openPositions.$marketId")) ?: return null
val position = parser.asNativeMap(parser.value(subaccount, "openPositions.$marketId"))
?: return null
val tickSize = parser.asString(parser.value(market, "configs.tickSize")) ?: "0.01"
val oraclePrice = parser.asDouble(
parser.value(
Expand All @@ -56,7 +53,7 @@ internal class TriggerOrdersInputValidator(
),
) ?: return null

validateTriggerOrders(transaction, market, subaccount, configs, environment)?.let {
validateTriggerOrders(transaction, market)?.let {
errors.addAll(it)
}

Expand Down Expand Up @@ -103,17 +100,8 @@ internal class TriggerOrdersInputValidator(
private fun validateTriggerOrders(
triggerOrders: Map<String, Any>,
market: Map<String, Any>?,
subaccount: Map<String, Any>?,
configs: Map<String, Any>?,
environment: V4Environment?,
): MutableList<Any>? {
val triggerErrors = mutableListOf<Any>()
validateOrderCount(triggerOrders, subaccount, configs, environment)?.let {
/*
USER_MAX_ORDERS
*/
triggerErrors.addAll(it)
}
validateSize(parser.asDouble(triggerOrders["size"]), market)?.let {
/*
AMOUNT_INPUT_STEP_SIZE
Expand Down Expand Up @@ -206,6 +194,7 @@ internal class TriggerOrdersInputValidator(
null
}
}

RelativeToPrice.BELOW -> {
if (triggerPrice >= liquidationPrice) {
liquidationPriceError(
Expand All @@ -219,6 +208,7 @@ internal class TriggerOrdersInputValidator(
null
}
}

else -> null
}
}
Expand Down Expand Up @@ -249,137 +239,6 @@ internal class TriggerOrdersInputValidator(
)
}

private fun validateOrderCount(
triggerOrders: Map<String, Any>,
subaccount: Map<String, Any>?,
configs: Map<String, Any>?,
environment: V4Environment?,
): List<Any>? {
val equityTier = equityTier(subaccount, configs)

val fallbackMaxNumOrders = MAX_NUM_OPEN_UNTRIGGERED_ORDERS

val equityTierLimit = equityTier?.maxOrders ?: fallbackMaxNumOrders
val nextLevelRequiredTotalNetCollateralUSD =
equityTier?.nextLevelRequiredTotalNetCollateralUSD
val numOrders = orderCount(subaccount)
var numOrdersToCreate = 0
var numOrdersToCancel = 0

if (parser.value(triggerOrders, "stopLossOrder.price.triggerPrice") != null && parser.value(triggerOrders, "stopLossOrder.orderId") == null) {
numOrdersToCreate += 1
} else if (parser.value(triggerOrders, "stopLossOrder.price.triggerPrice") == null && parser.value(triggerOrders, "stopLossOrder.orderId") != null) {
numOrdersToCancel += 1
}
if (parser.value(triggerOrders, "takeProfitOrder.price.triggerPrice") != null && parser.value(triggerOrders, "takeProfitOrder.orderId") == null) {
numOrdersToCreate += 1
} else if (parser.value(triggerOrders, "takeProfitOrder.price.triggerPrice") == null && parser.value(triggerOrders, "takeProfitOrder.orderId") != null) {
numOrdersToCancel += 1
}

val documentation = environment?.links?.documentation
val link = if (documentation != null) "$documentation/trading/other_limits" else null

return if ((numOrders + numOrdersToCreate - numOrdersToCancel) > equityTierLimit) {
listOf(
if (nextLevelRequiredTotalNetCollateralUSD != null) {
error(
"ERROR",
"USER_MAX_ORDERS",
null, // No input field since the error is not related to a specific field
null,
"ERRORS.TRADE_BOX_TITLE.USER_MAX_ORDERS",
"ERRORS.TRADE_BOX.USER_MAX_ORDERS_FOR_CURRENT_EQUITY_TIER",
mapOf(
"EQUITY" to mapOf(
"value" to nextLevelRequiredTotalNetCollateralUSD,
"format" to "price",
),
"LIMIT" to mapOf(
"value" to equityTierLimit,
"format" to "string",
),
),
null,
link,
)
} else {
error(
"ERROR",
"USER_MAX_ORDERS",
null, // No input field since the error is not related to a specific field
null,
"ERRORS.TRADE_BOX_TITLE.USER_MAX_ORDERS",
"ERRORS.TRADE_BOX.USER_MAX_ORDERS_FOR_TOP_EQUITY_TIER",
mapOf(
"LIMIT" to mapOf(
"value" to equityTierLimit,
"format" to "string",
),
),
null,
link,
)
},
)
} else {
null
}
}

private fun equityTier(
subaccount: Map<String, Any>?,
configs: Map<String, Any>?
): EquityTier? {
var equityTier: EquityTier? = null
val equity: Double = parser.asDouble(parser.value(subaccount, "equity.current")) ?: 0.0
parser.asNativeMap(parser.value(configs, "equityTiers"))?.let { equityTiers ->
parser.asNativeList(equityTiers["statefulOrderEquityTiers"])?.let { tiers ->
if (tiers.isEmpty()) return null
for (tier in tiers) {
parser.asNativeMap(tier)?.let { item ->
val requiredTotalNetCollateralUSD =
parser.asDouble(item["requiredTotalNetCollateralUSD"]) ?: 0.0
if (requiredTotalNetCollateralUSD <= equity) {
val maxNumOrders = parser.asInt(item["maxOrders"]) ?: 0
equityTier = EquityTier(
requiredTotalNetCollateralUSD,
maxNumOrders,
)
} else if (equityTier?.nextLevelRequiredTotalNetCollateralUSD == null) {
equityTier?.nextLevelRequiredTotalNetCollateralUSD =
requiredTotalNetCollateralUSD
}
}
}
}
} ?: run {
return null
}
return equityTier
}

private fun orderCount(
subaccount: Map<String, Any>?,
): Int {
var count = 0
parser.asNativeMap(subaccount?.get("orders"))?.let { orders ->
for ((_, item) in orders) {
parser.asNativeMap(item)?.let { order ->
val status = parser.asString(order["status"])?.let { OrderStatus.invoke(it) }
val orderType = parser.asString(order["type"])?.let { OrderType.invoke(it) }
val timeInForce = parser.asString(order["timeInForce"])?.let { OrderTimeInForce.invoke(it) }
if (orderType != null && timeInForce != null && status != null) {
if (isOrderIncludedInEquityTierLimit(orderType, timeInForce, status)) {
count += 1
}
}
}
}
}
return count
}

private fun validateRequiredInput(
triggerOrder: Map<String, Any>,
): List<Any>? {
Expand All @@ -390,7 +249,11 @@ internal class TriggerOrdersInputValidator(

if (triggerPrice == null && limitPrice != null) {
errors.add(
required("REQUIRED_TRIGGER_PRICE", "price.triggerPrice", "APP.TRADE.ENTER_TRIGGER_PRICE"),
required(
"REQUIRED_TRIGGER_PRICE",
"price.triggerPrice",
"APP.TRADE.ENTER_TRIGGER_PRICE",
),
)
}

Expand Down Expand Up @@ -553,6 +416,7 @@ internal class TriggerOrdersInputValidator(
}
}
}

else -> null
}
}
Expand Down Expand Up @@ -585,8 +449,7 @@ internal class TriggerOrdersInputValidator(
parser.asNativeMap(market?.get("configs"))?.let { configs ->
val errors = mutableListOf<Map<String, Any>>()

parser.asDouble(configs["stepSize"])?.let {
stepSize ->
parser.asDouble(configs["stepSize"])?.let { stepSize ->
if (Rounder.round(size, stepSize) != size) {
errors.add(
error(
Expand Down Expand Up @@ -695,39 +558,6 @@ internal class TriggerOrdersInputValidator(
}
}

private fun isOrderIncludedInEquityTierLimit(
orderType: OrderType,
timeInForce: OrderTimeInForce,
status: OrderStatus
): Boolean {
val isCurrentOrderStateful = isStatefulOrder(orderType, timeInForce)
// Short term with IOC or FOK should not be counted
val isShortTermAndRequiresImmediateExecution =
!isCurrentOrderStateful && (timeInForce == OrderTimeInForce.IOC || timeInForce == OrderTimeInForce.FOK)

return if (!isShortTermAndRequiresImmediateExecution) {
when (status) {
OrderStatus.open, OrderStatus.pending, OrderStatus.untriggered, OrderStatus.partiallyFilled -> true
else -> false
}
} else {
false
}
}

private fun isStatefulOrder(orderType: OrderType, timeInForce: OrderTimeInForce): Boolean {
return when (orderType) {
OrderType.market -> false
OrderType.limit -> {
when (timeInForce) {
OrderTimeInForce.GTT -> true
else -> false
}
}
else -> true
}
}

private fun requiredTriggerToLiquidationPrice(type: OrderType?, side: OrderSide): RelativeToPrice? {
return when (type) {
OrderType.stopMarket ->
Expand Down
Loading

0 comments on commit f2d688a

Please sign in to comment.