From 7488a349b02f7d7f5bcfa81de272186e009412ad Mon Sep 17 00:00:00 2001 From: mulan xia Date: Wed, 24 Apr 2024 11:46:36 -0400 Subject: [PATCH 01/10] wip --- .../validator/TriggerOrdersInputValidator.kt | 134 ++++++++++++++++++ 1 file changed, 134 insertions(+) diff --git a/src/commonMain/kotlin/exchange.dydx.abacus/validator/TriggerOrdersInputValidator.kt b/src/commonMain/kotlin/exchange.dydx.abacus/validator/TriggerOrdersInputValidator.kt index f1b9efa48..531217a79 100644 --- a/src/commonMain/kotlin/exchange.dydx.abacus/validator/TriggerOrdersInputValidator.kt +++ b/src/commonMain/kotlin/exchange.dydx.abacus/validator/TriggerOrdersInputValidator.kt @@ -7,6 +7,7 @@ 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.utils.Logger internal data class EquityTier( val requiredTotalNetCollateralUSD: Double, @@ -51,6 +52,7 @@ 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 tickSize = parser.asString(parser.value(market, "configs.tickSize")) ?: "0.01" val oraclePrice = parser.asDouble( parser.value( @@ -72,6 +74,7 @@ internal class TriggerOrdersInputValidator( market, oraclePrice, tickSize, + validateTakeProfitTriggerToLiquidationPrice(takeProfitOrder, position, tickSize) ) } else { null @@ -87,6 +90,7 @@ internal class TriggerOrdersInputValidator( market, oraclePrice, tickSize, + validateStopLossTriggerToLiquidationPrice(stopLossOrder, position, tickSize) ) } else { null @@ -125,14 +129,135 @@ internal class TriggerOrdersInputValidator( return if (triggerErrors.size > 0) triggerErrors else null } + private fun validateStopLossTriggerToLiquidationPrice( + triggerOrder: Map, + position: Map, + tickSize: String, + ): List? { + val liquidationPrice = parser.asDouble(parser.value(position, "size.liquidationPrice")) ?: return null + val triggerPrice = + parser.asDouble(parser.value(triggerOrder, "price.triggerPrice")) ?: return null + + val side = parser.value(triggerOrder, "side") + + Logger.e { "xcxc stop loss, ${liquidationPrice}, ${triggerPrice}, ${side}"} + return when (parser.value(triggerOrder, "side")) { + "SELL" -> { + if (triggerPrice > liquidationPrice) { + listOf(error( + "ERROR", + "BRACKET_ORDER_STOP_LOSS_BELOW_LIQUIDATION_PRICE", + listOf(TriggerOrdersInputField.stopLossPrice.rawValue), + "APP.TRADE.MODIFY_TRIGGER_PRICE", + "ERRORS.TRADE_BOX_TITLE.BRACKET_ORDER_STOP_LOSS_BELOW_LIQUIDATION_PRICE", + "ERRORS.TRADE_BOX.BRACKET_ORDER_STOP_LOSS_BELOW_LIQUIDATION_PRICE", + mapOf( + "TRIGGER_PRICE_LIMIT" to mapOf( + "value" to liquidationPrice, + "format" to "price", + "tickSize" to tickSize, + ), + ), + )) + } else { + null + } + } + "BUY" -> { + if (triggerPrice < liquidationPrice) { + listOf(error( + "ERROR", + "BRACKET_ORDER_STOP_LOSS_ABOVE_LIQUIDATION_PRICE", + listOf(TriggerOrdersInputField.stopLossPrice.rawValue), + "APP.TRADE.MODIFY_TRIGGER_PRICE", + "ERRORS.TRADE_BOX_TITLE.BRACKET_ORDER_STOP_LOSS_ABOVE_LIQUIDATION_PRICE", + "ERRORS.TRADE_BOX.BRACKET_ORDER_STOP_LOSS_ABOVE_LIQUIDATION_PRICE", + mapOf( + "TRIGGER_PRICE_LIMIT" to mapOf( + "value" to liquidationPrice, + "format" to "price", + "tickSize" to tickSize, + ), + ), + )) + } else { + null + } + } + else -> null + } + } + + private fun validateTakeProfitTriggerToLiquidationPrice( + triggerOrder: Map, + position: Map, + tickSize: String, + ): List? { + + val liquidationPrice = parser.asDouble(parser.value(position, "size.liquidationPrice")) ?: return null + val triggerPrice = + parser.asDouble(parser.value(triggerOrder, "price.triggerPrice")) ?: return null + + val side = parser.value(triggerOrder, "side") + Logger.e { "xcxc take profit, ${liquidationPrice}, ${triggerPrice}, ${side}"} + return when (parser.value(triggerOrder, "side")) { + "SELL" -> { + if (triggerPrice < liquidationPrice) { + listOf(error( + "ERROR", + "BRACKET_ORDER_TAKE_PROFIT_ABOVE_LIQUIDATION_PRICE", + listOf(TriggerOrdersInputField.stopLossPrice.rawValue), + "APP.TRADE.MODIFY_TRIGGER_PRICE", + "ERRORS.TRADE_BOX_TITLE.BRACKET_ORDER_TAKE_PROFIT_ABOVE_LIQUIDATION_PRICE", + "ERRORS.TRADE_BOX.BRACKET_ORDER_TAKE_PROFIT_ABOVE_LIQUIDATION_PRICE", + mapOf( + "TRIGGER_PRICE_LIMIT" to mapOf( + "value" to liquidationPrice, + "format" to "price", + "tickSize" to tickSize, + ), + ), + )) + } else { + null + } + } + "BUY" -> { + if (triggerPrice > liquidationPrice) { + listOf(error( + "ERROR", + "BRACKET_ORDER_TAKE_PROFIT_BELOW_LIQUIDATION_PRICE", + listOf(TriggerOrdersInputField.stopLossPrice.rawValue), + "APP.TRADE.MODIFY_TRIGGER_PRICE", + "ERRORS.TRADE_BOX_TITLE.BRACKET_ORDER_TAKE_PROFIT_BELOW_LIQUIDATION_PRICE", + "ERRORS.TRADE_BOX.BRACKET_ORDER_TAKE_PROFIT_BELOW_LIQUIDATION_PRICE", + mapOf( + "TRIGGER_PRICE_LIMIT" to mapOf( + "value" to liquidationPrice, + "format" to "price", + "tickSize" to tickSize, + ), + ), + )) + } else { + null + } + } + else -> null + } + } + private fun validateTriggerOrder( triggerOrder: Map, market: Map?, oraclePrice: Double, tickSize: String, + liquidationPriceErrors: List?, ): MutableList? { val triggerErrors = mutableListOf() + Logger.e { "xcxc validating trigger order "} + validateRequiredInput(triggerOrder)?.let { /* REQUIRED_TRIGGER_PRICE @@ -166,6 +291,15 @@ internal class TriggerOrdersInputValidator( */ triggerErrors.addAll(it) } + + liquidationPriceErrors?.let { + /* + xcxc + */ + Logger.e { "xcxc, ${it} "} + triggerErrors.addAll(it) + } + return if (triggerErrors.size > 0) triggerErrors else null } From 88ab02e2730da36b9aa8233237745bc4d6b5b920 Mon Sep 17 00:00:00 2001 From: mulan xia Date: Thu, 25 Apr 2024 12:04:50 -0400 Subject: [PATCH 02/10] wip --- .../validator/TriggerOrdersInputValidator.kt | 211 +++++++----------- .../TriggerOrdersInputValidationTests.kt | 64 ++++++ 2 files changed, 148 insertions(+), 127 deletions(-) diff --git a/src/commonMain/kotlin/exchange.dydx.abacus/validator/TriggerOrdersInputValidator.kt b/src/commonMain/kotlin/exchange.dydx.abacus/validator/TriggerOrdersInputValidator.kt index 531217a79..bcb32ece1 100644 --- a/src/commonMain/kotlin/exchange.dydx.abacus/validator/TriggerOrdersInputValidator.kt +++ b/src/commonMain/kotlin/exchange.dydx.abacus/validator/TriggerOrdersInputValidator.kt @@ -7,7 +7,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.utils.Logger internal data class EquityTier( val requiredTotalNetCollateralUSD: Double, @@ -74,7 +73,6 @@ internal class TriggerOrdersInputValidator( market, oraclePrice, tickSize, - validateTakeProfitTriggerToLiquidationPrice(takeProfitOrder, position, tickSize) ) } else { null @@ -90,7 +88,7 @@ internal class TriggerOrdersInputValidator( market, oraclePrice, tickSize, - validateStopLossTriggerToLiquidationPrice(stopLossOrder, position, tickSize) + validateStopLossTriggerToLiquidationPrice(stopLossOrder, position, tickSize), ) } else { null @@ -129,135 +127,15 @@ internal class TriggerOrdersInputValidator( return if (triggerErrors.size > 0) triggerErrors else null } - private fun validateStopLossTriggerToLiquidationPrice( - triggerOrder: Map, - position: Map, - tickSize: String, - ): List? { - val liquidationPrice = parser.asDouble(parser.value(position, "size.liquidationPrice")) ?: return null - val triggerPrice = - parser.asDouble(parser.value(triggerOrder, "price.triggerPrice")) ?: return null - - val side = parser.value(triggerOrder, "side") - - Logger.e { "xcxc stop loss, ${liquidationPrice}, ${triggerPrice}, ${side}"} - return when (parser.value(triggerOrder, "side")) { - "SELL" -> { - if (triggerPrice > liquidationPrice) { - listOf(error( - "ERROR", - "BRACKET_ORDER_STOP_LOSS_BELOW_LIQUIDATION_PRICE", - listOf(TriggerOrdersInputField.stopLossPrice.rawValue), - "APP.TRADE.MODIFY_TRIGGER_PRICE", - "ERRORS.TRADE_BOX_TITLE.BRACKET_ORDER_STOP_LOSS_BELOW_LIQUIDATION_PRICE", - "ERRORS.TRADE_BOX.BRACKET_ORDER_STOP_LOSS_BELOW_LIQUIDATION_PRICE", - mapOf( - "TRIGGER_PRICE_LIMIT" to mapOf( - "value" to liquidationPrice, - "format" to "price", - "tickSize" to tickSize, - ), - ), - )) - } else { - null - } - } - "BUY" -> { - if (triggerPrice < liquidationPrice) { - listOf(error( - "ERROR", - "BRACKET_ORDER_STOP_LOSS_ABOVE_LIQUIDATION_PRICE", - listOf(TriggerOrdersInputField.stopLossPrice.rawValue), - "APP.TRADE.MODIFY_TRIGGER_PRICE", - "ERRORS.TRADE_BOX_TITLE.BRACKET_ORDER_STOP_LOSS_ABOVE_LIQUIDATION_PRICE", - "ERRORS.TRADE_BOX.BRACKET_ORDER_STOP_LOSS_ABOVE_LIQUIDATION_PRICE", - mapOf( - "TRIGGER_PRICE_LIMIT" to mapOf( - "value" to liquidationPrice, - "format" to "price", - "tickSize" to tickSize, - ), - ), - )) - } else { - null - } - } - else -> null - } - } - - private fun validateTakeProfitTriggerToLiquidationPrice( - triggerOrder: Map, - position: Map, - tickSize: String, - ): List? { - - val liquidationPrice = parser.asDouble(parser.value(position, "size.liquidationPrice")) ?: return null - val triggerPrice = - parser.asDouble(parser.value(triggerOrder, "price.triggerPrice")) ?: return null - - val side = parser.value(triggerOrder, "side") - Logger.e { "xcxc take profit, ${liquidationPrice}, ${triggerPrice}, ${side}"} - return when (parser.value(triggerOrder, "side")) { - "SELL" -> { - if (triggerPrice < liquidationPrice) { - listOf(error( - "ERROR", - "BRACKET_ORDER_TAKE_PROFIT_ABOVE_LIQUIDATION_PRICE", - listOf(TriggerOrdersInputField.stopLossPrice.rawValue), - "APP.TRADE.MODIFY_TRIGGER_PRICE", - "ERRORS.TRADE_BOX_TITLE.BRACKET_ORDER_TAKE_PROFIT_ABOVE_LIQUIDATION_PRICE", - "ERRORS.TRADE_BOX.BRACKET_ORDER_TAKE_PROFIT_ABOVE_LIQUIDATION_PRICE", - mapOf( - "TRIGGER_PRICE_LIMIT" to mapOf( - "value" to liquidationPrice, - "format" to "price", - "tickSize" to tickSize, - ), - ), - )) - } else { - null - } - } - "BUY" -> { - if (triggerPrice > liquidationPrice) { - listOf(error( - "ERROR", - "BRACKET_ORDER_TAKE_PROFIT_BELOW_LIQUIDATION_PRICE", - listOf(TriggerOrdersInputField.stopLossPrice.rawValue), - "APP.TRADE.MODIFY_TRIGGER_PRICE", - "ERRORS.TRADE_BOX_TITLE.BRACKET_ORDER_TAKE_PROFIT_BELOW_LIQUIDATION_PRICE", - "ERRORS.TRADE_BOX.BRACKET_ORDER_TAKE_PROFIT_BELOW_LIQUIDATION_PRICE", - mapOf( - "TRIGGER_PRICE_LIMIT" to mapOf( - "value" to liquidationPrice, - "format" to "price", - "tickSize" to tickSize, - ), - ), - )) - } else { - null - } - } - else -> null - } - } - private fun validateTriggerOrder( triggerOrder: Map, market: Map?, oraclePrice: Double, tickSize: String, - liquidationPriceErrors: List?, + liquidationPriceErrors: List? = null, ): MutableList? { val triggerErrors = mutableListOf() - Logger.e { "xcxc validating trigger order "} - validateRequiredInput(triggerOrder)?.let { /* REQUIRED_TRIGGER_PRICE @@ -291,18 +169,84 @@ internal class TriggerOrdersInputValidator( */ triggerErrors.addAll(it) } - + liquidationPriceErrors?.let { /* - xcxc + SELL_TRIGGER_TOO_CLOSE_TO_LIQUIDATION_PRICE + BUY_TRIGGER_TOO_CLOSE_TO_LIQUIDATION_PRICE */ - Logger.e { "xcxc, ${it} "} triggerErrors.addAll(it) } return if (triggerErrors.size > 0) triggerErrors else null } + private fun validateStopLossTriggerToLiquidationPrice( + triggerOrder: Map, + position: Map, + tickSize: String, + ): List? { + val liquidationPrice = parser.asDouble(parser.value(position, "liquidationPrice.current")) + val triggerPrice = parser.asDouble(parser.value(triggerOrder, "price.triggerPrice")) + + if (liquidationPrice == null || triggerPrice == null) { + return null + } + + val type = parser.asString(triggerOrder["type"]) + val side = parser.asString(triggerOrder["side"]) + + return when (requiredTriggerToLiquidationPrice(type, side)) { + RelativeToPrice.ABOVE -> { + if (triggerPrice <= liquidationPrice) { + listOf( + error( + "ERROR", + "SELL_TRIGGER_TOO_CLOSE_TO_LIQUIDATION_PRICE", + listOf(TriggerOrdersInputField.stopLossPrice.rawValue), + "APP.TRADE.MODIFY_TRIGGER_PRICE", + "ERRORS.TRADE_BOX_TITLE.SELL_TRIGGER_TOO_CLOSE_TO_LIQUIDATION_PRICE", + "ERRORS.TRADE_BOX.SELL_TRIGGER_TOO_CLOSE_TO_LIQUIDATION_PRICE", + mapOf( + "TRIGGER_PRICE_LIMIT" to mapOf( + "value" to liquidationPrice, + "format" to "price", + "tickSize" to tickSize, + ), + ), + ), + ) + } else { + null + } + } + RelativeToPrice.BELOW -> { + if (triggerPrice >= liquidationPrice) { + listOf( + error( + "ERROR", + "BUY_TRIGGER_TOO_CLOSE_TO_LIQUIDATION_PRICE", + listOf(TriggerOrdersInputField.stopLossPrice.rawValue), + "APP.TRADE.MODIFY_TRIGGER_PRICE", + "ERRORS.TRADE_BOX_TITLE.BUY_TRIGGER_TOO_CLOSE_TO_LIQUIDATION_PRICE", + "ERRORS.TRADE_BOX.BUY_TRIGGER_TOO_CLOSE_TO_LIQUIDATION_PRICE", + mapOf( + "TRIGGER_PRICE_LIMIT" to mapOf( + "value" to liquidationPrice, + "format" to "price", + "tickSize" to tickSize, + ), + ), + ), + ) + } else { + null + } + } + else -> null + } + } + private fun validateOrderCount( triggerOrders: Map, subaccount: Map?, @@ -677,6 +621,19 @@ internal class TriggerOrdersInputValidator( return null } + private fun requiredTriggerToLiquidationPrice(type: String?, side: String?): RelativeToPrice? { + return when (type) { + "STOP_MARKET" -> + when (side) { + "BUY" -> RelativeToPrice.BELOW + "SELL" -> RelativeToPrice.ABOVE + else -> null + } + + else -> null + } + } + private fun requiredTriggerToIndexPrice(type: String, side: String): RelativeToPrice? { return when (type) { "STOP_LIMIT", "STOP_MARKET" -> diff --git a/src/commonTest/kotlin/exchange.dydx.abacus/validation/TriggerOrdersInputValidationTests.kt b/src/commonTest/kotlin/exchange.dydx.abacus/validation/TriggerOrdersInputValidationTests.kt index 48204c6a9..10a4670f2 100644 --- a/src/commonTest/kotlin/exchange.dydx.abacus/validation/TriggerOrdersInputValidationTests.kt +++ b/src/commonTest/kotlin/exchange.dydx.abacus/validation/TriggerOrdersInputValidationTests.kt @@ -116,6 +116,42 @@ class TriggerOrdersInputValidationTests : V4BaseTests() { perp.triggerOrders("STOP_MARKET", TriggerOrdersInputField.stopLossOrderType, 0) }, null) + test( + { + perp.triggerOrders("900", TriggerOrdersInputField.stopLossPrice, 0) + }, + """ + { + "input": { + "current": "triggerOrders", + "triggerOrders": { + "stopLossOrder": { + "type": "STOP_MARKET" + } + }, + "errors": [ + { + "type": "ERROR", + "code": "SELL_TRIGGER_TOO_CLOSE_TO_LIQUIDATION_PRICE", + "fields": ["stopLossOrder.price.triggerPrice"], + "resources": { + "title": { + "stringKey": "ERRORS.TRADE_BOX_TITLE.SELL_TRIGGER_TOO_CLOSE_TO_LIQUIDATION_PRICE" + }, + "text": { + "stringKey": "ERRORS.TRADE_BOX.SELL_TRIGGER_TOO_CLOSE_TO_LIQUIDATION_PRICE" + }, + "action": { + "stringKey": "APP.TRADE.MODIFY_TRIGGER_PRICE" + } + } + } + ] + } + } + """.trimIndent(), + ) + test( { perp.triggerOrders("2000", TriggerOrdersInputField.stopLossPrice, 0) @@ -238,6 +274,34 @@ class TriggerOrdersInputValidationTests : V4BaseTests() { """.trimIndent(), ) + test({ + perp.triggerOrders("800", TriggerOrdersInputField.stopLossLimitPrice, 0) + }, null) + + test( + { + perp.triggerOrders("900", TriggerOrdersInputField.stopLossPrice, 0) + }, + """ + { + "input": { + "current": "triggerOrders", + "triggerOrders": { + "stopLossOrder": { + "type": "STOP_LIMIT", + "side": "SELL", + "price": { + "triggerPrice": "900", + "limitPrice": "800" + } + } + }, + "errors": null + } + } + """.trimIndent(), + ) + test( { perp.triggerOrders("1000", TriggerOrdersInputField.stopLossPrice, 0) From 04becfd72c4d3183fd6bf8f2c863241d6bed3147 Mon Sep 17 00:00:00 2001 From: mulan xia Date: Thu, 25 Apr 2024 13:09:03 -0400 Subject: [PATCH 03/10] bump version --- build.gradle.kts | 2 +- v4_abacus.podspec | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 3a8d5cb54..4bdfaeb1f 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -49,7 +49,7 @@ allprojects { } group = "exchange.dydx.abacus" -version = "1.6.45" +version = "1.6.46" repositories { google() diff --git a/v4_abacus.podspec b/v4_abacus.podspec index 985d6ae94..2555755e5 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.6.45' + spec.version = '1.6.46' spec.homepage = 'https://github.com/dydxprotocol/v4-abacus' spec.source = { :http=> ''} spec.authors = '' From 10ed4016b369c27e4a55cffeb1ccea73c5cae344 Mon Sep 17 00:00:00 2001 From: mulan xia Date: Thu, 25 Apr 2024 16:56:18 -0400 Subject: [PATCH 04/10] ugh detekt is still failing --- .../validator/TriggerOrdersInputValidator.kt | 282 ++++++++++-------- .../trade/TradeBracketOrdersValidator.kt | 4 - .../trade/TradeTriggerPriceValidator.kt | 5 +- 3 files changed, 169 insertions(+), 122 deletions(-) diff --git a/src/commonMain/kotlin/exchange.dydx.abacus/validator/TriggerOrdersInputValidator.kt b/src/commonMain/kotlin/exchange.dydx.abacus/validator/TriggerOrdersInputValidator.kt index bcb32ece1..b168dc282 100644 --- a/src/commonMain/kotlin/exchange.dydx.abacus/validator/TriggerOrdersInputValidator.kt +++ b/src/commonMain/kotlin/exchange.dydx.abacus/validator/TriggerOrdersInputValidator.kt @@ -1,5 +1,7 @@ package exchange.dydx.abacus.validator import abs +import exchange.dydx.abacus.output.input.OrderSide +import exchange.dydx.abacus.output.input.OrderType import exchange.dydx.abacus.protocols.LocalizerProtocol import exchange.dydx.abacus.protocols.ParserProtocol import exchange.dydx.abacus.state.app.helper.Formatter @@ -7,13 +9,7 @@ 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 - -internal data class EquityTier( - val requiredTotalNetCollateralUSD: Double, - val maxOrders: Int, -) { - var nextLevelRequiredTotalNetCollateralUSD: Double? = null -} +import exchange.dydx.abacus.validator.trade.EquityTier enum class RelativeToPrice(val rawValue: String) { ABOVE("ABOVE"), @@ -73,6 +69,7 @@ internal class TriggerOrdersInputValidator( market, oraclePrice, tickSize, + position, ) } else { null @@ -88,7 +85,7 @@ internal class TriggerOrdersInputValidator( market, oraclePrice, tickSize, - validateStopLossTriggerToLiquidationPrice(stopLossOrder, position, tickSize), + position, ) } else { null @@ -132,7 +129,7 @@ internal class TriggerOrdersInputValidator( market: Map?, oraclePrice: Double, tickSize: String, - liquidationPriceErrors: List? = null, + position: Map, ): MutableList? { val triggerErrors = mutableListOf() @@ -170,7 +167,7 @@ internal class TriggerOrdersInputValidator( triggerErrors.addAll(it) } - liquidationPriceErrors?.let { + validateTriggerToLiquidationPrice(triggerOrder, position, tickSize)?.let { /* SELL_TRIGGER_TOO_CLOSE_TO_LIQUIDATION_PRICE BUY_TRIGGER_TOO_CLOSE_TO_LIQUIDATION_PRICE @@ -181,40 +178,29 @@ internal class TriggerOrdersInputValidator( return if (triggerErrors.size > 0) triggerErrors else null } - private fun validateStopLossTriggerToLiquidationPrice( + private fun validateTriggerToLiquidationPrice( triggerOrder: Map, position: Map, tickSize: String, ): List? { val liquidationPrice = parser.asDouble(parser.value(position, "liquidationPrice.current")) val triggerPrice = parser.asDouble(parser.value(triggerOrder, "price.triggerPrice")) + val type = parser.asString(triggerOrder["type"])?.let { OrderType.invoke(it) } + val side = parser.asString(triggerOrder["side"])?.let { OrderSide.invoke(it) } - if (liquidationPrice == null || triggerPrice == null) { + if (side == null || liquidationPrice == null || triggerPrice == null) { return null } - val type = parser.asString(triggerOrder["type"]) - val side = parser.asString(triggerOrder["side"]) - return when (requiredTriggerToLiquidationPrice(type, side)) { RelativeToPrice.ABOVE -> { if (triggerPrice <= liquidationPrice) { - listOf( - error( - "ERROR", - "SELL_TRIGGER_TOO_CLOSE_TO_LIQUIDATION_PRICE", - listOf(TriggerOrdersInputField.stopLossPrice.rawValue), - "APP.TRADE.MODIFY_TRIGGER_PRICE", - "ERRORS.TRADE_BOX_TITLE.SELL_TRIGGER_TOO_CLOSE_TO_LIQUIDATION_PRICE", - "ERRORS.TRADE_BOX.SELL_TRIGGER_TOO_CLOSE_TO_LIQUIDATION_PRICE", - mapOf( - "TRIGGER_PRICE_LIMIT" to mapOf( - "value" to liquidationPrice, - "format" to "price", - "tickSize" to tickSize, - ), - ), - ), + liquidationPriceError( + "SELL_TRIGGER_TOO_CLOSE_TO_LIQUIDATION_PRICE", + "ERRORS.TRADE_BOX_TITLE.SELL_TRIGGER_TOO_CLOSE_TO_LIQUIDATION_PRICE", + "ERRORS.TRADE_BOX.SELL_TRIGGER_TOO_CLOSE_TO_LIQUIDATION_PRICE", + liquidationPrice, + tickSize, ) } else { null @@ -222,22 +208,12 @@ internal class TriggerOrdersInputValidator( } RelativeToPrice.BELOW -> { if (triggerPrice >= liquidationPrice) { - listOf( - error( - "ERROR", - "BUY_TRIGGER_TOO_CLOSE_TO_LIQUIDATION_PRICE", - listOf(TriggerOrdersInputField.stopLossPrice.rawValue), - "APP.TRADE.MODIFY_TRIGGER_PRICE", - "ERRORS.TRADE_BOX_TITLE.BUY_TRIGGER_TOO_CLOSE_TO_LIQUIDATION_PRICE", - "ERRORS.TRADE_BOX.BUY_TRIGGER_TOO_CLOSE_TO_LIQUIDATION_PRICE", - mapOf( - "TRIGGER_PRICE_LIMIT" to mapOf( - "value" to liquidationPrice, - "format" to "price", - "tickSize" to tickSize, - ), - ), - ), + liquidationPriceError( + "BUY_TRIGGER_TOO_CLOSE_TO_LIQUIDATION_PRICE", + "ERRORS.TRADE_BOX_TITLE.BUY_TRIGGER_TOO_CLOSE_TO_LIQUIDATION_PRICE", + "ERRORS.TRADE_BOX.BUY_TRIGGER_TOO_CLOSE_TO_LIQUIDATION_PRICE", + liquidationPrice, + tickSize, ) } else { null @@ -247,6 +223,31 @@ internal class TriggerOrdersInputValidator( } } + private fun liquidationPriceError( + errorCode: String, + titleStringKey: String, + textStringKey: String, + liquidationPrice: Double?, + tickSize: String, + ): List? { + return listOf( + error( + "ERROR", + errorCode, + listOf(TriggerOrdersInputField.stopLossPrice.rawValue), + "APP.TRADE.MODIFY_TRIGGER_PRICE", + titleStringKey, + textStringKey, + mapOf( + "TRIGGER_PRICE_LIMIT" to mapOf( + "value" to liquidationPrice, + "tickSize" to tickSize, + ), + ), + ), + ) + } + private fun validateOrderCount( triggerOrders: Map, subaccount: Map?, @@ -420,11 +421,13 @@ internal class TriggerOrdersInputValidator( private fun validateCalculatedPricesPositive( triggerOrder: Map, ): List? { - val type = parser.asString(triggerOrder["type"]) + val type = parser.asString(triggerOrder["type"])?.let { + OrderType.invoke(it) + } val triggerPrice = parser.asDouble(parser.value(triggerOrder, "price.triggerPrice")) val limitPrice = parser.asDouble(parser.value(triggerOrder, "price.limitPrice")) val inputField = parser.asString(parser.value(triggerOrder, "price.input")) - val fields = if (type == "STOP_LIMIT" || type == "STOP_MARKET") { + val fields = if (type == OrderType.stopLimit || type == OrderType.stopMarket) { if (triggerPrice != null && triggerPrice <= 0) { listOfNotNull(inputField) } else if (limitPrice != null && limitPrice <= 0) { @@ -432,7 +435,7 @@ internal class TriggerOrdersInputValidator( } else { null } - } else if (type == "TAKE_PROFIT" || type == "TAKE_PROFIT_MARKET") { + } else if (type == OrderType.takeProfitLimit || type == OrderType.takeProfitMarket) { if (triggerPrice != null && triggerPrice <= 0) { listOfNotNull(inputField) } else if (limitPrice != null && limitPrice <= 0) { @@ -464,8 +467,16 @@ internal class TriggerOrdersInputValidator( oraclePrice: Double, tickSize: String, ): List? { - val type = parser.asString(triggerOrder["type"]) ?: return null - val side = parser.asString(triggerOrder["side"]) ?: return null + val type = parser.asString(triggerOrder["type"])?.let { + OrderType.invoke(it) + } + val side = parser.asString(triggerOrder["side"])?.let { + OrderSide.invoke(it) + } + + if (type == null || side == null) { + return null + } val triggerPrice = parser.asDouble(parser.value(triggerOrder, "price.triggerPrice")) ?: return null @@ -508,58 +519,83 @@ internal class TriggerOrdersInputValidator( private fun validateLimitPrice( triggerOrder: Map, ): List? { - val type = parser.asString(triggerOrder["type"]) - val fields = if (type == "STOP_LIMIT") { - listOf( - TriggerOrdersInputField.stopLossLimitPrice.rawValue, - ) - } else if (type == "TAKE_PROFIT") { - listOf( - TriggerOrdersInputField.takeProfitLimitPrice.rawValue, - ) - } else { - null + val type = parser.asString(triggerOrder["type"])?.let { OrderType.invoke(it) } + val side = parser.asString(triggerOrder["side"])?.let { OrderSide.invoke(it) } + + if (type == null || side == null) { + return null + } + + val fields = when (type) { + OrderType.stopLimit -> listOf(TriggerOrdersInputField.stopLossLimitPrice.rawValue) + OrderType.takeProfitLimit -> listOf(TriggerOrdersInputField.takeProfitLimitPrice.rawValue) + else -> null } + return when (type) { - "STOP_LIMIT", "TAKE_PROFIT" -> { - parser.asString(parser.value(triggerOrder, "side"))?.let { side -> - parser.asDouble(parser.value(triggerOrder, "price.limitPrice")) - ?.let { limitPrice -> - parser.asDouble(parser.value(triggerOrder, "price.triggerPrice")) - ?.let { triggerPrice -> - if (side == "BUY" && limitPrice < triggerPrice) { - return listOf( - error( - "ERROR", - "LIMIT_MUST_ABOVE_TRIGGER_PRICE", - fields, - "APP.TRADE.MODIFY_TRIGGER_PRICE", - if (type == "STOP_LIMIT") "ERRORS.TRIGGERS_FORM_TITLE.STOP_LOSS_LIMIT_MUST_ABOVE_TRIGGER_PRICE" else "ERRORS.TRIGGERS_FORM_TITLE.TAKE_PROFIT_LIMIT_MUST_ABOVE_TRIGGER_PRICE", - if (type == "STOP_LIMIT") "ERRORS.TRIGGERS_FORM.STOP_LOSS_LIMIT_MUST_ABOVE_TRIGGER_PRICE" else "ERRORS.TRIGGERS_FORM.TAKE_PROFIT_LIMIT_MUST_ABOVE_TRIGGER_PRICE", - ), - ) - } else if (side == "SELL" && limitPrice > triggerPrice) { - return listOf( - error( - "ERROR", - "LIMIT_MUST_BELOW_TRIGGER_PRICE", - fields, - "APP.TRADE.MODIFY_TRIGGER_PRICE", - if (type == "STOP_LIMIT") "ERRORS.TRIGGERS_FORM_TITLE.STOP_LOSS_LIMIT_MUST_BELOW_TRIGGER_PRICE" else "ERRORS.TRIGGERS_FORM_TITLE.TAKE_PROFIT_LIMIT_MUST_BELOW_TRIGGER_PRICE", - if (type == "STOP_LIMIT") "ERRORS.TRIGGERS_FORM.STOP_LOSS_LIMIT_MUST_BELOW_TRIGGER_PRICE" else "ERRORS.TRIGGERS_FORM.TAKE_PROFIT_LIMIT_MUST_BELOW_TRIGGER_PRICE", - ), - ) - } else { - null - } + OrderType.stopLimit, OrderType.takeProfitLimit -> { + parser.asDouble(parser.value(triggerOrder, "price.limitPrice")) + ?.let { limitPrice -> + parser.asDouble(parser.value(triggerOrder, "price.triggerPrice")) + ?.let { triggerPrice -> + if (side == OrderSide.buy && limitPrice < triggerPrice) { + return limitPriceError( + "LIMIT_MUST_ABOVE_TRIGGER_PRICE", + fields, + if (type == OrderType.stopLimit) { + "ERRORS.TRIGGERS_FORM_TITLE.STOP_LOSS_LIMIT_MUST_ABOVE_TRIGGER_PRICE" + } else { + "ERRORS.TRIGGERS_FORM_TITLE.TAKE_PROFIT_LIMIT_MUST_ABOVE_TRIGGER_PRICE" + }, + if (type == OrderType.stopLimit) { + "ERRORS.TRIGGERS_FORM.STOP_LOSS_LIMIT_MUST_ABOVE_TRIGGER_PRICE" + } else { + "ERRORS.TRIGGERS_FORM.TAKE_PROFIT_LIMIT_MUST_ABOVE_TRIGGER_PRICE" + }, + ) + } else if (side == OrderSide.sell && limitPrice > triggerPrice) { + return limitPriceError( + "LIMIT_MUST_BELOW_TRIGGER_PRICE", + fields, + if (type == OrderType.stopLimit) { + "ERRORS.TRIGGERS_FORM_TITLE.STOP_LOSS_LIMIT_MUST_BELOW_TRIGGER_PRICE" + } else { + "ERRORS.TRIGGERS_FORM_TITLE.TAKE_PROFIT_LIMIT_MUST_BELOW_TRIGGER_PRICE" + }, + if (type == OrderType.stopLimit) { + "ERRORS.TRIGGERS_FORM.STOP_LOSS_LIMIT_MUST_BELOW_TRIGGER_PRICE" + } else { + "ERRORS.TRIGGERS_FORM.TAKE_PROFIT_LIMIT_MUST_BELOW_TRIGGER_PRICE" + }, + ) + } else { + null } - } - } + } + } } else -> null } } + private fun limitPriceError( + errorCode: String, + fields: List?, + titleStringKey: String, + textStringKey: String, + ): List? { + return listOf( + error( + "ERROR", + errorCode, + fields, + "APP.TRADE.MODIFY_TRIGGER_PRICE", + titleStringKey, + textStringKey, + ), + ) + } + private fun validateSize( orderSize: Double?, market: Map?, @@ -621,35 +657,30 @@ internal class TriggerOrdersInputValidator( return null } - private fun requiredTriggerToLiquidationPrice(type: String?, side: String?): RelativeToPrice? { + private fun requiredTriggerToLiquidationPrice(type: OrderType?, side: OrderSide): RelativeToPrice? { return when (type) { - "STOP_MARKET" -> + OrderType.stopMarket -> when (side) { - "BUY" -> RelativeToPrice.BELOW - "SELL" -> RelativeToPrice.ABOVE - else -> null + OrderSide.buy -> RelativeToPrice.BELOW + OrderSide.sell -> RelativeToPrice.ABOVE } - else -> null } } - private fun requiredTriggerToIndexPrice(type: String, side: String): RelativeToPrice? { + private fun requiredTriggerToIndexPrice(type: OrderType, side: OrderSide): RelativeToPrice? { return when (type) { - "STOP_LIMIT", "STOP_MARKET" -> + OrderType.stopLimit, OrderType.stopMarket -> when (side) { - "BUY" -> RelativeToPrice.ABOVE - "SELL" -> RelativeToPrice.BELOW - else -> null + OrderSide.buy -> RelativeToPrice.ABOVE + OrderSide.sell -> RelativeToPrice.BELOW } - "TAKE_PROFIT", "TAKE_PROFIT_MARKET" -> + OrderType.takeProfitLimit, OrderType.takeProfitMarket -> when (side) { - "BUY" -> RelativeToPrice.BELOW - "SELL" -> RelativeToPrice.ABOVE - else -> null + OrderSide.buy -> RelativeToPrice.BELOW + OrderSide.sell -> RelativeToPrice.ABOVE } - else -> null } } @@ -658,7 +689,7 @@ internal class TriggerOrdersInputValidator( triggerToIndex: RelativeToPrice, oraclePrice: Double, tickSize: String, - type: String, + type: OrderType, inputField: String?, ): Map { val action = "APP.TRADE.MODIFY_TRIGGER_PRICE" @@ -671,6 +702,7 @@ internal class TriggerOrdersInputValidator( ), ) val fields = listOfNotNull(inputField) + val isStopLoss = type == OrderType.stopLimit || type == OrderType.stopMarket return when (triggerToIndex) { RelativeToPrice.ABOVE -> error( @@ -678,8 +710,16 @@ internal class TriggerOrdersInputValidator( "TRIGGER_MUST_ABOVE_INDEX_PRICE", fields, action, - if (type == "STOP_LIMIT" || type == "STOP_MARKET") "ERRORS.TRIGGERS_FORM_TITLE.STOP_LOSS_TRIGGER_MUST_ABOVE_INDEX_PRICE" else "ERRORS.TRIGGERS_FORM_TITLE.TAKE_PROFIT_TRIGGER_MUST_ABOVE_INDEX_PRICE", - if (type == "STOP_LIMIT" || type == "STOP_MARKET") "ERRORS.TRIGGERS_FORM.STOP_LOSS_TRIGGER_MUST_ABOVE_INDEX_PRICE" else "ERRORS.TRIGGERS_FORM.TAKE_PROFIT_TRIGGER_MUST_ABOVE_INDEX_PRICE", + if (isStopLoss) { + "ERRORS.TRIGGERS_FORM_TITLE.STOP_LOSS_TRIGGER_MUST_ABOVE_INDEX_PRICE" + } else { + "ERRORS.TRIGGERS_FORM_TITLE.TAKE_PROFIT_TRIGGER_MUST_ABOVE_INDEX_PRICE" + }, + if (isStopLoss) { + "ERRORS.TRIGGERS_FORM.STOP_LOSS_TRIGGER_MUST_ABOVE_INDEX_PRICE" + } else { + "ERRORS.TRIGGERS_FORM.TAKE_PROFIT_TRIGGER_MUST_ABOVE_INDEX_PRICE" + }, params, ) @@ -688,8 +728,16 @@ internal class TriggerOrdersInputValidator( "TRIGGER_MUST_BELOW_INDEX_PRICE", fields, action, - if (type == "STOP_LIMIT" || type == "STOP_MARKET") "ERRORS.TRIGGERS_FORM_TITLE.STOP_LOSS_TRIGGER_MUST_BELOW_INDEX_PRICE" else "ERRORS.TRIGGERS_FORM_TITLE.TAKE_PROFIT_TRIGGER_MUST_BELOW_INDEX_PRICE", - if (type == "STOP_LIMIT" || type == "STOP_MARKET") "ERRORS.TRIGGERS_FORM.STOP_LOSS_TRIGGER_MUST_BELOW_INDEX_PRICE" else "ERRORS.TRIGGERS_FORM.TAKE_PROFIT_TRIGGER_MUST_BELOW_INDEX_PRICE", + if (isStopLoss) { + "ERRORS.TRIGGERS_FORM_TITLE.STOP_LOSS_TRIGGER_MUST_BELOW_INDEX_PRICE" + } else { + "ERRORS.TRIGGERS_FORM_TITLE.TAKE_PROFIT_TRIGGER_MUST_BELOW_INDEX_PRICE" + }, + if (isStopLoss) { + "ERRORS.TRIGGERS_FORM.STOP_LOSS_TRIGGER_MUST_BELOW_INDEX_PRICE" + } else { + "ERRORS.TRIGGERS_FORM.TAKE_PROFIT_TRIGGER_MUST_BELOW_INDEX_PRICE" + }, params, ) } diff --git a/src/commonMain/kotlin/exchange.dydx.abacus/validator/trade/TradeBracketOrdersValidator.kt b/src/commonMain/kotlin/exchange.dydx.abacus/validator/trade/TradeBracketOrdersValidator.kt index 7dd2f21da..6b64bcf27 100644 --- a/src/commonMain/kotlin/exchange.dydx.abacus/validator/trade/TradeBracketOrdersValidator.kt +++ b/src/commonMain/kotlin/exchange.dydx.abacus/validator/trade/TradeBracketOrdersValidator.kt @@ -163,7 +163,6 @@ internal class TradeBracketOrdersValidator( mapOf( "TRIGGER_PRICE_LIMIT" to mapOf( "value" to liquidationPrice, - "format" to "price", "tickSize" to tickSize, ), ), @@ -186,7 +185,6 @@ internal class TradeBracketOrdersValidator( mapOf( "TRIGGER_PRICE_LIMIT" to mapOf( "value" to liquidationPrice, - "format" to "price", "tickSize" to tickSize, ), ), @@ -328,7 +326,6 @@ internal class TradeBracketOrdersValidator( mapOf( "TRIGGER_PRICE_LIMIT" to mapOf( "value" to liquidationPrice, - "format" to "price", "tickSize" to tickSize, ), ), @@ -348,7 +345,6 @@ internal class TradeBracketOrdersValidator( mapOf( "TRIGGER_PRICE_LIMIT" to mapOf( "value" to liquidationPrice, - "format" to "price", "tickSize" to tickSize, ), ), diff --git a/src/commonMain/kotlin/exchange.dydx.abacus/validator/trade/TradeTriggerPriceValidator.kt b/src/commonMain/kotlin/exchange.dydx.abacus/validator/trade/TradeTriggerPriceValidator.kt index 705707fb8..ac36ba3c1 100644 --- a/src/commonMain/kotlin/exchange.dydx.abacus/validator/trade/TradeTriggerPriceValidator.kt +++ b/src/commonMain/kotlin/exchange.dydx.abacus/validator/trade/TradeTriggerPriceValidator.kt @@ -103,6 +103,7 @@ internal class TradeTriggerPriceValidator( triggerToLiquidationError( triggerToLiquidation, liquidationPrice, + tickSize, ), ) } @@ -114,6 +115,7 @@ internal class TradeTriggerPriceValidator( triggerToLiquidationError( triggerToLiquidation, liquidationPrice, + tickSize, ), ) } @@ -234,12 +236,13 @@ internal class TradeTriggerPriceValidator( private fun triggerToLiquidationError( triggerToLiquidation: RelativeToPrice, triggerLiquidation: Double, + tickSize: String, ): Map { val fields = listOf("price.triggerPrice") val action = "APP.TRADE.MODIFY_TRIGGER_PRICE" // Localizations uses TRIGGER_PRICE_LIMIT as paramater name val params = - mapOf("TRIGGER_PRICE_LIMIT" to mapOf("value" to triggerLiquidation, "format" to "price")) + mapOf("TRIGGER_PRICE_LIMIT" to mapOf("value" to triggerLiquidation, "tickSize" to tickSize)) return when (triggerToLiquidation) { RelativeToPrice.ABOVE -> error( "ERROR", From c8144a69d48d45ae47c22282bb068270a02bf37c Mon Sep 17 00:00:00 2001 From: mulan xia Date: Thu, 25 Apr 2024 17:47:53 -0400 Subject: [PATCH 05/10] clean up a bit --- .../validator/TriggerOrdersInputValidator.kt | 127 ++++++++++-------- .../trade/TradeInputDataValidator.kt | 8 +- 2 files changed, 74 insertions(+), 61 deletions(-) diff --git a/src/commonMain/kotlin/exchange.dydx.abacus/validator/TriggerOrdersInputValidator.kt b/src/commonMain/kotlin/exchange.dydx.abacus/validator/TriggerOrdersInputValidator.kt index b168dc282..21433a8c9 100644 --- a/src/commonMain/kotlin/exchange.dydx.abacus/validator/TriggerOrdersInputValidator.kt +++ b/src/commonMain/kotlin/exchange.dydx.abacus/validator/TriggerOrdersInputValidator.kt @@ -1,6 +1,8 @@ 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 @@ -9,6 +11,7 @@ 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.AccountLimitConstants.MAX_NUM_OPEN_UNTRIGGERED_ORDERS import exchange.dydx.abacus.validator.trade.EquityTier enum class RelativeToPrice(val rawValue: String) { @@ -28,9 +31,6 @@ internal class TriggerOrdersInputValidator( ) : BaseInputValidator(localizer, formatter, parser), ValidatorProtocol { - @Suppress("PropertyName") - private val MAX_NUM_OPEN_UNTRIGGERED_ORDERS: Int = 20 - override fun validate( wallet: Map?, user: Map?, @@ -365,42 +365,20 @@ internal class TriggerOrdersInputValidator( 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") - ) { + 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 isStatefulOrder(orderType: String, timeInForce: String): Boolean { - return when (orderType) { - "MARKET" -> false - - "LIMIT" -> { - when (parser.asString(timeInForce)) { - "GTT" -> true - else -> false - } - } - - else -> true - } - } - private fun validateRequiredInput( triggerOrder: Map, ): List? { @@ -657,34 +635,6 @@ internal class TriggerOrdersInputValidator( return null } - private fun requiredTriggerToLiquidationPrice(type: OrderType?, side: OrderSide): RelativeToPrice? { - return when (type) { - OrderType.stopMarket -> - when (side) { - OrderSide.buy -> RelativeToPrice.BELOW - OrderSide.sell -> RelativeToPrice.ABOVE - } - else -> null - } - } - - private fun requiredTriggerToIndexPrice(type: OrderType, side: OrderSide): RelativeToPrice? { - return when (type) { - OrderType.stopLimit, OrderType.stopMarket -> - when (side) { - OrderSide.buy -> RelativeToPrice.ABOVE - OrderSide.sell -> RelativeToPrice.BELOW - } - - OrderType.takeProfitLimit, OrderType.takeProfitMarket -> - when (side) { - OrderSide.buy -> RelativeToPrice.BELOW - OrderSide.sell -> RelativeToPrice.ABOVE - } - else -> null - } - } - private fun triggerToIndexError( triggerToIndex: RelativeToPrice, oraclePrice: Double, @@ -743,3 +693,64 @@ 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 -> + when (side) { + OrderSide.buy -> RelativeToPrice.BELOW + OrderSide.sell -> RelativeToPrice.ABOVE + } + else -> null + } +} + +private fun requiredTriggerToIndexPrice(type: OrderType, side: OrderSide): RelativeToPrice? { + return when (type) { + OrderType.stopLimit, OrderType.stopMarket -> + when (side) { + OrderSide.buy -> RelativeToPrice.ABOVE + OrderSide.sell -> RelativeToPrice.BELOW + } + + OrderType.takeProfitLimit, OrderType.takeProfitMarket -> + when (side) { + OrderSide.buy -> RelativeToPrice.BELOW + OrderSide.sell -> RelativeToPrice.ABOVE + } + else -> null + } +} 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 bfab29e4a..5dd1bccad 100644 --- a/src/commonMain/kotlin/exchange.dydx.abacus/validator/trade/TradeInputDataValidator.kt +++ b/src/commonMain/kotlin/exchange.dydx.abacus/validator/trade/TradeInputDataValidator.kt @@ -10,6 +10,7 @@ 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.AccountLimitConstants.MAX_NUM_OPEN_UNTRIGGERED_ORDERS import kotlin.time.Duration.Companion.days /* @@ -35,6 +36,10 @@ internal data class EquityTier( var nextLevelRequiredTotalNetCollateralUSD: Double? = null } +internal object AccountLimitConstants { + const val MAX_NUM_OPEN_UNTRIGGERED_ORDERS = 20 +} + internal class TradeInputDataValidator( localizer: LocalizerProtocol?, formatter: Formatter?, @@ -42,9 +47,6 @@ internal class TradeInputDataValidator( ) : BaseInputValidator(localizer, formatter, parser), TradeValidatorProtocol { - @Suppress("PropertyName") - private val MAX_NUM_OPEN_UNTRIGGERED_ORDERS: Int = 20 - override fun validateTrade( subaccount: Map?, market: Map?, From 4ed3984eae9362c902607b9c187cdbbe1ee0c1d8 Mon Sep 17 00:00:00 2001 From: mulan xia Date: Thu, 25 Apr 2024 17:49:58 -0400 Subject: [PATCH 06/10] rename const --- .../validator/TriggerOrdersInputValidator.kt | 2 +- .../validator/trade/TradeInputDataValidator.kt | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/commonMain/kotlin/exchange.dydx.abacus/validator/TriggerOrdersInputValidator.kt b/src/commonMain/kotlin/exchange.dydx.abacus/validator/TriggerOrdersInputValidator.kt index 21433a8c9..dbafb4100 100644 --- a/src/commonMain/kotlin/exchange.dydx.abacus/validator/TriggerOrdersInputValidator.kt +++ b/src/commonMain/kotlin/exchange.dydx.abacus/validator/TriggerOrdersInputValidator.kt @@ -11,8 +11,8 @@ 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.AccountLimitConstants.MAX_NUM_OPEN_UNTRIGGERED_ORDERS 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"), 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 5dd1bccad..69ebfc2d7 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,7 @@ 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.AccountLimitConstants.MAX_NUM_OPEN_UNTRIGGERED_ORDERS +import exchange.dydx.abacus.validator.trade.SubaccountLimitConstants.MAX_NUM_OPEN_UNTRIGGERED_ORDERS import kotlin.time.Duration.Companion.days /* @@ -36,7 +36,7 @@ internal data class EquityTier( var nextLevelRequiredTotalNetCollateralUSD: Double? = null } -internal object AccountLimitConstants { +internal object SubaccountLimitConstants { const val MAX_NUM_OPEN_UNTRIGGERED_ORDERS = 20 } From 0eb9852894e980b47d97710562eaf39f7ab8d672 Mon Sep 17 00:00:00 2001 From: mulan xia Date: Thu, 25 Apr 2024 17:55:01 -0400 Subject: [PATCH 07/10] update to trigger form tx --- .../validator/TriggerOrdersInputValidator.kt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/commonMain/kotlin/exchange.dydx.abacus/validator/TriggerOrdersInputValidator.kt b/src/commonMain/kotlin/exchange.dydx.abacus/validator/TriggerOrdersInputValidator.kt index dbafb4100..8d7d6dfa7 100644 --- a/src/commonMain/kotlin/exchange.dydx.abacus/validator/TriggerOrdersInputValidator.kt +++ b/src/commonMain/kotlin/exchange.dydx.abacus/validator/TriggerOrdersInputValidator.kt @@ -197,8 +197,8 @@ internal class TriggerOrdersInputValidator( if (triggerPrice <= liquidationPrice) { liquidationPriceError( "SELL_TRIGGER_TOO_CLOSE_TO_LIQUIDATION_PRICE", - "ERRORS.TRADE_BOX_TITLE.SELL_TRIGGER_TOO_CLOSE_TO_LIQUIDATION_PRICE", - "ERRORS.TRADE_BOX.SELL_TRIGGER_TOO_CLOSE_TO_LIQUIDATION_PRICE", + "ERRORS.TRIGGERS_FORM_TITLE.SELL_TRIGGER_TOO_CLOSE_TO_LIQUIDATION_PRICE", + "ERRORS.TRIGGERS_FORM.SELL_TRIGGER_TOO_CLOSE_TO_LIQUIDATION_PRICE_NO_LIMIT", liquidationPrice, tickSize, ) @@ -210,8 +210,8 @@ internal class TriggerOrdersInputValidator( if (triggerPrice >= liquidationPrice) { liquidationPriceError( "BUY_TRIGGER_TOO_CLOSE_TO_LIQUIDATION_PRICE", - "ERRORS.TRADE_BOX_TITLE.BUY_TRIGGER_TOO_CLOSE_TO_LIQUIDATION_PRICE", - "ERRORS.TRADE_BOX.BUY_TRIGGER_TOO_CLOSE_TO_LIQUIDATION_PRICE", + "ERRORS.TRIGGERS_FORM_TITLE.BUY_TRIGGER_TOO_CLOSE_TO_LIQUIDATION_PRICE", + "ERRORS.TRIGGERS_FORM.BUY_TRIGGER_TOO_CLOSE_TO_LIQUIDATION_PRICE_NO_LIMIT", liquidationPrice, tickSize, ) From 5bb8264d75c61c7b2332d8557642e541cc401d2f Mon Sep 17 00:00:00 2001 From: mulan xia Date: Thu, 25 Apr 2024 18:04:20 -0400 Subject: [PATCH 08/10] forgot to update test oops --- .../validation/TriggerOrdersInputValidationTests.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/commonTest/kotlin/exchange.dydx.abacus/validation/TriggerOrdersInputValidationTests.kt b/src/commonTest/kotlin/exchange.dydx.abacus/validation/TriggerOrdersInputValidationTests.kt index 10a4670f2..df69f010b 100644 --- a/src/commonTest/kotlin/exchange.dydx.abacus/validation/TriggerOrdersInputValidationTests.kt +++ b/src/commonTest/kotlin/exchange.dydx.abacus/validation/TriggerOrdersInputValidationTests.kt @@ -136,10 +136,10 @@ class TriggerOrdersInputValidationTests : V4BaseTests() { "fields": ["stopLossOrder.price.triggerPrice"], "resources": { "title": { - "stringKey": "ERRORS.TRADE_BOX_TITLE.SELL_TRIGGER_TOO_CLOSE_TO_LIQUIDATION_PRICE" + "stringKey": "ERRORS.TRIGGERS_FORM_TITLE.SELL_TRIGGER_TOO_CLOSE_TO_LIQUIDATION_PRICE" }, "text": { - "stringKey": "ERRORS.TRADE_BOX.SELL_TRIGGER_TOO_CLOSE_TO_LIQUIDATION_PRICE" + "stringKey": "ERRORS.TRIGGERS_FORM.SELL_TRIGGER_TOO_CLOSE_TO_LIQUIDATION_PRICE_NO_LIMIT" }, "action": { "stringKey": "APP.TRADE.MODIFY_TRIGGER_PRICE" From be09d6109d7cf760eac0162dfe767a91e97465cc Mon Sep 17 00:00:00 2001 From: mulan xia Date: Fri, 26 Apr 2024 09:44:16 -0400 Subject: [PATCH 09/10] bump v --- build.gradle.kts | 2 +- v4_abacus.podspec | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 321de0235..df84668cc 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -49,7 +49,7 @@ allprojects { } group = "exchange.dydx.abacus" -version = "1.6.47" +version = "1.6.48" repositories { google() diff --git a/v4_abacus.podspec b/v4_abacus.podspec index b15605f10..76d91e713 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.6.47' + spec.version = '1.6.48' spec.homepage = 'https://github.com/dydxprotocol/v4-abacus' spec.source = { :http=> ''} spec.authors = '' From 40fdc3ff26c832a77cae42fcf213ed24bae7526a Mon Sep 17 00:00:00 2001 From: mulan xia Date: Fri, 26 Apr 2024 09:59:47 -0400 Subject: [PATCH 10/10] Add back price format --- .../validator/TriggerOrdersInputValidator.kt | 1 + .../validator/trade/TradeBracketOrdersValidator.kt | 4 ++++ .../validator/trade/TradeTriggerPriceValidator.kt | 8 +++++++- 3 files changed, 12 insertions(+), 1 deletion(-) diff --git a/src/commonMain/kotlin/exchange.dydx.abacus/validator/TriggerOrdersInputValidator.kt b/src/commonMain/kotlin/exchange.dydx.abacus/validator/TriggerOrdersInputValidator.kt index 8d7d6dfa7..32f7e2b17 100644 --- a/src/commonMain/kotlin/exchange.dydx.abacus/validator/TriggerOrdersInputValidator.kt +++ b/src/commonMain/kotlin/exchange.dydx.abacus/validator/TriggerOrdersInputValidator.kt @@ -241,6 +241,7 @@ internal class TriggerOrdersInputValidator( mapOf( "TRIGGER_PRICE_LIMIT" to mapOf( "value" to liquidationPrice, + "format" to "price", "tickSize" to tickSize, ), ), diff --git a/src/commonMain/kotlin/exchange.dydx.abacus/validator/trade/TradeBracketOrdersValidator.kt b/src/commonMain/kotlin/exchange.dydx.abacus/validator/trade/TradeBracketOrdersValidator.kt index 6b64bcf27..7dd2f21da 100644 --- a/src/commonMain/kotlin/exchange.dydx.abacus/validator/trade/TradeBracketOrdersValidator.kt +++ b/src/commonMain/kotlin/exchange.dydx.abacus/validator/trade/TradeBracketOrdersValidator.kt @@ -163,6 +163,7 @@ internal class TradeBracketOrdersValidator( mapOf( "TRIGGER_PRICE_LIMIT" to mapOf( "value" to liquidationPrice, + "format" to "price", "tickSize" to tickSize, ), ), @@ -185,6 +186,7 @@ internal class TradeBracketOrdersValidator( mapOf( "TRIGGER_PRICE_LIMIT" to mapOf( "value" to liquidationPrice, + "format" to "price", "tickSize" to tickSize, ), ), @@ -326,6 +328,7 @@ internal class TradeBracketOrdersValidator( mapOf( "TRIGGER_PRICE_LIMIT" to mapOf( "value" to liquidationPrice, + "format" to "price", "tickSize" to tickSize, ), ), @@ -345,6 +348,7 @@ internal class TradeBracketOrdersValidator( mapOf( "TRIGGER_PRICE_LIMIT" to mapOf( "value" to liquidationPrice, + "format" to "price", "tickSize" to tickSize, ), ), diff --git a/src/commonMain/kotlin/exchange.dydx.abacus/validator/trade/TradeTriggerPriceValidator.kt b/src/commonMain/kotlin/exchange.dydx.abacus/validator/trade/TradeTriggerPriceValidator.kt index ac36ba3c1..b4b985dc5 100644 --- a/src/commonMain/kotlin/exchange.dydx.abacus/validator/trade/TradeTriggerPriceValidator.kt +++ b/src/commonMain/kotlin/exchange.dydx.abacus/validator/trade/TradeTriggerPriceValidator.kt @@ -242,7 +242,13 @@ internal class TradeTriggerPriceValidator( val action = "APP.TRADE.MODIFY_TRIGGER_PRICE" // Localizations uses TRIGGER_PRICE_LIMIT as paramater name val params = - mapOf("TRIGGER_PRICE_LIMIT" to mapOf("value" to triggerLiquidation, "tickSize" to tickSize)) + mapOf( + "TRIGGER_PRICE_LIMIT" to mapOf( + "value" to triggerLiquidation, + "format" to "price", + "tickSize" to tickSize, + ), + ) return when (triggerToLiquidation) { RelativeToPrice.ABOVE -> error( "ERROR",