From c7ec887939f5850bc2f5511fa4eb2dd9fa6dc78c Mon Sep 17 00:00:00 2001 From: aleka Date: Wed, 29 May 2024 13:51:22 -0400 Subject: [PATCH] Remove equity tier validation (#397) Co-authored-by: mobile-build-bot-git --- build.gradle.kts | 2 +- .../validator/TriggerOrdersInputValidator.kt | 194 ++---------------- .../trade/TradeInputDataValidator.kt | 182 +--------------- v4_abacus.podspec | 2 +- 4 files changed, 15 insertions(+), 365 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 2ce50ac20..b9c1df508 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -51,7 +51,7 @@ allprojects { } group = "exchange.dydx.abacus" -version = "1.7.35" +version = "1.7.36" repositories { google() diff --git a/src/commonMain/kotlin/exchange.dydx.abacus/validator/TriggerOrdersInputValidator.kt b/src/commonMain/kotlin/exchange.dydx.abacus/validator/TriggerOrdersInputValidator.kt index 32f7e2b17..9c5c65adf 100644 --- a/src/commonMain/kotlin/exchange.dydx.abacus/validator/TriggerOrdersInputValidator.kt +++ b/src/commonMain/kotlin/exchange.dydx.abacus/validator/TriggerOrdersInputValidator.kt @@ -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 @@ -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"), @@ -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( @@ -56,7 +53,7 @@ internal class TriggerOrdersInputValidator( ), ) ?: return null - validateTriggerOrders(transaction, market, subaccount, configs, environment)?.let { + validateTriggerOrders(transaction, market)?.let { errors.addAll(it) } @@ -103,17 +100,8 @@ internal class TriggerOrdersInputValidator( private fun validateTriggerOrders( triggerOrders: Map, market: Map?, - subaccount: Map?, - configs: Map?, - environment: V4Environment?, ): MutableList? { val triggerErrors = mutableListOf() - validateOrderCount(triggerOrders, subaccount, configs, environment)?.let { - /* - USER_MAX_ORDERS - */ - triggerErrors.addAll(it) - } validateSize(parser.asDouble(triggerOrders["size"]), market)?.let { /* AMOUNT_INPUT_STEP_SIZE @@ -206,6 +194,7 @@ internal class TriggerOrdersInputValidator( null } } + RelativeToPrice.BELOW -> { if (triggerPrice >= liquidationPrice) { liquidationPriceError( @@ -219,6 +208,7 @@ internal class TriggerOrdersInputValidator( null } } + else -> null } } @@ -249,137 +239,6 @@ internal class TriggerOrdersInputValidator( ) } - private fun validateOrderCount( - triggerOrders: Map, - subaccount: Map?, - configs: Map?, - environment: V4Environment?, - ): List? { - 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?, - configs: Map? - ): 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?, - ): 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, ): List? { @@ -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", + ), ) } @@ -553,6 +416,7 @@ internal class TriggerOrdersInputValidator( } } } + else -> null } } @@ -585,8 +449,7 @@ internal class TriggerOrdersInputValidator( parser.asNativeMap(market?.get("configs"))?.let { configs -> val errors = mutableListOf>() - parser.asDouble(configs["stepSize"])?.let { - stepSize -> + parser.asDouble(configs["stepSize"])?.let { stepSize -> if (Rounder.round(size, stepSize) != size) { errors.add( error( @@ -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 -> diff --git a/src/commonMain/kotlin/exchange.dydx.abacus/validator/trade/TradeInputDataValidator.kt b/src/commonMain/kotlin/exchange.dydx.abacus/validator/trade/TradeInputDataValidator.kt index 69ebfc2d7..5c3e234d1 100644 --- a/src/commonMain/kotlin/exchange.dydx.abacus/validator/trade/TradeInputDataValidator.kt +++ b/src/commonMain/kotlin/exchange.dydx.abacus/validator/trade/TradeInputDataValidator.kt @@ -10,7 +10,6 @@ import exchange.dydx.abacus.utils.Rounder import exchange.dydx.abacus.validator.BaseInputValidator import exchange.dydx.abacus.validator.PositionChange import exchange.dydx.abacus.validator.TradeValidatorProtocol -import exchange.dydx.abacus.validator.trade.SubaccountLimitConstants.MAX_NUM_OPEN_UNTRIGGERED_ORDERS import kotlin.time.Duration.Companion.days /* @@ -29,17 +28,6 @@ LIMIT_PRICE_TRIGGER_PRICE_SLIPPAGE_LOWER */ -internal data class EquityTier( - val requiredTotalNetCollateralUSD: Double, - val maxOrders: Int, -) { - var nextLevelRequiredTotalNetCollateralUSD: Double? = null -} - -internal object SubaccountLimitConstants { - const val MAX_NUM_OPEN_UNTRIGGERED_ORDERS = 20 -} - internal class TradeInputDataValidator( localizer: LocalizerProtocol?, formatter: Formatter?, @@ -56,23 +44,14 @@ internal class TradeInputDataValidator( restricted: Boolean, environment: V4Environment?, ): List? { - return validateTradeInput(subaccount, market, configs, trade, environment) + return validateTradeInput(market, trade) } private fun validateTradeInput( - subaccount: Map?, market: Map?, - configs: Map?, trade: Map, - environment: V4Environment?, ): List? { val errors = mutableListOf() - validateOrder(trade, subaccount, configs, environment)?.let { - /* - USER_MAX_ORDERS - */ - errors.addAll(it) - } validateSize(trade, market)?.let { /* AMOUNT_INPUT_STEP_SIZE @@ -103,150 +82,6 @@ internal class TradeInputDataValidator( return if (errors.size > 0) errors else null } - private fun validateOrder( - trade: Map, - subaccount: Map?, - configs: Map?, - environment: V4Environment?, - ): List? { - /* - USER_MAX_ORDERS - */ - val fallbackMaxNumOrders = MAX_NUM_OPEN_UNTRIGGERED_ORDERS - val orderType = parser.asString(trade["type"]) - val timeInForce = parser.asString(trade["timeInForce"]) - - if (orderType == null || timeInForce == null) return null - - val equityTier = - equityTier(isStatefulOrder(orderType, timeInForce), subaccount, configs) - val equityTierLimit = equityTier?.maxOrders ?: fallbackMaxNumOrders - val nextLevelRequiredTotalNetCollateralUSD = - equityTier?.nextLevelRequiredTotalNetCollateralUSD - val numOrders = orderCount(isStatefulOrder(orderType, timeInForce), subaccount) - - // Equity tier limit is not applicable for `MARKET` orders and `LIMIT` orders with FOK or IOC time in force - val isEquityTierLimitApplicable = orderType != "MARKET" && - !(orderType == "LIMIT" && (timeInForce == "FOK" || timeInForce == "IOC")) - - val documentation = environment?.links?.documentation - val link = if (documentation != null) "$documentation/trading/other_limits" else null - return if (numOrders >= equityTierLimit && isEquityTierLimitApplicable) { - listOf( - if (nextLevelRequiredTotalNetCollateralUSD != null) { - error( - "ERROR", - "USER_MAX_ORDERS", - null, - 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, - 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( - isStatefulOrder: Boolean, - subaccount: Map?, - configs: Map? - ): EquityTier? { - /* - USER_MAX_ORDERS according to Equity Tier - */ - var equityTier: EquityTier? = null - val equity: Double = parser.asDouble(parser.value(subaccount, "equity.current")) ?: 0.0 - val equityTierKey: String = - if (isStatefulOrder) "statefulOrderEquityTiers" else "shortTermOrderEquityTiers" - parser.asNativeMap(parser.value(configs, "equityTiers"))?.let { equityTiers -> - parser.asNativeList(equityTiers[equityTierKey])?.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( - shouldCountStatefulOrders: Boolean, - subaccount: Map?, - ): 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"]) - val orderType = parser.asString(order["type"]) - val timeInForce = parser.asString(order["timeInForce"]) - if (orderType != null && timeInForce != null) { - val isCurrentOrderStateful = isStatefulOrder(orderType, timeInForce) - // Short term with IOC or FOK should not be counted - val isShortTermAndRequiresImmediateExecution = - !isCurrentOrderStateful && (timeInForce == "IOC" || timeInForce == "FOK") - if (!isShortTermAndRequiresImmediateExecution && - (status == "OPEN" || status == "PENDING" || status == "UNTRIGGERED" || status == "PARTIALLY_FILLED") && - (isCurrentOrderStateful == shouldCountStatefulOrders) - ) { - count += 1 - } - } - } - } - } - - return count - } - private fun validateSize( trade: Map, market: Map?, @@ -402,19 +237,4 @@ internal class TradeInputDataValidator( null } } - - private fun isStatefulOrder(orderType: String, timeInForce: String): Boolean { - return when (orderType) { - "MARKET" -> false - - "LIMIT" -> { - when (parser.asString(timeInForce)) { - "GTT" -> true - else -> false - } - } - - else -> true - } - } } diff --git a/v4_abacus.podspec b/v4_abacus.podspec index 2e668c1f2..cfdd72ba9 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.7.34' + spec.version = '1.7.35' spec.homepage = 'https://github.com/dydxprotocol/v4-abacus' spec.source = { :http=> ''} spec.authors = ''