diff --git a/accountant/accountant-app/src/main/kotlin/co/nilin/opex/accountant/app/config/AppConfig.kt b/accountant/accountant-app/src/main/kotlin/co/nilin/opex/accountant/app/config/AppConfig.kt index 8b4e26e95..a621b0ef2 100644 --- a/accountant/accountant-app/src/main/kotlin/co/nilin/opex/accountant/app/config/AppConfig.kt +++ b/accountant/accountant-app/src/main/kotlin/co/nilin/opex/accountant/app/config/AppConfig.kt @@ -9,7 +9,10 @@ import co.nilin.opex.accountant.core.service.FinancialActionJobManagerImpl import co.nilin.opex.accountant.core.service.OrderManagerImpl import co.nilin.opex.accountant.core.service.TradeManagerImpl import co.nilin.opex.accountant.core.spi.* -import co.nilin.opex.accountant.ports.kafka.listener.consumer.* +import co.nilin.opex.accountant.ports.kafka.listener.consumer.EventKafkaListener +import co.nilin.opex.accountant.ports.kafka.listener.consumer.OrderKafkaListener +import co.nilin.opex.accountant.ports.kafka.listener.consumer.TempEventKafkaListener +import co.nilin.opex.accountant.ports.kafka.listener.consumer.TradeKafkaListener import co.nilin.opex.accountant.ports.kafka.listener.spi.FAResponseListener import org.springframework.beans.factory.annotation.Autowired import org.springframework.context.annotation.Bean @@ -36,7 +39,6 @@ class AppConfig { @Bean fun orderManager( pairConfigLoader: PairConfigLoader, - userLevelLoader: UserLevelLoader, financialActionPersister: FinancialActionPersister, financeActionLoader: FinancialActionLoader, orderPersister: OrderPersister, @@ -44,18 +46,17 @@ class AppConfig { tempEventRepublisher: TempEventRepublisher, richOrderPublisher: RichOrderPublisher, financialActionPublisher: FinancialActionPublisher, - jsonMapper: JsonMapper + feeCalculator: FeeCalculator ): OrderManager { return OrderManagerImpl( pairConfigLoader, - userLevelLoader, financialActionPersister, financeActionLoader, orderPersister, tempEventPersister, richOrderPublisher, financialActionPublisher, - jsonMapper + feeCalculator ) } @@ -69,7 +70,8 @@ class AppConfig { richOrderPublisher: RichOrderPublisher, feeCalculator: FeeCalculator, financialActionPublisher: FinancialActionPublisher, - jsonMapper: JsonMapper + currencyRatePersister: CurrencyRatePersister, + userVolumePersister: UserVolumePersister, ): TradeManager { return TradeManagerImpl( financeActionPersister, @@ -80,7 +82,8 @@ class AppConfig { richOrderPublisher, feeCalculator, financialActionPublisher, - jsonMapper + currencyRatePersister, + userVolumePersister ) } @@ -129,11 +132,8 @@ class AppConfig { fun configureEventListener( eventKafkaListener: EventKafkaListener, accountantEventListener: AccountantEventListener, - kycLevelUpdatedKafkaListener: KycLevelUpdatedKafkaListener, - kycLevelUpdatedEventListener: KycLevelUpdatedListener ) { eventKafkaListener.addListener(accountantEventListener) - kycLevelUpdatedKafkaListener.addEventListener(kycLevelUpdatedEventListener) } @Autowired diff --git a/accountant/accountant-app/src/main/kotlin/co/nilin/opex/accountant/app/controller/FeeController.kt b/accountant/accountant-app/src/main/kotlin/co/nilin/opex/accountant/app/controller/FeeController.kt new file mode 100644 index 000000000..426d5a834 --- /dev/null +++ b/accountant/accountant-app/src/main/kotlin/co/nilin/opex/accountant/app/controller/FeeController.kt @@ -0,0 +1,27 @@ +package co.nilin.opex.accountant.app.controller + +import co.nilin.opex.accountant.core.api.FeeCalculator +import co.nilin.opex.accountant.core.model.FeeConfig +import co.nilin.opex.accountant.core.model.UserFee +import co.nilin.opex.accountant.core.spi.FeeConfigService +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 + +@RestController +@RequestMapping("/fee") +class FeeController( + private val feeConfigService: FeeConfigService, + private val feeCalculator: FeeCalculator, +) { + @GetMapping("/config") + suspend fun getFeeConfigs(): List { + return feeConfigService.loadFeeConfigs() + } + + @GetMapping("/{uuid}") + suspend fun getUserFee(@PathVariable uuid: String): UserFee { + return feeCalculator.getUserFee(uuid) + } +} \ No newline at end of file diff --git a/accountant/accountant-app/src/main/kotlin/co/nilin/opex/accountant/app/controller/PairConfigController.kt b/accountant/accountant-app/src/main/kotlin/co/nilin/opex/accountant/app/controller/PairConfigController.kt index 962218b17..a0dce60d2 100644 --- a/accountant/accountant-app/src/main/kotlin/co/nilin/opex/accountant/app/controller/PairConfigController.kt +++ b/accountant/accountant-app/src/main/kotlin/co/nilin/opex/accountant/app/controller/PairConfigController.kt @@ -1,25 +1,21 @@ package co.nilin.opex.accountant.app.controller -import co.nilin.opex.accountant.app.data.PairFeeResponse +import co.nilin.opex.accountant.core.model.FeeConfig import co.nilin.opex.accountant.core.model.PairConfig -import co.nilin.opex.accountant.core.model.PairFeeConfig +import co.nilin.opex.accountant.core.spi.FeeConfigService import co.nilin.opex.accountant.core.spi.PairConfigLoader -import co.nilin.opex.common.OpexError import co.nilin.opex.matching.engine.core.model.OrderDirection -import org.springframework.web.bind.annotation.* +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 @RestController @RequestMapping("/config") -class PairConfigController(private val pairConfigLoader: PairConfigLoader) { - - @GetMapping("/{pair}/fee/{direction}-{userLevel}") - suspend fun fetchPairFeeConfig( - @PathVariable("pair") pair: String, - @PathVariable("direction") direction: OrderDirection, - @PathVariable("userLevel") level: String - ): PairFeeConfig { - return pairConfigLoader.load(pair, direction, level) - } +class PairConfigController( + private val pairConfigLoader: PairConfigLoader, + private val feeConfigService: FeeConfigService +) { @GetMapping("/{pair}/{direction}") suspend fun fetchPairConfig( @@ -35,19 +31,7 @@ class PairConfigController(private val pairConfigLoader: PairConfigLoader) { } @GetMapping("/fee") - suspend fun getFeeConfigs(): List { - return pairConfigLoader.loadPairFeeConfigs() - .map { PairFeeResponse(it.pairConfig.pair, it.direction, it.userLevel, it.makerFee, it.takerFee) } - } - - @GetMapping("/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 OpexError.PairFeeNotFound.exception() - return PairFeeResponse(fee.pairConfig.pair, fee.direction, fee.userLevel, fee.makerFee, fee.takerFee) + suspend fun getFeeConfigs(): List { + return feeConfigService.loadFeeConfigs() } } \ No newline at end of file diff --git a/accountant/accountant-app/src/main/kotlin/co/nilin/opex/accountant/app/listener/KycLevelUpdatedListener.kt b/accountant/accountant-app/src/main/kotlin/co/nilin/opex/accountant/app/listener/KycLevelUpdatedListener.kt deleted file mode 100644 index 98ae6147b..000000000 --- a/accountant/accountant-app/src/main/kotlin/co/nilin/opex/accountant/app/listener/KycLevelUpdatedListener.kt +++ /dev/null @@ -1,34 +0,0 @@ -package co.nilin.opex.accountant.app.listener - -import co.nilin.opex.accountant.core.inout.KycLevelUpdatedEvent -import co.nilin.opex.accountant.ports.kafka.listener.spi.KycLevelUpdatedEventListener -import co.nilin.opex.accountant.ports.postgres.impl.UserLevelLoaderImpl -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch -import org.slf4j.LoggerFactory -import org.springframework.stereotype.Component - -@Component -class KycLevelUpdatedListener(val userLevelLoaderImpl: UserLevelLoaderImpl) : KycLevelUpdatedEventListener { - - private val logger = LoggerFactory.getLogger(KycLevelUpdatedListener::class.java) - val scope = CoroutineScope(Dispatchers.IO) - override fun id(): String { - return "KycLevelUpdatedListener" - } - - override fun onEvent( - event: KycLevelUpdatedEvent, - partition: Int, offset: Long, timestamp: Long, eventId: String - ) { - logger.info("==========================================================================") - logger.info("Incoming UserLevelUpdated event: $event") - logger.info("==========================================================================") - scope.launch { - userLevelLoaderImpl.update(event.userId, event.kycLevel) - } - } - - -} \ No newline at end of file diff --git a/accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/api/FeeCalculator.kt b/accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/api/FeeCalculator.kt index 97adf6933..205bf336b 100644 --- a/accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/api/FeeCalculator.kt +++ b/accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/api/FeeCalculator.kt @@ -3,10 +3,13 @@ package co.nilin.opex.accountant.core.api import co.nilin.opex.accountant.core.model.FeeFinancialActions import co.nilin.opex.accountant.core.model.FinancialAction import co.nilin.opex.accountant.core.model.Order +import co.nilin.opex.accountant.core.model.UserFee import co.nilin.opex.matching.engine.core.eventh.events.TradeEvent interface FeeCalculator { + suspend fun getUserFee(uuid: String): UserFee + suspend fun createFeeActions( trade: TradeEvent, makerOrder: Order, diff --git a/accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/inout/UserCurrencyVolume.kt b/accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/inout/UserCurrencyVolume.kt new file mode 100644 index 000000000..34b3df7a8 --- /dev/null +++ b/accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/inout/UserCurrencyVolume.kt @@ -0,0 +1,14 @@ +package co.nilin.opex.accountant.core.inout + +import java.math.BigDecimal + +data class UserCurrencyVolume( + val currency: String, + val volume: BigDecimal, + val valueUSDT: BigDecimal, + val valueIRT: BigDecimal +) { + companion object { + fun zero(currency: String) = UserCurrencyVolume(currency, BigDecimal.ZERO, BigDecimal.ZERO, BigDecimal.ZERO) + } +} \ No newline at end of file diff --git a/accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/inout/UserTotalVolumeValue.kt b/accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/inout/UserTotalVolumeValue.kt new file mode 100644 index 000000000..ca9a858d8 --- /dev/null +++ b/accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/inout/UserTotalVolumeValue.kt @@ -0,0 +1,12 @@ +package co.nilin.opex.accountant.core.inout + +import java.math.BigDecimal + +data class UserTotalVolumeValue( + val valueUSDT: BigDecimal, + val valueIRT: BigDecimal +) { + companion object { + fun zero() = UserTotalVolumeValue(BigDecimal.ZERO, BigDecimal.ZERO) + } +} \ No newline at end of file diff --git a/accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/model/FeeConfig.kt b/accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/model/FeeConfig.kt new file mode 100644 index 000000000..00df04871 --- /dev/null +++ b/accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/model/FeeConfig.kt @@ -0,0 +1,16 @@ +package co.nilin.opex.accountant.core.model + +import java.math.BigDecimal + +data class FeeConfig( + val name: String, + val displayOrder: Int, + val minAssetVolume: BigDecimal, + val maxAssetVolume: BigDecimal? = null, + val minTradeVolume: BigDecimal, + val maxTradeVolume: BigDecimal? = null, + val makerFee: BigDecimal, + val takerFee: BigDecimal, + val condition: Condition +) +enum class Condition { AND, OR } \ No newline at end of file diff --git a/accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/model/PairFeeConfig.kt b/accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/model/PairFeeConfig.kt deleted file mode 100644 index 052e6f2f8..000000000 --- a/accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/model/PairFeeConfig.kt +++ /dev/null @@ -1,11 +0,0 @@ -package co.nilin.opex.accountant.core.model - -import java.math.BigDecimal - -class PairFeeConfig( - val pairConfig: PairConfig, - val direction: String, - val userLevel: String, - val makerFee: BigDecimal, - val takerFee: BigDecimal -) \ No newline at end of file diff --git a/accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/model/TotalAssetsSnapshot.kt b/accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/model/TotalAssetsSnapshot.kt new file mode 100644 index 000000000..ef38bbefc --- /dev/null +++ b/accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/model/TotalAssetsSnapshot.kt @@ -0,0 +1,11 @@ +package co.nilin.opex.accountant.core.model + +import java.math.BigDecimal +import java.time.LocalDateTime + +data class TotalAssetsSnapshot( + val uuid: String, + val totalUSDT: BigDecimal, + val totalIRT: BigDecimal, + val snapshotDate: LocalDateTime, +) \ No newline at end of file diff --git a/accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/model/UserFee.kt b/accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/model/UserFee.kt new file mode 100644 index 000000000..fb6909d55 --- /dev/null +++ b/accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/model/UserFee.kt @@ -0,0 +1,10 @@ +package co.nilin.opex.accountant.core.model + +import java.math.BigDecimal + +data class UserFee( + val name: String, + val displayOrder: Int, + val makerFee: BigDecimal, + val takerFee: BigDecimal, +) diff --git a/accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/service/FeeCalculatorImpl.kt b/accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/service/FeeCalculatorImpl.kt index 7e22d75ab..af73520b1 100644 --- a/accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/service/FeeCalculatorImpl.kt +++ b/accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/service/FeeCalculatorImpl.kt @@ -2,21 +2,56 @@ package co.nilin.opex.accountant.core.service import co.nilin.opex.accountant.core.api.FeeCalculator import co.nilin.opex.accountant.core.model.* +import co.nilin.opex.accountant.core.spi.FeeConfigService import co.nilin.opex.accountant.core.spi.JsonMapper +import co.nilin.opex.accountant.core.spi.UserVolumePersister +import co.nilin.opex.accountant.core.spi.WalletProxy +import co.nilin.opex.accountant.core.utils.CacheManager import co.nilin.opex.matching.engine.core.eventh.events.TradeEvent import org.slf4j.LoggerFactory import org.springframework.beans.factory.annotation.Value import org.springframework.stereotype.Component -import java.time.LocalDateTime +import java.math.BigDecimal +import java.time.* +import java.util.concurrent.TimeUnit @Component class FeeCalculatorImpl( + private val walletProxy: WalletProxy, + private val feeConfigService: FeeConfigService, + private val userVolumePersister: UserVolumePersister, + private val cacheManager: CacheManager, @Value("\${app.address}") private val platformAddress: String, private val jsonMapper: JsonMapper ) : FeeCalculator { private val logger = LoggerFactory.getLogger(FeeCalculatorImpl::class.java) + private fun remainingMillisUntil1AM(zone: ZoneId = ZoneId.of("GMT+03:30")): Long { + val now = ZonedDateTime.now(zone) + val target = now.toLocalDate().plusDays(1).atTime(1, 0).atZone(zone) + val remainingMillis = Duration.between(now, target).toMillis() + return if (remainingMillis > 0) remainingMillis else Duration.ofHours(1).toMillis() + } + + override suspend fun getUserFee(uuid: String): UserFee { + val cached = cacheManager.get(uuid) + if (cached != null) { + return cached + } + + val totalAssets = walletProxy.getUserTotalAssets(uuid) + val userVolumeData = userVolumePersister.getUserVolumeData(uuid, LocalDate.now().minusMonths(1L)) + + val feeConfig = feeConfigService.loadMatchingFeeConfig( + totalAssets?.totalUSDT ?: BigDecimal.ZERO, + userVolumeData?.valueUSDT ?: BigDecimal.ZERO + ) + val remainingMillis = remainingMillisUntil1AM() + cacheManager.put(uuid, feeConfig, remainingMillis, TimeUnit.MILLISECONDS) + return feeConfig + } + override suspend fun createFeeActions( trade: TradeEvent, makerOrder: Order, diff --git a/accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/service/OrderManagerImpl.kt b/accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/service/OrderManagerImpl.kt index d4bfe0b42..34bf51f4b 100644 --- a/accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/service/OrderManagerImpl.kt +++ b/accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/service/OrderManagerImpl.kt @@ -1,5 +1,6 @@ package co.nilin.opex.accountant.core.service +import co.nilin.opex.accountant.core.api.FeeCalculator import co.nilin.opex.accountant.core.api.OrderManager import co.nilin.opex.accountant.core.inout.OrderStatus import co.nilin.opex.accountant.core.inout.RichOrder @@ -15,14 +16,13 @@ import java.time.LocalDateTime open class OrderManagerImpl( private val pairConfigLoader: PairConfigLoader, - private val userLevelLoader: UserLevelLoader, private val financialActionPersister: FinancialActionPersister, private val financeActionLoader: FinancialActionLoader, private val orderPersister: OrderPersister, private val tempEventPersister: TempEventPersister, private val richOrderPublisher: RichOrderPublisher, private val financialActionPublisher: FinancialActionPublisher, - private val jsonMapper: JsonMapper, + private val feeCalculator: FeeCalculator ) : OrderManager { @Transactional @@ -36,14 +36,13 @@ open class OrderManagerImpl( submitOrderEvent.pair.rightSideName } - val level = userLevelLoader.load(submitOrderEvent.uuid) - val pairFeeConfig = pairConfigLoader.load( + val pairConfig = pairConfigLoader.load( submitOrderEvent.pair.toString(), submitOrderEvent.direction, - level ) - val makerFee = pairFeeConfig.makerFee * BigDecimal.ONE //user level formula - val takerFee = pairFeeConfig.takerFee * BigDecimal.ONE //user level formula + val fee = feeCalculator.getUserFee(submitOrderEvent.uuid) + val makerFee = fee.makerFee + val takerFee = fee.takerFee //create fa for transfer uuid symbol main wallet to uuid symbol exchange wallet /* @@ -52,11 +51,11 @@ open class OrderManagerImpl( */ val amount = if (submitOrderEvent.direction == OrderDirection.ASK) { - BigDecimal(submitOrderEvent.quantity).multiply(pairFeeConfig.pairConfig.leftSideFraction) + BigDecimal(submitOrderEvent.quantity).multiply(pairConfig.leftSideFraction) } else { - BigDecimal(submitOrderEvent.quantity).multiply(pairFeeConfig.pairConfig.leftSideFraction) + BigDecimal(submitOrderEvent.quantity).multiply(pairConfig.leftSideFraction) .multiply(submitOrderEvent.price.toBigDecimal()) - .multiply(pairFeeConfig.pairConfig.rightSideFraction) + .multiply(pairConfig.rightSideFraction) } //store order (ouid, uuid, fees, userlevel, pair, direction, price, quantity, filledQ, status, transfered) @@ -67,8 +66,8 @@ open class OrderManagerImpl( null, makerFee, takerFee, - pairFeeConfig.pairConfig.leftSideFraction, - pairFeeConfig.pairConfig.rightSideFraction, + pairConfig.leftSideFraction, + pairConfig.rightSideFraction, submitOrderEvent.uuid, submitOrderEvent.userLevel, submitOrderEvent.direction, @@ -78,10 +77,10 @@ open class OrderManagerImpl( submitOrderEvent.quantity, submitOrderEvent.quantity - submitOrderEvent.remainedQuantity, submitOrderEvent.price.toBigDecimal() - .multiply(pairFeeConfig.pairConfig.rightSideFraction), + .multiply(pairConfig.rightSideFraction), submitOrderEvent.quantity.toBigDecimal() - .multiply(pairFeeConfig.pairConfig.leftSideFraction), - BigDecimal(submitOrderEvent.quantity - submitOrderEvent.remainedQuantity).multiply(pairFeeConfig.pairConfig.leftSideFraction), + .multiply(pairConfig.leftSideFraction), + BigDecimal(submitOrderEvent.quantity - submitOrderEvent.remainedQuantity).multiply(pairConfig.leftSideFraction), amount, amount, OrderStatus.REQUESTED.code @@ -269,22 +268,10 @@ open class OrderManagerImpl( } } - private fun createMap(rejectOrderEvent: RejectOrderEvent, order: Order): Map { - val orderMap: Map = jsonMapper.toMap(order) - val eventMap: Map = jsonMapper.toMap(rejectOrderEvent) - return orderMap + eventMap - } + private suspend fun getFee(uuid: String): BigDecimal { - private fun createMap(cancelOrderEvent: CancelOrderEvent, order: Order): Map { - val orderMap: Map = jsonMapper.toMap(order) - val eventMap: Map = jsonMapper.toMap(cancelOrderEvent) - return orderMap + eventMap - } - private fun createMap(submitOrderEvent: SubmitOrderEvent, order: Order): Map { - val orderMap: Map = jsonMapper.toMap(order) - val eventMap: Map = jsonMapper.toMap(submitOrderEvent) - return orderMap + eventMap + return BigDecimal.ONE } } \ 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 b6c574ea3..fb1d5a37e 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 @@ -8,9 +8,12 @@ import co.nilin.opex.accountant.core.inout.RichTrade import co.nilin.opex.accountant.core.model.* import co.nilin.opex.accountant.core.spi.* import co.nilin.opex.matching.engine.core.eventh.events.TradeEvent +import kotlinx.coroutines.reactive.awaitFirstOrNull +import kotlinx.coroutines.reactor.awaitSingleOrNull import org.slf4j.LoggerFactory import org.springframework.transaction.annotation.Transactional import java.math.BigDecimal +import java.time.LocalDate import java.time.LocalDateTime open class TradeManagerImpl( @@ -22,7 +25,8 @@ open class TradeManagerImpl( private val richOrderPublisher: RichOrderPublisher, private val feeCalculator: FeeCalculator, private val financialActionPublisher: FinancialActionPublisher, - private val jsonMapper: JsonMapper + private val currencyRatePersister: CurrencyRatePersister, + private val userVolumePersister: UserVolumePersister ) : TradeManager { private val logger = LoggerFactory.getLogger(TradeManagerImpl::class.java) @@ -154,39 +158,77 @@ open class TradeManagerImpl( val takerPrice = trade.takerPrice.toBigDecimal().multiply(takerOrder.rightSideFraction) val makerPrice = trade.makerPrice.toBigDecimal().multiply(makerOrder.rightSideFraction) - richTradePublisher.publish( - RichTrade( - trade.tradeId, - trade.pair.toString(), - trade.takerOuid, - trade.takerUuid, - trade.takerOrderId, - trade.takerDirection, - takerPrice, - takerOrder.origQuantity, - takerOrder.origPrice.multiply(takerOrder.origQuantity), - trade.takerRemainedQuantity.toBigDecimal().multiply(takerOrder.leftSideFraction), - feeActions.takerFeeAction.amount, - feeActions.takerFeeAction.symbol, - trade.makerOuid, - trade.makerUuid, - trade.makerOrderId, - trade.makerDirection, - 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 - ) + val richTrade = RichTrade( + trade.tradeId, + trade.pair.toString(), + trade.takerOuid, + trade.takerUuid, + trade.takerOrderId, + trade.takerDirection, + takerPrice, + takerOrder.origQuantity, + takerOrder.origPrice.multiply(takerOrder.origQuantity), + trade.takerRemainedQuantity.toBigDecimal().multiply(takerOrder.leftSideFraction), + feeActions.takerFeeAction.amount, + feeActions.takerFeeAction.symbol, + trade.makerOuid, + trade.makerUuid, + trade.makerOrderId, + trade.makerDirection, + 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 ) + richTradePublisher.publish(richTrade) + + currencyRatePersister.updateRate(trade.pair.leftSideName, trade.pair.rightSideName, makerPrice) + calculateTradeVolume(richTrade, trade.pair.leftSideName, trade.pair.rightSideName) + return financeActionPersister.persist(financialActions) //return financeActionPersister.persist(financialActions).also { publishFinancialActions(it) } } + private suspend fun calculateTradeVolume(trade: RichTrade, base: String, quote: String) { + val today = LocalDate.now() + + var valueUSDT = BigDecimal.ZERO + var valueIRT = BigDecimal.ZERO + + if (quote.equals("IRT", true)) { + val baseUSDTRate = currencyRatePersister.getRate(base, "USDT") + valueUSDT = trade.matchedQuantity * baseUSDTRate + valueIRT = trade.matchedQuantity * trade.matchedPrice + } else if (quote.equals("USDT", true)) { + val baseIRTRate = currencyRatePersister.getRate(base, "IRT") + valueUSDT = trade.matchedQuantity * trade.matchedPrice + valueIRT = trade.matchedQuantity * baseIRTRate + } + + userVolumePersister.update( + trade.makerUuid, + base, + today, + trade.matchedQuantity, + valueUSDT, + valueIRT + ) + userVolumePersister.update( + trade.takerUuid, + base, + today, + trade.matchedQuantity, + valueUSDT, + valueIRT + ) + logger.info("Trade volume updated") + } + private fun createFinalizeOrderFinancialAction( parentTransferAction: FinancialAction?, order: Order, @@ -250,10 +292,4 @@ open class TradeManagerImpl( if (!list.contains(financialAction)) list.add(financialAction) } - - private fun createMap(tradeEvent: TradeEvent, order: Order): Map { - val orderMap: Map = jsonMapper.toMap(order) - val eventMap: Map = jsonMapper.toMap(tradeEvent) - return orderMap + eventMap - } } \ No newline at end of file diff --git a/accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/spi/CurrencyRatePersister.kt b/accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/spi/CurrencyRatePersister.kt new file mode 100644 index 000000000..96ded5fde --- /dev/null +++ b/accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/spi/CurrencyRatePersister.kt @@ -0,0 +1,10 @@ +package co.nilin.opex.accountant.core.spi + +import java.math.BigDecimal + +interface CurrencyRatePersister { + + suspend fun updateRate(base: String, quote: String, rate: BigDecimal) + + suspend fun getRate(base: String, quote: String): BigDecimal +} \ No newline at end of file diff --git a/accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/spi/FeeConfigService.kt b/accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/spi/FeeConfigService.kt new file mode 100644 index 000000000..e258cd7c9 --- /dev/null +++ b/accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/spi/FeeConfigService.kt @@ -0,0 +1,15 @@ +package co.nilin.opex.accountant.core.spi + +import co.nilin.opex.accountant.core.model.FeeConfig +import co.nilin.opex.accountant.core.model.UserFee +import java.math.BigDecimal + +interface FeeConfigService { + + suspend fun loadFeeConfigs(): List + suspend fun saveFee(config: FeeConfig) + suspend fun updateFee(config: FeeConfig) + suspend fun loadFeeConfig(name: String): FeeConfig + suspend fun loadMatchingFeeConfig(assetVolume: BigDecimal, tradeVolume: BigDecimal): UserFee + +} \ No newline at end of file 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 800a8fe4b..a6a1d1c67 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 @@ -1,20 +1,11 @@ package co.nilin.opex.accountant.core.spi import co.nilin.opex.accountant.core.model.PairConfig -import co.nilin.opex.accountant.core.model.PairFeeConfig import co.nilin.opex.matching.engine.core.model.OrderDirection interface PairConfigLoader { suspend fun loadPairConfigs(): List - 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 } \ No newline at end of file diff --git a/accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/spi/UserLevelLoader.kt b/accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/spi/UserLevelLoader.kt deleted file mode 100644 index 1946ee4fd..000000000 --- a/accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/spi/UserLevelLoader.kt +++ /dev/null @@ -1,11 +0,0 @@ -package co.nilin.opex.accountant.core.spi - -import co.nilin.opex.accountant.core.model.KycLevel - -interface UserLevelLoader { - - suspend fun load(uuid: String): String - - suspend fun update(uuid: String, userLevel: KycLevel) - -} \ No newline at end of file diff --git a/accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/spi/UserVolumePersister.kt b/accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/spi/UserVolumePersister.kt new file mode 100644 index 000000000..a9e53d6ce --- /dev/null +++ b/accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/spi/UserVolumePersister.kt @@ -0,0 +1,19 @@ +package co.nilin.opex.accountant.core.spi + +import co.nilin.opex.accountant.core.inout.UserTotalVolumeValue +import java.math.BigDecimal +import java.time.LocalDate + +interface UserVolumePersister { + + suspend fun update( + userId: String, + currency: String, + date: LocalDate, + volume: BigDecimal, + valueUSDT: BigDecimal, + valueIRT: BigDecimal + ) + + suspend fun getUserVolumeData(uuid: String, startDate: LocalDate): UserTotalVolumeValue? +} \ No newline at end of file diff --git a/accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/spi/WalletProxy.kt b/accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/spi/WalletProxy.kt index 59bdce3fe..6da17ef2b 100644 --- a/accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/spi/WalletProxy.kt +++ b/accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/spi/WalletProxy.kt @@ -1,5 +1,6 @@ package co.nilin.opex.accountant.core.spi +import co.nilin.opex.accountant.core.model.TotalAssetsSnapshot import co.nilin.opex.accountant.core.model.WalletType import java.math.BigDecimal @@ -18,4 +19,8 @@ interface WalletProxy { ) suspend fun canFulfil(symbol: String, walletType: WalletType, uuid: String, amount: BigDecimal): Boolean + + suspend fun getUserTotalAssets( + uuid: String + ): TotalAssetsSnapshot? } \ No newline at end of file diff --git a/accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/utils/CacheManager.kt b/accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/utils/CacheManager.kt new file mode 100644 index 000000000..2004a5dfc --- /dev/null +++ b/accountant/accountant-core/src/main/kotlin/co/nilin/opex/accountant/core/utils/CacheManager.kt @@ -0,0 +1,43 @@ +package co.nilin.opex.accountant.core.utils + +import org.springframework.stereotype.Component +import java.util.concurrent.ConcurrentHashMap +import java.util.concurrent.TimeUnit + +@Component +class CacheManager { + + private val cacheMap = ConcurrentHashMap>() + + data class CacheEntry( + val value: T, + val timestamp: Long + ) + + fun put(key: K, value: V, expirationTime: Long, timeUnit: TimeUnit) { + val expirationMillis = timeUnit.toMillis(expirationTime) + cacheMap[key] = CacheEntry(value, System.currentTimeMillis() + expirationMillis) + } + + fun get(key: K): V? { + val entry = cacheMap[key] + return if (entry != null && !isExpired(entry)) { + entry.value + } else { + cacheMap.remove(key) + null + } + } + + private fun isExpired(entry: CacheEntry): Boolean { + return System.currentTimeMillis() > entry.timestamp + } + + fun remove(key: K) { + cacheMap.remove(key) + } + + fun clear() { + cacheMap.clear() + } +} \ No newline at end of file diff --git a/accountant/accountant-core/src/test/kotlin/co/nilin/opex/accountant/core/service/FeeCalculatorImplTest.kt b/accountant/accountant-core/src/test/kotlin/co/nilin/opex/accountant/core/service/FeeCalculatorImplTest.kt index 8add7dac4..f3ef40b87 100644 --- a/accountant/accountant-core/src/test/kotlin/co/nilin/opex/accountant/core/service/FeeCalculatorImplTest.kt +++ b/accountant/accountant-core/src/test/kotlin/co/nilin/opex/accountant/core/service/FeeCalculatorImplTest.kt @@ -2,16 +2,33 @@ package co.nilin.opex.accountant.core.service import co.nilin.opex.accountant.core.model.FinancialAction import co.nilin.opex.accountant.core.model.FinancialActionCategory +import co.nilin.opex.accountant.core.model.UserFee import co.nilin.opex.accountant.core.model.WalletType +import co.nilin.opex.accountant.core.spi.FeeConfigService +import co.nilin.opex.accountant.core.spi.UserVolumePersister +import co.nilin.opex.accountant.core.spi.WalletProxy +import co.nilin.opex.accountant.core.utils.CacheManager import co.nilin.opex.matching.engine.core.eventh.events.TradeEvent +import io.mockk.mockk import kotlinx.coroutines.runBlocking import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.Test internal class FeeCalculatorImplTest { + private val walletProxy = mockk() + private val feeConfigService = mockk() + private val userVolumePersister = mockk() + private val cacheManager = mockk>() private val receiverAddress = "0x0" - private val feeCalculator = FeeCalculatorImpl(receiverAddress, JsonMapperTestImpl()) + private val feeCalculator = FeeCalculatorImpl( + walletProxy, + feeConfigService, + userVolumePersister, + cacheManager, + receiverAddress, + JsonMapperTestImpl() + ) @Test fun givenTradeEventsAndOrders_whenFeeCalculated_feeActionsNotNull(): Unit = runBlocking { diff --git a/accountant/accountant-core/src/test/kotlin/co/nilin/opex/accountant/core/service/OrderManagerImplTest.kt b/accountant/accountant-core/src/test/kotlin/co/nilin/opex/accountant/core/service/OrderManagerImplTest.kt index 24d1c05d6..0abca2b78 100644 --- a/accountant/accountant-core/src/test/kotlin/co/nilin/opex/accountant/core/service/OrderManagerImplTest.kt +++ b/accountant/accountant-core/src/test/kotlin/co/nilin/opex/accountant/core/service/OrderManagerImplTest.kt @@ -1,5 +1,6 @@ package co.nilin.opex.accountant.core.service +import co.nilin.opex.accountant.core.api.FeeCalculator import co.nilin.opex.accountant.core.inout.OrderStatus import co.nilin.opex.accountant.core.model.* import co.nilin.opex.accountant.core.spi.* @@ -30,19 +31,18 @@ internal class OrderManagerImplTest { private val tempEventPersister = mockk() private val pairConfigLoader = mockk() private val richOrderPublisher = mockk() - private val userLevelLoader = mockk() private val financialActionPublisher = mockk() + private val feeCalculator = mockk() private val orderManager = OrderManagerImpl( pairConfigLoader, - userLevelLoader, financialActionPersister, financialActionLoader, orderPersister, tempEventPersister, richOrderPublisher, financialActionPublisher, - JsonMapperTestImpl() + feeCalculator ) init { @@ -54,7 +54,6 @@ internal class OrderManagerImplTest { coEvery { financialActionPersister.persist(any()) } returnsArgument (0) coEvery { financialActionPersister.updateStatus(any(), any()) } returns Unit coEvery { financialActionPersister.updateStatus(any(), any()) } returns Unit - coEvery { userLevelLoader.load(any()) } returns "*" coEvery { financialActionPublisher.publish(any()) } returns Unit } @@ -73,18 +72,17 @@ internal class OrderManagerImplTest { "ouid", "uuid", null, pair, 30, 60, 0, OrderDirection.ASK, MatchConstraint.GTC, OrderType.LIMIT_ORDER ) - coEvery { - pairConfigLoader.load(pair.toString(), submitOrderEvent.direction, any()) - } returns PairFeeConfig( - pairConfig, - submitOrderEvent.direction.toString(), - "", - BigDecimal.valueOf(0.1), - BigDecimal.valueOf(0.12) - ) - coEvery { financialActionPersister.persist(any()) } returnsArgument (0) + coEvery { + pairConfigLoader.load(pair.toString(), submitOrderEvent.direction) + } returns pairConfig + + coEvery { + feeCalculator.getUserFee(submitOrderEvent.uuid) + } returns UserFee( + "Test", 1, BigDecimal.valueOf(0.08), BigDecimal.valueOf(0.1) + ) //when val financialActions = orderManager.handleRequestOrder(submitOrderEvent) @@ -130,17 +128,17 @@ internal class OrderManagerImplTest { "ouid", "uuid", null, pair, 35, 14, 0, OrderDirection.BID, MatchConstraint.GTC, OrderType.LIMIT_ORDER ) + coEvery { financialActionPersister.persist(any()) } returnsArgument (0) + coEvery { - pairConfigLoader.load(pair.toString(), submitOrderEvent.direction, any()) - } returns PairFeeConfig( - pairConfig, - submitOrderEvent.direction.toString(), - "", - BigDecimal.valueOf(0.08), - BigDecimal.valueOf(0.1) - ) + pairConfigLoader.load(pair.toString(), submitOrderEvent.direction) + } returns pairConfig - coEvery { financialActionPersister.persist(any()) } returnsArgument (0) + coEvery { + feeCalculator.getUserFee(submitOrderEvent.uuid) + } returns UserFee( + "Test", 1, BigDecimal.valueOf(0.08), BigDecimal.valueOf(0.1) + ) //when val financialActions = orderManager.handleRequestOrder(submitOrderEvent) diff --git a/accountant/accountant-core/src/test/kotlin/co/nilin/opex/accountant/core/service/TradeManagerImplTest.kt b/accountant/accountant-core/src/test/kotlin/co/nilin/opex/accountant/core/service/TradeManagerImplTest.kt index ac5045208..d0489dfdf 100644 --- a/accountant/accountant-core/src/test/kotlin/co/nilin/opex/accountant/core/service/TradeManagerImplTest.kt +++ b/accountant/accountant-core/src/test/kotlin/co/nilin/opex/accountant/core/service/TradeManagerImplTest.kt @@ -1,7 +1,9 @@ package co.nilin.opex.accountant.core.service +import co.nilin.opex.accountant.core.api.FeeCalculator import co.nilin.opex.accountant.core.model.* import co.nilin.opex.accountant.core.spi.* +import co.nilin.opex.accountant.core.utils.CacheManager import co.nilin.opex.matching.engine.core.eventh.events.SubmitOrderEvent import co.nilin.opex.matching.engine.core.eventh.events.TradeEvent import co.nilin.opex.matching.engine.core.model.MatchConstraint @@ -9,7 +11,9 @@ import co.nilin.opex.matching.engine.core.model.OrderDirection import co.nilin.opex.matching.engine.core.model.OrderType import co.nilin.opex.matching.engine.core.model.Pair import io.mockk.coEvery +import io.mockk.just import io.mockk.mockk +import io.mockk.runs import kotlinx.coroutines.runBlocking import org.assertj.core.api.Assertions.assertThat import org.junit.jupiter.api.Test @@ -24,20 +28,25 @@ internal class TradeManagerImplTest { private val tempEventPersister = mockk() private val richOrderPublisher = mockk() private val richTradePublisher = mockk() - private val userLevelLoader = mockk() private val financialActionPublisher = mockk() + private val currencyRatePersister = mockk() + private val userVolumePersister = mockk() + private val feeCalculator = mockk() + private val walletProxy = mockk() + private val feeConfigService = mockk() + private val cacheManager = mockk>() + private val jsonMapper = JsonMapperTestImpl() private val orderManager = OrderManagerImpl( pairConfigLoader, - userLevelLoader, financialActionPersister, financeActionLoader, orderPersister, tempEventPersister, richOrderPublisher, financialActionPublisher, - jsonMapper + feeCalculator ) private val tradeManager = TradeManagerImpl( @@ -47,9 +56,17 @@ internal class TradeManagerImplTest { tempEventPersister, richTradePublisher, richOrderPublisher, - FeeCalculatorImpl("0x0", jsonMapper), + FeeCalculatorImpl( + walletProxy, + feeConfigService, + userVolumePersister, + cacheManager, + "0x0", + JsonMapperTestImpl() + ), financialActionPublisher, - jsonMapper + currencyRatePersister, + userVolumePersister, ) init { @@ -58,10 +75,11 @@ internal class TradeManagerImplTest { coEvery { financeActionLoader.findLast(any(), any()) } returns null coEvery { richOrderPublisher.publish(any()) } returns Unit coEvery { richTradePublisher.publish(any()) } returns Unit - coEvery { userLevelLoader.load(any()) } returns "*" coEvery { financialActionPublisher.publish(any()) } returns Unit coEvery { financialActionPersister.updateStatus(any(), any()) } returns Unit coEvery { financialActionPersister.updateStatus(any(), any()) } returns Unit + coEvery { currencyRatePersister.updateRate(any(), any(), any()) } just runs + coEvery { userVolumePersister.update(any(), any(), any(), any(), any(), any()) } just runs } @Test @@ -309,22 +327,23 @@ internal class TradeManagerImplTest { takerFee: BigDecimal ) { coEvery { - pairConfigLoader.load(pair.toString(), submitOrderEvent.direction, any()) - } returns PairFeeConfig( - pairConfig, - submitOrderEvent.direction.toString(), - "", - makerFee, - takerFee + feeCalculator.getUserFee(submitOrderEvent.uuid) + } returns UserFee( + "Test", 1, makerFee, takerFee ) + + coEvery { + pairConfigLoader.load(pair.toString(), submitOrderEvent.direction) + } returns pairConfig + coEvery { financialActionPersister.persist(any()) } returnsArgument (0) val financialActions = orderManager.handleRequestOrder(submitOrderEvent) - val orderPairFeeConfig = - pairConfigLoader.load(submitOrderEvent.pair.toString(), submitOrderEvent.direction, "") - val orderMakerFee = orderPairFeeConfig.makerFee * BigDecimal.ONE //user level formula - val orderTakerFee = orderPairFeeConfig.takerFee * BigDecimal.ONE //user level formula + val pairConfig = + pairConfigLoader.load(submitOrderEvent.pair.toString(), submitOrderEvent.direction) + val orderMakerFee = makerFee + val orderTakerFee = takerFee coEvery { orderPersister.load(submitOrderEvent.ouid) } returns Order( submitOrderEvent.pair.toString(), @@ -332,8 +351,8 @@ internal class TradeManagerImplTest { null, orderMakerFee, orderTakerFee, - orderPairFeeConfig.pairConfig.leftSideFraction, - orderPairFeeConfig.pairConfig.rightSideFraction, + pairConfig.leftSideFraction, + pairConfig.rightSideFraction, submitOrderEvent.uuid, submitOrderEvent.userLevel, submitOrderEvent.direction, diff --git a/accountant/accountant-ports/accountant-persister-postgres/src/main/kotlin/co/nilin/opex/accountant/ports/postgres/dao/CurrencyRateRepository.kt b/accountant/accountant-ports/accountant-persister-postgres/src/main/kotlin/co/nilin/opex/accountant/ports/postgres/dao/CurrencyRateRepository.kt new file mode 100644 index 000000000..76ab3e390 --- /dev/null +++ b/accountant/accountant-ports/accountant-persister-postgres/src/main/kotlin/co/nilin/opex/accountant/ports/postgres/dao/CurrencyRateRepository.kt @@ -0,0 +1,30 @@ +package co.nilin.opex.accountant.ports.postgres.dao + +import co.nilin.opex.accountant.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, rate) + values (:base, :quote, :rate) + on conflict (base, quote) + do update set rate = excluded.rate + """ + ) + fun createOrUpdate(base: String, quote: String, rate: BigDecimal): Mono + + @Query("select rate from currency_rate where base = :base and quote = :quote and source = :source") + fun findByBaseAndQuote(base: String, quote: String): Mono + + @Query("select * from currency_rate where quote = :quote and source = :source") + fun findAllByQuote(quote: 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/dao/FeeConfigRepository.kt b/accountant/accountant-ports/accountant-persister-postgres/src/main/kotlin/co/nilin/opex/accountant/ports/postgres/dao/FeeConfigRepository.kt new file mode 100644 index 000000000..c1cefcb8b --- /dev/null +++ b/accountant/accountant-ports/accountant-persister-postgres/src/main/kotlin/co/nilin/opex/accountant/ports/postgres/dao/FeeConfigRepository.kt @@ -0,0 +1,45 @@ +package co.nilin.opex.accountant.ports.postgres.dao + +import co.nilin.opex.accountant.core.model.FeeConfig +import co.nilin.opex.accountant.core.model.UserFee +import co.nilin.opex.accountant.ports.postgres.model.FeeConfigModel +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 FeeConfigRepository : ReactiveCrudRepository { + + @Query("select * from fee_config order by display_order") + fun findAllByOrder(): Flux + + fun findByName(name: String): Mono + + @Query( + """ + SELECT name,display_order,maker_fee,taker_fee + FROM fee_config + WHERE + ( + condition = 'AND' + AND (:assetVolume BETWEEN min_asset_volume AND COALESCE(max_asset_volume, :assetVolume)) + AND (:tradeVolume BETWEEN min_trade_volume AND COALESCE(max_trade_volume, :tradeVolume)) + ) + OR + ( + condition = 'OR' + AND ( + (:assetVolume BETWEEN min_asset_volume AND COALESCE(max_asset_volume, :assetVolume)) + OR (:tradeVolume BETWEEN min_trade_volume AND COALESCE(max_trade_volume, :tradeVolume)) + ) + ) + ORDER BY display_order DESC + LIMIT 1 + """ + ) + fun findMatchingConfig(assetVolume: BigDecimal, tradeVolume: BigDecimal): Mono + +} \ No newline at end of file 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 deleted file mode 100644 index 105bdc042..000000000 --- a/accountant/accountant-ports/accountant-persister-postgres/src/main/kotlin/co/nilin/opex/accountant/ports/postgres/dao/PairFeeConfigRepository.kt +++ /dev/null @@ -1,25 +0,0 @@ -package co.nilin.opex.accountant.ports.postgres.dao - -import co.nilin.opex.accountant.ports.postgres.model.PairFeeConfigModel -import co.nilin.opex.matching.engine.core.model.OrderDirection -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/dao/UserLevelMapperRepository.kt b/accountant/accountant-ports/accountant-persister-postgres/src/main/kotlin/co/nilin/opex/accountant/ports/postgres/dao/UserLevelMapperRepository.kt deleted file mode 100644 index 9156013b6..000000000 --- a/accountant/accountant-ports/accountant-persister-postgres/src/main/kotlin/co/nilin/opex/accountant/ports/postgres/dao/UserLevelMapperRepository.kt +++ /dev/null @@ -1,13 +0,0 @@ -package co.nilin.opex.accountant.ports.postgres.dao - -import co.nilin.opex.accountant.ports.postgres.model.UserLevelMapperModel -import org.springframework.data.repository.reactive.ReactiveCrudRepository -import org.springframework.stereotype.Repository -import reactor.core.publisher.Mono - -@Repository -interface UserLevelMapperRepository : ReactiveCrudRepository { - - fun findByUuid(uuid: String): Mono - -} \ No newline at end of file diff --git a/accountant/accountant-ports/accountant-persister-postgres/src/main/kotlin/co/nilin/opex/accountant/ports/postgres/dao/UserLevelRepository.kt b/accountant/accountant-ports/accountant-persister-postgres/src/main/kotlin/co/nilin/opex/accountant/ports/postgres/dao/UserLevelRepository.kt deleted file mode 100644 index 9bf546613..000000000 --- a/accountant/accountant-ports/accountant-persister-postgres/src/main/kotlin/co/nilin/opex/accountant/ports/postgres/dao/UserLevelRepository.kt +++ /dev/null @@ -1,17 +0,0 @@ -package co.nilin.opex.accountant.ports.postgres.dao - -import co.nilin.opex.accountant.ports.postgres.model.UserLevelModel -import org.springframework.data.r2dbc.repository.Query -import org.springframework.data.repository.reactive.ReactiveCrudRepository -import org.springframework.stereotype.Repository -import reactor.core.publisher.Mono - -@Repository -interface UserLevelRepository : ReactiveCrudRepository { - - fun findByLevel(level: String): Mono - - @Query("insert into user_level (level) values (:level) on conflict do nothing") - fun insert(level: String): Mono - -} \ No newline at end of file diff --git a/accountant/accountant-ports/accountant-persister-postgres/src/main/kotlin/co/nilin/opex/accountant/ports/postgres/dao/UserTradeVolumeRepository.kt b/accountant/accountant-ports/accountant-persister-postgres/src/main/kotlin/co/nilin/opex/accountant/ports/postgres/dao/UserTradeVolumeRepository.kt new file mode 100644 index 000000000..82e3f4ef7 --- /dev/null +++ b/accountant/accountant-ports/accountant-persister-postgres/src/main/kotlin/co/nilin/opex/accountant/ports/postgres/dao/UserTradeVolumeRepository.kt @@ -0,0 +1,59 @@ +package co.nilin.opex.accountant.ports.postgres.dao + +import co.nilin.opex.accountant.core.inout.UserCurrencyVolume +import co.nilin.opex.accountant.core.inout.UserTotalVolumeValue +import co.nilin.opex.accountant.ports.postgres.model.UserTradeVolumeModel +import org.springframework.data.r2dbc.repository.Query +import org.springframework.data.repository.reactive.ReactiveCrudRepository +import org.springframework.stereotype.Repository +import reactor.core.publisher.Mono +import java.math.BigDecimal +import java.time.LocalDate + +@Repository +interface UserTradeVolumeRepository : ReactiveCrudRepository { + + @Query( + """ + insert into user_trade_volume (user_id, currency, date, volume, value_usdt, value_irt) + values (:userId, :currency, :date, :volume, :valueUSDT, :valueIRT) + on conflict (user_id, currency, date) + do update + set volume = user_trade_volume.volume + EXCLUDED.volume, + value_usdt = user_trade_volume.value_usdt + EXCLUDED.value_usdt, + value_irt = user_trade_volume.value_irt + EXCLUDED.value_irt + """ + ) + fun insertOrUpdate( + userId: String, + currency: String, + date: LocalDate, + volume: BigDecimal, + valueUSDT: BigDecimal, + valueIRT: BigDecimal + ): Mono + + @Query( + """ + select sum(value_usdt) as value_USDT, sum(value_irt) as value_IRT + from user_trade_volume + where user_id = :userId and date >= :startDate + group by user_id + """ + ) + fun findTotalValueByUserAndAndDateAfter(userId: String, startDate: LocalDate): Mono + + @Query( + """ + select currency, sum(volume) as volume, sum(value_usdt) as value_USDT, sum(value_irt) as value_IRT + from user_trade_volume + where user_id = :userId and date >= :startDate and currency = :currency + group by currency + """ + ) + fun findByUserAndCurrencyAndDateAfter( + userId: String, + currency: String, + startDate: LocalDate + ): Mono +} \ 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/CurrencyRatePersisterImpl.kt b/accountant/accountant-ports/accountant-persister-postgres/src/main/kotlin/co/nilin/opex/accountant/ports/postgres/impl/CurrencyRatePersisterImpl.kt new file mode 100644 index 000000000..cbeb0dd06 --- /dev/null +++ b/accountant/accountant-ports/accountant-persister-postgres/src/main/kotlin/co/nilin/opex/accountant/ports/postgres/impl/CurrencyRatePersisterImpl.kt @@ -0,0 +1,19 @@ +package co.nilin.opex.accountant.ports.postgres.impl + +import co.nilin.opex.accountant.core.spi.CurrencyRatePersister +import co.nilin.opex.accountant.ports.postgres.dao.CurrencyRateRepository +import kotlinx.coroutines.reactor.awaitSingleOrNull +import org.springframework.stereotype.Component +import java.math.BigDecimal + +@Component +class CurrencyRatePersisterImpl(private val repository: CurrencyRateRepository) : CurrencyRatePersister { + + override suspend fun updateRate(base: String, quote: String, rate: BigDecimal) { + repository.createOrUpdate(base, quote, rate).awaitSingleOrNull() + } + + override suspend fun getRate(base: String, quote: String): BigDecimal { + return repository.findByBaseAndQuote(base, quote).awaitSingleOrNull() ?: BigDecimal.ZERO + } +} \ 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/FeeConfigServiceImpl.kt b/accountant/accountant-ports/accountant-persister-postgres/src/main/kotlin/co/nilin/opex/accountant/ports/postgres/impl/FeeConfigServiceImpl.kt new file mode 100644 index 000000000..13a8dae4d --- /dev/null +++ b/accountant/accountant-ports/accountant-persister-postgres/src/main/kotlin/co/nilin/opex/accountant/ports/postgres/impl/FeeConfigServiceImpl.kt @@ -0,0 +1,65 @@ +package co.nilin.opex.accountant.ports.postgres.impl + +import co.nilin.opex.accountant.core.model.FeeConfig +import co.nilin.opex.accountant.core.model.UserFee +import co.nilin.opex.accountant.core.spi.FeeConfigService +import co.nilin.opex.accountant.ports.postgres.dao.FeeConfigRepository +import co.nilin.opex.accountant.ports.postgres.model.FeeConfigModel +import co.nilin.opex.common.OpexError +import kotlinx.coroutines.reactive.awaitFirst +import kotlinx.coroutines.reactive.awaitFirstOrElse +import kotlinx.coroutines.reactive.awaitFirstOrNull +import org.springframework.stereotype.Component +import java.math.BigDecimal + +@Component +class FeeConfigServiceImpl( + private val feeConfigRepository: FeeConfigRepository, +) : FeeConfigService { + + override suspend fun loadFeeConfigs(): List { + return feeConfigRepository.findAllByOrder() + .collectList() + .awaitFirstOrElse { emptyList() } + + } + + override suspend fun saveFee(config: FeeConfig) { + feeConfigRepository.findByName(config.name).awaitFirstOrNull()?.let { + throw OpexError.FeeConfigAlreadyExists.exception("FeeConfig with name '${config.name}' already exists") + } + feeConfigRepository.save(config.asFeeConfigModel()).awaitFirst() + } + + override suspend fun updateFee(config: FeeConfig) { + feeConfigRepository.findByName(config.name).awaitFirstOrNull() + ?: throw OpexError.FeeConfigNotFound.exception("FeeConfig with name '${config.name}' not found") + + feeConfigRepository.save(config.asFeeConfigModel()).awaitFirst() + } + + override suspend fun loadFeeConfig(name: String): FeeConfig { + return feeConfigRepository.findByName(name).awaitFirstOrNull() + ?: throw OpexError.FeeConfigNotFound.exception("FeeConfig with name '${name}' not found") + } + + override suspend fun loadMatchingFeeConfig( + assetVolume: BigDecimal, + tradeVolume: BigDecimal + ): UserFee { + return feeConfigRepository.findMatchingConfig(assetVolume, tradeVolume).awaitFirst() + } + + private fun FeeConfig.asFeeConfigModel() = + FeeConfigModel( + name, + displayOrder, + minAssetVolume, + maxAssetVolume, + minTradeVolume, + maxTradeVolume, + makerFee, + takerFee, + condition + ) +} \ 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 78487926e..5fe137ca4 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 @@ -1,24 +1,17 @@ package co.nilin.opex.accountant.ports.postgres.impl import co.nilin.opex.accountant.core.model.PairConfig -import co.nilin.opex.accountant.core.model.PairFeeConfig import co.nilin.opex.accountant.core.spi.PairConfigLoader import co.nilin.opex.accountant.ports.postgres.dao.PairConfigRepository -import co.nilin.opex.accountant.ports.postgres.dao.PairFeeConfigRepository import co.nilin.opex.accountant.ports.postgres.model.PairConfigModel -import co.nilin.opex.accountant.ports.postgres.model.PairFeeConfigModel import co.nilin.opex.common.OpexError import co.nilin.opex.matching.engine.core.model.OrderDirection 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 @Component class PairConfigLoaderImpl( private val pairConfigRepository: PairConfigRepository, - private val pairFeeConfigRepository: PairFeeConfigRepository ) : PairConfigLoader { override suspend fun loadPairConfigs(): List { @@ -28,89 +21,6 @@ class PairConfigLoaderImpl( .map { it.asPairConfig() } } - override suspend fun loadPairFeeConfigs(): List { - return pairFeeConfigRepository.findAll() - .collectList() - .awaitFirstOrElse { emptyList() } - .map { - val pairConfig = pairConfigRepository.findById(it.pairConfigId).awaitSingle().asPairConfig() - PairFeeConfig(pairConfig, it.direction, it.userLevel, it.makerFee, it.takerFee) - } - } - - override suspend fun loadPairFeeConfigs(direction: OrderDirection, userLevel: String): List { - return pairFeeConfigRepository.findByDirectionAndUserLevel(direction, userLevel) - .collectList() - .awaitFirstOrElse { emptyList() } - .map { - val pairConfig = pairConfigRepository.findById(it.pairConfigId).awaitSingle().asPairConfig() - PairFeeConfig( - pairConfig, - 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 - val pairConfig = pairConfigRepository.findById(fee.pairConfigId).awaitSingle().asPairConfig() - return PairFeeConfig( - pairConfig, - 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 { - throw OpexError.InvalidPair.messageFormattedException(pair) - } - - var pairFeeConfig: PairFeeConfigModel? - if (userLevel.isEmpty()) { - pairFeeConfig = pairFeeConfigRepository - .findByPairAndDirectionAndUserLevel(pair, direction, "*") - .awaitFirstOrElse { - throw OpexError.InvalidPair.messageFormattedException(pair) - } - } else { - pairFeeConfig = pairFeeConfigRepository - .findByPairAndDirectionAndUserLevel(pair, direction, userLevel) - .awaitFirstOrNull() - if (pairFeeConfig == null) { - pairFeeConfig = pairFeeConfigRepository - .findByPairAndDirectionAndUserLevel(pair, direction, "*") - .awaitFirstOrElse { - throw OpexError.InvalidPair.messageFormattedException(pair) - } - } - } - - return PairFeeConfig( - PairConfig( - pair, - pairConfig.leftSideWalletSymbol, - pairConfig.rightSideWalletSymbol, - pairConfig.leftSideFraction, - pairConfig.rightSideFraction - ), - pairFeeConfig!!.direction, - pairFeeConfig.userLevel, - pairFeeConfig.makerFee, - pairFeeConfig.takerFee - ) - } - override suspend fun load(pair: String, direction: OrderDirection): PairConfig { return pairConfigRepository .findById(pair).awaitFirstOrElse { diff --git a/accountant/accountant-ports/accountant-persister-postgres/src/main/kotlin/co/nilin/opex/accountant/ports/postgres/impl/UserLevelLoaderImpl.kt b/accountant/accountant-ports/accountant-persister-postgres/src/main/kotlin/co/nilin/opex/accountant/ports/postgres/impl/UserLevelLoaderImpl.kt deleted file mode 100644 index 4ecd0b13f..000000000 --- a/accountant/accountant-ports/accountant-persister-postgres/src/main/kotlin/co/nilin/opex/accountant/ports/postgres/impl/UserLevelLoaderImpl.kt +++ /dev/null @@ -1,46 +0,0 @@ -package co.nilin.opex.accountant.ports.postgres.impl - -import co.nilin.opex.accountant.core.model.KycLevel -import co.nilin.opex.accountant.core.spi.UserLevelLoader -import co.nilin.opex.accountant.ports.postgres.dao.UserLevelMapperRepository -import co.nilin.opex.accountant.ports.postgres.dao.UserLevelRepository -import co.nilin.opex.accountant.ports.postgres.model.UserLevelMapperModel -import kotlinx.coroutines.reactor.awaitSingleOrNull -import org.springframework.stereotype.Component - -@Component -class UserLevelLoaderImpl( - private val userLevelMapperRepository: UserLevelMapperRepository, - private val userLevelRepository: UserLevelRepository -) : UserLevelLoader { - - override suspend fun load(uuid: String): String { - val mapper = userLevelMapperRepository.findByUuid(uuid).awaitSingleOrNull() - return mapper?.userLevel ?: "*" - } - - override suspend fun update(uuid: String, userLevel: KycLevel) { - - userLevelRepository.findByLevel(userLevel.name).awaitSingleOrNull()?.let { - userLevelMapperRepository.findByUuid(uuid).awaitSingleOrNull() - ?.let { - userLevelMapperRepository.save(UserLevelMapperModel(it.id, it.uuid, userLevel.name)) - .awaitSingleOrNull() - } - ?: run { - userLevelMapperRepository.save(UserLevelMapperModel(null, uuid, userLevel.name)).awaitSingleOrNull() - } - } ?: run { - userLevelRepository.insert(userLevel.name).awaitSingleOrNull() - userLevelMapperRepository.findByUuid(uuid).awaitSingleOrNull() - ?.let { - userLevelMapperRepository.save(UserLevelMapperModel(it.id, it.uuid, userLevel.name)) - .awaitSingleOrNull() - } - ?: run { - userLevelMapperRepository.save(UserLevelMapperModel(null, uuid, userLevel.name)).awaitSingleOrNull() - } - - } - } -} \ 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/UserVolumePersisterImpl.kt b/accountant/accountant-ports/accountant-persister-postgres/src/main/kotlin/co/nilin/opex/accountant/ports/postgres/impl/UserVolumePersisterImpl.kt new file mode 100644 index 000000000..5a02f5a2b --- /dev/null +++ b/accountant/accountant-ports/accountant-persister-postgres/src/main/kotlin/co/nilin/opex/accountant/ports/postgres/impl/UserVolumePersisterImpl.kt @@ -0,0 +1,28 @@ +package co.nilin.opex.accountant.ports.postgres.impl + +import co.nilin.opex.accountant.core.inout.UserTotalVolumeValue +import co.nilin.opex.accountant.core.spi.UserVolumePersister +import co.nilin.opex.accountant.ports.postgres.dao.UserTradeVolumeRepository +import kotlinx.coroutines.reactor.awaitSingleOrNull +import org.springframework.stereotype.Component +import java.math.BigDecimal +import java.time.LocalDate + +@Component +class UserVolumePersisterImpl(private val repository: UserTradeVolumeRepository) : UserVolumePersister { + + override suspend fun update( + userId: String, + currency: String, + date: LocalDate, + volume: BigDecimal, + valueUSDT: BigDecimal, + valueIRT: BigDecimal + ) { + repository.insertOrUpdate(userId, currency, date, volume, valueUSDT, valueIRT).awaitSingleOrNull() + } + + override suspend fun getUserVolumeData(uuid: String, startDate: LocalDate): UserTotalVolumeValue? { + return repository.findTotalValueByUserAndAndDateAfter(uuid, startDate).awaitSingleOrNull() + } +} \ No newline at end of file diff --git a/accountant/accountant-ports/accountant-persister-postgres/src/main/kotlin/co/nilin/opex/accountant/ports/postgres/model/CurrencyRateModel.kt b/accountant/accountant-ports/accountant-persister-postgres/src/main/kotlin/co/nilin/opex/accountant/ports/postgres/model/CurrencyRateModel.kt new file mode 100644 index 000000000..e3c929322 --- /dev/null +++ b/accountant/accountant-ports/accountant-persister-postgres/src/main/kotlin/co/nilin/opex/accountant/ports/postgres/model/CurrencyRateModel.kt @@ -0,0 +1,13 @@ +package co.nilin.opex.accountant.ports.postgres.model + +import org.springframework.data.annotation.Id +import org.springframework.data.relational.core.mapping.Table +import java.math.BigDecimal + +@Table("currency_rate") +data class CurrencyRateModel( + val base: String, + val quote: String, + val rate: BigDecimal, + @Id val id: Long? = null, +) \ No newline at end of file diff --git a/accountant/accountant-ports/accountant-persister-postgres/src/main/kotlin/co/nilin/opex/accountant/ports/postgres/model/FeeConfigModel.kt b/accountant/accountant-ports/accountant-persister-postgres/src/main/kotlin/co/nilin/opex/accountant/ports/postgres/model/FeeConfigModel.kt new file mode 100644 index 000000000..49446a51c --- /dev/null +++ b/accountant/accountant-ports/accountant-persister-postgres/src/main/kotlin/co/nilin/opex/accountant/ports/postgres/model/FeeConfigModel.kt @@ -0,0 +1,21 @@ +package co.nilin.opex.accountant.ports.postgres.model + +import co.nilin.opex.accountant.core.model.Condition +import org.springframework.data.annotation.Id +import org.springframework.data.relational.core.mapping.Table +import java.math.BigDecimal + +@Table(name = "fee_config") +data class FeeConfigModel( + @Id + val name: String, + val displayOrder: Int, + val minAssetVolume: BigDecimal, + val maxAssetVolume: BigDecimal? = null, + val minTradeVolume: BigDecimal, + val maxTradeVolume: BigDecimal? = null, + val makerFee: BigDecimal, + val takerFee: BigDecimal, + val condition: Condition, +) + diff --git a/accountant/accountant-ports/accountant-persister-postgres/src/main/kotlin/co/nilin/opex/accountant/ports/postgres/model/PairFeeConfigModel.kt b/accountant/accountant-ports/accountant-persister-postgres/src/main/kotlin/co/nilin/opex/accountant/ports/postgres/model/PairFeeConfigModel.kt deleted file mode 100644 index cd46a2abc..000000000 --- a/accountant/accountant-ports/accountant-persister-postgres/src/main/kotlin/co/nilin/opex/accountant/ports/postgres/model/PairFeeConfigModel.kt +++ /dev/null @@ -1,15 +0,0 @@ -package co.nilin.opex.accountant.ports.postgres.model - -import org.springframework.data.relational.core.mapping.Column -import org.springframework.data.relational.core.mapping.Table -import java.math.BigDecimal - -@Table("pair_fee_config") -data class PairFeeConfigModel( - val id: Long?, - @Column("pair_config_id") val pairConfigId: String, - @Column("direction") val direction: String, - @Column("user_level") val userLevel: String, - @Column("maker_fee") val makerFee: BigDecimal, - @Column("taker_fee") val takerFee: BigDecimal -) \ No newline at end of file diff --git a/accountant/accountant-ports/accountant-persister-postgres/src/main/kotlin/co/nilin/opex/accountant/ports/postgres/model/UserLevelMapperModel.kt b/accountant/accountant-ports/accountant-persister-postgres/src/main/kotlin/co/nilin/opex/accountant/ports/postgres/model/UserLevelMapperModel.kt deleted file mode 100644 index af4c5512e..000000000 --- a/accountant/accountant-ports/accountant-persister-postgres/src/main/kotlin/co/nilin/opex/accountant/ports/postgres/model/UserLevelMapperModel.kt +++ /dev/null @@ -1,11 +0,0 @@ -package co.nilin.opex.accountant.ports.postgres.model - -import org.springframework.data.annotation.Id -import org.springframework.data.relational.core.mapping.Table - -@Table("user_level_mapper") -data class UserLevelMapperModel( - @Id val id: Long?, - val uuid: String, - val userLevel: String -) \ No newline at end of file diff --git a/accountant/accountant-ports/accountant-persister-postgres/src/main/kotlin/co/nilin/opex/accountant/ports/postgres/model/UserLevelModel.kt b/accountant/accountant-ports/accountant-persister-postgres/src/main/kotlin/co/nilin/opex/accountant/ports/postgres/model/UserLevelModel.kt deleted file mode 100644 index 2e9af91b0..000000000 --- a/accountant/accountant-ports/accountant-persister-postgres/src/main/kotlin/co/nilin/opex/accountant/ports/postgres/model/UserLevelModel.kt +++ /dev/null @@ -1,7 +0,0 @@ -package co.nilin.opex.accountant.ports.postgres.model - -import org.springframework.data.annotation.Id -import org.springframework.data.relational.core.mapping.Table - -@Table("user_level") -data class UserLevelModel(@Id val level: String) \ No newline at end of file diff --git a/accountant/accountant-ports/accountant-persister-postgres/src/main/kotlin/co/nilin/opex/accountant/ports/postgres/model/UserTradeVolumeModel.kt b/accountant/accountant-ports/accountant-persister-postgres/src/main/kotlin/co/nilin/opex/accountant/ports/postgres/model/UserTradeVolumeModel.kt new file mode 100644 index 000000000..01c1b6386 --- /dev/null +++ b/accountant/accountant-ports/accountant-persister-postgres/src/main/kotlin/co/nilin/opex/accountant/ports/postgres/model/UserTradeVolumeModel.kt @@ -0,0 +1,20 @@ +package co.nilin.opex.accountant.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.LocalDate + +@Table("user_trade_volume") +class UserTradeVolumeModel( + val userId: String, + val currency: String, + val date: LocalDate, + val volume: BigDecimal, + @Column("value_usdt") + val valueUSDT: BigDecimal, + @Column("value_irt") + val valueIRT: BigDecimal, + @Id val id: Long? = null +) \ No newline at end of file diff --git a/accountant/accountant-ports/accountant-persister-postgres/src/main/resources/schema.sql b/accountant/accountant-ports/accountant-persister-postgres/src/main/resources/schema.sql index c541c8e59..02bf568eb 100644 --- a/accountant/accountant-ports/accountant-persister-postgres/src/main/resources/schema.sql +++ b/accountant/accountant-ports/accountant-persister-postgres/src/main/resources/schema.sql @@ -1,276 +1,134 @@ CREATE TABLE IF NOT EXISTS orders ( - id - SERIAL - PRIMARY - KEY, - ouid - VARCHAR -( - 72 -) NOT NULL UNIQUE, - uuid VARCHAR -( - 72 -) NOT NULL, - pair VARCHAR -( - 72 -) NOT NULL, - matching_engine_id INTEGER, - maker_fee DECIMAL NOT NULL, - taker_fee DECIMAL NOT NULL, - left_side_fraction DECIMAL NOT NULL, - right_side_fraction DECIMAL NOT NULL, - user_level VARCHAR -( - 20 -) NOT NULL, - direction VARCHAR -( - 20 -) NOT NULL, - match_constraint VARCHAR -( - 30 -) NOT NULL, - order_type VARCHAR -( - 30 -) NOT NULL, - price DECIMAL NOT NULL, - quantity DECIMAL NOT NULL, - filled_quantity DECIMAL NOT NULL, - orig_price DECIMAL NOT NULL, - orig_quantity DECIMAL NOT NULL, - filled_orig_quantity DECIMAL NOT NULL, - first_transfer_amount DECIMAL NOT NULL, - remained_transfer_amount DECIMAL NOT NULL, - status INTEGER NOT NULL, - agent VARCHAR -( - 20 -), - ip VARCHAR -( - 11 -), - create_date TIMESTAMP NOT NULL - ); + id SERIAL PRIMARY KEY, + ouid VARCHAR(72) NOT NULL UNIQUE, + uuid VARCHAR(72) NOT NULL, + pair VARCHAR(72) NOT NULL, + matching_engine_id INTEGER, + maker_fee DECIMAL NOT NULL, + taker_fee DECIMAL NOT NULL, + left_side_fraction DECIMAL NOT NULL, + right_side_fraction DECIMAL NOT NULL, + user_level VARCHAR(20) NOT NULL, + direction VARCHAR(20) NOT NULL, + match_constraint VARCHAR(30) NOT NULL, + order_type VARCHAR(30) NOT NULL, + price DECIMAL NOT NULL, + quantity DECIMAL NOT NULL, + filled_quantity DECIMAL NOT NULL, + orig_price DECIMAL NOT NULL, + orig_quantity DECIMAL NOT NULL, + filled_orig_quantity DECIMAL NOT NULL, + first_transfer_amount DECIMAL NOT NULL, + remained_transfer_amount DECIMAL NOT NULL, + status INTEGER NOT NULL, + agent VARCHAR(20), + ip VARCHAR(11), + create_date TIMESTAMP NOT NULL +); CREATE TABLE IF NOT EXISTS fi_actions ( - id - SERIAL - PRIMARY - KEY, - uuid - VARCHAR -( - 72 -) NOT NULL UNIQUE, - parent_id INTEGER, - event_type VARCHAR -( - 72 -) NOT NULL, - pointer VARCHAR -( - 72 -) NOT NULL, - symbol VARCHAR -( - 36 -) NOT NULL, - amount DECIMAL NOT NULL, - sender VARCHAR -( - 36 -) NOT NULL, - sender_wallet_type VARCHAR -( - 36 -) NOT NULL, - receiver VARCHAR -( - 36 -) NOT NULL, - receiver_wallet_type VARCHAR -( - 36 -) NOT NULL, - agent VARCHAR -( - 20 -), - ip VARCHAR -( - 11 -), - create_date TIMESTAMP NOT NULL, - status VARCHAR -( - 20 -) - ); -CREATE INDEX IF NOT EXISTS idx_fi_actions_symbol ON fi_actions(symbol); -CREATE INDEX IF NOT EXISTS idx_fi_event_type ON fi_actions(event_type); -CREATE INDEX IF NOT EXISTS idx_fi_actions_status ON fi_actions(status); -CREATE INDEX IF NOT EXISTS idx_fi_actions_pointer ON fi_actions(pointer); + id SERIAL PRIMARY KEY, + uuid VARCHAR(72) NOT NULL UNIQUE, + parent_id INTEGER, + event_type VARCHAR(72) NOT NULL, + pointer VARCHAR(72) NOT NULL, + symbol VARCHAR(36) NOT NULL, + amount DECIMAL NOT NULL, + sender VARCHAR(36) NOT NULL, + sender_wallet_type VARCHAR(36) NOT NULL, + receiver VARCHAR(36) NOT NULL, + receiver_wallet_type VARCHAR(36) NOT NULL, + agent VARCHAR(20), + ip VARCHAR(11), + create_date TIMESTAMP NOT NULL, + status VARCHAR(20) +); +CREATE INDEX IF NOT EXISTS idx_fi_actions_symbol ON fi_actions (symbol); +CREATE INDEX IF NOT EXISTS idx_fi_event_type ON fi_actions (event_type); +CREATE INDEX IF NOT EXISTS idx_fi_actions_status ON fi_actions (status); +CREATE INDEX IF NOT EXISTS idx_fi_actions_pointer ON fi_actions (pointer); ALTER TABLE fi_actions - ADD COLUMN IF NOT EXISTS category_name VARCHAR (36); + ADD COLUMN IF NOT EXISTS category_name VARCHAR(36); CREATE TABLE IF NOT EXISTS fi_action_retry ( - id - SERIAL - PRIMARY - KEY, - fa_id - INTEGER - NOT - NULL - UNIQUE - REFERENCES - fi_actions -( - id -), - retries INTEGER NOT NULL DEFAULT 0, + id SERIAL PRIMARY KEY, + fa_id INTEGER NOT NULL UNIQUE REFERENCES fi_actions (id), + retries INTEGER NOT NULL DEFAULT 0, next_run_time TIMESTAMP NOT NULL, - is_resolved BOOLEAN NOT NULL DEFAULT false, - has_given_up BOOLEAN NOT NULL DEFAULT false - ); + is_resolved BOOLEAN NOT NULL DEFAULT false, + has_given_up BOOLEAN NOT NULL DEFAULT false +); CREATE TABLE IF NOT EXISTS fi_action_error ( - id - SERIAL - PRIMARY - KEY, - fa_id - INTEGER - NOT - NULL - REFERENCES - fi_actions -( - id -), - error TEXT NOT NULL, - message TEXT NOT NULL, - body TEXT, - retry_id INTEGER REFERENCES fi_action_retry -( - id -), - date TIMESTAMP NOT NULL DEFAULT CURRENT_DATE - ); + id SERIAL PRIMARY KEY, + fa_id INTEGER NOT NULL REFERENCES fi_actions (id), + error TEXT NOT NULL, + message TEXT NOT NULL, + body TEXT, + retry_id INTEGER REFERENCES fi_action_retry (id), + date TIMESTAMP NOT NULL DEFAULT CURRENT_DATE +); CREATE TABLE IF NOT EXISTS pair_config ( - pair VARCHAR -( - 72 -) PRIMARY KEY, - left_side_wallet_symbol VARCHAR -( - 36 -) NOT NULL, - right_side_wallet_symbol VARCHAR -( - 36 -) NOT NULL, - left_side_fraction DECIMAL NOT NULL, - right_side_fraction DECIMAL NOT NULL, - UNIQUE -( - left_side_wallet_symbol, - right_side_wallet_symbol -) - ); + pair VARCHAR(72) PRIMARY KEY, + left_side_wallet_symbol VARCHAR(36) NOT NULL, + right_side_wallet_symbol VARCHAR(36) NOT NULL, + left_side_fraction DECIMAL NOT NULL, + right_side_fraction DECIMAL NOT NULL, + UNIQUE (left_side_wallet_symbol, right_side_wallet_symbol) +); -CREATE TABLE IF NOT EXISTS user_level -( - level VARCHAR +CREATE TABLE IF NOT EXISTS temp_events ( - 36 -) PRIMARY KEY - ); + id SERIAL PRIMARY KEY, + ouid VARCHAR(72) NOT NULL, + event_type VARCHAR(72) NOT NULL, + event_body TEXT NOT NULL, + event_date TIMESTAMP NOT NULL +); -CREATE TABLE IF NOT EXISTS pair_fee_config -( - id - SERIAL - PRIMARY - KEY, - pair_config_id - VARCHAR -( - 72 -) NOT NULL REFERENCES pair_config -( - pair -), - direction VARCHAR -( - 36 -) NOT NULL, - user_level VARCHAR -( - 36 -) NOT NULL REFERENCES user_level -( - level -), - maker_fee DECIMAL NOT NULL, - taker_fee DECIMAL NOT NULL, - UNIQUE -( - direction, - user_level, - pair_config_id -) - ); +CREATE TABLE IF NOT EXISTS user_trade_volume +( + id SERIAL PRIMARY KEY, + user_id VARCHAR(36) NOT NULL, + currency TEXT NOT NULL, + date DATE not null, + volume decimal not null, + value_usdt decimal not null, + value_irt decimal not null, + unique (user_id, currency, date) +); -CREATE TABLE IF NOT EXISTS user_level_mapper -( - id - SERIAL - PRIMARY - KEY, - uuid - VARCHAR -( - 36 -) NOT NULL UNIQUE, - user_level VARCHAR -( - 36 -) NOT NULL REFERENCES user_level -( - level -) - ); +CREATE TABLE IF NOT EXISTS currency_rate +( + id SERIAL PRIMARY KEY, + base VARCHAR(25) NOT NULL, + quote VARCHAR(25) NOT NULL, + rate DECIMAL NOT NULL DEFAULT 0, + UNIQUE (base, quote) +); -CREATE TABLE IF NOT EXISTS temp_events -( - id - SERIAL - PRIMARY - KEY, - ouid - VARCHAR -( - 72 -) NOT NULL, - event_type VARCHAR -( - 72 -) NOT NULL, - event_body TEXT NOT NULL, - event_date TIMESTAMP NOT NULL + CREATE TABLE IF NOT EXISTS fee_config ( + name VARCHAR(50) PRIMARY KEY, + display_order INTEGER NOT NULL UNIQUE, + min_asset_volume Decimal NOT NULL, + max_asset_volume Decimal, + min_trade_volume Decimal NOT NULL, + max_trade_volume Decimal, + maker_fee Decimal NOT NULL, + taker_fee Decimal NOT NULL, + condition VARCHAR(10) NOT NULL ); + DROP TABLE IF EXISTS pair_fee_config; + DROP TABLE IF EXISTS user_fee; + DROP TABLE IF EXISTS user_level_mapper; + DROP TABLE IF EXISTS user_level; + COMMIT; diff --git a/accountant/accountant-ports/accountant-persister-postgres/src/test/kotlin/co/nilin/opex/accountant/ports/postgres/PairConfigLoaderTest.kt b/accountant/accountant-ports/accountant-persister-postgres/src/test/kotlin/co/nilin/opex/accountant/ports/postgres/PairConfigLoaderTest.kt index 73da00a7f..37a22ed39 100644 --- a/accountant/accountant-ports/accountant-persister-postgres/src/test/kotlin/co/nilin/opex/accountant/ports/postgres/PairConfigLoaderTest.kt +++ b/accountant/accountant-ports/accountant-persister-postgres/src/test/kotlin/co/nilin/opex/accountant/ports/postgres/PairConfigLoaderTest.kt @@ -1,13 +1,11 @@ package co.nilin.opex.accountant.ports.postgres import co.nilin.opex.accountant.ports.postgres.dao.PairConfigRepository -import co.nilin.opex.accountant.ports.postgres.dao.PairFeeConfigRepository import co.nilin.opex.accountant.ports.postgres.impl.PairConfigLoaderImpl import co.nilin.opex.matching.engine.core.model.OrderDirection import co.nilin.opex.utility.error.data.OpexException import io.mockk.every import io.mockk.mockk -import io.mockk.verify import kotlinx.coroutines.runBlocking import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThatThrownBy @@ -22,11 +20,7 @@ class PairConfigLoaderTest { every { findAll() } returns Flux.just(Valid.pairConfigModel, Valid.pairConfigModel) every { findById(any() as String) } returns Mono.just(Valid.pairConfigModel) } - private val pairFeeConfigRepository = mockk { - every { findAll() } returns Flux.just(Valid.pairFeeConfigModel, Valid.pairFeeConfigModel) - every { findByPairAndDirectionAndUserLevel(any(), any(), any()) } returns Mono.just(Valid.pairFeeConfigModel) - } - private val pairConfigLoader = PairConfigLoaderImpl(pairConfigRepository, pairFeeConfigRepository) + private val pairConfigLoader = PairConfigLoaderImpl(pairConfigRepository) @Test fun givenPairConfigs_whenListNotEmpty_resultIsNotEmptyAndValid(): Unit = runBlocking { @@ -41,84 +35,14 @@ class PairConfigLoaderTest { } } - @Test - fun givenPairFeeConfigs_whenListNotEmpty_resultIsNotEmptyAndValid(): Unit = runBlocking { - val configs = pairConfigLoader.loadPairFeeConfigs() - assertThat(configs.size).isEqualTo(2) - with(configs[1]) { - assertThat(pairConfig.pair).isEqualTo(Valid.pairConfig.pair) - assertThat(userLevel).isEqualTo(Valid.pairFeeConfigModel.userLevel) - assertThat(direction).isEqualTo(Valid.pairFeeConfigModel.direction) - assertThat(makerFee).isEqualTo(Valid.pairFeeConfigModel.makerFee) - assertThat(takerFee).isEqualTo(Valid.pairFeeConfigModel.takerFee) - } - } - @Test fun givenPairDirectionUserLevel_whenPairConfigNotFound_throwsException(): Unit = runBlocking { every { pairConfigRepository.findById(any() as String) } returns Mono.empty() assertThatThrownBy { - runBlocking { pairConfigLoader.load("BTC_USDT", OrderDirection.BID, "*") } - }.isInstanceOf(OpexException::class.java) - } - - @Test - fun givenPairDirection_whenUserLevelEmpty_loadWithDefaultUserLevel(): Unit = runBlocking { - val pair = pairConfigLoader.load("BTC_USDT", OrderDirection.BID, "") - assertThat(pair).isNotNull - verify(exactly = 1) { - pairFeeConfigRepository.findByPairAndDirectionAndUserLevel( - eq("BTC_USDT"), - eq(OrderDirection.BID), - eq("*") - ) - } - } - - @Test - fun givenPairDirection_whenPairFeeConfigNotFound_throwsException(): Unit = runBlocking { - every { - pairFeeConfigRepository.findByPairAndDirectionAndUserLevel(any(), any(), eq("*")) - } returns Mono.empty() - - assertThatThrownBy { - runBlocking { pairConfigLoader.load("BTC_USDT", OrderDirection.BID, "") } + runBlocking { pairConfigLoader.load("BTC_USDT", OrderDirection.BID) } }.isInstanceOf(OpexException::class.java) } - @Test - fun givenPairDirectionUserLevel_whenPairFeeConfigNotFound_loadWithDefaultUserLevel(): Unit = runBlocking { - every { - pairFeeConfigRepository.findByPairAndDirectionAndUserLevel(any(), any(), eq("1")) - } returns Mono.empty() - - val pair = pairConfigLoader.load("BTC_USDT", OrderDirection.BID, "1") - assertThat(pair).isNotNull - verify(exactly = 1) { - pairFeeConfigRepository.findByPairAndDirectionAndUserLevel( - eq("BTC_USDT"), - eq(OrderDirection.BID), - eq("*") - ) - } - } - - @Test - fun givenPairDirectionUserLevel_whenPairFeeConfigNotFoundWithActualAndDefaultUserLevel_throwsException(): Unit = - runBlocking { - every { - pairFeeConfigRepository.findByPairAndDirectionAndUserLevel(any(), any(), eq("1")) - } returns Mono.empty() - - every { - pairFeeConfigRepository.findByPairAndDirectionAndUserLevel(any(), any(), eq("*")) - } returns Mono.empty() - - assertThatThrownBy { - runBlocking { pairConfigLoader.load("BTC_USDT", OrderDirection.BID, "1") } - }.isInstanceOf(OpexException::class.java) - } - @Test fun givenPairDirection_whenPairConfigNotFound_throwException(): Unit = runBlocking { every { pairConfigRepository.findById(any() as String) } returns Mono.empty() diff --git a/accountant/accountant-ports/accountant-persister-postgres/src/test/kotlin/co/nilin/opex/accountant/ports/postgres/Valid.kt b/accountant/accountant-ports/accountant-persister-postgres/src/test/kotlin/co/nilin/opex/accountant/ports/postgres/Valid.kt index af2eb5f88..b96047449 100644 --- a/accountant/accountant-ports/accountant-persister-postgres/src/test/kotlin/co/nilin/opex/accountant/ports/postgres/Valid.kt +++ b/accountant/accountant-ports/accountant-persister-postgres/src/test/kotlin/co/nilin/opex/accountant/ports/postgres/Valid.kt @@ -85,15 +85,6 @@ object Valid { 0.01.toBigDecimal() ) - val pairFeeConfigModel = PairFeeConfigModel( - 1, - "BTC_USDT", - "BID", - "1", - 0.01.toBigDecimal(), - 0.01.toBigDecimal() - ) - class TestCoreEvent(val leftSidePair: String, val rightSidePair: String) : CoreEvent(Pair(leftSidePair, rightSidePair), currentTime) diff --git a/accountant/accountant-ports/accountant-wallet-proxy/src/main/kotlin/co/nilin/opex/accountant/ports/walletproxy/proxy/WalletProxyImpl.kt b/accountant/accountant-ports/accountant-wallet-proxy/src/main/kotlin/co/nilin/opex/accountant/ports/walletproxy/proxy/WalletProxyImpl.kt index c9ed17d3e..01be95214 100644 --- a/accountant/accountant-ports/accountant-wallet-proxy/src/main/kotlin/co/nilin/opex/accountant/ports/walletproxy/proxy/WalletProxyImpl.kt +++ b/accountant/accountant-ports/accountant-wallet-proxy/src/main/kotlin/co/nilin/opex/accountant/ports/walletproxy/proxy/WalletProxyImpl.kt @@ -1,10 +1,12 @@ package co.nilin.opex.accountant.ports.walletproxy.proxy +import co.nilin.opex.accountant.core.model.TotalAssetsSnapshot import co.nilin.opex.accountant.core.model.WalletType import co.nilin.opex.accountant.core.spi.WalletProxy import co.nilin.opex.accountant.ports.walletproxy.data.BooleanResponse import co.nilin.opex.accountant.ports.walletproxy.data.TransferResult import kotlinx.coroutines.reactive.awaitFirst +import kotlinx.coroutines.reactive.awaitFirstOrNull import org.springframework.beans.factory.annotation.Value import org.springframework.http.MediaType import org.springframework.stereotype.Component @@ -56,4 +58,16 @@ class WalletProxyImpl( .awaitFirst() .result } + + override suspend fun getUserTotalAssets( + uuid: String, + ): TotalAssetsSnapshot? { + return webClient.get() + .uri("$walletBaseUrl/stats/total-assets/$uuid") + .header("Content-Type", "application/json") + .retrieve() + .onStatus({ t -> t.isError }, { it.createException() }) + .bodyToMono() + .awaitFirstOrNull() + } } \ No newline at end of file diff --git a/api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/FeeConfig.kt b/api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/FeeConfig.kt new file mode 100644 index 000000000..e7f29715b --- /dev/null +++ b/api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/FeeConfig.kt @@ -0,0 +1,17 @@ +package co.nilin.opex.api.core.inout + +import java.math.BigDecimal + +data class FeeConfig( + val name: String, + val displayOrder: Int, + val minAssetVolume: BigDecimal, + val maxAssetVolume: BigDecimal? = null, + val minTradeVolume: BigDecimal, + val maxTradeVolume: BigDecimal? = null, + val makerFee: BigDecimal, + val takerFee: BigDecimal, + val condition: Condition +) +enum class Condition { AND, OR } + diff --git a/api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/UserFee.kt b/api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/UserFee.kt new file mode 100644 index 000000000..176b9757d --- /dev/null +++ b/api/api-core/src/main/kotlin/co/nilin/opex/api/core/inout/UserFee.kt @@ -0,0 +1,13 @@ +package co.nilin.opex.api.core.inout + +import java.math.BigDecimal + +data class UserFee( + val name: String, + val displayOrder: Int, + val makerFee: BigDecimal, + val takerFee: BigDecimal, + + ) + + 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 14124a738..6f1b2a64f 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 @@ -1,14 +1,14 @@ package co.nilin.opex.api.core.spi -import co.nilin.opex.api.core.inout.PairFeeResponse +import co.nilin.opex.api.core.inout.FeeConfig import co.nilin.opex.api.core.inout.PairConfigResponse +import co.nilin.opex.api.core.inout.UserFee interface AccountantProxy { fun getPairConfigs(): List - fun getFeeConfigs(): List - - fun getFeeConfig(symbol: String): PairFeeResponse + fun getFeeConfigs(): List + fun getUserFee(uuid: String): UserFee } \ 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/WalletController.kt b/api/api-ports/api-binance-rest/src/main/kotlin/co/nilin/opex/api/ports/binance/controller/WalletController.kt index f601a756f..550fed030 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 @@ -188,39 +188,39 @@ class WalletController( // } // } - @GetMapping("/v1/asset/tradeFee") - fun getPairFees( - @RequestParam(required = false) - symbol: String?, - @RequestParam(required = false) - recvWindow: Long?, //The value cannot be greater than 60000 - @RequestParam - timestamp: Long - ): List { - return if (symbol != null) { - val internalSymbol = symbolMapper.toInternalSymbol(symbol) ?: throw OpexError.SymbolNotFound.exception() - - 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/tradeFee") +// fun getPairFees( +// @RequestParam(required = false) +// symbol: String?, +// @RequestParam(required = false) +// recvWindow: Long?, //The value cannot be greater than 60000 +// @RequestParam +// timestamp: Long +// ): List { +// return if (symbol != null) { +// val internalSymbol = symbolMapper.toInternalSymbol(symbol) ?: throw OpexError.SymbolNotFound.exception() +// +// 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") fun getUserAssets( 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 d2cbdc718..f27c92a21 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,6 +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.FeeConfig import java.util.* data class ExchangeInfoResponse( @@ -9,6 +9,6 @@ data class ExchangeInfoResponse( val rateLimits: List = RateLimit.values() .map { RateLimitResponse(it.rateLimitType, it.interval, it.intervalNum, it.limit) }, val exchangeFilters: List = emptyList(), - val fees: List = emptyList(), + val fees: List = emptyList(), val symbols: List ) \ No newline at end of file diff --git a/api/api-ports/api-opex-rest/src/main/kotlin/co/nilin/opex/api/ports/opex/controller/MarketController.kt b/api/api-ports/api-opex-rest/src/main/kotlin/co/nilin/opex/api/ports/opex/controller/MarketController.kt index da9cedfef..e91de1b06 100644 --- a/api/api-ports/api-opex-rest/src/main/kotlin/co/nilin/opex/api/ports/opex/controller/MarketController.kt +++ b/api/api-ports/api-opex-rest/src/main/kotlin/co/nilin/opex/api/ports/opex/controller/MarketController.kt @@ -8,8 +8,8 @@ import co.nilin.opex.api.ports.opex.data.OrderBookResponse import co.nilin.opex.api.ports.opex.data.RecentTradeResponse import co.nilin.opex.common.OpexError import co.nilin.opex.common.utils.Interval -import kotlinx.coroutines.async -import kotlinx.coroutines.coroutineScope +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.time.ZoneId @@ -58,8 +58,8 @@ class MarketController( return walletProxy.getGateWays(includeOffChainGateways, includeOnChainGateways) } - @GetMapping("/pair/fee") - fun getPairFees(): List { + @GetMapping("/fee") + fun getFeeConfigs(): List { return accountantProxy.getFeeConfigs() } @@ -67,7 +67,7 @@ class MarketController( fun getMarketStats( @RequestParam interval: String, @RequestParam(required = false) limit: Int? - ): MarketStatResponse { + ): MarketStatResponse { val intervalEnum = Interval.findByLabel(interval) ?: Interval.Week val validLimit = getValidLimit(limit) diff --git a/api/api-ports/api-opex-rest/src/main/kotlin/co/nilin/opex/api/ports/opex/controller/UserDataController.kt b/api/api-ports/api-opex-rest/src/main/kotlin/co/nilin/opex/api/ports/opex/controller/UserDataController.kt index be849eb15..756241e94 100644 --- a/api/api-ports/api-opex-rest/src/main/kotlin/co/nilin/opex/api/ports/opex/controller/UserDataController.kt +++ b/api/api-ports/api-opex-rest/src/main/kotlin/co/nilin/opex/api/ports/opex/controller/UserDataController.kt @@ -1,17 +1,25 @@ package co.nilin.opex.api.ports.opex.controller import co.nilin.opex.api.core.inout.UserCurrencyVolume +import co.nilin.opex.api.core.inout.UserFee import co.nilin.opex.api.core.inout.UserTotalVolumeValue +import co.nilin.opex.api.core.spi.AccountantProxy import co.nilin.opex.api.core.spi.MarketUserDataProxy import co.nilin.opex.common.OpexError import co.nilin.opex.common.utils.Interval import org.springframework.security.core.annotation.CurrentSecurityContext import org.springframework.security.core.context.SecurityContext -import org.springframework.web.bind.annotation.* +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 @RequestMapping("/opex/v1/user/data") -class UserDataController(private val proxy: MarketUserDataProxy) { +class UserDataController( + private val proxy: MarketUserDataProxy, + private val accountantProxy: AccountantProxy, +) { @GetMapping("/trade/volume") suspend fun getTradeVolumeByCurrency( @@ -34,6 +42,11 @@ class UserDataController(private val proxy: MarketUserDataProxy) { return proxy.getTotalTradeVolumeValue(uuid, interval) } + @GetMapping("/fee") + fun getUserFee(@CurrentSecurityContext securityContext: SecurityContext): UserFee { + return accountantProxy.getUserFee(securityContext.authentication.name) + } + private fun checkValidInterval(interval: Interval) { if (interval == Interval.Day || interval == Interval.Week || interval == Interval.Month || interval == Interval.Year) return diff --git a/api/api-ports/api-proxy-rest/src/main/kotlin/co/nilin/opex/api/ports/proxy/impl/AccountantProxyImpl.kt b/api/api-ports/api-proxy-rest/src/main/kotlin/co/nilin/opex/api/ports/proxy/impl/AccountantProxyImpl.kt index f8c82a8a6..d8b684241 100644 --- a/api/api-ports/api-proxy-rest/src/main/kotlin/co/nilin/opex/api/ports/proxy/impl/AccountantProxyImpl.kt +++ b/api/api-ports/api-proxy-rest/src/main/kotlin/co/nilin/opex/api/ports/proxy/impl/AccountantProxyImpl.kt @@ -1,9 +1,11 @@ package co.nilin.opex.api.ports.proxy.impl +import co.nilin.opex.api.core.inout.FeeConfig import co.nilin.opex.api.core.inout.PairConfigResponse -import co.nilin.opex.api.core.inout.PairFeeResponse +import co.nilin.opex.api.core.inout.UserFee import co.nilin.opex.api.core.spi.AccountantProxy import co.nilin.opex.api.ports.proxy.utils.noBody +import co.nilin.opex.common.OpexError import co.nilin.opex.common.utils.LoggerDelegate import org.springframework.beans.factory.annotation.Value import org.springframework.http.HttpMethod @@ -28,21 +30,21 @@ class AccountantProxyImpl(private val restTemplate: RestTemplate) : AccountantPr ).body?.toList() ?: emptyList() } - override fun getFeeConfigs(): List { + override fun getFeeConfigs(): List { logger.info("fetching fee configs") - return restTemplate.exchange>( - "$baseUrl/config/fee", + return restTemplate.exchange>( + "$baseUrl/fee/config", HttpMethod.GET, noBody() ).body?.toList() ?: emptyList() } - override fun getFeeConfig(symbol: String): PairFeeResponse { - logger.info("fetching fee configs for $symbol") - return restTemplate.exchange( - "$baseUrl/config/fee/$symbol", + override fun getUserFee(uuid: String): UserFee { + logger.info("fetching user fee") + return restTemplate.exchange( + "$baseUrl/fee/${uuid}", HttpMethod.GET, noBody() - ).body!! + ).body ?: throw OpexError.FeeConfigNotFound.exception() } } \ No newline at end of file diff --git a/common/src/main/kotlin/co/nilin/opex/common/OpexError.kt b/common/src/main/kotlin/co/nilin/opex/common/OpexError.kt index 8ed892852..202667dc0 100644 --- a/common/src/main/kotlin/co/nilin/opex/common/OpexError.kt +++ b/common/src/main/kotlin/co/nilin/opex/common/OpexError.kt @@ -23,6 +23,8 @@ enum class OpexError(val code: Int, val message: String?, val status: HttpStatus 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), + FeeConfigAlreadyExists(2003, "Fee config already exists", HttpStatus.CONFLICT), + FeeConfigNotFound(2004, "Fee config not found", HttpStatus.NOT_FOUND), // code 3000: matching-engine diff --git a/market/market-app/src/main/kotlin/co/nilin/opex/market/app/service/ReportingService.kt b/market/market-app/src/main/kotlin/co/nilin/opex/market/app/service/ReportingService.kt index f4a952100..af95b163f 100644 --- a/market/market-app/src/main/kotlin/co/nilin/opex/market/app/service/ReportingService.kt +++ b/market/market-app/src/main/kotlin/co/nilin/opex/market/app/service/ReportingService.kt @@ -16,9 +16,9 @@ class ReportingService(private val marketQueryHandler: MarketQueryHandler) { runBlocking { try { val count = marketQueryHandler.numberOfOrders(Interval.FifteenMinutes, null) - logger.info("in the last 30 minutes, the number of orders : $count") + logger.info("In the last 30 minutes, the number of orders : $count") } catch (e: Exception) { - logger.error("Could not report orders cont", e) + logger.error("Could not report orders count", e) } } } diff --git a/market/market-ports/market-persister-postgres/src/main/kotlin/co/nilin/opex/market/ports/postgres/impl/TradePersisterImpl.kt b/market/market-ports/market-persister-postgres/src/main/kotlin/co/nilin/opex/market/ports/postgres/impl/TradePersisterImpl.kt index 652435bfd..7e44f7147 100644 --- a/market/market-ports/market-persister-postgres/src/main/kotlin/co/nilin/opex/market/ports/postgres/impl/TradePersisterImpl.kt +++ b/market/market-ports/market-persister-postgres/src/main/kotlin/co/nilin/opex/market/ports/postgres/impl/TradePersisterImpl.kt @@ -68,7 +68,7 @@ class TradePersisterImpl( ).awaitFirstOrNull() logger.info("Rate between ${pair[0]} and ${pair[1]} updated") - calculateTradeVolume(trade, pair[0].uppercase(), pair[1].uppercase()) + //calculateTradeVolume(trade, pair[0].uppercase(), pair[1].uppercase()) // Moved to accountant updateCache(trade, tradeEntity) } diff --git a/wallet/wallet-app/src/main/kotlin/co/nilin/opex/wallet/app/config/SecurityConfig.kt b/wallet/wallet-app/src/main/kotlin/co/nilin/opex/wallet/app/config/SecurityConfig.kt index ccb593ba6..3904d4740 100644 --- a/wallet/wallet-app/src/main/kotlin/co/nilin/opex/wallet/app/config/SecurityConfig.kt +++ b/wallet/wallet-app/src/main/kotlin/co/nilin/opex/wallet/app/config/SecurityConfig.kt @@ -37,6 +37,7 @@ class SecurityConfig(private val webClient: WebClient) { .pathMatchers("/withdraw/**").authenticated() .pathMatchers("/transaction/**").authenticated() .pathMatchers("/admin/**").hasAuthority("ROLE_admin") + .pathMatchers("/stats/total-assets/**").permitAll() .pathMatchers("/stats/**").hasAuthority("ROLE_admin") .pathMatchers(HttpMethod.GET, "/currency/**").permitAll() .pathMatchers("/actuator/**").permitAll() diff --git a/wallet/wallet-app/src/main/kotlin/co/nilin/opex/wallet/app/controller/WalletStatController.kt b/wallet/wallet-app/src/main/kotlin/co/nilin/opex/wallet/app/controller/WalletStatController.kt index 4303035c9..648b3a70f 100644 --- a/wallet/wallet-app/src/main/kotlin/co/nilin/opex/wallet/app/controller/WalletStatController.kt +++ b/wallet/wallet-app/src/main/kotlin/co/nilin/opex/wallet/app/controller/WalletStatController.kt @@ -3,16 +3,18 @@ package co.nilin.opex.wallet.app.controller import co.nilin.opex.wallet.core.inout.WalletData import co.nilin.opex.wallet.core.inout.WalletDataResponse import co.nilin.opex.wallet.core.inout.WalletTotal +import co.nilin.opex.wallet.core.model.TotalAssetsSnapshot import co.nilin.opex.wallet.core.model.WalletType +import co.nilin.opex.wallet.core.spi.TotalAssetsSnapshotManager import co.nilin.opex.wallet.core.spi.WalletDataManager -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 org.springframework.web.bind.annotation.* @RestController @RequestMapping("/stats") -class WalletStatController(private val walletDataManager: WalletDataManager) { +class WalletStatController( + private val walletDataManager: WalletDataManager, + private val totalAssetsSnapshotManager: TotalAssetsSnapshotManager +) { @GetMapping("/wallets") suspend fun walletData( @@ -46,4 +48,11 @@ class WalletStatController(private val walletDataManager: WalletDataManager) { suspend fun userWalletTotal(): List? { return walletDataManager.findUserWalletsTotal() } + + @GetMapping("/total-assets/{uuid}") + suspend fun getWalletTotalAssetsSnapshot( + @PathVariable uuid: String, + ): TotalAssetsSnapshot? { + return totalAssetsSnapshotManager.getUserLastSnapshot(uuid) + } } \ No newline at end of file diff --git a/wallet/wallet-core/src/main/kotlin/co/nilin/opex/wallet/core/model/TotalAssetsSnapshot.kt b/wallet/wallet-core/src/main/kotlin/co/nilin/opex/wallet/core/model/TotalAssetsSnapshot.kt index d382482c1..3e7dafeb3 100644 --- a/wallet/wallet-core/src/main/kotlin/co/nilin/opex/wallet/core/model/TotalAssetsSnapshot.kt +++ b/wallet/wallet-core/src/main/kotlin/co/nilin/opex/wallet/core/model/TotalAssetsSnapshot.kt @@ -4,7 +4,7 @@ import java.math.BigDecimal import java.time.LocalDateTime data class TotalAssetsSnapshot( - val owner: Long, + val uuid: String, val totalUSDT: BigDecimal, val totalIRT: BigDecimal, val snapshotDate: LocalDateTime, diff --git a/wallet/wallet-core/src/main/kotlin/co/nilin/opex/wallet/core/spi/TotalAssetsSnapshotManager.kt b/wallet/wallet-core/src/main/kotlin/co/nilin/opex/wallet/core/spi/TotalAssetsSnapshotManager.kt index 413f02e67..333e2c95c 100644 --- a/wallet/wallet-core/src/main/kotlin/co/nilin/opex/wallet/core/spi/TotalAssetsSnapshotManager.kt +++ b/wallet/wallet-core/src/main/kotlin/co/nilin/opex/wallet/core/spi/TotalAssetsSnapshotManager.kt @@ -1,13 +1,10 @@ package co.nilin.opex.wallet.core.spi import co.nilin.opex.wallet.core.model.TotalAssetsSnapshot -import java.time.LocalDateTime interface TotalAssetsSnapshotManager { suspend fun createSnapshot() - suspend fun getByOwnerIdAndDate( - ownerId: Long, - fromDate: LocalDateTime? = null, - toDate: LocalDateTime? = null - ): List + suspend fun getUserLastSnapshot( + uuid: String + ): TotalAssetsSnapshot? } \ No newline at end of file diff --git a/wallet/wallet-ports/wallet-persister-postgres/src/main/kotlin/co/nilin/opex/wallet/ports/postgres/dao/TotalAssetsSnapshotRepository.kt b/wallet/wallet-ports/wallet-persister-postgres/src/main/kotlin/co/nilin/opex/wallet/ports/postgres/dao/TotalAssetsSnapshotRepository.kt index 58fbe4a05..b84c11263 100644 --- a/wallet/wallet-ports/wallet-persister-postgres/src/main/kotlin/co/nilin/opex/wallet/ports/postgres/dao/TotalAssetsSnapshotRepository.kt +++ b/wallet/wallet-ports/wallet-persister-postgres/src/main/kotlin/co/nilin/opex/wallet/ports/postgres/dao/TotalAssetsSnapshotRepository.kt @@ -1,14 +1,11 @@ package co.nilin.opex.wallet.ports.postgres.dao -import co.nilin.opex.wallet.core.model.TotalAssetsSnapshot import co.nilin.opex.wallet.ports.postgres.model.TotalAssetsSnapshotModel import org.springframework.data.r2dbc.repository.Modifying 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.time.LocalDateTime @Repository interface TotalAssetsSnapshotRepository : ReactiveCrudRepository { @@ -17,7 +14,7 @@ interface TotalAssetsSnapshotRepository : ReactiveCrudRepository 0 - GROUP BY w.owner + GROUP BY wo.uuid """ ) fun createSnapshotsDirectly(): Mono @@ -55,16 +53,12 @@ interface TotalAssetsSnapshotRepository : ReactiveCrudRepository :startTime) - and (:endTime is null or snapshot_date <= :endTime) + where uuid = :uuid order by id desc + limit 1 """ ) - fun findByOwnerIdAndSnapshotDate( - ownerId: Long, - startTime: LocalDateTime?, - endTime: LocalDateTime? - ): Flux - + fun findLastSnapshotByUuid( + uuid: String, + ): Mono } \ No newline at end of file diff --git a/wallet/wallet-ports/wallet-persister-postgres/src/main/kotlin/co/nilin/opex/wallet/ports/postgres/impl/TotalAssetsSnapshotImpl.kt b/wallet/wallet-ports/wallet-persister-postgres/src/main/kotlin/co/nilin/opex/wallet/ports/postgres/impl/TotalAssetsSnapshotImpl.kt index 852c47379..3ad6b1aa3 100644 --- a/wallet/wallet-ports/wallet-persister-postgres/src/main/kotlin/co/nilin/opex/wallet/ports/postgres/impl/TotalAssetsSnapshotImpl.kt +++ b/wallet/wallet-ports/wallet-persister-postgres/src/main/kotlin/co/nilin/opex/wallet/ports/postgres/impl/TotalAssetsSnapshotImpl.kt @@ -9,7 +9,6 @@ import co.nilin.opex.wallet.ports.postgres.util.toTotalAssetsSnapshot import kotlinx.coroutines.reactive.awaitFirstOrNull import org.slf4j.LoggerFactory import org.springframework.stereotype.Service -import java.time.LocalDateTime @Service class TotalAssetsSnapshotImpl( @@ -31,11 +30,10 @@ class TotalAssetsSnapshotImpl( logger.info("Snapshot creation finished in {} ms", (end - start)) } - override suspend fun getByOwnerIdAndDate( - ownerId: Long, fromDate: LocalDateTime?, toDate: LocalDateTime? - ): List { - return totalAssetsSnapshotRepository.findByOwnerIdAndSnapshotDate(ownerId, fromDate, toDate).collectList() - .awaitFirstOrNull()?.map { it.toTotalAssetsSnapshot() } ?: emptyList() + override suspend fun getUserLastSnapshot( + uuid: String + ): TotalAssetsSnapshot? { + return totalAssetsSnapshotRepository.findLastSnapshotByUuid(uuid).awaitFirstOrNull()?.toTotalAssetsSnapshot() } suspend fun savePrices() { @@ -46,5 +44,4 @@ class TotalAssetsSnapshotImpl( ).awaitFirstOrNull() } } - } diff --git a/wallet/wallet-ports/wallet-persister-postgres/src/main/kotlin/co/nilin/opex/wallet/ports/postgres/model/TotalAssetsSnapshotModel.kt b/wallet/wallet-ports/wallet-persister-postgres/src/main/kotlin/co/nilin/opex/wallet/ports/postgres/model/TotalAssetsSnapshotModel.kt index abec5015a..227608d91 100644 --- a/wallet/wallet-ports/wallet-persister-postgres/src/main/kotlin/co/nilin/opex/wallet/ports/postgres/model/TotalAssetsSnapshotModel.kt +++ b/wallet/wallet-ports/wallet-persister-postgres/src/main/kotlin/co/nilin/opex/wallet/ports/postgres/model/TotalAssetsSnapshotModel.kt @@ -10,7 +10,7 @@ import java.time.LocalDateTime data class TotalAssetsSnapshotModel( @Id val id: Long? = null, - val owner: Long, + val uuid: String, @Column("total_usdt") val totalUSDT: BigDecimal, @Column("total_irt") diff --git a/wallet/wallet-ports/wallet-persister-postgres/src/main/kotlin/co/nilin/opex/wallet/ports/postgres/util/Convertor.kt b/wallet/wallet-ports/wallet-persister-postgres/src/main/kotlin/co/nilin/opex/wallet/ports/postgres/util/Convertor.kt index 41c9c410d..c3fd3b3cf 100644 --- a/wallet/wallet-ports/wallet-persister-postgres/src/main/kotlin/co/nilin/opex/wallet/ports/postgres/util/Convertor.kt +++ b/wallet/wallet-ports/wallet-persister-postgres/src/main/kotlin/co/nilin/opex/wallet/ports/postgres/util/Convertor.kt @@ -175,7 +175,7 @@ fun CurrencyModel.toCurrencyData(): CurrencyData { fun TotalAssetsSnapshotModel.toTotalAssetsSnapshot(): TotalAssetsSnapshot { return TotalAssetsSnapshot( - owner, + uuid, totalUSDT, totalIRT, snapshotDate 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 f7868864c..896b55423 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 @@ -546,11 +546,12 @@ $$; CREATE TABLE IF NOT EXISTS total_assets_snapshot ( id SERIAL PRIMARY KEY, - owner INTEGER REFERENCES wallet_owner (id), + uuid VARCHAR(36) NOT NULL, total_usdt DECIMAL NOT NULL, total_irt DECIMAL NOT NULL, snapshot_date TIMESTAMP NOT NULL ); +CREATE INDEX IF NOT EXISTS idx_total_assets_snapshot_uuid ON total_assets_snapshot (uuid); CREATE TABLE IF NOT EXISTS price (