From 3c11a769829633ec60ca2e373c7c3ffa717cb33d Mon Sep 17 00:00:00 2001 From: Barsukovskii Mikhail Date: Fri, 22 Mar 2019 11:44:15 +0500 Subject: [PATCH 1/2] LWDEV-10476 Pre-trade control: configurable max value and calculated max volume --- .../engine/daos/azure/AzureAssetPair.java | 12 --- .../lykke/matching/engine/daos/AssetPair.kt | 2 - .../engine/daos/order/MaxOrderVolumeInfo.kt | 16 ++++ .../AzureDictionariesDatabaseAccessor.kt | 2 - .../engine/matching/MatchingEngine.kt | 63 ++++++------ .../order/process/LimitOrderProcessor.kt | 17 ++-- .../order/process/StopLimitOrderProcessor.kt | 4 +- .../engine/services/AssetOrderBook.kt | 10 +- .../engine/services/MarketOrderService.kt | 14 +-- .../validators/MarketOrderValidator.kt | 12 +-- .../business/LimitOrderBusinessValidator.kt | 2 + .../business/StopOrderBusinessValidator.kt | 6 +- .../impl/LimitOrderBusinessValidatorImpl.kt | 6 +- .../impl/StopOrderBusinessValidatorImpl.kt | 9 +- .../validators/common/OrderValidationUtils.kt | 17 ++++ .../impl/MarketOrderValidatorImpl.kt | 46 ++++++--- .../impl/LimitOrderInputValidatorImpl.kt | 21 ++-- .../engine/matching/MatchingEngineFeeTest.kt | 8 +- .../matching/MatchingEngineLimitOrderTest.kt | 80 ++++++---------- .../matching/MatchingEngineMarketOrderTest.kt | 95 ++++++++----------- .../engine/matching/MatchingEngineTest.kt | 5 +- .../engine/services/LimitOrderServiceTest.kt | 10 +- .../engine/services/MarketOrderServiceTest.kt | 14 ++- .../services/MultiLimitOrderServiceTest.kt | 10 +- .../engine/services/StopLimitOrderTest.kt | 1 - .../validator/MarketOrderValidatorTest.kt | 37 ++++---- .../LimitOrderBusinessValidatorTest.kt | 49 ++++++++-- .../StopOrderBusinessValidatorTest.kt | 63 ++++++++++++ 28 files changed, 388 insertions(+), 243 deletions(-) create mode 100644 src/main/kotlin/com/lykke/matching/engine/daos/order/MaxOrderVolumeInfo.kt create mode 100644 src/test/kotlin/com/lykke/matching/engine/services/validator/business/StopOrderBusinessValidatorTest.kt diff --git a/src/main/java/com/lykke/matching/engine/daos/azure/AzureAssetPair.java b/src/main/java/com/lykke/matching/engine/daos/azure/AzureAssetPair.java index f212b9dd3..161ed2a2f 100644 --- a/src/main/java/com/lykke/matching/engine/daos/azure/AzureAssetPair.java +++ b/src/main/java/com/lykke/matching/engine/daos/azure/AzureAssetPair.java @@ -11,7 +11,6 @@ public class AzureAssetPair extends TableServiceEntity { private int accuracy; private Double minVolume; private Double minInvertedVolume; - private Double maxVolume; private Double maxValue; private Double marketOrderPriceDeviationThreshold; @@ -23,7 +22,6 @@ public AzureAssetPair(String baseAssetId, int accuracy, Double minVolume, Double minInvertedVolume, - Double maxVolume, Double maxValue, Double marketOrderPriceDeviationThreshold) { super(ASSET_PAIR, baseAssetId + quotingAssetId); @@ -32,7 +30,6 @@ public AzureAssetPair(String baseAssetId, this.accuracy = accuracy; this.minVolume = minVolume; this.minInvertedVolume = minInvertedVolume; - this.maxVolume = maxVolume; this.maxValue = maxValue; this.marketOrderPriceDeviationThreshold = marketOrderPriceDeviationThreshold; } @@ -81,14 +78,6 @@ public void setMinInvertedVolume(Double minInvertedVolume) { this.minInvertedVolume = minInvertedVolume; } - public Double getMaxVolume() { - return maxVolume; - } - - public void setMaxVolume(Double maxVolume) { - this.maxVolume = maxVolume; - } - public Double getMaxValue() { return maxValue; } @@ -113,7 +102,6 @@ public String toString() { ", accuracy=" + accuracy + ", minVolume=" + minVolume + ", minInvertedVolume=" + minInvertedVolume + - ", maxVolume=" + maxVolume + ", maxValue=" + maxValue + ", marketOrderPriceDeviationThreshold=" + marketOrderPriceDeviationThreshold + ")"; diff --git a/src/main/kotlin/com/lykke/matching/engine/daos/AssetPair.kt b/src/main/kotlin/com/lykke/matching/engine/daos/AssetPair.kt index ed56ce35f..5b2e2e515 100644 --- a/src/main/kotlin/com/lykke/matching/engine/daos/AssetPair.kt +++ b/src/main/kotlin/com/lykke/matching/engine/daos/AssetPair.kt @@ -9,7 +9,6 @@ class AssetPair( val accuracy: Int, val minVolume: BigDecimal? = null, val minInvertedVolume: BigDecimal? = null, - val maxVolume: BigDecimal? = null, val maxValue: BigDecimal? = null, val marketOrderPriceDeviationThreshold: BigDecimal? = null ) { @@ -21,7 +20,6 @@ class AssetPair( "accuracy=$accuracy, " + "minVolume=$minVolume, " + "minInvertedVolume=$minInvertedVolume, " + - "maxVolume=$maxVolume, " + "maxValue=$maxValue, " + "marketOrderPriceDeviationThreshold=$marketOrderPriceDeviationThreshold" } diff --git a/src/main/kotlin/com/lykke/matching/engine/daos/order/MaxOrderVolumeInfo.kt b/src/main/kotlin/com/lykke/matching/engine/daos/order/MaxOrderVolumeInfo.kt new file mode 100644 index 000000000..fe2493804 --- /dev/null +++ b/src/main/kotlin/com/lykke/matching/engine/daos/order/MaxOrderVolumeInfo.kt @@ -0,0 +1,16 @@ +package com.lykke.matching.engine.daos.order + +import com.lykke.matching.engine.utils.NumberUtils +import java.math.BigDecimal + +class MaxOrderVolumeInfo(private val maxValue: BigDecimal, + private val midPrice: BigDecimal) { + + val maxVolume: BigDecimal = NumberUtils.divideWithMaxScale(maxValue, midPrice) + + override fun toString(): String { + return "maxValue=${NumberUtils.roundForPrint(maxValue)}, " + + "midPrice=${NumberUtils.roundForPrint(midPrice)}, " + + "maxVolume=${NumberUtils.roundForPrint(maxVolume)}" + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/lykke/matching/engine/database/azure/AzureDictionariesDatabaseAccessor.kt b/src/main/kotlin/com/lykke/matching/engine/database/azure/AzureDictionariesDatabaseAccessor.kt index d0cf6e90b..f59dee747 100644 --- a/src/main/kotlin/com/lykke/matching/engine/database/azure/AzureDictionariesDatabaseAccessor.kt +++ b/src/main/kotlin/com/lykke/matching/engine/database/azure/AzureDictionariesDatabaseAccessor.kt @@ -34,7 +34,6 @@ class AzureDictionariesDatabaseAccessor(dictsConfig: String): DictionariesDataba asset.accuracy, asset.minVolume?.toBigDecimal(), asset.minInvertedVolume?.toBigDecimal(), - asset.maxVolume?.toBigDecimal(), asset.maxValue?.toBigDecimal(), asset.marketOrderPriceDeviationThreshold?.toBigDecimal()) } @@ -57,7 +56,6 @@ class AzureDictionariesDatabaseAccessor(dictsConfig: String): DictionariesDataba assetPair.accuracy, assetPair.minVolume?.toBigDecimal(), assetPair.minInvertedVolume?.toBigDecimal(), - assetPair.maxVolume?.toBigDecimal(), assetPair.maxValue?.toBigDecimal(), assetPair.marketOrderPriceDeviationThreshold?.toBigDecimal()) } diff --git a/src/main/kotlin/com/lykke/matching/engine/matching/MatchingEngine.kt b/src/main/kotlin/com/lykke/matching/engine/matching/MatchingEngine.kt index 91ad71d2f..cc255f242 100644 --- a/src/main/kotlin/com/lykke/matching/engine/matching/MatchingEngine.kt +++ b/src/main/kotlin/com/lykke/matching/engine/matching/MatchingEngine.kt @@ -21,6 +21,7 @@ import com.lykke.matching.engine.outgoing.messages.v2.builders.bigDecimalToStrin import com.lykke.matching.engine.outgoing.messages.v2.enums.TradeRole import com.lykke.matching.engine.services.GenericLimitOrderService import com.lykke.matching.engine.order.transaction.ExecutionContext +import com.lykke.matching.engine.services.validators.common.OrderValidationUtils import com.lykke.matching.engine.utils.NumberUtils import org.springframework.stereotype.Component import java.math.BigDecimal @@ -39,14 +40,16 @@ class MatchingEngine(private val genericLimitOrderService: GenericLimitOrderServ } fun match(originOrder: Order, - orderBook: PriorityBlockingQueue, - messageId: String, balance: BigDecimal? = null, priceDeviationThreshold: BigDecimal? = null, executionContext: ExecutionContext): MatchingResult { + val assetOrderBook = executionContext.orderBooksHolder.getChangedCopyOrOriginalOrderBook(originOrder.assetPairId) + val orderBook = assetOrderBook.getOrderBook(!originOrder.isBuySide()) + val balancesGetter = executionContext.walletOperationsProcessor val orderWrapper = CopyWrapper(originOrder) val order = orderWrapper.copy + val isMarketOrder = isMarketOrder(order) val assetPair = executionContext.assetPairsById[order.assetPairId]!! val availableBalance = balance ?: getBalance(order, assetPair, balancesGetter) val workingOrderBook = PriorityBlockingQueue(orderBook) @@ -83,11 +86,10 @@ class MatchingEngine(private val genericLimitOrderService: GenericLimitOrderServ val marketOrderTrades = LinkedList() - val limitOrdersReport = LimitOrdersReport(messageId) + val limitOrdersReport = LimitOrdersReport(executionContext.messageId) var totalLimitVolume = BigDecimal.ZERO var matchedWithZeroLatestTrade = false - if (checkOrderBook(order, workingOrderBook)) { while (getMarketBalance(availableBalances, order, asset) >= BigDecimal.ZERO && workingOrderBook.size > 0 && !NumberUtils.equalsWithDefaultDelta(remainingVolume, BigDecimal.ZERO) @@ -309,7 +311,6 @@ class MatchingEngine(private val genericLimitOrderService: GenericLimitOrderServ totalLimitVolume += (if (order.isStraight()) marketRoundedVolume else oppositeRoundedVolume).abs() matchedOrders.add(matchedLimitOrderCopyWrapper) } - } if (order.takePrice() == null && remainingVolume > BigDecimal.ZERO) { if (matchedWithZeroLatestTrade) { @@ -340,15 +341,22 @@ class MatchingEngine(private val genericLimitOrderService: GenericLimitOrderServ } val executionPrice = calculateExecutionPrice(order, assetPair, totalLimitPrice, totalVolume) - if (!checkMaxVolume(order, assetPair, executionPrice)) { - order.updateStatus(OrderStatus.InvalidVolume, now) - executionContext.info("Too large volume of market order (${order.externalId}): volume=${order.volume}, price=$executionPrice, maxVolume=${assetPair.maxVolume}, straight=${order.isStraight()}") - return MatchingResult(orderWrapper, cancelledLimitOrders) - } - if (!checkMaxValue(order, assetPair, executionPrice)) { - order.updateStatus(OrderStatus.InvalidValue, now) - executionContext.info("Too large value of market order (${order.externalId}): volume=${order.volume}, price=$executionPrice, maxValue=${assetPair.maxValue}, straight=${order.isStraight()}") - return MatchingResult(orderWrapper, cancelledLimitOrders) + if (isMarketOrder) { + val maxVolumeInfo = OrderValidationUtils.calculateMaxVolume(assetPair, assetOrderBook) + if (!checkMaxVolume(order, executionPrice, maxVolumeInfo?.maxVolume)) { + order.updateStatus(OrderStatus.InvalidVolume, now) + executionContext.info("Too large volume of market order (${order.externalId}): " + + "volume=${order.volume}, price=$executionPrice, " + + "straight=${order.isStraight()}, maxVolumeInfo: $maxVolumeInfo") + return MatchingResult(orderWrapper, cancelledLimitOrders) + } + if (!checkMaxValue(order, executionPrice, assetPair.maxValue)) { + order.updateStatus(OrderStatus.InvalidValue, now) + executionContext.info("Too large value of market order (${order.externalId}): " + + "volume=${order.volume}, price=$executionPrice, " + + "straight=${order.isStraight()}, maxValue=${assetPair.maxValue}") + return MatchingResult(orderWrapper, cancelledLimitOrders) + } } if (order.takePrice() == null && !checkExecutionPriceDeviation(order.isBuySide(), executionPrice, bestPrice, priceDeviationThreshold)) { order.updateStatus(OrderStatus.TooHighPriceDeviation, now) @@ -387,9 +395,6 @@ class MatchingEngine(private val genericLimitOrderService: GenericLimitOrderServ false) } - private fun checkOrderBook(order: Order, orderBook: PriorityBlockingQueue): Boolean = - orderBook.isEmpty() || orderBook.peek().assetPairId == order.assetPairId && orderBook.peek().isBuySide() != order.isBuySide() - private fun getCrossVolume(volume: BigDecimal, straight: Boolean, price: BigDecimal): BigDecimal { return if (straight) volume else NumberUtils.divideWithMaxScale(volume, price) } @@ -424,22 +429,22 @@ class MatchingEngine(private val genericLimitOrderService: GenericLimitOrderServ } private fun checkMaxVolume(order: Order, - assetPair: AssetPair, - executionPrice: BigDecimal): Boolean { + executionPrice: BigDecimal, + maxVolume: BigDecimal?): Boolean { return when { - order.takePrice() != null || assetPair.maxVolume == null -> true - order.isStraight() -> order.getAbsVolume() <= assetPair.maxVolume - else -> order.getAbsVolume() / executionPrice <= assetPair.maxVolume + maxVolume == null -> true + order.isStraight() -> order.getAbsVolume() <= maxVolume + else -> order.getAbsVolume() / executionPrice <= maxVolume } } private fun checkMaxValue(order: Order, - assetPair: AssetPair, - executionPrice: BigDecimal): Boolean { + executionPrice: BigDecimal, + maxValue: BigDecimal?): Boolean { return when { - order.takePrice() != null || assetPair.maxValue == null -> true - order.isStraight() -> order.getAbsVolume() * executionPrice <= assetPair.maxValue - else -> order.getAbsVolume() <= assetPair.maxValue + maxValue == null -> true + order.isStraight() -> order.getAbsVolume() * executionPrice <= maxValue + else -> order.getAbsVolume() <= maxValue } } @@ -476,4 +481,8 @@ class MatchingEngine(private val genericLimitOrderService: GenericLimitOrderServ } return result } + + private fun isMarketOrder(order: Order): Boolean { + return order.takePrice() == null + } } \ No newline at end of file diff --git a/src/main/kotlin/com/lykke/matching/engine/order/process/LimitOrderProcessor.kt b/src/main/kotlin/com/lykke/matching/engine/order/process/LimitOrderProcessor.kt index da66949f3..47fceaceb 100644 --- a/src/main/kotlin/com/lykke/matching/engine/order/process/LimitOrderProcessor.kt +++ b/src/main/kotlin/com/lykke/matching/engine/order/process/LimitOrderProcessor.kt @@ -1,6 +1,7 @@ package com.lykke.matching.engine.order.process import com.lykke.matching.engine.balance.BalanceException +import com.lykke.matching.engine.daos.AssetPair import com.lykke.matching.engine.daos.LimitOrder import com.lykke.matching.engine.daos.WalletOperation import com.lykke.matching.engine.daos.order.OrderTimeInForce @@ -45,14 +46,15 @@ class LimitOrderProcessor(private val limitOrderInputValidator: LimitOrderInputV if (preProcessorValidationResult != null && !preProcessorValidationResult.isValid) { return preProcessorValidationResult } + val assetPair = orderContext.executionContext.assetPairsById[orderContext.order.assetPairId] // fixme: input validator will be moved from the business thread after multilimit order context release - val inputValidationResult = performInputValidation(orderContext) - return if (!inputValidationResult.isValid) inputValidationResult else performBusinessValidation(orderContext) + val inputValidationResult = performInputValidation(orderContext, assetPair) + return if (!inputValidationResult.isValid) inputValidationResult else performBusinessValidation(orderContext, assetPair!!) } - private fun performInputValidation(orderContext: LimitOrderExecutionContext): OrderValidationResult { + private fun performInputValidation(orderContext: LimitOrderExecutionContext, + assetPair: AssetPair?): OrderValidationResult { val order = orderContext.order - val assetPair = orderContext.executionContext.assetPairsById[order.assetPairId] val baseAsset = assetPair?.let { orderContext.executionContext.assetsById[assetPair.baseAssetId] } try { limitOrderInputValidator.validateLimitOrder(applicationSettingsHolder.isTrustedClient(order.clientId), @@ -66,13 +68,15 @@ class LimitOrderProcessor(private val limitOrderInputValidator: LimitOrderInputV return OrderValidationResult(true) } - private fun performBusinessValidation(orderContext: LimitOrderExecutionContext): OrderValidationResult { + private fun performBusinessValidation(orderContext: LimitOrderExecutionContext, + assetPair: AssetPair): OrderValidationResult { val order = orderContext.order try { limitOrderBusinessValidator.performValidation(applicationSettingsHolder.isTrustedClient(order.clientId), order, orderContext.availableLimitAssetBalance!!, orderContext.limitVolume!!, + assetPair, orderContext.executionContext.orderBooksHolder.getChangedCopyOrOriginalOrderBook(order.assetPairId), orderContext.executionContext.date) } catch (e: OrderValidationException) { @@ -128,10 +132,7 @@ class LimitOrderProcessor(private val limitOrderInputValidator: LimitOrderInputV private fun matchOrder(orderContext: LimitOrderExecutionContext): ProcessedOrder { val executionContext = orderContext.executionContext val order = orderContext.order - val orderBook = executionContext.orderBooksHolder.getChangedCopyOrOriginalOrderBook(order.assetPairId) val matchingResult = matchingEngine.match(order, - orderBook.getOrderBook(!order.isBuySide()), - executionContext.messageId, orderContext.availableLimitAssetBalance!!, applicationSettingsHolder.limitOrderPriceDeviationThreshold(order.assetPairId), executionContext = executionContext) diff --git a/src/main/kotlin/com/lykke/matching/engine/order/process/StopLimitOrderProcessor.kt b/src/main/kotlin/com/lykke/matching/engine/order/process/StopLimitOrderProcessor.kt index cfe0e49e5..2c85d60f7 100644 --- a/src/main/kotlin/com/lykke/matching/engine/order/process/StopLimitOrderProcessor.kt +++ b/src/main/kotlin/com/lykke/matching/engine/order/process/StopLimitOrderProcessor.kt @@ -59,7 +59,9 @@ class StopLimitOrderProcessor(private val limitOrderInputValidator: LimitOrderIn stopOrderBusinessValidator.performValidation(calculateAvailableBalance(orderContext), orderContext.limitVolume, orderContext.order, - orderContext.executionContext.date) + orderContext.executionContext.date, + orderContext.executionContext.assetPairsById[orderContext.order.assetPairId]!!, + orderContext.executionContext.orderBooksHolder.getChangedCopyOrOriginalOrderBook(orderContext.order.assetPairId)) } catch (e: OrderValidationException) { return OrderValidationResult(false, false, e.message, e.orderStatus) } diff --git a/src/main/kotlin/com/lykke/matching/engine/services/AssetOrderBook.kt b/src/main/kotlin/com/lykke/matching/engine/services/AssetOrderBook.kt index 8d668a609..d2ab5f8c7 100644 --- a/src/main/kotlin/com/lykke/matching/engine/services/AssetOrderBook.kt +++ b/src/main/kotlin/com/lykke/matching/engine/services/AssetOrderBook.kt @@ -2,6 +2,7 @@ package com.lykke.matching.engine.services import com.lykke.matching.engine.daos.LimitOrder import com.lykke.matching.engine.services.utils.AbstractAssetOrderBook +import com.lykke.matching.engine.utils.NumberUtils import java.math.BigDecimal import java.util.Comparator import java.util.concurrent.PriorityBlockingQueue @@ -15,7 +16,7 @@ class AssetOrderBook(assetId: String): AbstractAssetOrderBook(assetId) { } result - }) + }) val BUY_COMPARATOR = Comparator({ o1, o2 -> var result = o2.price.compareTo(o1.price) @@ -40,6 +41,13 @@ class AssetOrderBook(assetId: String): AbstractAssetOrderBook(assetId) { fun getAskPrice() = askOrderBook.peek()?.price ?: BigDecimal.ZERO fun getBidPrice() = bidOrderBook.peek()?.price ?: BigDecimal.ZERO + fun getMidPrice(): BigDecimal? { + return if (!NumberUtils.equalsIgnoreScale(BigDecimal.ZERO, getAskPrice()) && !NumberUtils.equalsIgnoreScale(BigDecimal.ZERO, getBidPrice())) { + NumberUtils.divideWithMaxScale(getAskPrice() + getBidPrice(), BigDecimal.valueOf(2)) + } else + null + } + fun leadToNegativeSpread(order: LimitOrder): Boolean { val book = getOrderBook(!order.isBuySide()) if (book.isEmpty()) { diff --git a/src/main/kotlin/com/lykke/matching/engine/services/MarketOrderService.kt b/src/main/kotlin/com/lykke/matching/engine/services/MarketOrderService.kt index d15fc3616..34242042c 100644 --- a/src/main/kotlin/com/lykke/matching/engine/services/MarketOrderService.kt +++ b/src/main/kotlin/com/lykke/matching/engine/services/MarketOrderService.kt @@ -101,8 +101,14 @@ class MarketOrderService @Autowired constructor( feeInstruction, listOfFee(feeInstruction, feeInstructions)) try { - marketOrderValidator.performValidation(order, getOrderBook(order), feeInstruction, feeInstructions) + marketOrderValidator.performValidation(order, + genericLimitOrderService.getOrderBook(order.assetPairId), + feeInstruction, + feeInstructions) } catch (e: OrderValidationException) { + if (e.message.isNotEmpty()) { + LOGGER.info("Invalid market order (${order.externalId}, messageId: ${messageWrapper.messageId}): ${e.message}") + } order.updateStatus(e.orderStatus, now) sendErrorNotification(messageWrapper, order, now) writeErrorResponse(messageWrapper, order, e.message) @@ -121,8 +127,6 @@ class MarketOrderService @Autowired constructor( val marketOrderExecutionContext = MarketOrderExecutionContext(order, executionContext) val matchingResult = matchingEngine.match(order, - getOrderBook(order), - messageWrapper.messageId!!, priceDeviationThreshold = assetPair.marketOrderPriceDeviationThreshold ?: applicationSettingsHolder.marketOrderPriceDeviationThreshold(assetPair.assetPairId), executionContext = executionContext) marketOrderExecutionContext.matchingResult = matchingResult @@ -213,10 +217,6 @@ class MarketOrderService @Autowired constructor( } } - private fun getOrderBook(order: MarketOrder) = - genericLimitOrderService.getOrderBook(order.assetPairId).getOrderBook(!order.isBuySide()) - - private fun parse(array: ByteArray): ProtocolMessages.MarketOrder { return ProtocolMessages.MarketOrder.parseFrom(array) } diff --git a/src/main/kotlin/com/lykke/matching/engine/services/validators/MarketOrderValidator.kt b/src/main/kotlin/com/lykke/matching/engine/services/validators/MarketOrderValidator.kt index 65c7bcad0..5b91d4405 100644 --- a/src/main/kotlin/com/lykke/matching/engine/services/validators/MarketOrderValidator.kt +++ b/src/main/kotlin/com/lykke/matching/engine/services/validators/MarketOrderValidator.kt @@ -1,13 +1,13 @@ package com.lykke.matching.engine.services.validators -import com.lykke.matching.engine.daos.v2.FeeInstruction import com.lykke.matching.engine.daos.MarketOrder -import com.lykke.matching.engine.daos.LimitOrder import com.lykke.matching.engine.daos.fee.v2.NewFeeInstruction -import java.util.concurrent.PriorityBlockingQueue - +import com.lykke.matching.engine.daos.v2.FeeInstruction +import com.lykke.matching.engine.services.AssetOrderBook interface MarketOrderValidator { - fun performValidation(order: MarketOrder, orderBook: PriorityBlockingQueue, - feeInstruction: FeeInstruction?, feeInstructions: List?) + fun performValidation(order: MarketOrder, + orderBook: AssetOrderBook, + feeInstruction: FeeInstruction?, + feeInstructions: List?) } \ No newline at end of file diff --git a/src/main/kotlin/com/lykke/matching/engine/services/validators/business/LimitOrderBusinessValidator.kt b/src/main/kotlin/com/lykke/matching/engine/services/validators/business/LimitOrderBusinessValidator.kt index 47ee86987..418082caa 100644 --- a/src/main/kotlin/com/lykke/matching/engine/services/validators/business/LimitOrderBusinessValidator.kt +++ b/src/main/kotlin/com/lykke/matching/engine/services/validators/business/LimitOrderBusinessValidator.kt @@ -1,5 +1,6 @@ package com.lykke.matching.engine.services.validators.business +import com.lykke.matching.engine.daos.AssetPair import com.lykke.matching.engine.daos.LimitOrder import com.lykke.matching.engine.services.AssetOrderBook import java.math.BigDecimal @@ -10,6 +11,7 @@ interface LimitOrderBusinessValidator { order: LimitOrder, availableBalance: BigDecimal, limitVolume: BigDecimal, + assetPair: AssetPair, orderBook: AssetOrderBook, date: Date) } \ No newline at end of file diff --git a/src/main/kotlin/com/lykke/matching/engine/services/validators/business/StopOrderBusinessValidator.kt b/src/main/kotlin/com/lykke/matching/engine/services/validators/business/StopOrderBusinessValidator.kt index a320cc111..911bcc4fc 100644 --- a/src/main/kotlin/com/lykke/matching/engine/services/validators/business/StopOrderBusinessValidator.kt +++ b/src/main/kotlin/com/lykke/matching/engine/services/validators/business/StopOrderBusinessValidator.kt @@ -1,6 +1,8 @@ package com.lykke.matching.engine.services.validators.business +import com.lykke.matching.engine.daos.AssetPair import com.lykke.matching.engine.daos.LimitOrder +import com.lykke.matching.engine.services.AssetOrderBook import java.math.BigDecimal import java.util.* @@ -8,5 +10,7 @@ interface StopOrderBusinessValidator { fun performValidation(availableBalance: BigDecimal, limitVolume: BigDecimal, order: LimitOrder, - orderProcessingTime: Date) + orderProcessingTime: Date, + assetPair: AssetPair, + orderBook: AssetOrderBook) } \ No newline at end of file diff --git a/src/main/kotlin/com/lykke/matching/engine/services/validators/business/impl/LimitOrderBusinessValidatorImpl.kt b/src/main/kotlin/com/lykke/matching/engine/services/validators/business/impl/LimitOrderBusinessValidatorImpl.kt index 1b3d6e4a4..8ccc73eb6 100644 --- a/src/main/kotlin/com/lykke/matching/engine/services/validators/business/impl/LimitOrderBusinessValidatorImpl.kt +++ b/src/main/kotlin/com/lykke/matching/engine/services/validators/business/impl/LimitOrderBusinessValidatorImpl.kt @@ -1,5 +1,6 @@ package com.lykke.matching.engine.services.validators.business.impl +import com.lykke.matching.engine.daos.AssetPair import com.lykke.matching.engine.daos.LimitOrder import com.lykke.matching.engine.order.OrderStatus import com.lykke.matching.engine.services.AssetOrderBook @@ -12,9 +13,11 @@ import java.util.Date @Component class LimitOrderBusinessValidatorImpl: LimitOrderBusinessValidator { - override fun performValidation(isTrustedClient: Boolean, order: LimitOrder, + override fun performValidation(isTrustedClient: Boolean, + order: LimitOrder, availableBalance: BigDecimal, limitVolume: BigDecimal, + assetPair: AssetPair, orderBook: AssetOrderBook, date: Date) { @@ -26,6 +29,7 @@ class LimitOrderBusinessValidatorImpl: LimitOrderBusinessValidator { validatePreviousOrderNotFound(order) validateNotEnoughFounds(order) OrderValidationUtils.validateExpiration(order, date) + OrderValidationUtils.validateMaxVolume(order, assetPair, orderBook) } private fun validatePreviousOrderNotFound(order: LimitOrder) { diff --git a/src/main/kotlin/com/lykke/matching/engine/services/validators/business/impl/StopOrderBusinessValidatorImpl.kt b/src/main/kotlin/com/lykke/matching/engine/services/validators/business/impl/StopOrderBusinessValidatorImpl.kt index e3443c2e0..6e633808c 100644 --- a/src/main/kotlin/com/lykke/matching/engine/services/validators/business/impl/StopOrderBusinessValidatorImpl.kt +++ b/src/main/kotlin/com/lykke/matching/engine/services/validators/business/impl/StopOrderBusinessValidatorImpl.kt @@ -1,6 +1,8 @@ package com.lykke.matching.engine.services.validators.business.impl +import com.lykke.matching.engine.daos.AssetPair import com.lykke.matching.engine.daos.LimitOrder +import com.lykke.matching.engine.services.AssetOrderBook import com.lykke.matching.engine.services.validators.business.StopOrderBusinessValidator import com.lykke.matching.engine.services.validators.common.OrderValidationUtils import org.springframework.stereotype.Component @@ -8,12 +10,15 @@ import java.math.BigDecimal import java.util.* @Component -class StopOrderBusinessValidatorImpl: StopOrderBusinessValidator { +class StopOrderBusinessValidatorImpl : StopOrderBusinessValidator { override fun performValidation(availableBalance: BigDecimal, limitVolume: BigDecimal, order: LimitOrder, - orderProcessingTime: Date) { + orderProcessingTime: Date, + assetPair: AssetPair, + orderBook: AssetOrderBook) { OrderValidationUtils.validateBalance(availableBalance, limitVolume) OrderValidationUtils.validateExpiration(order, orderProcessingTime) + OrderValidationUtils.validateMaxVolume(order, assetPair, orderBook) } } \ No newline at end of file diff --git a/src/main/kotlin/com/lykke/matching/engine/services/validators/common/OrderValidationUtils.kt b/src/main/kotlin/com/lykke/matching/engine/services/validators/common/OrderValidationUtils.kt index e6e5ee3cc..f3c3589f2 100644 --- a/src/main/kotlin/com/lykke/matching/engine/services/validators/common/OrderValidationUtils.kt +++ b/src/main/kotlin/com/lykke/matching/engine/services/validators/common/OrderValidationUtils.kt @@ -3,7 +3,9 @@ package com.lykke.matching.engine.services.validators.common import com.lykke.matching.engine.daos.AssetPair import com.lykke.matching.engine.daos.LimitOrder import com.lykke.matching.engine.daos.Order +import com.lykke.matching.engine.daos.order.MaxOrderVolumeInfo import com.lykke.matching.engine.order.OrderStatus +import com.lykke.matching.engine.services.AssetOrderBook import com.lykke.matching.engine.services.validators.impl.OrderValidationException import java.math.BigDecimal import java.util.Date @@ -27,5 +29,20 @@ class OrderValidationUtils { throw OrderValidationException(OrderStatus.Cancelled, "expired") } } + + fun validateMaxVolume(order: Order, + assetPair: AssetPair, + orderBook: AssetOrderBook) { + val maxVolumeInfo = calculateMaxVolume(assetPair, orderBook) ?: return + if (order.getAbsVolume() > maxVolumeInfo.maxVolume) { + throw OrderValidationException(OrderStatus.InvalidVolume, "volume is too large ($maxVolumeInfo)") + } + } + + fun calculateMaxVolume(assetPair: AssetPair, orderBook: AssetOrderBook): MaxOrderVolumeInfo? { + val midPrice = orderBook.getMidPrice() ?: return null + val maxValue = assetPair.maxValue ?: return null + return MaxOrderVolumeInfo(maxValue, midPrice) + } } } \ No newline at end of file diff --git a/src/main/kotlin/com/lykke/matching/engine/services/validators/impl/MarketOrderValidatorImpl.kt b/src/main/kotlin/com/lykke/matching/engine/services/validators/impl/MarketOrderValidatorImpl.kt index 155f0bf4c..8cf8c33ae 100644 --- a/src/main/kotlin/com/lykke/matching/engine/services/validators/impl/MarketOrderValidatorImpl.kt +++ b/src/main/kotlin/com/lykke/matching/engine/services/validators/impl/MarketOrderValidatorImpl.kt @@ -1,14 +1,16 @@ package com.lykke.matching.engine.services.validators.impl -import com.lykke.matching.engine.daos.v2.FeeInstruction -import com.lykke.matching.engine.daos.MarketOrder +import com.lykke.matching.engine.daos.AssetPair import com.lykke.matching.engine.daos.LimitOrder +import com.lykke.matching.engine.daos.MarketOrder import com.lykke.matching.engine.daos.fee.v2.NewFeeInstruction +import com.lykke.matching.engine.daos.v2.FeeInstruction import com.lykke.matching.engine.fee.checkFee import com.lykke.matching.engine.holders.ApplicationSettingsHolder import com.lykke.matching.engine.holders.AssetsHolder import com.lykke.matching.engine.holders.AssetsPairsHolder import com.lykke.matching.engine.order.OrderStatus +import com.lykke.matching.engine.services.AssetOrderBook import com.lykke.matching.engine.services.validators.MarketOrderValidator import com.lykke.matching.engine.services.validators.common.OrderValidationUtils import com.lykke.matching.engine.utils.NumberUtils @@ -28,13 +30,17 @@ class MarketOrderValidatorImpl private val LOGGER = Logger.getLogger(MarketOrderValidatorImpl::class.java.name) } - override fun performValidation(order: MarketOrder, orderBook: PriorityBlockingQueue, - feeInstruction: FeeInstruction?, feeInstructions: List?) { + override fun performValidation(order: MarketOrder, + orderBook: AssetOrderBook, + feeInstruction: FeeInstruction?, + feeInstructions: List?) { isAssetKnown(order) isAssetEnabled(order) - isVolumeValid(order) + val assetPair = getAssetPair(order) + isVolumeValid(order, assetPair, orderBook) + validateMaxValueForNotStraight(order, assetPair) isFeeValid(feeInstruction, feeInstructions, order) - isOrderBookValid(order, orderBook) + isOrderBookValid(order, orderBook.getOrderBook(!order.isBuySide())) isVolumeAccuracyValid(order) isPriceAccuracyValid(order) } @@ -54,7 +60,9 @@ class MarketOrderValidatorImpl } } - private fun isVolumeValid(order: MarketOrder) { + private fun isVolumeValid(order: MarketOrder, + assetPair: AssetPair, + orderBook: AssetOrderBook) { if (NumberUtils.equalsIgnoreScale(BigDecimal.ZERO, order.volume)) { val message = "volume can not be equal to zero" LOGGER.info(message) @@ -65,15 +73,25 @@ class MarketOrderValidatorImpl LOGGER.info("Too small volume for $order") throw OrderValidationException(OrderStatus.TooSmallVolume) } + validateMaxVolumeForStraight(order, assetPair, orderBook) + } - val assetPair = getAssetPair(order) - if (order.isStraight() && assetPair.maxVolume != null && order.getAbsVolume() > assetPair.maxVolume) { - LOGGER.info("Too large volume for $order") - throw OrderValidationException(OrderStatus.InvalidVolume) + private fun validateMaxVolumeForStraight(order: MarketOrder, + assetPair: AssetPair, + orderBook: AssetOrderBook) { + if (!order.isStraight()) { + return + } + OrderValidationUtils.validateMaxVolume(order, assetPair, orderBook) + } + + private fun validateMaxValueForNotStraight(order: MarketOrder, assetPair: AssetPair) { + if (order.isStraight()) { + return } - if (!order.isStraight() && assetPair.maxValue != null && order.getAbsVolume() > assetPair.maxValue) { - LOGGER.info("Too large value for $order") - throw OrderValidationException(OrderStatus.InvalidValue) + val maxValue = assetPair.maxValue ?: return + if (order.getAbsVolume() > maxValue) { + throw OrderValidationException(OrderStatus.InvalidValue, "Too large value (maxValue=$maxValue)") } } diff --git a/src/main/kotlin/com/lykke/matching/engine/services/validators/input/impl/LimitOrderInputValidatorImpl.kt b/src/main/kotlin/com/lykke/matching/engine/services/validators/input/impl/LimitOrderInputValidatorImpl.kt index 1809a23b8..7396624e6 100644 --- a/src/main/kotlin/com/lykke/matching/engine/services/validators/input/impl/LimitOrderInputValidatorImpl.kt +++ b/src/main/kotlin/com/lykke/matching/engine/services/validators/input/impl/LimitOrderInputValidatorImpl.kt @@ -43,7 +43,6 @@ class LimitOrderInputValidatorImpl(val applicationSettingsHolder: ApplicationSet validateMaxValue(order, assetPair) validatePriceAccuracy(order, assetPair) validateVolumeAccuracy(order, baseAsset!!) - validateValue(order, assetPair) } override fun validateStopOrder(singleLimitOrderParsedData: SingleLimitOrderParsedData) { @@ -111,24 +110,18 @@ class LimitOrderInputValidatorImpl(val applicationSettingsHolder: ApplicationSet } } - private fun validateValue(order: LimitOrder, assetPair: AssetPair) { - if (assetPair.maxValue != null && order.getAbsVolume() * order.price > assetPair.maxValue) { - throw OrderValidationException(OrderStatus.InvalidValue, "value is too large") - } - } - private fun validateMaxValue(limitOrder: LimitOrder, assetPair: AssetPair) { - if (assetPair.maxVolume != null && limitOrder.getAbsVolume() > assetPair.maxVolume) { - throw OrderValidationException(OrderStatus.InvalidVolume, "volume is too large") + val maxValue = assetPair.maxValue ?: return + if (limitOrder.getAbsVolume() * limitOrder.price > maxValue) { + throw OrderValidationException(OrderStatus.InvalidValue, "value is too large (maxValue=$maxValue)") } } private fun validateStopOrderMaxValue(limitOrder: LimitOrder, assetPair: AssetPair) { - validateMaxValue(limitOrder, assetPair) - - if (assetPair.maxValue != null && (limitOrder.lowerLimitPrice != null && limitOrder.getAbsVolume() * limitOrder.lowerPrice!! > assetPair.maxValue - || limitOrder.upperLimitPrice != null && limitOrder.getAbsVolume() * limitOrder.upperPrice!! > assetPair.maxValue)) { - throw OrderValidationException(OrderStatus.InvalidValue, "value is too large") + val maxValue = assetPair.maxValue ?: return + if ((limitOrder.lowerLimitPrice != null && limitOrder.getAbsVolume() * limitOrder.lowerPrice!! > maxValue + || limitOrder.upperLimitPrice != null && limitOrder.getAbsVolume() * limitOrder.upperPrice!! > maxValue)) { + throw OrderValidationException(OrderStatus.InvalidValue, "value is too large (maxValue=$maxValue)") } } diff --git a/src/test/kotlin/com/lykke/matching/engine/matching/MatchingEngineFeeTest.kt b/src/test/kotlin/com/lykke/matching/engine/matching/MatchingEngineFeeTest.kt index a55e74e86..04c3b811f 100644 --- a/src/test/kotlin/com/lykke/matching/engine/matching/MatchingEngineFeeTest.kt +++ b/src/test/kotlin/com/lykke/matching/engine/matching/MatchingEngineFeeTest.kt @@ -50,7 +50,7 @@ class MatchingEngineFeeTest : MatchingEngineTest() { ) ) - val matchingResult = match(limitOrder, getOrderBook("EURUSD", true)) + val matchingResult = match(limitOrder) assertCashMovementsEquals( listOf( @@ -91,7 +91,7 @@ class MatchingEngineFeeTest : MatchingEngineTest() { ) ) - val matchingResult = match(limitOrder, getOrderBook("EURUSD", false)) + val matchingResult = match(limitOrder) assertCashMovementsEquals( listOf( @@ -132,7 +132,7 @@ class MatchingEngineFeeTest : MatchingEngineTest() { ) ) - val matchingResult = match(limitOrder, getOrderBook("EURUSD", true)) + val matchingResult = match(limitOrder) assertCashMovementsEquals( listOf( @@ -173,7 +173,7 @@ class MatchingEngineFeeTest : MatchingEngineTest() { ) ) - val matchingResult = match(limitOrder, getOrderBook("EURUSD", false)) + val matchingResult = match(limitOrder) assertCashMovementsEquals( listOf( diff --git a/src/test/kotlin/com/lykke/matching/engine/matching/MatchingEngineLimitOrderTest.kt b/src/test/kotlin/com/lykke/matching/engine/matching/MatchingEngineLimitOrderTest.kt index d246c1109..0556f56f6 100644 --- a/src/test/kotlin/com/lykke/matching/engine/matching/MatchingEngineLimitOrderTest.kt +++ b/src/test/kotlin/com/lykke/matching/engine/matching/MatchingEngineLimitOrderTest.kt @@ -27,7 +27,7 @@ class MatchingEngineLimitOrderTest : MatchingEngineTest() { @Test fun testMatchLimitOrderBuyWithEmptyOrderBook() { val limitOrder = buildLimitOrder(price = 1.2, volume = 100.0) - val matchingResult = match(limitOrder, getOrderBook("EURUSD", false)) + val matchingResult = match(limitOrder) assertLimitOrderMatchingResult(matchingResult, status = OrderStatus.InOrderBook) } @@ -35,28 +35,17 @@ class MatchingEngineLimitOrderTest : MatchingEngineTest() { @Test fun testMatchLimitOrderSellWithEmptyOrderBook() { val limitOrder = buildLimitOrder(clientId = "Client2", price = 1.2, volume = -100.0) - val matchingResult = match(limitOrder, getOrderBook("EURUSD", true)) + val matchingResult = match(limitOrder) assertLimitOrderMatchingResult(matchingResult, remainingVolume = BigDecimal.valueOf( -100.0), status = OrderStatus.InOrderBook) } - @Test - fun testMatchLimitOrderWithSameOrderBookSide() { - testBalanceHolderWrapper.updateBalance("Client2", "USD", 1000.0) - testOrderBookWrapper.addLimitOrder(buildLimitOrder(clientId = "Client2", price = 1.2, volume = 100.0)) - - val limitOrder = buildLimitOrder(price = 1.2, volume = 100.0) - val matchingResult = match(limitOrder, getOrderBook("EURUSD", true)) - - assertLimitOrderMatchingResult(matchingResult, orderBookSize = 1, status = OrderStatus.InOrderBook) - } - @Test fun testMatchNoLiquidityLimitOrderBuy() { testOrderBookWrapper.addLimitOrder(buildLimitOrder(clientId = "Client2", price = 1.3, volume = -100.0)) val limitOrder = buildLimitOrder(price = 1.2, volume = 100.0) - val matchingResult = match(limitOrder, getOrderBook("EURUSD", false)) + val matchingResult = match(limitOrder) assertLimitOrderMatchingResult(matchingResult, orderBookSize = 1, status = OrderStatus.InOrderBook) } @@ -66,29 +55,18 @@ class MatchingEngineLimitOrderTest : MatchingEngineTest() { testOrderBookWrapper.addLimitOrder(buildLimitOrder(price = 1.2, volume = 100.0)) val limitOrder = buildLimitOrder(clientId = "Client2", price = 1.3, volume = -100.0) - val matchingResult = match(limitOrder, getOrderBook("EURUSD", true)) + val matchingResult = match(limitOrder) assertLimitOrderMatchingResult(matchingResult, remainingVolume = BigDecimal.valueOf(-100.0), orderBookSize = 1, status = OrderStatus.InOrderBook) } - @Test - fun testMatchLimitOrderWithAnotherAssetPair() { - testBalanceHolderWrapper.updateBalance("Client2", "BTC", 100.0) - testOrderBookWrapper.addLimitOrder(buildLimitOrder(clientId = "Client2", assetId = "BTCUSD", price = 1.2, volume = -100.0)) - - val limitOrder = buildLimitOrder(price = 1.2, volume = 100.0) - val matchingResult = match(limitOrder, getOrderBook("BTCUSD", false)) - - assertLimitOrderMatchingResult(matchingResult, orderBookSize = 1, status = OrderStatus.InOrderBook) - } - @Test fun testMatchLimitOrderWithOwnLimitOrder() { testBalanceHolderWrapper.updateBalance("Client1", "EUR", 100.0) testOrderBookWrapper.addLimitOrder(buildLimitOrder(clientId = "Client1", price = 1.2, volume = -100.0)) val limitOrder = buildLimitOrder(price = 1.2, volume = 100.0) - val matchingResult = match(limitOrder, getOrderBook("EURUSD", false)) + val matchingResult = match(limitOrder) assertLimitOrderMatchingResult(matchingResult, skipSize = 1, status = OrderStatus.InOrderBook) assertEquals(1, getOrderBook("EURUSD", false).size) @@ -100,7 +78,7 @@ class MatchingEngineLimitOrderTest : MatchingEngineTest() { testOrderBookWrapper.addLimitOrder(buildLimitOrder(price = 1.2, volume = 100.0)) val limitOrder = buildLimitOrder(clientId = "Client2", price = 1.2, volume = -100.0) - val matchingResult = match(limitOrder, getOrderBook("EURUSD", true)) + val matchingResult = match(limitOrder) assertLimitOrderMatchingResult(matchingResult, cancelledSize = 1, remainingVolume = BigDecimal.valueOf(-100.0), status = OrderStatus.InOrderBook) } @@ -111,7 +89,7 @@ class MatchingEngineLimitOrderTest : MatchingEngineTest() { testOrderBookWrapper.addLimitOrder(buildLimitOrder(clientId = "Client2", price = 1.2, volume = -100.0)) val limitOrder = buildLimitOrder(price = 1.2, volume = 100.0) - val matchingResult = match(limitOrder, getOrderBook("EURUSD", false)) + val matchingResult = match(limitOrder) assertLimitOrderMatchingResult(matchingResult, cancelledSize = 1, status = OrderStatus.InOrderBook) } @@ -124,7 +102,7 @@ class MatchingEngineLimitOrderTest : MatchingEngineTest() { testOrderBookWrapper.addLimitOrder(buildLimitOrder(clientId = "Client2", price = 1.2, volume = -100.0)) val limitOrder = buildLimitOrder(price = 1.2, volume = 100.0) - val matchingResult = match(limitOrder, getOrderBook("EURUSD", false)) + val matchingResult = match(limitOrder) assertLimitOrderMatchingResult(matchingResult, cancelledSize = 1, status = OrderStatus.InOrderBook) } @@ -136,7 +114,7 @@ class MatchingEngineLimitOrderTest : MatchingEngineTest() { testOrderBookWrapper.addLimitOrder(buildLimitOrder(price = 1.2, volume = 100.0)) val limitOrder = buildLimitOrder(clientId = "Client2", price = 1.2, volume = -100.0) - val matchingResult = match(limitOrder, getOrderBook("EURUSD", true)) + val matchingResult = match(limitOrder) assertLimitOrderMatchingResult(matchingResult, cancelledSize = 1, remainingVolume = BigDecimal.valueOf(-100.0), status = OrderStatus.InOrderBook) } @@ -147,7 +125,7 @@ class MatchingEngineLimitOrderTest : MatchingEngineTest() { testOrderBookWrapper.addLimitOrder(buildLimitOrder(clientId = "Client2", price = 1.2, volume = -100.0)) val limitOrder = buildLimitOrder(price = 1.2, volume = 100.0) - val matchingResult = match(limitOrder, getOrderBook("EURUSD", false)) + val matchingResult = match(limitOrder) assertLimitOrderMatchingResult(matchingResult, status = OrderStatus.ReservedVolumeGreaterThanBalance, marketBalance = null) } @@ -158,7 +136,7 @@ class MatchingEngineLimitOrderTest : MatchingEngineTest() { testOrderBookWrapper.addLimitOrder(buildLimitOrder(price = 1.2, volume = 100.0)) val limitOrder = buildLimitOrder(clientId = "Client2", price = 1.2, volume = -100.0) - val matchingResult = match(limitOrder, getOrderBook("EURUSD", true)) + val matchingResult = match(limitOrder) assertLimitOrderMatchingResult(matchingResult, status = OrderStatus.ReservedVolumeGreaterThanBalance, marketBalance = null, remainingVolume = BigDecimal.valueOf(-100.0)) } @@ -170,7 +148,7 @@ class MatchingEngineLimitOrderTest : MatchingEngineTest() { testOrderBookWrapper.addLimitOrder(buildLimitOrder(clientId = "Client2", price = 1.2, volume = -100.0)) val limitOrder = buildLimitOrder(price = 1.2, volume = 100.0) - val matchingResult = match(limitOrder, getOrderBook("EURUSD", false)) + val matchingResult = match(limitOrder) assertLimitOrderMatchingResult(matchingResult, status = OrderStatus.ReservedVolumeGreaterThanBalance, marketBalance = null) } @@ -182,7 +160,7 @@ class MatchingEngineLimitOrderTest : MatchingEngineTest() { testOrderBookWrapper.addLimitOrder(buildLimitOrder(price = 1.2, volume = 100.0)) val limitOrder = buildLimitOrder(clientId = "Client2", price = 1.2, volume = -100.0) - val matchingResult = match(limitOrder, getOrderBook("EURUSD", true)) + val matchingResult = match(limitOrder) assertLimitOrderMatchingResult(matchingResult, status = OrderStatus.ReservedVolumeGreaterThanBalance, marketBalance = null, remainingVolume = BigDecimal.valueOf(-100.0)) } @@ -191,7 +169,7 @@ class MatchingEngineLimitOrderTest : MatchingEngineTest() { fun testMatchLimitOrderPriceDeviation() { testOrderBookWrapper.addLimitOrder(buildLimitOrder(price = 1.2, volume = 1.0)) val limitOrder = buildLimitOrder(clientId = "Client2", price = 1.1, volume = -100.0) - val matchingResult = match(limitOrder, getOrderBook("EURUSD", true), priceDeviationThreshold = BigDecimal.valueOf(0.08)) + val matchingResult = match(limitOrder, priceDeviationThreshold = BigDecimal.valueOf(0.08)) assertLimitOrderMatchingResult(matchingResult, status = OrderStatus.TooHighPriceDeviation, marketBalance = null, remainingVolume = BigDecimal.valueOf(-100.0)) } @@ -200,7 +178,7 @@ class MatchingEngineLimitOrderTest : MatchingEngineTest() { testOrderBookWrapper.addLimitOrder(buildLimitOrder(uid = "uncompleted", clientId = "Client2", price = 1.19, volume = -100.0, reservedVolume = 100.0)) val limitOrder = buildLimitOrder(price = 1.21, volume = 91.1) - val matchingResult = match(limitOrder, getOrderBook("EURUSD", false)) + val matchingResult = match(limitOrder) assertLimitOrderMatchingResult(matchingResult, status = OrderStatus.Matched, marketBalance = BigDecimal.valueOf(891.59), @@ -226,7 +204,7 @@ class MatchingEngineLimitOrderTest : MatchingEngineTest() { testOrderBookWrapper.addLimitOrder(buildLimitOrder(uid = "uncompleted", price = 1.21, volume = 108.1, reservedVolume = 130.81)) val limitOrder = buildLimitOrder(clientId = "Client2", price = 1.19, volume = -100.0) - val matchingResult = match(limitOrder, getOrderBook("EURUSD", true)) + val matchingResult = match(limitOrder) assertLimitOrderMatchingResult(matchingResult, status = OrderStatus.Matched, marketBalance = BigDecimal.valueOf(900.00), @@ -254,7 +232,7 @@ class MatchingEngineLimitOrderTest : MatchingEngineTest() { testOrderBookWrapper.addLimitOrder(buildLimitOrder(uid = "completed", clientId = "Client2", price = 1.19, volume = -89.1, reservedVolume = 89.1)) val limitOrder = buildLimitOrder(price = 1.21, volume = 91.1) - val matchingResult = match(limitOrder, getOrderBook("EURUSD", false)) + val matchingResult = match(limitOrder) assertLimitOrderMatchingResult(matchingResult, status = OrderStatus.Processing, marketBalance = BigDecimal.valueOf(893.97), @@ -293,7 +271,7 @@ class MatchingEngineLimitOrderTest : MatchingEngineTest() { testOrderBookWrapper.addLimitOrder(buildLimitOrder(uid = "completed", price = 1.21, volume = 91.1, reservedVolume = 110.24)) val limitOrder = buildLimitOrder(clientId = "Client2", price = 1.19, volume = -92.2) - val matchingResult = match(limitOrder, getOrderBook("EURUSD", true)) + val matchingResult = match(limitOrder) assertLimitOrderMatchingResult(matchingResult, status = OrderStatus.Processing, marketBalance = BigDecimal.valueOf(908.9), @@ -331,7 +309,7 @@ class MatchingEngineLimitOrderTest : MatchingEngineTest() { testOrderBookWrapper.addLimitOrder(buildLimitOrder(uid = "completed", clientId = "Client2", price = 1.2, volume = -100.0, reservedVolume = 100.0)) val limitOrder = buildLimitOrder(price = 1.2, volume = 100.0) - val matchingResult = match(limitOrder, getOrderBook("EURUSD", false)) + val matchingResult = match(limitOrder) assertLimitOrderMatchingResult(matchingResult, status = OrderStatus.Matched, marketBalance = BigDecimal.valueOf(880.0), remainingVolume = BigDecimal.ZERO, skipSize = 0, cancelledSize = 0, lkkTradesSize = 2, cashMovementsSize = 4, marketOrderTradesSize = 1, completedLimitOrdersSize = 1, @@ -346,7 +324,7 @@ class MatchingEngineLimitOrderTest : MatchingEngineTest() { testOrderBookWrapper.addLimitOrder(buildLimitOrder(uid = "completed", price = 1.2, volume = 100.0, reservedVolume = 120.0)) val limitOrder = buildLimitOrder(clientId = "Client2", price = 1.2, volume = -100.0) - val matchingResult = match(limitOrder, getOrderBook("EURUSD", true)) + val matchingResult = match(limitOrder) assertLimitOrderMatchingResult(matchingResult, status = OrderStatus.Matched, marketBalance = BigDecimal.valueOf(900.0), remainingVolume = BigDecimal.ZERO, skipSize = 0, cancelledSize = 0, lkkTradesSize = 2, cashMovementsSize = 4, marketOrderTradesSize = 1, completedLimitOrdersSize = 1, @@ -362,7 +340,7 @@ class MatchingEngineLimitOrderTest : MatchingEngineTest() { testOrderBookWrapper.addLimitOrder(buildLimitOrder(clientId = "Client2", price = 1.2, volume = -50.0, reservedVolume = 50.0)) val limitOrder = buildLimitOrder(price = 1.2, volume = 100.0) - val matchingResult = match(limitOrder, getOrderBook("EURUSD", false)) + val matchingResult = match(limitOrder) assertLimitOrderMatchingResult(matchingResult, status = OrderStatus.Matched, marketBalance = BigDecimal.valueOf(880.0), remainingVolume = BigDecimal.ZERO, skipSize = 0, cancelledSize = 0, lkkTradesSize = 4, cashMovementsSize = 8, marketOrderTradesSize = 2, completedLimitOrdersSize = 2, limitOrdersReportSize = 2) @@ -376,7 +354,7 @@ class MatchingEngineLimitOrderTest : MatchingEngineTest() { testOrderBookWrapper.addLimitOrder(buildLimitOrder(price = 1.2, volume = 50.0, reservedVolume = 60.0)) val limitOrder = buildLimitOrder(clientId = "Client2", price = 1.2, volume = -100.0) - val matchingResult = match(limitOrder, getOrderBook("EURUSD", true)) + val matchingResult = match(limitOrder) assertLimitOrderMatchingResult(matchingResult, status = OrderStatus.Matched, marketBalance = BigDecimal.valueOf(900.0), remainingVolume = BigDecimal.ZERO, skipSize = 0, cancelledSize = 0, lkkTradesSize = 4, cashMovementsSize = 8, marketOrderTradesSize = 2, completedLimitOrdersSize = 2, @@ -395,7 +373,7 @@ class MatchingEngineLimitOrderTest : MatchingEngineTest() { testOrderBookWrapper.addLimitOrder(buildLimitOrder(clientId = "Client4", price = 1.3, volume = -40.0, reservedVolume = 40.0)) val limitOrder = buildLimitOrder(price = 1.2, volume = 100.0) - val matchingResult = match(limitOrder, getOrderBook("EURUSD", false)) + val matchingResult = match(limitOrder) assertLimitOrderMatchingResult(matchingResult, status = OrderStatus.Processing, @@ -424,7 +402,7 @@ class MatchingEngineLimitOrderTest : MatchingEngineTest() { testOrderBookWrapper.addLimitOrder(buildLimitOrder(clientId = "Client1", price = 1.1, volume = 40.0, reservedVolume = 44.0)) val limitOrder = buildLimitOrder(clientId = "Client2", price = 1.2, volume = -100.0) - val matchingResult = match(limitOrder, getOrderBook("EURUSD", true)) + val matchingResult = match(limitOrder) assertLimitOrderMatchingResult(matchingResult, status = OrderStatus.Processing, @@ -459,7 +437,7 @@ class MatchingEngineLimitOrderTest : MatchingEngineTest() { val limitOrder = buildLimitOrder(clientId = "Client2", assetId = "BTCUSD", price = 6110.0, volume = 30.0) Thread.sleep(100) - val matchingResult = match(limitOrder, getOrderBook("BTCUSD", false)) + val matchingResult = match(limitOrder) assertLimitOrderMatchingResult(matchingResult, status = OrderStatus.Matched, marketBalance = BigDecimal.valueOf(6999.85), @@ -498,7 +476,7 @@ class MatchingEngineLimitOrderTest : MatchingEngineTest() { testOrderBookWrapper.addLimitOrder(buildLimitOrder(clientId = "Client3", assetId = "BTCUSD", volume = -0.1, price = 6110.0)) val limitOrder = buildLimitOrder(clientId = "Client2", assetId = "BTCUSD", price = 6110.0, volume = 30.0) - val matchingResult = match(limitOrder, getOrderBook("BTCUSD", false)) + val matchingResult = match(limitOrder) assertLimitOrderMatchingResult(matchingResult, status = OrderStatus.Matched, marketBalance = BigDecimal.valueOf(6999.80), @@ -523,7 +501,7 @@ class MatchingEngineLimitOrderTest : MatchingEngineTest() { testOrderBookWrapper.addLimitOrder(buildLimitOrder(clientId = "Client3", price = 1.30001, volume = -52.33, reservedVolume = 52.33)) val limitOrder = buildLimitOrder(price = 1.31, volume = 100.0) - val matchingResult = match(limitOrder, getOrderBook("EURUSD", false)) + val matchingResult = match(limitOrder) assertLimitOrderMatchingResult(matchingResult, status = OrderStatus.Matched, marketBalance = BigDecimal.valueOf(872.21), remainingVolume = BigDecimal.ZERO, skipSize = 0, cancelledSize = 0, lkkTradesSize = 4, cashMovementsSize = 8, marketOrderTradesSize = 2, completedLimitOrdersSize = 1, @@ -554,7 +532,7 @@ class MatchingEngineLimitOrderTest : MatchingEngineTest() { testOrderBookWrapper.addLimitOrder(buildLimitOrder(clientId = "Client2", assetId = "LKK1YLKK", volume = 5500.0, price = 1.0085)) val matchingResult = match(buildLimitOrder(clientId = "Client1", assetId = "LKK1YLKK", volume = -5495.03, price = 1.0082, - fees = buildLimitOrderFeeInstructions(type = FeeType.CLIENT_FEE, takerSize = 0.0009, targetClientId = "Client5")), getOrderBook("LKK1YLKK", true)) + fees = buildLimitOrderFeeInstructions(type = FeeType.CLIENT_FEE, takerSize = 0.0009, targetClientId = "Client5"))) assertLimitOrderMatchingResult(matchingResult, status = OrderStatus.Matched, marketBalance = BigDecimal.ZERO, remainingVolume = BigDecimal.ZERO, skipSize = 0, cancelledSize = 0, lkkTradesSize = 4, cashMovementsSize = 10, marketOrderTradesSize = 2, completedLimitOrdersSize = 1, @@ -569,7 +547,7 @@ class MatchingEngineLimitOrderTest : MatchingEngineTest() { testOrderBookWrapper.addLimitOrder(buildLimitOrder(clientId = "Client1", assetId = "BTCCHF", price = 0.231, volume = 1.0, reservedVolume = 0.24)) val limitOrder = buildLimitOrder(clientId = "Client2", assetId = "BTCCHF", price = 0.231, volume = -0.001) - val matchingResult = match(limitOrder, getOrderBook("BTCCHF", true)) + val matchingResult = match(limitOrder) assertLimitOrderMatchingResult(matchingResult, status = OrderStatus.InOrderBook, marketBalance = BigDecimal.valueOf(0.001), diff --git a/src/test/kotlin/com/lykke/matching/engine/matching/MatchingEngineMarketOrderTest.kt b/src/test/kotlin/com/lykke/matching/engine/matching/MatchingEngineMarketOrderTest.kt index bbe924913..00665853f 100644 --- a/src/test/kotlin/com/lykke/matching/engine/matching/MatchingEngineMarketOrderTest.kt +++ b/src/test/kotlin/com/lykke/matching/engine/matching/MatchingEngineMarketOrderTest.kt @@ -14,7 +14,6 @@ import org.springframework.test.annotation.DirtiesContext import org.springframework.test.context.junit4.SpringRunner import java.math.BigDecimal import kotlin.test.assertNotNull -import kotlin.test.assertTrue @RunWith(SpringRunner::class) @SpringBootTest(classes = [(TestApplicationContext::class), (MatchingEngineTest.Config::class)]) @@ -24,7 +23,7 @@ class MatchingEngineMarketOrderTest : MatchingEngineTest() { @Test fun testMatchMarketOrderBuyWithEmptyOrderBook() { val marketOrder = buildMarketOrder(volume = 100.0) - val matchingResult = match(marketOrder, getOrderBook("EURUSD", false)) + val matchingResult = match(marketOrder) assertMarketOrderMatchingResult(matchingResult) } @@ -32,7 +31,7 @@ class MatchingEngineMarketOrderTest : MatchingEngineTest() { @Test fun testMatchMarketOrderSellWithEmptyOrderBook() { val marketOrder = buildMarketOrder(volume = -100.0) - val matchingResult = match(marketOrder, getOrderBook("EURUSD", false)) + val matchingResult = match(marketOrder) assertMarketOrderMatchingResult(matchingResult) } @@ -40,7 +39,7 @@ class MatchingEngineMarketOrderTest : MatchingEngineTest() { @Test fun testMatchMarketOrderNotStraightBuyWithEmptyOrderBook() { val marketOrder = buildMarketOrder(volume = -100.0, straight = false) - val matchingResult = match(marketOrder, getOrderBook("EURUSD", false)) + val matchingResult = match(marketOrder) assertMarketOrderMatchingResult(matchingResult) } @@ -48,7 +47,7 @@ class MatchingEngineMarketOrderTest : MatchingEngineTest() { @Test fun testMatchMarketOrderNotStraightSellWithEmptyOrderBook() { val marketOrder = buildMarketOrder(volume = 100.0, straight = false) - val matchingResult = match(marketOrder, getOrderBook("EURUSD", true)) + val matchingResult = match(marketOrder) assertMarketOrderMatchingResult(matchingResult) } @@ -59,7 +58,7 @@ class MatchingEngineMarketOrderTest : MatchingEngineTest() { testOrderBookWrapper.addLimitOrder(buildLimitOrder(clientId = "Client2", price = 1.2, volume = 100.0)) val marketOrder = buildMarketOrder(volume = 100.0) - val matchingResult = match(marketOrder, getOrderBook("EURUSD", true)) + val matchingResult = match(marketOrder) assertMarketOrderMatchingResult(matchingResult) } @@ -70,7 +69,7 @@ class MatchingEngineMarketOrderTest : MatchingEngineTest() { testOrderBookWrapper.addLimitOrder(buildLimitOrder(clientId = "Client2", price = 1.2, volume = 100.0)) val marketOrder = buildMarketOrder(volume = -120.0, straight = false) - val matchingResult = match(marketOrder, getOrderBook("EURUSD", true)) + val matchingResult = match(marketOrder) assertMarketOrderMatchingResult(matchingResult) } @@ -80,7 +79,7 @@ class MatchingEngineMarketOrderTest : MatchingEngineTest() { testOrderBookWrapper.addLimitOrder(buildLimitOrder(clientId = "Client2", price = 1.3, volume = -100.0)) val marketOrder = buildMarketOrder(volume = 100.01) - val matchingResult = match(marketOrder, getOrderBook("EURUSD", false)) + val matchingResult = match(marketOrder) assertMarketOrderMatchingResult(matchingResult) } @@ -90,7 +89,7 @@ class MatchingEngineMarketOrderTest : MatchingEngineTest() { testOrderBookWrapper.addLimitOrder(buildLimitOrder(clientId = "Client2", price = 1.3, volume = -100.0)) val marketOrder = buildMarketOrder(volume = -130.01, straight = false) - val matchingResult = match(marketOrder, getOrderBook("EURUSD", false)) + val matchingResult = match(marketOrder) assertMarketOrderMatchingResult(matchingResult) } @@ -100,7 +99,7 @@ class MatchingEngineMarketOrderTest : MatchingEngineTest() { testOrderBookWrapper.addLimitOrder(buildLimitOrder(price = 1.2, volume = 100.0)) val marketOrder = buildMarketOrder(clientId = "Client2", volume = -100.01) - val matchingResult = match(marketOrder, getOrderBook("EURUSD", true)) + val matchingResult = match(marketOrder) assertMarketOrderMatchingResult(matchingResult) } @@ -110,21 +109,7 @@ class MatchingEngineMarketOrderTest : MatchingEngineTest() { testOrderBookWrapper.addLimitOrder(buildLimitOrder(price = 1.2, volume = 100.0)) val marketOrder = buildMarketOrder(clientId = "Client2", volume = 120.01, straight = false) - val matchingResult = match(marketOrder, getOrderBook("EURUSD", true)) - - assertMarketOrderMatchingResult(matchingResult) - } - - @Test - fun testMatchNoLiquidityMarketOrderNotStraightSell2() { - testBalanceHolderWrapper.updateBalance("Client2", "BTC", 0.000028) - - testOrderBookWrapper.addLimitOrder(buildLimitOrder(clientId = "Client1", assetId = "BTCUSD", price = 1000.0, volume = 0.00001)) - testOrderBookWrapper.addLimitOrder(buildLimitOrder(clientId = "Client1", assetId = "BTCUSD", price = 1000.0, volume = 0.000009)) - testOrderBookWrapper.addLimitOrder(buildLimitOrder(clientId = "Client1", assetId = "BTCUSD", price = 1000.0, volume = 0.000009)) - - val marketOrder = buildMarketOrder(clientId = "Client2", assetId = "BTCUSD", volume = 0.02, straight = false) - val matchingResult = match(marketOrder, getOrderBook("EURUSD", true)) + val matchingResult = match(marketOrder) assertMarketOrderMatchingResult(matchingResult) } @@ -135,7 +120,7 @@ class MatchingEngineMarketOrderTest : MatchingEngineTest() { testOrderBookWrapper.addLimitOrder(buildLimitOrder(clientId = "Client2", assetId = "BTCUSD", price = 1.2, volume = -100.0)) val marketOrder = buildMarketOrder(volume = 100.0) - val matchingResult = match(marketOrder, getOrderBook("BTCUSD", false)) + val matchingResult = match(marketOrder) assertMarketOrderMatchingResult(matchingResult) } @@ -146,7 +131,7 @@ class MatchingEngineMarketOrderTest : MatchingEngineTest() { testOrderBookWrapper.addLimitOrder(buildLimitOrder(clientId = "Client1", price = 1.2, volume = -100.0)) val marketOrder = buildMarketOrder(volume = 100.0) - val matchingResult = match(marketOrder, getOrderBook("EURUSD", false)) + val matchingResult = match(marketOrder) assertMarketOrderMatchingResult(matchingResult) assertEquals(1, getOrderBook("EURUSD", false).size) @@ -158,7 +143,7 @@ class MatchingEngineMarketOrderTest : MatchingEngineTest() { testOrderBookWrapper.addLimitOrder(buildLimitOrder(clientId = "Client2", price = 1.2, volume = -100.0)) val marketOrder = buildMarketOrder(volume = 100.0) - val matchingResult = match(marketOrder, getOrderBook("EURUSD", false)) + val matchingResult = match(marketOrder) assertMarketOrderMatchingResult(matchingResult, cancelledSize = 1) } @@ -169,7 +154,7 @@ class MatchingEngineMarketOrderTest : MatchingEngineTest() { testOrderBookWrapper.addLimitOrder(buildLimitOrder(clientId = "Client2", price = 1.2, volume = -100.0)) val marketOrder = buildMarketOrder(volume = -120.0, straight = false) - val matchingResult = match(marketOrder, getOrderBook("EURUSD", false)) + val matchingResult = match(marketOrder) assertMarketOrderMatchingResult(matchingResult, cancelledSize = 1) } @@ -180,7 +165,7 @@ class MatchingEngineMarketOrderTest : MatchingEngineTest() { testOrderBookWrapper.addLimitOrder(buildLimitOrder(price = 1.2, volume = 100.0)) val marketOrder = buildMarketOrder(clientId = "Client2", volume = -100.0) - val matchingResult = match(marketOrder, getOrderBook("EURUSD", true)) + val matchingResult = match(marketOrder) assertMarketOrderMatchingResult(matchingResult, cancelledSize = 1) } @@ -192,7 +177,7 @@ class MatchingEngineMarketOrderTest : MatchingEngineTest() { testOrderBookWrapper.addLimitOrder(buildLimitOrder(clientId = "Client2", price = 1.2, volume = -100.0)) val marketOrder = buildMarketOrder(volume = 100.0) - val matchingResult = match(marketOrder, getOrderBook("EURUSD", false)) + val matchingResult = match(marketOrder) assertMarketOrderMatchingResult(matchingResult, cancelledSize = 1) } @@ -204,7 +189,7 @@ class MatchingEngineMarketOrderTest : MatchingEngineTest() { testOrderBookWrapper.addLimitOrder(buildLimitOrder(price = 1.2, volume = 100.0)) val marketOrder = buildMarketOrder(clientId = "Client2", volume = -100.0) - val matchingResult = match(marketOrder, getOrderBook("EURUSD", true)) + val matchingResult = match(marketOrder) assertMarketOrderMatchingResult(matchingResult, cancelledSize = 1) } @@ -215,7 +200,7 @@ class MatchingEngineMarketOrderTest : MatchingEngineTest() { testOrderBookWrapper.addLimitOrder(buildLimitOrder(clientId = "Client2", price = 1.2, volume = -100.0)) val marketOrder = buildMarketOrder(volume = 100.0) - val matchingResult = match(marketOrder, getOrderBook("EURUSD", false)) + val matchingResult = match(marketOrder) assertMarketOrderMatchingResult(matchingResult, status = OrderStatus.NotEnoughFunds, marketBalance = null) } @@ -226,7 +211,7 @@ class MatchingEngineMarketOrderTest : MatchingEngineTest() { testOrderBookWrapper.addLimitOrder(buildLimitOrder(price = 1.2, volume = 100.0)) val marketOrder = buildMarketOrder(clientId = "Client2", volume = -100.0) - val matchingResult = match(marketOrder, getOrderBook("EURUSD", true)) + val matchingResult = match(marketOrder) assertMarketOrderMatchingResult(matchingResult, status = OrderStatus.NotEnoughFunds, marketBalance = null) } @@ -237,7 +222,7 @@ class MatchingEngineMarketOrderTest : MatchingEngineTest() { testOrderBookWrapper.addLimitOrder(buildLimitOrder(price = 1.2, volume = 100.0)) val marketOrder = buildMarketOrder(clientId = "Client2", volume = 120.0, straight = false) - val matchingResult = match(marketOrder, getOrderBook("EURUSD", true)) + val matchingResult = match(marketOrder) assertMarketOrderMatchingResult(matchingResult, status = OrderStatus.NotEnoughFunds, marketBalance = null) } @@ -249,7 +234,7 @@ class MatchingEngineMarketOrderTest : MatchingEngineTest() { testOrderBookWrapper.addLimitOrder(buildLimitOrder(clientId = "Client2", price = 1.2, volume = -100.0)) val marketOrder = buildMarketOrder(volume = 100.0) - val matchingResult = match(marketOrder, getOrderBook("EURUSD", false)) + val matchingResult = match(marketOrder) assertMarketOrderMatchingResult(matchingResult, status = OrderStatus.NotEnoughFunds, marketBalance = null) } @@ -261,7 +246,7 @@ class MatchingEngineMarketOrderTest : MatchingEngineTest() { testOrderBookWrapper.addLimitOrder(buildLimitOrder(price = 1.2, volume = 100.0)) val marketOrder = buildMarketOrder(clientId = "Client2", volume = -100.0) - val matchingResult = match(marketOrder, getOrderBook("EURUSD", true)) + val matchingResult = match(marketOrder) assertMarketOrderMatchingResult(matchingResult, status = OrderStatus.NotEnoughFunds, marketBalance = null) } @@ -272,7 +257,7 @@ class MatchingEngineMarketOrderTest : MatchingEngineTest() { testOrderBookWrapper.addLimitOrder(buildLimitOrder(clientId = "Client2", price = 1.2, volume = -100.0)) val marketOrder = buildMarketOrder(volume = 100.0, reservedVolume = 120.01) - val matchingResult = match(marketOrder, getOrderBook("EURUSD", false)) + val matchingResult = match(marketOrder) assertMarketOrderMatchingResult(matchingResult, status = OrderStatus.ReservedVolumeGreaterThanBalance, marketBalance = null) } @@ -283,7 +268,7 @@ class MatchingEngineMarketOrderTest : MatchingEngineTest() { testOrderBookWrapper.addLimitOrder(buildLimitOrder(price = 1.2, volume = 100.0)) val marketOrder = buildMarketOrder(clientId = "Client2", volume = -100.0, reservedVolume = 100.01) - val matchingResult = match(marketOrder, getOrderBook("EURUSD", true)) + val matchingResult = match(marketOrder) assertMarketOrderMatchingResult(matchingResult, status = OrderStatus.ReservedVolumeGreaterThanBalance, marketBalance = null) } @@ -295,7 +280,7 @@ class MatchingEngineMarketOrderTest : MatchingEngineTest() { testOrderBookWrapper.addLimitOrder(buildLimitOrder(price = 1.0, volume = 1.0)) val marketOrder = buildMarketOrder(clientId = "Client2", volume = -3.0) - val matchingResult = match(marketOrder, getOrderBook("EURUSD", true), priceDeviationThreshold = BigDecimal.valueOf(0.08)) + val matchingResult = match(marketOrder, priceDeviationThreshold = BigDecimal.valueOf(0.08)) assertMarketOrderMatchingResult(matchingResult, status = OrderStatus.TooHighPriceDeviation, marketBalance = null) } @@ -306,7 +291,7 @@ class MatchingEngineMarketOrderTest : MatchingEngineTest() { testOrderBookWrapper.addLimitOrder(buildLimitOrder(price = 1.0, volume = 1.0)) val marketOrder = buildMarketOrder(clientId = "Client2", volume = 3.3, straight = false) - val matchingResult = match(marketOrder, getOrderBook("EURUSD", true), priceDeviationThreshold = BigDecimal.valueOf(0.08)) + val matchingResult = match(marketOrder, priceDeviationThreshold = BigDecimal.valueOf(0.08)) assertMarketOrderMatchingResult(matchingResult, status = OrderStatus.TooHighPriceDeviation, marketBalance = null) } @@ -317,7 +302,7 @@ class MatchingEngineMarketOrderTest : MatchingEngineTest() { testOrderBookWrapper.addLimitOrder(buildLimitOrder(uid = "uncompleted", clientId = "Client2", price = 1.19, volume = -100.0, reservedVolume = 100.0)) val marketOrder = buildMarketOrder(volume = 91.1) - val matchingResult = match(marketOrder, getOrderBook("EURUSD", false)) + val matchingResult = match(marketOrder) assertMarketOrderMatchingResult(matchingResult, status = OrderStatus.Matched, marketBalance = BigDecimal.valueOf(891.59), @@ -359,7 +344,7 @@ class MatchingEngineMarketOrderTest : MatchingEngineTest() { testOrderBookWrapper.addLimitOrder(buildLimitOrder(uid = "uncompleted", price = 1.21, volume = 108.1, reservedVolume = 130.81)) val marketOrder = buildMarketOrder(clientId = "Client2", volume = -100.0) - val matchingResult = match(marketOrder, getOrderBook("EURUSD", true)) + val matchingResult = match(marketOrder) assertMarketOrderMatchingResult(matchingResult, status = OrderStatus.Matched, marketBalance = BigDecimal.valueOf(900.00), @@ -385,7 +370,7 @@ class MatchingEngineMarketOrderTest : MatchingEngineTest() { testOrderBookWrapper.addLimitOrder(buildLimitOrder(uid = "uncompleted", clientId = "Client2", price = 1.19, volume = -100.0, reservedVolume = 100.0)) val marketOrder = buildMarketOrder(volume = -100.0, straight = false) - val matchingResult = match(marketOrder, getOrderBook("EURUSD", false)) + val matchingResult = match(marketOrder) assertMarketOrderMatchingResult(matchingResult, status = OrderStatus.Matched, marketBalance = BigDecimal.valueOf(900.00), @@ -411,7 +396,7 @@ class MatchingEngineMarketOrderTest : MatchingEngineTest() { testOrderBookWrapper.addLimitOrder(buildLimitOrder(uid = "uncompleted", price = 1.21, volume = 108.1, reservedVolume = 130.81)) val marketOrder = buildMarketOrder(clientId = "Client2", volume = 100.0, straight = false) - val matchingResult = match(marketOrder, getOrderBook("EURUSD", true)) + val matchingResult = match(marketOrder) assertMarketOrderMatchingResult(matchingResult, status = OrderStatus.Matched, marketBalance = BigDecimal.valueOf(917.3553), @@ -437,7 +422,7 @@ class MatchingEngineMarketOrderTest : MatchingEngineTest() { testOrderBookWrapper.addLimitOrder(buildLimitOrder(uid = "completed", clientId = "Client2", price = 1.2, volume = -100.0, reservedVolume = 100.0)) val marketOrder = buildMarketOrder(volume = 100.0) - val matchingResult = match(marketOrder, getOrderBook("EURUSD", false)) + val matchingResult = match(marketOrder) assertMarketOrderMatchingResult(matchingResult, status = OrderStatus.Matched, marketBalance = BigDecimal.valueOf(880.0), marketPrice = BigDecimal.valueOf(1.2), @@ -453,7 +438,7 @@ class MatchingEngineMarketOrderTest : MatchingEngineTest() { testOrderBookWrapper.addLimitOrder(buildLimitOrder(uid = "completed", clientId = "Client2", price = 1.2, volume = -100.0, reservedVolume = 100.0)) val marketOrder = buildMarketOrder(volume = -120.0, straight = false) - val matchingResult = match(marketOrder, getOrderBook("EURUSD", false)) + val matchingResult = match(marketOrder) assertMarketOrderMatchingResult(matchingResult, status = OrderStatus.Matched, marketBalance = BigDecimal.valueOf(880.0), marketPrice = BigDecimal.valueOf(1.2), lkkTradesSize = 2, cashMovementsSize = 4, marketOrderTradesSize = 1, completedLimitOrdersSize = 1, @@ -468,7 +453,7 @@ class MatchingEngineMarketOrderTest : MatchingEngineTest() { testOrderBookWrapper.addLimitOrder(buildLimitOrder(uid = "completed", price = 1.2, volume = 100.0, reservedVolume = 120.0)) val marketOrder = buildMarketOrder(clientId = "Client2", volume = -100.0) - val matchingResult = match(marketOrder, getOrderBook("EURUSD", true)) + val matchingResult = match(marketOrder) assertMarketOrderMatchingResult(matchingResult, status = OrderStatus.Matched, marketBalance = BigDecimal.valueOf(900.0), marketPrice = BigDecimal.valueOf(1.2), lkkTradesSize = 2, cashMovementsSize = 4, marketOrderTradesSize = 1, completedLimitOrdersSize = 1, @@ -482,7 +467,7 @@ class MatchingEngineMarketOrderTest : MatchingEngineTest() { testOrderBookWrapper.addLimitOrder(buildLimitOrder(uid = "completed", price = 1.2, volume = 100.0, reservedVolume = 120.0)) val marketOrder = buildMarketOrder(clientId = "Client2", volume = 120.0, straight = false) - val matchingResult = match(marketOrder, getOrderBook("EURUSD", true)) + val matchingResult = match(marketOrder) assertMarketOrderMatchingResult(matchingResult, status = OrderStatus.Matched, marketBalance = BigDecimal.valueOf(900.0), marketPrice = BigDecimal.valueOf(1.2), lkkTradesSize = 2, cashMovementsSize = 4, marketOrderTradesSize = 1, completedLimitOrdersSize = 1, @@ -497,7 +482,7 @@ class MatchingEngineMarketOrderTest : MatchingEngineTest() { testOrderBookWrapper.addLimitOrder(buildLimitOrder(clientId = "Client2", price = 1.2, volume = -50.0, reservedVolume = 50.0)) val marketOrder = buildMarketOrder(volume = 100.0) - val matchingResult = match(marketOrder, getOrderBook("EURUSD", false)) + val matchingResult = match(marketOrder) assertMarketOrderMatchingResult(matchingResult, status = OrderStatus.Matched, marketBalance = BigDecimal.valueOf(880.0), marketPrice = BigDecimal.valueOf(1.2), skipSize = 0, cancelledSize = 0, lkkTradesSize = 4, @@ -519,7 +504,7 @@ class MatchingEngineMarketOrderTest : MatchingEngineTest() { Thread.sleep(100) val marketOrder = buildMarketOrder(volume = 100.0) - val matchingResult = match(marketOrder, getOrderBook("EURUSD", false)) + val matchingResult = match(marketOrder) assertMarketOrderMatchingResult(matchingResult, status = OrderStatus.Matched, @@ -553,7 +538,7 @@ class MatchingEngineMarketOrderTest : MatchingEngineTest() { testOrderBookWrapper.addLimitOrder(buildLimitOrder(clientId = "Client1", price = 1.1, volume = 40.0, reservedVolume = 44.0)) val marketOrder = buildMarketOrder(clientId = "Client2", volume = 122.0, straight = false) - val matchingResult = match(marketOrder, getOrderBook("EURUSD", true)) + val matchingResult = match(marketOrder) assertMarketOrderMatchingResult(matchingResult, status = OrderStatus.Matched, @@ -583,7 +568,7 @@ class MatchingEngineMarketOrderTest : MatchingEngineTest() { testOrderBookWrapper.addLimitOrder(buildLimitOrder(clientId = "Client1", assetId = "BTCUSD", volume = -0.00947867, price = 10550.0, reservedVolume = 0.00947867)) val marketOrder = buildMarketOrder(clientId = "Client3", assetId = "BTCUSD", volume = -100.0, straight = false) - match(marketOrder, getOrderBook("BTCUSD", false)) + match(marketOrder) val orders = testDatabaseAccessor.getOrders("BTCUSD", false).filter { it.clientId == "Client1" } assertEquals(1, orders.size) @@ -597,7 +582,7 @@ class MatchingEngineMarketOrderTest : MatchingEngineTest() { testBalanceHolderWrapper.updateBalance("Client4", "CHF", 1.0) val marketOrder = buildMarketOrder(clientId = "Client4", assetId = "BTCCHF", volume = -0.3772, straight = false) - val matchingResult = match(marketOrder, getOrderBook("BTCCHF", false)) + val matchingResult = match(marketOrder) assertEquals(1, matchingResult.marketOrderTrades.size) assertEquals("0.00041770", matchingResult.marketOrderTrades.first().limitVolume) @@ -613,7 +598,7 @@ class MatchingEngineMarketOrderTest : MatchingEngineTest() { testOrderBookWrapper.addLimitOrder(buildLimitOrder(assetId = "BTCCHF", price = 6007.991, volume = -0.01165173, clientId = "Client2")) val marketOrder = buildMarketOrder(clientId = "Client1", assetId = "BTCCHF", volume = 0.01665173) - val matchingResult = match(marketOrder, getOrderBook("BTCCHF", false)) + val matchingResult = match(marketOrder) assertMarketOrderMatchingResult(matchingResult, status = OrderStatus.NotEnoughFunds) } diff --git a/src/test/kotlin/com/lykke/matching/engine/matching/MatchingEngineTest.kt b/src/test/kotlin/com/lykke/matching/engine/matching/MatchingEngineTest.kt index f1632e387..913fa7e73 100644 --- a/src/test/kotlin/com/lykke/matching/engine/matching/MatchingEngineTest.kt +++ b/src/test/kotlin/com/lykke/matching/engine/matching/MatchingEngineTest.kt @@ -14,9 +14,9 @@ import com.lykke.matching.engine.holders.BalancesHolder import com.lykke.matching.engine.messages.MessageType import com.lykke.matching.engine.order.OrderStatus import com.lykke.matching.engine.order.utils.TestOrderBookWrapper -import com.lykke.matching.engine.services.GenericLimitOrderService import com.lykke.matching.engine.order.transaction.ExecutionContext import com.lykke.matching.engine.order.transaction.ExecutionContextFactory +import com.lykke.matching.engine.services.GenericLimitOrderService import org.junit.After import org.junit.Assert.assertEquals import com.lykke.matching.engine.utils.assertEquals @@ -109,11 +109,8 @@ abstract class MatchingEngineTest { } protected fun match(order: Order, - orderBook: PriorityBlockingQueue, priceDeviationThreshold: BigDecimal? = null): MatchingResult { return matchingEngine.match(order, - orderBook, - "test", priceDeviationThreshold = priceDeviationThreshold, executionContext = executionContext) } diff --git a/src/test/kotlin/com/lykke/matching/engine/services/LimitOrderServiceTest.kt b/src/test/kotlin/com/lykke/matching/engine/services/LimitOrderServiceTest.kt index 38f15c966..eb2bce572 100644 --- a/src/test/kotlin/com/lykke/matching/engine/services/LimitOrderServiceTest.kt +++ b/src/test/kotlin/com/lykke/matching/engine/services/LimitOrderServiceTest.kt @@ -1649,10 +1649,14 @@ class LimitOrderServiceTest : AbstractTest() { fun testOrderMaxVolume() { testBalanceHolderWrapper.updateBalance("Client1", "BTC", 1.1) testDictionariesDatabaseAccessor.addAssetPair(AssetPair("BTCUSD", "BTC", "USD", 8, - maxVolume = BigDecimal.valueOf(1.0))) + maxValue = BigDecimal.valueOf(5000.0))) assetPairsCache.update() - singleLimitOrderService.processMessage(messageBuilder.buildLimitOrderWrapper(buildLimitOrder(clientId = "Client1", assetId = "BTCUSD", volume = -1.1, price = 10000.0))) + // orderBook with midPrice = 5000.0 + testOrderBookWrapper.addLimitOrder(buildLimitOrder(clientId = "Client2", assetId = "BTCUSD", volume = -1.0, price = 5500.0)) + testOrderBookWrapper.addLimitOrder(buildLimitOrder(clientId = "Client2", assetId = "BTCUSD", volume = 1.0, price = 4500.0)) + + singleLimitOrderService.processMessage(messageBuilder.buildLimitOrderWrapper(buildLimitOrder(clientId = "Client1", assetId = "BTCUSD", volume = -1.1, price = 4000.0))) assertEquals(1, clientsEventsQueue.size) val event = clientsEventsQueue.poll() as ExecutionEvent @@ -1661,7 +1665,7 @@ class LimitOrderServiceTest : AbstractTest() { assertEquals(OutgoingOrderStatus.REJECTED, eventOrder.status) assertEquals(OrderRejectReason.INVALID_VOLUME, eventOrder.rejectReason) - assertOrderBookSize("BTCUSD", false, 0) + assertOrderBookSize("BTCUSD", false, 1) } @Test diff --git a/src/test/kotlin/com/lykke/matching/engine/services/MarketOrderServiceTest.kt b/src/test/kotlin/com/lykke/matching/engine/services/MarketOrderServiceTest.kt index cf29e019e..4df578410 100644 --- a/src/test/kotlin/com/lykke/matching/engine/services/MarketOrderServiceTest.kt +++ b/src/test/kotlin/com/lykke/matching/engine/services/MarketOrderServiceTest.kt @@ -797,10 +797,17 @@ class MarketOrderServiceTest : AbstractTest() { @Test fun testStraightOrderMaxVolume() { testBalanceHolderWrapper.updateBalance("Client1", "BTC", 1.1) + testBalanceHolderWrapper.updateBalance("Client2", "BTC", 1.0) + testBalanceHolderWrapper.updateBalance("Client2", "USD", 20000.0) testDictionariesDatabaseAccessor.addAssetPair(AssetPair("BTCUSD", "BTC", "USD", 8, - maxVolume = BigDecimal.valueOf(1.0))) + maxValue = BigDecimal.valueOf(10000))) assetPairsCache.update() + // orderBook with midPrice=10000 + testOrderBookWrapper.addLimitOrder(buildLimitOrder(clientId = "Client2", assetId = "BTCUSD", volume = -1.0, price = 10001.0)) + testOrderBookWrapper.addLimitOrder(buildLimitOrder(clientId = "Client2", assetId = "BTCUSD", volume = 1.0, price = 9999.0)) + testOrderBookWrapper.addLimitOrder(buildLimitOrder(clientId = "Client2", assetId = "BTCUSD", volume = 1.0, price = 9998.0)) + marketOrderService.processMessage(buildMarketOrderWrapper(buildMarketOrder(clientId = "Client1", assetId = "BTCUSD", volume = -1.1))) assertEquals(1, clientsEventsQueue.size) @@ -814,11 +821,14 @@ class MarketOrderServiceTest : AbstractTest() { @Test fun testNotStraightOrderMaxVolume() { testBalanceHolderWrapper.updateBalance("Client1", "BTC", 1.1) + testBalanceHolderWrapper.updateBalance("Client2", "BTC", 1.0) testBalanceHolderWrapper.updateBalance("Client2", "USD", 11000.0) testDictionariesDatabaseAccessor.addAssetPair(AssetPair("BTCUSD", "BTC", "USD", 8, - maxVolume = BigDecimal.valueOf(1.0))) + maxValue = BigDecimal.valueOf(11000))) assetPairsCache.update() + // orderBook with midPrice=11000 + testOrderBookWrapper.addLimitOrder(buildLimitOrder(clientId = "Client2", assetId = "BTCUSD", volume = -1.0, price = 12000.0)) testOrderBookWrapper.addLimitOrder(buildLimitOrder(clientId = "Client2", assetId = "BTCUSD", volume = 1.1, price = 10000.0)) marketOrderService.processMessage(buildMarketOrderWrapper(buildMarketOrder(clientId = "Client1", assetId = "BTCUSD", volume = 11000.0, straight = false))) diff --git a/src/test/kotlin/com/lykke/matching/engine/services/MultiLimitOrderServiceTest.kt b/src/test/kotlin/com/lykke/matching/engine/services/MultiLimitOrderServiceTest.kt index 6782f05b7..58660fc9d 100644 --- a/src/test/kotlin/com/lykke/matching/engine/services/MultiLimitOrderServiceTest.kt +++ b/src/test/kotlin/com/lykke/matching/engine/services/MultiLimitOrderServiceTest.kt @@ -1331,12 +1331,16 @@ class MultiLimitOrderServiceTest: AbstractTest() { fun testOrderMaxVolume() { testBalanceHolderWrapper.updateBalance("Client1", "BTC", 1.1) testDictionariesDatabaseAccessor.addAssetPair(AssetPair("BTCUSD", "BTC", "USD", 8, - maxVolume = BigDecimal.valueOf(1.0))) + maxValue = BigDecimal.valueOf(5000.0))) assetPairsCache.update() - multiLimitOrderService.processMessage(buildMultiLimitOrderWrapper("BTCUSD", "Client1", listOf(IncomingLimitOrder(-1.1, 10000.0)))) + // orderBook with midPrice = 5000.0 + testOrderBookWrapper.addLimitOrder(buildLimitOrder(clientId = "Client2", assetId = "BTCUSD", volume = -1.0, price = 5500.0)) + testOrderBookWrapper.addLimitOrder(buildLimitOrder(clientId = "Client2", assetId = "BTCUSD", volume = 1.0, price = 4500.0)) - assertOrderBookSize("BTCUSD", false, 0) + multiLimitOrderService.processMessage(buildMultiLimitOrderWrapper("BTCUSD", "Client1", listOf(IncomingLimitOrder(-1.1, 4000.0)))) + + assertOrderBookSize("BTCUSD", false, 1) } @Test diff --git a/src/test/kotlin/com/lykke/matching/engine/services/StopLimitOrderTest.kt b/src/test/kotlin/com/lykke/matching/engine/services/StopLimitOrderTest.kt index ebaecfc79..f4924c422 100644 --- a/src/test/kotlin/com/lykke/matching/engine/services/StopLimitOrderTest.kt +++ b/src/test/kotlin/com/lykke/matching/engine/services/StopLimitOrderTest.kt @@ -81,7 +81,6 @@ class StopLimitOrderTest : AbstractTest() { testBalanceHolderWrapper.updateReservedBalance("Client1", "USD", 0.0) testDictionariesDatabaseAccessor.addAssetPair(AssetPair("BTCUSD", "BTC", "USD", 6)) - testDictionariesDatabaseAccessor.addAssetPair(AssetPair("BTCEUR", "BTC", "USD", 6, maxValue = BigDecimal.valueOf(8000))) initServices() } diff --git a/src/test/kotlin/com/lykke/matching/engine/services/validator/MarketOrderValidatorTest.kt b/src/test/kotlin/com/lykke/matching/engine/services/validator/MarketOrderValidatorTest.kt index 8077327de..75eee6b46 100644 --- a/src/test/kotlin/com/lykke/matching/engine/services/validator/MarketOrderValidatorTest.kt +++ b/src/test/kotlin/com/lykke/matching/engine/services/validator/MarketOrderValidatorTest.kt @@ -25,7 +25,6 @@ import org.springframework.test.annotation.DirtiesContext import org.springframework.test.context.junit4.SpringRunner import java.math.BigDecimal import java.util.* -import java.util.concurrent.PriorityBlockingQueue import kotlin.test.assertEquals @RunWith(SpringRunner::class) @@ -34,11 +33,11 @@ import kotlin.test.assertEquals class MarketOrderValidatorTest { companion object { - val CLIENT_NAME = "Client" - val OPERATION_ID = "test" - val ASSET_PAIR_ID = "EURUSD" - val BASE_ASSET_ID = "EUR" - val QUOTING_ASSET_ID = "USD" + private const val CLIENT_NAME = "Client" + private const val OPERATION_ID = "test" + private const val ASSET_PAIR_ID = "EURUSD" + private const val BASE_ASSET_ID = "EUR" + private const val QUOTING_ASSET_ID = "USD" } @TestConfiguration @@ -85,7 +84,7 @@ class MarketOrderValidatorTest { //when try { - marketOrderValidator.performValidation(order, getOrderBook(order.isBuySide()), NewFeeInstruction.create(getFeeInstruction()), null) + marketOrderValidator.performValidation(order, getOrderBook(), NewFeeInstruction.create(getFeeInstruction()), null) } catch (e: OrderValidationException) { assertEquals(OrderStatus.UnknownAsset, e.orderStatus) throw e @@ -102,7 +101,7 @@ class MarketOrderValidatorTest { //when try { - marketOrderValidator.performValidation(order, getOrderBook(order.isBuySide()), NewFeeInstruction.create(getFeeInstruction()), null) + marketOrderValidator.performValidation(order, getOrderBook(), NewFeeInstruction.create(getFeeInstruction()), null) } catch (e: OrderValidationException) { assertEquals(OrderStatus.DisabledAsset, e.orderStatus) throw e @@ -118,7 +117,7 @@ class MarketOrderValidatorTest { //when try { - marketOrderValidator.performValidation(order, getOrderBook(order.isBuySide()), NewFeeInstruction.create(getFeeInstruction()), null) + marketOrderValidator.performValidation(order, getOrderBook(), NewFeeInstruction.create(getFeeInstruction()), null) } catch (e: OrderValidationException) { assertEquals(OrderStatus.TooSmallVolume, e.orderStatus) throw e @@ -133,7 +132,7 @@ class MarketOrderValidatorTest { //when try { - marketOrderValidator.performValidation(order, getOrderBook(order.isBuySide()), + marketOrderValidator.performValidation(order, getOrderBook(), NewFeeInstruction.create(getInvalidFee()), null) } catch (e: OrderValidationException) { assertEquals(OrderStatus.InvalidFee, e.orderStatus) @@ -149,7 +148,7 @@ class MarketOrderValidatorTest { //when try { - marketOrderValidator.performValidation(order, AssetOrderBook(ASSET_PAIR_ID).getOrderBook(order.isBuySide()), + marketOrderValidator.performValidation(order, AssetOrderBook(ASSET_PAIR_ID), NewFeeInstruction.create(getFeeInstruction()), null) } catch (e: OrderValidationException) { assertEquals(OrderStatus.NoLiquidity, e.orderStatus) @@ -166,7 +165,7 @@ class MarketOrderValidatorTest { //when try { - marketOrderValidator.performValidation(order, getOrderBook(order.isBuySide()), NewFeeInstruction.create(getFeeInstruction()), null) + marketOrderValidator.performValidation(order, getOrderBook(), NewFeeInstruction.create(getFeeInstruction()), null) } catch (e: OrderValidationException) { assertEquals(OrderStatus.InvalidVolumeAccuracy, e.orderStatus) throw e @@ -182,7 +181,7 @@ class MarketOrderValidatorTest { //when try { - marketOrderValidator.performValidation(order, getOrderBook(order.isBuySide()), NewFeeInstruction.create(getFeeInstruction()), null) + marketOrderValidator.performValidation(order, getOrderBook(), NewFeeInstruction.create(getFeeInstruction()), null) } catch (e: OrderValidationException) { assertEquals(OrderStatus.InvalidPriceAccuracy, e.orderStatus) throw e @@ -196,7 +195,7 @@ class MarketOrderValidatorTest { val order = toMarketOrder(marketOrderBuilder.build()) //when - marketOrderValidator.performValidation(order, getOrderBook(order.isBuySide()), NewFeeInstruction.create(getFeeInstruction()), null) + marketOrderValidator.performValidation(order, getOrderBook(), NewFeeInstruction.create(getFeeInstruction()), null) } private fun toMarketOrder(message: ProtocolMessages.MarketOrder): MarketOrder { @@ -206,7 +205,7 @@ class MarketOrderValidatorTest { NewFeeInstruction.create(message.fee), listOf(NewFeeInstruction.create(message.fee))) } - private fun getOrderBook(isBuy: Boolean): PriorityBlockingQueue { + private fun getOrderBook(): AssetOrderBook { val assetOrderBook = AssetOrderBook(ASSET_PAIR_ID) val now = Date() assetOrderBook.addOrder(LimitOrder("test", "test", @@ -215,7 +214,13 @@ class MarketOrderValidatorTest { null, null, null, null, null, null, null, null, null, null, null, null)) - return assetOrderBook.getOrderBook(isBuy) + assetOrderBook.addOrder(LimitOrder("test", "test", + ASSET_PAIR_ID, CLIENT_NAME, BigDecimal.valueOf(-1.0), BigDecimal.valueOf(2.0), + OrderStatus.InOrderBook.name, now, now, now, BigDecimal.valueOf(1.0), now, BigDecimal.valueOf(1.0), + null, null, null, null, null, null, null, null, + null, null, null, null)) + + return assetOrderBook } private fun getDefaultMarketOrderBuilder(): ProtocolMessages.MarketOrder.Builder { diff --git a/src/test/kotlin/com/lykke/matching/engine/services/validator/business/LimitOrderBusinessValidatorTest.kt b/src/test/kotlin/com/lykke/matching/engine/services/validator/business/LimitOrderBusinessValidatorTest.kt index 97d3fa7f7..e4b55277f 100644 --- a/src/test/kotlin/com/lykke/matching/engine/services/validator/business/LimitOrderBusinessValidatorTest.kt +++ b/src/test/kotlin/com/lykke/matching/engine/services/validator/business/LimitOrderBusinessValidatorTest.kt @@ -1,5 +1,6 @@ package com.lykke.matching.engine.services.validator.business +import com.lykke.matching.engine.daos.AssetPair import com.lykke.matching.engine.daos.FeeType import com.lykke.matching.engine.daos.LimitOrder import com.lykke.matching.engine.daos.fee.v2.NewLimitOrderFeeInstruction @@ -9,6 +10,7 @@ import com.lykke.matching.engine.order.OrderStatus import com.lykke.matching.engine.services.AssetOrderBook import com.lykke.matching.engine.services.validators.business.impl.LimitOrderBusinessValidatorImpl import com.lykke.matching.engine.services.validators.impl.OrderValidationException +import com.lykke.matching.engine.utils.MessageBuilder.Companion.buildLimitOrder import org.junit.Assert.assertEquals import org.junit.Test import java.math.BigDecimal @@ -17,7 +19,15 @@ import java.util.* class LimitOrderBusinessValidatorTest { private companion object { - private const val ASSET_PAIR_ID = "BTCUSD" + private val ASSET_PAIR = AssetPair("AssetPair", + "Asset1", + "Asset2", + 2) + private val MAX_VALUE_ASSET_PAIR = AssetPair("AssetPair", + "Asset1", + "Asset2", + 2, + maxValue = BigDecimal.valueOf(5.0)) } @@ -28,13 +38,14 @@ class LimitOrderBusinessValidatorTest { try { //when - val orderBook = AssetOrderBook(ASSET_PAIR_ID) + val orderBook = getValidOrderBook() orderBook.addOrder(getLimitOrder(fee = getValidFee(), volume = BigDecimal.valueOf(-1.0))) limitOrderBusinessValidatorImpl.performValidation(true, getLimitOrder(fee = getValidFee()), BigDecimal.valueOf(10.0), BigDecimal.valueOf(9.0), + ASSET_PAIR, orderBook, Date()) } catch (e: OrderValidationException) { @@ -54,6 +65,7 @@ class LimitOrderBusinessValidatorTest { getLimitOrder(status = OrderStatus.NotFoundPrevious.name, fee = getValidFee()), BigDecimal.valueOf(12.0), BigDecimal.valueOf(11.0), + ASSET_PAIR, getValidOrderBook(), Date()) } catch (e: OrderValidationException) { @@ -74,6 +86,7 @@ class LimitOrderBusinessValidatorTest { getLimitOrder(status = OrderStatus.NotEnoughFunds.name, fee = getValidFee()), BigDecimal.valueOf(12.0), BigDecimal.valueOf(11.0), + ASSET_PAIR, getValidOrderBook(), Date()) } catch (e: OrderValidationException) { @@ -93,13 +106,37 @@ class LimitOrderBusinessValidatorTest { getLimitOrder(fee = getValidFee()), BigDecimal.valueOf(12.0), BigDecimal.valueOf(11.0), + ASSET_PAIR, getValidOrderBook(), Date()) } - private fun getLimitOrder(fee: LimitOrderFeeInstruction?, + @Test(expected = OrderValidationException::class) + fun testMaxVolume() { + val validator = LimitOrderBusinessValidatorImpl() + + // orderBook with midPrice=2.0 + val orderBook = getValidOrderBook() + orderBook.addOrder(buildLimitOrder(volume = -1.0, price = 3.0)) + orderBook.addOrder(buildLimitOrder(volume = 1.0, price = 1.0)) + + try { + validator.performValidation(false, + getLimitOrder(volume = BigDecimal.valueOf(-2.6), price = BigDecimal.valueOf(1.0)), + BigDecimal.ONE, + BigDecimal.ONE, + MAX_VALUE_ASSET_PAIR, + orderBook, + Date()) + } catch (e: OrderValidationException) { + assertEquals(OrderStatus.InvalidVolume, e.orderStatus) + throw e + } + } + + private fun getLimitOrder(fee: LimitOrderFeeInstruction? = null, fees: List? = null, - assetPair: String = ASSET_PAIR_ID, + assetPair: String = ASSET_PAIR.assetPairId, price: BigDecimal = BigDecimal.valueOf(1.0), volume: BigDecimal = BigDecimal.valueOf(1.0), status: String = OrderStatus.InOrderBook.name): LimitOrder { @@ -111,11 +148,11 @@ class LimitOrderBusinessValidatorTest { childOrderExternalId = null) } - fun getValidFee(): LimitOrderFeeInstruction { + private fun getValidFee(): LimitOrderFeeInstruction { return LimitOrderFeeInstruction(FeeType.NO_FEE, null, null, null, null, null, null) } private fun getValidOrderBook(): AssetOrderBook { - return AssetOrderBook(ASSET_PAIR_ID) + return AssetOrderBook(ASSET_PAIR.assetPairId) } } \ No newline at end of file diff --git a/src/test/kotlin/com/lykke/matching/engine/services/validator/business/StopOrderBusinessValidatorTest.kt b/src/test/kotlin/com/lykke/matching/engine/services/validator/business/StopOrderBusinessValidatorTest.kt new file mode 100644 index 000000000..f963b3289 --- /dev/null +++ b/src/test/kotlin/com/lykke/matching/engine/services/validator/business/StopOrderBusinessValidatorTest.kt @@ -0,0 +1,63 @@ +package com.lykke.matching.engine.services.validator.business + +import com.lykke.matching.engine.daos.AssetPair +import com.lykke.matching.engine.daos.LimitOrder +import com.lykke.matching.engine.daos.order.LimitOrderType +import com.lykke.matching.engine.order.OrderStatus +import com.lykke.matching.engine.services.AssetOrderBook +import com.lykke.matching.engine.services.validators.business.impl.StopOrderBusinessValidatorImpl +import com.lykke.matching.engine.services.validators.impl.OrderValidationException +import com.lykke.matching.engine.utils.MessageBuilder.Companion.buildLimitOrder +import org.junit.Test +import java.math.BigDecimal +import java.util.Date +import kotlin.test.assertEquals + +class StopOrderBusinessValidatorTest { + + companion object { + private val MAX_VALUE_ASSET_PAIR = AssetPair("AssetPair", + "Asset1", + "Asset2", + 2, + maxValue = BigDecimal.valueOf(5.0)) + } + + private val validator = StopOrderBusinessValidatorImpl() + + @Test(expected = OrderValidationException::class) + fun testMaxVolume() { + val stopOrder = buildStopOrder(volume = -2.6, lowerLimitPrice = 1.0, lowerPrice = 1.0) + + // orderBook with midPrice=2.0 + val orderBook = AssetOrderBook(MAX_VALUE_ASSET_PAIR.assetPairId) + orderBook.addOrder(buildLimitOrder(volume = -1.0, price = 3.0)) + orderBook.addOrder(buildLimitOrder(volume = 1.0, price = 1.0)) + + try { + validator.performValidation(BigDecimal.ONE, + BigDecimal.ONE, + stopOrder, + Date(), + MAX_VALUE_ASSET_PAIR, + orderBook) + } catch (e: OrderValidationException) { + assertEquals(OrderStatus.InvalidVolume, e.orderStatus) + throw e + } + } + + private fun buildStopOrder(volume: Double, + lowerLimitPrice: Double? = null, + lowerPrice: Double? = null, + upperLimitPrice: Double? = null, + upperPrice: Double? = null): LimitOrder { + return buildLimitOrder(type = LimitOrderType.STOP_LIMIT, + volume = volume, + lowerLimitPrice = lowerLimitPrice, + lowerPrice = lowerPrice, + upperLimitPrice = upperLimitPrice, + upperPrice = upperPrice) + } + +} \ No newline at end of file From b06b22d4f26214fbe32d50d452d09253e56398c6 Mon Sep 17 00:00:00 2001 From: Barsukovskii Mikhail Date: Mon, 15 Apr 2019 23:31:38 +0500 Subject: [PATCH 2/2] LWDEV-10476 log error in case of invalid market order --- .../lykke/matching/engine/services/MarketOrderService.kt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/kotlin/com/lykke/matching/engine/services/MarketOrderService.kt b/src/main/kotlin/com/lykke/matching/engine/services/MarketOrderService.kt index c6c9b8a99..3b9f9dc18 100644 --- a/src/main/kotlin/com/lykke/matching/engine/services/MarketOrderService.kt +++ b/src/main/kotlin/com/lykke/matching/engine/services/MarketOrderService.kt @@ -108,9 +108,9 @@ class MarketOrderService @Autowired constructor( feeInstruction, feeInstructions) } catch (e: OrderValidationException) { - if (e.message.isNotEmpty()) { - LOGGER.info("Invalid market order (${order.externalId}, messageId: ${messageWrapper.messageId}): ${e.message}") - } + val errorMessage = "Invalid market order (${order.externalId}, messageId: ${messageWrapper.messageId}): ${e.orderStatus}" + + (if (e.message.isNotEmpty()) ", ${e.message}" else "") + LOGGER.error(errorMessage) order.updateStatus(e.orderStatus, now) sendErrorNotification(messageWrapper, order, now) writeErrorResponse(messageWrapper, order, e.message) @@ -166,7 +166,7 @@ class MarketOrderService @Autowired constructor( matchingResultHandlingHelper.processWalletOperations(marketOrderExecutionContext) true } catch (e: BalanceException) { - order.updateStatus(OrderStatus.NotEnoughFunds, now) + order.updateStatus(NotEnoughFunds, now) marketOrderExecutionContext.executionContext.marketOrderWithTrades = MarketOrderWithTrades(messageWrapper.messageId!!, order) LOGGER.error("$order: Unable to process wallet operations after matching: ${e.message}") false