From ba00f8e27ed206c7182c4c81d651ad27bd6787ee Mon Sep 17 00:00:00 2001 From: Peyman Date: Sun, 26 Jun 2022 14:38:59 +0430 Subject: [PATCH 01/14] Add "scheduled" profile for accountant --- docker-compose.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/docker-compose.yml b/docker-compose.yml index 807557a65..ba0bf4dbb 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -195,6 +195,7 @@ services: image: ghcr.io/opexdev/accountant environment: - JAVA_OPTS=-Xmx256m -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005 + - SPRING_PROFILES_ACTIVE=scheduled - KAFKA_IP_PORT=kafka-1:29092,kafka-2:29092,kafka-3:29092 - CONSUL_HOST=consul - DB_IP_PORT=postgres-accountant From 777a3a444f0784dd4136688d5d75ccaa1012d1bc Mon Sep 17 00:00:00 2001 From: Ebrahim Hoseiny Fadae Date: Sun, 3 Jul 2022 15:51:04 +0430 Subject: [PATCH 02/14] Close #293, Fix bc gateway database rate limit error (#294) * bc-gateway: Reduce database calls when syncing deposits * bc-gateway: Add more logs --- .../core/service/WalletSyncServiceImpl.kt | 15 +++----- .../impl/AssignedAddressHandlerImpl.kt | 35 ++++++++----------- .../postgres/impl/CurrencyHandlerImpl.kt | 4 +-- 3 files changed, 22 insertions(+), 32 deletions(-) diff --git a/bc-gateway/bc-gateway-core/src/main/kotlin/co/nilin/opex/bcgateway/core/service/WalletSyncServiceImpl.kt b/bc-gateway/bc-gateway-core/src/main/kotlin/co/nilin/opex/bcgateway/core/service/WalletSyncServiceImpl.kt index cacf7ff34..2400ab198 100644 --- a/bc-gateway/bc-gateway-core/src/main/kotlin/co/nilin/opex/bcgateway/core/service/WalletSyncServiceImpl.kt +++ b/bc-gateway/bc-gateway-core/src/main/kotlin/co/nilin/opex/bcgateway/core/service/WalletSyncServiceImpl.kt @@ -11,10 +11,6 @@ import co.nilin.opex.bcgateway.core.spi.WalletProxy import co.nilin.opex.bcgateway.core.utils.LoggerDelegate import kotlinx.coroutines.async import kotlinx.coroutines.coroutineScope -import kotlinx.coroutines.flow.asFlow -import kotlinx.coroutines.flow.map -import kotlinx.coroutines.flow.mapNotNull -import kotlinx.coroutines.flow.toList import org.slf4j.Logger import org.springframework.stereotype.Service import org.springframework.transaction.annotation.Transactional @@ -32,15 +28,14 @@ class WalletSyncServiceImpl( @Transactional override suspend fun syncTransfers(transfers: List) = coroutineScope { + logger.debug("Received ${transfers.size} number of transfers") + val groupedByChain = currencyHandler.fetchAllImplementations().groupBy { it.chain.name } val deposits = transfers.map { async { coroutineScope { - val currencyImpl = async { - currencyHandler.findByChainAndTokenAddress(it.chain, it.tokenAddress) - ?: throw IllegalStateException("Currency implementation not found") - } - val uuid = async { assignedAddressHandler.findUuid(it.receiver.address, it.receiver.memo) } - uuid.await()?.let { it to currencyImpl.await() } + val currencyImpl = groupedByChain[it.chain]?.find { c -> c.tokenAddress == it.tokenAddress } + ?: throw IllegalStateException("Currency implementation not found") + assignedAddressHandler.findUuid(it.receiver.address, it.receiver.memo)?.let { it to currencyImpl } }?.let { (uuid, currencyImpl) -> sendDeposit(uuid, currencyImpl, it) logger.info("Deposit synced for $uuid on ${currencyImpl.currency.symbol} - to ${it.receiver.address}") diff --git a/bc-gateway/bc-gateway-ports/bc-gateway-persister-postgres/src/main/kotlin/co/nilin/opex/bcgateway/ports/postgres/impl/AssignedAddressHandlerImpl.kt b/bc-gateway/bc-gateway-ports/bc-gateway-persister-postgres/src/main/kotlin/co/nilin/opex/bcgateway/ports/postgres/impl/AssignedAddressHandlerImpl.kt index 010ae69ef..61fc0b9b6 100644 --- a/bc-gateway/bc-gateway-ports/bc-gateway-persister-postgres/src/main/kotlin/co/nilin/opex/bcgateway/ports/postgres/impl/AssignedAddressHandlerImpl.kt +++ b/bc-gateway/bc-gateway-ports/bc-gateway-persister-postgres/src/main/kotlin/co/nilin/opex/bcgateway/ports/postgres/impl/AssignedAddressHandlerImpl.kt @@ -23,29 +23,26 @@ class AssignedAddressHandlerImpl( ) : AssignedAddressHandler { override suspend fun fetchAssignedAddresses(user: String, addressTypes: List): List { if (addressTypes.isEmpty()) return emptyList() + val addressTypeMap = addressTypeRepository.findAll().map { aam -> + AddressType(aam.id!!, aam.type, aam.addressRegex, aam.memoRegex) + }.collectMap { it.id }.awaitFirst() return assignedAddressRepository.findByUuidAndAddressType( user, addressTypes.map(AddressType::id) - ) - .map { model -> - AssignedAddress( - model.uuid, model.address, model.memo, - addressTypeRepository - .findById(model.addressTypeId) - .map { aam -> - AddressType(aam.id!!, aam.type, aam.addressRegex, aam.memoRegex) - } - .awaitFirst(), - assignedAddressChainRepository.findByAssignedAddress(model.id!!) - .map { cm -> - chainLoader.fetchChainInfo(cm.chain) - } - .toList().toMutableList() - ) - }.toList() + ).map { model -> + AssignedAddress( + model.uuid, + model.address, + model.memo, + addressTypeMap.getValue(model.addressTypeId), + assignedAddressChainRepository.findByAssignedAddress(model.id!!).map { cm -> + chainLoader.fetchChainInfo(cm.chain) + }.toList().toMutableList() + ) + }.toList() } override suspend fun persist(assignedAddress: AssignedAddress) { - try { + runCatching { assignedAddressRepository.save( AssignedAddressModel( null, @@ -55,8 +52,6 @@ class AssignedAddressHandlerImpl( assignedAddress.type.id ) ).awaitFirst() - } catch (e: Exception) { - } } diff --git a/bc-gateway/bc-gateway-ports/bc-gateway-persister-postgres/src/main/kotlin/co/nilin/opex/bcgateway/ports/postgres/impl/CurrencyHandlerImpl.kt b/bc-gateway/bc-gateway-ports/bc-gateway-persister-postgres/src/main/kotlin/co/nilin/opex/bcgateway/ports/postgres/impl/CurrencyHandlerImpl.kt index 076f18ebf..caf51f8b4 100644 --- a/bc-gateway/bc-gateway-ports/bc-gateway-persister-postgres/src/main/kotlin/co/nilin/opex/bcgateway/ports/postgres/impl/CurrencyHandlerImpl.kt +++ b/bc-gateway/bc-gateway-ports/bc-gateway-persister-postgres/src/main/kotlin/co/nilin/opex/bcgateway/ports/postgres/impl/CurrencyHandlerImpl.kt @@ -147,13 +147,13 @@ class CurrencyHandlerImpl( currencyModel: CurrencyModel? = null ): CurrencyImplementation { val addressTypesModel = chainRepository.findAddressTypesByName(currencyImplementationModel.chain) - val addressTypes = addressTypesModel.map { AddressType(it.id!!, it.type, it.addressRegex, it.memoRegex) } + val addressTypes = addressTypesModel.map { AddressType(it.id!!, it.type, it.addressRegex, it.memoRegex) }.toList() val currencyModelVal = currencyModel ?: currencyRepository.findBySymbol(currencyImplementationModel.currencySymbol).awaitSingle() val currency = Currency(currencyModelVal.symbol, currencyModelVal.name) return CurrencyImplementation( currency, - Chain(currencyImplementationModel.chain, addressTypes.toList()), + Chain(currencyImplementationModel.chain, addressTypes), currencyImplementationModel.token, currencyImplementationModel.tokenAddress, currencyImplementationModel.tokenName, From 5d52567b0c323b7dc9b0e2343fbef15aecccc823 Mon Sep 17 00:00:00 2001 From: ebrahimmfadae Date: Sun, 3 Jul 2022 17:25:34 +0430 Subject: [PATCH 03/14] bc-gateway: Improve logs --- .../opex/bcgateway/app/controller/WalletSyncController.kt | 5 +++++ .../opex/bcgateway/core/service/WalletSyncServiceImpl.kt | 1 - 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/bc-gateway/bc-gateway-app/src/main/kotlin/co/nilin/opex/bcgateway/app/controller/WalletSyncController.kt b/bc-gateway/bc-gateway-app/src/main/kotlin/co/nilin/opex/bcgateway/app/controller/WalletSyncController.kt index 8fa0f4b5c..710e25b6e 100644 --- a/bc-gateway/bc-gateway-app/src/main/kotlin/co/nilin/opex/bcgateway/app/controller/WalletSyncController.kt +++ b/bc-gateway/bc-gateway-app/src/main/kotlin/co/nilin/opex/bcgateway/app/controller/WalletSyncController.kt @@ -2,9 +2,11 @@ package co.nilin.opex.bcgateway.app.controller import co.nilin.opex.bcgateway.core.api.WalletSyncService import co.nilin.opex.bcgateway.core.model.Transfer +import co.nilin.opex.bcgateway.core.utils.LoggerDelegate import co.nilin.opex.bcgateway.ports.postgres.impl.ChainHandler import co.nilin.opex.utility.error.data.OpexError import co.nilin.opex.utility.error.data.OpexException +import org.slf4j.Logger import org.springframework.web.bind.annotation.PathVariable import org.springframework.web.bind.annotation.PutMapping import org.springframework.web.bind.annotation.RequestBody @@ -12,8 +14,11 @@ import org.springframework.web.bind.annotation.RestController @RestController class WalletSyncController(private val chainHandler: ChainHandler, private val walletSyncService: WalletSyncService) { + private val logger: Logger by LoggerDelegate() + @PutMapping("wallet-sync/{chain}") suspend fun syncTransferOnChain(@PathVariable chain: String, @RequestBody transfers: List) { + logger.debug("Received ${transfers.size} transfer(s) for chain: $chain") runCatching { chainHandler.fetchChainInfo(chain) }.onFailure { diff --git a/bc-gateway/bc-gateway-core/src/main/kotlin/co/nilin/opex/bcgateway/core/service/WalletSyncServiceImpl.kt b/bc-gateway/bc-gateway-core/src/main/kotlin/co/nilin/opex/bcgateway/core/service/WalletSyncServiceImpl.kt index 2400ab198..01ff7d969 100644 --- a/bc-gateway/bc-gateway-core/src/main/kotlin/co/nilin/opex/bcgateway/core/service/WalletSyncServiceImpl.kt +++ b/bc-gateway/bc-gateway-core/src/main/kotlin/co/nilin/opex/bcgateway/core/service/WalletSyncServiceImpl.kt @@ -28,7 +28,6 @@ class WalletSyncServiceImpl( @Transactional override suspend fun syncTransfers(transfers: List) = coroutineScope { - logger.debug("Received ${transfers.size} number of transfers") val groupedByChain = currencyHandler.fetchAllImplementations().groupBy { it.chain.name } val deposits = transfers.map { async { From 60e69c3775a606b271d6722c48aa3939bbd117a4 Mon Sep 17 00:00:00 2001 From: Ebrahim Hoseiny Fadae Date: Tue, 12 Jul 2022 15:54:57 +0430 Subject: [PATCH 04/14] Close #297, Fix and improve withdraw request service (#308) --- .../api/core/inout/WithdrawHistoryResponse.kt | 3 +- .../binance/controller/WalletController.kt | 2 +- .../app/controller/WithdrawController.kt | 19 +++++++++--- .../wallet/app/dto/WithdrawHistoryResponse.kt | 3 +- .../opex/wallet/core/inout/WithdrawCommand.kt | 4 +-- .../wallet/core/inout/WithdrawResponse.kt | 2 +- .../nilin/opex/wallet/core/model/Withdraw.kt | 3 +- .../wallet/core/service/WithdrawService.kt | 31 +++++++++++++------ .../ports/postgres/dao/WithdrawRepository.kt | 2 +- .../postgres/impl/WithdrawPersisterImpl.kt | 8 +++-- .../ports/postgres/model/WithdrawModel.kt | 3 +- .../src/main/resources/schema.sql | 11 ++++--- 12 files changed, 60 insertions(+), 31 deletions(-) diff --git a/api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/WithdrawHistoryResponse.kt b/api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/WithdrawHistoryResponse.kt index bb9e75daf..3d0d6607a 100644 --- a/api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/WithdrawHistoryResponse.kt +++ b/api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/WithdrawHistoryResponse.kt @@ -6,10 +6,11 @@ data class WithdrawHistoryResponse( val withdrawId: Long?, val uuid: String, val amount: BigDecimal, + val currency: String, val acceptedFee: BigDecimal, val appliedFee: BigDecimal?, val destAmount: BigDecimal?, - val destCurrency: String?, + val destSymbol: String?, val destAddress: String?, val destNetwork: String?, var destNote: String?, diff --git a/api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/api/ports/binance/controller/WalletController.kt b/api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/api/ports/binance/controller/WalletController.kt index c299e91ca..49da873a1 100644 --- a/api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/api/ports/binance/controller/WalletController.kt +++ b/api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/api/ports/binance/controller/WalletController.kt @@ -127,7 +127,7 @@ class WalletController( LocalDateTime.ofInstant(Instant.ofEpochMilli(it.createDate), ZoneId.systemDefault()) .toString() .replace("T", " "), - it.destCurrency ?: "", + it.destSymbol ?: "", it.withdrawId?.toString() ?: "", "", it.destNetwork ?: "", diff --git a/wallet/wallet-app/src/main/kotlin/co/nilin/opex/wallet/app/controller/WithdrawController.kt b/wallet/wallet-app/src/main/kotlin/co/nilin/opex/wallet/app/controller/WithdrawController.kt index 86204bda4..fbd8a10cd 100644 --- a/wallet/wallet-app/src/main/kotlin/co/nilin/opex/wallet/app/controller/WithdrawController.kt +++ b/wallet/wallet-app/src/main/kotlin/co/nilin/opex/wallet/app/controller/WithdrawController.kt @@ -64,20 +64,28 @@ class WithdrawController(private val withdrawService: WithdrawService) { ) suspend fun requestWithdraw( principal: Principal, - @PathVariable("symbol") symbol: String, + @PathVariable("currency") currency: String, @PathVariable("amount") amount: BigDecimal, @RequestParam("description", required = false) description: String?, @RequestParam("transferRef", required = false) transferRef: String?, @RequestParam("fee") fee: BigDecimal, - @RequestParam("destCurrency") destCurrency: String, + @RequestParam("destSymbol") destSymbol: String, @RequestParam("destAddress") destAddress: String, @RequestParam("destNetwork") destNetwork: String, @RequestParam("destNote", required = false) destNote: String?, ): WithdrawResult { return withdrawService.requestWithdraw( WithdrawCommand( - principal.name, symbol, - amount, description, transferRef, destCurrency, destAddress, destNetwork, destNote, fee + principal.name, + currency, + amount, + description, + transferRef, + destSymbol, + destAddress, + destNetwork, + destNote, + fee ) ) } @@ -99,10 +107,11 @@ class WithdrawController(private val withdrawService: WithdrawService) { it.withdrawId, it.ownerUuid, it.amount, + it.currency, it.acceptedFee, it.appliedFee, it.destAmount, - it.destCurrency, + it.destSymbol, it.destAddress, it.destNetwork, it.destNote, diff --git a/wallet/wallet-app/src/main/kotlin/co/nilin/opex/wallet/app/dto/WithdrawHistoryResponse.kt b/wallet/wallet-app/src/main/kotlin/co/nilin/opex/wallet/app/dto/WithdrawHistoryResponse.kt index f01016723..4db260698 100644 --- a/wallet/wallet-app/src/main/kotlin/co/nilin/opex/wallet/app/dto/WithdrawHistoryResponse.kt +++ b/wallet/wallet-app/src/main/kotlin/co/nilin/opex/wallet/app/dto/WithdrawHistoryResponse.kt @@ -6,10 +6,11 @@ data class WithdrawHistoryResponse( val withdrawId: Long? = null, val uuid: String, val amount: BigDecimal, + val currency: String, val acceptedFee: BigDecimal, val appliedFee: BigDecimal?, val destAmount: BigDecimal?, - val destCurrency: String?, + val destSymbol: String?, val destAddress: String?, val destNetwork: String?, var destNote: String?, diff --git a/wallet/wallet-core/src/main/kotlin/co/nilin/opex/wallet/core/inout/WithdrawCommand.kt b/wallet/wallet-core/src/main/kotlin/co/nilin/opex/wallet/core/inout/WithdrawCommand.kt index 50d0ffd4c..10c213f78 100644 --- a/wallet/wallet-core/src/main/kotlin/co/nilin/opex/wallet/core/inout/WithdrawCommand.kt +++ b/wallet/wallet-core/src/main/kotlin/co/nilin/opex/wallet/core/inout/WithdrawCommand.kt @@ -4,11 +4,11 @@ import java.math.BigDecimal class WithdrawCommand( val uuid: String, - val symbol: String, + val currency: String, val amount: BigDecimal, val description: String?, val transferRef: String?, - val destCurrency: String, + val destSymbol: String, val destAddress: String, val destNetwork: String, val destNote: String?, diff --git a/wallet/wallet-core/src/main/kotlin/co/nilin/opex/wallet/core/inout/WithdrawResponse.kt b/wallet/wallet-core/src/main/kotlin/co/nilin/opex/wallet/core/inout/WithdrawResponse.kt index 9eb60c3da..6a7f3f6a6 100644 --- a/wallet/wallet-core/src/main/kotlin/co/nilin/opex/wallet/core/inout/WithdrawResponse.kt +++ b/wallet/wallet-core/src/main/kotlin/co/nilin/opex/wallet/core/inout/WithdrawResponse.kt @@ -15,7 +15,7 @@ class WithdrawResponse( val appliedFee: BigDecimal?, val amount: BigDecimal?, val destAmount: BigDecimal?, - val destCurrency: String?, + val destSymbol: String?, val destAddress: String?, val destNetwork: String?, var destNote: String?, diff --git a/wallet/wallet-core/src/main/kotlin/co/nilin/opex/wallet/core/model/Withdraw.kt b/wallet/wallet-core/src/main/kotlin/co/nilin/opex/wallet/core/model/Withdraw.kt index 76a2eb507..e99a3eadc 100644 --- a/wallet/wallet-core/src/main/kotlin/co/nilin/opex/wallet/core/model/Withdraw.kt +++ b/wallet/wallet-core/src/main/kotlin/co/nilin/opex/wallet/core/model/Withdraw.kt @@ -6,6 +6,7 @@ import java.time.LocalDateTime data class Withdraw( val withdrawId: Long? = null, val ownerUuid: String, + val currency: String, val wallet: Long, val amount: BigDecimal, val requestTransaction: String, @@ -13,7 +14,7 @@ data class Withdraw( val acceptedFee: BigDecimal, val appliedFee: BigDecimal?, val destAmount: BigDecimal?, - val destCurrency: String?, + val destSymbol: String?, val destAddress: String?, val destNetwork: String?, var destNote: String?, diff --git a/wallet/wallet-core/src/main/kotlin/co/nilin/opex/wallet/core/service/WithdrawService.kt b/wallet/wallet-core/src/main/kotlin/co/nilin/opex/wallet/core/service/WithdrawService.kt index a3d333580..ed92c5380 100644 --- a/wallet/wallet-core/src/main/kotlin/co/nilin/opex/wallet/core/service/WithdrawService.kt +++ b/wallet/wallet-core/src/main/kotlin/co/nilin/opex/wallet/core/service/WithdrawService.kt @@ -26,7 +26,7 @@ class WithdrawService( suspend fun requestWithdraw( withdrawCommand: WithdrawCommand ): WithdrawResult { - val currency = currencyService.getCurrency(withdrawCommand.symbol) ?: throw IllegalArgumentException() + val currency = currencyService.getCurrency(withdrawCommand.currency) ?: throw IllegalArgumentException() val owner = walletOwnerManager.findWalletOwner(withdrawCommand.uuid) ?: throw IllegalArgumentException() val sourceWallet = walletManager.findWalletByOwnerAndCurrencyAndType(owner, "main", currency) @@ -51,12 +51,23 @@ class WithdrawService( ) val withdraw = withdrawPersister.persist( Withdraw( - null, owner.uuid, receiverWallet.id!!, withdrawCommand.amount, - transferResultDetailed.tx, null, - withdrawCommand.acceptedFee, null, - null, withdrawCommand.destCurrency, - withdrawCommand.destAddress, withdrawCommand.destNetwork, - withdrawCommand.destNote, null, null, "CREATED" + null, + owner.uuid, + currency.symbol, + receiverWallet.id!!, + withdrawCommand.amount, + transferResultDetailed.tx, + null, + withdrawCommand.acceptedFee, + null, + null, + withdrawCommand.destSymbol, + withdrawCommand.destAddress, + withdrawCommand.destNetwork, + withdrawCommand.destNote, + null, + null, + "CREATED" ) ) return WithdrawResult(withdraw.withdrawId!!, withdraw.status) @@ -97,6 +108,7 @@ class WithdrawService( Withdraw( withdraw.withdrawId, withdraw.ownerUuid, + withdraw.currency, withdraw.wallet, withdraw.amount, withdraw.requestTransaction, @@ -104,7 +116,7 @@ class WithdrawService( withdraw.acceptedFee, withdraw.appliedFee, withdraw.amount.subtract(acceptCommand.appliedFee), - withdraw.destCurrency, + withdraw.destSymbol, withdraw.destAddress, withdraw.destNetwork, withdraw.destNote ?: ("" + "-----------" + (acceptCommand.destNote ?: "")), @@ -150,6 +162,7 @@ class WithdrawService( Withdraw( withdraw.withdrawId, withdraw.ownerUuid, + withdraw.currency, withdraw.wallet, withdraw.amount, withdraw.requestTransaction, @@ -157,7 +170,7 @@ class WithdrawService( withdraw.acceptedFee, null, null, - withdraw.destCurrency, + withdraw.destSymbol, withdraw.destAddress, withdraw.destNetwork, withdraw.destNote ?: ("" + "-----------" + (rejectCommand.destNote ?: "")), diff --git a/wallet/wallet-ports/wallet-persister-postgres/src/main/kotlin/co/nilin/opex/wallet/ports/postgres/dao/WithdrawRepository.kt b/wallet/wallet-ports/wallet-persister-postgres/src/main/kotlin/co/nilin/opex/wallet/ports/postgres/dao/WithdrawRepository.kt index 03d7faea3..1e8acc4fa 100644 --- a/wallet/wallet-ports/wallet-persister-postgres/src/main/kotlin/co/nilin/opex/wallet/ports/postgres/dao/WithdrawRepository.kt +++ b/wallet/wallet-ports/wallet-persister-postgres/src/main/kotlin/co/nilin/opex/wallet/ports/postgres/dao/WithdrawRepository.kt @@ -108,7 +108,7 @@ interface WithdrawRepository : ReactiveCrudRepository { """ select * from withdraws where uuid = :uuid - and dest_currency = :currency + and currency = :currency and create_date > :startTime and create_date <= :endTime limit :limit diff --git a/wallet/wallet-ports/wallet-persister-postgres/src/main/kotlin/co/nilin/opex/wallet/ports/postgres/impl/WithdrawPersisterImpl.kt b/wallet/wallet-ports/wallet-persister-postgres/src/main/kotlin/co/nilin/opex/wallet/ports/postgres/impl/WithdrawPersisterImpl.kt index 0499d80df..341d4aa74 100644 --- a/wallet/wallet-ports/wallet-persister-postgres/src/main/kotlin/co/nilin/opex/wallet/ports/postgres/impl/WithdrawPersisterImpl.kt +++ b/wallet/wallet-ports/wallet-persister-postgres/src/main/kotlin/co/nilin/opex/wallet/ports/postgres/impl/WithdrawPersisterImpl.kt @@ -97,6 +97,7 @@ class WithdrawPersisterImpl( WithdrawModel( withdraw.withdrawId, withdraw.ownerUuid, + withdraw.currency, withdraw.wallet, withdraw.amount, withdraw.requestTransaction, @@ -104,7 +105,7 @@ class WithdrawPersisterImpl( withdraw.acceptedFee, withdraw.appliedFee, withdraw.destAmount, - withdraw.destCurrency, + withdraw.destSymbol, withdraw.destNetwork, withdraw.destAddress, withdraw.destNote, @@ -157,7 +158,7 @@ class WithdrawPersisterImpl( appliedFee, amount, destAmount, - destCurrency, + destSymbol, destAddress, destNetwork, destNote, @@ -173,6 +174,7 @@ class WithdrawPersisterImpl( return Withdraw( id, ownerUuid, + currency, wallet, amount, requestTransaction, @@ -180,7 +182,7 @@ class WithdrawPersisterImpl( acceptedFee, appliedFee, destAmount, - destCurrency, + destSymbol, destAddress, destNetwork, destNote, diff --git a/wallet/wallet-ports/wallet-persister-postgres/src/main/kotlin/co/nilin/opex/wallet/ports/postgres/model/WithdrawModel.kt b/wallet/wallet-ports/wallet-persister-postgres/src/main/kotlin/co/nilin/opex/wallet/ports/postgres/model/WithdrawModel.kt index 8dab4a5eb..3b3e5a8ad 100644 --- a/wallet/wallet-ports/wallet-persister-postgres/src/main/kotlin/co/nilin/opex/wallet/ports/postgres/model/WithdrawModel.kt +++ b/wallet/wallet-ports/wallet-persister-postgres/src/main/kotlin/co/nilin/opex/wallet/ports/postgres/model/WithdrawModel.kt @@ -10,6 +10,7 @@ import java.time.LocalDateTime data class WithdrawModel( @Id var id: Long?, @Column("uuid") val ownerUuid: String, + @Column("currency") val currency: String, @Column("wallet") val wallet: Long, @Column("amount") val amount: BigDecimal, @Column("req_transaction_id") val requestTransaction: String, @@ -17,7 +18,7 @@ data class WithdrawModel( @Column("accepted_fee") val acceptedFee: BigDecimal, @Column("applied_fee") val appliedFee: BigDecimal?, @Column("dest_amount") val destAmount: BigDecimal?, - @Column("dest_currency") val destCurrency: String?, + @Column("dest_symbol") val destSymbol: String?, @Column("dest_network") val destNetwork: String?, @Column("dest_address") val destAddress: String?, @Column("dest_notes") var destNote: String?, diff --git a/wallet/wallet-ports/wallet-persister-postgres/src/main/resources/schema.sql b/wallet/wallet-ports/wallet-persister-postgres/src/main/resources/schema.sql index f88eba345..e01ad593a 100644 --- a/wallet/wallet-ports/wallet-persister-postgres/src/main/resources/schema.sql +++ b/wallet/wallet-ports/wallet-persister-postgres/src/main/resources/schema.sql @@ -65,12 +65,13 @@ CREATE TABLE IF NOT EXISTS withdraws uuid VARCHAR(36) NOT NULL, req_transaction_id VARCHAR(20) NOT NULL UNIQUE, final_transaction_id VARCHAR(20) UNIQUE, - wallet INTEGER REFERENCES wallet (id), - amount DECIMAL NOT NULL, - accepted_fee DECIMAL, + currency VARCHAR(20) NOT NULL REFERENCES currency (symbol), + wallet INTEGER NOT NULL REFERENCES wallet (id), + amount DECIMAL NOT NULL, + accepted_fee DECIMAL NOT NULL, applied_fee DECIMAL, dest_amount DECIMAL, - dest_currency VARCHAR(20) REFERENCES currency (symbol), + dest_symbol VARCHAR(20), dest_network VARCHAR(20), dest_address VARCHAR(80), dest_notes TEXT, @@ -78,6 +79,6 @@ CREATE TABLE IF NOT EXISTS withdraws description TEXT, status_reason TEXT, status VARCHAR(20), - create_date TIMESTAMP NOT NULL, + create_date TIMESTAMP NOT NULL, accept_date TIMESTAMP ); From 4d6442dfb0d0d890f6a555e988b3a82664f69e5c Mon Sep 17 00:00:00 2001 From: Ebrahim Hoseiny Fadae Date: Sat, 6 Aug 2022 17:56:19 +0430 Subject: [PATCH 05/14] Close #292, Add elk stack for monitoring logs (#310) * elastic: Add elastic stack services to docker-compose.yml * elastic: Create immutable filebeat container * elastic: Fix ssl connection issue * elastic: Update docker-compose configs --- docker-compose.build.yml | 3 +++ docker-compose.override.yml | 2 ++ docker-compose.yml | 30 +++++++++++++++++++++++++++++ docker-images/filebeat/Dockerfile | 2 ++ docker-images/filebeat/filebeat.yml | 24 +++++++++++++++++++++++ 5 files changed, 61 insertions(+) create mode 100644 docker-images/filebeat/Dockerfile create mode 100644 docker-images/filebeat/filebeat.yml diff --git a/docker-compose.build.yml b/docker-compose.build.yml index fd0601794..a850eac1b 100644 --- a/docker-compose.build.yml +++ b/docker-compose.build.yml @@ -45,3 +45,6 @@ services: admin: image: ghcr.io/opexdev/admin:$TAG build: admin/admin-app + filebeat: + image: ghcr.io/opexdev/filebeat:$TAG + build: docker-images/filebeat diff --git a/docker-compose.override.yml b/docker-compose.override.yml index 15de102ab..42d161abf 100644 --- a/docker-compose.override.yml +++ b/docker-compose.override.yml @@ -41,3 +41,5 @@ services: build: captcha/captcha-app admin: build: admin/admin-app + filebeat: + build: docker-images/filebeat diff --git a/docker-compose.yml b/docker-compose.yml index ba0bf4dbb..fd96279eb 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -481,6 +481,35 @@ services: deploy: restart_policy: condition: on-failure + elasticsearch: + image: elastic/elasticsearch:8.3.2 + environment: + - ES_JAVA_OPTS=-Xms1g -Xmx1g + - discovery.type=single-node + - ingest.geoip.downloader.enabled=false + - node.name=elasticsearch + - network.publish_host=elasticsearch + volumes: + - elasticsearch_data:/usr/share/elasticsearch/data + networks: + - default + kibana: + image: elastic/kibana:8.3.2 + environment: + - SERVER_PUBLICBASEURL=$KIBANA_PUBLIC_URL + - ELASTICSEARCH_SSL_VERIFICATIONMODE=certificate + networks: + - default + filebeat: + image: ghcr.io/opexdev/opex-filebeat + user: root + environment: + - FILEBEAT_API_KEY=$FILEBEAT_API_KEY + volumes: + - /var/lib/docker:/var/lib/docker:ro + - /var/run/docker.sock:/var/run/docker.sock + networks: + - default volumes: zookeeper-data: zookeeper-log: @@ -499,6 +528,7 @@ volumes: referral-data: storage-data: admin-data: + elasticsearch_data: networks: default: driver: bridge diff --git a/docker-images/filebeat/Dockerfile b/docker-images/filebeat/Dockerfile new file mode 100644 index 000000000..17c6916d4 --- /dev/null +++ b/docker-images/filebeat/Dockerfile @@ -0,0 +1,2 @@ +FROM elastic/filebeat:8.3.2 +COPY filebeat.yml /usr/share/filebeat/filebeat.yml diff --git a/docker-images/filebeat/filebeat.yml b/docker-images/filebeat/filebeat.yml new file mode 100644 index 000000000..cb86e7279 --- /dev/null +++ b/docker-images/filebeat/filebeat.yml @@ -0,0 +1,24 @@ +filebeat.inputs: + - type: filestream + paths: + - '/var/lib/docker/containers/*/*.log' + +processors: + - add_docker_metadata: + host: "unix:///var/run/docker.sock" + + - decode_json_fields: + fields: [ "message" ] + target: "json" + overwrite_keys: true + +output.elasticsearch: + hosts: [ "elasticsearch:9200" ] + protocol: https + api_key: ${FILEBEAT_API_KEY} + ssl.verification_mode: "none" + indices: + - index: "filebeat-%{[agent.version]}-%{+yyyy.MM.dd}" + +logging.json: true +logging.metrics.enabled: false From a49734dfd1c6790022c36b22e6282868770b50fa Mon Sep 17 00:00:00 2001 From: Ebrahim Hoseiny Fadae Date: Sat, 6 Aug 2022 19:49:22 +0430 Subject: [PATCH 06/14] docker-compose: Fix filebeat container name (#311) --- docker-compose.build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-compose.build.yml b/docker-compose.build.yml index a850eac1b..4cd76b501 100644 --- a/docker-compose.build.yml +++ b/docker-compose.build.yml @@ -46,5 +46,5 @@ services: image: ghcr.io/opexdev/admin:$TAG build: admin/admin-app filebeat: - image: ghcr.io/opexdev/filebeat:$TAG + image: ghcr.io/opexdev/opex-filebeat:$TAG build: docker-images/filebeat From c1f3a7301d5e4f0c44c570adfbfcf94ce5d5cbcc Mon Sep 17 00:00:00 2001 From: Ebrahim Hoseiny Fadae Date: Sat, 6 Aug 2022 20:59:01 +0430 Subject: [PATCH 07/14] Change filebeat input type (#312) * filebeat: Change input format to container --- docker-images/filebeat/filebeat.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-images/filebeat/filebeat.yml b/docker-images/filebeat/filebeat.yml index cb86e7279..ca28f7289 100644 --- a/docker-images/filebeat/filebeat.yml +++ b/docker-images/filebeat/filebeat.yml @@ -1,5 +1,5 @@ filebeat.inputs: - - type: filestream + - type: container paths: - '/var/lib/docker/containers/*/*.log' From 193bab573d1e66e76016cea62f1ca3e9358ba1fb Mon Sep 17 00:00:00 2001 From: Peyman Date: Wed, 10 Aug 2022 15:01:57 +0430 Subject: [PATCH 08/14] Close #289: Market service (#309) * Added market module * Added landing services * Close #289 --- .gitignore | 1 - .../app/controller/AccountantController.kt | 18 +- .../opex/accountant/core/inout/RichTrade.kt | 1 + .../core/service/TradeManagerImpl.kt | 9 +- .../accountant/core/spi/PairConfigLoader.kt | 4 + .../postgres/dao/PairFeeConfigRepository.kt | 6 + .../postgres/impl/PairConfigLoaderImpl.kt | 33 ++ .../accountant/ports/kafka/submitter/Valid.kt | 1 + api/api-app/pom.xml | 4 +- .../co/nilin/opex/api/app/config/AppConfig.kt | 30 -- .../opex/api/app/config/InitializeService.kt | 1 + .../api/app/utils}/VaultUserIdMechanism.kt | 4 +- .../src/main/resources/application.yml | 6 +- .../opex/api/core/event/RichOrderEvent.kt | 3 - .../co/nilin/opex/api/core/inout/BestPrice.kt | 9 + .../opex/api/core/inout/CountResponse.kt | 3 + .../opex/api/core/inout/CreateOrderRequest.kt | 19 -- .../api/core/inout/CreateOrderResponse.kt | 21 -- .../nilin/opex/api/core/inout/CurrencyRate.kt | 10 + .../nilin/opex/api/core/inout/GlobalPrice.kt | 6 + ...{MarketTradeResponse.kt => MarketTrade.kt} | 4 +- .../co/nilin/opex/api/core/inout/Order.kt | 29 ++ .../{OrderBookResponse.kt => OrderBook.kt} | 2 +- .../nilin/opex/api/core/inout/OrderEnums.kt | 43 +-- .../nilin/opex/api/core/inout/PriceChange.kt | 23 ++ .../co/nilin/opex/api/core/inout/PriceStat.kt | 9 + ...{PriceTickerResponse.kt => PriceTicker.kt} | 2 +- .../core/inout/{TradeResponse.kt => Trade.kt} | 7 +- .../opex/api/core/inout/TradeVolumeStat.kt | 10 + .../co/nilin/opex/api/core/inout/Wallet.kt | 5 +- .../opex/api/core/spi/AccountantProxy.kt | 2 + .../opex/api/core/spi/GlobalMarketProxy.kt | 9 + .../nilin/opex/api/core/spi/MEGatewayProxy.kt | 21 -- .../opex/api/core/spi/MarketDataProxy.kt | 42 +++ .../opex/api/core/spi/MarketQueryHandler.kt | 31 -- .../opex/api/core/spi/MarketStatProxy.kt | 16 + .../opex/api/core/spi/MarketUserDataProxy.kt | 30 ++ .../opex/api/core/spi/MatchingGatewayProxy.kt | 29 ++ .../nilin/opex/api/core/spi/OrderPersister.kt | 11 - .../nilin/opex/api/core/spi/SymbolMapper.kt | 4 +- .../nilin/opex/api/core/spi/TradePersister.kt | 7 - .../opex/api/core/spi/UserQueryHandler.kt | 12 - .../co/nilin/opex/api/core/spi/WalletProxy.kt | 2 + .../co/nilin/opex/api/core/utils}/Interval.kt | 6 +- .../opex/api/core/utils}/LoggerDelegate.kt | 2 +- .../ports/binance/config/SecurityConfig.kt | 1 + .../ports/binance/config/WebClientConfig.kt | 13 +- .../binance/controller/AccountController.kt | 282 +++++------------- .../binance/controller/LandingController.kt | 74 +++++ .../binance/controller/MarketController.kt | 60 ++-- .../binance/controller/WalletController.kt | 120 +++++++- .../api/ports/binance/data/AssetResponse.kt | 12 + .../binance/data/AssetsEstimatedValue.kt | 9 + .../ports/binance/data/CancelOrderResponse.kt | 25 ++ .../binance/data/ExchangeInfoResponse.kt | 1 - .../opex/api/ports/binance/data/FillsData.kt} | 18 +- .../ports/binance/data/GlobalPriceResponse.kt | 9 + .../ports/binance/data/MarketInfoResponse.kt | 7 + .../ports/binance/data/MarketStatResponse.kt | 11 + .../ports/binance/data/NewOrderResponse.kt | 28 ++ .../api/ports/binance/data/PairFeeResponse.kt | 7 + .../ports/binance/data}/QueryOrderResponse.kt | 56 ++-- .../opex/api/ports/binance/data}/RateLimit.kt | 2 +- .../ports/binance/data/RateLimitResponse.kt | 2 - .../api/ports/binance/data}/RateLimitType.kt | 2 +- .../api/ports/binance/data/TradeResponse.kt | 22 ++ .../ports/binance/proxy/MEGatewayProxyImpl.kt | 55 ---- .../api/ports/binance/util/BalanceParser.kt | 33 -- .../api/ports/binance/util/EnumExtensions.kt | 22 ++ .../ports/postgres/dao/SymbolMapRepository.kt | 4 + .../ports/postgres/impl/SymbolMapperImpl.kt | 18 +- .../postgres/impl/UserQueryHandlerImpl.kt | 138 --------- .../api/ports/postgres/model/OrderModel.kt | 39 --- .../api/ports/postgres/model/TradeModel.kt | 28 -- .../src/main/resources/schema.sql | 89 +----- .../ports/postgres/impl/SymbolMapperTest.kt | 7 +- .../ports/postgres/impl/TradePersisterTest.kt | 24 -- .../api/ports/postgres/impl/sample/Samples.kt | 193 ------------ api/api-ports/api-proxy-rest/pom.xml | 57 ++++ .../api/ports/proxy/data/AllOrderRequest.kt | 10 + .../ports/proxy/data/AssignAddressRequest.kt | 6 + .../ports/proxy/data}/CancelOrderRequest.kt | 2 +- .../ports/proxy/data/CreateOrderRequest.kt | 16 + .../ports/proxy/data/DepositDetailsRequest.kt | 3 + .../api/ports/proxy/data/QueryOrderRequest.kt | 7 + .../opex/api/ports/proxy/data/TradeRequest.kt | 11 + .../ports/proxy}/data/TransactionRequest.kt | 2 +- .../ports/proxy/impl}/AccountantProxyImpl.kt | 27 +- .../proxy/impl/BinanceGlobalMarketProxy.kt | 42 +++ .../proxy/impl}/BlockchainGatewayProxyImpl.kt | 23 +- .../ports/proxy/impl/MarketDataProxyImpl.kt | 227 ++++++++++++++ .../ports/proxy/impl/MarketStatProxyImpl.kt | 78 +++++ .../proxy/impl/MarketUserDataProxyImpl.kt | 100 +++++++ .../proxy/impl/MatchingGatewayProxyImpl.kt | 73 +++++ .../api/ports/proxy/impl}/WalletProxyImpl.kt | 36 ++- api/pom.xml | 6 +- docker-compose.build.yml | 3 + docker-compose.override.yml | 4 + docker-compose.yml | 47 +-- docker-images/vault/vault.json | 3 +- docker-images/vault/workflow-vault.sh | 5 +- market/market-app/.gitignore | 33 ++ market/market-app/Dockerfile | 5 + market/market-app/pom.xml | 101 +++++++ .../opex/market/app/MarketAppApplication.kt | 13 + .../nilin/opex/market/app/config/AppConfig.kt | 30 ++ .../opex/market}/app/config/AppDispatchers.kt | 3 +- .../market/app/config/InitializeService.kt | 30 ++ .../opex/market/app/config/SecurityConfig.kt | 38 +++ .../opex/market/app/config/WebClientConfig.kt | 25 ++ .../market/app/controller/ChartController.kt | 22 ++ .../market/app/controller/MarketController.kt | 90 ++++++ .../app/controller/MarketStatsController.kt | 37 +++ .../market/app/controller/RateController.kt | 26 ++ .../app/controller/UserDataController.kt | 38 +++ .../opex/market/app/data/CountResponse.kt | 3 + .../market/app/listener/MarketListenerImpl.kt | 22 +- .../nilin/opex/market/app/utils/Extensions.kt | 17 ++ .../market/app/utils}/VaultUserIdMechanism.kt | 4 +- .../src/main/resources/application.yml | 49 +++ market/market-core/pom.xml | 44 +++ .../opex/market}/core/event/RichOrder.kt | 8 +- .../opex/market/core/event/RichOrderEvent.kt | 3 + .../market}/core/event/RichOrderUpdate.kt | 4 +- .../opex/market}/core/event/RichTrade.kt | 5 +- .../core/inout/AggregatedOrderPriceModel.kt | 2 +- .../market}/core/inout/AllOrderRequest.kt | 2 +- .../nilin/opex/market/core/inout/BestPrice.kt | 9 + .../opex/market/core/inout/CandleData.kt | 18 ++ .../opex/market/core/inout/CurrencyRate.kt | 10 + .../opex/market/core/inout/MarketTrade.kt | 17 ++ .../co/nilin/opex/market/core/inout/Order.kt | 29 ++ .../nilin/opex/market/core/inout/OrderBook.kt | 8 + .../opex/market/core/inout/OrderEnums.kt | 64 ++++ .../opex/market/core/inout/OrderMetaData.kt | 17 ++ .../opex/market/core/inout/PriceChange.kt | 4 +- .../nilin/opex/market/core/inout/PriceStat.kt | 9 + .../opex/market/core/inout/PriceTicker.kt | 6 + .../market}/core/inout/QueryOrderRequest.kt | 2 +- .../opex/market/core/inout/RateSource.kt | 5 + .../co/nilin/opex/market/core/inout/Trade.kt | 20 ++ .../opex/market}/core/inout/TradeRequest.kt | 2 +- .../opex/market/core/inout/TradeVolumeStat.kt | 10 + .../market/core/spi/MarketQueryHandler.kt | 46 +++ .../opex/market/core/spi/MarketRateService.kt | 12 + .../opex/market/core/spi/OrderPersister.kt | 14 + .../opex/market/core/spi/TradePersister.kt | 7 + .../opex/market/core/spi/UserQueryHandler.kt | 16 + .../market-eventlistener-kafka}/pom.xml | 16 +- .../listener/config/KafkaConsumerConfig.kt | 26 +- .../listener/config/KafkaProducerConfig.kt | 12 +- .../kafka/listener/config/KafkaTopicConfig.kt | 2 +- .../listener/consumer/OrderKafkaListener.kt | 6 +- .../listener/consumer/TradeKafkaListener.kt | 6 +- .../kafka/listener/spi/RichOrderListener.kt | 4 +- .../kafka/listener/spi/RichTradeListener.kt | 6 +- .../market-persister-postgres/pom.xml | 71 +++++ .../ports/postgres/config/PostgresConfig.kt | 24 ++ .../postgres/dao/CurrencyRateRepository.kt | 31 ++ .../ports/postgres/dao/OrderRepository.kt | 21 +- .../postgres/dao/OrderStatusRepository.kt | 4 +- .../ports/postgres/dao/TradeRepository.kt | 199 +++++++++++- .../postgres/impl/MarketQueryHandlerImpl.kt | 143 ++++----- .../postgres/impl/MarketRateServiceImpl.kt | 31 ++ .../ports/postgres/impl/OrderPersisterImpl.kt | 27 +- .../ports/postgres/impl/TradePersisterImpl.kt | 30 +- .../postgres/impl/UserQueryHandlerImpl.kt | 98 ++++++ .../ports/postgres/model/CandleInfoData.kt | 4 +- .../ports/postgres/model/CurrencyRateModel.kt | 15 + .../market/ports/postgres/model/OrderModel.kt | 36 +++ .../ports/postgres/model/OrderStatusModel.kt | 2 +- .../market/ports/postgres/model/TradeModel.kt | 30 ++ .../ports/postgres/model/TradeTickerData.kt | 2 +- .../ports/postgres/util/DTOExtensions.kt | 31 ++ .../ports/postgres/util/EnumExtensions.kt | 12 +- .../src/main/resources/schema.sql | 94 ++++++ .../postgres/impl/MarketQueryHandlerTest.kt | 31 +- .../ports/postgres/impl/OrderPersisterTest.kt | 8 +- .../ports/postgres/impl/TradePersisterTest.kt | 26 ++ .../postgres/impl/UserQueryHandlerTest.kt | 22 +- .../ports/postgres/impl/sample/Samples.kt | 211 +++++++++++++ market/pom.xml | 83 ++++++ pom.xml | 1 + .../opex/utility/error/data/OpexError.kt | 10 +- .../app/controller/BalanceController.kt | 3 +- .../app/controller/WalletOwnerController.kt | 57 ++-- .../wallet/app/dto/OwnerLimitsResponse.kt | 7 + .../nilin/opex/wallet/app/dto/WalletData.kt | 10 + .../opex/wallet/app/utils/BalanceParser.kt | 51 ++++ .../wallet/app/utils/VaultUserIdMechanism.kt | 9 + .../src/main/resources/application.yml | 2 +- .../opex/wallet/core/spi/WalletManager.kt | 10 + .../ports/postgres/dao/WalletRepository.kt | 3 + .../ports/postgres/impl/WalletManagerImpl.kt | 17 ++ .../websocket/app/service/MarketService.kt | 2 +- 195 files changed, 3866 insertions(+), 1405 deletions(-) delete mode 100644 api/api-app/src/main/kotlin/co/nilin/opex/api/app/config/AppConfig.kt rename {wallet/wallet-app/src/main/kotlin/co/nilin/opex/util/vault => api/api-app/src/main/kotlin/co/nilin/opex/api/app/utils}/VaultUserIdMechanism.kt (65%) delete mode 100644 api/api-core/src/main/kotlin/co/nilin/opex/api/core/event/RichOrderEvent.kt create mode 100644 api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/BestPrice.kt create mode 100644 api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/CountResponse.kt delete mode 100644 api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/CreateOrderRequest.kt delete mode 100644 api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/CreateOrderResponse.kt create mode 100644 api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/CurrencyRate.kt create mode 100644 api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/GlobalPrice.kt rename api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/{MarketTradeResponse.kt => MarketTrade.kt} (78%) create mode 100644 api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/Order.kt rename api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/{OrderBookResponse.kt => OrderBook.kt} (80%) create mode 100644 api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/PriceChange.kt create mode 100644 api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/PriceStat.kt rename api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/{PriceTickerResponse.kt => PriceTicker.kt} (73%) rename api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/{TradeResponse.kt => Trade.kt} (76%) create mode 100644 api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/TradeVolumeStat.kt create mode 100644 api/api-core/src/main/kotlin/co/nilin/opex/api/core/spi/GlobalMarketProxy.kt delete mode 100644 api/api-core/src/main/kotlin/co/nilin/opex/api/core/spi/MEGatewayProxy.kt create mode 100644 api/api-core/src/main/kotlin/co/nilin/opex/api/core/spi/MarketDataProxy.kt delete mode 100644 api/api-core/src/main/kotlin/co/nilin/opex/api/core/spi/MarketQueryHandler.kt create mode 100644 api/api-core/src/main/kotlin/co/nilin/opex/api/core/spi/MarketStatProxy.kt create mode 100644 api/api-core/src/main/kotlin/co/nilin/opex/api/core/spi/MarketUserDataProxy.kt create mode 100644 api/api-core/src/main/kotlin/co/nilin/opex/api/core/spi/MatchingGatewayProxy.kt delete mode 100644 api/api-core/src/main/kotlin/co/nilin/opex/api/core/spi/OrderPersister.kt delete mode 100644 api/api-core/src/main/kotlin/co/nilin/opex/api/core/spi/TradePersister.kt delete mode 100644 api/api-core/src/main/kotlin/co/nilin/opex/api/core/spi/UserQueryHandler.kt rename api/{api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/api/ports/binance/data => api-core/src/main/kotlin/co/nilin/opex/api/core/utils}/Interval.kt (89%) rename api/{api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/api/ports/binance/util => api-core/src/main/kotlin/co/nilin/opex/api/core/utils}/LoggerDelegate.kt (92%) create mode 100644 api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/api/ports/binance/controller/LandingController.kt create mode 100644 api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/api/ports/binance/data/AssetResponse.kt create mode 100644 api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/api/ports/binance/data/AssetsEstimatedValue.kt create mode 100644 api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/api/ports/binance/data/CancelOrderResponse.kt rename api/{api-core/src/main/kotlin/co/nilin/opex/api/core/inout/OrderTradeData.kt => api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/api/ports/binance/data/FillsData.kt} (65%) create mode 100644 api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/api/ports/binance/data/GlobalPriceResponse.kt create mode 100644 api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/api/ports/binance/data/MarketInfoResponse.kt create mode 100644 api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/api/ports/binance/data/MarketStatResponse.kt create mode 100644 api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/api/ports/binance/data/NewOrderResponse.kt create mode 100644 api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/api/ports/binance/data/PairFeeResponse.kt rename api/{api-core/src/main/kotlin/co/nilin/opex/api/core/inout => api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/api/ports/binance/data}/QueryOrderResponse.kt (65%) rename api/{api-core/src/main/kotlin/co/nilin/opex/api/core/inout => api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/api/ports/binance/data}/RateLimit.kt (89%) rename api/{api-core/src/main/kotlin/co/nilin/opex/api/core/inout => api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/api/ports/binance/data}/RateLimitType.kt (64%) create mode 100644 api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/api/ports/binance/data/TradeResponse.kt delete mode 100644 api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/api/ports/binance/proxy/MEGatewayProxyImpl.kt delete mode 100644 api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/api/ports/binance/util/BalanceParser.kt delete mode 100644 api/api-ports/api-persister-postgres/src/main/kotlin/co/nilin/opex/api/ports/postgres/impl/UserQueryHandlerImpl.kt delete mode 100644 api/api-ports/api-persister-postgres/src/main/kotlin/co/nilin/opex/api/ports/postgres/model/OrderModel.kt delete mode 100644 api/api-ports/api-persister-postgres/src/main/kotlin/co/nilin/opex/api/ports/postgres/model/TradeModel.kt delete mode 100644 api/api-ports/api-persister-postgres/src/test/kotlin/co/nilin/opex/api/ports/postgres/impl/TradePersisterTest.kt create mode 100644 api/api-ports/api-proxy-rest/pom.xml create mode 100644 api/api-ports/api-proxy-rest/src/main/kotlin/co/nilin/opex/api/ports/proxy/data/AllOrderRequest.kt create mode 100644 api/api-ports/api-proxy-rest/src/main/kotlin/co/nilin/opex/api/ports/proxy/data/AssignAddressRequest.kt rename api/{api-core/src/main/kotlin/co/nilin/opex/api/core/inout => api-ports/api-proxy-rest/src/main/kotlin/co/nilin/opex/api/ports/proxy/data}/CancelOrderRequest.kt (69%) create mode 100644 api/api-ports/api-proxy-rest/src/main/kotlin/co/nilin/opex/api/ports/proxy/data/CreateOrderRequest.kt create mode 100644 api/api-ports/api-proxy-rest/src/main/kotlin/co/nilin/opex/api/ports/proxy/data/DepositDetailsRequest.kt create mode 100644 api/api-ports/api-proxy-rest/src/main/kotlin/co/nilin/opex/api/ports/proxy/data/QueryOrderRequest.kt create mode 100644 api/api-ports/api-proxy-rest/src/main/kotlin/co/nilin/opex/api/ports/proxy/data/TradeRequest.kt rename api/api-ports/{api-binance-rest/src/main/kotlin/co/nilin/opex/api/ports/binance => api-proxy-rest/src/main/kotlin/co/nilin/opex/api/ports/proxy}/data/TransactionRequest.kt (76%) rename api/api-ports/{api-binance-rest/src/main/kotlin/co/nilin/opex/api/ports/binance/proxy => api-proxy-rest/src/main/kotlin/co/nilin/opex/api/ports/proxy/impl}/AccountantProxyImpl.kt (63%) create mode 100644 api/api-ports/api-proxy-rest/src/main/kotlin/co/nilin/opex/api/ports/proxy/impl/BinanceGlobalMarketProxy.kt rename api/api-ports/{api-binance-rest/src/main/kotlin/co/nilin/opex/api/ports/binance/proxy => api-proxy-rest/src/main/kotlin/co/nilin/opex/api/ports/proxy/impl}/BlockchainGatewayProxyImpl.kt (78%) create mode 100644 api/api-ports/api-proxy-rest/src/main/kotlin/co/nilin/opex/api/ports/proxy/impl/MarketDataProxyImpl.kt create mode 100644 api/api-ports/api-proxy-rest/src/main/kotlin/co/nilin/opex/api/ports/proxy/impl/MarketStatProxyImpl.kt create mode 100644 api/api-ports/api-proxy-rest/src/main/kotlin/co/nilin/opex/api/ports/proxy/impl/MarketUserDataProxyImpl.kt create mode 100644 api/api-ports/api-proxy-rest/src/main/kotlin/co/nilin/opex/api/ports/proxy/impl/MatchingGatewayProxyImpl.kt rename api/api-ports/{api-binance-rest/src/main/kotlin/co/nilin/opex/api/ports/binance/proxy => api-proxy-rest/src/main/kotlin/co/nilin/opex/api/ports/proxy/impl}/WalletProxyImpl.kt (75%) create mode 100644 market/market-app/.gitignore create mode 100644 market/market-app/Dockerfile create mode 100644 market/market-app/pom.xml create mode 100644 market/market-app/src/main/kotlin/co/nilin/opex/market/app/MarketAppApplication.kt create mode 100644 market/market-app/src/main/kotlin/co/nilin/opex/market/app/config/AppConfig.kt rename {api/api-app/src/main/kotlin/co/nilin/opex/api => market/market-app/src/main/kotlin/co/nilin/opex/market}/app/config/AppDispatchers.kt (62%) create mode 100644 market/market-app/src/main/kotlin/co/nilin/opex/market/app/config/InitializeService.kt create mode 100644 market/market-app/src/main/kotlin/co/nilin/opex/market/app/config/SecurityConfig.kt create mode 100644 market/market-app/src/main/kotlin/co/nilin/opex/market/app/config/WebClientConfig.kt create mode 100644 market/market-app/src/main/kotlin/co/nilin/opex/market/app/controller/ChartController.kt create mode 100644 market/market-app/src/main/kotlin/co/nilin/opex/market/app/controller/MarketController.kt create mode 100644 market/market-app/src/main/kotlin/co/nilin/opex/market/app/controller/MarketStatsController.kt create mode 100644 market/market-app/src/main/kotlin/co/nilin/opex/market/app/controller/RateController.kt create mode 100644 market/market-app/src/main/kotlin/co/nilin/opex/market/app/controller/UserDataController.kt create mode 100644 market/market-app/src/main/kotlin/co/nilin/opex/market/app/data/CountResponse.kt rename api/api-app/src/main/kotlin/co/nilin/opex/api/app/listener/ApiListenerImpl.kt => market/market-app/src/main/kotlin/co/nilin/opex/market/app/listener/MarketListenerImpl.kt (59%) create mode 100644 market/market-app/src/main/kotlin/co/nilin/opex/market/app/utils/Extensions.kt rename {api/api-app/src/main/kotlin/co/nilin/opex/util/vault => market/market-app/src/main/kotlin/co/nilin/opex/market/app/utils}/VaultUserIdMechanism.kt (64%) create mode 100644 market/market-app/src/main/resources/application.yml create mode 100644 market/market-core/pom.xml rename {api/api-core/src/main/kotlin/co/nilin/opex/api => market/market-core/src/main/kotlin/co/nilin/opex/market}/core/event/RichOrder.kt (75%) create mode 100644 market/market-core/src/main/kotlin/co/nilin/opex/market/core/event/RichOrderEvent.kt rename {api/api-core/src/main/kotlin/co/nilin/opex/api => market/market-core/src/main/kotlin/co/nilin/opex/market}/core/event/RichOrderUpdate.kt (82%) rename {api/api-core/src/main/kotlin/co/nilin/opex/api => market/market-core/src/main/kotlin/co/nilin/opex/market}/core/event/RichTrade.kt (87%) rename {api/api-core/src/main/kotlin/co/nilin/opex/api => market/market-core/src/main/kotlin/co/nilin/opex/market}/core/inout/AggregatedOrderPriceModel.kt (76%) rename {api/api-core/src/main/kotlin/co/nilin/opex/api => market/market-core/src/main/kotlin/co/nilin/opex/market}/core/inout/AllOrderRequest.kt (81%) create mode 100644 market/market-core/src/main/kotlin/co/nilin/opex/market/core/inout/BestPrice.kt create mode 100644 market/market-core/src/main/kotlin/co/nilin/opex/market/core/inout/CandleData.kt create mode 100644 market/market-core/src/main/kotlin/co/nilin/opex/market/core/inout/CurrencyRate.kt create mode 100644 market/market-core/src/main/kotlin/co/nilin/opex/market/core/inout/MarketTrade.kt create mode 100644 market/market-core/src/main/kotlin/co/nilin/opex/market/core/inout/Order.kt create mode 100644 market/market-core/src/main/kotlin/co/nilin/opex/market/core/inout/OrderBook.kt create mode 100644 market/market-core/src/main/kotlin/co/nilin/opex/market/core/inout/OrderEnums.kt create mode 100644 market/market-core/src/main/kotlin/co/nilin/opex/market/core/inout/OrderMetaData.kt rename api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/PriceChangeResponse.kt => market/market-core/src/main/kotlin/co/nilin/opex/market/core/inout/PriceChange.kt (91%) create mode 100644 market/market-core/src/main/kotlin/co/nilin/opex/market/core/inout/PriceStat.kt create mode 100644 market/market-core/src/main/kotlin/co/nilin/opex/market/core/inout/PriceTicker.kt rename {api/api-core/src/main/kotlin/co/nilin/opex/api => market/market-core/src/main/kotlin/co/nilin/opex/market}/core/inout/QueryOrderRequest.kt (74%) create mode 100644 market/market-core/src/main/kotlin/co/nilin/opex/market/core/inout/RateSource.kt create mode 100644 market/market-core/src/main/kotlin/co/nilin/opex/market/core/inout/Trade.kt rename {api/api-core/src/main/kotlin/co/nilin/opex/api => market/market-core/src/main/kotlin/co/nilin/opex/market}/core/inout/TradeRequest.kt (82%) create mode 100644 market/market-core/src/main/kotlin/co/nilin/opex/market/core/inout/TradeVolumeStat.kt create mode 100644 market/market-core/src/main/kotlin/co/nilin/opex/market/core/spi/MarketQueryHandler.kt create mode 100644 market/market-core/src/main/kotlin/co/nilin/opex/market/core/spi/MarketRateService.kt create mode 100644 market/market-core/src/main/kotlin/co/nilin/opex/market/core/spi/OrderPersister.kt create mode 100644 market/market-core/src/main/kotlin/co/nilin/opex/market/core/spi/TradePersister.kt create mode 100644 market/market-core/src/main/kotlin/co/nilin/opex/market/core/spi/UserQueryHandler.kt rename {api/api-ports/api-eventlistener-kafka => market/market-ports/market-eventlistener-kafka}/pom.xml (80%) rename api/api-ports/api-eventlistener-kafka/src/main/kotlin/co/nilin/opex/api/ports/kafka/listener/config/ApiKafkaConfig.kt => market/market-ports/market-eventlistener-kafka/src/main/kotlin/co/nilin/opex/market/ports/kafka/listener/config/KafkaConsumerConfig.kt (76%) rename {api/api-ports/api-eventlistener-kafka/src/main/kotlin/co/nilin/opex/api => market/market-ports/market-eventlistener-kafka/src/main/kotlin/co/nilin/opex/market}/ports/kafka/listener/config/KafkaProducerConfig.kt (78%) rename {api/api-ports/api-eventlistener-kafka/src/main/kotlin/co/nilin/opex/api => market/market-ports/market-eventlistener-kafka/src/main/kotlin/co/nilin/opex/market}/ports/kafka/listener/config/KafkaTopicConfig.kt (95%) rename {api/api-ports/api-eventlistener-kafka/src/main/kotlin/co/nilin/opex/api => market/market-ports/market-eventlistener-kafka/src/main/kotlin/co/nilin/opex/market}/ports/kafka/listener/consumer/OrderKafkaListener.kt (80%) rename {api/api-ports/api-eventlistener-kafka/src/main/kotlin/co/nilin/opex/api => market/market-ports/market-eventlistener-kafka/src/main/kotlin/co/nilin/opex/market}/ports/kafka/listener/consumer/TradeKafkaListener.kt (80%) rename {api/api-ports/api-eventlistener-kafka/src/main/kotlin/co/nilin/opex/api => market/market-ports/market-eventlistener-kafka/src/main/kotlin/co/nilin/opex/market}/ports/kafka/listener/spi/RichOrderListener.kt (56%) rename {api/api-ports/api-eventlistener-kafka/src/main/kotlin/co/nilin/opex/api => market/market-ports/market-eventlistener-kafka/src/main/kotlin/co/nilin/opex/market}/ports/kafka/listener/spi/RichTradeListener.kt (56%) create mode 100644 market/market-ports/market-persister-postgres/pom.xml create mode 100644 market/market-ports/market-persister-postgres/src/main/kotlin/co/nilin/opex/market/ports/postgres/config/PostgresConfig.kt create mode 100644 market/market-ports/market-persister-postgres/src/main/kotlin/co/nilin/opex/market/ports/postgres/dao/CurrencyRateRepository.kt rename {api/api-ports/api-persister-postgres/src/main/kotlin/co/nilin/opex/api => market/market-ports/market-persister-postgres/src/main/kotlin/co/nilin/opex/market}/ports/postgres/dao/OrderRepository.kt (82%) rename {api/api-ports/api-persister-postgres/src/main/kotlin/co/nilin/opex/api => market/market-ports/market-persister-postgres/src/main/kotlin/co/nilin/opex/market}/ports/postgres/dao/OrderStatusRepository.kt (86%) rename {api/api-ports/api-persister-postgres/src/main/kotlin/co/nilin/opex/api => market/market-ports/market-persister-postgres/src/main/kotlin/co/nilin/opex/market}/ports/postgres/dao/TradeRepository.kt (51%) rename {api/api-ports/api-persister-postgres/src/main/kotlin/co/nilin/opex/api => market/market-ports/market-persister-postgres/src/main/kotlin/co/nilin/opex/market}/ports/postgres/impl/MarketQueryHandlerImpl.kt (62%) create mode 100644 market/market-ports/market-persister-postgres/src/main/kotlin/co/nilin/opex/market/ports/postgres/impl/MarketRateServiceImpl.kt rename {api/api-ports/api-persister-postgres/src/main/kotlin/co/nilin/opex/api => market/market-ports/market-persister-postgres/src/main/kotlin/co/nilin/opex/market}/ports/postgres/impl/OrderPersisterImpl.kt (71%) rename {api/api-ports/api-persister-postgres/src/main/kotlin/co/nilin/opex/api => market/market-ports/market-persister-postgres/src/main/kotlin/co/nilin/opex/market}/ports/postgres/impl/TradePersisterImpl.kt (52%) create mode 100644 market/market-ports/market-persister-postgres/src/main/kotlin/co/nilin/opex/market/ports/postgres/impl/UserQueryHandlerImpl.kt rename {api/api-ports/api-persister-postgres/src/main/kotlin/co/nilin/opex/api => market/market-ports/market-persister-postgres/src/main/kotlin/co/nilin/opex/market}/ports/postgres/model/CandleInfoData.kt (79%) create mode 100644 market/market-ports/market-persister-postgres/src/main/kotlin/co/nilin/opex/market/ports/postgres/model/CurrencyRateModel.kt create mode 100644 market/market-ports/market-persister-postgres/src/main/kotlin/co/nilin/opex/market/ports/postgres/model/OrderModel.kt rename {api/api-ports/api-persister-postgres/src/main/kotlin/co/nilin/opex/api => market/market-ports/market-persister-postgres/src/main/kotlin/co/nilin/opex/market}/ports/postgres/model/OrderStatusModel.kt (90%) create mode 100644 market/market-ports/market-persister-postgres/src/main/kotlin/co/nilin/opex/market/ports/postgres/model/TradeModel.kt rename {api/api-ports/api-persister-postgres/src/main/kotlin/co/nilin/opex/api => market/market-ports/market-persister-postgres/src/main/kotlin/co/nilin/opex/market}/ports/postgres/model/TradeTickerData.kt (94%) create mode 100644 market/market-ports/market-persister-postgres/src/main/kotlin/co/nilin/opex/market/ports/postgres/util/DTOExtensions.kt rename {api/api-ports/api-persister-postgres/src/main/kotlin/co/nilin/opex/api => market/market-ports/market-persister-postgres/src/main/kotlin/co/nilin/opex/market}/ports/postgres/util/EnumExtensions.kt (67%) create mode 100644 market/market-ports/market-persister-postgres/src/main/resources/schema.sql rename {api/api-ports/api-persister-postgres/src/test/kotlin/co/nilin/opex/api => market/market-ports/market-persister-postgres/src/test/kotlin/co/nilin/opex/market}/ports/postgres/impl/MarketQueryHandlerTest.kt (82%) rename {api/api-ports/api-persister-postgres/src/test/kotlin/co/nilin/opex/api => market/market-ports/market-persister-postgres/src/test/kotlin/co/nilin/opex/market}/ports/postgres/impl/OrderPersisterTest.kt (83%) create mode 100644 market/market-ports/market-persister-postgres/src/test/kotlin/co/nilin/opex/market/ports/postgres/impl/TradePersisterTest.kt rename {api/api-ports/api-persister-postgres/src/test/kotlin/co/nilin/opex/api => market/market-ports/market-persister-postgres/src/test/kotlin/co/nilin/opex/market}/ports/postgres/impl/UserQueryHandlerTest.kt (88%) create mode 100644 market/market-ports/market-persister-postgres/src/test/kotlin/co/nilin/opex/market/ports/postgres/impl/sample/Samples.kt create mode 100644 market/pom.xml create mode 100644 wallet/wallet-app/src/main/kotlin/co/nilin/opex/wallet/app/dto/OwnerLimitsResponse.kt create mode 100644 wallet/wallet-app/src/main/kotlin/co/nilin/opex/wallet/app/dto/WalletData.kt create mode 100644 wallet/wallet-app/src/main/kotlin/co/nilin/opex/wallet/app/utils/BalanceParser.kt create mode 100644 wallet/wallet-app/src/main/kotlin/co/nilin/opex/wallet/app/utils/VaultUserIdMechanism.kt diff --git a/.gitignore b/.gitignore index 14f29d0a2..8a75ab211 100644 --- a/.gitignore +++ b/.gitignore @@ -42,4 +42,3 @@ build/ !/.mvn/ .DS_Store -resources diff --git a/accountant/accountant-app/src/main/kotlin/co/nilin/opex/accountant/app/controller/AccountantController.kt b/accountant/accountant-app/src/main/kotlin/co/nilin/opex/accountant/app/controller/AccountantController.kt index a7b2b64f9..b2a1eade6 100644 --- a/accountant/accountant-app/src/main/kotlin/co/nilin/opex/accountant/app/controller/AccountantController.kt +++ b/accountant/accountant-app/src/main/kotlin/co/nilin/opex/accountant/app/controller/AccountantController.kt @@ -9,9 +9,12 @@ import co.nilin.opex.accountant.core.spi.WalletProxy import co.nilin.opex.accountant.ports.walletproxy.data.BooleanResponse import co.nilin.opex.matching.engine.core.eventh.events.SubmitOrderEvent import co.nilin.opex.matching.engine.core.model.OrderDirection +import co.nilin.opex.utility.error.data.OpexError +import co.nilin.opex.utility.error.data.OpexException import org.slf4j.LoggerFactory import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.PathVariable +import org.springframework.web.bind.annotation.RequestParam import org.springframework.web.bind.annotation.RestController import java.math.BigDecimal @@ -59,9 +62,20 @@ class AccountantController( return pairConfigLoader.loadPairConfigs() } - @GetMapping("/config/fee/all") - suspend fun fetchFeeConfigs(): List { + @GetMapping("/config/fee") + suspend fun getFeeConfigs(): List { return pairConfigLoader.loadPairFeeConfigs() .map { PairFeeResponse(it.pairConfig.pair, it.direction, it.userLevel, it.makerFee, it.takerFee) } } + + @GetMapping("/config/fee/{pair}") + suspend fun getFeeConfig( + @PathVariable pair: String, + @RequestParam(required = false) direction: OrderDirection?, + @RequestParam(required = false) userLevel: String? + ): PairFeeResponse { + val fee = pairConfigLoader.loadPairFeeConfigs(pair, direction ?: OrderDirection.BID, userLevel ?: "*") + ?: throw OpexException(OpexError.PairFeeNotFound) + return PairFeeResponse(fee.pairConfig.pair, fee.direction, fee.userLevel, fee.makerFee, fee.takerFee) + } } \ No newline at end of file diff --git a/accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/inout/RichTrade.kt b/accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/inout/RichTrade.kt index 069c2cd0a..6b22535b8 100644 --- a/accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/inout/RichTrade.kt +++ b/accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/inout/RichTrade.kt @@ -27,6 +27,7 @@ class RichTrade( val makerRemainedQuantity: BigDecimal, val makerCommision: BigDecimal, val makerCommisionAsset: String, + val matchedPrice: BigDecimal, val matchedQuantity: BigDecimal, val tradeDateTime: LocalDateTime ) \ No newline at end of file diff --git a/accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/service/TradeManagerImpl.kt b/accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/service/TradeManagerImpl.kt index b2180441f..5c4bec9f9 100644 --- a/accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/service/TradeManagerImpl.kt +++ b/accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/service/TradeManagerImpl.kt @@ -9,6 +9,7 @@ import co.nilin.opex.accountant.core.model.FinancialAction import co.nilin.opex.accountant.core.model.Order import co.nilin.opex.accountant.core.spi.* import co.nilin.opex.matching.engine.core.eventh.events.TradeEvent +import co.nilin.opex.matching.engine.core.model.OrderDirection import org.slf4j.LoggerFactory import org.springframework.transaction.annotation.Transactional import java.math.BigDecimal @@ -135,6 +136,9 @@ open class TradeManagerImpl( financialActions.add(takerFeeAction) } + val takerPrice = trade.takerPrice.toBigDecimal().multiply(takerOrder.rightSideFraction) + val makerPrice = trade.makerPrice.toBigDecimal().multiply(makerOrder.rightSideFraction) + richTradePublisher.publish( RichTrade( trade.tradeId, @@ -143,7 +147,7 @@ open class TradeManagerImpl( trade.takerUuid, trade.takerOrderId, trade.takerDirection, - trade.takerPrice.toBigDecimal().multiply(takerOrder.rightSideFraction), + takerPrice, takerOrder.origQuantity, takerOrder.origPrice.multiply(takerOrder.origQuantity), trade.takerRemainedQuantity.toBigDecimal().multiply(takerOrder.leftSideFraction), @@ -153,12 +157,13 @@ open class TradeManagerImpl( trade.makerUuid, trade.makerOrderId, trade.makerDirection, - trade.makerPrice.toBigDecimal().multiply(makerOrder.rightSideFraction), + makerPrice, makerOrder.origQuantity, makerOrder.origPrice.multiply(makerOrder.origQuantity), trade.makerRemainedQuantity.toBigDecimal().multiply(makerOrder.leftSideFraction), feeActions.makerFeeAction.amount, feeActions.makerFeeAction.symbol, + makerPrice, trade.matchedQuantity.toBigDecimal().multiply(makerOrder.leftSideFraction), trade.eventDate ) diff --git a/accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/spi/PairConfigLoader.kt b/accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/spi/PairConfigLoader.kt index bb54bf0a3..800a8fe4b 100644 --- a/accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/spi/PairConfigLoader.kt +++ b/accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/spi/PairConfigLoader.kt @@ -10,6 +10,10 @@ interface PairConfigLoader { suspend fun loadPairFeeConfigs(): List + suspend fun loadPairFeeConfigs(direction: OrderDirection, userLevel: String): List + + suspend fun loadPairFeeConfigs(pair: String, direction: OrderDirection, userLevel: String): PairFeeConfig? + suspend fun load(pair: String, direction: OrderDirection, userLevel: String): PairFeeConfig suspend fun load(pair: String, direction: OrderDirection): PairConfig diff --git a/accountant/accountant-ports/accountant-persister-postgres/src/main/kotlin/co/nilin/opex/accountant/ports/postgres/dao/PairFeeConfigRepository.kt b/accountant/accountant-ports/accountant-persister-postgres/src/main/kotlin/co/nilin/opex/accountant/ports/postgres/dao/PairFeeConfigRepository.kt index dd1bcf4df..105bdc042 100644 --- a/accountant/accountant-ports/accountant-persister-postgres/src/main/kotlin/co/nilin/opex/accountant/ports/postgres/dao/PairFeeConfigRepository.kt +++ b/accountant/accountant-ports/accountant-persister-postgres/src/main/kotlin/co/nilin/opex/accountant/ports/postgres/dao/PairFeeConfigRepository.kt @@ -6,14 +6,20 @@ import org.springframework.data.r2dbc.repository.Query import org.springframework.data.repository.query.Param import org.springframework.data.repository.reactive.ReactiveCrudRepository import org.springframework.stereotype.Repository +import reactor.core.publisher.Flux import reactor.core.publisher.Mono @Repository interface PairFeeConfigRepository : ReactiveCrudRepository { + @Query("select * from pair_fee_config where pair_config_id = :pair and direction = :direction and user_level = :userLevel") fun findByPairAndDirectionAndUserLevel( @Param("pair") pair: String, @Param("direction") direction: OrderDirection, @Param("userLevel") userLevel: String ): Mono + + @Query("select * from pair_fee_config where direction = :direction and user_level = :userLevel") + fun findByDirectionAndUserLevel(direction: OrderDirection, userLevel: String): Flux + } \ No newline at end of file diff --git a/accountant/accountant-ports/accountant-persister-postgres/src/main/kotlin/co/nilin/opex/accountant/ports/postgres/impl/PairConfigLoaderImpl.kt b/accountant/accountant-ports/accountant-persister-postgres/src/main/kotlin/co/nilin/opex/accountant/ports/postgres/impl/PairConfigLoaderImpl.kt index 89443d0e0..2df91a806 100644 --- a/accountant/accountant-ports/accountant-persister-postgres/src/main/kotlin/co/nilin/opex/accountant/ports/postgres/impl/PairConfigLoaderImpl.kt +++ b/accountant/accountant-ports/accountant-persister-postgres/src/main/kotlin/co/nilin/opex/accountant/ports/postgres/impl/PairConfigLoaderImpl.kt @@ -13,7 +13,9 @@ import co.nilin.opex.utility.error.data.OpexException import kotlinx.coroutines.reactive.awaitFirstOrElse import kotlinx.coroutines.reactive.awaitFirstOrNull import kotlinx.coroutines.reactive.awaitSingle +import kotlinx.coroutines.reactor.awaitSingleOrNull import org.springframework.stereotype.Component +import java.math.BigDecimal @Component class PairConfigLoaderImpl( @@ -38,6 +40,37 @@ class PairConfigLoaderImpl( } } + override suspend fun loadPairFeeConfigs(direction: OrderDirection, userLevel: String): List { + return pairFeeConfigRepository.findByDirectionAndUserLevel(direction, userLevel) + .collectList() + .awaitFirstOrElse { emptyList() } + .map { + PairFeeConfig( + PairConfig(it.pairConfigId, "", "", BigDecimal.ZERO, BigDecimal.ZERO), + it.direction, + it.userLevel, + it.makerFee, + it.takerFee + ) + } + } + + override suspend fun loadPairFeeConfigs( + pair: String, + direction: OrderDirection, + userLevel: String + ): PairFeeConfig? { + val fee = pairFeeConfigRepository.findByPairAndDirectionAndUserLevel(pair, direction, userLevel) + .awaitSingleOrNull() ?: return null + return PairFeeConfig( + PairConfig(pair, "", "", BigDecimal.ZERO, BigDecimal.ZERO), + fee.direction, + fee.userLevel, + fee.makerFee, + fee.takerFee + ) + } + override suspend fun load(pair: String, direction: OrderDirection, userLevel: String): PairFeeConfig { val pairConfig = pairConfigRepository.findById(pair).awaitFirstOrElse { val error = OpexError.InvalidPair diff --git a/accountant/accountant-ports/accountant-submitter-kafka/src/test/kotlin/co/nilin/opex/accountant/ports/kafka/submitter/Valid.kt b/accountant/accountant-ports/accountant-submitter-kafka/src/test/kotlin/co/nilin/opex/accountant/ports/kafka/submitter/Valid.kt index 90356d53d..2b6d754da 100644 --- a/accountant/accountant-ports/accountant-submitter-kafka/src/test/kotlin/co/nilin/opex/accountant/ports/kafka/submitter/Valid.kt +++ b/accountant/accountant-ports/accountant-submitter-kafka/src/test/kotlin/co/nilin/opex/accountant/ports/kafka/submitter/Valid.kt @@ -42,6 +42,7 @@ object Valid { 1.0.toBigDecimal(), "", 1.0.toBigDecimal(), + 1.0.toBigDecimal(), currentTime ) diff --git a/api/api-app/pom.xml b/api/api-app/pom.xml index b66a2230b..06d27bcd6 100644 --- a/api/api-app/pom.xml +++ b/api/api-app/pom.xml @@ -44,8 +44,8 @@ api-core - co.nilin.opex.api.ports.kafka.listener - api-eventlistener-kafka + co.nilin.opex.api.ports.proxy + api-proxy-rest co.nilin.opex.api.ports.binance diff --git a/api/api-app/src/main/kotlin/co/nilin/opex/api/app/config/AppConfig.kt b/api/api-app/src/main/kotlin/co/nilin/opex/api/app/config/AppConfig.kt deleted file mode 100644 index f24c6dfe5..000000000 --- a/api/api-app/src/main/kotlin/co/nilin/opex/api/app/config/AppConfig.kt +++ /dev/null @@ -1,30 +0,0 @@ -package co.nilin.opex.api.app.config - -import co.nilin.opex.api.app.listener.ApiListenerImpl -import co.nilin.opex.api.core.spi.OrderPersister -import co.nilin.opex.api.core.spi.TradePersister -import co.nilin.opex.api.ports.kafka.listener.consumer.OrderKafkaListener -import co.nilin.opex.api.ports.kafka.listener.consumer.TradeKafkaListener -import org.springframework.beans.factory.annotation.Autowired -import org.springframework.context.annotation.Bean -import org.springframework.context.annotation.Configuration - -@Configuration -class AppConfig { - - @Bean - fun apiListener(richOrderPersister: OrderPersister, richTradePersister: TradePersister): ApiListenerImpl { - return ApiListenerImpl(richOrderPersister, richTradePersister) - } - - @Autowired - fun configureListeners( - orderKafkaListener: OrderKafkaListener, - tradeKafkaListener: TradeKafkaListener, - appListener: ApiListenerImpl - ) { - orderKafkaListener.addOrderListener(appListener) - tradeKafkaListener.addTradeListener(appListener) - } - -} \ No newline at end of file diff --git a/api/api-app/src/main/kotlin/co/nilin/opex/api/app/config/InitializeService.kt b/api/api-app/src/main/kotlin/co/nilin/opex/api/app/config/InitializeService.kt index 724f59cb4..f574ee36d 100644 --- a/api/api-app/src/main/kotlin/co/nilin/opex/api/app/config/InitializeService.kt +++ b/api/api-app/src/main/kotlin/co/nilin/opex/api/app/config/InitializeService.kt @@ -13,6 +13,7 @@ import javax.annotation.PostConstruct @Component @DependsOn("postgresConfig") class InitializeService(private val symbolMapRepository: SymbolMapRepository) { + @Autowired private lateinit var preferences: Preferences diff --git a/wallet/wallet-app/src/main/kotlin/co/nilin/opex/util/vault/VaultUserIdMechanism.kt b/api/api-app/src/main/kotlin/co/nilin/opex/api/app/utils/VaultUserIdMechanism.kt similarity index 65% rename from wallet/wallet-app/src/main/kotlin/co/nilin/opex/util/vault/VaultUserIdMechanism.kt rename to api/api-app/src/main/kotlin/co/nilin/opex/api/app/utils/VaultUserIdMechanism.kt index e9efaf92a..9b6c8cdbf 100644 --- a/wallet/wallet-app/src/main/kotlin/co/nilin/opex/util/vault/VaultUserIdMechanism.kt +++ b/api/api-app/src/main/kotlin/co/nilin/opex/api/app/utils/VaultUserIdMechanism.kt @@ -1,8 +1,8 @@ -package co.nilin.opex.util.vault +package co.nilin.opex.api.app.utils import org.springframework.vault.authentication.AppIdUserIdMechanism -class VaultUserIdMechanism() : AppIdUserIdMechanism { +class VaultUserIdMechanism : AppIdUserIdMechanism { override fun createUserId(): String { return System.getenv("BACKEND_USER") } diff --git a/api/api-app/src/main/resources/application.yml b/api/api-app/src/main/resources/application.yml index 369dbba85..929405bd4 100644 --- a/api/api-app/src/main/resources/application.yml +++ b/api/api-app/src/main/resources/application.yml @@ -27,7 +27,7 @@ spring: scheme: http authentication: APPID app-id: - user-id: co.nilin.opex.util.vault.VaultUserIdMechanism + user-id: co.nilin.opex.api.app.utils.VaultUserIdMechanism fail-fast: true kv: enabled: true @@ -51,8 +51,12 @@ app: url: lb://opex-matching-gateway wallet: url: lb://opex-wallet + market: + url: lb://opex-market opex-bc-gateway: url: lb://opex-bc-gateway auth: cert-url: lb://opex-auth/auth/realms/opex/protocol/openid-connect/certs + binance: + api-url: https://api1.binance.com swagger.authUrl: ${SWAGGER_AUTH_URL:https://api.opex.dev/auth}/realms/opex/protocol/openid-connect/token diff --git a/api/api-core/src/main/kotlin/co/nilin/opex/api/core/event/RichOrderEvent.kt b/api/api-core/src/main/kotlin/co/nilin/opex/api/core/event/RichOrderEvent.kt deleted file mode 100644 index f382d586a..000000000 --- a/api/api-core/src/main/kotlin/co/nilin/opex/api/core/event/RichOrderEvent.kt +++ /dev/null @@ -1,3 +0,0 @@ -package co.nilin.opex.api.core.event - -interface RichOrderEvent \ No newline at end of file diff --git a/api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/BestPrice.kt b/api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/BestPrice.kt new file mode 100644 index 000000000..5a8893578 --- /dev/null +++ b/api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/BestPrice.kt @@ -0,0 +1,9 @@ +package co.nilin.opex.api.core.inout + +import java.math.BigDecimal + +data class BestPrice( + val symbol: String, + val bidPrice: BigDecimal?, + val askPrice: BigDecimal?, +) \ No newline at end of file diff --git a/api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/CountResponse.kt b/api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/CountResponse.kt new file mode 100644 index 000000000..d5e5e778f --- /dev/null +++ b/api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/CountResponse.kt @@ -0,0 +1,3 @@ +package co.nilin.opex.api.core.inout + +data class CountResponse(val value: Long) diff --git a/api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/CreateOrderRequest.kt b/api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/CreateOrderRequest.kt deleted file mode 100644 index 7b51525c6..000000000 --- a/api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/CreateOrderRequest.kt +++ /dev/null @@ -1,19 +0,0 @@ -package co.nilin.opex.api.core.inout - -import java.math.BigDecimal - -data class CreateOrderRequest( - val symbol: String, - val side: OrderSide, - val type: OrderType, - val timeInForce: TimeInForce?, - val quantity: BigDecimal?, - val quoteOrderQty: BigDecimal?, - val price: BigDecimal?, - val newClientOrderId: String?, /* A unique id among open orders. Automatically generated if not sent. - Orders with the same newClientOrderID can be accepted only when the previous one is filled, otherwise the order will be rejected. - */ - val stopPrice: BigDecimal?, //Used with STOP_LOSS, STOP_LOSS_LIMIT, TAKE_PROFIT, and TAKE_PROFIT_LIMIT orders. - val icebergQty: BigDecimal?, //Used with LIMIT, STOP_LOSS_LIMIT, and TAKE_PROFIT_LIMIT to create an iceberg order. - val newOrderRespType: OrderResponseType?, //Set the response JSON. ACK, RESULT, or FULL; MARKET and LIMIT order types default to FULL, all other orders default to ACK. -) \ No newline at end of file diff --git a/api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/CreateOrderResponse.kt b/api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/CreateOrderResponse.kt deleted file mode 100644 index 9d2f64078..000000000 --- a/api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/CreateOrderResponse.kt +++ /dev/null @@ -1,21 +0,0 @@ -package co.nilin.opex.api.core.inout - -import java.math.BigDecimal -import java.util.* - -data class CreateOrderResponse( - val symbol: String, - val orderId: Long, - val orderListId: Long, //Unless OCO, value will be -1 - val clientOrderId: String, - val transactTime: Date, - val price: BigDecimal?, - val origQty: BigDecimal?, - val executedQty: BigDecimal?, - val cummulativeQuoteQty: BigDecimal, - val status: OrderStatus?, - val timeInForce: TimeInForce?, - val type: OrderType?, - val side: OrderSide?, - val fills: List? -) \ No newline at end of file diff --git a/api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/CurrencyRate.kt b/api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/CurrencyRate.kt new file mode 100644 index 000000000..1ae109763 --- /dev/null +++ b/api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/CurrencyRate.kt @@ -0,0 +1,10 @@ +package co.nilin.opex.api.core.inout + +import java.math.BigDecimal + +data class CurrencyRate( + val base: String, + val quote: String, + val source: String, + val rate: BigDecimal +) \ No newline at end of file diff --git a/api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/GlobalPrice.kt b/api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/GlobalPrice.kt new file mode 100644 index 000000000..b21e80e32 --- /dev/null +++ b/api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/GlobalPrice.kt @@ -0,0 +1,6 @@ +package co.nilin.opex.api.core.inout + +data class GlobalPrice( + val symbol: String, + val price: Float +) \ No newline at end of file diff --git a/api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/MarketTradeResponse.kt b/api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/MarketTrade.kt similarity index 78% rename from api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/MarketTradeResponse.kt rename to api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/MarketTrade.kt index c8baafb1f..b74542850 100644 --- a/api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/MarketTradeResponse.kt +++ b/api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/MarketTrade.kt @@ -3,8 +3,10 @@ package co.nilin.opex.api.core.inout import java.math.BigDecimal import java.util.* -data class MarketTradeResponse( +data class MarketTrade( val symbol: String, + val baseAsset: String, + val quoteAsset: String, val id: Long, val price: BigDecimal, val qty: BigDecimal, diff --git a/api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/Order.kt b/api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/Order.kt new file mode 100644 index 000000000..5b31396bb --- /dev/null +++ b/api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/Order.kt @@ -0,0 +1,29 @@ +package co.nilin.opex.api.core.inout + +import java.math.BigDecimal +import java.time.LocalDateTime + +data class Order( + var id: Long, + val ouid: String, + val uuid: String, + val clientOrderId: String?, + val symbol: String, + val orderId: Long?, + val makerFee: BigDecimal, + val takerFee: BigDecimal, + val leftSideFraction: BigDecimal, + val rightSideFraction: BigDecimal, + val userLevel: String, + val direction: OrderDirection, + val constraint: MatchConstraint, + val type: MatchingOrderType, + val price: BigDecimal, + val quantity: BigDecimal, + val quoteQuantity: BigDecimal, + val executedQuantity: BigDecimal, + val accumulativeQuoteQty: BigDecimal, + val status: OrderStatus, + val createDate: LocalDateTime, + val updateDate: LocalDateTime, +) \ No newline at end of file diff --git a/api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/OrderBookResponse.kt b/api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/OrderBook.kt similarity index 80% rename from api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/OrderBookResponse.kt rename to api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/OrderBook.kt index 53e8f0c01..c0c1fc6f9 100644 --- a/api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/OrderBookResponse.kt +++ b/api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/OrderBook.kt @@ -2,7 +2,7 @@ package co.nilin.opex.api.core.inout import java.math.BigDecimal -data class OrderBookResponse( +data class OrderBook( val price: BigDecimal?, val quantity: BigDecimal? ) \ No newline at end of file diff --git a/api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/OrderEnums.kt b/api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/OrderEnums.kt index 8fb115e38..aba4d3351 100644 --- a/api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/OrderEnums.kt +++ b/api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/OrderEnums.kt @@ -6,37 +6,18 @@ enum class TimeInForce { FOK, //Fill or Kill, An order will expire if the full order cannot be filled upon execution. } -enum class OrderStatus(val code: Int, val orderOfAppearance: Int) { - - REQUESTED(0, 0), - NEW(1, 1), //The order has been accepted by the engine. - PARTIALLY_FILLED(4, 2), //A part of the order has been filled. - FILLED(5, 3), //The order has been completed. - CANCELED(2, 3), //The order has been canceled by the user. - REJECTED(3, 3), //The order was not accepted by the engine and not processed. - EXPIRED( - 6, - 3 - ); //The order was canceled according to the order type's rules (e.g. LIMIT FOK orders with no fill, LIMIT IOC or MARKET orders that partially fill) or by the exchange, (e.g. orders canceled during liquidation, orders canceled during maintenance) - - fun comesBefore(status: OrderStatus?): Boolean { - if (status == null) - return false - return orderOfAppearance < status.orderOfAppearance - } - - fun comesAfter(status: OrderStatus?): Boolean { - if (status == null) - return false - return orderOfAppearance > status.orderOfAppearance - } - - companion object { - fun fromCode(code: Int?): OrderStatus? { - if (code == null) - return null - return values().find { it.code == code } - } +enum class OrderStatus(val code: Int) { + + REQUESTED(0), + NEW(1), //The order has been accepted by the engine. + PARTIALLY_FILLED(4), //A part of the order has been filled. + FILLED(5), //The order has been completed. + CANCELED(2), //The order has been canceled by the user. + REJECTED(3), //The order was not accepted by the engine and not processed. + EXPIRED(6); //The order was canceled according to the order type's rules (e.g. LIMIT FOK orders with no fill, LIMIT IOC or MARKET orders that partially fill) or by the exchange, (e.g. orders canceled during liquidation, orders canceled during maintenance) + + fun isWorking(): Boolean { + return listOf(NEW, PARTIALLY_FILLED).contains(this) } } diff --git a/api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/PriceChange.kt b/api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/PriceChange.kt new file mode 100644 index 000000000..9819dbadc --- /dev/null +++ b/api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/PriceChange.kt @@ -0,0 +1,23 @@ +package co.nilin.opex.api.core.inout + +import java.math.BigDecimal + +data class PriceChange( + var symbol: String, + val priceChange: BigDecimal = BigDecimal.ZERO, + val priceChangePercent: BigDecimal = BigDecimal.ZERO, + val weightedAvgPrice: BigDecimal = BigDecimal.ZERO, + val lastPrice: BigDecimal = BigDecimal.ZERO, + val lastQty: BigDecimal = BigDecimal.ZERO, + val bidPrice: BigDecimal = BigDecimal.ZERO, + val askPrice: BigDecimal = BigDecimal.ZERO, + val openPrice: BigDecimal = BigDecimal.ZERO, + val highPrice: BigDecimal = BigDecimal.ZERO, + val lowPrice: BigDecimal = BigDecimal.ZERO, + val volume: BigDecimal = BigDecimal.ZERO, + val openTime: Long = 0, + val closeTime: Long = 0, + val firstId: Long = 0, + val lastId: Long = 0, + val count: Long = 0, +) diff --git a/api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/PriceStat.kt b/api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/PriceStat.kt new file mode 100644 index 000000000..6723195b9 --- /dev/null +++ b/api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/PriceStat.kt @@ -0,0 +1,9 @@ +package co.nilin.opex.api.core.inout + +import java.math.BigDecimal + +data class PriceStat( + var symbol: String, + val lastPrice: BigDecimal, + val priceChangePercent: Double +) \ No newline at end of file diff --git a/api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/PriceTickerResponse.kt b/api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/PriceTicker.kt similarity index 73% rename from api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/PriceTickerResponse.kt rename to api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/PriceTicker.kt index d370637ef..79df08a9a 100644 --- a/api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/PriceTickerResponse.kt +++ b/api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/PriceTicker.kt @@ -1,6 +1,6 @@ package co.nilin.opex.api.core.inout -data class PriceTickerResponse( +data class PriceTicker( val symbol: String?, val price: String? ) \ No newline at end of file diff --git a/api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/TradeResponse.kt b/api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/Trade.kt similarity index 76% rename from api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/TradeResponse.kt rename to api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/Trade.kt index ffbb1d604..6212380ba 100644 --- a/api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/TradeResponse.kt +++ b/api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/Trade.kt @@ -3,14 +3,13 @@ package co.nilin.opex.api.core.inout import java.math.BigDecimal import java.util.* -data class TradeResponse( +data class Trade( val symbol: String, val id: Long, val orderId: Long, - val orderListId: Long = -1, val price: BigDecimal, - val qty: BigDecimal, - val quoteQty: BigDecimal, + val quantity: BigDecimal, + val quoteQuantity: BigDecimal, val commission: BigDecimal, val commissionAsset: String, val time: Date, diff --git a/api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/TradeVolumeStat.kt b/api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/TradeVolumeStat.kt new file mode 100644 index 000000000..8214bea8f --- /dev/null +++ b/api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/TradeVolumeStat.kt @@ -0,0 +1,10 @@ +package co.nilin.opex.api.core.inout + +import java.math.BigDecimal + +data class TradeVolumeStat( + val symbol: String, + val volume: BigDecimal, + val tradeCount: BigDecimal, + val change: Double +) \ No newline at end of file diff --git a/api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/Wallet.kt b/api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/Wallet.kt index 72e6d2f1b..8c9276f1a 100644 --- a/api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/Wallet.kt +++ b/api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/Wallet.kt @@ -4,6 +4,7 @@ import java.math.BigDecimal data class Wallet( val asset: String, - val balance: BigDecimal, - val type: String + var balance: BigDecimal, + var locked: BigDecimal, + var withdraw: BigDecimal ) \ No newline at end of file diff --git a/api/api-core/src/main/kotlin/co/nilin/opex/api/core/spi/AccountantProxy.kt b/api/api-core/src/main/kotlin/co/nilin/opex/api/core/spi/AccountantProxy.kt index 4697b0bc8..c6a32e2ab 100644 --- a/api/api-core/src/main/kotlin/co/nilin/opex/api/core/spi/AccountantProxy.kt +++ b/api/api-core/src/main/kotlin/co/nilin/opex/api/core/spi/AccountantProxy.kt @@ -9,4 +9,6 @@ interface AccountantProxy { suspend fun getFeeConfigs(): List + suspend fun getFeeConfig(symbol: String): PairFeeResponse + } \ No newline at end of file diff --git a/api/api-core/src/main/kotlin/co/nilin/opex/api/core/spi/GlobalMarketProxy.kt b/api/api-core/src/main/kotlin/co/nilin/opex/api/core/spi/GlobalMarketProxy.kt new file mode 100644 index 000000000..bbb1ead57 --- /dev/null +++ b/api/api-core/src/main/kotlin/co/nilin/opex/api/core/spi/GlobalMarketProxy.kt @@ -0,0 +1,9 @@ +package co.nilin.opex.api.core.spi + +import co.nilin.opex.api.core.inout.GlobalPrice + +interface GlobalMarketProxy { + + suspend fun getPrices(symbols: List): List + +} \ No newline at end of file diff --git a/api/api-core/src/main/kotlin/co/nilin/opex/api/core/spi/MEGatewayProxy.kt b/api/api-core/src/main/kotlin/co/nilin/opex/api/core/spi/MEGatewayProxy.kt deleted file mode 100644 index 6ae30766f..000000000 --- a/api/api-core/src/main/kotlin/co/nilin/opex/api/core/spi/MEGatewayProxy.kt +++ /dev/null @@ -1,21 +0,0 @@ -package co.nilin.opex.api.core.spi - -import co.nilin.opex.api.core.inout.* -import java.math.BigDecimal - -interface MEGatewayProxy { - - data class CreateOrderRequest( - var uuid: String?, - val pair: String, - val price: BigDecimal, - val quantity: BigDecimal, - val direction: OrderDirection, - val matchConstraint: MatchConstraint?, - val orderType: MatchingOrderType - ) - - suspend fun createNewOrder(order: CreateOrderRequest, token: String?): OrderSubmitResult? - - suspend fun cancelOrder(request: CancelOrderRequest, token: String?): OrderSubmitResult? -} \ No newline at end of file diff --git a/api/api-core/src/main/kotlin/co/nilin/opex/api/core/spi/MarketDataProxy.kt b/api/api-core/src/main/kotlin/co/nilin/opex/api/core/spi/MarketDataProxy.kt new file mode 100644 index 000000000..2ba190aef --- /dev/null +++ b/api/api-core/src/main/kotlin/co/nilin/opex/api/core/spi/MarketDataProxy.kt @@ -0,0 +1,42 @@ +package co.nilin.opex.api.core.spi + +import co.nilin.opex.api.core.inout.* +import java.time.LocalDateTime + +interface MarketDataProxy { + + suspend fun getTradeTickerData(startFrom: Long): List + + suspend fun getTradeTickerDataBySymbol(symbol: String, startFrom: Long): PriceChange + + suspend fun openBidOrders(symbol: String, limit: Int): List + + suspend fun openAskOrders(symbol: String, limit: Int): List + + suspend fun lastOrder(symbol: String): Order? + + suspend fun recentTrades(symbol: String, limit: Int): List + + suspend fun lastPrice(symbol: String?): List + + suspend fun getBestPriceForSymbols(symbols: List): List + + suspend fun getCandleInfo( + symbol: String, + interval: String, + startTime: Long?, + endTime: Long?, + limit: Int + ): List + + suspend fun getMarketCurrencyRates(quote: String, base: String? = null): List + + suspend fun getExternalCurrencyRates(quote: String, base: String? = null): List + + suspend fun countActiveUsers(since: Long): Long + + suspend fun countTotalOrders(since: Long): Long + + suspend fun countTotalTrades(since: Long): Long + +} \ No newline at end of file diff --git a/api/api-core/src/main/kotlin/co/nilin/opex/api/core/spi/MarketQueryHandler.kt b/api/api-core/src/main/kotlin/co/nilin/opex/api/core/spi/MarketQueryHandler.kt deleted file mode 100644 index 6bd4f7509..000000000 --- a/api/api-core/src/main/kotlin/co/nilin/opex/api/core/spi/MarketQueryHandler.kt +++ /dev/null @@ -1,31 +0,0 @@ -package co.nilin.opex.api.core.spi - -import co.nilin.opex.api.core.inout.* -import kotlinx.coroutines.flow.Flow -import java.time.LocalDateTime - -interface MarketQueryHandler { - - suspend fun getTradeTickerData(startFrom: LocalDateTime): List - - suspend fun getTradeTickerDataBySymbol(symbol: String, startFrom: LocalDateTime): PriceChangeResponse - - suspend fun openBidOrders(symbol: String, limit: Int): List - - suspend fun openAskOrders(symbol: String, limit: Int): List - - suspend fun lastOrder(symbol: String): QueryOrderResponse? - - suspend fun recentTrades(symbol: String, limit: Int): Flow - - suspend fun lastPrice(symbol: String?): List - - suspend fun getCandleInfo( - symbol: String, - interval: String, - startTime: Long?, - endTime: Long?, - limit: Int - ): List - -} \ No newline at end of file diff --git a/api/api-core/src/main/kotlin/co/nilin/opex/api/core/spi/MarketStatProxy.kt b/api/api-core/src/main/kotlin/co/nilin/opex/api/core/spi/MarketStatProxy.kt new file mode 100644 index 000000000..5bcc7921c --- /dev/null +++ b/api/api-core/src/main/kotlin/co/nilin/opex/api/core/spi/MarketStatProxy.kt @@ -0,0 +1,16 @@ +package co.nilin.opex.api.core.spi + +import co.nilin.opex.api.core.inout.PriceStat +import co.nilin.opex.api.core.inout.TradeVolumeStat + +interface MarketStatProxy { + + suspend fun getMostIncreasedInPricePairs(interval: Long, limit: Int): List + + suspend fun getMostDecreasedInPricePairs(interval: Long, limit: Int): List + + suspend fun getHighestVolumePair(interval: Long): TradeVolumeStat? + + suspend fun getTradeCountPair(interval: Long): TradeVolumeStat? + +} \ No newline at end of file diff --git a/api/api-core/src/main/kotlin/co/nilin/opex/api/core/spi/MarketUserDataProxy.kt b/api/api-core/src/main/kotlin/co/nilin/opex/api/core/spi/MarketUserDataProxy.kt new file mode 100644 index 000000000..813e2ba04 --- /dev/null +++ b/api/api-core/src/main/kotlin/co/nilin/opex/api/core/spi/MarketUserDataProxy.kt @@ -0,0 +1,30 @@ +package co.nilin.opex.api.core.spi + +import co.nilin.opex.api.core.inout.Order +import co.nilin.opex.api.core.inout.Trade +import java.security.Principal +import java.util.* + +interface MarketUserDataProxy { + + suspend fun queryOrder(principal: Principal, symbol: String, orderId: Long?, origClientOrderId: String?): Order? + + suspend fun openOrders(principal: Principal, symbol: String?): List + + suspend fun allOrders( + principal: Principal, + symbol: String?, + startTime: Date?, + endTime: Date?, + limit: Int? + ): List + + suspend fun allTrades( + principal: Principal, + symbol: String?, + fromTrade: Long?, + startTime: Date?, + endTime: Date?, + limit: Int? + ): List +} \ No newline at end of file diff --git a/api/api-core/src/main/kotlin/co/nilin/opex/api/core/spi/MatchingGatewayProxy.kt b/api/api-core/src/main/kotlin/co/nilin/opex/api/core/spi/MatchingGatewayProxy.kt new file mode 100644 index 000000000..973ad9b58 --- /dev/null +++ b/api/api-core/src/main/kotlin/co/nilin/opex/api/core/spi/MatchingGatewayProxy.kt @@ -0,0 +1,29 @@ +package co.nilin.opex.api.core.spi + +import co.nilin.opex.api.core.inout.MatchConstraint +import co.nilin.opex.api.core.inout.MatchingOrderType +import co.nilin.opex.api.core.inout.OrderDirection +import co.nilin.opex.api.core.inout.OrderSubmitResult +import java.math.BigDecimal + +interface MatchingGatewayProxy { + + suspend fun createNewOrder( + uuid: String?, + pair: String, + price: BigDecimal, + quantity: BigDecimal, + direction: OrderDirection, + matchConstraint: MatchConstraint?, + orderType: MatchingOrderType, + token: String? + ): OrderSubmitResult? + + suspend fun cancelOrder( + ouid: String, + uuid: String, + orderId: Long, + symbol: String, + token: String? + ): OrderSubmitResult? +} \ No newline at end of file diff --git a/api/api-core/src/main/kotlin/co/nilin/opex/api/core/spi/OrderPersister.kt b/api/api-core/src/main/kotlin/co/nilin/opex/api/core/spi/OrderPersister.kt deleted file mode 100644 index 9e7aa7a74..000000000 --- a/api/api-core/src/main/kotlin/co/nilin/opex/api/core/spi/OrderPersister.kt +++ /dev/null @@ -1,11 +0,0 @@ -package co.nilin.opex.api.core.spi - -import co.nilin.opex.api.core.event.RichOrder -import co.nilin.opex.api.core.event.RichOrderUpdate - -interface OrderPersister { - - suspend fun save(order: RichOrder) - - suspend fun update(orderUpdate: RichOrderUpdate) -} \ No newline at end of file diff --git a/api/api-core/src/main/kotlin/co/nilin/opex/api/core/spi/SymbolMapper.kt b/api/api-core/src/main/kotlin/co/nilin/opex/api/core/spi/SymbolMapper.kt index da866f989..ce66de33f 100644 --- a/api/api-core/src/main/kotlin/co/nilin/opex/api/core/spi/SymbolMapper.kt +++ b/api/api-core/src/main/kotlin/co/nilin/opex/api/core/spi/SymbolMapper.kt @@ -2,9 +2,9 @@ package co.nilin.opex.api.core.spi interface SymbolMapper { - suspend fun map(symbol: String?): String? + suspend fun fromInternalSymbol(symbol: String?): String? - suspend fun unmap(alias: String?): String? + suspend fun toInternalSymbol(alias: String?): String? suspend fun symbolToAliasMap(): Map } diff --git a/api/api-core/src/main/kotlin/co/nilin/opex/api/core/spi/TradePersister.kt b/api/api-core/src/main/kotlin/co/nilin/opex/api/core/spi/TradePersister.kt deleted file mode 100644 index 8503fa603..000000000 --- a/api/api-core/src/main/kotlin/co/nilin/opex/api/core/spi/TradePersister.kt +++ /dev/null @@ -1,7 +0,0 @@ -package co.nilin.opex.api.core.spi - -import co.nilin.opex.api.core.event.RichTrade - -interface TradePersister { - suspend fun save(trade: RichTrade) -} \ No newline at end of file diff --git a/api/api-core/src/main/kotlin/co/nilin/opex/api/core/spi/UserQueryHandler.kt b/api/api-core/src/main/kotlin/co/nilin/opex/api/core/spi/UserQueryHandler.kt deleted file mode 100644 index 0fb37a671..000000000 --- a/api/api-core/src/main/kotlin/co/nilin/opex/api/core/spi/UserQueryHandler.kt +++ /dev/null @@ -1,12 +0,0 @@ -package co.nilin.opex.api.core.spi - -import co.nilin.opex.api.core.inout.* -import kotlinx.coroutines.flow.Flow -import java.security.Principal - -interface UserQueryHandler { - suspend fun queryOrder(principal: Principal, request: QueryOrderRequest): QueryOrderResponse? - suspend fun openOrders(principal: Principal, symbol: String?): Flow - suspend fun allOrders(principal: Principal, allOrderRequest: AllOrderRequest): Flow - suspend fun allTrades(principal: Principal, request: TradeRequest): Flow -} \ No newline at end of file diff --git a/api/api-core/src/main/kotlin/co/nilin/opex/api/core/spi/WalletProxy.kt b/api/api-core/src/main/kotlin/co/nilin/opex/api/core/spi/WalletProxy.kt index 6289963df..b93517a00 100644 --- a/api/api-core/src/main/kotlin/co/nilin/opex/api/core/spi/WalletProxy.kt +++ b/api/api-core/src/main/kotlin/co/nilin/opex/api/core/spi/WalletProxy.kt @@ -9,6 +9,8 @@ interface WalletProxy { suspend fun getWallets(uuid: String?, token: String?): List + suspend fun getWallet(uuid: String?, token: String?, symbol: String): Wallet + suspend fun getOwnerLimits(uuid: String?, token: String?): OwnerLimitsResponse suspend fun getDepositTransactions( diff --git a/api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/api/ports/binance/data/Interval.kt b/api/api-core/src/main/kotlin/co/nilin/opex/api/core/utils/Interval.kt similarity index 89% rename from api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/api/ports/binance/data/Interval.kt rename to api/api-core/src/main/kotlin/co/nilin/opex/api/core/utils/Interval.kt index cd242b69d..a4fc97d88 100644 --- a/api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/api/ports/binance/data/Interval.kt +++ b/api/api-core/src/main/kotlin/co/nilin/opex/api/core/utils/Interval.kt @@ -1,4 +1,4 @@ -package co.nilin.opex.api.ports.binance.data +package co.nilin.opex.api.core.utils import java.time.Instant import java.time.LocalDateTime @@ -22,9 +22,11 @@ enum class Interval(val label: String, val unit: TimeUnit, val duration: Long) { TwentyFourHours("24h", TimeUnit.HOURS, 24), Day("1d", TimeUnit.DAYS, 1), ThreeDays("3d", TimeUnit.DAYS, 3), + SevenDay("7d", TimeUnit.DAYS, 7), Week("1w", TimeUnit.DAYS, 7), Month("1M", TimeUnit.DAYS, 31), - ThreeMonth("3M", TimeUnit.DAYS, 90); + ThreeMonth("3M", TimeUnit.DAYS, 90), + Year("1Y", TimeUnit.DAYS, 365); private fun getOffsetTime() = unit.toMillis(duration) diff --git a/api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/api/ports/binance/util/LoggerDelegate.kt b/api/api-core/src/main/kotlin/co/nilin/opex/api/core/utils/LoggerDelegate.kt similarity index 92% rename from api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/api/ports/binance/util/LoggerDelegate.kt rename to api/api-core/src/main/kotlin/co/nilin/opex/api/core/utils/LoggerDelegate.kt index 85bb44abc..405dc639e 100644 --- a/api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/api/ports/binance/util/LoggerDelegate.kt +++ b/api/api-core/src/main/kotlin/co/nilin/opex/api/core/utils/LoggerDelegate.kt @@ -1,4 +1,4 @@ -package co.nilin.opex.api.ports.binance.util +package co.nilin.opex.api.core.utils import org.slf4j.Logger import org.slf4j.LoggerFactory diff --git a/api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/api/ports/binance/config/SecurityConfig.kt b/api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/api/ports/binance/config/SecurityConfig.kt index 0e36d28e5..b9a1b33d1 100644 --- a/api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/api/ports/binance/config/SecurityConfig.kt +++ b/api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/api/ports/binance/config/SecurityConfig.kt @@ -29,6 +29,7 @@ class SecurityConfig(private val webClient: WebClient) { .pathMatchers("/v3/exchangeInfo").permitAll() .pathMatchers("/v3/klines").permitAll() .pathMatchers("/socket").permitAll() + .pathMatchers("/v1/landing/**").permitAll() .pathMatchers("/**").hasAuthority("SCOPE_trust") .anyExchange().authenticated() .and() diff --git a/api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/api/ports/binance/config/WebClientConfig.kt b/api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/api/ports/binance/config/WebClientConfig.kt index 7c30597f8..2248bd0b4 100644 --- a/api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/api/ports/binance/config/WebClientConfig.kt +++ b/api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/api/ports/binance/config/WebClientConfig.kt @@ -1,12 +1,13 @@ package co.nilin.opex.api.ports.binance.config import co.nilin.opex.utility.log.CustomLogger +import org.springframework.beans.factory.annotation.Qualifier import org.springframework.cloud.client.ServiceInstance -import org.springframework.cloud.client.loadbalancer.LoadBalancerProperties import org.springframework.cloud.client.loadbalancer.reactive.ReactiveLoadBalancer import org.springframework.cloud.client.loadbalancer.reactive.ReactorLoadBalancerExchangeFilterFunction import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration +import org.springframework.context.annotation.Primary import org.springframework.http.client.reactive.ReactorClientHttpConnector import org.springframework.web.reactive.function.client.WebClient import reactor.netty.http.client.HttpClient @@ -22,16 +23,10 @@ class WebClientConfig { ReactorClientHttpConnector( HttpClient .create() - .doOnRequest { request, connection -> - connection.addHandlerFirst(logger) - } - ) - ) - .filter( - ReactorLoadBalancerExchangeFilterFunction( - loadBalancerFactory, LoadBalancerProperties(), emptyList() + .doOnRequest { _, connection -> connection.addHandlerFirst(logger) } ) ) + .filter(ReactorLoadBalancerExchangeFilterFunction(loadBalancerFactory, emptyList())) .build() } diff --git a/api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/api/ports/binance/controller/AccountController.kt b/api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/api/ports/binance/controller/AccountController.kt index f8f54f5b2..8e92ad7cb 100644 --- a/api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/api/ports/binance/controller/AccountController.kt +++ b/api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/api/ports/binance/controller/AccountController.kt @@ -1,125 +1,39 @@ package co.nilin.opex.api.ports.binance.controller import co.nilin.opex.api.core.inout.* -import co.nilin.opex.api.core.spi.MEGatewayProxy +import co.nilin.opex.api.core.spi.MarketUserDataProxy +import co.nilin.opex.api.core.spi.MatchingGatewayProxy import co.nilin.opex.api.core.spi.SymbolMapper -import co.nilin.opex.api.core.spi.UserQueryHandler import co.nilin.opex.api.core.spi.WalletProxy -import co.nilin.opex.api.ports.binance.data.AccountInfoResponse +import co.nilin.opex.api.ports.binance.data.* import co.nilin.opex.api.ports.binance.util.* import co.nilin.opex.utility.error.data.OpexError import co.nilin.opex.utility.error.data.OpexException -import com.fasterxml.jackson.annotation.JsonInclude import io.swagger.annotations.ApiParam import io.swagger.annotations.ApiResponse import io.swagger.annotations.Example import io.swagger.annotations.ExampleProperty -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.map import org.springframework.http.MediaType import org.springframework.security.core.annotation.CurrentSecurityContext import org.springframework.security.core.context.SecurityContext import org.springframework.web.bind.annotation.* import java.math.BigDecimal import java.security.Principal +import java.time.ZoneId import java.util.* @RestController class AccountController( - val queryHandler: UserQueryHandler, - val matchingGatewayProxy: MEGatewayProxy, + val queryHandler: MarketUserDataProxy, + val matchingGatewayProxy: MatchingGatewayProxy, val walletProxy: WalletProxy, val symbolMapper: SymbolMapper ) { - data class FillsData( - val price: BigDecimal, - val qty: BigDecimal, - val commission: BigDecimal, - val commissionAsset: String - ) - - @JsonInclude(JsonInclude.Include.NON_NULL) - data class NewOrderResponse( - val symbol: String, - val orderId: Long, - val orderListId: Long, //Unless OCO, value will be -1 - val clientOrderId: String?, - val transactTime: Date, - val price: BigDecimal?, - val origQty: BigDecimal?, - val executedQty: BigDecimal?, - val cummulativeQuoteQty: BigDecimal?, - val status: OrderStatus?, - val timeInForce: TimeInForce?, - val type: OrderType?, - val side: OrderSide?, - val fills: List? - ) - - @JsonInclude(JsonInclude.Include.NON_NULL) - data class CancelOrderResponse( - val symbol: String, - val origClientOrderId: String?, - val orderId: Long?, - val orderListId: Long, //Unless OCO, value will be -1 - val clientOrderId: String?, - val price: BigDecimal?, - val origQty: BigDecimal?, - val executedQty: BigDecimal?, - val cummulativeQuoteQty: BigDecimal?, - val status: OrderStatus?, - val timeInForce: TimeInForce?, - val type: OrderType?, - val side: OrderSide? - ) - - @JsonInclude(JsonInclude.Include.NON_NULL) - data class QueryOrderResponse( - val symbol: String, - val ouid: String, - val orderId: Long, - val orderListId: Long, //Unless part of an OCO, the value will always be -1. - val clientOrderId: String, - val price: BigDecimal, - val origQty: BigDecimal, - val executedQty: BigDecimal, - val cummulativeQuoteQty: BigDecimal, - val status: OrderStatus, - val timeInForce: TimeInForce, - val type: OrderType, - val side: OrderSide, - val stopPrice: BigDecimal?, - val icebergQty: BigDecimal?, - val time: Date, - val updateTime: Date, - val isWorking: Boolean, - val origQuoteOrderQty: BigDecimal - ) - - @JsonInclude(JsonInclude.Include.NON_NULL) - data class TradeResponse( - val symbol: String, - val id: Long, - val orderId: Long, - val orderListId: Long = -1, - val price: BigDecimal, - val qty: BigDecimal, - val quoteQty: BigDecimal, - val commission: BigDecimal, - val commissionAsset: String, - val time: Date, - val isBuyer: Boolean, - val isMaker: Boolean, - val isBestMatch: Boolean - ) - - private val logger by LoggerDelegate() - /* - Send in a new order. - Weight: 1 - Data Source: Matching Engine + Send in a new order. + Weight: 1 + Data Source: Matching Engine */ @PostMapping( "/v3/order", @@ -175,19 +89,17 @@ class AccountController( timestamp: Long, @CurrentSecurityContext securityContext: SecurityContext ): NewOrderResponse { - val internalSymbol = symbolMapper.unmap(symbol) ?: throw OpexException(OpexError.SymbolNotFound) + val internalSymbol = symbolMapper.toInternalSymbol(symbol) ?: throw OpexException(OpexError.SymbolNotFound) - val request = MEGatewayProxy.CreateOrderRequest( + matchingGatewayProxy.createNewOrder( securityContext.jwtAuthentication().name, internalSymbol, price ?: BigDecimal.ZERO, // Maybe make this nullable as well? quantity ?: BigDecimal.ZERO, side.asOrderDirection(), timeInForce?.asMatchConstraint(), - type.asMatchingOrderType() + type.asMatchingOrderType(), securityContext.jwtAuthentication().tokenValue() ) - - matchingGatewayProxy.createNewOrder(request, securityContext.jwtAuthentication().tokenValue()) return NewOrderResponse( symbol, -1, @@ -228,11 +140,11 @@ class AccountController( timestamp: Long, @CurrentSecurityContext securityContext: SecurityContext ): CancelOrderResponse { - val localSymbol = symbolMapper.unmap(symbol) ?: throw OpexException(OpexError.SymbolNotFound) + val localSymbol = symbolMapper.toInternalSymbol(symbol) ?: throw OpexException(OpexError.SymbolNotFound) if (orderId == null && origClientOrderId == null) throw OpexException(OpexError.BadRequest, message = "'orderId' or 'origClientOrderId' must be sent") - val order = queryHandler.queryOrder(principal, QueryOrderRequest(localSymbol, orderId, origClientOrderId)) + val order = queryHandler.queryOrder(principal, localSymbol, orderId, origClientOrderId) ?: throw OpexException(OpexError.OrderNotFound) val response = CancelOrderResponse( @@ -242,13 +154,13 @@ class AccountController( -1, null, order.price, - order.origQty, - order.executedQty, - order.cummulativeQuoteQty, + order.quantity, + order.executedQuantity, + order.accumulativeQuoteQty, OrderStatus.CANCELED, - order.timeInForce, - order.type, - order.side + order.constraint.asTimeInForce(), + order.type.asOrderType(), + order.direction.asOrderSide() ) if (order.status == OrderStatus.CANCELED) @@ -258,8 +170,13 @@ class AccountController( throw OpexException(OpexError.CancelOrderNotAllowed) - val request = CancelOrderRequest(order.ouid, principal.name, order.orderId, localSymbol) - matchingGatewayProxy.cancelOrder(request, securityContext.jwtAuthentication().tokenValue()) + matchingGatewayProxy.cancelOrder( + order.ouid, + principal.name, + order.orderId ?: 0, + localSymbol, + securityContext.jwtAuthentication().tokenValue() + ) return response } @@ -298,35 +215,12 @@ class AccountController( @RequestParam(name = "timestamp") timestamp: Long ): QueryOrderResponse { - val internalSymbol = symbolMapper.unmap(symbol) ?: throw OpexException(OpexError.SymbolNotFound) - - val response = queryHandler.queryOrder(principal, QueryOrderRequest(internalSymbol, orderId, origClientOrderId)) + val internalSymbol = symbolMapper.toInternalSymbol(symbol) ?: throw OpexException(OpexError.SymbolNotFound) + return queryHandler.queryOrder(principal, internalSymbol, orderId, origClientOrderId) + ?.asQueryOrderResponse() ?: throw OpexException(OpexError.OrderNotFound) - - return QueryOrderResponse( - symbol, - response.ouid, - response.orderId, - response.orderListId, - response.clientOrderId, - response.price, - response.origQty, - response.executedQty, - response.cummulativeQuoteQty, - response.status, - response.timeInForce, - response.type, - response.side, - response.stopPrice, - response.icebergQty, - response.time, - response.updateTime, - response.isWorking, - response.origQuoteOrderQty - ) } - /* Get all open orders on a symbol. Careful when accessing this with no symbol. @@ -358,33 +252,9 @@ class AccountController( recvWindow: Long?, //The value cannot be greater than 60000 @RequestParam(name = "timestamp") timestamp: Long - ): Flow { - val internalSymbol = symbolMapper.unmap(symbol) ?: throw OpexException(OpexError.SymbolNotFound) - - return queryHandler.openOrders(principal, internalSymbol) - .map { response -> - QueryOrderResponse( - symbol ?: "", - response.ouid, - response.orderId, - response.orderListId, - response.clientOrderId, - response.price, - response.origQty, - response.executedQty, - response.cummulativeQuoteQty, - response.status, - response.timeInForce, - response.type, - response.side, - response.stopPrice, - response.icebergQty, - response.time, - response.updateTime, - response.isWorking, - response.origQuoteOrderQty - ) - } + ): List { + val internalSymbol = symbolMapper.toInternalSymbol(symbol) ?: throw OpexException(OpexError.SymbolNotFound) + return queryHandler.openOrders(principal, internalSymbol).map { it.asQueryOrderResponse() } } /* @@ -423,33 +293,10 @@ class AccountController( recvWindow: Long?, //The value cannot be greater than 60000 @RequestParam(name = "timestamp") timestamp: Long - ): Flow { - val internalSymbol = symbolMapper.unmap(symbol) ?: throw OpexException(OpexError.SymbolNotFound) - - return queryHandler.allOrders(principal, AllOrderRequest(internalSymbol, startTime, endTime, limit)) - .map { response -> - QueryOrderResponse( - symbol ?: "", - response.ouid, - response.orderId, - response.orderListId, - response.clientOrderId, - response.price, - response.origQty, - response.executedQty, - response.cummulativeQuoteQty, - response.status, - response.timeInForce, - response.type, - response.side, - response.stopPrice, - response.icebergQty, - response.time, - response.updateTime, - response.isWorking, - response.origQuoteOrderQty - ) - } + ): List { + val internalSymbol = symbolMapper.toInternalSymbol(symbol) ?: throw OpexException(OpexError.SymbolNotFound) + return queryHandler.allOrders(principal, internalSymbol, startTime, endTime, limit) + .map { it.asQueryOrderResponse() } } /* @@ -492,25 +339,25 @@ class AccountController( recvWindow: Long?, //The value cannot be greater than 60000 @RequestParam(name = "timestamp") timestamp: Long - ): Flow { - val internalSymbol = symbolMapper.unmap(symbol) ?: throw OpexException(OpexError.SymbolNotFound) + ): List { + val internalSymbol = symbolMapper.toInternalSymbol(symbol) ?: throw OpexException(OpexError.SymbolNotFound) - return queryHandler.allTrades(principal, TradeRequest(internalSymbol, fromId, startTime, endTime, limit)) - .map { response -> + return queryHandler.allTrades(principal, internalSymbol, fromId, startTime, endTime, limit) + .map { TradeResponse( symbol ?: "", - response.id, - response.orderId, + it.id, + it.orderId, -1, - response.price, - response.qty, - response.quoteQty, - response.commission, - response.commissionAsset, - response.time, - response.isBuyer, - response.isMaker, - response.isBestMatch + it.price, + it.quantity, + it.quoteQuantity, + it.commission, + it.commissionAsset, + it.time, + it.isBuyer, + it.isMaker, + it.isBestMatch ) } } @@ -541,7 +388,6 @@ class AccountController( val auth = securityContext.jwtAuthentication() val wallets = walletProxy.getWallets(auth.name, auth.tokenValue()) val limits = walletProxy.getOwnerLimits(auth.name, auth.tokenValue()) - val parsedBalances = BalanceParser.parse(wallets) val accountType = "SPOT" //TODO replace commissions and accountType with actual data @@ -555,9 +401,31 @@ class AccountController( limits.canDeposit, Date().time, accountType, - parsedBalances, + wallets.map { BalanceResponse(it.asset, it.balance, it.locked, it.withdraw) }, listOf(accountType) ) } + private fun Order.asQueryOrderResponse() = QueryOrderResponse( + symbol, + ouid, + orderId ?: 0, + -1, + "", + price, + quantity, + executedQuantity, + accumulativeQuoteQty, + status, + constraint.asTimeInForce(), + type.asOrderType(), + direction.asOrderSide(), + null, + null, + Date.from(createDate.atZone(ZoneId.systemDefault()).toInstant()), + Date.from(updateDate.atZone(ZoneId.systemDefault()).toInstant()), + status.isWorking(), + quoteQuantity + ) + } \ No newline at end of file diff --git a/api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/api/ports/binance/controller/LandingController.kt b/api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/api/ports/binance/controller/LandingController.kt new file mode 100644 index 000000000..8f5a2469f --- /dev/null +++ b/api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/api/ports/binance/controller/LandingController.kt @@ -0,0 +1,74 @@ +package co.nilin.opex.api.ports.binance.controller + +import co.nilin.opex.api.core.spi.GlobalMarketProxy +import co.nilin.opex.api.core.spi.MarketDataProxy +import co.nilin.opex.api.core.spi.MarketStatProxy +import co.nilin.opex.api.core.spi.SymbolMapper +import co.nilin.opex.api.core.utils.Interval +import co.nilin.opex.api.ports.binance.data.GlobalPriceResponse +import co.nilin.opex.api.ports.binance.data.MarketInfoResponse +import co.nilin.opex.api.ports.binance.data.MarketStatResponse +import org.slf4j.LoggerFactory +import org.springframework.web.bind.annotation.GetMapping +import org.springframework.web.bind.annotation.RequestMapping +import org.springframework.web.bind.annotation.RequestParam +import org.springframework.web.bind.annotation.RestController + +@RestController // Custom service +@RequestMapping("/v1/landing") +class LandingController( + private val marketStatProxy: MarketStatProxy, + private val marketDataProxy: MarketDataProxy, + private val globalMarketProxy: GlobalMarketProxy, + private val symbolMapper: SymbolMapper +) { + + private val logger = LoggerFactory.getLogger(LandingController::class.java) + + @GetMapping("/globalPrices") + suspend fun getCurrencyPrices(@RequestParam usdSymbol: String): GlobalPriceResponse { + val irtUSDPrice = marketDataProxy.getExternalCurrencyRates("IRT", usdSymbol) + val globalPrice = try { + globalMarketProxy.getPrices(symbolMapper.symbolToAliasMap().entries.map { it.value }) + } catch (e: Exception) { + logger.error("Could not fetch prices") + emptyList() + } + + return GlobalPriceResponse(irtUSDPrice, globalPrice) + } + + @GetMapping("/marketStats") + suspend fun getMarketStats( + @RequestParam interval: String, + @RequestParam(required = false) limit: Int? + ): MarketStatResponse { + val since = (Interval.findByLabel(interval) ?: Interval.Week).getDate().time + val validLimit = getValidLimit(limit) + + return MarketStatResponse( + marketStatProxy.getMostIncreasedInPricePairs(since, validLimit), + marketStatProxy.getMostDecreasedInPricePairs(since, validLimit), + marketStatProxy.getHighestVolumePair(since), + marketStatProxy.getTradeCountPair(since) + ) + } + + @GetMapping("/exchangeInfo") + suspend fun marketInfo(@RequestParam interval: String): MarketInfoResponse { + val since = (Interval.findByLabel(interval) ?: Interval.ThreeMonth).getDate().time + return MarketInfoResponse( + marketDataProxy.countActiveUsers(since), + marketDataProxy.countTotalOrders(since), + marketDataProxy.countTotalTrades(since), + ) + } + + private fun getValidLimit(limit: Int?): Int = when { + limit == null -> 100 + limit > 1000 -> 1000 + limit < 1 -> 1 + else -> limit + } + +} \ No newline at end of file diff --git a/api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/api/ports/binance/controller/MarketController.kt b/api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/api/ports/binance/controller/MarketController.kt index 6fcceabad..7bbdcdb1d 100644 --- a/api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/api/ports/binance/controller/MarketController.kt +++ b/api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/api/ports/binance/controller/MarketController.kt @@ -1,16 +1,16 @@ package co.nilin.opex.api.ports.binance.controller -import co.nilin.opex.api.core.inout.PriceChangeResponse -import co.nilin.opex.api.core.inout.PriceTickerResponse +import co.nilin.opex.api.core.inout.PriceChange +import co.nilin.opex.api.core.inout.PriceTicker import co.nilin.opex.api.core.spi.AccountantProxy -import co.nilin.opex.api.core.spi.MarketQueryHandler +import co.nilin.opex.api.core.spi.MarketDataProxy +import co.nilin.opex.api.core.spi.MarketStatProxy import co.nilin.opex.api.core.spi.SymbolMapper +import co.nilin.opex.api.core.utils.Interval import co.nilin.opex.api.ports.binance.data.* import co.nilin.opex.utility.error.data.OpexError import co.nilin.opex.utility.error.data.OpexException import co.nilin.opex.utility.error.data.throwError -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.map import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.PathVariable import org.springframework.web.bind.annotation.RequestParam @@ -22,8 +22,8 @@ import java.time.ZoneId @RestController class MarketController( private val accountantProxy: AccountantProxy, - private val marketQueryHandler: MarketQueryHandler, - private val symbolMapper: SymbolMapper, + private val marketDataProxy: MarketDataProxy, + private val symbolMapper: SymbolMapper ) { private val orderBookValidLimits = arrayListOf(5, 10, 20, 50, 100, 500, 1000, 5000) @@ -42,15 +42,15 @@ class MarketController( limit: Int? // Default 100; max 5000. Valid limits:[5, 10, 20, 50, 100, 500, 1000, 5000] ): OrderBookResponse { val validLimit = limit ?: 100 - val localSymbol = symbolMapper.unmap(symbol) ?: throw OpexException(OpexError.SymbolNotFound) + val localSymbol = symbolMapper.toInternalSymbol(symbol) ?: throw OpexException(OpexError.SymbolNotFound) if (!orderBookValidLimits.contains(validLimit)) throwError(OpexError.InvalidLimitForOrderBook) val mappedBidOrders = ArrayList>() val mappedAskOrders = ArrayList>() - val bidOrders = marketQueryHandler.openBidOrders(localSymbol, validLimit) - val askOrders = marketQueryHandler.openAskOrders(localSymbol, validLimit) + val bidOrders = marketDataProxy.openBidOrders(localSymbol, validLimit) + val askOrders = marketDataProxy.openAskOrders(localSymbol, validLimit) bidOrders.forEach { val mapped = arrayListOf().apply { @@ -68,7 +68,7 @@ class MarketController( mappedAskOrders.add(mapped) } - val lastOrder = marketQueryHandler.lastOrder(localSymbol) + val lastOrder = marketDataProxy.lastOrder(localSymbol) return OrderBookResponse(lastOrder?.orderId ?: -1, mappedBidOrders, mappedAskOrders) } @@ -79,13 +79,13 @@ class MarketController( symbol: String, @RequestParam("limit", required = false) limit: Int? // Default 500; max 1000. - ): Flow { + ): List { val validLimit = limit ?: 500 - val localSymbol = symbolMapper.unmap(symbol) ?: throw OpexException(OpexError.SymbolNotFound) + val localSymbol = symbolMapper.toInternalSymbol(symbol) ?: throw OpexException(OpexError.SymbolNotFound) if (validLimit !in 1..1000) throwError(OpexError.InvalidLimitForRecentTrades) - return marketQueryHandler.recentTrades(localSymbol, validLimit) + return marketDataProxy.recentTrades(localSymbol, validLimit) .map { RecentTradeResponse( it.id, @@ -105,33 +105,43 @@ class MarketController( duration: String, @RequestParam("symbol", required = false) symbol: String?, - ): List { + ): List { val localSymbol = if (symbol.isNullOrEmpty()) null else - symbolMapper.unmap(symbol) ?: throw OpexException(OpexError.SymbolNotFound) + symbolMapper.toInternalSymbol(symbol) ?: throw OpexException(OpexError.SymbolNotFound) if (!validDurations.contains(duration)) throwError(OpexError.InvalidPriceChangeDuration) - val startDate = Interval.findByLabel(duration)?.getLocalDateTime() ?: Interval.Day.getLocalDateTime() + val startDate = Interval.findByLabel(duration) ?: Interval.Week - return if (symbol.isNullOrEmpty()) - marketQueryHandler.getTradeTickerData(startDate) + val result = if (symbol.isNullOrEmpty()) + marketDataProxy.getTradeTickerData(startDate.getDate().time).toMutableList() else - listOf(marketQueryHandler.getTradeTickerDataBySymbol(localSymbol!!, startDate)) + arrayListOf(marketDataProxy.getTradeTickerDataBySymbol(localSymbol!!, startDate.getDate().time)) + + symbolMapper.symbolToAliasMap().entries.forEach { map -> + val price = result.find { it.symbol == map.key } + if (price == null && symbol.isNullOrEmpty()) + result.add(PriceChange(map.value)) + else + price?.symbol = map.value + } + + return result } // Weight // 1 for a single symbol // 2 when the symbol parameter is omitted @GetMapping("/v3/ticker/price") - suspend fun priceTicker(@RequestParam("symbol", required = false) symbol: String?): List { + suspend fun priceTicker(@RequestParam("symbol", required = false) symbol: String?): List { val localSymbol = if (symbol == null) null else - symbolMapper.unmap(symbol) ?: throw OpexException(OpexError.SymbolNotFound) - return marketQueryHandler.lastPrice(localSymbol) + symbolMapper.toInternalSymbol(symbol) ?: throw OpexException(OpexError.SymbolNotFound) + return marketDataProxy.lastPrice(localSymbol) } @GetMapping("/v3/exchangeInfo") @@ -172,14 +182,14 @@ class MarketController( limit: Int? // Default 500; max 1000. ): List> { val validLimit = limit ?: 500 - val localSymbol = symbolMapper.unmap(symbol) ?: throw OpexException(OpexError.SymbolNotFound) + val localSymbol = symbolMapper.toInternalSymbol(symbol) ?: throw OpexException(OpexError.SymbolNotFound) if (validLimit !in 1..1000) throwError(OpexError.InvalidLimitForRecentTrades) val i = Interval.findByLabel(interval) ?: throw OpexException(OpexError.InvalidInterval) val list = ArrayList>() - marketQueryHandler.getCandleInfo(localSymbol, "${i.duration} ${i.unit}", startTime, endTime, validLimit) + marketDataProxy.getCandleInfo(localSymbol, "${i.duration} ${i.unit}", startTime, endTime, validLimit) .forEach { list.add( arrayListOf( diff --git a/api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/api/ports/binance/controller/WalletController.kt b/api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/api/ports/binance/controller/WalletController.kt index 49da873a1..64425fad2 100644 --- a/api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/api/ports/binance/controller/WalletController.kt +++ b/api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/api/ports/binance/controller/WalletController.kt @@ -2,19 +2,19 @@ package co.nilin.opex.api.ports.binance.controller import co.nilin.opex.api.core.inout.DepositDetails import co.nilin.opex.api.core.inout.TransactionHistoryResponse -import co.nilin.opex.api.core.spi.BlockchainGatewayProxy -import co.nilin.opex.api.core.spi.WalletProxy -import co.nilin.opex.api.ports.binance.data.AssignAddressResponse -import co.nilin.opex.api.ports.binance.data.DepositResponse -import co.nilin.opex.api.ports.binance.data.Interval -import co.nilin.opex.api.ports.binance.data.WithdrawResponse +import co.nilin.opex.api.core.spi.* +import co.nilin.opex.api.core.utils.Interval +import co.nilin.opex.api.ports.binance.data.* import co.nilin.opex.api.ports.binance.util.jwtAuthentication import co.nilin.opex.api.ports.binance.util.tokenValue +import co.nilin.opex.utility.error.data.OpexError +import co.nilin.opex.utility.error.data.OpexException import org.springframework.security.core.annotation.CurrentSecurityContext import org.springframework.security.core.context.SecurityContext import org.springframework.web.bind.annotation.GetMapping import org.springframework.web.bind.annotation.RequestParam import org.springframework.web.bind.annotation.RestController +import java.math.BigDecimal import java.time.Instant import java.time.LocalDateTime import java.time.ZoneId @@ -23,7 +23,10 @@ import java.util.* @RestController class WalletController( private val walletProxy: WalletProxy, - private val bcGatewayProxy: BlockchainGatewayProxy + private val symbolMapper: SymbolMapper, + private val marketDataProxy: MarketDataProxy, + private val accountantProxy: AccountantProxy, + private val bcGatewayProxy: BlockchainGatewayProxy, ) { @GetMapping("/v1/capital/deposit/address") @@ -141,6 +144,109 @@ class WalletController( } } + @GetMapping("/v1/asset/tradeFee") + suspend fun getPairFees( + @RequestParam(required = false) + symbol: String?, + @RequestParam(required = false) + recvWindow: Long?, //The value cannot be greater than 60000 + @RequestParam(name = "timestamp") + timestamp: Long + ): List { + return if (symbol != null) { + val internalSymbol = symbolMapper.toInternalSymbol(symbol) ?: throw OpexException(OpexError.SymbolNotFound) + + val fee = accountantProxy.getFeeConfig(internalSymbol) + arrayListOf().apply { + add( + PairFeeResponse( + symbol, + fee.makerFee.toDouble(), + fee.takerFee.toDouble() + ) + ) + } + } else + accountantProxy.getFeeConfigs() + .distinctBy { it.pair } + .map { + PairFeeResponse( + symbolMapper.fromInternalSymbol(it.pair) ?: it.pair, + it.makerFee.toDouble(), + it.takerFee.toDouble() + ) + } + } + + @GetMapping("/v1/asset/getUserAsset") + suspend fun getUserAssets( + @CurrentSecurityContext + securityContext: SecurityContext, + @RequestParam(required = false) + symbol: String?, + @RequestParam(required = false) + quoteAsset: String?, + @RequestParam(required = false) + calculateEvaluation: Boolean? + ): List { + val auth = securityContext.jwtAuthentication() + val result = arrayListOf() + + if (symbol != null) { + val wallet = walletProxy.getWallet(auth.name, auth.tokenValue(), symbol.uppercase()) + result.add(AssetResponse(wallet.asset, wallet.balance, wallet.locked, wallet.withdraw)) + } else { + result.addAll( + walletProxy.getWallets(auth.name, auth.tokenValue()) + .map { AssetResponse(it.asset, it.balance, it.locked, it.withdraw) } + ) + } + + if (quoteAsset == null) + return result + + val prices = marketDataProxy.getBestPriceForSymbols( + result.map { "${it.asset.uppercase()}_${quoteAsset.uppercase()}" } + ).associateBy { it.symbol.split("_")[0] } + + result.associateWith { prices[it.asset] } + .forEach { (asset, price) -> asset.valuation = price?.bidPrice ?: BigDecimal.ZERO } + + if (calculateEvaluation == true) + result.forEach { + it.free = it.free.multiply(it.valuation) + it.locked = it.locked.multiply(it.valuation) + it.withdrawing = it.withdrawing.multiply(it.valuation) + } + + return result + } + + @GetMapping("/v1/asset/estimatedValue") + suspend fun assetsEstimatedValue( + @CurrentSecurityContext + securityContext: SecurityContext, + @RequestParam + quoteAsset: String + ): AssetsEstimatedValue { + val auth = securityContext.jwtAuthentication() + val wallets = walletProxy.getWallets(auth.name, auth.tokenValue()) + val rates = marketDataProxy.getBestPriceForSymbols( + wallets.map { "${it.asset.uppercase()}_${quoteAsset.uppercase()}" } + ).associateBy { it.symbol.split("_")[0] } + + var value = BigDecimal.ZERO + val zeroAssets = arrayListOf() + wallets.associateWith { rates[it.asset] } + .forEach { (asset, price) -> + if (price == null || price.bidPrice == BigDecimal.ZERO) + zeroAssets.add(asset.asset) + else + value += asset.balance.multiply(price.bidPrice) + } + return AssetsEstimatedValue(value, quoteAsset.uppercase(), zeroAssets) + } + private fun matchDepositsAndDetails( deposits: List, details: List diff --git a/api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/api/ports/binance/data/AssetResponse.kt b/api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/api/ports/binance/data/AssetResponse.kt new file mode 100644 index 000000000..0c518254a --- /dev/null +++ b/api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/api/ports/binance/data/AssetResponse.kt @@ -0,0 +1,12 @@ +package co.nilin.opex.api.ports.binance.data + +import java.math.BigDecimal + +data class AssetResponse( + val asset: String, + var free: BigDecimal, + var locked: BigDecimal, + var withdrawing: BigDecimal, + var ipoable: BigDecimal = BigDecimal.ZERO, + var valuation: BigDecimal = BigDecimal.ZERO +) \ No newline at end of file diff --git a/api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/api/ports/binance/data/AssetsEstimatedValue.kt b/api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/api/ports/binance/data/AssetsEstimatedValue.kt new file mode 100644 index 000000000..137a3ba9e --- /dev/null +++ b/api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/api/ports/binance/data/AssetsEstimatedValue.kt @@ -0,0 +1,9 @@ +package co.nilin.opex.api.ports.binance.data + +import java.math.BigDecimal + +data class AssetsEstimatedValue( + val value: BigDecimal, + val evaluatedWith: String, + val zeroValueAssets: List +) \ No newline at end of file diff --git a/api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/api/ports/binance/data/CancelOrderResponse.kt b/api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/api/ports/binance/data/CancelOrderResponse.kt new file mode 100644 index 000000000..0fcc4a6ae --- /dev/null +++ b/api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/api/ports/binance/data/CancelOrderResponse.kt @@ -0,0 +1,25 @@ +package co.nilin.opex.api.ports.binance.data + +import co.nilin.opex.api.core.inout.OrderSide +import co.nilin.opex.api.core.inout.OrderStatus +import co.nilin.opex.api.core.inout.OrderType +import co.nilin.opex.api.core.inout.TimeInForce +import com.fasterxml.jackson.annotation.JsonInclude +import java.math.BigDecimal + +@JsonInclude(JsonInclude.Include.NON_NULL) +data class CancelOrderResponse( + val symbol: String, + val origClientOrderId: String?, + val orderId: Long?, + val orderListId: Long, //Unless OCO, value will be -1 + val clientOrderId: String?, + val price: BigDecimal?, + val origQty: BigDecimal?, + val executedQty: BigDecimal?, + val cummulativeQuoteQty: BigDecimal?, + val status: OrderStatus?, + val timeInForce: TimeInForce?, + val type: OrderType?, + val side: OrderSide? +) \ No newline at end of file diff --git a/api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/api/ports/binance/data/ExchangeInfoResponse.kt b/api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/api/ports/binance/data/ExchangeInfoResponse.kt index 7b55cd62c..d2cbdc718 100644 --- a/api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/api/ports/binance/data/ExchangeInfoResponse.kt +++ b/api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/api/ports/binance/data/ExchangeInfoResponse.kt @@ -1,7 +1,6 @@ package co.nilin.opex.api.ports.binance.data import co.nilin.opex.api.core.inout.PairFeeResponse -import co.nilin.opex.api.core.inout.RateLimit import java.util.* data class ExchangeInfoResponse( diff --git a/api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/OrderTradeData.kt b/api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/api/ports/binance/data/FillsData.kt similarity index 65% rename from api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/OrderTradeData.kt rename to api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/api/ports/binance/data/FillsData.kt index 86d45b08a..8739c22dd 100644 --- a/api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/OrderTradeData.kt +++ b/api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/api/ports/binance/data/FillsData.kt @@ -1,10 +1,10 @@ -package co.nilin.opex.api.core.inout - -import java.math.BigDecimal - -data class OrderTradeData( - val price: BigDecimal, - val qty: BigDecimal, - val commission: BigDecimal, - val commissionAsset: String +package co.nilin.opex.api.ports.binance.data + +import java.math.BigDecimal + +data class FillsData( + val price: BigDecimal, + val qty: BigDecimal, + val commission: BigDecimal, + val commissionAsset: String ) \ No newline at end of file diff --git a/api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/api/ports/binance/data/GlobalPriceResponse.kt b/api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/api/ports/binance/data/GlobalPriceResponse.kt new file mode 100644 index 000000000..391a4f601 --- /dev/null +++ b/api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/api/ports/binance/data/GlobalPriceResponse.kt @@ -0,0 +1,9 @@ +package co.nilin.opex.api.ports.binance.data + +import co.nilin.opex.api.core.inout.CurrencyRate +import co.nilin.opex.api.core.inout.GlobalPrice + +data class GlobalPriceResponse( + val usdRate: List, + val prices: List +) \ No newline at end of file diff --git a/api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/api/ports/binance/data/MarketInfoResponse.kt b/api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/api/ports/binance/data/MarketInfoResponse.kt new file mode 100644 index 000000000..e9041f686 --- /dev/null +++ b/api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/api/ports/binance/data/MarketInfoResponse.kt @@ -0,0 +1,7 @@ +package co.nilin.opex.api.ports.binance.data + +data class MarketInfoResponse( + val activeUsers: Long, + val totalOrders: Long, + val totalTrades: Long +) diff --git a/api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/api/ports/binance/data/MarketStatResponse.kt b/api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/api/ports/binance/data/MarketStatResponse.kt new file mode 100644 index 000000000..6d2b9bb5e --- /dev/null +++ b/api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/api/ports/binance/data/MarketStatResponse.kt @@ -0,0 +1,11 @@ +package co.nilin.opex.api.ports.binance.data + +import co.nilin.opex.api.core.inout.PriceStat +import co.nilin.opex.api.core.inout.TradeVolumeStat + +data class MarketStatResponse( + val mostIncreasedPrice: List, + val mostDecreasedPrice: List, + val mostVolume: TradeVolumeStat?, + val mostTrades: TradeVolumeStat? +) \ No newline at end of file diff --git a/api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/api/ports/binance/data/NewOrderResponse.kt b/api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/api/ports/binance/data/NewOrderResponse.kt new file mode 100644 index 000000000..69bbe87a2 --- /dev/null +++ b/api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/api/ports/binance/data/NewOrderResponse.kt @@ -0,0 +1,28 @@ +package co.nilin.opex.api.ports.binance.data + +import co.nilin.opex.api.core.inout.OrderSide +import co.nilin.opex.api.core.inout.OrderStatus +import co.nilin.opex.api.core.inout.OrderType +import co.nilin.opex.api.core.inout.TimeInForce +import co.nilin.opex.api.ports.binance.controller.AccountController +import com.fasterxml.jackson.annotation.JsonInclude +import java.math.BigDecimal +import java.util.* + +@JsonInclude(JsonInclude.Include.NON_NULL) +data class NewOrderResponse( + val symbol: String, + val orderId: Long, + val orderListId: Long, //Unless OCO, value will be -1 + val clientOrderId: String?, + val transactTime: Date, + val price: BigDecimal?, + val origQty: BigDecimal?, + val executedQty: BigDecimal?, + val cummulativeQuoteQty: BigDecimal?, + val status: OrderStatus?, + val timeInForce: TimeInForce?, + val type: OrderType?, + val side: OrderSide?, + val fills: List? +) \ No newline at end of file diff --git a/api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/api/ports/binance/data/PairFeeResponse.kt b/api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/api/ports/binance/data/PairFeeResponse.kt new file mode 100644 index 000000000..c678a9fd1 --- /dev/null +++ b/api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/api/ports/binance/data/PairFeeResponse.kt @@ -0,0 +1,7 @@ +package co.nilin.opex.api.ports.binance.data + +data class PairFeeResponse( + val symbol: String, + val makerCommission: Double, + val takerCommission: Double +) \ No newline at end of file diff --git a/api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/QueryOrderResponse.kt b/api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/api/ports/binance/data/QueryOrderResponse.kt similarity index 65% rename from api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/QueryOrderResponse.kt rename to api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/api/ports/binance/data/QueryOrderResponse.kt index 72aa1a0d6..8470e9971 100644 --- a/api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/QueryOrderResponse.kt +++ b/api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/api/ports/binance/data/QueryOrderResponse.kt @@ -1,26 +1,32 @@ -package co.nilin.opex.api.core.inout - -import java.math.BigDecimal -import java.util.* - -data class QueryOrderResponse( - val symbol: String, - val ouid: String, - val orderId: Long, - val orderListId: Long, //Unless part of an OCO, the value will always be -1. - val clientOrderId: String, - val price: BigDecimal, - val origQty: BigDecimal, - val executedQty: BigDecimal, - val cummulativeQuoteQty: BigDecimal, - val status: OrderStatus, - val timeInForce: TimeInForce, - val type: OrderType, - val side: OrderSide, - val stopPrice: BigDecimal?, - val icebergQty: BigDecimal?, - val time: Date, - val updateTime: Date, - val isWorking: Boolean, - val origQuoteOrderQty: BigDecimal +package co.nilin.opex.api.ports.binance.data + +import co.nilin.opex.api.core.inout.OrderSide +import co.nilin.opex.api.core.inout.OrderStatus +import co.nilin.opex.api.core.inout.OrderType +import co.nilin.opex.api.core.inout.TimeInForce +import com.fasterxml.jackson.annotation.JsonInclude +import java.math.BigDecimal +import java.util.* + +@JsonInclude(JsonInclude.Include.NON_NULL) +data class QueryOrderResponse( + val symbol: String, + val ouid: String, + val orderId: Long, + val orderListId: Long, //Unless part of an OCO, the value will always be -1. + val clientOrderId: String, + val price: BigDecimal, + val origQty: BigDecimal, + val executedQty: BigDecimal, + val cummulativeQuoteQty: BigDecimal, + val status: OrderStatus, + val timeInForce: TimeInForce, + val type: OrderType, + val side: OrderSide, + val stopPrice: BigDecimal?, + val icebergQty: BigDecimal?, + val time: Date, + val updateTime: Date, + val isWorking: Boolean, + val origQuoteOrderQty: BigDecimal ) \ No newline at end of file diff --git a/api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/RateLimit.kt b/api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/api/ports/binance/data/RateLimit.kt similarity index 89% rename from api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/RateLimit.kt rename to api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/api/ports/binance/data/RateLimit.kt index 7de6e91c3..a439059e6 100644 --- a/api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/RateLimit.kt +++ b/api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/api/ports/binance/data/RateLimit.kt @@ -1,4 +1,4 @@ -package co.nilin.opex.api.core.inout +package co.nilin.opex.api.ports.binance.data enum class RateLimit(val rateLimitType: RateLimitType, val interval: String, val intervalNum: Int, val limit: Int) { diff --git a/api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/api/ports/binance/data/RateLimitResponse.kt b/api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/api/ports/binance/data/RateLimitResponse.kt index 6abb048c4..47afb382e 100644 --- a/api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/api/ports/binance/data/RateLimitResponse.kt +++ b/api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/api/ports/binance/data/RateLimitResponse.kt @@ -1,7 +1,5 @@ package co.nilin.opex.api.ports.binance.data -import co.nilin.opex.api.core.inout.RateLimitType - data class RateLimitResponse( val rateLimitType: RateLimitType, val interval: String, diff --git a/api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/RateLimitType.kt b/api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/api/ports/binance/data/RateLimitType.kt similarity index 64% rename from api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/RateLimitType.kt rename to api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/api/ports/binance/data/RateLimitType.kt index 63231003a..8fbbfad45 100644 --- a/api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/RateLimitType.kt +++ b/api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/api/ports/binance/data/RateLimitType.kt @@ -1,4 +1,4 @@ -package co.nilin.opex.api.core.inout +package co.nilin.opex.api.ports.binance.data enum class RateLimitType { diff --git a/api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/api/ports/binance/data/TradeResponse.kt b/api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/api/ports/binance/data/TradeResponse.kt new file mode 100644 index 000000000..05c2d28aa --- /dev/null +++ b/api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/api/ports/binance/data/TradeResponse.kt @@ -0,0 +1,22 @@ +package co.nilin.opex.api.ports.binance.data + +import com.fasterxml.jackson.annotation.JsonInclude +import java.math.BigDecimal +import java.util.* + +@JsonInclude(JsonInclude.Include.NON_NULL) +data class TradeResponse( + val symbol: String, + val id: Long, + val orderId: Long, + val orderListId: Long = -1, + val price: BigDecimal, + val qty: BigDecimal, + val quoteQty: BigDecimal, + val commission: BigDecimal, + val commissionAsset: String, + val time: Date, + val isBuyer: Boolean, + val isMaker: Boolean, + val isBestMatch: Boolean +) \ No newline at end of file diff --git a/api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/api/ports/binance/proxy/MEGatewayProxyImpl.kt b/api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/api/ports/binance/proxy/MEGatewayProxyImpl.kt deleted file mode 100644 index b3b27337b..000000000 --- a/api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/api/ports/binance/proxy/MEGatewayProxyImpl.kt +++ /dev/null @@ -1,55 +0,0 @@ -package co.nilin.opex.api.ports.binance.proxy - -import co.nilin.opex.api.core.inout.CancelOrderRequest -import co.nilin.opex.api.core.inout.OrderSubmitResult -import co.nilin.opex.api.core.spi.MEGatewayProxy -import co.nilin.opex.api.ports.binance.util.LoggerDelegate -import kotlinx.coroutines.reactive.awaitSingleOrNull -import org.springframework.beans.factory.annotation.Value -import org.springframework.core.ParameterizedTypeReference -import org.springframework.http.MediaType -import org.springframework.stereotype.Component -import org.springframework.web.reactive.function.client.WebClient -import org.springframework.web.reactive.function.client.body -import reactor.core.publisher.Mono -import java.net.URI - -private inline fun typeRef(): ParameterizedTypeReference = - object : ParameterizedTypeReference() {} - -@Component -class MEGatewayProxyImpl(private val client: WebClient) : MEGatewayProxy { - - private val logger by LoggerDelegate() - - @Value("\${app.matching-gateway.url}") - private lateinit var baseUrl: String - - override suspend fun createNewOrder(order: MEGatewayProxy.CreateOrderRequest, token: String?): OrderSubmitResult? { - logger.info("calling matching-gateway order create") - return client.post() - .uri(URI.create("$baseUrl/order")) - .accept(MediaType.APPLICATION_JSON) - .contentType(MediaType.APPLICATION_JSON) - .header("Authorization", "Bearer $token") - .body(Mono.just(order)) - .retrieve() - .onStatus({ t -> t.isError }, { it.createException() }) - .bodyToMono(typeRef()) - .awaitSingleOrNull() - } - - override suspend fun cancelOrder(request: CancelOrderRequest, token: String?): OrderSubmitResult? { - logger.info("calling matching-gateway order cancel") - return client.post() - .uri(URI.create("$baseUrl/order/cancel")) - .accept(MediaType.APPLICATION_JSON) - .contentType(MediaType.APPLICATION_JSON) - .header("Authorization", "Bearer $token") - .body(Mono.just(request)) - .retrieve() - .onStatus({ t -> t.isError }, { it.createException() }) - .bodyToMono(typeRef()) - .awaitSingleOrNull() - } -} \ No newline at end of file diff --git a/api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/api/ports/binance/util/BalanceParser.kt b/api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/api/ports/binance/util/BalanceParser.kt deleted file mode 100644 index e257a1517..000000000 --- a/api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/api/ports/binance/util/BalanceParser.kt +++ /dev/null @@ -1,33 +0,0 @@ -package co.nilin.opex.api.ports.binance.util - -import co.nilin.opex.api.core.inout.Wallet -import co.nilin.opex.api.ports.binance.data.BalanceResponse -import java.math.BigDecimal - -object BalanceParser { - - fun parse(list: List): List { - val result = arrayListOf() - - for (w in list) { - result.addOrGet(w.asset).apply { - when (w.type) { - "exchange" -> locked = w.balance - "main" -> free = w.balance - "cashout" -> withdraw = w.balance - } - } - } - return result - } - - private fun ArrayList.addOrGet(symbol: String): BalanceResponse { - for (w in this) - if (w.asset == symbol) - return w - - add(BalanceResponse(symbol, BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO)) - return this.last() - } - -} \ No newline at end of file diff --git a/api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/api/ports/binance/util/EnumExtensions.kt b/api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/api/ports/binance/util/EnumExtensions.kt index 4e9bc4c89..faa3d32a8 100644 --- a/api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/api/ports/binance/util/EnumExtensions.kt +++ b/api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/api/ports/binance/util/EnumExtensions.kt @@ -8,6 +8,12 @@ fun OrderSide.asOrderDirection(): OrderDirection { return OrderDirection.ASK } +fun OrderDirection.asOrderSide(): OrderSide { + if (this == OrderDirection.BID) + return OrderSide.BUY + return OrderSide.SELL +} + fun TimeInForce.asMatchConstraint(): MatchConstraint { return when (this) { TimeInForce.GTC -> MatchConstraint.GTC @@ -16,6 +22,15 @@ fun TimeInForce.asMatchConstraint(): MatchConstraint { } } +fun MatchConstraint.asTimeInForce(): TimeInForce { + return when (this) { + MatchConstraint.GTC -> TimeInForce.GTC + MatchConstraint.IOC -> TimeInForce.IOC + MatchConstraint.FOK -> TimeInForce.FOK + else -> TimeInForce.GTC + } +} + fun OrderType.asMatchingOrderType(): MatchingOrderType { return when (this) { OrderType.LIMIT -> MatchingOrderType.LIMIT_ORDER @@ -24,6 +39,13 @@ fun OrderType.asMatchingOrderType(): MatchingOrderType { } } +fun MatchingOrderType.asOrderType(): OrderType { + return when (this) { + MatchingOrderType.LIMIT_ORDER -> OrderType.LIMIT + MatchingOrderType.MARKET_ORDER -> OrderType.MARKET + } +} + fun > R.equalsAny(vararg equals: R): Boolean { for (e in equals) if (this == e) diff --git a/api/api-ports/api-persister-postgres/src/main/kotlin/co/nilin/opex/api/ports/postgres/dao/SymbolMapRepository.kt b/api/api-ports/api-persister-postgres/src/main/kotlin/co/nilin/opex/api/ports/postgres/dao/SymbolMapRepository.kt index 66c478e2f..1ecc358fc 100644 --- a/api/api-ports/api-persister-postgres/src/main/kotlin/co/nilin/opex/api/ports/postgres/dao/SymbolMapRepository.kt +++ b/api/api-ports/api-persister-postgres/src/main/kotlin/co/nilin/opex/api/ports/postgres/dao/SymbolMapRepository.kt @@ -5,13 +5,17 @@ import org.springframework.data.r2dbc.repository.Query import org.springframework.data.repository.query.Param import org.springframework.data.repository.reactive.ReactiveCrudRepository import org.springframework.stereotype.Repository +import reactor.core.publisher.Flux import reactor.core.publisher.Mono @Repository interface SymbolMapRepository : ReactiveCrudRepository { + @Query("select * from symbol_maps where symbol = :symbol and alias_key = :aliasKey") fun findByAliasKeyAndSymbol(aliasKey: String, @Param("symbol") symbol: String): Mono @Query("select * from symbol_maps where alias_key = :aliasKey and alias = :alias") fun findByAliasKeyAndAlias(aliasKey: String, @Param("alias") alias: String): Mono + + fun findAllByAliasKey(aliasKey: String): Flux } diff --git a/api/api-ports/api-persister-postgres/src/main/kotlin/co/nilin/opex/api/ports/postgres/impl/SymbolMapperImpl.kt b/api/api-ports/api-persister-postgres/src/main/kotlin/co/nilin/opex/api/ports/postgres/impl/SymbolMapperImpl.kt index b635a2812..0aa8abe91 100644 --- a/api/api-ports/api-persister-postgres/src/main/kotlin/co/nilin/opex/api/ports/postgres/impl/SymbolMapperImpl.kt +++ b/api/api-ports/api-persister-postgres/src/main/kotlin/co/nilin/opex/api/ports/postgres/impl/SymbolMapperImpl.kt @@ -9,21 +9,25 @@ import org.springframework.stereotype.Component @Component class SymbolMapperImpl(val symbolMapRepository: SymbolMapRepository) : SymbolMapper { - override suspend fun map(symbol: String?): String? { + private var symbolsCache: Map? = null + + override suspend fun fromInternalSymbol(symbol: String?): String? { if (symbol == null) return null return symbolMapRepository.findByAliasKeyAndSymbol("binance", symbol).awaitFirstOrNull()?.alias } - override suspend fun unmap(alias: String?): String? { + override suspend fun toInternalSymbol(alias: String?): String? { if (alias == null) return null return symbolMapRepository.findByAliasKeyAndAlias("binance", alias).awaitFirstOrNull()?.symbol } override suspend fun symbolToAliasMap(): Map { - return symbolMapRepository.findAll() - .filter { it.aliasKey == "binance" } - .collectList() - .awaitFirstOrElse { emptyList() } - .associate { it.symbol to it.alias } + if (symbolsCache.isNullOrEmpty()) { + symbolsCache = symbolMapRepository.findAllByAliasKey("binance") + .collectList() + .awaitFirstOrElse { emptyList() } + .associate { it.symbol to it.alias } + } + return symbolsCache!! } } diff --git a/api/api-ports/api-persister-postgres/src/main/kotlin/co/nilin/opex/api/ports/postgres/impl/UserQueryHandlerImpl.kt b/api/api-ports/api-persister-postgres/src/main/kotlin/co/nilin/opex/api/ports/postgres/impl/UserQueryHandlerImpl.kt deleted file mode 100644 index 7d3b8c58a..000000000 --- a/api/api-ports/api-persister-postgres/src/main/kotlin/co/nilin/opex/api/ports/postgres/impl/UserQueryHandlerImpl.kt +++ /dev/null @@ -1,138 +0,0 @@ -package co.nilin.opex.api.ports.postgres.impl - -import co.nilin.opex.api.core.inout.* -import co.nilin.opex.api.core.spi.UserQueryHandler -import co.nilin.opex.api.ports.postgres.dao.OrderRepository -import co.nilin.opex.api.ports.postgres.dao.OrderStatusRepository -import co.nilin.opex.api.ports.postgres.dao.TradeRepository -import co.nilin.opex.api.ports.postgres.model.OrderModel -import co.nilin.opex.api.ports.postgres.model.OrderStatusModel -import co.nilin.opex.api.ports.postgres.util.* -import co.nilin.opex.utility.error.data.OpexError -import co.nilin.opex.utility.error.data.OpexException -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.filter -import kotlinx.coroutines.flow.map -import kotlinx.coroutines.reactive.awaitFirst -import kotlinx.coroutines.reactive.awaitFirstOrNull -import org.springframework.stereotype.Component -import java.math.BigDecimal -import java.security.Principal -import java.time.ZoneId -import java.util.* - -@Component -class UserQueryHandlerImpl( - private val orderRepository: OrderRepository, - private val tradeRepository: TradeRepository, - private val orderStatusRepository: OrderStatusRepository -) : UserQueryHandler { - - override suspend fun queryOrder(principal: Principal, request: QueryOrderRequest): QueryOrderResponse? { - val order = (if (request.origClientOrderId != null) { - orderRepository.findBySymbolAndClientOrderId(request.symbol, request.origClientOrderId!!) - } else { - orderRepository.findBySymbolAndOrderId(request.symbol, request.orderId!!) - - }).awaitFirstOrNull() ?: return null - - if (order.uuid != principal.name) - throw OpexException(OpexError.Forbidden) - - return order.asQueryResponse(orderStatusRepository.findMostRecentByOUID(order.ouid).awaitFirstOrNull()) - } - - override suspend fun openOrders(principal: Principal, symbol: String?): Flow { - return orderRepository.findByUuidAndSymbolAndStatus( - principal.name, - symbol, - listOf(OrderStatus.NEW.code, OrderStatus.PARTIALLY_FILLED.code) - ).filter { orderModel -> orderModel.constraint != null } - .map { it.asQueryResponse(orderStatusRepository.findMostRecentByOUID(it.ouid).awaitFirstOrNull()) } - } - - override suspend fun allOrders(principal: Principal, allOrderRequest: AllOrderRequest): Flow { - return orderRepository.findByUuidAndSymbolAndTimeBetween( - principal.name, - allOrderRequest.symbol, - allOrderRequest.startTime, - allOrderRequest.endTime - ).filter { orderModel -> orderModel.constraint != null } - .map { it.asQueryResponse(orderStatusRepository.findMostRecentByOUID(it.ouid).awaitFirstOrNull()) } - } - - override suspend fun allTrades(principal: Principal, request: TradeRequest): Flow { - return tradeRepository.findByUuidAndSymbolAndTimeBetweenAndTradeIdGreaterThan( - principal.name, request.symbol, request.fromTrade, request.startTime, request.endTime - ).map { trade -> - val takerOrder = orderRepository.findByOuid(trade.takerOuid).awaitFirst() - val makerOrder = orderRepository.findByOuid(trade.makerOuid).awaitFirst() - val isMakerBuyer = makerOrder.direction == OrderDirection.BID - TradeResponse( - trade.symbol, - trade.tradeId, - if (trade.takerUuid == principal.name) { - takerOrder.orderId!! - } else { - makerOrder.orderId!! - }, - -1, - if (trade.takerUuid == principal.name) { - trade.takerPrice - } else { - trade.makerPrice - }, - trade.matchedQuantity, - if (isMakerBuyer) { - makerOrder.quoteQuantity!! - } else { - takerOrder.quoteQuantity!! - }, - if (trade.takerUuid == principal.name) { - trade.takerCommision!! - } else { - trade.makerCommision!! - }, - if (trade.takerUuid == principal.name) { - trade.takerCommisionAsset!! - } else { - trade.makerCommisionAsset!! - }, - Date.from( - trade.createDate.atZone(ZoneId.systemDefault()).toInstant() - ), - if (trade.takerUuid == principal.name) { - OrderDirection.ASK == takerOrder.direction - } else { - OrderDirection.ASK == makerOrder.direction - }, - trade.makerUuid == principal.name, - true, - isMakerBuyer - ) - } - } - - - private fun OrderModel.asQueryResponse(orderStatusModel: OrderStatusModel?) = QueryOrderResponse( - symbol, - ouid, - orderId ?: -1, - -1, - clientOrderId ?: "", - price!!, - quantity!!, - orderStatusModel?.executedQuantity ?: BigDecimal.ZERO, - orderStatusModel?.accumulativeQuoteQty ?: BigDecimal.ZERO, - orderStatusModel?.status?.toOrderStatus() ?: OrderStatus.NEW, - constraint!!.toTimeInForce(), - type!!.toApiOrderType(), - direction!!.toOrderSide(), - null, - null, - Date.from(createDate!!.atZone(ZoneId.systemDefault()).toInstant()), - Date.from(updateDate.atZone(ZoneId.systemDefault()).toInstant()), - (orderStatusModel?.status?.toOrderStatus() ?: OrderStatus.NEW).isWorking(), - quoteQuantity!! - ) -} \ No newline at end of file diff --git a/api/api-ports/api-persister-postgres/src/main/kotlin/co/nilin/opex/api/ports/postgres/model/OrderModel.kt b/api/api-ports/api-persister-postgres/src/main/kotlin/co/nilin/opex/api/ports/postgres/model/OrderModel.kt deleted file mode 100644 index d060994ac..000000000 --- a/api/api-ports/api-persister-postgres/src/main/kotlin/co/nilin/opex/api/ports/postgres/model/OrderModel.kt +++ /dev/null @@ -1,39 +0,0 @@ -package co.nilin.opex.api.ports.postgres.model - -import co.nilin.opex.api.core.inout.MatchConstraint -import co.nilin.opex.api.core.inout.MatchingOrderType -import co.nilin.opex.api.core.inout.OrderDirection -import org.springframework.data.annotation.Id -import org.springframework.data.annotation.Version -import org.springframework.data.relational.core.mapping.Column -import org.springframework.data.relational.core.mapping.Table -import java.math.BigDecimal -import java.time.LocalDateTime - -@Table("orders") -data class OrderModel( - @Id var id: Long?, - @Column(value = "ouid") - val ouid: String, - val uuid: String, - @Column(value = "client_order_id") - val clientOrderId: String?, - val symbol: String, - @Column(value = "order_id") val orderId: Long?, - @Column("maker_fee") val makerFee: BigDecimal?, - @Column("taker_fee") val takerFee: BigDecimal?, - @Column("left_side_fraction") val leftSideFraction: BigDecimal?, - @Column("right_side_fraction") val rightSideFraction: BigDecimal?, - @Column("user_level") val userLevel: String?, - @Column("side") val direction: OrderDirection?, - @Column("match_constraint") val constraint: MatchConstraint?, - @Column("order_type") val type: MatchingOrderType?, - @Column("price") val price: BigDecimal?, - @Column("quantity") val quantity: BigDecimal?, - @Column("quote_quantity") val quoteQuantity: BigDecimal?, - @Column("create_date") val createDate: LocalDateTime?, - @Column("update_date") val updateDate: LocalDateTime, - @Version - @Column("version") - var version: Long? = null -) \ No newline at end of file diff --git a/api/api-ports/api-persister-postgres/src/main/kotlin/co/nilin/opex/api/ports/postgres/model/TradeModel.kt b/api/api-ports/api-persister-postgres/src/main/kotlin/co/nilin/opex/api/ports/postgres/model/TradeModel.kt deleted file mode 100644 index db4a7ccf4..000000000 --- a/api/api-ports/api-persister-postgres/src/main/kotlin/co/nilin/opex/api/ports/postgres/model/TradeModel.kt +++ /dev/null @@ -1,28 +0,0 @@ -package co.nilin.opex.api.ports.postgres.model - - -import org.springframework.data.annotation.Id -import org.springframework.data.relational.core.mapping.Column -import org.springframework.data.relational.core.mapping.Table -import java.math.BigDecimal -import java.time.LocalDateTime - -@Table("trades") -class TradeModel( - @Id var id: Long?, - @Column("trade_id") val tradeId: Long, - val symbol: String, - @Column("matched_quantity") val matchedQuantity: BigDecimal, - @Column("taker_price") val takerPrice: BigDecimal, - @Column("maker_price") val makerPrice: BigDecimal, - @Column("taker_commision") val takerCommision: BigDecimal?, - @Column("maker_commision") val makerCommision: BigDecimal?, - @Column("taker_commision_asset") val takerCommisionAsset: String?, - @Column("maker_commision_asset") val makerCommisionAsset: String?, - @Column("trade_date") val tradeDate: LocalDateTime, - @Column("maker_ouid") val makerOuid: String, - @Column("taker_ouid") val takerOuid: String, - @Column("maker_uuid") val makerUuid: String, - @Column("taker_uuid") val takerUuid: String, - @Column("create_date") val createDate: LocalDateTime -) \ No newline at end of file diff --git a/api/api-ports/api-persister-postgres/src/main/resources/schema.sql b/api/api-ports/api-persister-postgres/src/main/resources/schema.sql index 4c59f00ce..cf27d5df0 100644 --- a/api/api-ports/api-persister-postgres/src/main/resources/schema.sql +++ b/api/api-ports/api-persister-postgres/src/main/resources/schema.sql @@ -1,89 +1,8 @@ -CREATE TABLE IF NOT EXISTS orders -( - id SERIAL PRIMARY KEY, - ouid VARCHAR(72) NOT NULL UNIQUE, - uuid VARCHAR(72) NOT NULL, - client_order_id VARCHAR(72), - symbol VARCHAR(20) NOT NULL, - order_id INTEGER, - maker_fee DECIMAL, - taker_fee DECIMAL, - left_side_fraction DECIMAL, - right_side_fraction DECIMAL, - user_level VARCHAR(20), - side VARCHAR(20), - match_constraint VARCHAR(20), - order_type VARCHAR(20), - price DECIMAL, - quantity DECIMAL, - quote_quantity DECIMAL, - create_date TIMESTAMP, - update_date TIMESTAMP NOT NULL, - version INTEGER -); - -CREATE TABLE IF NOT EXISTS order_status -( - id SERIAL PRIMARY KEY, - ouid VARCHAR(72) NOT NULL, - executed_quantity DECIMAL, - accumulative_quote_qty DECIMAL, - status INTEGER NOT NULL, - appearance INTEGER NOT NULL, - date TIMESTAMP NOT NULL, - UNIQUE (ouid, status, appearance, executed_quantity) -); - -CREATE TABLE IF NOT EXISTS trades -( - id SERIAL PRIMARY KEY, - trade_id INTEGER NOT NULL, - symbol VARCHAR(20) NOT NULL, - matched_quantity DECIMAL NOT NULL, - taker_price DECIMAL NOT NULL, - maker_price DECIMAL NOT NULL, - taker_commision DECIMAL, - maker_commision DECIMAL, - taker_commision_asset VARCHAR(20), - maker_commision_asset VARCHAR(20), - trade_date TIMESTAMP NOT NULL, - maker_ouid VARCHAR(72) NOT NULL, - taker_ouid VARCHAR(72) NOT NULL, - maker_uuid VARCHAR(72) NOT NULL, - taker_uuid VARCHAR(72) NOT NULL, - create_date TIMESTAMP -); - CREATE TABLE IF NOT EXISTS symbol_maps ( - id SERIAL PRIMARY KEY, - symbol VARCHAR(72) NOT NULL, - alias_key VARCHAR(72) NOT NULL, - alias VARCHAR(72) NOT NULL, + id SERIAL PRIMARY KEY, + symbol VARCHAR(72) NOT NULL, + alias_key VARCHAR(72) NOT NULL, + alias VARCHAR(72) NOT NULL, UNIQUE (symbol, alias_key, alias) ); - -CREATE OR REPLACE FUNCTION interval_generator( - start_ts TIMESTAMP without TIME ZONE, - end_ts TIMESTAMP without TIME ZONE, - round_interval INTERVAL -) - RETURNS TABLE - ( - start_time TIMESTAMP without TIME ZONE, - end_time TIMESTAMP without TIME ZONE - ) -as -$$ -BEGIN - RETURN QUERY - SELECT (n) start_time, - (n + round_interval) end_time - FROM generate_series( - date_trunc('minute', start_ts), - end_ts, - round_interval - ) n; -END; - -$$ LANGUAGE 'plpgsql'; diff --git a/api/api-ports/api-persister-postgres/src/test/kotlin/co/nilin/opex/api/ports/postgres/impl/SymbolMapperTest.kt b/api/api-ports/api-persister-postgres/src/test/kotlin/co/nilin/opex/api/ports/postgres/impl/SymbolMapperTest.kt index 9b1ae1e1f..2283712ca 100644 --- a/api/api-ports/api-persister-postgres/src/test/kotlin/co/nilin/opex/api/ports/postgres/impl/SymbolMapperTest.kt +++ b/api/api-ports/api-persister-postgres/src/test/kotlin/co/nilin/opex/api/ports/postgres/impl/SymbolMapperTest.kt @@ -25,6 +25,9 @@ class SymbolMapperTest { every { symbolMapRepository.findByAliasKeyAndSymbol("binance", VALID.ETH_USDT) } returns Mono.just(VALID.SYMBOL_MAP_MODEL) + every { + symbolMapRepository.findAllByAliasKey("binance") + } returns Flux.just(VALID.SYMBOL_MAP_MODEL) every { symbolMapRepository.findAll() } returns Flux.just(VALID.SYMBOL_MAP_MODEL) @@ -32,14 +35,14 @@ class SymbolMapperTest { @Test fun givenSymbolAlias_whenMapSymbol_thenReturnAlias(): Unit = runBlocking { - val alis = symbolMapper.map(VALID.ETH_USDT) + val alis = symbolMapper.fromInternalSymbol(VALID.ETH_USDT) assertThat(alis).isEqualTo("ETHUSDT") } @Test fun givenSymbolAlias_whenUnmapAlias_thenReturnSymbol(): Unit = runBlocking { - val symbol = symbolMapper.unmap("ETHUSDT") + val symbol = symbolMapper.toInternalSymbol("ETHUSDT") assertThat(symbol).isEqualTo(VALID.ETH_USDT) } diff --git a/api/api-ports/api-persister-postgres/src/test/kotlin/co/nilin/opex/api/ports/postgres/impl/TradePersisterTest.kt b/api/api-ports/api-persister-postgres/src/test/kotlin/co/nilin/opex/api/ports/postgres/impl/TradePersisterTest.kt deleted file mode 100644 index 2ca75e76a..000000000 --- a/api/api-ports/api-persister-postgres/src/test/kotlin/co/nilin/opex/api/ports/postgres/impl/TradePersisterTest.kt +++ /dev/null @@ -1,24 +0,0 @@ -package co.nilin.opex.api.ports.postgres.impl - -import co.nilin.opex.api.ports.postgres.dao.TradeRepository -import co.nilin.opex.api.ports.postgres.impl.sample.VALID -import io.mockk.every -import io.mockk.mockk -import kotlinx.coroutines.runBlocking -import org.assertj.core.api.Assertions.assertThatNoException -import org.junit.jupiter.api.Test -import reactor.core.publisher.Mono - -class TradePersisterTest { - private val tradeRepository: TradeRepository = mockk() - private val tradePersister = TradePersisterImpl(tradeRepository) - - @Test - fun givenTradeRepo_whenSaveRichTrade_thenSuccess(): Unit = runBlocking { - every { - tradeRepository.save(any()) - } returns Mono.just(VALID.TRADE_MODEL) - - assertThatNoException().isThrownBy { runBlocking { tradePersister.save(VALID.RICH_TRADE) } } - } -} diff --git a/api/api-ports/api-persister-postgres/src/test/kotlin/co/nilin/opex/api/ports/postgres/impl/sample/Samples.kt b/api/api-ports/api-persister-postgres/src/test/kotlin/co/nilin/opex/api/ports/postgres/impl/sample/Samples.kt index 59060a794..b6fc9e909 100644 --- a/api/api-ports/api-persister-postgres/src/test/kotlin/co/nilin/opex/api/ports/postgres/impl/sample/Samples.kt +++ b/api/api-ports/api-persister-postgres/src/test/kotlin/co/nilin/opex/api/ports/postgres/impl/sample/Samples.kt @@ -1,211 +1,18 @@ package co.nilin.opex.api.ports.postgres.impl.sample -import co.nilin.opex.api.core.event.RichOrder -import co.nilin.opex.api.core.event.RichOrderUpdate -import co.nilin.opex.api.core.event.RichTrade -import co.nilin.opex.api.core.inout.* -import co.nilin.opex.api.ports.postgres.model.OrderModel -import co.nilin.opex.api.ports.postgres.model.OrderStatusModel import co.nilin.opex.api.ports.postgres.model.SymbolMapModel -import co.nilin.opex.api.ports.postgres.model.TradeModel -import co.nilin.opex.api.ports.postgres.util.isWorking -import java.math.BigDecimal import java.security.Principal import java.time.LocalDateTime -import java.time.ZoneId import java.time.ZoneOffset -import java.util.* object VALID { - private const val USER_LEVEL_REGISTERED = "registered" - private const val TIMESTAMP = 1653125840L - private val CREATE_DATE: LocalDateTime = LocalDateTime.ofEpochSecond(TIMESTAMP, 0, ZoneOffset.UTC) - private val UPDATE_DATE: LocalDateTime = LocalDateTime.ofEpochSecond(TIMESTAMP + 180, 0, ZoneOffset.UTC) - private val FROM_DATE: LocalDateTime = LocalDateTime.ofEpochSecond(TIMESTAMP - 600, 0, ZoneOffset.UTC) - private val TO_DATE: LocalDateTime = LocalDateTime.ofEpochSecond(TIMESTAMP + 600, 0, ZoneOffset.UTC) const val ETH_USDT = "ETH_USDT" - val PRINCIPAL = Principal { "98c7ca9b-2d9c-46dd-afa8-b0cd2f52a97c" } - - val MAKER_ORDER_MODEL = OrderModel( - 1, - "f1167d30-ccc0-4f86-ab5d-dd24aa3250df", - PRINCIPAL.name, - null, // Binance - ETH_USDT, - 1, // MatchingEngine ID - BigDecimal.valueOf(0.01), // Calculated? - BigDecimal.valueOf(0.01), // Calculated? - BigDecimal.valueOf(0.0001), - BigDecimal.valueOf(0.01), - USER_LEVEL_REGISTERED, - OrderDirection.ASK, - MatchConstraint.GTC, - MatchingOrderType.LIMIT_ORDER, - BigDecimal.valueOf(100000), - BigDecimal.valueOf(0.001), - BigDecimal.valueOf(100).stripTrailingZeros(), - CREATE_DATE, - UPDATE_DATE - ) - - val TAKER_ORDER_MODEL = MAKER_ORDER_MODEL.copy(2, "157b9b4a-cc66-43b9-b30b-40a8b66ea6aa") - - val MAKER_ORDER_STATUS_MODEL = OrderStatusModel( - MAKER_ORDER_MODEL.ouid, - BigDecimal.valueOf(0), // Filled amount - BigDecimal.valueOf(0), // --> See accountant - OrderStatus.FILLED.code, - OrderStatus.FILLED.orderOfAppearance, - CREATE_DATE - ) - - val TAKER_ORDER_STATUS_MODEL = MAKER_ORDER_STATUS_MODEL.copy(TAKER_ORDER_MODEL.ouid) - val SYMBOL_MAP_MODEL = SymbolMapModel( 1, ETH_USDT, "binance", ETH_USDT.replace("_", "") ) - - val TRADE_MODEL = TradeModel( - 1, - 1, - ETH_USDT, - BigDecimal.valueOf(0.001), // Minimum of orders quantities - BigDecimal.valueOf(100000), - BigDecimal.valueOf(100000), - BigDecimal.valueOf(0.001), // Calculated - BigDecimal.valueOf(0.001), // Calculated - "ETH", - "USDT", - CREATE_DATE, - MAKER_ORDER_MODEL.ouid, - TAKER_ORDER_MODEL.ouid, - PRINCIPAL.name, - PRINCIPAL.name, - UPDATE_DATE - ) - - val MAKER_QUERY_ORDER_RESPONSE = QueryOrderResponse( - ETH_USDT, - MAKER_ORDER_MODEL.ouid, - 1, - -1, // Binance - "", // Binance - BigDecimal.valueOf(100000), - BigDecimal.valueOf(0.001), - BigDecimal.valueOf(0), - BigDecimal.valueOf(0), - OrderStatus.FILLED, - TimeInForce.GTC, - OrderType.LIMIT, - OrderSide.SELL, - null, - null, - Date.from(CREATE_DATE.atZone(ZoneId.systemDefault()).toInstant()), - Date.from(UPDATE_DATE.atZone(ZoneId.systemDefault()).toInstant()), - OrderStatus.FILLED.isWorking(), - BigDecimal.valueOf(100000.0 * 0.001).stripTrailingZeros() - ) - - val AGGREGATED_ORDER_PRICE_MODEL = AggregatedOrderPriceModel( - BigDecimal.valueOf(100000), - BigDecimal.valueOf(0.001) - ) - - val ORDER_BOOK_RESPONSE = OrderBookResponse( - AGGREGATED_ORDER_PRICE_MODEL.price!!, - AGGREGATED_ORDER_PRICE_MODEL.quantity!! - ) - - val RICH_ORDER = RichOrder( - null, - ETH_USDT, - MAKER_ORDER_MODEL.ouid, - PRINCIPAL.name, - USER_LEVEL_REGISTERED, - BigDecimal.valueOf(0.01), - BigDecimal.valueOf(0.01), - BigDecimal.valueOf(0.0001), - BigDecimal.valueOf(0.01), - OrderDirection.ASK, - MatchConstraint.GTC, - MatchingOrderType.LIMIT_ORDER, - BigDecimal.valueOf(1000001), - BigDecimal.valueOf(0.01), - BigDecimal.valueOf(0), - BigDecimal.valueOf(0), - BigDecimal.valueOf(0), - 0 - ) - - val RICH_ORDER_UPDATE = RichOrderUpdate( - MAKER_ORDER_MODEL.ouid, - BigDecimal.valueOf(1000001), - BigDecimal.valueOf(0.01), - BigDecimal.valueOf(0.08), - OrderStatus.PARTIALLY_FILLED - ) - - val RICH_TRADE = RichTrade( - 1, - ETH_USDT, - MAKER_ORDER_MODEL.ouid, - PRINCIPAL.name, - 1, - OrderDirection.ASK, - BigDecimal.valueOf(100000), - BigDecimal.valueOf(0.01), - BigDecimal.valueOf(0), - BigDecimal.valueOf(0), - BigDecimal.valueOf(0), - "ETH", - TAKER_ORDER_MODEL.ouid, - PRINCIPAL.name, - 2, - OrderDirection.ASK, - BigDecimal.valueOf(100000), - BigDecimal.valueOf(0.01), - BigDecimal.valueOf(0), - BigDecimal.valueOf(0), - BigDecimal.valueOf(0), - "USDT", - BigDecimal.valueOf(0), - CREATE_DATE - ) - - val ALL_ORDER_REQUEST = AllOrderRequest( - ETH_USDT, - Date.from(FROM_DATE.toInstant(ZoneOffset.UTC)), - Date.from(TO_DATE.toInstant(ZoneOffset.UTC)), - 500 - ) - - val TRADE_REQUEST = TradeRequest( - ETH_USDT, - 1, - Date.from(FROM_DATE.toInstant(ZoneOffset.UTC)), - Date.from(TO_DATE.toInstant(ZoneOffset.UTC)), - 500 - ) - - val MARKET_TRADE_RESPONSE = MarketTradeResponse( - ETH_USDT, - 1, - BigDecimal.valueOf(100000), - BigDecimal.valueOf(0.001), - BigDecimal.valueOf(100000 * 0.001).stripTrailingZeros(), - Date.from(UPDATE_DATE.atZone(ZoneId.systemDefault()).toInstant()), - true, - MAKER_ORDER_MODEL.direction == OrderDirection.BID - ) - - val QUERY_ORDER_REQUEST = QueryOrderRequest( - ETH_USDT, - 1, - "2" - ) } diff --git a/api/api-ports/api-proxy-rest/pom.xml b/api/api-ports/api-proxy-rest/pom.xml new file mode 100644 index 000000000..bc3bcc88d --- /dev/null +++ b/api/api-ports/api-proxy-rest/pom.xml @@ -0,0 +1,57 @@ + + + 4.0.0 + + + co.nilin.opex.api + api + 1.0.0-beta.3 + ../../pom.xml + + + co.nilin.opex.api.ports.proxy + api-proxy-rest + api-proxy-rest + Proxies to internal services + + + + org.jetbrains.kotlin + kotlin-reflect + + + co.nilin.opex.api.core + api-core + + + co.nilin.opex.utility.error + error-handler + + + org.springframework.boot + spring-boot-starter-data-r2dbc + + + io.projectreactor.kotlin + reactor-kotlin-extensions + + + org.jetbrains.kotlinx + kotlinx-coroutines-reactor + + + org.jetbrains.kotlinx + kotlinx-coroutines-core + + + io.projectreactor + reactor-test + test + + + io.mockk + mockk + + + diff --git a/api/api-ports/api-proxy-rest/src/main/kotlin/co/nilin/opex/api/ports/proxy/data/AllOrderRequest.kt b/api/api-ports/api-proxy-rest/src/main/kotlin/co/nilin/opex/api/ports/proxy/data/AllOrderRequest.kt new file mode 100644 index 000000000..586b1e65d --- /dev/null +++ b/api/api-ports/api-proxy-rest/src/main/kotlin/co/nilin/opex/api/ports/proxy/data/AllOrderRequest.kt @@ -0,0 +1,10 @@ +package co.nilin.opex.api.ports.proxy.data + +import java.util.* + +class AllOrderRequest( + val symbol: String?, + val startTime: Date?, + val endTime: Date?, + val limit: Int? = 500, //Default 500; max 1000. +) \ No newline at end of file diff --git a/api/api-ports/api-proxy-rest/src/main/kotlin/co/nilin/opex/api/ports/proxy/data/AssignAddressRequest.kt b/api/api-ports/api-proxy-rest/src/main/kotlin/co/nilin/opex/api/ports/proxy/data/AssignAddressRequest.kt new file mode 100644 index 000000000..2850c9e7d --- /dev/null +++ b/api/api-ports/api-proxy-rest/src/main/kotlin/co/nilin/opex/api/ports/proxy/data/AssignAddressRequest.kt @@ -0,0 +1,6 @@ +package co.nilin.opex.api.ports.proxy.data + +data class AssignAddressRequest( + val uuid: String, + val currency: String +) \ No newline at end of file diff --git a/api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/CancelOrderRequest.kt b/api/api-ports/api-proxy-rest/src/main/kotlin/co/nilin/opex/api/ports/proxy/data/CancelOrderRequest.kt similarity index 69% rename from api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/CancelOrderRequest.kt rename to api/api-ports/api-proxy-rest/src/main/kotlin/co/nilin/opex/api/ports/proxy/data/CancelOrderRequest.kt index 874e5ff99..29eb240c4 100644 --- a/api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/CancelOrderRequest.kt +++ b/api/api-ports/api-proxy-rest/src/main/kotlin/co/nilin/opex/api/ports/proxy/data/CancelOrderRequest.kt @@ -1,3 +1,3 @@ -package co.nilin.opex.api.core.inout +package co.nilin.opex.api.ports.proxy.data class CancelOrderRequest(val ouid: String, val uuid: String, val orderId: Long, val symbol: String) \ No newline at end of file diff --git a/api/api-ports/api-proxy-rest/src/main/kotlin/co/nilin/opex/api/ports/proxy/data/CreateOrderRequest.kt b/api/api-ports/api-proxy-rest/src/main/kotlin/co/nilin/opex/api/ports/proxy/data/CreateOrderRequest.kt new file mode 100644 index 000000000..4aabaccbd --- /dev/null +++ b/api/api-ports/api-proxy-rest/src/main/kotlin/co/nilin/opex/api/ports/proxy/data/CreateOrderRequest.kt @@ -0,0 +1,16 @@ +package co.nilin.opex.api.ports.proxy.data + +import co.nilin.opex.api.core.inout.MatchConstraint +import co.nilin.opex.api.core.inout.MatchingOrderType +import co.nilin.opex.api.core.inout.OrderDirection +import java.math.BigDecimal + +data class CreateOrderRequest( + var uuid: String?, + val pair: String, + val price: BigDecimal, + val quantity: BigDecimal, + val direction: OrderDirection, + val matchConstraint: MatchConstraint?, + val orderType: MatchingOrderType +) \ No newline at end of file diff --git a/api/api-ports/api-proxy-rest/src/main/kotlin/co/nilin/opex/api/ports/proxy/data/DepositDetailsRequest.kt b/api/api-ports/api-proxy-rest/src/main/kotlin/co/nilin/opex/api/ports/proxy/data/DepositDetailsRequest.kt new file mode 100644 index 000000000..91c992bac --- /dev/null +++ b/api/api-ports/api-proxy-rest/src/main/kotlin/co/nilin/opex/api/ports/proxy/data/DepositDetailsRequest.kt @@ -0,0 +1,3 @@ +package co.nilin.opex.api.ports.proxy.data + +data class DepositDetailsRequest(val refs: List) \ No newline at end of file diff --git a/api/api-ports/api-proxy-rest/src/main/kotlin/co/nilin/opex/api/ports/proxy/data/QueryOrderRequest.kt b/api/api-ports/api-proxy-rest/src/main/kotlin/co/nilin/opex/api/ports/proxy/data/QueryOrderRequest.kt new file mode 100644 index 000000000..6b9dce678 --- /dev/null +++ b/api/api-ports/api-proxy-rest/src/main/kotlin/co/nilin/opex/api/ports/proxy/data/QueryOrderRequest.kt @@ -0,0 +1,7 @@ +package co.nilin.opex.api.ports.proxy.data + +data class QueryOrderRequest( + val symbol: String, + val orderId: Long?, + val origClientOrderId: String? +) \ No newline at end of file diff --git a/api/api-ports/api-proxy-rest/src/main/kotlin/co/nilin/opex/api/ports/proxy/data/TradeRequest.kt b/api/api-ports/api-proxy-rest/src/main/kotlin/co/nilin/opex/api/ports/proxy/data/TradeRequest.kt new file mode 100644 index 000000000..6cb3d8047 --- /dev/null +++ b/api/api-ports/api-proxy-rest/src/main/kotlin/co/nilin/opex/api/ports/proxy/data/TradeRequest.kt @@ -0,0 +1,11 @@ +package co.nilin.opex.api.ports.proxy.data + +import java.util.* + +class TradeRequest( + val symbol: String?, + val fromTrade: Long?, + val startTime: Date?, + val endTime: Date?, + val limit: Int? = 500 //Default 500; max 1000. +) \ No newline at end of file diff --git a/api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/api/ports/binance/data/TransactionRequest.kt b/api/api-ports/api-proxy-rest/src/main/kotlin/co/nilin/opex/api/ports/proxy/data/TransactionRequest.kt similarity index 76% rename from api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/api/ports/binance/data/TransactionRequest.kt rename to api/api-ports/api-proxy-rest/src/main/kotlin/co/nilin/opex/api/ports/proxy/data/TransactionRequest.kt index 2f64051b9..f3b61e984 100644 --- a/api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/api/ports/binance/data/TransactionRequest.kt +++ b/api/api-ports/api-proxy-rest/src/main/kotlin/co/nilin/opex/api/ports/proxy/data/TransactionRequest.kt @@ -1,4 +1,4 @@ -package co.nilin.opex.api.ports.binance.data +package co.nilin.opex.api.ports.proxy.data data class TransactionRequest( val coin: String?, diff --git a/api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/api/ports/binance/proxy/AccountantProxyImpl.kt b/api/api-ports/api-proxy-rest/src/main/kotlin/co/nilin/opex/api/ports/proxy/impl/AccountantProxyImpl.kt similarity index 63% rename from api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/api/ports/binance/proxy/AccountantProxyImpl.kt rename to api/api-ports/api-proxy-rest/src/main/kotlin/co/nilin/opex/api/ports/proxy/impl/AccountantProxyImpl.kt index 7bee20c11..4b64008ce 100644 --- a/api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/api/ports/binance/proxy/AccountantProxyImpl.kt +++ b/api/api-ports/api-proxy-rest/src/main/kotlin/co/nilin/opex/api/ports/proxy/impl/AccountantProxyImpl.kt @@ -1,18 +1,16 @@ -package co.nilin.opex.api.ports.binance.proxy +package co.nilin.opex.api.ports.proxy.impl import co.nilin.opex.api.core.inout.PairFeeResponse import co.nilin.opex.api.core.inout.PairInfoResponse import co.nilin.opex.api.core.spi.AccountantProxy -import co.nilin.opex.api.ports.binance.util.LoggerDelegate +import co.nilin.opex.api.core.utils.LoggerDelegate import kotlinx.coroutines.reactive.awaitSingle import org.springframework.beans.factory.annotation.Value -import org.springframework.core.ParameterizedTypeReference import org.springframework.http.MediaType import org.springframework.stereotype.Component import org.springframework.web.reactive.function.client.WebClient - -private inline fun typeRef(): ParameterizedTypeReference = - object : ParameterizedTypeReference() {} +import org.springframework.web.reactive.function.client.bodyToFlux +import org.springframework.web.reactive.function.client.bodyToMono @Component class AccountantProxyImpl(private val webClient: WebClient) : AccountantProxy { @@ -29,7 +27,7 @@ class AccountantProxyImpl(private val webClient: WebClient) : AccountantProxy { .accept(MediaType.APPLICATION_JSON) .retrieve() .onStatus({ t -> t.isError }, { it.createException() }) - .bodyToFlux(typeRef()) + .bodyToFlux() .collectList() .awaitSingle() } @@ -37,12 +35,23 @@ class AccountantProxyImpl(private val webClient: WebClient) : AccountantProxy { override suspend fun getFeeConfigs(): List { logger.info("fetching fee configs") return webClient.get() - .uri("$baseUrl/config/fee/all") + .uri("$baseUrl/config/fee") .accept(MediaType.APPLICATION_JSON) .retrieve() .onStatus({ t -> t.isError }, { it.createException() }) - .bodyToFlux(typeRef()) + .bodyToFlux() .collectList() .awaitSingle() } + + override suspend fun getFeeConfig(symbol: String): PairFeeResponse { + logger.info("fetching fee configs") + return webClient.get() + .uri("$baseUrl/config/fee/$symbol") + .accept(MediaType.APPLICATION_JSON) + .retrieve() + .onStatus({ t -> t.isError }, { it.createException() }) + .bodyToMono() + .awaitSingle() + } } \ No newline at end of file diff --git a/api/api-ports/api-proxy-rest/src/main/kotlin/co/nilin/opex/api/ports/proxy/impl/BinanceGlobalMarketProxy.kt b/api/api-ports/api-proxy-rest/src/main/kotlin/co/nilin/opex/api/ports/proxy/impl/BinanceGlobalMarketProxy.kt new file mode 100644 index 000000000..f362c0273 --- /dev/null +++ b/api/api-ports/api-proxy-rest/src/main/kotlin/co/nilin/opex/api/ports/proxy/impl/BinanceGlobalMarketProxy.kt @@ -0,0 +1,42 @@ +package co.nilin.opex.api.ports.proxy.impl + +import co.nilin.opex.api.core.inout.GlobalPrice +import co.nilin.opex.api.core.spi.GlobalMarketProxy +import kotlinx.coroutines.reactive.awaitFirstOrElse +import org.springframework.beans.factory.annotation.Value +import org.springframework.http.HttpHeaders +import org.springframework.http.MediaType +import org.springframework.stereotype.Component +import org.springframework.web.reactive.function.client.WebClient +import org.springframework.web.reactive.function.client.bodyToFlux +import org.springframework.web.util.UriComponentsBuilder +import java.net.URLEncoder + +@Component +class BinanceGlobalMarketProxy( + @Value("\${app.binance.api-url}") + val baseUrl: String +) : GlobalMarketProxy { + + private val webClient = WebClient.builder().build() + + override suspend fun getPrices(symbols: List): List { + // Binance encoding requires to change some of the Java's encoding model + // https://binance-docs.github.io/apidocs/spot/en/#symbol-price-ticker + val param = symbols.map { s -> "\"$s\"" }.toString().replace(" ", "") + val uri= UriComponentsBuilder.fromUriString("$baseUrl/api/v3/ticker/price") + .queryParam("symbols", URLEncoder.encode(param, Charsets.UTF_8).replace("%2C", ",")) + .build(true) + .toUri() + + return webClient.get() + .uri(uri) + .accept(MediaType.APPLICATION_JSON) + .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) + .retrieve() + .onStatus({ t -> t.isError }, { it.createException() }) + .bodyToFlux() + .collectList() + .awaitFirstOrElse { emptyList() } + } +} \ No newline at end of file diff --git a/api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/api/ports/binance/proxy/BlockchainGatewayProxyImpl.kt b/api/api-ports/api-proxy-rest/src/main/kotlin/co/nilin/opex/api/ports/proxy/impl/BlockchainGatewayProxyImpl.kt similarity index 78% rename from api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/api/ports/binance/proxy/BlockchainGatewayProxyImpl.kt rename to api/api-ports/api-proxy-rest/src/main/kotlin/co/nilin/opex/api/ports/proxy/impl/BlockchainGatewayProxyImpl.kt index d71500d1a..ab24c6388 100644 --- a/api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/api/ports/binance/proxy/BlockchainGatewayProxyImpl.kt +++ b/api/api-ports/api-proxy-rest/src/main/kotlin/co/nilin/opex/api/ports/proxy/impl/BlockchainGatewayProxyImpl.kt @@ -1,33 +1,25 @@ -package co.nilin.opex.api.ports.binance.proxy +package co.nilin.opex.api.ports.proxy.impl import co.nilin.opex.api.core.inout.AssignResponse import co.nilin.opex.api.core.inout.DepositDetails import co.nilin.opex.api.core.spi.BlockchainGatewayProxy -import co.nilin.opex.api.ports.binance.util.LoggerDelegate +import co.nilin.opex.api.core.utils.LoggerDelegate +import co.nilin.opex.api.ports.proxy.data.AssignAddressRequest +import co.nilin.opex.api.ports.proxy.data.DepositDetailsRequest import kotlinx.coroutines.reactive.awaitFirstOrElse import kotlinx.coroutines.reactor.awaitSingleOrNull import org.springframework.beans.factory.annotation.Value -import org.springframework.core.ParameterizedTypeReference import org.springframework.http.MediaType import org.springframework.stereotype.Component import org.springframework.web.reactive.function.client.WebClient import org.springframework.web.reactive.function.client.body +import org.springframework.web.reactive.function.client.bodyToFlux import reactor.core.publisher.Mono import java.net.URI -private inline fun typeRef(): ParameterizedTypeReference = - object : ParameterizedTypeReference() {} - @Component class BlockchainGatewayProxyImpl(private val client: WebClient) : BlockchainGatewayProxy { - private data class AssignAddressRequest( - val uuid: String, - val currency: String - ) - - private data class DepositDetailsRequest(val refs: List) - private val logger by LoggerDelegate() @Value("\${app.opex-bc-gateway.url}") @@ -55,7 +47,8 @@ class BlockchainGatewayProxyImpl(private val client: WebClient) : BlockchainGate .body(Mono.just(DepositDetailsRequest(refs))) .retrieve() .onStatus({ t -> t.isError }, { it.createException() }) - .bodyToMono(typeRef>()) + .bodyToFlux() + .collectList() .awaitFirstOrElse { emptyList() } } -} +} \ No newline at end of file diff --git a/api/api-ports/api-proxy-rest/src/main/kotlin/co/nilin/opex/api/ports/proxy/impl/MarketDataProxyImpl.kt b/api/api-ports/api-proxy-rest/src/main/kotlin/co/nilin/opex/api/ports/proxy/impl/MarketDataProxyImpl.kt new file mode 100644 index 000000000..b231b066a --- /dev/null +++ b/api/api-ports/api-proxy-rest/src/main/kotlin/co/nilin/opex/api/ports/proxy/impl/MarketDataProxyImpl.kt @@ -0,0 +1,227 @@ +package co.nilin.opex.api.ports.proxy.impl + +import co.nilin.opex.api.core.inout.* +import co.nilin.opex.api.core.spi.MarketDataProxy +import co.nilin.opex.api.core.utils.LoggerDelegate +import kotlinx.coroutines.reactive.awaitFirstOrElse +import kotlinx.coroutines.reactor.awaitSingle +import kotlinx.coroutines.reactor.awaitSingleOrNull +import org.springframework.beans.factory.annotation.Value +import org.springframework.http.HttpHeaders +import org.springframework.http.MediaType +import org.springframework.stereotype.Component +import org.springframework.web.reactive.function.client.WebClient +import org.springframework.web.reactive.function.client.bodyToFlux +import org.springframework.web.reactive.function.client.bodyToMono + +@Component +class MarketDataProxyImpl(private val webClient: WebClient) : MarketDataProxy { + + private val logger by LoggerDelegate() + + @Value("\${app.market.url}") + private lateinit var baseUrl: String + + override suspend fun getTradeTickerData(startFrom: Long): List { + return webClient.get() + .uri("$baseUrl/v1/market/ticker") { + it.queryParam("since", startFrom) + it.build() + }.accept(MediaType.APPLICATION_JSON) + .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) + .retrieve() + .onStatus({ t -> t.isError }, { it.createException() }) + .bodyToFlux() + .collectList() + .awaitFirstOrElse { emptyList() } + } + + override suspend fun getTradeTickerDataBySymbol(symbol: String, startFrom: Long): PriceChange { + return webClient.get() + .uri("$baseUrl/v1/market/$symbol/ticker") { + it.queryParam("since", startFrom) + it.build() + }.accept(MediaType.APPLICATION_JSON) + .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) + .retrieve() + .onStatus({ t -> t.isError }, { it.createException() }) + .bodyToMono() + .awaitSingle() + } + + override suspend fun openBidOrders(symbol: String, limit: Int): List { + return webClient.get() + .uri("$baseUrl/v1/market/$symbol/order-book") { + it.queryParam("limit", limit) + it.queryParam("direction", OrderDirection.BID) + it.build() + }.accept(MediaType.APPLICATION_JSON) + .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) + .retrieve() + .onStatus({ t -> t.isError }, { it.createException() }) + .bodyToFlux() + .collectList() + .awaitFirstOrElse { emptyList() } + } + + override suspend fun openAskOrders(symbol: String, limit: Int): List { + return webClient.get() + .uri("$baseUrl/v1/market/$symbol/order-book") { + it.queryParam("limit", limit) + it.queryParam("direction", OrderDirection.ASK) + it.build() + }.accept(MediaType.APPLICATION_JSON) + .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) + .retrieve() + .onStatus({ t -> t.isError }, { it.createException() }) + .bodyToFlux() + .collectList() + .awaitFirstOrElse { emptyList() } + } + + override suspend fun lastOrder(symbol: String): Order? { + return webClient.get() + .uri("$baseUrl/v1/market/$symbol/last-order") + .accept(MediaType.APPLICATION_JSON) + .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) + .retrieve() + .onStatus({ t -> t.isError }, { it.createException() }) + .bodyToMono() + .awaitSingleOrNull() + } + + override suspend fun recentTrades(symbol: String, limit: Int): List { + return webClient.get() + .uri("$baseUrl/v1/market/$symbol/recent-trades") { + it.queryParam("limit", limit) + it.build() + }.accept(MediaType.APPLICATION_JSON) + .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) + .retrieve() + .onStatus({ t -> t.isError }, { it.createException() }) + .bodyToFlux() + .collectList() + .awaitFirstOrElse { emptyList() } + } + + override suspend fun lastPrice(symbol: String?): List { + return webClient.get() + .uri("$baseUrl/v1/market/prices") { + it.queryParam("symbol", symbol) + it.build() + }.accept(MediaType.APPLICATION_JSON) + .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) + .retrieve() + .onStatus({ t -> t.isError }, { it.createException() }) + .bodyToFlux() + .collectList() + .awaitFirstOrElse { emptyList() } + } + + override suspend fun getBestPriceForSymbols(symbols: List): List { + return webClient.get() + .uri("$baseUrl/v1/market/best-prices") { + it.queryParam("symbols", symbols) + it.build() + }.accept(MediaType.APPLICATION_JSON) + .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) + .retrieve() + .onStatus({ t -> t.isError }, { it.createException() }) + .bodyToFlux() + .collectList() + .awaitFirstOrElse { emptyList() } + } + + override suspend fun getCandleInfo( + symbol: String, + interval: String, + startTime: Long?, + endTime: Long?, + limit: Int + ): List { + return webClient.get() + .uri("$baseUrl/v1/chart/$symbol/candle") { + it.queryParam("interval", interval) + it.queryParam("since", startTime) + it.queryParam("until", endTime) + it.queryParam("limit", limit) + it.build() + }.accept(MediaType.APPLICATION_JSON) + .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) + .retrieve() + .onStatus({ t -> t.isError }, { it.createException() }) + .bodyToFlux() + .collectList() + .awaitFirstOrElse { emptyList() } + } + + override suspend fun getMarketCurrencyRates(quote: String, base: String?): List { + return webClient.get() + .uri(if (base.isNullOrEmpty()) "$baseUrl/v1/rate/EXTERNAL" else "$baseUrl/v1/rate/$base/EXTERNAL") { + it.queryParam("quote", quote) + it.build() + }.accept(MediaType.APPLICATION_JSON) + .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) + .retrieve() + .onStatus({ t -> t.isError }, { it.createException() }) + .bodyToFlux() + .collectList() + .awaitFirstOrElse { emptyList() } + } + + override suspend fun getExternalCurrencyRates(quote: String, base: String?): List { + return webClient.get() + .uri(if (base.isNullOrEmpty()) "$baseUrl/v1/rate/EXTERNAL" else "$baseUrl/v1/rate/$base/EXTERNAL") { + it.queryParam("quote", quote) + it.build() + }.accept(MediaType.APPLICATION_JSON) + .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) + .retrieve() + .onStatus({ t -> t.isError }, { it.createException() }) + .bodyToFlux() + .collectList() + .awaitFirstOrElse { emptyList() } + } + + override suspend fun countActiveUsers(since: Long): Long { + return webClient.get() + .uri("$baseUrl/v1/market/active-users") { + it.queryParam("interval", since) + it.build() + }.accept(MediaType.APPLICATION_JSON) + .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) + .retrieve() + .onStatus({ t -> t.isError }, { it.createException() }) + .bodyToMono() + .awaitSingleOrNull() + ?.value ?: 0 + } + + override suspend fun countTotalOrders(since: Long): Long { + return webClient.get() + .uri("$baseUrl/v1/market/active-users") { + it.queryParam("interval", since) + it.build() + }.accept(MediaType.APPLICATION_JSON) + .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) + .retrieve() + .onStatus({ t -> t.isError }, { it.createException() }) + .bodyToMono() + .awaitSingleOrNull() + ?.value ?: 0 + } + + override suspend fun countTotalTrades(since: Long): Long { + return webClient.get() + .uri("$baseUrl/v1/market/trades-count") { + it.queryParam("interval", since) + it.build() + }.accept(MediaType.APPLICATION_JSON) + .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) + .retrieve() + .onStatus({ t -> t.isError }, { it.createException() }) + .bodyToMono() + .awaitSingleOrNull() + ?.value ?: 0 + } +} \ No newline at end of file diff --git a/api/api-ports/api-proxy-rest/src/main/kotlin/co/nilin/opex/api/ports/proxy/impl/MarketStatProxyImpl.kt b/api/api-ports/api-proxy-rest/src/main/kotlin/co/nilin/opex/api/ports/proxy/impl/MarketStatProxyImpl.kt new file mode 100644 index 000000000..395272f86 --- /dev/null +++ b/api/api-ports/api-proxy-rest/src/main/kotlin/co/nilin/opex/api/ports/proxy/impl/MarketStatProxyImpl.kt @@ -0,0 +1,78 @@ +package co.nilin.opex.api.ports.proxy.impl + +import co.nilin.opex.api.core.inout.PriceStat +import co.nilin.opex.api.core.inout.TradeVolumeStat +import co.nilin.opex.api.core.spi.MarketStatProxy +import kotlinx.coroutines.reactive.awaitFirstOrElse +import kotlinx.coroutines.reactor.awaitSingleOrNull +import org.springframework.beans.factory.annotation.Value +import org.springframework.http.HttpHeaders +import org.springframework.http.MediaType +import org.springframework.stereotype.Component +import org.springframework.web.reactive.function.client.WebClient +import org.springframework.web.reactive.function.client.bodyToFlux +import org.springframework.web.reactive.function.client.bodyToMono + +@Component +class MarketStatProxyImpl( + private val webClient: WebClient, + @Value("\${app.market.url}") + private val baseUrl: String +) : MarketStatProxy { + + override suspend fun getMostIncreasedInPricePairs(interval: Long, limit: Int): List { + return webClient.get() + .uri("$baseUrl/v1/stats/price/most-increased") { + it.queryParam("interval", interval) + it.queryParam("limit", limit) + it.build() + }.accept(MediaType.APPLICATION_JSON) + .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) + .retrieve() + .onStatus({ t -> t.isError }, { it.createException() }) + .bodyToFlux() + .collectList() + .awaitFirstOrElse { emptyList() } + } + + override suspend fun getMostDecreasedInPricePairs(interval: Long, limit: Int): List { + return webClient.get() + .uri("$baseUrl/v1/stats/price/most-decreased") { + it.queryParam("interval", interval) + it.queryParam("limit", limit) + it.build() + }.accept(MediaType.APPLICATION_JSON) + .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) + .retrieve() + .onStatus({ t -> t.isError }, { it.createException() }) + .bodyToFlux() + .collectList() + .awaitFirstOrElse { emptyList() } + } + + override suspend fun getHighestVolumePair(interval: Long): TradeVolumeStat? { + return webClient.get() + .uri("$baseUrl/v1/stats/volume/highest") { + it.queryParam("interval", interval) + it.build() + }.accept(MediaType.APPLICATION_JSON) + .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) + .retrieve() + .onStatus({ t -> t.isError }, { it.createException() }) + .bodyToMono() + .awaitSingleOrNull() + } + + override suspend fun getTradeCountPair(interval: Long): TradeVolumeStat? { + return webClient.get() + .uri("$baseUrl/v1/stats/most-trades") { + it.queryParam("interval", interval) + it.build() + }.accept(MediaType.APPLICATION_JSON) + .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) + .retrieve() + .onStatus({ t -> t.isError }, { it.createException() }) + .bodyToMono() + .awaitSingleOrNull() + } +} \ No newline at end of file diff --git a/api/api-ports/api-proxy-rest/src/main/kotlin/co/nilin/opex/api/ports/proxy/impl/MarketUserDataProxyImpl.kt b/api/api-ports/api-proxy-rest/src/main/kotlin/co/nilin/opex/api/ports/proxy/impl/MarketUserDataProxyImpl.kt new file mode 100644 index 000000000..9987c76c1 --- /dev/null +++ b/api/api-ports/api-proxy-rest/src/main/kotlin/co/nilin/opex/api/ports/proxy/impl/MarketUserDataProxyImpl.kt @@ -0,0 +1,100 @@ +package co.nilin.opex.api.ports.proxy.impl + +import co.nilin.opex.api.core.inout.Order +import co.nilin.opex.api.core.inout.Trade +import co.nilin.opex.api.core.spi.MarketUserDataProxy +import co.nilin.opex.api.core.utils.LoggerDelegate +import co.nilin.opex.api.ports.proxy.data.AllOrderRequest +import co.nilin.opex.api.ports.proxy.data.QueryOrderRequest +import co.nilin.opex.api.ports.proxy.data.TradeRequest +import kotlinx.coroutines.reactive.awaitFirstOrElse +import kotlinx.coroutines.reactor.awaitSingleOrNull +import org.springframework.beans.factory.annotation.Value +import org.springframework.http.HttpHeaders +import org.springframework.http.MediaType +import org.springframework.stereotype.Component +import org.springframework.web.reactive.function.client.WebClient +import org.springframework.web.reactive.function.client.body +import org.springframework.web.reactive.function.client.bodyToFlux +import org.springframework.web.reactive.function.client.bodyToMono +import reactor.core.publisher.Mono +import java.security.Principal +import java.util.* + +@Component +class MarketUserDataProxyImpl(private val webClient: WebClient) : MarketUserDataProxy { + + private val logger by LoggerDelegate() + + @Value("\${app.market.url}") + private lateinit var baseUrl: String + + override suspend fun queryOrder( + principal: Principal, + symbol: String, + orderId: Long?, + origClientOrderId: String? + ): Order? { + return webClient.post() + .uri("$baseUrl/v1/user/${principal.name}/order/query") + .accept(MediaType.APPLICATION_JSON) + .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) + .body(Mono.just(QueryOrderRequest(symbol, orderId, origClientOrderId))) + .retrieve() + .onStatus({ t -> t.isError }, { it.createException() }) + .bodyToMono() + .awaitSingleOrNull() + } + + override suspend fun openOrders(principal: Principal, symbol: String?): List { + return webClient.get() + .uri("$baseUrl/v1/user/${principal.name}/orders/$symbol/open") + .accept(MediaType.APPLICATION_JSON) + .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) + .retrieve() + .onStatus({ t -> t.isError }, { it.createException() }) + .bodyToFlux() + .collectList() + .awaitFirstOrElse { emptyList() } + } + + override suspend fun allOrders( + principal: Principal, + symbol: String?, + startTime: Date?, + endTime: Date?, + limit: Int? + ): List { + return webClient.post() + .uri("$baseUrl/v1/user/${principal.name}/orders") + .accept(MediaType.APPLICATION_JSON) + .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) + .body(Mono.just(AllOrderRequest(symbol, startTime, endTime, limit))) + .retrieve() + .onStatus({ t -> t.isError }, { it.createException() }) + .bodyToFlux() + .collectList() + .awaitFirstOrElse { emptyList() } + } + + override suspend fun allTrades( + principal: Principal, + symbol: String?, + fromTrade: Long?, + startTime: Date?, + endTime: Date?, + limit: Int? + ): List { + return webClient.post() + .uri("$baseUrl/v1/user/${principal.name}/trades") + .accept(MediaType.APPLICATION_JSON) + .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE) + .body(Mono.just(TradeRequest(symbol, fromTrade, startTime, endTime, limit))) + .retrieve() + .onStatus({ t -> t.isError }, { it.createException() }) + .bodyToFlux() + .collectList() + .awaitFirstOrElse { emptyList() } + } + +} \ No newline at end of file diff --git a/api/api-ports/api-proxy-rest/src/main/kotlin/co/nilin/opex/api/ports/proxy/impl/MatchingGatewayProxyImpl.kt b/api/api-ports/api-proxy-rest/src/main/kotlin/co/nilin/opex/api/ports/proxy/impl/MatchingGatewayProxyImpl.kt new file mode 100644 index 000000000..85b186928 --- /dev/null +++ b/api/api-ports/api-proxy-rest/src/main/kotlin/co/nilin/opex/api/ports/proxy/impl/MatchingGatewayProxyImpl.kt @@ -0,0 +1,73 @@ +package co.nilin.opex.api.ports.proxy.impl + +import co.nilin.opex.api.core.inout.MatchConstraint +import co.nilin.opex.api.core.inout.MatchingOrderType +import co.nilin.opex.api.core.inout.OrderDirection +import co.nilin.opex.api.core.inout.OrderSubmitResult +import co.nilin.opex.api.core.spi.MatchingGatewayProxy +import co.nilin.opex.api.core.utils.LoggerDelegate +import co.nilin.opex.api.ports.proxy.data.CancelOrderRequest +import co.nilin.opex.api.ports.proxy.data.CreateOrderRequest +import kotlinx.coroutines.reactor.awaitSingleOrNull +import org.springframework.beans.factory.annotation.Value +import org.springframework.http.MediaType +import org.springframework.stereotype.Component +import org.springframework.web.reactive.function.client.WebClient +import org.springframework.web.reactive.function.client.body +import org.springframework.web.reactive.function.client.bodyToMono +import reactor.core.publisher.Mono +import java.math.BigDecimal +import java.net.URI + +@Component +class MatchingGatewayProxyImpl(private val client: WebClient) : MatchingGatewayProxy { + + private val logger by LoggerDelegate() + + @Value("\${app.matching-gateway.url}") + private lateinit var baseUrl: String + + override suspend fun createNewOrder( + uuid: String?, + pair: String, + price: BigDecimal, + quantity: BigDecimal, + direction: OrderDirection, + matchConstraint: MatchConstraint?, + orderType: MatchingOrderType, + token: String? + ): OrderSubmitResult? { + logger.info("calling matching-gateway order create") + return client.post() + .uri(URI.create("$baseUrl/order")) + .accept(MediaType.APPLICATION_JSON) + .contentType(MediaType.APPLICATION_JSON) + .header("Authorization", "Bearer $token") + .body(Mono.just(CreateOrderRequest(uuid, pair, price, quantity, direction, matchConstraint, orderType))) + .retrieve() + .onStatus({ t -> t.isError }, { it.createException() }) + .bodyToMono() + .awaitSingleOrNull() + + } + + override suspend fun cancelOrder( + ouid: String, + uuid: String, + orderId: Long, + symbol: String, + token: String? + ): OrderSubmitResult? { + logger.info("calling matching-gateway order cancel") + return client.post() + .uri(URI.create("$baseUrl/order/cancel")) + .accept(MediaType.APPLICATION_JSON) + .contentType(MediaType.APPLICATION_JSON) + .header("Authorization", "Bearer $token") + .body(Mono.just(CancelOrderRequest(ouid, uuid, orderId, symbol))) + .retrieve() + .onStatus({ t -> t.isError }, { it.createException() }) + .bodyToMono() + .awaitSingleOrNull() + } +} \ No newline at end of file diff --git a/api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/api/ports/binance/proxy/WalletProxyImpl.kt b/api/api-ports/api-proxy-rest/src/main/kotlin/co/nilin/opex/api/ports/proxy/impl/WalletProxyImpl.kt similarity index 75% rename from api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/api/ports/binance/proxy/WalletProxyImpl.kt rename to api/api-ports/api-proxy-rest/src/main/kotlin/co/nilin/opex/api/ports/proxy/impl/WalletProxyImpl.kt index e2b1f6644..80482441f 100644 --- a/api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/api/ports/binance/proxy/WalletProxyImpl.kt +++ b/api/api-ports/api-proxy-rest/src/main/kotlin/co/nilin/opex/api/ports/proxy/impl/WalletProxyImpl.kt @@ -1,26 +1,24 @@ -package co.nilin.opex.api.ports.binance.proxy +package co.nilin.opex.api.ports.proxy.impl import co.nilin.opex.api.core.inout.OwnerLimitsResponse import co.nilin.opex.api.core.inout.TransactionHistoryResponse import co.nilin.opex.api.core.inout.Wallet import co.nilin.opex.api.core.inout.WithdrawHistoryResponse import co.nilin.opex.api.core.spi.WalletProxy -import co.nilin.opex.api.ports.binance.data.TransactionRequest -import co.nilin.opex.api.ports.binance.util.LoggerDelegate +import co.nilin.opex.api.core.utils.LoggerDelegate +import co.nilin.opex.api.ports.proxy.data.TransactionRequest import kotlinx.coroutines.reactive.awaitFirstOrElse import kotlinx.coroutines.reactive.awaitSingle import org.springframework.beans.factory.annotation.Value -import org.springframework.core.ParameterizedTypeReference import org.springframework.http.HttpHeaders import org.springframework.http.MediaType import org.springframework.stereotype.Component import org.springframework.web.reactive.function.client.WebClient import org.springframework.web.reactive.function.client.body +import org.springframework.web.reactive.function.client.bodyToFlux +import org.springframework.web.reactive.function.client.bodyToMono import reactor.core.publisher.Mono -private inline fun typeRef(): ParameterizedTypeReference = - object : ParameterizedTypeReference() {} - @Component class WalletProxyImpl(private val webClient: WebClient) : WalletProxy { @@ -32,25 +30,37 @@ class WalletProxyImpl(private val webClient: WebClient) : WalletProxy { override suspend fun getWallets(uuid: String?, token: String?): List { logger.info("fetching wallets for $uuid") return webClient.get() - .uri("$baseUrl/owner/wallet/all") + .uri("$baseUrl/v1/owner/$uuid/wallets") .accept(MediaType.APPLICATION_JSON) .header(HttpHeaders.AUTHORIZATION, "Bearer $token") .retrieve() .onStatus({ t -> t.isError }, { it.createException() }) - .bodyToFlux(typeRef()) + .bodyToFlux() .collectList() .awaitSingle() } + override suspend fun getWallet(uuid: String?, token: String?, symbol: String): Wallet { + logger.info("fetching wallets for $uuid") + return webClient.get() + .uri("$baseUrl/v1/owner/$uuid/wallets/$symbol") + .accept(MediaType.APPLICATION_JSON) + .header(HttpHeaders.AUTHORIZATION, "Bearer $token") + .retrieve() + .onStatus({ t -> t.isError }, { it.createException() }) + .bodyToMono() + .awaitSingle() + } + override suspend fun getOwnerLimits(uuid: String?, token: String?): OwnerLimitsResponse { logger.info("fetching owner limits for $uuid") return webClient.get() - .uri("$baseUrl/owner/limits") + .uri("$baseUrl/v1/owner/$uuid/limits") .accept(MediaType.APPLICATION_JSON) .header(HttpHeaders.AUTHORIZATION, "Bearer $token") .retrieve() .onStatus({ t -> t.isError }, { it.createException() }) - .bodyToMono(typeRef()) + .bodyToMono() .awaitSingle() } @@ -71,7 +81,7 @@ class WalletProxyImpl(private val webClient: WebClient) : WalletProxy { .body(Mono.just(TransactionRequest(coin, startTime, endTime, limit, offset))) .retrieve() .onStatus({ t -> t.isError }, { it.createException() }) - .bodyToFlux(typeRef()) + .bodyToFlux() .collectList() .awaitFirstOrElse { emptyList() } } @@ -93,7 +103,7 @@ class WalletProxyImpl(private val webClient: WebClient) : WalletProxy { .body(Mono.just(TransactionRequest(coin, startTime, endTime, limit, offset))) .retrieve() .onStatus({ t -> t.isError }, { it.createException() }) - .bodyToFlux(typeRef()) + .bodyToFlux() .collectList() .awaitFirstOrElse { emptyList() } } diff --git a/api/pom.xml b/api/pom.xml index 574bba6e8..f3d0726c2 100644 --- a/api/pom.xml +++ b/api/pom.xml @@ -20,7 +20,7 @@ api-app api-ports/api-persister-postgres api-ports/api-binance-rest - api-ports/api-eventlistener-kafka + api-ports/api-proxy-rest @@ -38,8 +38,8 @@ ${project.version} - co.nilin.opex.api.ports.kafka.listener - api-eventlistener-kafka + co.nilin.opex.api.ports.proxy + api-proxy-rest ${project.version} diff --git a/docker-compose.build.yml b/docker-compose.build.yml index 4cd76b501..6d610a59b 100644 --- a/docker-compose.build.yml +++ b/docker-compose.build.yml @@ -24,6 +24,9 @@ services: wallet: image: ghcr.io/opexdev/wallet:$TAG build: wallet/wallet-app + market: + image: ghcr.io/opexdev/market:$TAG + build: market/market-app api: image: ghcr.io/opexdev/api:$TAG build: api/api-app diff --git a/docker-compose.override.yml b/docker-compose.override.yml index 42d161abf..038bf9623 100644 --- a/docker-compose.override.yml +++ b/docker-compose.override.yml @@ -23,6 +23,10 @@ services: build: wallet/wallet-app volumes: - "./preferences-dev.yml:/preferences.yml" + market: + build: market/market-app + volumes: + - "./preferences-dev.yml:/preferences.yml" api: build: api/api-app volumes: diff --git a/docker-compose.yml b/docker-compose.yml index fd96279eb..6848c3688 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -40,7 +40,6 @@ services: - KAFKA_LISTENERS=CLIENT://kafka-1:29092,EXTERNAL://kafka-1:9092 - KAFKA_ADVERTISED_LISTENERS=CLIENT://kafka-1:29092,EXTERNAL://kafka-1:9092 - KAFKA_INTER_BROKER_LISTENER_NAME=CLIENT - - KAFKA_MIN_INSYNC_REPLICAS=2 - KAFKA_UNCLEAN_LEADER_ELECTION_ENABLE=false depends_on: - zookeeper @@ -61,7 +60,6 @@ services: - KAFKA_LISTENERS=CLIENT://kafka-2:29092,EXTERNAL://kafka-2:9092 - KAFKA_ADVERTISED_LISTENERS=CLIENT://kafka-2:29092,EXTERNAL://kafka-2:9092 - KAFKA_INTER_BROKER_LISTENER_NAME=CLIENT - - KAFKA_MIN_INSYNC_REPLICAS=2 - KAFKA_UNCLEAN_LEADER_ELECTION_ENABLE=false depends_on: - zookeeper @@ -82,7 +80,6 @@ services: - KAFKA_LISTENERS=CLIENT://kafka-3:29092,EXTERNAL://kafka-3:9092 - KAFKA_ADVERTISED_LISTENERS=CLIENT://kafka-3:29092,EXTERNAL://kafka-3:9092 - KAFKA_INTER_BROKER_LISTENER_NAME=CLIENT - - KAFKA_MIN_INSYNC_REPLICAS=2 - KAFKA_UNCLEAN_LEADER_ELECTION_ENABLE=false depends_on: - zookeeper @@ -131,16 +128,6 @@ services: deploy: restart_policy: condition: on-failure - vault-ui: - image: djenriquez/vault-ui - environment: - - VAULT_URL_DEFAULT=http://vault:8200 - - VAULT_AUTH_DEFAULT=USERNAMEPASSWORD - depends_on: - - vault - deploy: - restart_policy: - condition: on-failure consul: image: consul environment: @@ -179,6 +166,10 @@ services: <<: *postgres-db volumes: - wallet-data:/var/lib/postgresql/data/ + postgres-market: + <<: *postgres-db + volumes: + - market-data:/var/lib/postgresql/data/ postgres-api: <<: *postgres-db volumes: @@ -328,11 +319,34 @@ services: deploy: restart_policy: condition: on-failure + market: + image: ghcr.io/opexdev/market + environment: + - JAVA_OPTS=-Xmx256m -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005 + - KAFKA_IP_PORT=kafka-1:29092,kafka-2:29092,kafka-3:29092 + - CONSUL_HOST=consul + - DB_IP_PORT=postgres-market + - BACKEND_USER=${BACKEND_USER} + - VAULT_HOST=vault + - SWAGGER_AUTH_URL=$KEYCLOAK_FRONTEND_URL + - PREFERENCES=$PREFERENCES + depends_on: + - kafka-1 + - kafka-2 + - kafka-3 + - auth + - consul + - vault + - postgres-market + networks: + - default + deploy: + restart_policy: + condition: on-failure api: image: ghcr.io/opexdev/api environment: - JAVA_OPTS=-Xmx256m -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005 - - KAFKA_IP_PORT=kafka-1:29092,kafka-2:29092,kafka-3:29092 - CONSUL_HOST=consul - DB_IP_PORT=postgres-api - BACKEND_USER=${BACKEND_USER} @@ -342,12 +356,10 @@ services: configs: - preferences.yml depends_on: - - kafka-1 - - kafka-2 - - kafka-3 - accountant - matching-gateway - wallet + - market - bc-gateway - auth - referral @@ -523,6 +535,7 @@ volumes: eventlog-data: auth-data: wallet-data: + market-data: api-data: bc-gateway-data: referral-data: diff --git a/docker-images/vault/vault.json b/docker-images/vault/vault.json index fbe384e20..a1e2fe453 100644 --- a/docker-images/vault/vault.json +++ b/docker-images/vault/vault.json @@ -12,5 +12,6 @@ }, "default_lease_ttl": "168h", "max_lease_ttl": "0h", - "api_addr": "http://0.0.0.0:8200" + "api_addr": "http://0.0.0.0:8200", + "ui": true } diff --git a/docker-images/vault/workflow-vault.sh b/docker-images/vault/workflow-vault.sh index 1af92b520..10f04d7bf 100755 --- a/docker-images/vault/workflow-vault.sh +++ b/docker-images/vault/workflow-vault.sh @@ -46,6 +46,7 @@ init_secrets() { ## Enable backend apps vault write auth/app-id/map/app-id/opex-accountant value=backend-policy display_name=opex-accountant vault write auth/app-id/map/app-id/opex-api value=backend-policy display_name=opex-api + vault write auth/app-id/map/app-id/opex-market value=backend-policy display_name=opex-market vault write auth/app-id/map/app-id/opex-bc-gateway value=backend-policy display_name=opex-bc-gateway vault write auth/app-id/map/app-id/opex-eventlog value=backend-policy display_name=opex-eventlog vault write auth/app-id/map/app-id/opex-auth value=backend-policy display_name=opex-auth @@ -61,11 +62,12 @@ init_secrets() { ## Enable user-id vault write auth/app-id/map/user-id/${BACKEND_USER} \ - value=opex-wallet,opex-websocket,opex-eventlog,opex-auth,opex-accountant,opex-api,opex-bc-gateway,opex-payment,opex-admin,bitcoin-scanner,ethereum-scanner,tron-scanner,scanner-scheduler,opex-referral + value=opex-wallet,opex-websocket,opex-eventlog,opex-auth,opex-accountant,opex-api,opex-market,opex-bc-gateway,opex-payment,opex-admin,bitcoin-scanner,ethereum-scanner,tron-scanner,scanner-scheduler,opex-referral ## Check login app-id vault write auth/app-id/login/opex-accountant user_id=${BACKEND_USER} vault write auth/app-id/login/opex-api user_id=${BACKEND_USER} + vault write auth/app-id/login/opex-market user_id=${BACKEND_USER} vault write auth/app-id/login/opex-bc-gateway user_id=${BACKEND_USER} vault write auth/app-id/login/opex-eventlog user_id=${BACKEND_USER} vault write auth/app-id/login/opex-auth user_id=${BACKEND_USER} @@ -83,6 +85,7 @@ init_secrets() { vault kv put secret/opex smtppass=${SMTP_PASS} vault kv put secret/opex-accountant dbusername=${DB_USER} dbpassword=${DB_PASS} db_read_only_username=${DB_READ_ONLY_USER} db_read_only_pass=${DB_READ_ONLY_PASS} vault kv put secret/opex-api dbusername=${DB_USER} dbpassword=${DB_PASS} db_read_only_username=${DB_READ_ONLY_USER} db_read_only_pass=${DB_READ_ONLY_PASS} + vault kv put secret/opex-market dbusername=${DB_USER} dbpassword=${DB_PASS} db_read_only_username=${DB_READ_ONLY_USER} db_read_only_pass=${DB_READ_ONLY_PASS} vault kv put secret/opex-bc-gateway dbusername=${DB_USER} dbpassword=${DB_PASS} db_read_only_username=${DB_READ_ONLY_USER} db_read_only_pass=${DB_READ_ONLY_PASS} vault kv put secret/opex-eventlog dbusername=${DB_USER} dbpassword=${DB_PASS} db_read_only_username=${DB_READ_ONLY_USER} db_read_only_pass=${DB_READ_ONLY_PASS} vault kv put secret/opex-auth dbusername=${DB_USER} dbpassword=${DB_PASS} admin_username=${KEYCLOAK_ADMIN_USERNAME} admin_password=${KEYCLOAK_ADMIN_PASSWORD} diff --git a/market/market-app/.gitignore b/market/market-app/.gitignore new file mode 100644 index 000000000..549e00a2a --- /dev/null +++ b/market/market-app/.gitignore @@ -0,0 +1,33 @@ +HELP.md +target/ +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ +build/ +!**/src/main/**/build/ +!**/src/test/**/build/ + +### VS Code ### +.vscode/ diff --git a/market/market-app/Dockerfile b/market/market-app/Dockerfile new file mode 100644 index 000000000..3e24f1ef1 --- /dev/null +++ b/market/market-app/Dockerfile @@ -0,0 +1,5 @@ +FROM openjdk:11 +ARG JAR_FILE=target/*.jar +COPY ${JAR_FILE} app.jar +ENTRYPOINT ["sh", "-c", "java ${JAVA_OPTS} -jar /app.jar"] +HEALTHCHECK --interval=45s --start-period=30s --retries=5 CMD curl -sf 'http://localhost:8080/actuator/health' >/dev/null || exit 1 \ No newline at end of file diff --git a/market/market-app/pom.xml b/market/market-app/pom.xml new file mode 100644 index 000000000..a264a8522 --- /dev/null +++ b/market/market-app/pom.xml @@ -0,0 +1,101 @@ + + + 4.0.0 + + + co.nilin.opex.market + market + 1.0.0-beta.3 + + + co.nilin.opex.market.app + market-app + market-app + Market app Opex + + + + org.springframework.cloud + spring-cloud-starter-consul-all + + + org.springframework.boot + spring-boot-starter-security + + + org.springframework.boot + spring-boot-starter-webflux + + + org.jetbrains.kotlinx + kotlinx-coroutines-reactor + + + org.jetbrains.kotlin + kotlin-reflect + + + org.springframework.boot + spring-boot-starter-oauth2-resource-server + + + com.fasterxml.jackson.module + jackson-module-kotlin + + + org.springframework.boot + spring-boot-starter + + + co.nilin.opex.utility.log + logging-handler + + + co.nilin.opex.utility.error + error-handler + + + co.nilin.opex.utility.interceptors + interceptors + + + co.nilin.opex.market.core + market-core + + + co.nilin.opex.market.ports.kafka.listener + market-eventlistener-kafka + + + org.springframework.boot + spring-boot-starter-actuator + + + co.nilin.opex.market.ports.postgres + market-persister-postgres + + + io.springfox + springfox-boot-starter + 3.0.0 + + + org.springframework.cloud + spring-cloud-starter-vault-config + + + co.nilin.opex.utility.preferences + preferences + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + diff --git a/market/market-app/src/main/kotlin/co/nilin/opex/market/app/MarketAppApplication.kt b/market/market-app/src/main/kotlin/co/nilin/opex/market/app/MarketAppApplication.kt new file mode 100644 index 000000000..251dfcc6b --- /dev/null +++ b/market/market-app/src/main/kotlin/co/nilin/opex/market/app/MarketAppApplication.kt @@ -0,0 +1,13 @@ +package co.nilin.opex.market.app + +import org.springframework.boot.autoconfigure.SpringBootApplication +import org.springframework.boot.runApplication +import org.springframework.context.annotation.ComponentScan + +@SpringBootApplication +@ComponentScan("co.nilin.opex") +class MarketAppApplication + +fun main(args: Array) { + runApplication(*args) +} diff --git a/market/market-app/src/main/kotlin/co/nilin/opex/market/app/config/AppConfig.kt b/market/market-app/src/main/kotlin/co/nilin/opex/market/app/config/AppConfig.kt new file mode 100644 index 000000000..f646e01e8 --- /dev/null +++ b/market/market-app/src/main/kotlin/co/nilin/opex/market/app/config/AppConfig.kt @@ -0,0 +1,30 @@ +package co.nilin.opex.market.app.config + +import co.nilin.opex.market.app.listener.MarketListenerImpl +import co.nilin.opex.market.core.spi.OrderPersister +import co.nilin.opex.market.core.spi.TradePersister +import co.nilin.opex.market.ports.kafka.listener.consumer.OrderKafkaListener +import co.nilin.opex.market.ports.kafka.listener.consumer.TradeKafkaListener +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration + +@Configuration +class AppConfig { + + @Bean + fun marketListener(richOrderPersister: OrderPersister, richTradePersister: TradePersister): MarketListenerImpl { + return MarketListenerImpl(richOrderPersister, richTradePersister) + } + + @Autowired + fun configureListeners( + orderKafkaListener: OrderKafkaListener, + tradeKafkaListener: TradeKafkaListener, + appListener: MarketListenerImpl + ) { + orderKafkaListener.addOrderListener(appListener) + tradeKafkaListener.addTradeListener(appListener) + } + +} \ No newline at end of file diff --git a/api/api-app/src/main/kotlin/co/nilin/opex/api/app/config/AppDispatchers.kt b/market/market-app/src/main/kotlin/co/nilin/opex/market/app/config/AppDispatchers.kt similarity index 62% rename from api/api-app/src/main/kotlin/co/nilin/opex/api/app/config/AppDispatchers.kt rename to market/market-app/src/main/kotlin/co/nilin/opex/market/app/config/AppDispatchers.kt index 0db73c8ba..85b157c36 100644 --- a/api/api-app/src/main/kotlin/co/nilin/opex/api/app/config/AppDispatchers.kt +++ b/market/market-app/src/main/kotlin/co/nilin/opex/market/app/config/AppDispatchers.kt @@ -1,9 +1,8 @@ -package co.nilin.opex.api.app.config +package co.nilin.opex.market.app.config import kotlinx.coroutines.asCoroutineDispatcher import java.util.concurrent.Executors object AppDispatchers { - val apiExecutor = Executors.newSingleThreadExecutor().asCoroutineDispatcher() val kafkaExecutor = Executors.newSingleThreadExecutor().asCoroutineDispatcher() } \ No newline at end of file diff --git a/market/market-app/src/main/kotlin/co/nilin/opex/market/app/config/InitializeService.kt b/market/market-app/src/main/kotlin/co/nilin/opex/market/app/config/InitializeService.kt new file mode 100644 index 000000000..c23a269ed --- /dev/null +++ b/market/market-app/src/main/kotlin/co/nilin/opex/market/app/config/InitializeService.kt @@ -0,0 +1,30 @@ +package co.nilin.opex.market.app.config + +import co.nilin.opex.market.core.inout.RateSource +import co.nilin.opex.market.ports.postgres.dao.CurrencyRateRepository +import co.nilin.opex.utility.preferences.Preferences +import kotlinx.coroutines.reactor.awaitSingleOrNull +import kotlinx.coroutines.runBlocking +import org.springframework.beans.factory.annotation.Autowired +import org.springframework.context.annotation.DependsOn +import org.springframework.stereotype.Component +import java.math.BigDecimal +import javax.annotation.PostConstruct + +@Component +@DependsOn("postgresConfig") +class InitializeService(private val rateRepository: CurrencyRateRepository) { + + @Autowired + private lateinit var preferences: Preferences + + @PostConstruct + fun init() = runBlocking { + preferences.currencies.forEach { + rateRepository.createOrUpdate(it.symbol, it.symbol, RateSource.MARKET, BigDecimal.ONE) + .awaitSingleOrNull() + rateRepository.createOrUpdate(it.symbol, it.symbol, RateSource.EXTERNAL, BigDecimal.ONE) + .awaitSingleOrNull() + } + } +} diff --git a/market/market-app/src/main/kotlin/co/nilin/opex/market/app/config/SecurityConfig.kt b/market/market-app/src/main/kotlin/co/nilin/opex/market/app/config/SecurityConfig.kt new file mode 100644 index 000000000..e1851cb6a --- /dev/null +++ b/market/market-app/src/main/kotlin/co/nilin/opex/market/app/config/SecurityConfig.kt @@ -0,0 +1,38 @@ +package co.nilin.opex.market.app.config + +import org.springframework.beans.factory.annotation.Value +import org.springframework.context.annotation.Bean +import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity +import org.springframework.security.config.web.server.ServerHttpSecurity +import org.springframework.security.oauth2.jwt.NimbusReactiveJwtDecoder +import org.springframework.security.oauth2.jwt.ReactiveJwtDecoder +import org.springframework.security.web.server.SecurityWebFilterChain +import org.springframework.web.reactive.function.client.WebClient + +@EnableWebFluxSecurity +class SecurityConfig(private val webClient: WebClient) { + + @Value("\${app.auth.cert-url}") + private lateinit var jwkUrl: String + + @Bean + fun springSecurityFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain? { + + http.csrf().disable() + .authorizeExchange() + .pathMatchers("/actuator/**").permitAll() + .anyExchange().permitAll() + .and() + .oauth2ResourceServer() + .jwt() + return http.build() + } + + @Bean + @Throws(Exception::class) + fun reactiveJwtDecoder(): ReactiveJwtDecoder? { + return NimbusReactiveJwtDecoder.withJwkSetUri(jwkUrl) + .webClient(webClient) + .build() + } +} diff --git a/market/market-app/src/main/kotlin/co/nilin/opex/market/app/config/WebClientConfig.kt b/market/market-app/src/main/kotlin/co/nilin/opex/market/app/config/WebClientConfig.kt new file mode 100644 index 000000000..b9a804522 --- /dev/null +++ b/market/market-app/src/main/kotlin/co/nilin/opex/market/app/config/WebClientConfig.kt @@ -0,0 +1,25 @@ +package co.nilin.opex.market.app.config + +import org.springframework.cloud.client.ServiceInstance +import org.springframework.cloud.client.loadbalancer.LoadBalancerProperties +import org.springframework.cloud.client.loadbalancer.reactive.ReactiveLoadBalancer +import org.springframework.cloud.client.loadbalancer.reactive.ReactorLoadBalancerExchangeFilterFunction +import org.springframework.context.annotation.Bean +import org.springframework.context.annotation.Configuration +import org.springframework.web.reactive.function.client.WebClient + +@Configuration +class WebClientConfig { + + @Bean + fun webClient(loadBalancerFactory: ReactiveLoadBalancer.Factory): WebClient { + return WebClient.builder() + .filter( + ReactorLoadBalancerExchangeFilterFunction( + loadBalancerFactory, LoadBalancerProperties(), emptyList() + ) + ) + .build() + } + +} diff --git a/market/market-app/src/main/kotlin/co/nilin/opex/market/app/controller/ChartController.kt b/market/market-app/src/main/kotlin/co/nilin/opex/market/app/controller/ChartController.kt new file mode 100644 index 000000000..88cd3f2dc --- /dev/null +++ b/market/market-app/src/main/kotlin/co/nilin/opex/market/app/controller/ChartController.kt @@ -0,0 +1,22 @@ +package co.nilin.opex.market.app.controller + +import co.nilin.opex.market.core.inout.CandleData +import co.nilin.opex.market.core.spi.MarketQueryHandler +import org.springframework.web.bind.annotation.* + +@RestController +@RequestMapping("/v1/chart") +class ChartController(private val marketQueryHandler: MarketQueryHandler) { + + @GetMapping("/{symbol}/candle") + suspend fun getCandleDataForSymbol( + @PathVariable symbol: String, + @RequestParam interval: String, + @RequestParam(required = false) since: Long?, + @RequestParam(required = false) until: Long?, + @RequestParam(required = false) limit: Int = 500 + ): List { + return marketQueryHandler.getCandleInfo(symbol, interval, since, until, limit) + } + +} \ No newline at end of file diff --git a/market/market-app/src/main/kotlin/co/nilin/opex/market/app/controller/MarketController.kt b/market/market-app/src/main/kotlin/co/nilin/opex/market/app/controller/MarketController.kt new file mode 100644 index 000000000..3bb3fe0ed --- /dev/null +++ b/market/market-app/src/main/kotlin/co/nilin/opex/market/app/controller/MarketController.kt @@ -0,0 +1,90 @@ +package co.nilin.opex.market.app.controller + +import co.nilin.opex.market.app.data.CountResponse +import co.nilin.opex.market.app.utils.asLocalDateTime +import co.nilin.opex.market.core.inout.* +import co.nilin.opex.market.core.spi.MarketQueryHandler +import co.nilin.opex.utility.error.data.OpexError +import co.nilin.opex.utility.error.data.OpexException +import org.springframework.web.bind.annotation.* +import java.time.LocalDateTime +import java.util.* + +@RestController +@RequestMapping("/v1/market") +class MarketController(private val marketQueryHandler: MarketQueryHandler) { + + @GetMapping("/ticker") + suspend fun priceChangeSince(@RequestParam since: Long): List { + return marketQueryHandler.getTradeTickerData(since.asLocalDateTime()) + } + + @GetMapping("/{symbol}/ticker") + suspend fun priceChangeForSymbolSince( + @PathVariable symbol: String, + @RequestParam since: Long + ): PriceChange { + return marketQueryHandler.getTradeTickerDateBySymbol(symbol, since.asLocalDateTime()) + ?: throw OpexException(OpexError.PriceChangeNotFound) + } + + @GetMapping("/{symbol}/order-book") + suspend fun getOrderBookForSymbol( + @PathVariable symbol: String, + @RequestParam direction: OrderDirection, + @RequestParam(required = false) limit: Int = 500 + ): List { + return if (direction == OrderDirection.BID) + marketQueryHandler.openBidOrders(symbol, limit) + else + marketQueryHandler.openAskOrders(symbol, limit) + } + + @GetMapping("/{symbol}/recent-trades") + suspend fun getRecentTradesForSymbol( + @PathVariable symbol: String, + @RequestParam(required = false) limit: Int = 500 + ): List { + return marketQueryHandler.recentTrades(symbol, limit) + } + + @GetMapping("/{symbol}/last-order") + suspend fun getLastOrderForSymbol(@PathVariable symbol: String): Order { + return marketQueryHandler.lastOrder(symbol) ?: throw OpexException(OpexError.LastOrderNotFound) + } + + @GetMapping("/prices") + suspend fun getLastPriceForSymbol(@RequestParam(required = false) symbol: String?): List { + return marketQueryHandler.lastPrice(symbol) + } + + @GetMapping("/best-prices") + suspend fun getOrderBookForSymbol(@RequestParam symbols: List): List { + return marketQueryHandler.getBestPriceForSymbols(symbols) + } + + @GetMapping("/active-users") + suspend fun getNumberOfActiveUsers(@RequestParam interval: Long): CountResponse { + val active = marketQueryHandler.numberOfActiveUsers(interval.asLocalDateTime()) + return CountResponse(active) + } + + @GetMapping("/orders-count") + suspend fun getNumberOfOrders( + @RequestParam interval: Long, + @RequestParam(required = false) symbol: String? + ): CountResponse { + val count = marketQueryHandler.numberOfOrders(interval.asLocalDateTime(), symbol) + return CountResponse(count) + } + + @GetMapping("/trades-count") + suspend fun getNumberOfTrades( + @RequestParam interval: Long, + @RequestParam(required = false) symbol: String? + ): CountResponse { + val count = marketQueryHandler.numberOfTrades(interval.asLocalDateTime(), symbol) + return CountResponse(count) + } + +} \ No newline at end of file diff --git a/market/market-app/src/main/kotlin/co/nilin/opex/market/app/controller/MarketStatsController.kt b/market/market-app/src/main/kotlin/co/nilin/opex/market/app/controller/MarketStatsController.kt new file mode 100644 index 000000000..abc2e040a --- /dev/null +++ b/market/market-app/src/main/kotlin/co/nilin/opex/market/app/controller/MarketStatsController.kt @@ -0,0 +1,37 @@ +package co.nilin.opex.market.app.controller + +import co.nilin.opex.market.app.utils.asLocalDateTime +import co.nilin.opex.market.core.inout.PriceStat +import co.nilin.opex.market.core.inout.TradeVolumeStat +import co.nilin.opex.market.core.spi.MarketQueryHandler +import org.springframework.web.bind.annotation.GetMapping +import org.springframework.web.bind.annotation.RequestMapping +import org.springframework.web.bind.annotation.RequestParam +import org.springframework.web.bind.annotation.RestController +import java.util.* + +@RestController +@RequestMapping("/v1/stats") +class MarketStatsController(private val marketQueryHandler: MarketQueryHandler) { + + @GetMapping("/price/most-increased") + suspend fun getMostIncreasedPrices(@RequestParam interval: Long, @RequestParam limit: Int): List { + return marketQueryHandler.mostIncreasePrice(Date(interval).asLocalDateTime(), limit) + } + + @GetMapping("/price/most-decreased") + suspend fun getMostDecreasedPrices(@RequestParam interval: Long, @RequestParam limit: Int): List { + return marketQueryHandler.mostDecreasePrice(Date(interval).asLocalDateTime(), limit) + } + + @GetMapping("/volume/highest") + suspend fun getHighestVolume(@RequestParam interval: Long): TradeVolumeStat? { + return marketQueryHandler.mostVolume(interval.asLocalDateTime()) + } + + @GetMapping("/most-trades") + suspend fun getMostTrades(@RequestParam interval: Long): TradeVolumeStat? { + return marketQueryHandler.mostTrades(interval.asLocalDateTime()) + } + +} \ No newline at end of file diff --git a/market/market-app/src/main/kotlin/co/nilin/opex/market/app/controller/RateController.kt b/market/market-app/src/main/kotlin/co/nilin/opex/market/app/controller/RateController.kt new file mode 100644 index 000000000..3a5c4bf4f --- /dev/null +++ b/market/market-app/src/main/kotlin/co/nilin/opex/market/app/controller/RateController.kt @@ -0,0 +1,26 @@ +package co.nilin.opex.market.app.controller + +import co.nilin.opex.market.core.inout.CurrencyRate +import co.nilin.opex.market.core.inout.RateSource +import co.nilin.opex.market.core.spi.MarketRateService +import org.springframework.web.bind.annotation.* + +@RestController +@RequestMapping("/v1/rate") +class RateController(private val marketRateService: MarketRateService) { + + @GetMapping("/{source}") + suspend fun getRates(@PathVariable source: RateSource, @RequestParam quote: String): List { + return marketRateService.currencyRate(quote.uppercase(), source) + } + + @GetMapping("/{base}/{source}") + suspend fun getRate( + @PathVariable source: RateSource, + @PathVariable base: String, + @RequestParam quote: String + ): CurrencyRate { + return marketRateService.currencyRate(base.uppercase(), quote.uppercase(), source) + } + +} \ No newline at end of file diff --git a/market/market-app/src/main/kotlin/co/nilin/opex/market/app/controller/UserDataController.kt b/market/market-app/src/main/kotlin/co/nilin/opex/market/app/controller/UserDataController.kt new file mode 100644 index 000000000..276152351 --- /dev/null +++ b/market/market-app/src/main/kotlin/co/nilin/opex/market/app/controller/UserDataController.kt @@ -0,0 +1,38 @@ +package co.nilin.opex.market.app.controller + +import co.nilin.opex.market.core.inout.* +import co.nilin.opex.market.core.spi.UserQueryHandler +import co.nilin.opex.utility.error.data.OpexError +import co.nilin.opex.utility.error.data.OpexException +import org.springframework.web.bind.annotation.* + +@RestController +@RequestMapping("/v1/user") +class UserDataController(private val userQueryHandler: UserQueryHandler) { + + @GetMapping("/{uuid}/order/{ouid}") + suspend fun getOrder(@PathVariable uuid: String, @PathVariable ouid: String): Order { + return userQueryHandler.getOrder(uuid, ouid) ?: throw OpexException(OpexError.NotFound) + } + + @PostMapping("/{uuid}/order/query") + suspend fun queryUserOrder(@PathVariable uuid: String, request: QueryOrderRequest): Order { + return userQueryHandler.queryOrder(uuid, request) ?: throw OpexException(OpexError.NotFound) + } + + @GetMapping("/{uuid}/orders/{symbol}/open") + suspend fun getUserOpenOrders(@PathVariable uuid: String, @PathVariable symbol: String): List { + return userQueryHandler.openOrders(uuid, symbol) + } + + @PostMapping("/{uuid}/orders") + suspend fun getUserOrders(@PathVariable uuid: String, request: AllOrderRequest): List { + return userQueryHandler.allOrders(uuid, request) + } + + @PostMapping("/{uuid}/trades") + suspend fun getUserTrades(@PathVariable uuid: String, request: TradeRequest): List { + return userQueryHandler.allTrades(uuid, request) + } + +} \ No newline at end of file diff --git a/market/market-app/src/main/kotlin/co/nilin/opex/market/app/data/CountResponse.kt b/market/market-app/src/main/kotlin/co/nilin/opex/market/app/data/CountResponse.kt new file mode 100644 index 000000000..2648f6a23 --- /dev/null +++ b/market/market-app/src/main/kotlin/co/nilin/opex/market/app/data/CountResponse.kt @@ -0,0 +1,3 @@ +package co.nilin.opex.market.app.data + +data class CountResponse(val value: Long) \ No newline at end of file diff --git a/api/api-app/src/main/kotlin/co/nilin/opex/api/app/listener/ApiListenerImpl.kt b/market/market-app/src/main/kotlin/co/nilin/opex/market/app/listener/MarketListenerImpl.kt similarity index 59% rename from api/api-app/src/main/kotlin/co/nilin/opex/api/app/listener/ApiListenerImpl.kt rename to market/market-app/src/main/kotlin/co/nilin/opex/market/app/listener/MarketListenerImpl.kt index 8c864945c..764e2c7cf 100644 --- a/api/api-app/src/main/kotlin/co/nilin/opex/api/app/listener/ApiListenerImpl.kt +++ b/market/market-app/src/main/kotlin/co/nilin/opex/market/app/listener/MarketListenerImpl.kt @@ -1,17 +1,17 @@ -package co.nilin.opex.api.app.listener +package co.nilin.opex.market.app.listener -import co.nilin.opex.api.app.config.AppDispatchers -import co.nilin.opex.api.core.event.RichOrder -import co.nilin.opex.api.core.event.RichOrderEvent -import co.nilin.opex.api.core.event.RichOrderUpdate -import co.nilin.opex.api.core.event.RichTrade -import co.nilin.opex.api.core.spi.OrderPersister -import co.nilin.opex.api.core.spi.TradePersister -import co.nilin.opex.api.ports.kafka.listener.spi.RichOrderListener -import co.nilin.opex.api.ports.kafka.listener.spi.RichTradeListener +import co.nilin.opex.market.app.config.AppDispatchers +import co.nilin.opex.market.core.event.RichOrder +import co.nilin.opex.market.core.event.RichOrderEvent +import co.nilin.opex.market.core.event.RichOrderUpdate +import co.nilin.opex.market.core.event.RichTrade +import co.nilin.opex.market.core.spi.OrderPersister +import co.nilin.opex.market.core.spi.TradePersister +import co.nilin.opex.market.ports.kafka.listener.spi.RichOrderListener +import co.nilin.opex.market.ports.kafka.listener.spi.RichTradeListener import kotlinx.coroutines.runBlocking -class ApiListenerImpl( +class MarketListenerImpl( private val richOrderPersister: OrderPersister, private val richTradePersister: TradePersister ) : RichTradeListener, RichOrderListener { diff --git a/market/market-app/src/main/kotlin/co/nilin/opex/market/app/utils/Extensions.kt b/market/market-app/src/main/kotlin/co/nilin/opex/market/app/utils/Extensions.kt new file mode 100644 index 000000000..1baa07442 --- /dev/null +++ b/market/market-app/src/main/kotlin/co/nilin/opex/market/app/utils/Extensions.kt @@ -0,0 +1,17 @@ +package co.nilin.opex.market.app.utils + +import java.time.LocalDateTime +import java.time.ZoneId +import java.util.* + +fun LocalDateTime.asDate(): Date { + return Date.from(atZone(ZoneId.systemDefault()).toInstant()) +} + +fun Date.asLocalDateTime(): LocalDateTime { + return LocalDateTime.ofInstant(toInstant(), ZoneId.systemDefault()) +} + +fun Long.asLocalDateTime(): LocalDateTime { + return LocalDateTime.ofInstant(Date(this).toInstant(), ZoneId.systemDefault()) +} \ No newline at end of file diff --git a/api/api-app/src/main/kotlin/co/nilin/opex/util/vault/VaultUserIdMechanism.kt b/market/market-app/src/main/kotlin/co/nilin/opex/market/app/utils/VaultUserIdMechanism.kt similarity index 64% rename from api/api-app/src/main/kotlin/co/nilin/opex/util/vault/VaultUserIdMechanism.kt rename to market/market-app/src/main/kotlin/co/nilin/opex/market/app/utils/VaultUserIdMechanism.kt index e9efaf92a..224bdd275 100644 --- a/api/api-app/src/main/kotlin/co/nilin/opex/util/vault/VaultUserIdMechanism.kt +++ b/market/market-app/src/main/kotlin/co/nilin/opex/market/app/utils/VaultUserIdMechanism.kt @@ -1,8 +1,8 @@ -package co.nilin.opex.util.vault +package co.nilin.opex.market.app.utils import org.springframework.vault.authentication.AppIdUserIdMechanism -class VaultUserIdMechanism() : AppIdUserIdMechanism { +class VaultUserIdMechanism : AppIdUserIdMechanism { override fun createUserId(): String { return System.getenv("BACKEND_USER") } diff --git a/market/market-app/src/main/resources/application.yml b/market/market-app/src/main/resources/application.yml new file mode 100644 index 000000000..776147a09 --- /dev/null +++ b/market/market-app/src/main/resources/application.yml @@ -0,0 +1,49 @@ +server.port: 8080 +logging: + level: + co.nilin: DEBUG + reactor.netty.http.client: DEBUG +spring: + application: + name: opex-market + main: + allow-bean-definition-overriding: true + allow-circular-references: true + kafka: + bootstrap-servers: ${KAFKA_IP_PORT:localhost:9092} + consumer: + group-id: market + r2dbc: + url: r2dbc:postgresql://${DB_IP_PORT:localhost}/opex + username: ${dbusername:opex} + password: ${dbpassword:hiopex} + initialization-mode: always + cloud: + bootstrap: + enabled: true + vault: + host: ${VAULT_HOST} + port: 8200 + scheme: http + authentication: APPID + app-id: + user-id: co.nilin.opex.market.app.utils.VaultUserIdMechanism + fail-fast: true + kv: + enabled: true + backend: secret + profile-separator: '/' + application-name: ${spring.application.name} + consul: + host: ${CONSUL_HOST:localhost} + port: 8500 + discovery: + instance-id: ${spring.application.name}:${server.port} + healthCheckInterval: 20s + prefer-ip-address: true + config: + import: vault://secret/${spring.application.name} +app: + auth: + cert-url: lb://opex-auth/auth/realms/opex/protocol/openid-connect/certs +swagger.authUrl: ${SWAGGER_AUTH_URL:https://api.opex.dev/auth}/realms/opex/protocol/openid-connect/token diff --git a/market/market-core/pom.xml b/market/market-core/pom.xml new file mode 100644 index 000000000..146107297 --- /dev/null +++ b/market/market-core/pom.xml @@ -0,0 +1,44 @@ + + + 4.0.0 + + + co.nilin.opex.market + market + 1.0.0-beta.3 + + + co.nilin.opex.market.core + market-core + market-core + Market logic of Opex + + + + org.jetbrains.kotlin + kotlin-reflect + + + org.springframework.boot + spring-boot-starter + + + io.projectreactor.kotlin + reactor-kotlin-extensions + + + org.jetbrains.kotlinx + kotlinx-coroutines-reactor + + + org.jetbrains.kotlinx + kotlinx-coroutines-core + + + org.springframework + spring-tx + provided + + + diff --git a/api/api-core/src/main/kotlin/co/nilin/opex/api/core/event/RichOrder.kt b/market/market-core/src/main/kotlin/co/nilin/opex/market/core/event/RichOrder.kt similarity index 75% rename from api/api-core/src/main/kotlin/co/nilin/opex/api/core/event/RichOrder.kt rename to market/market-core/src/main/kotlin/co/nilin/opex/market/core/event/RichOrder.kt index fc8ff44c6..b82119dfc 100644 --- a/api/api-core/src/main/kotlin/co/nilin/opex/api/core/event/RichOrder.kt +++ b/market/market-core/src/main/kotlin/co/nilin/opex/market/core/event/RichOrder.kt @@ -1,8 +1,8 @@ -package co.nilin.opex.api.core.event +package co.nilin.opex.market.core.event -import co.nilin.opex.api.core.inout.MatchConstraint -import co.nilin.opex.api.core.inout.MatchingOrderType -import co.nilin.opex.api.core.inout.OrderDirection +import co.nilin.opex.market.core.inout.MatchConstraint +import co.nilin.opex.market.core.inout.MatchingOrderType +import co.nilin.opex.market.core.inout.OrderDirection import java.math.BigDecimal data class RichOrder( diff --git a/market/market-core/src/main/kotlin/co/nilin/opex/market/core/event/RichOrderEvent.kt b/market/market-core/src/main/kotlin/co/nilin/opex/market/core/event/RichOrderEvent.kt new file mode 100644 index 000000000..10d94d0a7 --- /dev/null +++ b/market/market-core/src/main/kotlin/co/nilin/opex/market/core/event/RichOrderEvent.kt @@ -0,0 +1,3 @@ +package co.nilin.opex.market.core.event + +interface RichOrderEvent \ No newline at end of file diff --git a/api/api-core/src/main/kotlin/co/nilin/opex/api/core/event/RichOrderUpdate.kt b/market/market-core/src/main/kotlin/co/nilin/opex/market/core/event/RichOrderUpdate.kt similarity index 82% rename from api/api-core/src/main/kotlin/co/nilin/opex/api/core/event/RichOrderUpdate.kt rename to market/market-core/src/main/kotlin/co/nilin/opex/market/core/event/RichOrderUpdate.kt index b00821ed7..df048861e 100644 --- a/api/api-core/src/main/kotlin/co/nilin/opex/api/core/event/RichOrderUpdate.kt +++ b/market/market-core/src/main/kotlin/co/nilin/opex/market/core/event/RichOrderUpdate.kt @@ -1,6 +1,6 @@ -package co.nilin.opex.api.core.event +package co.nilin.opex.market.core.event -import co.nilin.opex.api.core.inout.OrderStatus +import co.nilin.opex.market.core.inout.OrderStatus import java.math.BigDecimal data class RichOrderUpdate( diff --git a/api/api-core/src/main/kotlin/co/nilin/opex/api/core/event/RichTrade.kt b/market/market-core/src/main/kotlin/co/nilin/opex/market/core/event/RichTrade.kt similarity index 87% rename from api/api-core/src/main/kotlin/co/nilin/opex/api/core/event/RichTrade.kt rename to market/market-core/src/main/kotlin/co/nilin/opex/market/core/event/RichTrade.kt index 819b887f9..22476b548 100644 --- a/api/api-core/src/main/kotlin/co/nilin/opex/api/core/event/RichTrade.kt +++ b/market/market-core/src/main/kotlin/co/nilin/opex/market/core/event/RichTrade.kt @@ -1,6 +1,6 @@ -package co.nilin.opex.api.core.event +package co.nilin.opex.market.core.event -import co.nilin.opex.api.core.inout.OrderDirection +import co.nilin.opex.market.core.inout.OrderDirection import java.math.BigDecimal import java.time.LocalDateTime @@ -27,6 +27,7 @@ class RichTrade( val makerRemainedQuantity: BigDecimal, val makerCommision: BigDecimal, val makerCommisionAsset: String, + val matchedPrice: BigDecimal, val matchedQuantity: BigDecimal, val tradeDateTime: LocalDateTime ) \ No newline at end of file diff --git a/api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/AggregatedOrderPriceModel.kt b/market/market-core/src/main/kotlin/co/nilin/opex/market/core/inout/AggregatedOrderPriceModel.kt similarity index 76% rename from api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/AggregatedOrderPriceModel.kt rename to market/market-core/src/main/kotlin/co/nilin/opex/market/core/inout/AggregatedOrderPriceModel.kt index 38f07c062..67c08fed5 100644 --- a/api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/AggregatedOrderPriceModel.kt +++ b/market/market-core/src/main/kotlin/co/nilin/opex/market/core/inout/AggregatedOrderPriceModel.kt @@ -1,4 +1,4 @@ -package co.nilin.opex.api.core.inout +package co.nilin.opex.market.core.inout import java.math.BigDecimal diff --git a/api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/AllOrderRequest.kt b/market/market-core/src/main/kotlin/co/nilin/opex/market/core/inout/AllOrderRequest.kt similarity index 81% rename from api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/AllOrderRequest.kt rename to market/market-core/src/main/kotlin/co/nilin/opex/market/core/inout/AllOrderRequest.kt index 6eb74b1f1..cb40117b9 100644 --- a/api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/AllOrderRequest.kt +++ b/market/market-core/src/main/kotlin/co/nilin/opex/market/core/inout/AllOrderRequest.kt @@ -1,4 +1,4 @@ -package co.nilin.opex.api.core.inout +package co.nilin.opex.market.core.inout import java.util.* diff --git a/market/market-core/src/main/kotlin/co/nilin/opex/market/core/inout/BestPrice.kt b/market/market-core/src/main/kotlin/co/nilin/opex/market/core/inout/BestPrice.kt new file mode 100644 index 000000000..af901b5fe --- /dev/null +++ b/market/market-core/src/main/kotlin/co/nilin/opex/market/core/inout/BestPrice.kt @@ -0,0 +1,9 @@ +package co.nilin.opex.market.core.inout + +import java.math.BigDecimal + +data class BestPrice( + val symbol: String, + val bidPrice: BigDecimal?, + val askPrice: BigDecimal?, +) \ No newline at end of file diff --git a/market/market-core/src/main/kotlin/co/nilin/opex/market/core/inout/CandleData.kt b/market/market-core/src/main/kotlin/co/nilin/opex/market/core/inout/CandleData.kt new file mode 100644 index 000000000..808ef41e0 --- /dev/null +++ b/market/market-core/src/main/kotlin/co/nilin/opex/market/core/inout/CandleData.kt @@ -0,0 +1,18 @@ +package co.nilin.opex.market.core.inout + +import java.math.BigDecimal +import java.time.LocalDateTime + +data class CandleData( + val openTime: LocalDateTime, + val closeTime: LocalDateTime, + val open: BigDecimal, + val close: BigDecimal, + val high: BigDecimal, + val low: BigDecimal, + val volume: BigDecimal, + val quoteAssetVolume: BigDecimal, + val trades: Int, + val takerBuyBaseAssetVolume: BigDecimal, + val takerBuyQuoteAssetVolume: BigDecimal, +) diff --git a/market/market-core/src/main/kotlin/co/nilin/opex/market/core/inout/CurrencyRate.kt b/market/market-core/src/main/kotlin/co/nilin/opex/market/core/inout/CurrencyRate.kt new file mode 100644 index 000000000..c3eba529b --- /dev/null +++ b/market/market-core/src/main/kotlin/co/nilin/opex/market/core/inout/CurrencyRate.kt @@ -0,0 +1,10 @@ +package co.nilin.opex.market.core.inout + +import java.math.BigDecimal + +data class CurrencyRate( + val base: String, + val quote: String, + val source: RateSource, + val rate: BigDecimal +) \ No newline at end of file diff --git a/market/market-core/src/main/kotlin/co/nilin/opex/market/core/inout/MarketTrade.kt b/market/market-core/src/main/kotlin/co/nilin/opex/market/core/inout/MarketTrade.kt new file mode 100644 index 000000000..799f45365 --- /dev/null +++ b/market/market-core/src/main/kotlin/co/nilin/opex/market/core/inout/MarketTrade.kt @@ -0,0 +1,17 @@ +package co.nilin.opex.market.core.inout + +import java.math.BigDecimal +import java.util.* + +data class MarketTrade( + val symbol: String, + val baseAsset: String, + val quoteAsset: String, + val id: Long, + val price: BigDecimal, + val quantity: BigDecimal, + val quoteQuantity: BigDecimal, + val time: Date, + val isBestMatch: Boolean, + val isMakerBuyer: Boolean +) \ No newline at end of file diff --git a/market/market-core/src/main/kotlin/co/nilin/opex/market/core/inout/Order.kt b/market/market-core/src/main/kotlin/co/nilin/opex/market/core/inout/Order.kt new file mode 100644 index 000000000..95bbc4b40 --- /dev/null +++ b/market/market-core/src/main/kotlin/co/nilin/opex/market/core/inout/Order.kt @@ -0,0 +1,29 @@ +package co.nilin.opex.market.core.inout + +import java.math.BigDecimal +import java.time.LocalDateTime + +data class Order( + var id: Long, + val ouid: String, + val uuid: String, + val clientOrderId: String?, + val symbol: String, + val orderId: Long?, + val makerFee: BigDecimal, + val takerFee: BigDecimal, + val leftSideFraction: BigDecimal, + val rightSideFraction: BigDecimal, + val userLevel: String, + val direction: OrderDirection, + val constraint: MatchConstraint, + val type: MatchingOrderType, + val price: BigDecimal, + val quantity: BigDecimal, + val quoteQuantity: BigDecimal, + val executedQuantity: BigDecimal, + val accumulativeQuoteQty: BigDecimal, + val status: OrderStatus, + val createDate: LocalDateTime, + val updateDate: LocalDateTime, +) \ No newline at end of file diff --git a/market/market-core/src/main/kotlin/co/nilin/opex/market/core/inout/OrderBook.kt b/market/market-core/src/main/kotlin/co/nilin/opex/market/core/inout/OrderBook.kt new file mode 100644 index 000000000..2d9009bc4 --- /dev/null +++ b/market/market-core/src/main/kotlin/co/nilin/opex/market/core/inout/OrderBook.kt @@ -0,0 +1,8 @@ +package co.nilin.opex.market.core.inout + +import java.math.BigDecimal + +data class OrderBook( + val price: BigDecimal?, + val quantity: BigDecimal? +) \ No newline at end of file diff --git a/market/market-core/src/main/kotlin/co/nilin/opex/market/core/inout/OrderEnums.kt b/market/market-core/src/main/kotlin/co/nilin/opex/market/core/inout/OrderEnums.kt new file mode 100644 index 000000000..7a1ec7594 --- /dev/null +++ b/market/market-core/src/main/kotlin/co/nilin/opex/market/core/inout/OrderEnums.kt @@ -0,0 +1,64 @@ +package co.nilin.opex.market.core.inout + +enum class TimeInForce { + GTC, //Good Til Canceled, An order will be on the book unless the order is canceled. + IOC, //Immediate Or Cancel, An order will try to fill the order as much as it can before the order expires. + FOK, //Fill or Kill, An order will expire if the full order cannot be filled upon execution. +} + +enum class OrderStatus(val code: Int, val orderOfAppearance: Int) { + + REQUESTED(0, 0), + NEW(1, 1), //The order has been accepted by the engine. + PARTIALLY_FILLED(4, 2), //A part of the order has been filled. + FILLED(5, 3), //The order has been completed. + CANCELED(2, 3), //The order has been canceled by the user. + REJECTED(3, 3), //The order was not accepted by the engine and not processed. + EXPIRED( + 6, + 3 + ); //The order was canceled according to the order type's rules (e.g. LIMIT FOK orders with no fill, LIMIT IOC or MARKET orders that partially fill) or by the exchange, (e.g. orders canceled during liquidation, orders canceled during maintenance) + + fun comesBefore(status: OrderStatus?): Boolean { + if (status == null) + return false + return orderOfAppearance < status.orderOfAppearance + } + + fun comesAfter(status: OrderStatus?): Boolean { + if (status == null) + return false + return orderOfAppearance > status.orderOfAppearance + } + + companion object { + fun fromCode(code: Int?): OrderStatus? { + if (code == null) + return null + return values().find { it.code == code } + } + } +} + +enum class OrderType { + LIMIT, // timeInForce, quantity, price + MARKET, // quantity or quoteOrderQty + STOP_LOSS, // quantity, stopPrice + STOP_LOSS_LIMIT, // timeInForce, quantity, price, stopPrice + TAKE_PROFIT, // quantity, stopPrice + TAKE_PROFIT_LIMIT, // timeInForce, quantity, price, stopPrice + LIMIT_MAKER; // quantity, price + + companion object { + fun activeTypes() = listOf(LIMIT, MARKET) + } +} + +enum class OrderSide { + BUY, + SELL +} + +enum class OrderResponseType { + ACK, RESULT, FULL +} \ No newline at end of file diff --git a/market/market-core/src/main/kotlin/co/nilin/opex/market/core/inout/OrderMetaData.kt b/market/market-core/src/main/kotlin/co/nilin/opex/market/core/inout/OrderMetaData.kt new file mode 100644 index 000000000..23bbec56c --- /dev/null +++ b/market/market-core/src/main/kotlin/co/nilin/opex/market/core/inout/OrderMetaData.kt @@ -0,0 +1,17 @@ +package co.nilin.opex.market.core.inout + +enum class OrderDirection { + ASK, BID +} + +enum class MatchConstraint { + GTC, + IOC, + IOC_BUDGET, + FOK, + FOK_BUDGET +} + +enum class MatchingOrderType { + LIMIT_ORDER, MARKET_ORDER +} \ No newline at end of file diff --git a/api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/PriceChangeResponse.kt b/market/market-core/src/main/kotlin/co/nilin/opex/market/core/inout/PriceChange.kt similarity index 91% rename from api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/PriceChangeResponse.kt rename to market/market-core/src/main/kotlin/co/nilin/opex/market/core/inout/PriceChange.kt index 545c758fc..d58e25745 100644 --- a/api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/PriceChangeResponse.kt +++ b/market/market-core/src/main/kotlin/co/nilin/opex/market/core/inout/PriceChange.kt @@ -1,8 +1,8 @@ -package co.nilin.opex.api.core.inout +package co.nilin.opex.market.core.inout import java.math.BigDecimal -data class PriceChangeResponse( +data class PriceChange( val symbol: String, val priceChange: BigDecimal = BigDecimal.ZERO, val priceChangePercent: BigDecimal = BigDecimal.ZERO, diff --git a/market/market-core/src/main/kotlin/co/nilin/opex/market/core/inout/PriceStat.kt b/market/market-core/src/main/kotlin/co/nilin/opex/market/core/inout/PriceStat.kt new file mode 100644 index 000000000..22664ec47 --- /dev/null +++ b/market/market-core/src/main/kotlin/co/nilin/opex/market/core/inout/PriceStat.kt @@ -0,0 +1,9 @@ +package co.nilin.opex.market.core.inout + +import java.math.BigDecimal + +data class PriceStat( + val symbol: String, + val lastPrice: BigDecimal, + val priceChangePercent: Double +) \ No newline at end of file diff --git a/market/market-core/src/main/kotlin/co/nilin/opex/market/core/inout/PriceTicker.kt b/market/market-core/src/main/kotlin/co/nilin/opex/market/core/inout/PriceTicker.kt new file mode 100644 index 000000000..726f2e749 --- /dev/null +++ b/market/market-core/src/main/kotlin/co/nilin/opex/market/core/inout/PriceTicker.kt @@ -0,0 +1,6 @@ +package co.nilin.opex.market.core.inout + +data class PriceTicker( + val symbol: String?, + val price: String? +) \ No newline at end of file diff --git a/api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/QueryOrderRequest.kt b/market/market-core/src/main/kotlin/co/nilin/opex/market/core/inout/QueryOrderRequest.kt similarity index 74% rename from api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/QueryOrderRequest.kt rename to market/market-core/src/main/kotlin/co/nilin/opex/market/core/inout/QueryOrderRequest.kt index 5b0890e65..493d2b543 100644 --- a/api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/QueryOrderRequest.kt +++ b/market/market-core/src/main/kotlin/co/nilin/opex/market/core/inout/QueryOrderRequest.kt @@ -1,4 +1,4 @@ -package co.nilin.opex.api.core.inout +package co.nilin.opex.market.core.inout data class QueryOrderRequest( val symbol: String, diff --git a/market/market-core/src/main/kotlin/co/nilin/opex/market/core/inout/RateSource.kt b/market/market-core/src/main/kotlin/co/nilin/opex/market/core/inout/RateSource.kt new file mode 100644 index 000000000..7ac0daa53 --- /dev/null +++ b/market/market-core/src/main/kotlin/co/nilin/opex/market/core/inout/RateSource.kt @@ -0,0 +1,5 @@ +package co.nilin.opex.market.core.inout + +enum class RateSource { + MARKET, EXTERNAL +} \ No newline at end of file diff --git a/market/market-core/src/main/kotlin/co/nilin/opex/market/core/inout/Trade.kt b/market/market-core/src/main/kotlin/co/nilin/opex/market/core/inout/Trade.kt new file mode 100644 index 000000000..603284947 --- /dev/null +++ b/market/market-core/src/main/kotlin/co/nilin/opex/market/core/inout/Trade.kt @@ -0,0 +1,20 @@ +package co.nilin.opex.market.core.inout + +import java.math.BigDecimal +import java.util.* + +data class Trade( + val symbol: String, + val id: Long, + val orderId: Long, + val price: BigDecimal, + val quantity: BigDecimal, + val quoteQuantity: BigDecimal, + val commission: BigDecimal, + val commissionAsset: String, + val time: Date, + val isBuyer: Boolean, + val isMaker: Boolean, + val isBestMatch: Boolean, + val isMakerBuyer: Boolean +) \ No newline at end of file diff --git a/api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/TradeRequest.kt b/market/market-core/src/main/kotlin/co/nilin/opex/market/core/inout/TradeRequest.kt similarity index 82% rename from api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/TradeRequest.kt rename to market/market-core/src/main/kotlin/co/nilin/opex/market/core/inout/TradeRequest.kt index 511949d47..803f0d1f7 100644 --- a/api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/TradeRequest.kt +++ b/market/market-core/src/main/kotlin/co/nilin/opex/market/core/inout/TradeRequest.kt @@ -1,4 +1,4 @@ -package co.nilin.opex.api.core.inout +package co.nilin.opex.market.core.inout import java.util.* diff --git a/market/market-core/src/main/kotlin/co/nilin/opex/market/core/inout/TradeVolumeStat.kt b/market/market-core/src/main/kotlin/co/nilin/opex/market/core/inout/TradeVolumeStat.kt new file mode 100644 index 000000000..0e4c5e30b --- /dev/null +++ b/market/market-core/src/main/kotlin/co/nilin/opex/market/core/inout/TradeVolumeStat.kt @@ -0,0 +1,10 @@ +package co.nilin.opex.market.core.inout + +import java.math.BigDecimal + +data class TradeVolumeStat( + val symbol: String, + val volume: BigDecimal, + val tradeCount: BigDecimal, + val change: Double +) \ No newline at end of file diff --git a/market/market-core/src/main/kotlin/co/nilin/opex/market/core/spi/MarketQueryHandler.kt b/market/market-core/src/main/kotlin/co/nilin/opex/market/core/spi/MarketQueryHandler.kt new file mode 100644 index 000000000..bfb391ca1 --- /dev/null +++ b/market/market-core/src/main/kotlin/co/nilin/opex/market/core/spi/MarketQueryHandler.kt @@ -0,0 +1,46 @@ +package co.nilin.opex.market.core.spi + +import co.nilin.opex.market.core.inout.* +import java.time.LocalDateTime + +interface MarketQueryHandler { + + suspend fun getTradeTickerData(startFrom: LocalDateTime): List + + suspend fun getTradeTickerDateBySymbol(symbol: String, startFrom: LocalDateTime): PriceChange? + + suspend fun openBidOrders(symbol: String, limit: Int): List + + suspend fun openAskOrders(symbol: String, limit: Int): List + + suspend fun lastOrder(symbol: String): Order? + + suspend fun recentTrades(symbol: String, limit: Int): List + + suspend fun lastPrice(symbol: String?): List + + suspend fun getBestPriceForSymbols(symbols: List): List + + suspend fun getCandleInfo( + symbol: String, + interval: String, + startTime: Long?, + endTime: Long?, + limit: Int + ): List + + suspend fun numberOfActiveUsers(interval: LocalDateTime): Long + + suspend fun numberOfTrades(interval: LocalDateTime, pair: String? = null): Long + + suspend fun numberOfOrders(interval: LocalDateTime, pair: String? = null): Long + + suspend fun mostIncreasePrice(interval: LocalDateTime, limit: Int): List + + suspend fun mostDecreasePrice(interval: LocalDateTime, limit: Int): List + + suspend fun mostVolume(interval: LocalDateTime): TradeVolumeStat? + + suspend fun mostTrades(interval: LocalDateTime): TradeVolumeStat? + +} \ No newline at end of file diff --git a/market/market-core/src/main/kotlin/co/nilin/opex/market/core/spi/MarketRateService.kt b/market/market-core/src/main/kotlin/co/nilin/opex/market/core/spi/MarketRateService.kt new file mode 100644 index 000000000..230b7cc4b --- /dev/null +++ b/market/market-core/src/main/kotlin/co/nilin/opex/market/core/spi/MarketRateService.kt @@ -0,0 +1,12 @@ +package co.nilin.opex.market.core.spi + +import co.nilin.opex.market.core.inout.CurrencyRate +import co.nilin.opex.market.core.inout.RateSource + +interface MarketRateService { + + suspend fun currencyRate(quote: String, source: RateSource): List + + suspend fun currencyRate(base: String, quote: String, source: RateSource): CurrencyRate + +} \ No newline at end of file diff --git a/market/market-core/src/main/kotlin/co/nilin/opex/market/core/spi/OrderPersister.kt b/market/market-core/src/main/kotlin/co/nilin/opex/market/core/spi/OrderPersister.kt new file mode 100644 index 000000000..eb46e463e --- /dev/null +++ b/market/market-core/src/main/kotlin/co/nilin/opex/market/core/spi/OrderPersister.kt @@ -0,0 +1,14 @@ +package co.nilin.opex.market.core.spi + +import co.nilin.opex.market.core.event.RichOrder +import co.nilin.opex.market.core.event.RichOrderUpdate +import co.nilin.opex.market.core.inout.Order + +interface OrderPersister { + + suspend fun save(order: RichOrder) + + suspend fun update(orderUpdate: RichOrderUpdate) + + suspend fun load(ouid: String): Order? +} \ No newline at end of file diff --git a/market/market-core/src/main/kotlin/co/nilin/opex/market/core/spi/TradePersister.kt b/market/market-core/src/main/kotlin/co/nilin/opex/market/core/spi/TradePersister.kt new file mode 100644 index 000000000..37b041d93 --- /dev/null +++ b/market/market-core/src/main/kotlin/co/nilin/opex/market/core/spi/TradePersister.kt @@ -0,0 +1,7 @@ +package co.nilin.opex.market.core.spi + +import co.nilin.opex.market.core.event.RichTrade + +interface TradePersister { + suspend fun save(trade: RichTrade) +} \ No newline at end of file diff --git a/market/market-core/src/main/kotlin/co/nilin/opex/market/core/spi/UserQueryHandler.kt b/market/market-core/src/main/kotlin/co/nilin/opex/market/core/spi/UserQueryHandler.kt new file mode 100644 index 000000000..4d83b2576 --- /dev/null +++ b/market/market-core/src/main/kotlin/co/nilin/opex/market/core/spi/UserQueryHandler.kt @@ -0,0 +1,16 @@ +package co.nilin.opex.market.core.spi + +import co.nilin.opex.market.core.inout.* + +interface UserQueryHandler { + + suspend fun getOrder(uuid: String, ouid: String): Order? + + suspend fun queryOrder(uuid: String, request: QueryOrderRequest): Order? + + suspend fun openOrders(uuid: String, symbol: String?): List + + suspend fun allOrders(uuid: String, allOrderRequest: AllOrderRequest): List + + suspend fun allTrades(uuid: String, request: TradeRequest): List +} \ No newline at end of file diff --git a/api/api-ports/api-eventlistener-kafka/pom.xml b/market/market-ports/market-eventlistener-kafka/pom.xml similarity index 80% rename from api/api-ports/api-eventlistener-kafka/pom.xml rename to market/market-ports/market-eventlistener-kafka/pom.xml index 60bc708ed..27abe4901 100644 --- a/api/api-ports/api-eventlistener-kafka/pom.xml +++ b/market/market-ports/market-eventlistener-kafka/pom.xml @@ -4,16 +4,16 @@ 4.0.0 - co.nilin.opex.api - api + co.nilin.opex.market + market 1.0.0-beta.3 ../../pom.xml - co.nilin.opex.api.ports.kafka.listener - api-eventlistener-kafka - api-eventlistener-kafka - Api kafka listener of Opex + co.nilin.opex.market.ports.kafka.listener + market-eventlistener-kafka + market-eventlistener-kafka + Market kafka listener of Opex @@ -29,8 +29,8 @@ spring-boot-starter-webflux - co.nilin.opex.api.core - api-core + co.nilin.opex.market.core + market-core org.springframework.kafka diff --git a/api/api-ports/api-eventlistener-kafka/src/main/kotlin/co/nilin/opex/api/ports/kafka/listener/config/ApiKafkaConfig.kt b/market/market-ports/market-eventlistener-kafka/src/main/kotlin/co/nilin/opex/market/ports/kafka/listener/config/KafkaConsumerConfig.kt similarity index 76% rename from api/api-ports/api-eventlistener-kafka/src/main/kotlin/co/nilin/opex/api/ports/kafka/listener/config/ApiKafkaConfig.kt rename to market/market-ports/market-eventlistener-kafka/src/main/kotlin/co/nilin/opex/market/ports/kafka/listener/config/KafkaConsumerConfig.kt index c5bf1831d..7a55d05a4 100644 --- a/api/api-ports/api-eventlistener-kafka/src/main/kotlin/co/nilin/opex/api/ports/kafka/listener/config/ApiKafkaConfig.kt +++ b/market/market-ports/market-eventlistener-kafka/src/main/kotlin/co/nilin/opex/market/ports/kafka/listener/config/KafkaConsumerConfig.kt @@ -1,9 +1,9 @@ -package co.nilin.opex.api.ports.kafka.listener.config +package co.nilin.opex.market.ports.kafka.listener.config -import co.nilin.opex.api.core.event.RichOrderEvent -import co.nilin.opex.api.core.event.RichTrade -import co.nilin.opex.api.ports.kafka.listener.consumer.OrderKafkaListener -import co.nilin.opex.api.ports.kafka.listener.consumer.TradeKafkaListener +import co.nilin.opex.market.core.event.RichOrderEvent +import co.nilin.opex.market.core.event.RichTrade +import co.nilin.opex.market.ports.kafka.listener.consumer.OrderKafkaListener +import co.nilin.opex.market.ports.kafka.listener.consumer.TradeKafkaListener import org.apache.kafka.clients.consumer.ConsumerConfig import org.apache.kafka.common.TopicPartition import org.apache.kafka.common.serialization.StringDeserializer @@ -22,7 +22,7 @@ import org.springframework.util.backoff.FixedBackOff import java.util.regex.Pattern @Configuration -class ApiKafkaConfig { +class KafkaConsumerConfig { @Value("\${spring.kafka.bootstrap-servers}") private lateinit var bootstrapServers: String @@ -30,7 +30,7 @@ class ApiKafkaConfig { @Value("\${spring.kafka.consumer.group-id}") private lateinit var groupId: String - @Bean("apiConsumerConfig") + @Bean("marketConsumerConfig") fun consumerConfigs(): Map { return mapOf( ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG to bootstrapServers, @@ -38,17 +38,17 @@ class ApiKafkaConfig { ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG to StringDeserializer::class.java, ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG to JsonDeserializer::class.java, JsonDeserializer.TRUSTED_PACKAGES to "co.nilin.opex.*", - JsonDeserializer.TYPE_MAPPINGS to "rich_order_event:co.nilin.opex.api.core.event.RichOrderEvent,rich_order:co.nilin.opex.api.core.event.RichOrder,rich_order_update:co.nilin.opex.api.core.event.RichOrderUpdate,rich_trade:co.nilin.opex.api.core.event.RichTrade" + JsonDeserializer.TYPE_MAPPINGS to "rich_order_event:co.nilin.opex.market.core.event.RichOrderEvent,rich_order:co.nilin.opex.market.core.event.RichOrder,rich_order_update:co.nilin.opex.market.core.event.RichOrderUpdate,rich_trade:co.nilin.opex.market.core.event.RichTrade" ) } @Bean("richTradeConsumerFactory") - fun richTradeConsumerFactory(@Qualifier("apiConsumerConfig") consumerConfigs: Map): ConsumerFactory { + fun richTradeConsumerFactory(@Qualifier("marketConsumerConfig") consumerConfigs: Map): ConsumerFactory { return DefaultKafkaConsumerFactory(consumerConfigs) } @Bean("richOrderConsumerFactory") - fun richOrderConsumerFactory(@Qualifier("apiConsumerConfig") consumerConfigs: Map): ConsumerFactory { + fun richOrderConsumerFactory(@Qualifier("marketConsumerConfig") consumerConfigs: Map): ConsumerFactory { return DefaultKafkaConsumerFactory(consumerConfigs) } @@ -62,7 +62,7 @@ class ApiKafkaConfig { val containerProps = ContainerProperties(Pattern.compile("richTrade")) containerProps.messageListener = tradeListener val container = ConcurrentMessageListenerContainer(consumerFactory, containerProps) - container.setBeanName("ApiTradeKafkaListenerContainer") + container.setBeanName("marketTradeKafkaListenerContainer") container.commonErrorHandler = createConsumerErrorHandler(template, "richTrade.DLT") container.start() } @@ -77,14 +77,14 @@ class ApiKafkaConfig { val containerProps = ContainerProperties(Pattern.compile("richOrder")) containerProps.messageListener = orderListener val container = ConcurrentMessageListenerContainer(consumerFactory, containerProps) - container.setBeanName("ApiOrderKafkaListenerContainer") + container.setBeanName("marketOrderKafkaListenerContainer") container.commonErrorHandler = createConsumerErrorHandler(template, "richOrder.DLT") container.start() } private fun createConsumerErrorHandler(kafkaTemplate: KafkaTemplate<*, *>, dltTopic: String): CommonErrorHandler { val recoverer = DeadLetterPublishingRecoverer(kafkaTemplate) { cr, _ -> - cr.headers().add("dlt-origin-module", "API".toByteArray()) + cr.headers().add("dlt-origin-module", "MARKET".toByteArray()) TopicPartition(dltTopic, cr.partition()) } return DefaultErrorHandler(recoverer, FixedBackOff(5_000, 20)) diff --git a/api/api-ports/api-eventlistener-kafka/src/main/kotlin/co/nilin/opex/api/ports/kafka/listener/config/KafkaProducerConfig.kt b/market/market-ports/market-eventlistener-kafka/src/main/kotlin/co/nilin/opex/market/ports/kafka/listener/config/KafkaProducerConfig.kt similarity index 78% rename from api/api-ports/api-eventlistener-kafka/src/main/kotlin/co/nilin/opex/api/ports/kafka/listener/config/KafkaProducerConfig.kt rename to market/market-ports/market-eventlistener-kafka/src/main/kotlin/co/nilin/opex/market/ports/kafka/listener/config/KafkaProducerConfig.kt index 353147cd2..e249bbe56 100644 --- a/api/api-ports/api-eventlistener-kafka/src/main/kotlin/co/nilin/opex/api/ports/kafka/listener/config/KafkaProducerConfig.kt +++ b/market/market-ports/market-eventlistener-kafka/src/main/kotlin/co/nilin/opex/market/ports/kafka/listener/config/KafkaProducerConfig.kt @@ -1,7 +1,7 @@ -package co.nilin.opex.api.ports.kafka.listener.config +package co.nilin.opex.market.ports.kafka.listener.config -import co.nilin.opex.api.core.event.RichOrderEvent -import co.nilin.opex.api.core.event.RichTrade +import co.nilin.opex.market.core.event.RichOrderEvent +import co.nilin.opex.market.core.event.RichTrade import org.apache.kafka.clients.producer.ProducerConfig import org.apache.kafka.common.serialization.StringSerializer import org.springframework.beans.factory.annotation.Qualifier @@ -19,7 +19,7 @@ class KafkaProducerConfig { @Value("\${spring.kafka.bootstrap-servers}") private lateinit var bootstrapServers: String - @Bean("apiProducerConfigs") + @Bean("marketProducerConfigs") fun producerConfigs(): Map { return mapOf( ProducerConfig.BOOTSTRAP_SERVERS_CONFIG to bootstrapServers, @@ -30,7 +30,7 @@ class KafkaProducerConfig { } @Bean("richTradeProducerFactory") - fun richTradeProducerFactory(@Qualifier("apiProducerConfigs") producerConfigs: Map): ProducerFactory { + fun richTradeProducerFactory(@Qualifier("marketProducerConfigs") producerConfigs: Map): ProducerFactory { return DefaultKafkaProducerFactory(producerConfigs) } @@ -40,7 +40,7 @@ class KafkaProducerConfig { } @Bean("richOrderProducerFactory") - fun richOrderProducerFactory(@Qualifier("apiProducerConfigs") producerConfigs: Map): ProducerFactory { + fun richOrderProducerFactory(@Qualifier("marketProducerConfigs") producerConfigs: Map): ProducerFactory { return DefaultKafkaProducerFactory(producerConfigs) } diff --git a/api/api-ports/api-eventlistener-kafka/src/main/kotlin/co/nilin/opex/api/ports/kafka/listener/config/KafkaTopicConfig.kt b/market/market-ports/market-eventlistener-kafka/src/main/kotlin/co/nilin/opex/market/ports/kafka/listener/config/KafkaTopicConfig.kt similarity index 95% rename from api/api-ports/api-eventlistener-kafka/src/main/kotlin/co/nilin/opex/api/ports/kafka/listener/config/KafkaTopicConfig.kt rename to market/market-ports/market-eventlistener-kafka/src/main/kotlin/co/nilin/opex/market/ports/kafka/listener/config/KafkaTopicConfig.kt index aa5b62302..f52795363 100644 --- a/api/api-ports/api-eventlistener-kafka/src/main/kotlin/co/nilin/opex/api/ports/kafka/listener/config/KafkaTopicConfig.kt +++ b/market/market-ports/market-eventlistener-kafka/src/main/kotlin/co/nilin/opex/market/ports/kafka/listener/config/KafkaTopicConfig.kt @@ -1,4 +1,4 @@ -package co.nilin.opex.api.ports.kafka.listener.config +package co.nilin.opex.market.ports.kafka.listener.config import org.apache.kafka.clients.admin.NewTopic import org.apache.kafka.common.config.TopicConfig diff --git a/api/api-ports/api-eventlistener-kafka/src/main/kotlin/co/nilin/opex/api/ports/kafka/listener/consumer/OrderKafkaListener.kt b/market/market-ports/market-eventlistener-kafka/src/main/kotlin/co/nilin/opex/market/ports/kafka/listener/consumer/OrderKafkaListener.kt similarity index 80% rename from api/api-ports/api-eventlistener-kafka/src/main/kotlin/co/nilin/opex/api/ports/kafka/listener/consumer/OrderKafkaListener.kt rename to market/market-ports/market-eventlistener-kafka/src/main/kotlin/co/nilin/opex/market/ports/kafka/listener/consumer/OrderKafkaListener.kt index d8d9cb43c..1586c1129 100644 --- a/api/api-ports/api-eventlistener-kafka/src/main/kotlin/co/nilin/opex/api/ports/kafka/listener/consumer/OrderKafkaListener.kt +++ b/market/market-ports/market-eventlistener-kafka/src/main/kotlin/co/nilin/opex/market/ports/kafka/listener/consumer/OrderKafkaListener.kt @@ -1,7 +1,7 @@ -package co.nilin.opex.api.ports.kafka.listener.consumer +package co.nilin.opex.market.ports.kafka.listener.consumer -import co.nilin.opex.api.core.event.RichOrderEvent -import co.nilin.opex.api.ports.kafka.listener.spi.RichOrderListener +import co.nilin.opex.market.core.event.RichOrderEvent +import co.nilin.opex.market.ports.kafka.listener.spi.RichOrderListener import org.apache.kafka.clients.consumer.ConsumerRecord import org.springframework.kafka.listener.MessageListener import org.springframework.stereotype.Component diff --git a/api/api-ports/api-eventlistener-kafka/src/main/kotlin/co/nilin/opex/api/ports/kafka/listener/consumer/TradeKafkaListener.kt b/market/market-ports/market-eventlistener-kafka/src/main/kotlin/co/nilin/opex/market/ports/kafka/listener/consumer/TradeKafkaListener.kt similarity index 80% rename from api/api-ports/api-eventlistener-kafka/src/main/kotlin/co/nilin/opex/api/ports/kafka/listener/consumer/TradeKafkaListener.kt rename to market/market-ports/market-eventlistener-kafka/src/main/kotlin/co/nilin/opex/market/ports/kafka/listener/consumer/TradeKafkaListener.kt index ff4e28ed5..2e8def20f 100644 --- a/api/api-ports/api-eventlistener-kafka/src/main/kotlin/co/nilin/opex/api/ports/kafka/listener/consumer/TradeKafkaListener.kt +++ b/market/market-ports/market-eventlistener-kafka/src/main/kotlin/co/nilin/opex/market/ports/kafka/listener/consumer/TradeKafkaListener.kt @@ -1,7 +1,7 @@ -package co.nilin.opex.api.ports.kafka.listener.consumer +package co.nilin.opex.market.ports.kafka.listener.consumer -import co.nilin.opex.api.core.event.RichTrade -import co.nilin.opex.api.ports.kafka.listener.spi.RichTradeListener +import co.nilin.opex.market.core.event.RichTrade +import co.nilin.opex.market.ports.kafka.listener.spi.RichTradeListener import org.apache.kafka.clients.consumer.ConsumerRecord import org.springframework.kafka.listener.MessageListener import org.springframework.stereotype.Component diff --git a/api/api-ports/api-eventlistener-kafka/src/main/kotlin/co/nilin/opex/api/ports/kafka/listener/spi/RichOrderListener.kt b/market/market-ports/market-eventlistener-kafka/src/main/kotlin/co/nilin/opex/market/ports/kafka/listener/spi/RichOrderListener.kt similarity index 56% rename from api/api-ports/api-eventlistener-kafka/src/main/kotlin/co/nilin/opex/api/ports/kafka/listener/spi/RichOrderListener.kt rename to market/market-ports/market-eventlistener-kafka/src/main/kotlin/co/nilin/opex/market/ports/kafka/listener/spi/RichOrderListener.kt index 4b5940ecc..520b854e4 100644 --- a/api/api-ports/api-eventlistener-kafka/src/main/kotlin/co/nilin/opex/api/ports/kafka/listener/spi/RichOrderListener.kt +++ b/market/market-ports/market-eventlistener-kafka/src/main/kotlin/co/nilin/opex/market/ports/kafka/listener/spi/RichOrderListener.kt @@ -1,6 +1,6 @@ -package co.nilin.opex.api.ports.kafka.listener.spi +package co.nilin.opex.market.ports.kafka.listener.spi -import co.nilin.opex.api.core.event.RichOrderEvent +import co.nilin.opex.market.core.event.RichOrderEvent interface RichOrderListener { diff --git a/api/api-ports/api-eventlistener-kafka/src/main/kotlin/co/nilin/opex/api/ports/kafka/listener/spi/RichTradeListener.kt b/market/market-ports/market-eventlistener-kafka/src/main/kotlin/co/nilin/opex/market/ports/kafka/listener/spi/RichTradeListener.kt similarity index 56% rename from api/api-ports/api-eventlistener-kafka/src/main/kotlin/co/nilin/opex/api/ports/kafka/listener/spi/RichTradeListener.kt rename to market/market-ports/market-eventlistener-kafka/src/main/kotlin/co/nilin/opex/market/ports/kafka/listener/spi/RichTradeListener.kt index c05de2545..1fdc99503 100644 --- a/api/api-ports/api-eventlistener-kafka/src/main/kotlin/co/nilin/opex/api/ports/kafka/listener/spi/RichTradeListener.kt +++ b/market/market-ports/market-eventlistener-kafka/src/main/kotlin/co/nilin/opex/market/ports/kafka/listener/spi/RichTradeListener.kt @@ -1,8 +1,10 @@ -package co.nilin.opex.api.ports.kafka.listener.spi +package co.nilin.opex.market.ports.kafka.listener.spi -import co.nilin.opex.api.core.event.RichTrade +import co.nilin.opex.market.core.event.RichTrade interface RichTradeListener { + fun id(): String + fun onTrade(trade: RichTrade, partition: Int, offset: Long, timestamp: Long) } \ No newline at end of file diff --git a/market/market-ports/market-persister-postgres/pom.xml b/market/market-ports/market-persister-postgres/pom.xml new file mode 100644 index 000000000..df22b6931 --- /dev/null +++ b/market/market-ports/market-persister-postgres/pom.xml @@ -0,0 +1,71 @@ + + + 4.0.0 + + + co.nilin.opex.market + market + 1.0.0-beta.3 + ../../pom.xml + + + co.nilin.opex.market.ports.postgres + market-persister-postgres + market-persister-postgres + Persist items of Opex market on Postgres + + + + org.jetbrains.kotlin + kotlin-reflect + + + co.nilin.opex.market.core + market-core + + + co.nilin.opex.utility.error + error-handler + + + org.springframework.boot + spring-boot-starter-data-r2dbc + + + io.r2dbc + r2dbc-postgresql + runtime + + + org.postgresql + postgresql + runtime + + + io.projectreactor.kotlin + reactor-kotlin-extensions + + + org.jetbrains.kotlinx + kotlinx-coroutines-reactor + + + org.jetbrains.kotlinx + kotlinx-coroutines-core + + + com.google.code.gson + gson + + + io.projectreactor + reactor-test + test + + + io.mockk + mockk + + + diff --git a/market/market-ports/market-persister-postgres/src/main/kotlin/co/nilin/opex/market/ports/postgres/config/PostgresConfig.kt b/market/market-ports/market-persister-postgres/src/main/kotlin/co/nilin/opex/market/ports/postgres/config/PostgresConfig.kt new file mode 100644 index 000000000..b3e3ee34f --- /dev/null +++ b/market/market-ports/market-persister-postgres/src/main/kotlin/co/nilin/opex/market/ports/postgres/config/PostgresConfig.kt @@ -0,0 +1,24 @@ +package co.nilin.opex.market.ports.postgres.config + +import org.springframework.beans.factory.annotation.Value +import org.springframework.context.annotation.Configuration +import org.springframework.core.io.Resource +import org.springframework.data.r2dbc.repository.config.EnableR2dbcRepositories +import org.springframework.r2dbc.core.DatabaseClient + +@Configuration +@EnableR2dbcRepositories(basePackages = ["co.nilin.opex"]) +class PostgresConfig( + db: DatabaseClient, + @Value("classpath:schema.sql") private val schemaResource: Resource +) { + init { + val schemaReader = schemaResource.inputStream.reader() + val schema = schemaReader.readText().trim() + schemaReader.close() + val initDb = db.sql { schema } + initDb // initialize the database + .then() + .subscribe() // execute + } +} diff --git a/market/market-ports/market-persister-postgres/src/main/kotlin/co/nilin/opex/market/ports/postgres/dao/CurrencyRateRepository.kt b/market/market-ports/market-persister-postgres/src/main/kotlin/co/nilin/opex/market/ports/postgres/dao/CurrencyRateRepository.kt new file mode 100644 index 000000000..3e40642d2 --- /dev/null +++ b/market/market-ports/market-persister-postgres/src/main/kotlin/co/nilin/opex/market/ports/postgres/dao/CurrencyRateRepository.kt @@ -0,0 +1,31 @@ +package co.nilin.opex.market.ports.postgres.dao + +import co.nilin.opex.market.core.inout.RateSource +import co.nilin.opex.market.ports.postgres.model.CurrencyRateModel +import org.springframework.data.r2dbc.repository.Query +import org.springframework.data.repository.reactive.ReactiveCrudRepository +import org.springframework.stereotype.Repository +import reactor.core.publisher.Flux +import reactor.core.publisher.Mono +import java.math.BigDecimal + +@Repository +interface CurrencyRateRepository : ReactiveCrudRepository { + + @Query( + """ + insert into currency_rate (base, quote, source, rate) + values (:base, :quote, :source, :rate) + on conflict (base, quote, source) + do update set rate = excluded.rate + """ + ) + fun createOrUpdate(base: String, quote: String, source: RateSource, rate: BigDecimal): Mono + + @Query("select * from currency_rate where base = :base and quote = :quote and source = :source") + fun findByBaseAndQuoteAndSource(base: String, quote: String, source: RateSource): Mono + + @Query("select * from currency_rate where quote = :quote and source = :source") + fun findAllByQuoteAndSource(quote: String, source: RateSource): Flux + +} \ No newline at end of file diff --git a/api/api-ports/api-persister-postgres/src/main/kotlin/co/nilin/opex/api/ports/postgres/dao/OrderRepository.kt b/market/market-ports/market-persister-postgres/src/main/kotlin/co/nilin/opex/market/ports/postgres/dao/OrderRepository.kt similarity index 82% rename from api/api-ports/api-persister-postgres/src/main/kotlin/co/nilin/opex/api/ports/postgres/dao/OrderRepository.kt rename to market/market-ports/market-persister-postgres/src/main/kotlin/co/nilin/opex/market/ports/postgres/dao/OrderRepository.kt index 55a61b770..5f3f90158 100644 --- a/api/api-ports/api-persister-postgres/src/main/kotlin/co/nilin/opex/api/ports/postgres/dao/OrderRepository.kt +++ b/market/market-ports/market-persister-postgres/src/main/kotlin/co/nilin/opex/market/ports/postgres/dao/OrderRepository.kt @@ -1,8 +1,8 @@ -package co.nilin.opex.api.ports.postgres.dao +package co.nilin.opex.market.ports.postgres.dao -import co.nilin.opex.api.core.inout.AggregatedOrderPriceModel -import co.nilin.opex.api.core.inout.OrderDirection -import co.nilin.opex.api.ports.postgres.model.OrderModel +import co.nilin.opex.market.core.inout.AggregatedOrderPriceModel +import co.nilin.opex.market.core.inout.OrderDirection +import co.nilin.opex.market.ports.postgres.model.OrderModel import kotlinx.coroutines.flow.Flow import org.springframework.data.r2dbc.repository.Query import org.springframework.data.repository.query.Param @@ -10,6 +10,7 @@ import org.springframework.data.repository.reactive.ReactiveCrudRepository import org.springframework.stereotype.Repository import reactor.core.publisher.Flux import reactor.core.publisher.Mono +import java.time.LocalDateTime import java.util.* @Repository @@ -18,6 +19,9 @@ interface OrderRepository : ReactiveCrudRepository { @Query("select * from orders where ouid = :ouid") fun findByOuid(@Param("ouid") ouid: String): Mono + @Query("select * from orders where uuid = :uuid and ouid = :ouid") + fun findByUUIDAndOUID(@Param("uuid") uuid: String, @Param("ouid") ouid: String): Mono + @Query("select * from orders where symbol = :symbol and order_id = :orderId") fun findBySymbolAndOrderId( @Param("symbol") @@ -115,4 +119,13 @@ interface OrderRepository : ReactiveCrudRepository { @Query("select * from orders where symbol = :symbol order by create_date desc limit 1") fun findLastOrderBySymbol(@Param("symbol") symbol: String): Mono + + @Query("select count(distinct uuid) from orders where create_date >= :interval") + fun countUsersWhoMadeOrder(interval: LocalDateTime): Flow + + @Query("select count(*) from orders where create_date >= :interval") + fun countNewerThan(interval: LocalDateTime): Flow + + @Query("select count(*) from orders where symbol = :symbol and create_date >= :interval") + fun countBySymbolNewerThan(interval: LocalDateTime, symbol: String): Flow } \ No newline at end of file diff --git a/api/api-ports/api-persister-postgres/src/main/kotlin/co/nilin/opex/api/ports/postgres/dao/OrderStatusRepository.kt b/market/market-ports/market-persister-postgres/src/main/kotlin/co/nilin/opex/market/ports/postgres/dao/OrderStatusRepository.kt similarity index 86% rename from api/api-ports/api-persister-postgres/src/main/kotlin/co/nilin/opex/api/ports/postgres/dao/OrderStatusRepository.kt rename to market/market-ports/market-persister-postgres/src/main/kotlin/co/nilin/opex/market/ports/postgres/dao/OrderStatusRepository.kt index b984c5e43..81e75a0bc 100644 --- a/api/api-ports/api-persister-postgres/src/main/kotlin/co/nilin/opex/api/ports/postgres/dao/OrderStatusRepository.kt +++ b/market/market-ports/market-persister-postgres/src/main/kotlin/co/nilin/opex/market/ports/postgres/dao/OrderStatusRepository.kt @@ -1,6 +1,6 @@ -package co.nilin.opex.api.ports.postgres.dao +package co.nilin.opex.market.ports.postgres.dao -import co.nilin.opex.api.ports.postgres.model.OrderStatusModel +import co.nilin.opex.market.ports.postgres.model.OrderStatusModel import org.springframework.data.r2dbc.repository.Query import org.springframework.data.repository.reactive.ReactiveCrudRepository import org.springframework.stereotype.Repository diff --git a/api/api-ports/api-persister-postgres/src/main/kotlin/co/nilin/opex/api/ports/postgres/dao/TradeRepository.kt b/market/market-ports/market-persister-postgres/src/main/kotlin/co/nilin/opex/market/ports/postgres/dao/TradeRepository.kt similarity index 51% rename from api/api-ports/api-persister-postgres/src/main/kotlin/co/nilin/opex/api/ports/postgres/dao/TradeRepository.kt rename to market/market-ports/market-persister-postgres/src/main/kotlin/co/nilin/opex/market/ports/postgres/dao/TradeRepository.kt index 06656e428..bfba22dea 100644 --- a/api/api-ports/api-persister-postgres/src/main/kotlin/co/nilin/opex/api/ports/postgres/dao/TradeRepository.kt +++ b/market/market-ports/market-persister-postgres/src/main/kotlin/co/nilin/opex/market/ports/postgres/dao/TradeRepository.kt @@ -1,8 +1,11 @@ -package co.nilin.opex.api.ports.postgres.dao +package co.nilin.opex.market.ports.postgres.dao -import co.nilin.opex.api.ports.postgres.model.CandleInfoData -import co.nilin.opex.api.ports.postgres.model.TradeModel -import co.nilin.opex.api.ports.postgres.model.TradeTickerData +import co.nilin.opex.market.core.inout.BestPrice +import co.nilin.opex.market.core.inout.PriceStat +import co.nilin.opex.market.core.inout.TradeVolumeStat +import co.nilin.opex.market.ports.postgres.model.CandleInfoData +import co.nilin.opex.market.ports.postgres.model.TradeModel +import co.nilin.opex.market.ports.postgres.model.TradeTickerData import kotlinx.coroutines.flow.Flow import org.springframework.data.r2dbc.repository.Query import org.springframework.data.repository.query.Param @@ -16,9 +19,12 @@ import java.util.* @Repository interface TradeRepository : ReactiveCrudRepository { - @Query("select * from trades where :ouid in (taker_ouid, maker_ouid) ") + @Query("select * from trades where :ouid in (taker_ouid, maker_ouid)") fun findByOuid(@Param("ouid") ouid: String): Flow + @Query("select * from trades where symbol = :symbol order by create_date desc limit 1") + fun findMostRecentBySymbol(symbol: String): Flow + @Query( """ select * from trades where :uuid in (taker_uuid, maker_uuid) @@ -144,6 +150,83 @@ interface TradeRepository : ReactiveCrudRepository { createDate: LocalDateTime, ): Mono + @Query( + """ + select symbol, + ( + select price from orders + join order_status os on orders.ouid = os.ouid + where symbol = t.symbol and status in (1, 4) and side = 'BID' + and appearance = (select max(appearance) from order_status where ouid = orders.ouid) + and executed_quantity = (select max(executed_quantity) from order_status where ouid = orders.ouid) + order by create_date desc limit 1 + ) as bid_price, + ( + select price from orders + join order_status os on orders.ouid = os.ouid + where symbol = t.symbol and status in (1, 4) and side = 'ASK' + and appearance = (select max(appearance) from order_status where ouid = orders.ouid) + and executed_quantity = (select max(executed_quantity) from order_status where ouid = orders.ouid) + order by create_date limit 1 + ) as ask_price + from trades as t + group by symbol + """ + ) + fun bestAskAndBidPrice(): Flux + + @Query( + """ + select symbol, + ( + select price from orders + join order_status os on orders.ouid = os.ouid + where symbol = t.symbol and status in (1, 4) and side = 'BID' + and appearance = (select max(appearance) from order_status where ouid = orders.ouid) + and executed_quantity = (select max(executed_quantity) from order_status where ouid = orders.ouid) + order by create_date desc limit 1 + ) as bid_price, + ( + select price from orders + join order_status os on orders.ouid = os.ouid + where symbol = t.symbol and status in (1, 4) and side = 'ASK' + and appearance = (select max(appearance) from order_status where ouid = orders.ouid) + and executed_quantity = (select max(executed_quantity) from order_status where ouid = orders.ouid) + order by create_date limit 1 + ) as ask_price + from trades as t + where symbol in (:symbols) + group by symbol + """ + ) + fun bestAskAndBidPrice(symbols: List): Flux + + @Query( + """ + select symbol, + ( + select price from orders + join order_status os on orders.ouid = os.ouid + where symbol = t.symbol and status in (1, 4) and side = 'BID' + and appearance = (select max(appearance) from order_status where ouid = orders.ouid) + and executed_quantity = (select max(executed_quantity) from order_status where ouid = orders.ouid) + order by create_date desc limit 1 + ) as bid_price, + ( + select price from orders + join order_status os on orders.ouid = os.ouid + where symbol = t.symbol and status in (1, 4) and side = 'ASK' + and appearance = (select max(appearance) from order_status where ouid = orders.ouid) + and executed_quantity = (select max(executed_quantity) from order_status where ouid = orders.ouid) + order by create_date limit 1 + ) as ask_price + from trades as t + where symbol = :symbol + group by symbol + """ + ) + fun bestAskAndBidPrice(symbol: String): Mono + @Query("select * from trades where create_date in (select max(create_date) from trades group by symbol) and symbol = :symbol") fun findBySymbolGroupBySymbol(@Param("symbol") symbol: String): Flux @@ -187,6 +270,110 @@ interface TradeRepository : ReactiveCrudRepository { @Query("select * from trades order by create_date desc limit 1") suspend fun findLastByCreateDate(): Mono - @Query("select * from trades order by create_date asc limit 1") + @Query("select * from trades order by create_date limit 1") suspend fun findFirstByCreateDate(): Mono + + @Query("select count(*) from trades where create_date >= :interval") + fun countNewerThan(interval: LocalDateTime): Flow + + @Query("select count(*) from trades where symbol = :symbol and create_date >= :interval") + fun countBySymbolNewerThan(interval: LocalDateTime, symbol: String): Flow + + @Query( + """ + select + symbol, + coalesce((select matched_price from trades where create_date > :since and symbol = t.symbol order by create_date desc limit 1), 0.0) as last_price, + coalesce( + max( + (select matched_price from trades where create_date > :since and symbol = t.symbol order by create_date desc limit 1) + - (select matched_price from trades where create_date > :since and symbol = t.symbol order by create_date limit 1) + ), + 0.0 + ) as price_change, + coalesce( + ( + (select matched_price from trades where create_date > :since and symbol = t.symbol order by create_date desc limit 1) + - (select matched_price from trades where create_date > :since and symbol = t.symbol order by create_date limit 1) + ) / (select matched_price from trades where create_date > :since and symbol = t.symbol order by create_date limit 1) * 100, + 0.0 + ) as price_change_percent + from trades t + group by symbol + order by price_change_percent desc + limit :limit + """ + ) + fun findByMostIncreasedPrice(since: LocalDateTime, limit: Int): Flux + + @Query( + """ + select + symbol, + coalesce((select matched_price from trades where create_date > :since and symbol = t.symbol order by create_date desc limit 1), 0.0) as last_price, + coalesce( + max( + (select matched_price from trades where create_date > :since and symbol = t.symbol order by create_date desc limit 1) + - (select matched_price from trades where create_date > :since and symbol = t.symbol order by create_date limit 1) + ), + 0.0 + ) as price_change, + coalesce( + ( + (select matched_price from trades where create_date > :since and symbol = t.symbol order by create_date desc limit 1) + -(select matched_price from trades where create_date > :since and symbol = t.symbol order by create_date limit 1) + ) / (select matched_price from trades where create_date > :since and symbol = t.symbol order by create_date limit 1) * 100, + 0.0 + ) as price_change_percent + from trades t + group by symbol + order by price_change_percent + limit :limit + """ + ) + fun findByMostDecreasedPrice(since: LocalDateTime, limit: Int): Flux + + @Query( + """ + select + symbol, + coalesce(sum(matched_quantity), 0.0) as volume, + count(id) as trade_count, + coalesce( + ( + (select matched_quantity from trades where create_date > :since and symbol = t.symbol order by create_date desc limit 1) + - (select matched_quantity from trades where create_date > :since and symbol = t.symbol order by create_date limit 1) + ) / (select matched_quantity from trades where create_date > :since and symbol = t.symbol order by create_date limit 1) * 100, + 0.0 + ) as change + from trades t + where create_date > :since + group by symbol + order by volume + limit 1 + """ + ) + fun findByMostVolume(since: LocalDateTime): Mono + + @Query( + """ + select + symbol, + coalesce(sum(matched_quantity), 0.0) as volume, + count(id) as trade_count, + coalesce( + ( + (select matched_quantity from trades where create_date > :since and symbol = t.symbol order by create_date desc limit 1) + - (select matched_quantity from trades where create_date > :since and symbol = t.symbol order by create_date limit 1) + ) / (select matched_quantity from trades where create_date > :since and symbol = t.symbol order by create_date limit 1) * 100, + 0.0 + ) as change + from trades t + where create_date > :since + group by symbol + order by trade_count + limit 1 + """ + ) + fun findByMostTrades(since: LocalDateTime): Mono } \ No newline at end of file diff --git a/api/api-ports/api-persister-postgres/src/main/kotlin/co/nilin/opex/api/ports/postgres/impl/MarketQueryHandlerImpl.kt b/market/market-ports/market-persister-postgres/src/main/kotlin/co/nilin/opex/market/ports/postgres/impl/MarketQueryHandlerImpl.kt similarity index 62% rename from api/api-ports/api-persister-postgres/src/main/kotlin/co/nilin/opex/api/ports/postgres/impl/MarketQueryHandlerImpl.kt rename to market/market-ports/market-persister-postgres/src/main/kotlin/co/nilin/opex/market/ports/postgres/impl/MarketQueryHandlerImpl.kt index 794df1bec..3291eaf7b 100644 --- a/api/api-ports/api-persister-postgres/src/main/kotlin/co/nilin/opex/api/ports/postgres/impl/MarketQueryHandlerImpl.kt +++ b/market/market-ports/market-persister-postgres/src/main/kotlin/co/nilin/opex/market/ports/postgres/impl/MarketQueryHandlerImpl.kt @@ -1,20 +1,19 @@ -package co.nilin.opex.api.ports.postgres.impl - -import co.nilin.opex.api.core.inout.* -import co.nilin.opex.api.core.spi.MarketQueryHandler -import co.nilin.opex.api.core.spi.SymbolMapper -import co.nilin.opex.api.ports.postgres.dao.OrderRepository -import co.nilin.opex.api.ports.postgres.dao.OrderStatusRepository -import co.nilin.opex.api.ports.postgres.dao.TradeRepository -import co.nilin.opex.api.ports.postgres.model.OrderModel -import co.nilin.opex.api.ports.postgres.model.OrderStatusModel -import co.nilin.opex.api.ports.postgres.model.TradeTickerData -import co.nilin.opex.api.ports.postgres.util.* -import kotlinx.coroutines.flow.Flow +package co.nilin.opex.market.ports.postgres.impl + +import co.nilin.opex.market.core.inout.* +import co.nilin.opex.market.core.spi.MarketQueryHandler +import co.nilin.opex.market.ports.postgres.dao.OrderRepository +import co.nilin.opex.market.ports.postgres.dao.OrderStatusRepository +import co.nilin.opex.market.ports.postgres.dao.TradeRepository +import co.nilin.opex.market.ports.postgres.model.TradeTickerData +import co.nilin.opex.market.ports.postgres.util.asOrderDTO import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.singleOrNull +import kotlinx.coroutines.flow.toList import kotlinx.coroutines.reactive.awaitFirst import kotlinx.coroutines.reactive.awaitFirstOrElse import kotlinx.coroutines.reactive.awaitFirstOrNull +import kotlinx.coroutines.reactor.awaitSingleOrNull import org.springframework.stereotype.Component import java.math.BigDecimal import java.time.Instant @@ -27,11 +26,12 @@ import java.util.* class MarketQueryHandlerImpl( private val orderRepository: OrderRepository, private val tradeRepository: TradeRepository, - private val orderStatusRepository: OrderStatusRepository, - private val symbolMapper: SymbolMapper, + private val orderStatusRepository: OrderStatusRepository ) : MarketQueryHandler { - override suspend fun getTradeTickerData(startFrom: LocalDateTime): List { + //TODO merge order and status fetching in query + + override suspend fun getTradeTickerData(startFrom: LocalDateTime): List { return tradeRepository.tradeTicker(startFrom) .collectList() .awaitFirstOrElse { emptyList() } @@ -39,18 +39,13 @@ class MarketQueryHandlerImpl( } - override suspend fun getTradeTickerDataBySymbol(symbol: String, startFrom: LocalDateTime): PriceChangeResponse { + override suspend fun getTradeTickerDateBySymbol(symbol: String, startFrom: LocalDateTime): PriceChange? { return tradeRepository.tradeTickerBySymbol(symbol, startFrom) .awaitFirstOrNull() ?.asPriceChangeResponse(Date().time, startFrom.toInstant(ZoneOffset.UTC).toEpochMilli()) - ?: PriceChangeResponse( - symbol = symbol, - openTime = Date().time, - closeTime = startFrom.toInstant(ZoneOffset.UTC).toEpochMilli() - ) } - override suspend fun openBidOrders(symbol: String, limit: Int): List { + override suspend fun openBidOrders(symbol: String, limit: Int): List { return orderRepository.findBySymbolAndDirectionAndStatusSortDescendingByPrice( symbol, OrderDirection.BID, @@ -58,10 +53,10 @@ class MarketQueryHandlerImpl( listOf(OrderStatus.NEW.code, OrderStatus.PARTIALLY_FILLED.code) ).collectList() .awaitFirstOrElse { emptyList() } - .map { OrderBookResponse(it.price, it.quantity) } + .map { OrderBook(it.price, it.quantity) } } - override suspend fun openAskOrders(symbol: String, limit: Int): List { + override suspend fun openAskOrders(symbol: String, limit: Int): List { return orderRepository.findBySymbolAndDirectionAndStatusSortAscendingByPrice( symbol, OrderDirection.ASK, @@ -69,25 +64,27 @@ class MarketQueryHandlerImpl( listOf(OrderStatus.NEW.code, OrderStatus.PARTIALLY_FILLED.code) ).collectList() .awaitFirstOrElse { emptyList() } - .map { OrderBookResponse(it.price, it.quantity) } + .map { OrderBook(it.price, it.quantity) } } - override suspend fun lastOrder(symbol: String): QueryOrderResponse? { + override suspend fun lastOrder(symbol: String): Order? { val order = orderRepository.findLastOrderBySymbol(symbol).awaitFirstOrNull() ?: return null val status = orderStatusRepository.findMostRecentByOUID(order.ouid).awaitFirstOrNull() - return order.asQueryOrderResponse(status) + return order.asOrderDTO(status) } - override suspend fun recentTrades(symbol: String, limit: Int): Flow { + override suspend fun recentTrades(symbol: String, limit: Int): List { return tradeRepository.findBySymbolSortDescendingByCreateDate(symbol, limit) .map { val takerOrder = orderRepository.findByOuid(it.takerOuid).awaitFirst() val makerOrder = orderRepository.findByOuid(it.makerOuid).awaitFirst() val isMakerBuyer = makerOrder.direction == OrderDirection.BID - MarketTradeResponse( + MarketTrade( it.symbol, + it.baseAsset, + it.quoteAsset, it.tradeId, - if (isMakerBuyer) it.makerPrice else it.takerPrice, + it.matchedPrice, it.matchedQuantity, if (isMakerBuyer) makerOrder.quoteQuantity!! @@ -97,31 +94,23 @@ class MarketQueryHandlerImpl( true, isMakerBuyer ) - } + }.toList() } - override suspend fun lastPrice(symbol: String?): List { + override suspend fun lastPrice(symbol: String?): List { val list = if (symbol.isNullOrEmpty()) tradeRepository.findAllGroupBySymbol() else tradeRepository.findBySymbolGroupBySymbol(symbol) return list.collectList() .awaitFirstOrElse { emptyList() } - .map { - val makerOrder = orderRepository.findByOuid(it.makerOuid).awaitFirst() - val apiSymbol = try { - symbolMapper.map(it.symbol) - } catch (e: Exception) { - it.symbol - } - val isMakerBuyer = makerOrder.direction == OrderDirection.BID - PriceTickerResponse( - apiSymbol, - if (isMakerBuyer) it.takerPrice.min(it.makerPrice).toString() - else it.takerPrice.max(it.makerPrice).toString() - ) - } + .map { PriceTicker(it.symbol, it.matchedPrice.toString()) } + } + override suspend fun getBestPriceForSymbols(symbols: List): List { + return tradeRepository.bestAskAndBidPrice(symbols) + .collectList() + .awaitFirstOrElse { emptyList() } } override suspend fun getCandleInfo( @@ -167,29 +156,45 @@ class MarketQueryHandlerImpl( } } - private fun OrderModel.asQueryOrderResponse(orderStatusModel: OrderStatusModel?) = QueryOrderResponse( - symbol, - ouid, - orderId ?: -1, - -1, - clientOrderId ?: "", - price!!, - quantity!!, - orderStatusModel?.executedQuantity ?: BigDecimal.ZERO, - orderStatusModel?.accumulativeQuoteQty ?: BigDecimal.ZERO, - orderStatusModel?.status?.toOrderStatus() ?: OrderStatus.NEW, - constraint!!.toTimeInForce(), - type!!.toApiOrderType(), - direction!!.toOrderSide(), - null, - null, - Date.from(createDate!!.atZone(ZoneId.systemDefault()).toInstant()), - Date.from(updateDate.atZone(ZoneId.systemDefault()).toInstant()), - (orderStatusModel?.status?.toOrderStatus() ?: OrderStatus.NEW).isWorking(), - quoteQuantity!! - ) + override suspend fun numberOfActiveUsers(interval: LocalDateTime): Long { + return orderRepository.countUsersWhoMadeOrder(interval).singleOrNull() ?: 0L + } + + override suspend fun numberOfTrades(interval: LocalDateTime, pair: String?): Long { + return if (pair != null) + tradeRepository.countBySymbolNewerThan(interval, pair).singleOrNull() ?: 0 + else + tradeRepository.countNewerThan(interval).singleOrNull() ?: 0 + } + + override suspend fun numberOfOrders(interval: LocalDateTime, pair: String?): Long { + return if (pair != null) + orderRepository.countBySymbolNewerThan(interval, pair).singleOrNull() ?: 0 + else + orderRepository.countNewerThan(interval).singleOrNull() ?: 0 + } + + override suspend fun mostIncreasePrice(interval: LocalDateTime, limit: Int): List { + return tradeRepository.findByMostIncreasedPrice(interval, limit) + .collectList() + .awaitFirstOrElse { emptyList() } + } + + override suspend fun mostDecreasePrice(interval: LocalDateTime, limit: Int): List { + return tradeRepository.findByMostDecreasedPrice(interval, limit) + .collectList() + .awaitFirstOrElse { emptyList() } + } + + override suspend fun mostVolume(interval: LocalDateTime): TradeVolumeStat? { + return tradeRepository.findByMostVolume(interval).awaitSingleOrNull() + } + + override suspend fun mostTrades(interval: LocalDateTime): TradeVolumeStat? { + return tradeRepository.findByMostTrades(interval).awaitSingleOrNull() + } - private fun TradeTickerData.asPriceChangeResponse(openTime: Long, closeTime: Long) = PriceChangeResponse( + private fun TradeTickerData.asPriceChangeResponse(openTime: Long, closeTime: Long) = PriceChange( symbol, priceChange ?: BigDecimal.ZERO, priceChangePercent ?: BigDecimal.ZERO, diff --git a/market/market-ports/market-persister-postgres/src/main/kotlin/co/nilin/opex/market/ports/postgres/impl/MarketRateServiceImpl.kt b/market/market-ports/market-persister-postgres/src/main/kotlin/co/nilin/opex/market/ports/postgres/impl/MarketRateServiceImpl.kt new file mode 100644 index 000000000..61c687227 --- /dev/null +++ b/market/market-ports/market-persister-postgres/src/main/kotlin/co/nilin/opex/market/ports/postgres/impl/MarketRateServiceImpl.kt @@ -0,0 +1,31 @@ +package co.nilin.opex.market.ports.postgres.impl + +import co.nilin.opex.market.core.inout.CurrencyRate +import co.nilin.opex.market.core.inout.RateSource +import co.nilin.opex.market.core.spi.MarketRateService +import co.nilin.opex.market.ports.postgres.dao.CurrencyRateRepository +import kotlinx.coroutines.reactive.awaitFirstOrElse +import kotlinx.coroutines.reactor.awaitSingleOrNull +import org.springframework.stereotype.Component +import java.math.BigDecimal + +@Component +class MarketRateServiceImpl(private val rateRepository: CurrencyRateRepository) : MarketRateService { + + override suspend fun currencyRate(quote: String, source: RateSource): List { + return rateRepository.findAllByQuoteAndSource(quote, source) + .collectList() + .awaitFirstOrElse { emptyList() } + .map { CurrencyRate(it.base, it.quote, it.source, it.rate) } + } + + override suspend fun currencyRate(base: String, quote: String, source: RateSource): CurrencyRate { + val rate = rateRepository.findByBaseAndQuoteAndSource(base, quote, source).awaitSingleOrNull() + return CurrencyRate( + base, + quote, + source, + rate?.rate ?: BigDecimal.ZERO + ) + } +} \ No newline at end of file diff --git a/api/api-ports/api-persister-postgres/src/main/kotlin/co/nilin/opex/api/ports/postgres/impl/OrderPersisterImpl.kt b/market/market-ports/market-persister-postgres/src/main/kotlin/co/nilin/opex/market/ports/postgres/impl/OrderPersisterImpl.kt similarity index 71% rename from api/api-ports/api-persister-postgres/src/main/kotlin/co/nilin/opex/api/ports/postgres/impl/OrderPersisterImpl.kt rename to market/market-ports/market-persister-postgres/src/main/kotlin/co/nilin/opex/market/ports/postgres/impl/OrderPersisterImpl.kt index d6e956561..23dbc6b4e 100644 --- a/api/api-ports/api-persister-postgres/src/main/kotlin/co/nilin/opex/api/ports/postgres/impl/OrderPersisterImpl.kt +++ b/market/market-ports/market-persister-postgres/src/main/kotlin/co/nilin/opex/market/ports/postgres/impl/OrderPersisterImpl.kt @@ -1,14 +1,17 @@ -package co.nilin.opex.api.ports.postgres.impl +package co.nilin.opex.market.ports.postgres.impl -import co.nilin.opex.api.core.event.RichOrder -import co.nilin.opex.api.core.event.RichOrderUpdate -import co.nilin.opex.api.core.inout.OrderStatus -import co.nilin.opex.api.core.spi.OrderPersister -import co.nilin.opex.api.ports.postgres.dao.OrderRepository -import co.nilin.opex.api.ports.postgres.dao.OrderStatusRepository -import co.nilin.opex.api.ports.postgres.model.OrderModel -import co.nilin.opex.api.ports.postgres.model.OrderStatusModel +import co.nilin.opex.market.core.event.RichOrder +import co.nilin.opex.market.core.event.RichOrderUpdate +import co.nilin.opex.market.core.inout.Order +import co.nilin.opex.market.core.inout.OrderStatus +import co.nilin.opex.market.core.spi.OrderPersister +import co.nilin.opex.market.ports.postgres.dao.OrderRepository +import co.nilin.opex.market.ports.postgres.dao.OrderStatusRepository +import co.nilin.opex.market.ports.postgres.model.OrderModel +import co.nilin.opex.market.ports.postgres.model.OrderStatusModel +import co.nilin.opex.market.ports.postgres.util.asOrderDTO import kotlinx.coroutines.reactive.awaitFirstOrNull +import kotlinx.coroutines.reactor.awaitSingleOrNull import org.slf4j.LoggerFactory import org.springframework.stereotype.Component import java.time.LocalDateTime @@ -75,4 +78,10 @@ class OrderPersisterImpl( } logger.info("OrderStatus ${orderUpdate.ouid} updated with status of ${orderUpdate.status}") } + + override suspend fun load(ouid: String): Order? { + return orderRepository.findByOuid(ouid) + .awaitFirstOrNull() + ?.asOrderDTO(orderStatusRepository.findMostRecentByOUID(ouid).awaitSingleOrNull()) + } } \ No newline at end of file diff --git a/api/api-ports/api-persister-postgres/src/main/kotlin/co/nilin/opex/api/ports/postgres/impl/TradePersisterImpl.kt b/market/market-ports/market-persister-postgres/src/main/kotlin/co/nilin/opex/market/ports/postgres/impl/TradePersisterImpl.kt similarity index 52% rename from api/api-ports/api-persister-postgres/src/main/kotlin/co/nilin/opex/api/ports/postgres/impl/TradePersisterImpl.kt rename to market/market-ports/market-persister-postgres/src/main/kotlin/co/nilin/opex/market/ports/postgres/impl/TradePersisterImpl.kt index 0e03aadb5..abdde399f 100644 --- a/api/api-ports/api-persister-postgres/src/main/kotlin/co/nilin/opex/api/ports/postgres/impl/TradePersisterImpl.kt +++ b/market/market-ports/market-persister-postgres/src/main/kotlin/co/nilin/opex/market/ports/postgres/impl/TradePersisterImpl.kt @@ -1,9 +1,11 @@ -package co.nilin.opex.api.ports.postgres.impl +package co.nilin.opex.market.ports.postgres.impl -import co.nilin.opex.api.core.event.RichTrade -import co.nilin.opex.api.core.spi.TradePersister -import co.nilin.opex.api.ports.postgres.dao.TradeRepository -import co.nilin.opex.api.ports.postgres.model.TradeModel +import co.nilin.opex.market.core.event.RichTrade +import co.nilin.opex.market.core.inout.RateSource +import co.nilin.opex.market.core.spi.TradePersister +import co.nilin.opex.market.ports.postgres.dao.CurrencyRateRepository +import co.nilin.opex.market.ports.postgres.dao.TradeRepository +import co.nilin.opex.market.ports.postgres.model.TradeModel import kotlinx.coroutines.reactive.awaitFirstOrNull import org.slf4j.LoggerFactory import org.springframework.stereotype.Component @@ -11,17 +13,25 @@ import org.springframework.transaction.annotation.Transactional import java.time.LocalDateTime @Component -class TradePersisterImpl(private val tradeRepository: TradeRepository) : TradePersister { +class TradePersisterImpl( + private val tradeRepository: TradeRepository, + private val currencyRateRepository: CurrencyRateRepository +) : TradePersister { private val logger = LoggerFactory.getLogger(TradePersisterImpl::class.java) @Transactional override suspend fun save(trade: RichTrade) { + val pair = trade.pair.split("_") + tradeRepository.save( TradeModel( null, trade.id, trade.pair, + pair[0].uppercase(), + pair[1].uppercase(), + trade.matchedPrice, trade.matchedQuantity, trade.takerPrice, trade.makerPrice, @@ -38,5 +48,13 @@ class TradePersisterImpl(private val tradeRepository: TradeRepository) : TradePe ) ).awaitFirstOrNull() logger.info("RichTrade ${trade.id} saved") + + currencyRateRepository.createOrUpdate( + pair[0].uppercase(), + pair[1].uppercase(), + RateSource.MARKET, + trade.matchedPrice + ).awaitFirstOrNull() + logger.info("Rate between ${pair[0]} and ${pair[1]} updated") } } \ No newline at end of file diff --git a/market/market-ports/market-persister-postgres/src/main/kotlin/co/nilin/opex/market/ports/postgres/impl/UserQueryHandlerImpl.kt b/market/market-ports/market-persister-postgres/src/main/kotlin/co/nilin/opex/market/ports/postgres/impl/UserQueryHandlerImpl.kt new file mode 100644 index 000000000..ee3e17f6f --- /dev/null +++ b/market/market-ports/market-persister-postgres/src/main/kotlin/co/nilin/opex/market/ports/postgres/impl/UserQueryHandlerImpl.kt @@ -0,0 +1,98 @@ +package co.nilin.opex.market.ports.postgres.impl + +import co.nilin.opex.market.core.inout.* +import co.nilin.opex.market.core.spi.UserQueryHandler +import co.nilin.opex.market.ports.postgres.dao.OrderRepository +import co.nilin.opex.market.ports.postgres.dao.OrderStatusRepository +import co.nilin.opex.market.ports.postgres.dao.TradeRepository +import co.nilin.opex.market.ports.postgres.util.asOrderDTO +import co.nilin.opex.utility.error.data.OpexError +import co.nilin.opex.utility.error.data.OpexException +import kotlinx.coroutines.flow.filter +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.toList +import kotlinx.coroutines.reactive.awaitFirst +import kotlinx.coroutines.reactive.awaitFirstOrNull +import kotlinx.coroutines.reactor.awaitSingleOrNull +import org.springframework.stereotype.Component +import java.time.ZoneId +import java.util.* + +@Component +class UserQueryHandlerImpl( + private val orderRepository: OrderRepository, + private val tradeRepository: TradeRepository, + private val orderStatusRepository: OrderStatusRepository +) : UserQueryHandler { + + //TODO merge order and status fetching in query + + override suspend fun getOrder(uuid: String, ouid: String): Order? { + return orderRepository.findByUUIDAndOUID(uuid, ouid) + .awaitSingleOrNull() + ?.asOrderDTO(orderStatusRepository.findMostRecentByOUID(ouid).awaitFirstOrNull()) + } + + override suspend fun queryOrder(uuid: String, request: QueryOrderRequest): Order? { + val order = (if (request.origClientOrderId != null) { + orderRepository.findBySymbolAndClientOrderId(request.symbol, request.origClientOrderId!!) + } else { + orderRepository.findBySymbolAndOrderId(request.symbol, request.orderId!!) + }).awaitFirstOrNull() ?: return null + + if (order.uuid != uuid) + throw OpexException(OpexError.Forbidden) + + val status = orderStatusRepository.findMostRecentByOUID(order.ouid).awaitFirstOrNull() + return order.asOrderDTO(status) + } + + override suspend fun openOrders(uuid: String, symbol: String?): List { + return orderRepository.findByUuidAndSymbolAndStatus( + uuid, + symbol, + listOf(OrderStatus.NEW.code, OrderStatus.PARTIALLY_FILLED.code) + ).filter { orderModel -> orderModel.constraint != null } + .map { it.asOrderDTO(orderStatusRepository.findMostRecentByOUID(it.ouid).awaitFirstOrNull()) } + .toList() + } + + override suspend fun allOrders(uuid: String, allOrderRequest: AllOrderRequest): List { + return orderRepository.findByUuidAndSymbolAndTimeBetween( + uuid, + allOrderRequest.symbol, + allOrderRequest.startTime, + allOrderRequest.endTime + ).filter { orderModel -> orderModel.constraint != null } + .map { it.asOrderDTO(orderStatusRepository.findMostRecentByOUID(it.ouid).awaitFirstOrNull()) } + .toList() + } + + override suspend fun allTrades(uuid: String, request: TradeRequest): List { + return tradeRepository.findByUuidAndSymbolAndTimeBetweenAndTradeIdGreaterThan( + uuid, request.symbol, request.fromTrade, request.startTime, request.endTime + ).map { + val takerOrder = orderRepository.findByOuid(it.takerOuid).awaitFirst() + val makerOrder = orderRepository.findByOuid(it.makerOuid).awaitFirst() + val isMakerBuyer = makerOrder.direction == OrderDirection.BID + Trade( + it.symbol, + it.tradeId, + if (it.takerUuid == uuid) takerOrder.orderId!! else makerOrder.orderId!!, + if (it.takerUuid == uuid) it.takerPrice else it.makerPrice, + it.matchedQuantity, + if (isMakerBuyer) makerOrder.quoteQuantity!! else takerOrder.quoteQuantity!!, + if (it.takerUuid == uuid) it.takerCommission!! else it.makerCommission!!, + if (it.takerUuid == uuid) it.takerCommissionAsset!! else it.makerCommissionAsset!!, + Date.from(it.createDate.atZone(ZoneId.systemDefault()).toInstant()), + if (it.takerUuid == uuid) + OrderDirection.ASK == takerOrder.direction + else + OrderDirection.ASK == makerOrder.direction, + it.makerUuid == uuid, + true, + isMakerBuyer + ) + }.toList() + } +} \ No newline at end of file diff --git a/api/api-ports/api-persister-postgres/src/main/kotlin/co/nilin/opex/api/ports/postgres/model/CandleInfoData.kt b/market/market-ports/market-persister-postgres/src/main/kotlin/co/nilin/opex/market/ports/postgres/model/CandleInfoData.kt similarity index 79% rename from api/api-ports/api-persister-postgres/src/main/kotlin/co/nilin/opex/api/ports/postgres/model/CandleInfoData.kt rename to market/market-ports/market-persister-postgres/src/main/kotlin/co/nilin/opex/market/ports/postgres/model/CandleInfoData.kt index 4c31dbf72..f8c106add 100644 --- a/api/api-ports/api-persister-postgres/src/main/kotlin/co/nilin/opex/api/ports/postgres/model/CandleInfoData.kt +++ b/market/market-ports/market-persister-postgres/src/main/kotlin/co/nilin/opex/market/ports/postgres/model/CandleInfoData.kt @@ -1,13 +1,11 @@ -package co.nilin.opex.api.ports.postgres.model +package co.nilin.opex.market.ports.postgres.model import org.springframework.data.relational.core.mapping.Column import java.math.BigDecimal import java.time.LocalDateTime data class CandleInfoData( - @Column("open_time") val openTime: LocalDateTime, - @Column("close_time") val closeTime: LocalDateTime, val open: BigDecimal?, val close: BigDecimal?, diff --git a/market/market-ports/market-persister-postgres/src/main/kotlin/co/nilin/opex/market/ports/postgres/model/CurrencyRateModel.kt b/market/market-ports/market-persister-postgres/src/main/kotlin/co/nilin/opex/market/ports/postgres/model/CurrencyRateModel.kt new file mode 100644 index 000000000..490f622da --- /dev/null +++ b/market/market-ports/market-persister-postgres/src/main/kotlin/co/nilin/opex/market/ports/postgres/model/CurrencyRateModel.kt @@ -0,0 +1,15 @@ +package co.nilin.opex.market.ports.postgres.model + +import co.nilin.opex.market.core.inout.RateSource +import org.springframework.data.annotation.Id +import org.springframework.data.relational.core.mapping.Table +import java.math.BigDecimal + +@Table("currency_rate") +data class CurrencyRateModel( + @Id val id: Long? = null, + val base: String, + val quote: String, + val source: RateSource, + val rate: BigDecimal +) \ No newline at end of file diff --git a/market/market-ports/market-persister-postgres/src/main/kotlin/co/nilin/opex/market/ports/postgres/model/OrderModel.kt b/market/market-ports/market-persister-postgres/src/main/kotlin/co/nilin/opex/market/ports/postgres/model/OrderModel.kt new file mode 100644 index 000000000..04860594f --- /dev/null +++ b/market/market-ports/market-persister-postgres/src/main/kotlin/co/nilin/opex/market/ports/postgres/model/OrderModel.kt @@ -0,0 +1,36 @@ +package co.nilin.opex.market.ports.postgres.model + +import co.nilin.opex.market.core.inout.MatchConstraint +import co.nilin.opex.market.core.inout.MatchingOrderType +import co.nilin.opex.market.core.inout.OrderDirection +import org.springframework.data.annotation.Id +import org.springframework.data.annotation.Version +import org.springframework.data.relational.core.mapping.Column +import org.springframework.data.relational.core.mapping.Table +import java.math.BigDecimal +import java.time.LocalDateTime + +@Table("orders") +data class OrderModel( + @Id var id: Long?, + val ouid: String, + val uuid: String, + val clientOrderId: String?, + val symbol: String, + val orderId: Long?, + val makerFee: BigDecimal?, + val takerFee: BigDecimal?, + val leftSideFraction: BigDecimal?, + val rightSideFraction: BigDecimal?, + val userLevel: String?, + @Column("side") val direction: OrderDirection?, + @Column("match_constraint") val constraint: MatchConstraint?, + @Column("order_type") val type: MatchingOrderType?, + val price: BigDecimal?, + val quantity: BigDecimal?, + val quoteQuantity: BigDecimal?, + val createDate: LocalDateTime?, + val updateDate: LocalDateTime, + @Version + var version: Long? = null +) \ No newline at end of file diff --git a/api/api-ports/api-persister-postgres/src/main/kotlin/co/nilin/opex/api/ports/postgres/model/OrderStatusModel.kt b/market/market-ports/market-persister-postgres/src/main/kotlin/co/nilin/opex/market/ports/postgres/model/OrderStatusModel.kt similarity index 90% rename from api/api-ports/api-persister-postgres/src/main/kotlin/co/nilin/opex/api/ports/postgres/model/OrderStatusModel.kt rename to market/market-ports/market-persister-postgres/src/main/kotlin/co/nilin/opex/market/ports/postgres/model/OrderStatusModel.kt index ab58a6708..f412f8482 100644 --- a/api/api-ports/api-persister-postgres/src/main/kotlin/co/nilin/opex/api/ports/postgres/model/OrderStatusModel.kt +++ b/market/market-ports/market-persister-postgres/src/main/kotlin/co/nilin/opex/market/ports/postgres/model/OrderStatusModel.kt @@ -1,4 +1,4 @@ -package co.nilin.opex.api.ports.postgres.model +package co.nilin.opex.market.ports.postgres.model import org.springframework.data.annotation.Id import org.springframework.data.relational.core.mapping.Table diff --git a/market/market-ports/market-persister-postgres/src/main/kotlin/co/nilin/opex/market/ports/postgres/model/TradeModel.kt b/market/market-ports/market-persister-postgres/src/main/kotlin/co/nilin/opex/market/ports/postgres/model/TradeModel.kt new file mode 100644 index 000000000..e434f1c43 --- /dev/null +++ b/market/market-ports/market-persister-postgres/src/main/kotlin/co/nilin/opex/market/ports/postgres/model/TradeModel.kt @@ -0,0 +1,30 @@ +package co.nilin.opex.market.ports.postgres.model + +import org.springframework.data.annotation.Id +import org.springframework.data.relational.core.mapping.Column +import org.springframework.data.relational.core.mapping.Table +import java.math.BigDecimal +import java.time.LocalDateTime + +@Table("trades") +class TradeModel( + @Id var id: Long?, + val tradeId: Long, + val symbol: String, + val baseAsset: String, + val quoteAsset: String, + val matchedPrice: BigDecimal, + val matchedQuantity: BigDecimal, + val takerPrice: BigDecimal, + val makerPrice: BigDecimal, + val takerCommission: BigDecimal?, + val makerCommission: BigDecimal?, + val takerCommissionAsset: String?, + val makerCommissionAsset: String?, + val tradeDate: LocalDateTime, + val makerOuid: String, + val takerOuid: String, + val makerUuid: String, + val takerUuid: String, + val createDate: LocalDateTime +) \ No newline at end of file diff --git a/api/api-ports/api-persister-postgres/src/main/kotlin/co/nilin/opex/api/ports/postgres/model/TradeTickerData.kt b/market/market-ports/market-persister-postgres/src/main/kotlin/co/nilin/opex/market/ports/postgres/model/TradeTickerData.kt similarity index 94% rename from api/api-ports/api-persister-postgres/src/main/kotlin/co/nilin/opex/api/ports/postgres/model/TradeTickerData.kt rename to market/market-ports/market-persister-postgres/src/main/kotlin/co/nilin/opex/market/ports/postgres/model/TradeTickerData.kt index b15855541..8aacaf9b5 100644 --- a/api/api-ports/api-persister-postgres/src/main/kotlin/co/nilin/opex/api/ports/postgres/model/TradeTickerData.kt +++ b/market/market-ports/market-persister-postgres/src/main/kotlin/co/nilin/opex/market/ports/postgres/model/TradeTickerData.kt @@ -1,4 +1,4 @@ -package co.nilin.opex.api.ports.postgres.model +package co.nilin.opex.market.ports.postgres.model import org.springframework.data.relational.core.mapping.Column import java.math.BigDecimal diff --git a/market/market-ports/market-persister-postgres/src/main/kotlin/co/nilin/opex/market/ports/postgres/util/DTOExtensions.kt b/market/market-ports/market-persister-postgres/src/main/kotlin/co/nilin/opex/market/ports/postgres/util/DTOExtensions.kt new file mode 100644 index 000000000..b28ac6d54 --- /dev/null +++ b/market/market-ports/market-persister-postgres/src/main/kotlin/co/nilin/opex/market/ports/postgres/util/DTOExtensions.kt @@ -0,0 +1,31 @@ +package co.nilin.opex.market.ports.postgres.util + +import co.nilin.opex.market.core.inout.Order +import co.nilin.opex.market.core.inout.OrderStatus +import co.nilin.opex.market.ports.postgres.model.OrderModel +import co.nilin.opex.market.ports.postgres.model.OrderStatusModel + +fun OrderModel.asOrderDTO(status: OrderStatusModel?) = Order( + id!!, + ouid, + uuid, + clientOrderId, + symbol, + orderId, + makerFee!!, + takerFee!!, + leftSideFraction!!, + rightSideFraction!!, + userLevel!!, + direction!!, + constraint!!, + type!!, + price!!, + quantity!!, + quoteQuantity!!, + status?.executedQuantity!!, + status.accumulativeQuoteQty!!, + OrderStatus.fromCode(status.status)!!, + createDate!!, + updateDate +) \ No newline at end of file diff --git a/api/api-ports/api-persister-postgres/src/main/kotlin/co/nilin/opex/api/ports/postgres/util/EnumExtensions.kt b/market/market-ports/market-persister-postgres/src/main/kotlin/co/nilin/opex/market/ports/postgres/util/EnumExtensions.kt similarity index 67% rename from api/api-ports/api-persister-postgres/src/main/kotlin/co/nilin/opex/api/ports/postgres/util/EnumExtensions.kt rename to market/market-ports/market-persister-postgres/src/main/kotlin/co/nilin/opex/market/ports/postgres/util/EnumExtensions.kt index 8ba016a4f..74d0f276e 100644 --- a/api/api-ports/api-persister-postgres/src/main/kotlin/co/nilin/opex/api/ports/postgres/util/EnumExtensions.kt +++ b/market/market-ports/market-persister-postgres/src/main/kotlin/co/nilin/opex/market/ports/postgres/util/EnumExtensions.kt @@ -1,6 +1,6 @@ -package co.nilin.opex.api.ports.postgres.util +package co.nilin.opex.market.ports.postgres.util -import co.nilin.opex.api.core.inout.* +import co.nilin.opex.market.core.inout.* fun MatchConstraint.toTimeInForce(): TimeInForce { if (this == MatchConstraint.FOK_BUDGET) @@ -15,14 +15,6 @@ fun TimeInForce.toMatchConstraint(): MatchConstraint { return MatchConstraint.valueOf(this.name) } -fun MatchingOrderType.toApiOrderType(): OrderType { - if (this == MatchingOrderType.LIMIT_ORDER) - return OrderType.LIMIT - if (this == MatchingOrderType.MARKET_ORDER) - return OrderType.MARKET - throw IllegalArgumentException("OrderType $this is not supported!") -} - fun OrderDirection.toOrderSide(): OrderSide { if (this == OrderDirection.BID) return OrderSide.BUY diff --git a/market/market-ports/market-persister-postgres/src/main/resources/schema.sql b/market/market-ports/market-persister-postgres/src/main/resources/schema.sql new file mode 100644 index 000000000..dd12ca995 --- /dev/null +++ b/market/market-ports/market-persister-postgres/src/main/resources/schema.sql @@ -0,0 +1,94 @@ +CREATE TABLE IF NOT EXISTS orders +( + id SERIAL PRIMARY KEY, + ouid VARCHAR(72) NOT NULL UNIQUE, + uuid VARCHAR(72) NOT NULL, + client_order_id VARCHAR(72), + symbol VARCHAR(20) NOT NULL, + order_id INTEGER, + maker_fee DECIMAL, + taker_fee DECIMAL, + left_side_fraction DECIMAL, + right_side_fraction DECIMAL, + user_level VARCHAR(20), + side VARCHAR(20), + match_constraint VARCHAR(20), + order_type VARCHAR(20), + price DECIMAL, + quantity DECIMAL, + quote_quantity DECIMAL, + create_date TIMESTAMP, + update_date TIMESTAMP NOT NULL, + version INTEGER +); + +CREATE TABLE IF NOT EXISTS order_status +( + id SERIAL PRIMARY KEY, + ouid VARCHAR(72) NOT NULL, + executed_quantity DECIMAL, + accumulative_quote_qty DECIMAL, + status INTEGER NOT NULL, + appearance INTEGER NOT NULL, + date TIMESTAMP NOT NULL, + UNIQUE (ouid, status, appearance, executed_quantity) +); + +CREATE TABLE IF NOT EXISTS trades +( + id SERIAL PRIMARY KEY, + trade_id INTEGER NOT NULL, + symbol VARCHAR(20) NOT NULL, + base_asset VARCHAR(20) NOT NULL, + quote_asset VARCHAR(20) NOT NULL, + matched_price DECIMAL NOT NULL, + matched_quantity DECIMAL NOT NULL, + taker_price DECIMAL NOT NULL, + maker_price DECIMAL NOT NULL, + taker_commission DECIMAL, + maker_commission DECIMAL, + taker_commission_asset VARCHAR(20), + maker_commission_asset VARCHAR(20), + trade_date TIMESTAMP NOT NULL, + maker_ouid VARCHAR(72) NOT NULL, + taker_ouid VARCHAR(72) NOT NULL, + maker_uuid VARCHAR(72) NOT NULL, + taker_uuid VARCHAR(72) NOT NULL, + create_date TIMESTAMP +); + +CREATE TABLE IF NOT EXISTS currency_rate +( + id SERIAL PRIMARY KEY, + base VARCHAR(25) NOT NULL, + quote VARCHAR(25) NOT NULL, + source VARCHAR(25) NOT NULL, + rate DECIMAL NOT NULL, + UNIQUE (base, quote, source) +); + +CREATE OR REPLACE FUNCTION interval_generator( + start_ts TIMESTAMP without TIME ZONE, + end_ts TIMESTAMP without TIME ZONE, + round_interval INTERVAL +) + RETURNS TABLE + ( + start_time TIMESTAMP without TIME ZONE, + end_time TIMESTAMP without TIME ZONE + ) +as +$$ +BEGIN + RETURN QUERY + SELECT (n) start_time, + (n + round_interval) end_time + FROM generate_series( + date_trunc('minute', start_ts), + end_ts, + round_interval + ) n; +END; + +$$ LANGUAGE 'plpgsql'; + diff --git a/api/api-ports/api-persister-postgres/src/test/kotlin/co/nilin/opex/api/ports/postgres/impl/MarketQueryHandlerTest.kt b/market/market-ports/market-persister-postgres/src/test/kotlin/co/nilin/opex/market/ports/postgres/impl/MarketQueryHandlerTest.kt similarity index 82% rename from api/api-ports/api-persister-postgres/src/test/kotlin/co/nilin/opex/api/ports/postgres/impl/MarketQueryHandlerTest.kt rename to market/market-ports/market-persister-postgres/src/test/kotlin/co/nilin/opex/market/ports/postgres/impl/MarketQueryHandlerTest.kt index a715d759e..3cb846e7b 100644 --- a/api/api-ports/api-persister-postgres/src/test/kotlin/co/nilin/opex/api/ports/postgres/impl/MarketQueryHandlerTest.kt +++ b/market/market-ports/market-persister-postgres/src/test/kotlin/co/nilin/opex/market/ports/postgres/impl/MarketQueryHandlerTest.kt @@ -1,17 +1,13 @@ -package co.nilin.opex.api.ports.postgres.impl - -import co.nilin.opex.api.core.inout.OrderDirection -import co.nilin.opex.api.core.inout.OrderStatus -import co.nilin.opex.api.core.spi.SymbolMapper -import co.nilin.opex.api.ports.postgres.dao.OrderRepository -import co.nilin.opex.api.ports.postgres.dao.OrderStatusRepository -import co.nilin.opex.api.ports.postgres.dao.TradeRepository -import co.nilin.opex.api.ports.postgres.impl.sample.VALID -import io.mockk.coEvery +package co.nilin.opex.market.ports.postgres.impl + +import co.nilin.opex.market.core.inout.OrderDirection +import co.nilin.opex.market.core.inout.OrderStatus +import co.nilin.opex.market.ports.postgres.dao.OrderRepository +import co.nilin.opex.market.ports.postgres.dao.OrderStatusRepository +import co.nilin.opex.market.ports.postgres.dao.TradeRepository +import co.nilin.opex.market.ports.postgres.impl.sample.VALID import io.mockk.every import io.mockk.mockk -import kotlinx.coroutines.flow.count -import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.flow import kotlinx.coroutines.runBlocking import org.assertj.core.api.Assertions.assertThat @@ -23,9 +19,7 @@ class MarketQueryHandlerTest { private val orderRepository: OrderRepository = mockk() private val tradeRepository: TradeRepository = mockk() private val orderStatusRepository: OrderStatusRepository = mockk() - private val symbolMapper: SymbolMapper = mockk() - private val marketQueryHandler = - MarketQueryHandlerImpl(orderRepository, tradeRepository, orderStatusRepository, symbolMapper) + private val marketQueryHandler = MarketQueryHandlerImpl(orderRepository, tradeRepository, orderStatusRepository) @Test fun givenAggregatedOrderPrice_whenOpenASKOrders_thenReturnOrderBookResponseList(): Unit = runBlocking { @@ -75,7 +69,7 @@ class MarketQueryHandlerTest { val queryOrderResponse = marketQueryHandler.lastOrder(VALID.ETH_USDT) assertThat(queryOrderResponse).isNotNull - assertThat(queryOrderResponse).isEqualTo(VALID.MAKER_QUERY_ORDER_RESPONSE) + assertThat(queryOrderResponse).isEqualTo(VALID.MAKER_ORDER) } @Test @@ -89,15 +83,12 @@ class MarketQueryHandlerTest { every { orderRepository.findByOuid(VALID.MAKER_ORDER_MODEL.ouid) } returns Mono.just(VALID.MAKER_ORDER_MODEL) - coEvery { - symbolMapper.map(VALID.ETH_USDT) - } returns "ETHUSDT" val priceTickerResponse = marketQueryHandler.lastPrice(VALID.ETH_USDT) assertThat(priceTickerResponse).isNotNull assertThat(priceTickerResponse.size).isEqualTo(1) - assertThat(priceTickerResponse.first().symbol).isEqualTo("ETHUSDT") + assertThat(priceTickerResponse.first().symbol).isEqualTo("ETH_USDT") assertThat(priceTickerResponse.first().price).isEqualTo(VALID.TRADE_MODEL.let { it.makerPrice.min(it.takerPrice) }.toString()) } diff --git a/api/api-ports/api-persister-postgres/src/test/kotlin/co/nilin/opex/api/ports/postgres/impl/OrderPersisterTest.kt b/market/market-ports/market-persister-postgres/src/test/kotlin/co/nilin/opex/market/ports/postgres/impl/OrderPersisterTest.kt similarity index 83% rename from api/api-ports/api-persister-postgres/src/test/kotlin/co/nilin/opex/api/ports/postgres/impl/OrderPersisterTest.kt rename to market/market-ports/market-persister-postgres/src/test/kotlin/co/nilin/opex/market/ports/postgres/impl/OrderPersisterTest.kt index d06ec6834..55909bf19 100644 --- a/api/api-ports/api-persister-postgres/src/test/kotlin/co/nilin/opex/api/ports/postgres/impl/OrderPersisterTest.kt +++ b/market/market-ports/market-persister-postgres/src/test/kotlin/co/nilin/opex/market/ports/postgres/impl/OrderPersisterTest.kt @@ -1,8 +1,8 @@ -package co.nilin.opex.api.ports.postgres.impl +package co.nilin.opex.market.ports.postgres.impl -import co.nilin.opex.api.ports.postgres.dao.OrderRepository -import co.nilin.opex.api.ports.postgres.dao.OrderStatusRepository -import co.nilin.opex.api.ports.postgres.impl.sample.VALID +import co.nilin.opex.market.ports.postgres.dao.OrderRepository +import co.nilin.opex.market.ports.postgres.dao.OrderStatusRepository +import co.nilin.opex.market.ports.postgres.impl.sample.VALID import io.mockk.every import io.mockk.mockk import kotlinx.coroutines.runBlocking diff --git a/market/market-ports/market-persister-postgres/src/test/kotlin/co/nilin/opex/market/ports/postgres/impl/TradePersisterTest.kt b/market/market-ports/market-persister-postgres/src/test/kotlin/co/nilin/opex/market/ports/postgres/impl/TradePersisterTest.kt new file mode 100644 index 000000000..452ae0834 --- /dev/null +++ b/market/market-ports/market-persister-postgres/src/test/kotlin/co/nilin/opex/market/ports/postgres/impl/TradePersisterTest.kt @@ -0,0 +1,26 @@ +package co.nilin.opex.market.ports.postgres.impl + +import co.nilin.opex.market.ports.postgres.dao.CurrencyRateRepository +import co.nilin.opex.market.ports.postgres.dao.TradeRepository +import co.nilin.opex.market.ports.postgres.impl.sample.VALID +import io.mockk.every +import io.mockk.mockk +import kotlinx.coroutines.runBlocking +import org.assertj.core.api.Assertions.assertThatNoException +import org.junit.jupiter.api.Test +import reactor.core.publisher.Mono + +class TradePersisterTest { + + private val tradeRepository = mockk() + private val currencyRateRepository = mockk() + private val tradePersister = TradePersisterImpl(tradeRepository, currencyRateRepository) + + @Test + fun givenTradeRepo_whenSaveRichTrade_thenSuccess(): Unit = runBlocking { + every { tradeRepository.save(any()) } returns Mono.just(VALID.TRADE_MODEL) + every { currencyRateRepository.createOrUpdate(any(), any(), any(), any()) } returns Mono.empty() + + assertThatNoException().isThrownBy { runBlocking { tradePersister.save(VALID.RICH_TRADE) } } + } +} diff --git a/api/api-ports/api-persister-postgres/src/test/kotlin/co/nilin/opex/api/ports/postgres/impl/UserQueryHandlerTest.kt b/market/market-ports/market-persister-postgres/src/test/kotlin/co/nilin/opex/market/ports/postgres/impl/UserQueryHandlerTest.kt similarity index 88% rename from api/api-ports/api-persister-postgres/src/test/kotlin/co/nilin/opex/api/ports/postgres/impl/UserQueryHandlerTest.kt rename to market/market-ports/market-persister-postgres/src/test/kotlin/co/nilin/opex/market/ports/postgres/impl/UserQueryHandlerTest.kt index 15274da3f..7cbe2b398 100644 --- a/api/api-ports/api-persister-postgres/src/test/kotlin/co/nilin/opex/api/ports/postgres/impl/UserQueryHandlerTest.kt +++ b/market/market-ports/market-persister-postgres/src/test/kotlin/co/nilin/opex/market/ports/postgres/impl/UserQueryHandlerTest.kt @@ -1,10 +1,10 @@ -package co.nilin.opex.api.ports.postgres.impl +package co.nilin.opex.market.ports.postgres.impl -import co.nilin.opex.api.core.inout.OrderStatus -import co.nilin.opex.api.ports.postgres.dao.OrderRepository -import co.nilin.opex.api.ports.postgres.dao.OrderStatusRepository -import co.nilin.opex.api.ports.postgres.dao.TradeRepository -import co.nilin.opex.api.ports.postgres.impl.sample.VALID +import co.nilin.opex.market.core.inout.OrderStatus +import co.nilin.opex.market.ports.postgres.dao.OrderRepository +import co.nilin.opex.market.ports.postgres.dao.OrderStatusRepository +import co.nilin.opex.market.ports.postgres.dao.TradeRepository +import co.nilin.opex.market.ports.postgres.impl.sample.VALID import io.mockk.every import io.mockk.mockk import kotlinx.coroutines.flow.count @@ -37,11 +37,11 @@ class UserQueryHandlerTest { orderStatusRepository.findMostRecentByOUID(VALID.MAKER_ORDER_MODEL.ouid) } returns Mono.just(VALID.MAKER_ORDER_STATUS_MODEL) - val queryOrderResponses = userQueryHandler.allOrders(VALID.PRINCIPAL, VALID.ALL_ORDER_REQUEST) + val queryOrderResponses = userQueryHandler.allOrders(VALID.PRINCIPAL.name, VALID.ALL_ORDER_REQUEST) assertThat(queryOrderResponses).isNotNull assertThat(queryOrderResponses.count()).isEqualTo(1) - assertThat(queryOrderResponses.first()).isEqualTo(VALID.MAKER_QUERY_ORDER_RESPONSE) + assertThat(queryOrderResponses.first()).isEqualTo(VALID.MAKER_ORDER) } @Test @@ -64,7 +64,7 @@ class UserQueryHandlerTest { orderRepository.findByOuid(VALID.TRADE_MODEL.takerOuid) } returns Mono.just(VALID.TAKER_ORDER_MODEL) - val tradeResponses = userQueryHandler.allTrades(VALID.PRINCIPAL, VALID.TRADE_REQUEST) + val tradeResponses = userQueryHandler.allTrades(VALID.PRINCIPAL.name, VALID.TRADE_REQUEST) assertThat(tradeResponses).isNotNull assertThat(tradeResponses.count()).isEqualTo(1) @@ -85,7 +85,7 @@ class UserQueryHandlerTest { orderStatusRepository.findMostRecentByOUID(VALID.MAKER_ORDER_MODEL.ouid) } returns Mono.just(VALID.MAKER_ORDER_STATUS_MODEL) - val queryOrderResponses = userQueryHandler.openOrders(VALID.PRINCIPAL, VALID.ETH_USDT) + val queryOrderResponses = userQueryHandler.openOrders(VALID.PRINCIPAL.name, VALID.ETH_USDT) assertThat(queryOrderResponses).isNotNull assertThat(queryOrderResponses.count()).isEqualTo(1) @@ -103,7 +103,7 @@ class UserQueryHandlerTest { orderStatusRepository.findMostRecentByOUID(VALID.MAKER_ORDER_MODEL.ouid) } returns Mono.just(VALID.MAKER_ORDER_STATUS_MODEL) - val queryOrderResponse = userQueryHandler.queryOrder(VALID.PRINCIPAL, VALID.QUERY_ORDER_REQUEST) + val queryOrderResponse = userQueryHandler.queryOrder(VALID.PRINCIPAL.name, VALID.QUERY_ORDER_REQUEST) assertThat(queryOrderResponse).isNotNull } diff --git a/market/market-ports/market-persister-postgres/src/test/kotlin/co/nilin/opex/market/ports/postgres/impl/sample/Samples.kt b/market/market-ports/market-persister-postgres/src/test/kotlin/co/nilin/opex/market/ports/postgres/impl/sample/Samples.kt new file mode 100644 index 000000000..e460fbe28 --- /dev/null +++ b/market/market-ports/market-persister-postgres/src/test/kotlin/co/nilin/opex/market/ports/postgres/impl/sample/Samples.kt @@ -0,0 +1,211 @@ +package co.nilin.opex.market.ports.postgres.impl.sample + +import co.nilin.opex.market.core.event.RichOrder +import co.nilin.opex.market.core.event.RichOrderUpdate +import co.nilin.opex.market.core.event.RichTrade +import co.nilin.opex.market.core.inout.* +import co.nilin.opex.market.ports.postgres.model.OrderModel +import co.nilin.opex.market.ports.postgres.model.OrderStatusModel +import co.nilin.opex.market.ports.postgres.model.TradeModel +import co.nilin.opex.market.ports.postgres.util.isWorking +import java.math.BigDecimal +import java.security.Principal +import java.time.LocalDateTime +import java.time.ZoneId +import java.time.ZoneOffset +import java.util.* + +object VALID { + + private const val USER_LEVEL_REGISTERED = "registered" + private const val TIMESTAMP = 1653125840L + private val CREATE_DATE: LocalDateTime = LocalDateTime.ofEpochSecond(TIMESTAMP, 0, ZoneOffset.UTC) + private val UPDATE_DATE: LocalDateTime = LocalDateTime.ofEpochSecond(TIMESTAMP + 180, 0, ZoneOffset.UTC) + private val FROM_DATE: LocalDateTime = LocalDateTime.ofEpochSecond(TIMESTAMP - 600, 0, ZoneOffset.UTC) + private val TO_DATE: LocalDateTime = LocalDateTime.ofEpochSecond(TIMESTAMP + 600, 0, ZoneOffset.UTC) + + const val ETH_USDT = "ETH_USDT" + + val PRINCIPAL = Principal { "98c7ca9b-2d9c-46dd-afa8-b0cd2f52a97c" } + + val MAKER_ORDER_MODEL = OrderModel( + 1, + "f1167d30-ccc0-4f86-ab5d-dd24aa3250df", + PRINCIPAL.name, + null, // Binance + ETH_USDT, + 1, // MatchingEngine ID + BigDecimal.valueOf(0.01), // Calculated? + BigDecimal.valueOf(0.01), // Calculated? + BigDecimal.valueOf(0.0001), + BigDecimal.valueOf(0.01), + USER_LEVEL_REGISTERED, + OrderDirection.ASK, + MatchConstraint.GTC, + MatchingOrderType.LIMIT_ORDER, + BigDecimal.valueOf(100000), + BigDecimal.valueOf(0.001), + BigDecimal.valueOf(100).stripTrailingZeros(), + CREATE_DATE, + UPDATE_DATE + ) + + val MAKER_ORDER = Order( + 1, + MAKER_ORDER_MODEL.ouid, + PRINCIPAL.name, + null, + ETH_USDT, + 1, + BigDecimal.valueOf(0.01), + BigDecimal.valueOf(0.01), // Calculated? + BigDecimal.valueOf(0.0001), + BigDecimal.valueOf(0.01), + USER_LEVEL_REGISTERED, + OrderDirection.ASK, + MatchConstraint.GTC, + MatchingOrderType.LIMIT_ORDER, + BigDecimal.valueOf(100000), + BigDecimal.valueOf(0.001), + BigDecimal.valueOf(100).stripTrailingZeros(), + BigDecimal.valueOf(0), + BigDecimal.valueOf(0), + OrderStatus.FILLED, + CREATE_DATE, + UPDATE_DATE + ) + + val TAKER_ORDER_MODEL = MAKER_ORDER_MODEL.copy(2, "157b9b4a-cc66-43b9-b30b-40a8b66ea6aa") + + val MAKER_ORDER_STATUS_MODEL = OrderStatusModel( + MAKER_ORDER_MODEL.ouid, + BigDecimal.valueOf(0), // Filled amount + BigDecimal.valueOf(0), // --> See accountant + OrderStatus.FILLED.code, + OrderStatus.FILLED.orderOfAppearance, + CREATE_DATE + ) + + val TRADE_MODEL = TradeModel( + 1, + 1, + ETH_USDT, + "ETH", + "USDT", + BigDecimal.valueOf(100000), + BigDecimal.valueOf(0.001), // Minimum of orders quantities + BigDecimal.valueOf(100000), + BigDecimal.valueOf(100000), + BigDecimal.valueOf(0.001), // Calculated + BigDecimal.valueOf(0.001), // Calculated + "ETH", + "USDT", + CREATE_DATE, + MAKER_ORDER_MODEL.ouid, + TAKER_ORDER_MODEL.ouid, + PRINCIPAL.name, + PRINCIPAL.name, + UPDATE_DATE + ) + + val AGGREGATED_ORDER_PRICE_MODEL = AggregatedOrderPriceModel( + BigDecimal.valueOf(100000), + BigDecimal.valueOf(0.001) + ) + + val ORDER_BOOK_RESPONSE = OrderBook( + AGGREGATED_ORDER_PRICE_MODEL.price!!, + AGGREGATED_ORDER_PRICE_MODEL.quantity!! + ) + + val RICH_ORDER = RichOrder( + null, + ETH_USDT, + MAKER_ORDER_MODEL.ouid, + PRINCIPAL.name, + USER_LEVEL_REGISTERED, + BigDecimal.valueOf(0.01), + BigDecimal.valueOf(0.01), + BigDecimal.valueOf(0.0001), + BigDecimal.valueOf(0.01), + OrderDirection.ASK, + MatchConstraint.GTC, + MatchingOrderType.LIMIT_ORDER, + BigDecimal.valueOf(1000001), + BigDecimal.valueOf(0.01), + BigDecimal.valueOf(0), + BigDecimal.valueOf(0), + BigDecimal.valueOf(0), + 0 + ) + + val RICH_ORDER_UPDATE = RichOrderUpdate( + MAKER_ORDER_MODEL.ouid, + BigDecimal.valueOf(1000001), + BigDecimal.valueOf(0.01), + BigDecimal.valueOf(0.08), + OrderStatus.PARTIALLY_FILLED + ) + + val RICH_TRADE = RichTrade( + 1, + ETH_USDT, + MAKER_ORDER_MODEL.ouid, + PRINCIPAL.name, + 1, + OrderDirection.ASK, + BigDecimal.valueOf(100000), + BigDecimal.valueOf(0.01), + BigDecimal.valueOf(0), + BigDecimal.valueOf(0), + BigDecimal.valueOf(0), + "ETH", + TAKER_ORDER_MODEL.ouid, + PRINCIPAL.name, + 2, + OrderDirection.ASK, + BigDecimal.valueOf(100000), + BigDecimal.valueOf(0.01), + BigDecimal.valueOf(0), + BigDecimal.valueOf(0), + BigDecimal.valueOf(0), + "USDT", + BigDecimal.valueOf(0), + BigDecimal.valueOf(0), + CREATE_DATE + ) + + val ALL_ORDER_REQUEST = AllOrderRequest( + ETH_USDT, + Date.from(FROM_DATE.toInstant(ZoneOffset.UTC)), + Date.from(TO_DATE.toInstant(ZoneOffset.UTC)), + 500 + ) + + val TRADE_REQUEST = TradeRequest( + ETH_USDT, + 1, + Date.from(FROM_DATE.toInstant(ZoneOffset.UTC)), + Date.from(TO_DATE.toInstant(ZoneOffset.UTC)), + 500 + ) + + val MARKET_TRADE_RESPONSE = MarketTrade( + ETH_USDT, + "ETH", + "USDT", + 1, + BigDecimal.valueOf(100000), + BigDecimal.valueOf(0.001), + BigDecimal.valueOf(100000 * 0.001).stripTrailingZeros(), + Date.from(UPDATE_DATE.atZone(ZoneId.systemDefault()).toInstant()), + true, + MAKER_ORDER_MODEL.direction == OrderDirection.BID + ) + + val QUERY_ORDER_REQUEST = QueryOrderRequest( + ETH_USDT, + 1, + "2" + ) +} diff --git a/market/pom.xml b/market/pom.xml new file mode 100644 index 000000000..f81d36072 --- /dev/null +++ b/market/pom.xml @@ -0,0 +1,83 @@ + + + 4.0.0 + + + core + co.nilin.opex + 1.0.0-beta.3 + + + co.nilin.opex.market + market + market + pom + Market root of Opex + + + market-app + market-core + market-ports/market-eventlistener-kafka + market-ports/market-persister-postgres + + + + + org.springframework.boot + spring-boot-starter-test + + + + + + + co.nilin.opex.market.core + market-core + ${project.version} + + + co.nilin.opex.market.ports.kafka.listener + market-eventlistener-kafka + ${project.version} + + + co.nilin.opex.market.ports.binance + market-binance-rest + ${project.version} + + + co.nilin.opex.market.ports.postgres + market-persister-postgres + ${project.version} + + + co.nilin.opex.utility.error + error-handler + ${project.version} + + + co.nilin.opex.utility.log + logging-handler + ${project.version} + + + co.nilin.opex.utility.interceptors + interceptors + ${project.version} + + + co.nilin.opex.utility.preferences + preferences + ${project.version} + + + org.springframework.cloud + spring-cloud-dependencies + ${spring-cloud.version} + pom + import + + + + diff --git a/pom.xml b/pom.xml index f8d15c0b6..4db68166a 100644 --- a/pom.xml +++ b/pom.xml @@ -24,6 +24,7 @@ api bc-gateway eventlog + market matching-engine matching-gateway storage diff --git a/utility/error-handler/src/main/kotlin/co/nilin/opex/utility/error/data/OpexError.kt b/utility/error-handler/src/main/kotlin/co/nilin/opex/utility/error/data/OpexError.kt index 697fcbbce..56f628464 100644 --- a/utility/error-handler/src/main/kotlin/co/nilin/opex/utility/error/data/OpexError.kt +++ b/utility/error-handler/src/main/kotlin/co/nilin/opex/utility/error/data/OpexError.kt @@ -14,10 +14,12 @@ enum class OpexError(val code: Int, val message: String?, val status: HttpStatus ServiceUnavailable(1006, null, HttpStatus.SERVICE_UNAVAILABLE), InvalidRequestParam(1020, "Parameter '%s' is either missing or invalid", HttpStatus.BAD_REQUEST), InvalidRequestBody(1021, "Request body is invalid", HttpStatus.BAD_REQUEST), + NoRecordFound(1022,"No record found for this service",HttpStatus.NOT_FOUND), // code 2000: accountant InvalidPair(2001, "%s is not available", HttpStatus.BAD_REQUEST), InvalidPairFee(2002, "%s fee is not available", HttpStatus.BAD_REQUEST), + PairFeeNotFound(2002, "No fee for requested pair found", HttpStatus.NOT_FOUND), // code 3000: matching-engine @@ -63,7 +65,13 @@ enum class OpexError(val code: Int, val message: String?, val status: HttpStatus UserNotFoundAdmin(9001, "User not found", HttpStatus.NOT_FOUND), // code 10000: bc-gateway - InvalidCaptcha(10001, "Captcha is not valid", HttpStatus.BAD_REQUEST); + InvalidCaptcha(10001, "Captcha is not valid", HttpStatus.BAD_REQUEST), + + // code 11000: market + PriceChangeNotFound(11001, "Price change for requested symbol not found", HttpStatus.NOT_FOUND), + LastOrderNotFound(11002, "Last order for symbol not found", HttpStatus.NOT_FOUND), + + ; companion object { fun findByCode(code: Int?): OpexError? { diff --git a/wallet/wallet-app/src/main/kotlin/co/nilin/opex/wallet/app/controller/BalanceController.kt b/wallet/wallet-app/src/main/kotlin/co/nilin/opex/wallet/app/controller/BalanceController.kt index 62af1a471..39c26a5eb 100644 --- a/wallet/wallet-app/src/main/kotlin/co/nilin/opex/wallet/app/controller/BalanceController.kt +++ b/wallet/wallet-app/src/main/kotlin/co/nilin/opex/wallet/app/controller/BalanceController.kt @@ -19,7 +19,8 @@ import java.security.Principal class BalanceController( val walletManager: WalletManager, val walletOwnerManager: WalletOwnerManager, val currencyService: CurrencyService ) { - val logger = LoggerFactory.getLogger(BalanceController::class.java) + + private val logger = LoggerFactory.getLogger(BalanceController::class.java) data class BalanceResponse(val balance: BigDecimal) diff --git a/wallet/wallet-app/src/main/kotlin/co/nilin/opex/wallet/app/controller/WalletOwnerController.kt b/wallet/wallet-app/src/main/kotlin/co/nilin/opex/wallet/app/controller/WalletOwnerController.kt index 8e6372484..fbfd58095 100644 --- a/wallet/wallet-app/src/main/kotlin/co/nilin/opex/wallet/app/controller/WalletOwnerController.kt +++ b/wallet/wallet-app/src/main/kotlin/co/nilin/opex/wallet/app/controller/WalletOwnerController.kt @@ -2,35 +2,44 @@ package co.nilin.opex.wallet.app.controller import co.nilin.opex.utility.error.data.OpexError import co.nilin.opex.utility.error.data.OpexException +import co.nilin.opex.wallet.app.dto.OwnerLimitsResponse +import co.nilin.opex.wallet.app.dto.WalletData +import co.nilin.opex.wallet.app.utils.BalanceParser import co.nilin.opex.wallet.core.spi.WalletManager import co.nilin.opex.wallet.core.spi.WalletOwnerManager import io.swagger.annotations.ApiResponse import io.swagger.annotations.Example import io.swagger.annotations.ExampleProperty import org.springframework.web.bind.annotation.GetMapping +import org.springframework.web.bind.annotation.PathVariable +import org.springframework.web.bind.annotation.RequestMapping import org.springframework.web.bind.annotation.RestController -import java.math.BigDecimal -import java.security.Principal @RestController +@RequestMapping("/v1/owner") class WalletOwnerController( - val walletManager: WalletManager, - val walletOwnerManager: WalletOwnerManager + private val walletOwnerManager: WalletOwnerManager, + private val walletManager: WalletManager ) { - data class WalletData( - val asset: String, - val balance: BigDecimal, - val type: String - ) - - data class OwnerLimitsResponse( - val canTrade: Boolean, - val canWithdraw: Boolean, - val canDeposit: Boolean + @GetMapping("/{uuid}/wallets") + @ApiResponse( + message = "OK", + code = 200, + examples = Example( + ExampleProperty( + value = "{ }", + mediaType = "application/json" + ) + ) ) + suspend fun getAllWallets(@PathVariable uuid: String): List { + val owner = walletOwnerManager.findWalletOwner(uuid) ?: throw OpexException(OpexError.WalletOwnerNotFound) + val wallets = walletManager.findWalletsByOwner(owner) + return BalanceParser.parse(wallets) + } - @GetMapping("/owner/wallet/all") + @GetMapping("/{uuid}/wallets/{symbol}") @ApiResponse( message = "OK", code = 200, @@ -41,16 +50,13 @@ class WalletOwnerController( ) ) ) - suspend fun getAllWallets(principal: Principal): List { - val owner = walletOwnerManager.findWalletOwner(principal.name) - ?: throw OpexException(OpexError.WalletOwnerNotFound) - val wallets = walletManager.findWalletsByOwner(owner) - return wallets.map { - WalletData(it.currency.symbol, it.balance.amount, it.type) - } + suspend fun getWallet(@PathVariable uuid: String, @PathVariable symbol: String): WalletData { + val owner = walletOwnerManager.findWalletOwner(uuid) ?: throw OpexException(OpexError.WalletOwnerNotFound) + val wallets = walletManager.findWalletByOwnerAndSymbol(owner, symbol) + return BalanceParser.parseSingleCurrency(wallets) ?: throw OpexException(OpexError.WalletNotFound) } - @GetMapping("/owner/limits") + @GetMapping("/{uuid}/limits") @ApiResponse( message = "OK", code = 200, @@ -61,9 +67,8 @@ class WalletOwnerController( ) ) ) - suspend fun getWalletOwnerLimits(principal: Principal): OwnerLimitsResponse { - val owner = walletOwnerManager.findWalletOwner(principal.name) - ?: throw OpexException(OpexError.WalletOwnerNotFound) + suspend fun getWalletOwnerLimits(@PathVariable uuid: String): OwnerLimitsResponse { + val owner = walletOwnerManager.findWalletOwner(uuid) ?: throw OpexException(OpexError.WalletOwnerNotFound) return OwnerLimitsResponse(owner.isTradeAllowed, owner.isWithdrawAllowed, owner.isDepositAllowed) } } \ No newline at end of file diff --git a/wallet/wallet-app/src/main/kotlin/co/nilin/opex/wallet/app/dto/OwnerLimitsResponse.kt b/wallet/wallet-app/src/main/kotlin/co/nilin/opex/wallet/app/dto/OwnerLimitsResponse.kt new file mode 100644 index 000000000..e7c5a21b1 --- /dev/null +++ b/wallet/wallet-app/src/main/kotlin/co/nilin/opex/wallet/app/dto/OwnerLimitsResponse.kt @@ -0,0 +1,7 @@ +package co.nilin.opex.wallet.app.dto + +data class OwnerLimitsResponse( + val canTrade: Boolean, + val canWithdraw: Boolean, + val canDeposit: Boolean +) diff --git a/wallet/wallet-app/src/main/kotlin/co/nilin/opex/wallet/app/dto/WalletData.kt b/wallet/wallet-app/src/main/kotlin/co/nilin/opex/wallet/app/dto/WalletData.kt new file mode 100644 index 000000000..f8827c747 --- /dev/null +++ b/wallet/wallet-app/src/main/kotlin/co/nilin/opex/wallet/app/dto/WalletData.kt @@ -0,0 +1,10 @@ +package co.nilin.opex.wallet.app.dto + +import java.math.BigDecimal + +data class WalletData( + val asset: String, + var balance: BigDecimal, + var locked: BigDecimal, + var withdraw: BigDecimal +) diff --git a/wallet/wallet-app/src/main/kotlin/co/nilin/opex/wallet/app/utils/BalanceParser.kt b/wallet/wallet-app/src/main/kotlin/co/nilin/opex/wallet/app/utils/BalanceParser.kt new file mode 100644 index 000000000..12a234949 --- /dev/null +++ b/wallet/wallet-app/src/main/kotlin/co/nilin/opex/wallet/app/utils/BalanceParser.kt @@ -0,0 +1,51 @@ +package co.nilin.opex.wallet.app.utils + +import co.nilin.opex.wallet.app.dto.WalletData +import co.nilin.opex.wallet.core.model.Wallet +import java.math.BigDecimal + +object BalanceParser { + + fun parse(list: List): List { + val result = arrayListOf() + + for (w in list) { + result.addOrGet(w.currency.symbol).apply { + when (w.type) { + "main" -> balance = w.balance.amount + "exchange" -> locked = w.balance.amount + "cashout" -> withdraw = w.balance.amount + } + } + } + return result + } + + fun parseSingleCurrency(list: List): WalletData? { + if (list.isEmpty()) return null + val symbol = list[0].currency.symbol + val result = WalletData(symbol, BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO) + + for (w in list) { + if (w.currency.symbol != symbol) + throw IllegalStateException("Found multiple currencies while parsing for single") + + when (w.type) { + "main" -> result.balance = w.balance.amount + "exchange" -> result.locked = w.balance.amount + "cashout" -> result.withdraw = w.balance.amount + } + } + return result + } + + private fun ArrayList.addOrGet(symbol: String): WalletData { + for (w in this) + if (w.asset == symbol) + return w + + add(WalletData(symbol, BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO)) + return this.last() + } + +} \ No newline at end of file diff --git a/wallet/wallet-app/src/main/kotlin/co/nilin/opex/wallet/app/utils/VaultUserIdMechanism.kt b/wallet/wallet-app/src/main/kotlin/co/nilin/opex/wallet/app/utils/VaultUserIdMechanism.kt new file mode 100644 index 000000000..7f3ab0967 --- /dev/null +++ b/wallet/wallet-app/src/main/kotlin/co/nilin/opex/wallet/app/utils/VaultUserIdMechanism.kt @@ -0,0 +1,9 @@ +package co.nilin.opex.wallet.app.utils + +import org.springframework.vault.authentication.AppIdUserIdMechanism + +class VaultUserIdMechanism : AppIdUserIdMechanism { + override fun createUserId(): String { + return System.getenv("BACKEND_USER") + } +} \ No newline at end of file diff --git a/wallet/wallet-app/src/main/resources/application.yml b/wallet/wallet-app/src/main/resources/application.yml index 112eff304..87eecd1f2 100644 --- a/wallet/wallet-app/src/main/resources/application.yml +++ b/wallet/wallet-app/src/main/resources/application.yml @@ -26,7 +26,7 @@ spring: scheme: http authentication: APPID app-id: - user-id: co.nilin.opex.util.vault.VaultUserIdMechanism + user-id: co.nilin.opex.wallet.app.utils.VaultUserIdMechanism fail-fast: true kv: enabled: true diff --git a/wallet/wallet-core/src/main/kotlin/co/nilin/opex/wallet/core/spi/WalletManager.kt b/wallet/wallet-core/src/main/kotlin/co/nilin/opex/wallet/core/spi/WalletManager.kt index 97159a69f..f7b2bc430 100644 --- a/wallet/wallet-core/src/main/kotlin/co/nilin/opex/wallet/core/spi/WalletManager.kt +++ b/wallet/wallet-core/src/main/kotlin/co/nilin/opex/wallet/core/spi/WalletManager.kt @@ -7,13 +7,23 @@ import co.nilin.opex.wallet.core.model.WalletOwner import java.math.BigDecimal interface WalletManager { + suspend fun isDepositAllowed(wallet: Wallet, amount: BigDecimal): Boolean + suspend fun isWithdrawAllowed(wallet: Wallet, amount: BigDecimal): Boolean + suspend fun increaseBalance(wallet: Wallet, amount: BigDecimal) + suspend fun decreaseBalance(wallet: Wallet, amount: BigDecimal) + suspend fun findWalletByOwnerAndCurrencyAndType(owner: WalletOwner, walletType: String, currency: Currency): Wallet? + suspend fun findWalletsByOwnerAndType(owner: WalletOwner, walletType: String): List + suspend fun findWalletsByOwner(owner: WalletOwner): List + + suspend fun findWalletByOwnerAndSymbol(owner: WalletOwner, symbol: String): List + suspend fun createWallet( owner: WalletOwner, balance: Amount, diff --git a/wallet/wallet-ports/wallet-persister-postgres/src/main/kotlin/co/nilin/opex/wallet/ports/postgres/dao/WalletRepository.kt b/wallet/wallet-ports/wallet-persister-postgres/src/main/kotlin/co/nilin/opex/wallet/ports/postgres/dao/WalletRepository.kt index c164afb90..f46bb9228 100644 --- a/wallet/wallet-ports/wallet-persister-postgres/src/main/kotlin/co/nilin/opex/wallet/ports/postgres/dao/WalletRepository.kt +++ b/wallet/wallet-ports/wallet-persister-postgres/src/main/kotlin/co/nilin/opex/wallet/ports/postgres/dao/WalletRepository.kt @@ -29,6 +29,9 @@ interface WalletRepository : ReactiveCrudRepository { @Query("select * from wallet where owner = :owner") fun findByOwner(@Param("owner") owner: Long): Flux + @Query("select * from wallet where owner = :owner and currency = :currency") + fun findByOwnerAndCurrency(owner: Long, currency: String): Flux + @Modifying @Query("update wallet set balance = balance + :balance where id = :id") fun updateBalance(id: Long, delta: BigDecimal): Mono diff --git a/wallet/wallet-ports/wallet-persister-postgres/src/main/kotlin/co/nilin/opex/wallet/ports/postgres/impl/WalletManagerImpl.kt b/wallet/wallet-ports/wallet-persister-postgres/src/main/kotlin/co/nilin/opex/wallet/ports/postgres/impl/WalletManagerImpl.kt index 7ff0dedba..1979ae670 100644 --- a/wallet/wallet-ports/wallet-persister-postgres/src/main/kotlin/co/nilin/opex/wallet/ports/postgres/impl/WalletManagerImpl.kt +++ b/wallet/wallet-ports/wallet-persister-postgres/src/main/kotlin/co/nilin/opex/wallet/ports/postgres/impl/WalletManagerImpl.kt @@ -185,6 +185,23 @@ class WalletManagerImpl( } } + override suspend fun findWalletByOwnerAndSymbol(owner: WalletOwner, symbol: String): List { + val ownerModel = walletOwnerRepository.findById(owner.id!!).awaitFirst() + return walletRepository.findByOwnerAndCurrency(owner.id!!, symbol) + .collectList() + .awaitSingle() + .map { + val currency = currencyRepository.findById(it.currency).awaitFirst() + Wallet( + it.id!!, + ownerModel.toPlainObject(), + Amount(currency.toPlainObject(), it.balance), + currency.toPlainObject(), + it.type + ) + } + } + override suspend fun createWallet(owner: WalletOwner, balance: Amount, currency: Currency, type: String): Wallet { val walletModel = walletRepository .save(WalletModel(null, owner.id!!, type, currency.symbol, balance.amount)) diff --git a/websocket/websocket-app/src/main/kotlin/co/nilin/opex/websocket/app/service/MarketService.kt b/websocket/websocket-app/src/main/kotlin/co/nilin/opex/websocket/app/service/MarketService.kt index 94a37118c..56a1a10c8 100644 --- a/websocket/websocket-app/src/main/kotlin/co/nilin/opex/websocket/app/service/MarketService.kt +++ b/websocket/websocket-app/src/main/kotlin/co/nilin/opex/websocket/app/service/MarketService.kt @@ -73,7 +73,7 @@ class MarketService(private val marketQueryHandler: MarketQueryHandler) { } suspend fun getPriceOverview(symbol: String, duration: String): List { - val startDate = Interval.findByLabel(duration)?.getLocalDateTime() ?: Interval.Day.getLocalDateTime() + val startDate = Interval.findByLabel(duration)?.getLocalDateTime() ?: Interval.Week.getLocalDateTime() return listOf(marketQueryHandler.getTradeTickerDataBySymbol(symbol, startDate)) } From 0fbd35dcc12dc0b0eaf6fdc992aad6aadeb1bcb2 Mon Sep 17 00:00:00 2001 From: Peyman Date: Wed, 10 Aug 2022 15:49:22 +0430 Subject: [PATCH 09/14] Fix market exception handling issue --- .../nilin/opex/api/ports/proxy/impl/MarketDataProxyImpl.kt | 5 +++-- .../nilin/opex/market/app/controller/MarketController.kt | 7 +++---- .../kotlin/co/nilin/opex/utility/error/data/OpexError.kt | 2 -- 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/api/api-ports/api-proxy-rest/src/main/kotlin/co/nilin/opex/api/ports/proxy/impl/MarketDataProxyImpl.kt b/api/api-ports/api-proxy-rest/src/main/kotlin/co/nilin/opex/api/ports/proxy/impl/MarketDataProxyImpl.kt index b231b066a..9e3e38c8b 100644 --- a/api/api-ports/api-proxy-rest/src/main/kotlin/co/nilin/opex/api/ports/proxy/impl/MarketDataProxyImpl.kt +++ b/api/api-ports/api-proxy-rest/src/main/kotlin/co/nilin/opex/api/ports/proxy/impl/MarketDataProxyImpl.kt @@ -4,7 +4,6 @@ import co.nilin.opex.api.core.inout.* import co.nilin.opex.api.core.spi.MarketDataProxy import co.nilin.opex.api.core.utils.LoggerDelegate import kotlinx.coroutines.reactive.awaitFirstOrElse -import kotlinx.coroutines.reactor.awaitSingle import kotlinx.coroutines.reactor.awaitSingleOrNull import org.springframework.beans.factory.annotation.Value import org.springframework.http.HttpHeaders @@ -13,6 +12,7 @@ import org.springframework.stereotype.Component import org.springframework.web.reactive.function.client.WebClient import org.springframework.web.reactive.function.client.bodyToFlux import org.springframework.web.reactive.function.client.bodyToMono +import java.util.* @Component class MarketDataProxyImpl(private val webClient: WebClient) : MarketDataProxy { @@ -46,7 +46,8 @@ class MarketDataProxyImpl(private val webClient: WebClient) : MarketDataProxy { .retrieve() .onStatus({ t -> t.isError }, { it.createException() }) .bodyToMono() - .awaitSingle() + .awaitSingleOrNull() + ?: PriceChange(symbol, openTime = Date().time, closeTime = startFrom) } override suspend fun openBidOrders(symbol: String, limit: Int): List { diff --git a/market/market-app/src/main/kotlin/co/nilin/opex/market/app/controller/MarketController.kt b/market/market-app/src/main/kotlin/co/nilin/opex/market/app/controller/MarketController.kt index 3bb3fe0ed..2fa5845ea 100644 --- a/market/market-app/src/main/kotlin/co/nilin/opex/market/app/controller/MarketController.kt +++ b/market/market-app/src/main/kotlin/co/nilin/opex/market/app/controller/MarketController.kt @@ -23,9 +23,8 @@ class MarketController(private val marketQueryHandler: MarketQueryHandler) { suspend fun priceChangeForSymbolSince( @PathVariable symbol: String, @RequestParam since: Long - ): PriceChange { + ): PriceChange? { return marketQueryHandler.getTradeTickerDateBySymbol(symbol, since.asLocalDateTime()) - ?: throw OpexException(OpexError.PriceChangeNotFound) } @GetMapping("/{symbol}/order-book") @@ -49,8 +48,8 @@ class MarketController(private val marketQueryHandler: MarketQueryHandler) { } @GetMapping("/{symbol}/last-order") - suspend fun getLastOrderForSymbol(@PathVariable symbol: String): Order { - return marketQueryHandler.lastOrder(symbol) ?: throw OpexException(OpexError.LastOrderNotFound) + suspend fun getLastOrderForSymbol(@PathVariable symbol: String): Order? { + return marketQueryHandler.lastOrder(symbol) } @GetMapping("/prices") diff --git a/utility/error-handler/src/main/kotlin/co/nilin/opex/utility/error/data/OpexError.kt b/utility/error-handler/src/main/kotlin/co/nilin/opex/utility/error/data/OpexError.kt index 56f628464..96624729a 100644 --- a/utility/error-handler/src/main/kotlin/co/nilin/opex/utility/error/data/OpexError.kt +++ b/utility/error-handler/src/main/kotlin/co/nilin/opex/utility/error/data/OpexError.kt @@ -68,8 +68,6 @@ enum class OpexError(val code: Int, val message: String?, val status: HttpStatus InvalidCaptcha(10001, "Captcha is not valid", HttpStatus.BAD_REQUEST), // code 11000: market - PriceChangeNotFound(11001, "Price change for requested symbol not found", HttpStatus.NOT_FOUND), - LastOrderNotFound(11002, "Last order for symbol not found", HttpStatus.NOT_FOUND), ; From 31176049a4e7a2842aef8ca6bb00fda9256f2754 Mon Sep 17 00:00:00 2001 From: Ebrahim Hoseiny Fadae Date: Sat, 13 Aug 2022 11:32:27 +0430 Subject: [PATCH 10/14] Close #296, Change candle data order to ASC (#314) --- .../co/nilin/opex/market/ports/postgres/dao/TradeRepository.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/market/market-ports/market-persister-postgres/src/main/kotlin/co/nilin/opex/market/ports/postgres/dao/TradeRepository.kt b/market/market-ports/market-persister-postgres/src/main/kotlin/co/nilin/opex/market/ports/postgres/dao/TradeRepository.kt index bfba22dea..d2b743c07 100644 --- a/market/market-ports/market-persister-postgres/src/main/kotlin/co/nilin/opex/market/ports/postgres/dao/TradeRepository.kt +++ b/market/market-ports/market-persister-postgres/src/main/kotlin/co/nilin/opex/market/ports/postgres/dao/TradeRepository.kt @@ -250,7 +250,7 @@ interface TradeRepository : ReactiveCrudRepository { on t.create_date >= f.start_time and t.create_date < f.end_time where symbol = :symbol or symbol is null group by f.start_time, f.end_time - order by f.end_time desc + order by f.start_time asc limit :limit """ ) From 2d067729b40c2e479b66890d0d9d32f3f75ce455 Mon Sep 17 00:00:00 2001 From: Peyman Date: Sat, 13 Aug 2022 12:34:35 +0430 Subject: [PATCH 11/14] Fix MarketTrade variable name mismatch --- .../main/kotlin/co/nilin/opex/api/core/inout/MarketTrade.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/MarketTrade.kt b/api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/MarketTrade.kt index b74542850..99c74a4c5 100644 --- a/api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/MarketTrade.kt +++ b/api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/MarketTrade.kt @@ -9,8 +9,8 @@ data class MarketTrade( val quoteAsset: String, val id: Long, val price: BigDecimal, - val qty: BigDecimal, - val quoteQty: BigDecimal, + val quantity: BigDecimal, + val quoteQuantity: BigDecimal, val time: Date, val isBestMatch: Boolean, val isMakerBuyer: Boolean From aa483c5f421a1864b5f7c7d9d3c46072b4d00e23 Mon Sep 17 00:00:00 2001 From: Peyman Date: Sat, 13 Aug 2022 16:28:57 +0430 Subject: [PATCH 12/14] Fix MarketTrade variable name mismatch --- .../opex/api/ports/binance/controller/MarketController.kt | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/api/ports/binance/controller/MarketController.kt b/api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/api/ports/binance/controller/MarketController.kt index 7bbdcdb1d..ad3457440 100644 --- a/api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/api/ports/binance/controller/MarketController.kt +++ b/api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/api/ports/binance/controller/MarketController.kt @@ -4,7 +4,6 @@ import co.nilin.opex.api.core.inout.PriceChange import co.nilin.opex.api.core.inout.PriceTicker import co.nilin.opex.api.core.spi.AccountantProxy import co.nilin.opex.api.core.spi.MarketDataProxy -import co.nilin.opex.api.core.spi.MarketStatProxy import co.nilin.opex.api.core.spi.SymbolMapper import co.nilin.opex.api.core.utils.Interval import co.nilin.opex.api.ports.binance.data.* @@ -90,8 +89,8 @@ class MarketController( RecentTradeResponse( it.id, it.price, - it.qty, - it.quoteQty, + it.quantity, + it.quoteQuantity, it.time.time, it.isMakerBuyer, it.isBestMatch From e301b2fa058a597f4199f5692035056129779d97 Mon Sep 17 00:00:00 2001 From: Peyman Date: Sun, 14 Aug 2022 16:46:30 +0430 Subject: [PATCH 13/14] Fix symbol mapping Fix queryOrder body annotation Fix withdraw service param issue --- .../co/nilin/opex/api/core/inout/PriceTicker.kt | 2 +- .../nilin/opex/api/core/inout/TradeVolumeStat.kt | 2 +- .../ports/binance/controller/AccountController.kt | 10 +++++++--- .../ports/binance/controller/LandingController.kt | 14 ++++++++++---- .../ports/binance/controller/MarketController.kt | 3 ++- .../api/ports/binance/data/QueryOrderResponse.kt | 2 +- .../api/ports/proxy/impl/MarketDataProxyImpl.kt | 2 +- .../market/app/controller/UserDataController.kt | 6 +++--- .../wallet/app/controller/WithdrawController.kt | 2 +- 9 files changed, 27 insertions(+), 16 deletions(-) diff --git a/api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/PriceTicker.kt b/api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/PriceTicker.kt index 79df08a9a..5f0a210b7 100644 --- a/api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/PriceTicker.kt +++ b/api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/PriceTicker.kt @@ -1,6 +1,6 @@ package co.nilin.opex.api.core.inout data class PriceTicker( - val symbol: String?, + var symbol: String?, val price: String? ) \ No newline at end of file diff --git a/api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/TradeVolumeStat.kt b/api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/TradeVolumeStat.kt index 8214bea8f..a5915e15e 100644 --- a/api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/TradeVolumeStat.kt +++ b/api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/TradeVolumeStat.kt @@ -3,7 +3,7 @@ package co.nilin.opex.api.core.inout import java.math.BigDecimal data class TradeVolumeStat( - val symbol: String, + var symbol: String, val volume: BigDecimal, val tradeCount: BigDecimal, val change: Double diff --git a/api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/api/ports/binance/controller/AccountController.kt b/api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/api/ports/binance/controller/AccountController.kt index 8e92ad7cb..c44e9d7c6 100644 --- a/api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/api/ports/binance/controller/AccountController.kt +++ b/api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/api/ports/binance/controller/AccountController.kt @@ -218,6 +218,7 @@ class AccountController( val internalSymbol = symbolMapper.toInternalSymbol(symbol) ?: throw OpexException(OpexError.SymbolNotFound) return queryHandler.queryOrder(principal, internalSymbol, orderId, origClientOrderId) ?.asQueryOrderResponse() + ?.apply { this.symbol = symbol } ?: throw OpexException(OpexError.OrderNotFound) } @@ -254,7 +255,9 @@ class AccountController( timestamp: Long ): List { val internalSymbol = symbolMapper.toInternalSymbol(symbol) ?: throw OpexException(OpexError.SymbolNotFound) - return queryHandler.openOrders(principal, internalSymbol).map { it.asQueryOrderResponse() } + return queryHandler.openOrders(principal, internalSymbol).map { + it.asQueryOrderResponse().apply { symbol?.let { s -> this.symbol = s } } + } } /* @@ -295,8 +298,9 @@ class AccountController( timestamp: Long ): List { val internalSymbol = symbolMapper.toInternalSymbol(symbol) ?: throw OpexException(OpexError.SymbolNotFound) - return queryHandler.allOrders(principal, internalSymbol, startTime, endTime, limit) - .map { it.asQueryOrderResponse() } + return queryHandler.allOrders(principal, internalSymbol, startTime, endTime, limit).map { + it.asQueryOrderResponse().apply { symbol?.let { s -> this.symbol = s } } + } } /* diff --git a/api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/api/ports/binance/controller/LandingController.kt b/api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/api/ports/binance/controller/LandingController.kt index 8f5a2469f..2546e506f 100644 --- a/api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/api/ports/binance/controller/LandingController.kt +++ b/api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/api/ports/binance/controller/LandingController.kt @@ -45,12 +45,18 @@ class LandingController( ): MarketStatResponse { val since = (Interval.findByLabel(interval) ?: Interval.Week).getDate().time val validLimit = getValidLimit(limit) + val symbols = symbolMapper.symbolToAliasMap() + return MarketStatResponse( - marketStatProxy.getMostIncreasedInPricePairs(since, validLimit), - marketStatProxy.getMostDecreasedInPricePairs(since, validLimit), - marketStatProxy.getHighestVolumePair(since), - marketStatProxy.getTradeCountPair(since) + marketStatProxy.getMostIncreasedInPricePairs(since, validLimit).onEach { + symbols[it.symbol]?.let { s -> it.symbol = s } + }, + marketStatProxy.getMostDecreasedInPricePairs(since, validLimit).onEach { + symbols[it.symbol]?.let { s -> it.symbol = s } + }, + marketStatProxy.getHighestVolumePair(since)?.apply { symbols[symbol]?.let { symbol = it } }, + marketStatProxy.getTradeCountPair(since)?.apply { symbols[symbol]?.let { symbol = it } } ) } diff --git a/api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/api/ports/binance/controller/MarketController.kt b/api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/api/ports/binance/controller/MarketController.kt index ad3457440..6607129ba 100644 --- a/api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/api/ports/binance/controller/MarketController.kt +++ b/api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/api/ports/binance/controller/MarketController.kt @@ -136,11 +136,12 @@ class MarketController( // 2 when the symbol parameter is omitted @GetMapping("/v3/ticker/price") suspend fun priceTicker(@RequestParam("symbol", required = false) symbol: String?): List { + val symbols = symbolMapper.symbolToAliasMap() val localSymbol = if (symbol == null) null else symbolMapper.toInternalSymbol(symbol) ?: throw OpexException(OpexError.SymbolNotFound) - return marketDataProxy.lastPrice(localSymbol) + return marketDataProxy.lastPrice(localSymbol).onEach { symbols[it.symbol]?.let { s -> it.symbol = s } } } @GetMapping("/v3/exchangeInfo") diff --git a/api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/api/ports/binance/data/QueryOrderResponse.kt b/api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/api/ports/binance/data/QueryOrderResponse.kt index 8470e9971..84053728c 100644 --- a/api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/api/ports/binance/data/QueryOrderResponse.kt +++ b/api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/api/ports/binance/data/QueryOrderResponse.kt @@ -10,7 +10,7 @@ import java.util.* @JsonInclude(JsonInclude.Include.NON_NULL) data class QueryOrderResponse( - val symbol: String, + var symbol: String, val ouid: String, val orderId: Long, val orderListId: Long, //Unless part of an OCO, the value will always be -1. diff --git a/api/api-ports/api-proxy-rest/src/main/kotlin/co/nilin/opex/api/ports/proxy/impl/MarketDataProxyImpl.kt b/api/api-ports/api-proxy-rest/src/main/kotlin/co/nilin/opex/api/ports/proxy/impl/MarketDataProxyImpl.kt index 9e3e38c8b..c2c3144eb 100644 --- a/api/api-ports/api-proxy-rest/src/main/kotlin/co/nilin/opex/api/ports/proxy/impl/MarketDataProxyImpl.kt +++ b/api/api-ports/api-proxy-rest/src/main/kotlin/co/nilin/opex/api/ports/proxy/impl/MarketDataProxyImpl.kt @@ -200,7 +200,7 @@ class MarketDataProxyImpl(private val webClient: WebClient) : MarketDataProxy { override suspend fun countTotalOrders(since: Long): Long { return webClient.get() - .uri("$baseUrl/v1/market/active-users") { + .uri("$baseUrl/v1/market/orders-count") { it.queryParam("interval", since) it.build() }.accept(MediaType.APPLICATION_JSON) diff --git a/market/market-app/src/main/kotlin/co/nilin/opex/market/app/controller/UserDataController.kt b/market/market-app/src/main/kotlin/co/nilin/opex/market/app/controller/UserDataController.kt index 276152351..1bdcdd717 100644 --- a/market/market-app/src/main/kotlin/co/nilin/opex/market/app/controller/UserDataController.kt +++ b/market/market-app/src/main/kotlin/co/nilin/opex/market/app/controller/UserDataController.kt @@ -16,7 +16,7 @@ class UserDataController(private val userQueryHandler: UserQueryHandler) { } @PostMapping("/{uuid}/order/query") - suspend fun queryUserOrder(@PathVariable uuid: String, request: QueryOrderRequest): Order { + suspend fun queryUserOrder(@PathVariable uuid: String, @RequestBody request: QueryOrderRequest): Order { return userQueryHandler.queryOrder(uuid, request) ?: throw OpexException(OpexError.NotFound) } @@ -26,12 +26,12 @@ class UserDataController(private val userQueryHandler: UserQueryHandler) { } @PostMapping("/{uuid}/orders") - suspend fun getUserOrders(@PathVariable uuid: String, request: AllOrderRequest): List { + suspend fun getUserOrders(@PathVariable uuid: String, @RequestBody request: AllOrderRequest): List { return userQueryHandler.allOrders(uuid, request) } @PostMapping("/{uuid}/trades") - suspend fun getUserTrades(@PathVariable uuid: String, request: TradeRequest): List { + suspend fun getUserTrades(@PathVariable uuid: String, @RequestBody request: TradeRequest): List { return userQueryHandler.allTrades(uuid, request) } diff --git a/wallet/wallet-app/src/main/kotlin/co/nilin/opex/wallet/app/controller/WithdrawController.kt b/wallet/wallet-app/src/main/kotlin/co/nilin/opex/wallet/app/controller/WithdrawController.kt index fbd8a10cd..0b8727372 100644 --- a/wallet/wallet-app/src/main/kotlin/co/nilin/opex/wallet/app/controller/WithdrawController.kt +++ b/wallet/wallet-app/src/main/kotlin/co/nilin/opex/wallet/app/controller/WithdrawController.kt @@ -51,7 +51,7 @@ class WithdrawController(private val withdrawService: WithdrawService) { ) } - @PostMapping("/{amount}_{symbol}") + @PostMapping("/{amount}_{currency}") @ApiResponse( message = "OK", code = 200, From c661718fc754f06c76ec3cc6c6c0ddf281788e0d Mon Sep 17 00:00:00 2001 From: Peyman Date: Mon, 15 Aug 2022 20:59:26 +0430 Subject: [PATCH 14/14] Fixed pair config --- .../accountant/ports/postgres/impl/PairConfigLoaderImpl.kt | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/accountant/accountant-ports/accountant-persister-postgres/src/main/kotlin/co/nilin/opex/accountant/ports/postgres/impl/PairConfigLoaderImpl.kt b/accountant/accountant-ports/accountant-persister-postgres/src/main/kotlin/co/nilin/opex/accountant/ports/postgres/impl/PairConfigLoaderImpl.kt index 2df91a806..52c2c2ee2 100644 --- a/accountant/accountant-ports/accountant-persister-postgres/src/main/kotlin/co/nilin/opex/accountant/ports/postgres/impl/PairConfigLoaderImpl.kt +++ b/accountant/accountant-ports/accountant-persister-postgres/src/main/kotlin/co/nilin/opex/accountant/ports/postgres/impl/PairConfigLoaderImpl.kt @@ -45,8 +45,9 @@ class PairConfigLoaderImpl( .collectList() .awaitFirstOrElse { emptyList() } .map { + val pairConfig = pairConfigRepository.findById(it.pairConfigId).awaitSingle().asPairConfig() PairFeeConfig( - PairConfig(it.pairConfigId, "", "", BigDecimal.ZERO, BigDecimal.ZERO), + pairConfig, it.direction, it.userLevel, it.makerFee, @@ -62,8 +63,9 @@ class PairConfigLoaderImpl( ): PairFeeConfig? { val fee = pairFeeConfigRepository.findByPairAndDirectionAndUserLevel(pair, direction, userLevel) .awaitSingleOrNull() ?: return null + val pairConfig = pairConfigRepository.findById(fee.pairConfigId).awaitSingle().asPairConfig() return PairFeeConfig( - PairConfig(pair, "", "", BigDecimal.ZERO, BigDecimal.ZERO), + pairConfig, fee.direction, fee.userLevel, fee.makerFee,