Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ public class AzureAssetPair extends TableServiceEntity {
private int accuracy;
private Double minVolume;
private Double minInvertedVolume;
private Double maxVolume;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is there option to configure max volume on back office ? if yes - we should create a ticket to remove it. Also, maybe we should perform dictionary table cleanup - remove unused max volume column?

private Double maxValue;
private Double marketOrderPriceDeviationThreshold;

Expand All @@ -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);
Expand All @@ -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;
}
Expand Down Expand Up @@ -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;
}
Expand All @@ -113,7 +102,6 @@ public String toString() {
", accuracy=" + accuracy +
", minVolume=" + minVolume +
", minInvertedVolume=" + minInvertedVolume +
", maxVolume=" + maxVolume +
", maxValue=" + maxValue +
", marketOrderPriceDeviationThreshold=" + marketOrderPriceDeviationThreshold +
")";
Expand Down
2 changes: 0 additions & 2 deletions src/main/kotlin/com/lykke/matching/engine/daos/AssetPair.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
) {
Expand All @@ -21,7 +20,6 @@ class AssetPair(
"accuracy=$accuracy, " +
"minVolume=$minVolume, " +
"minInvertedVolume=$minInvertedVolume, " +
"maxVolume=$maxVolume, " +
"maxValue=$maxValue, " +
"marketOrderPriceDeviationThreshold=$marketOrderPriceDeviationThreshold"
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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)}"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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())
}
Expand All @@ -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())
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,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
Expand All @@ -40,11 +41,12 @@ class MatchingEngine(private val genericLimitOrderService: GenericLimitOrderServ
}

fun match(originOrder: Order,
orderBook: PriorityBlockingQueue<LimitOrder>,
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
Expand Down Expand Up @@ -85,11 +87,10 @@ class MatchingEngine(private val genericLimitOrderService: GenericLimitOrderServ

val marketOrderTrades = LinkedList<TradeInfo>()

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)
Expand Down Expand Up @@ -316,7 +317,6 @@ class MatchingEngine(private val genericLimitOrderService: GenericLimitOrderServ
totalLimitVolume += (if (order.isStraight()) marketRoundedVolume else oppositeRoundedVolume).abs()
matchedOrders.add(matchedLimitOrderCopyWrapper)
}
}

if (isMarketOrder && remainingVolume > BigDecimal.ZERO) {
if (matchedWithZeroLatestTrade) {
Expand Down Expand Up @@ -347,15 +347,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 (isMarketOrder && !checkExecutionPriceDeviation(order.isBuySide(), executionPrice, bestPrice, priceDeviationThreshold)) {
order.updateStatus(OrderStatus.TooHighPriceDeviation, now)
Expand Down Expand Up @@ -394,9 +401,6 @@ class MatchingEngine(private val genericLimitOrderService: GenericLimitOrderServ
false)
}

private fun checkOrderBook(order: Order, orderBook: PriorityBlockingQueue<LimitOrder>): 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)
}
Expand Down Expand Up @@ -431,22 +435,22 @@ class MatchingEngine(private val genericLimitOrderService: GenericLimitOrderServ
}

private fun checkMaxVolume(order: Order,
assetPair: AssetPair,
executionPrice: BigDecimal): Boolean {
executionPrice: BigDecimal,
maxVolume: BigDecimal?): Boolean {
return when {
!isMarketOrder(order) || 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 {
!isMarketOrder(order) || 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
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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 {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why asset pair is moved to the parameter ?

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),
Expand All @@ -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,
orderContext.executionContext.getOrderBookTotalSize())
Expand Down Expand Up @@ -129,10 +133,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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ class StopLimitOrderProcessor(private val limitOrderInputValidator: LimitOrderIn
orderContext.limitVolume,
orderContext.order,
orderContext.executionContext.date,
orderContext.executionContext.assetPairsById[orderContext.order.assetPairId]!!,
orderContext.executionContext.orderBooksHolder.getChangedCopyOrOriginalOrderBook(orderContext.order.assetPairId),
orderContext.executionContext.getOrderBookTotalSize())
} catch (e: OrderValidationException) {
return OrderValidationResult(false, false, e.message, e.orderStatus)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.*
import java.util.concurrent.PriorityBlockingQueue
Expand Down Expand Up @@ -47,6 +48,13 @@ open class AssetOrderBook(assetId: String) : AbstractAssetOrderBook(assetId) {
fun getAskPrice() = askOrderBook.peek()?.price ?: BigDecimal.ZERO
fun getBidPrice() = bidOrderBook.peek()?.price ?: BigDecimal.ZERO

fun getMidPrice(): BigDecimal? {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add test for this method

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()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,8 +103,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) {
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)
Expand All @@ -122,8 +128,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
Expand Down Expand Up @@ -162,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
Expand Down Expand Up @@ -215,10 +219,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)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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<LimitOrder>,
feeInstruction: FeeInstruction?, feeInstructions: List<NewFeeInstruction>?)
fun performValidation(order: MarketOrder,
orderBook: AssetOrderBook,
feeInstruction: FeeInstruction?,
feeInstructions: List<NewFeeInstruction>?)
}
Loading