feat: add real copy-trading pnl statistics
This commit is contained in:
@@ -24,6 +24,7 @@ data class CopyTradingStatisticsResponse(
|
||||
|
||||
// 持仓统计
|
||||
val currentPositionQuantity: String,
|
||||
val currentPositionCost: String,
|
||||
val currentPositionValue: String,
|
||||
|
||||
// 盈亏统计
|
||||
|
||||
+120
@@ -0,0 +1,120 @@
|
||||
package com.wrbug.polymarketbot.service.copytrading.statistics
|
||||
|
||||
import com.wrbug.polymarketbot.entity.CopyOrderTracking
|
||||
import com.wrbug.polymarketbot.entity.SellMatchDetail
|
||||
import com.wrbug.polymarketbot.entity.SellMatchRecord
|
||||
import com.wrbug.polymarketbot.util.div
|
||||
import com.wrbug.polymarketbot.util.gt
|
||||
import com.wrbug.polymarketbot.util.lte
|
||||
import com.wrbug.polymarketbot.util.multi
|
||||
import com.wrbug.polymarketbot.util.toSafeBigDecimal
|
||||
import java.math.BigDecimal
|
||||
import java.math.RoundingMode
|
||||
|
||||
/**
|
||||
* Pure calculator for copy-trading PnL.
|
||||
*
|
||||
* The statistics API used to expose totalPnl as realized-only PnL and hard-code
|
||||
* unrealized PnL/current position value to zero. That makes active or expired
|
||||
* open positions invisible. This calculator keeps the accounting explicit:
|
||||
*
|
||||
* - currentPositionCost: remaining shares at their tracked buy cost
|
||||
* - currentPositionValue: remaining shares marked by current Polymarket price
|
||||
* - totalUnrealizedPnl: current value - current cost
|
||||
* - totalPnl: realized + unrealized
|
||||
*/
|
||||
object CopyTradingPnlCalculator {
|
||||
fun calculate(
|
||||
buyOrders: List<CopyOrderTracking>,
|
||||
sellRecords: List<SellMatchRecord>,
|
||||
matchDetails: List<SellMatchDetail>,
|
||||
quotes: List<PositionValuationQuote> = emptyList()
|
||||
): CopyTradingPnlStatistics {
|
||||
val totalBuyQuantity = buyOrders.sumOf { it.quantity.toSafeBigDecimal() }
|
||||
val totalBuyAmount = buyOrders.sumOf { it.quantity.toSafeBigDecimal().multi(it.price) }
|
||||
val totalBuyOrders = buyOrders.size.toLong()
|
||||
val avgBuyPrice = if (totalBuyQuantity.gt(BigDecimal.ZERO)) {
|
||||
totalBuyAmount.div(totalBuyQuantity)
|
||||
} else {
|
||||
BigDecimal.ZERO
|
||||
}
|
||||
|
||||
val totalSellQuantity = sellRecords.sumOf { it.totalMatchedQuantity.toSafeBigDecimal() }
|
||||
val totalSellAmount = matchDetails.sumOf { it.matchedQuantity.toSafeBigDecimal().multi(it.sellPrice) }
|
||||
val totalSellOrders = sellRecords.size.toLong()
|
||||
|
||||
val openOrders = buyOrders.filter { it.remainingQuantity.toSafeBigDecimal().gt(BigDecimal.ZERO) }
|
||||
val currentPositionQuantity = openOrders.sumOf { it.remainingQuantity.toSafeBigDecimal() }
|
||||
val currentPositionCost = openOrders.sumOf { it.remainingQuantity.toSafeBigDecimal().multi(it.price) }
|
||||
val currentPositionValue = openOrders.sumOf { order ->
|
||||
val currentPrice = findQuote(order, quotes)?.currentPrice ?: BigDecimal.ZERO
|
||||
order.remainingQuantity.toSafeBigDecimal().multi(currentPrice)
|
||||
}
|
||||
|
||||
val totalRealizedPnl = matchDetails.sumOf { it.realizedPnl.toSafeBigDecimal() }
|
||||
val totalUnrealizedPnl = currentPositionValue.subtract(currentPositionCost)
|
||||
val totalPnl = totalRealizedPnl.add(totalUnrealizedPnl)
|
||||
|
||||
return CopyTradingPnlStatistics(
|
||||
totalBuyQuantity = totalBuyQuantity,
|
||||
totalBuyOrders = totalBuyOrders,
|
||||
totalBuyAmount = totalBuyAmount,
|
||||
avgBuyPrice = avgBuyPrice,
|
||||
totalSellQuantity = totalSellQuantity,
|
||||
totalSellOrders = totalSellOrders,
|
||||
totalSellAmount = totalSellAmount,
|
||||
currentPositionQuantity = currentPositionQuantity,
|
||||
currentPositionCost = currentPositionCost,
|
||||
currentPositionValue = currentPositionValue,
|
||||
totalRealizedPnl = totalRealizedPnl,
|
||||
totalUnrealizedPnl = totalUnrealizedPnl,
|
||||
totalPnl = totalPnl,
|
||||
totalPnlPercent = calculatePnlPercent(totalBuyAmount, totalPnl)
|
||||
)
|
||||
}
|
||||
|
||||
private fun findQuote(
|
||||
order: CopyOrderTracking,
|
||||
quotes: List<PositionValuationQuote>
|
||||
): PositionValuationQuote? {
|
||||
return quotes.firstOrNull { quote ->
|
||||
quote.marketId == order.marketId &&
|
||||
order.outcomeIndex != null &&
|
||||
quote.outcomeIndex == order.outcomeIndex
|
||||
} ?: quotes.firstOrNull { quote ->
|
||||
quote.marketId == order.marketId &&
|
||||
order.outcomeIndex == null &&
|
||||
!quote.side.isNullOrBlank() &&
|
||||
quote.side.equals(order.side, ignoreCase = true)
|
||||
}
|
||||
}
|
||||
|
||||
private fun calculatePnlPercent(totalBuyAmount: BigDecimal, totalPnl: BigDecimal): BigDecimal {
|
||||
if (totalBuyAmount.lte(BigDecimal.ZERO)) return BigDecimal.ZERO.setScale(2)
|
||||
return totalPnl.div(totalBuyAmount).multi(100).setScale(2, RoundingMode.HALF_UP)
|
||||
}
|
||||
}
|
||||
|
||||
data class PositionValuationQuote(
|
||||
val marketId: String,
|
||||
val outcomeIndex: Int?,
|
||||
val side: String?,
|
||||
val currentPrice: BigDecimal
|
||||
)
|
||||
|
||||
data class CopyTradingPnlStatistics(
|
||||
val totalBuyQuantity: BigDecimal,
|
||||
val totalBuyOrders: Long,
|
||||
val totalBuyAmount: BigDecimal,
|
||||
val avgBuyPrice: BigDecimal,
|
||||
val totalSellQuantity: BigDecimal,
|
||||
val totalSellOrders: Long,
|
||||
val totalSellAmount: BigDecimal,
|
||||
val currentPositionQuantity: BigDecimal,
|
||||
val currentPositionCost: BigDecimal,
|
||||
val currentPositionValue: BigDecimal,
|
||||
val totalRealizedPnl: BigDecimal,
|
||||
val totalUnrealizedPnl: BigDecimal,
|
||||
val totalPnl: BigDecimal,
|
||||
val totalPnlPercent: BigDecimal
|
||||
)
|
||||
+63
-96
@@ -7,13 +7,11 @@ import com.wrbug.polymarketbot.util.toSafeBigDecimal
|
||||
import com.wrbug.polymarketbot.util.multi
|
||||
import com.wrbug.polymarketbot.util.div
|
||||
import com.wrbug.polymarketbot.util.gt
|
||||
import com.wrbug.polymarketbot.util.eq
|
||||
import com.wrbug.polymarketbot.util.lte
|
||||
import org.slf4j.LoggerFactory
|
||||
import org.springframework.data.domain.PageRequest
|
||||
import org.springframework.data.domain.Pageable
|
||||
import org.springframework.data.domain.Sort
|
||||
import com.wrbug.polymarketbot.service.accounts.AccountService
|
||||
import com.wrbug.polymarketbot.service.common.BlockchainService
|
||||
import org.springframework.stereotype.Service
|
||||
import java.math.BigDecimal
|
||||
@@ -31,7 +29,8 @@ class CopyTradingStatisticsService(
|
||||
private val sellMatchDetailRepository: SellMatchDetailRepository,
|
||||
private val accountRepository: AccountRepository,
|
||||
private val leaderRepository: LeaderRepository,
|
||||
private val marketService: com.wrbug.polymarketbot.service.common.MarketService
|
||||
private val marketService: com.wrbug.polymarketbot.service.common.MarketService,
|
||||
private val blockchainService: BlockchainService
|
||||
) {
|
||||
|
||||
private val logger = LoggerFactory.getLogger(CopyTradingStatisticsService::class.java)
|
||||
@@ -58,15 +57,14 @@ class CopyTradingStatisticsService(
|
||||
// 5. 获取匹配明细
|
||||
val matchDetails = sellMatchDetailRepository.findByCopyTradingId(copyTradingId)
|
||||
|
||||
// 6. 计算统计信息
|
||||
val statistics = calculateStatistics(buyOrders, sellRecords, matchDetails)
|
||||
// 6. 获取当前价格并计算真实口径统计
|
||||
// currentPositionCost 使用跟单系统记录的剩余仓位成本;currentPositionValue 使用
|
||||
// Polymarket Data API 当前价格按剩余份额估值。若某个未平仓仓位没有报价,按 0
|
||||
// 估值,避免已归零/待赎回仓位继续被统计成成本价。
|
||||
val quotes = buildPositionValuationQuotes(account?.proxyAddress)
|
||||
val statistics = CopyTradingPnlCalculator.calculate(buyOrders, sellRecords, matchDetails, quotes)
|
||||
|
||||
// 7. 不再计算未实现盈亏和持仓价值(优化性能)
|
||||
// 未实现盈亏计算需要查询链上持仓和市场价格,性能开销大
|
||||
val unrealizedPnl = "0"
|
||||
val positionValue = "0"
|
||||
|
||||
// 8. 构建响应(总盈亏 = 已实现盈亏)
|
||||
// 7. 构建响应(总盈亏 = 已实现盈亏 + 未实现盈亏)
|
||||
val response = CopyTradingStatisticsResponse(
|
||||
copyTradingId = copyTradingId,
|
||||
accountId = copyTrading.accountId,
|
||||
@@ -74,19 +72,20 @@ class CopyTradingStatisticsService(
|
||||
leaderId = copyTrading.leaderId,
|
||||
leaderName = leader?.leaderName,
|
||||
enabled = copyTrading.enabled,
|
||||
totalBuyQuantity = statistics.totalBuyQuantity,
|
||||
totalBuyQuantity = statistics.totalBuyQuantity.toString(),
|
||||
totalBuyOrders = statistics.totalBuyOrders,
|
||||
totalBuyAmount = statistics.totalBuyAmount,
|
||||
avgBuyPrice = statistics.avgBuyPrice,
|
||||
totalSellQuantity = statistics.totalSellQuantity,
|
||||
totalBuyAmount = statistics.totalBuyAmount.toString(),
|
||||
avgBuyPrice = statistics.avgBuyPrice.toString(),
|
||||
totalSellQuantity = statistics.totalSellQuantity.toString(),
|
||||
totalSellOrders = statistics.totalSellOrders,
|
||||
totalSellAmount = statistics.totalSellAmount,
|
||||
currentPositionQuantity = statistics.currentPositionQuantity,
|
||||
currentPositionValue = positionValue,
|
||||
totalRealizedPnl = statistics.totalRealizedPnl,
|
||||
totalUnrealizedPnl = unrealizedPnl,
|
||||
totalPnl = statistics.totalRealizedPnl,
|
||||
totalPnlPercent = calculatePnlPercentOnlyRealized(statistics.totalBuyAmount, statistics.totalRealizedPnl)
|
||||
totalSellAmount = statistics.totalSellAmount.toString(),
|
||||
currentPositionQuantity = statistics.currentPositionQuantity.toString(),
|
||||
currentPositionCost = statistics.currentPositionCost.toString(),
|
||||
currentPositionValue = statistics.currentPositionValue.toString(),
|
||||
totalRealizedPnl = statistics.totalRealizedPnl.toString(),
|
||||
totalUnrealizedPnl = statistics.totalUnrealizedPnl.toString(),
|
||||
totalPnl = statistics.totalPnl.toString(),
|
||||
totalPnlPercent = statistics.totalPnlPercent.toString()
|
||||
)
|
||||
|
||||
Result.success(response)
|
||||
@@ -96,6 +95,48 @@ class CopyTradingStatisticsService(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取账户当前仓位报价,用于给跟单系统中仍有 remainingQuantity 的订单做市值估算。
|
||||
*
|
||||
* 注意:报价只用于估值,不直接使用 Data API 的 size/currentValue 汇总;这样可以按
|
||||
* copyTradingId 归因,避免同一钱包下多个 Leader 或手工仓位混在一起。
|
||||
*/
|
||||
private suspend fun buildPositionValuationQuotes(proxyAddress: String?): List<PositionValuationQuote> {
|
||||
if (proxyAddress.isNullOrBlank()) return emptyList()
|
||||
|
||||
return try {
|
||||
val positionsResult = blockchainService.getPositions(proxyAddress)
|
||||
if (positionsResult.isFailure) {
|
||||
logger.warn("获取持仓报价失败: proxyAddress=${proxyAddress.take(10)}..., error=${positionsResult.exceptionOrNull()?.message}")
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
positionsResult.getOrNull().orEmpty().mapNotNull { position ->
|
||||
val marketId = position.conditionId?.takeIf { it.isNotBlank() } ?: return@mapNotNull null
|
||||
val currentPrice = position.curPrice?.toSafeBigDecimal()
|
||||
?: derivePriceFromPositionValue(position.currentValue, position.size)
|
||||
?: BigDecimal.ZERO
|
||||
|
||||
PositionValuationQuote(
|
||||
marketId = marketId,
|
||||
outcomeIndex = position.outcomeIndex,
|
||||
side = position.outcome,
|
||||
currentPrice = currentPrice
|
||||
)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
logger.warn("获取持仓报价异常: proxyAddress=${proxyAddress.take(10)}..., error=${e.message}", e)
|
||||
emptyList()
|
||||
}
|
||||
}
|
||||
|
||||
private fun derivePriceFromPositionValue(currentValue: Double?, size: Double?): BigDecimal? {
|
||||
val value = currentValue?.toSafeBigDecimal() ?: return null
|
||||
val quantity = size?.toSafeBigDecimal() ?: return null
|
||||
if (quantity.lte(BigDecimal.ZERO)) return null
|
||||
return value.div(quantity)
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询订单列表
|
||||
*/
|
||||
@@ -337,65 +378,6 @@ class CopyTradingStatisticsService(
|
||||
return Pair(list, total)
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算统计信息
|
||||
*/
|
||||
private fun calculateStatistics(
|
||||
buyOrders: List<CopyOrderTracking>,
|
||||
sellRecords: List<SellMatchRecord>,
|
||||
matchDetails: List<SellMatchDetail>
|
||||
): StatisticsData {
|
||||
// 买入统计
|
||||
val totalBuyQuantity = buyOrders.sumOf { it.quantity.toSafeBigDecimal() }
|
||||
val totalBuyAmount = buyOrders.sumOf { it.quantity.toSafeBigDecimal().multi(it.price) }
|
||||
val totalBuyOrders = buyOrders.size.toLong()
|
||||
val avgBuyPrice = if (totalBuyQuantity.gt(BigDecimal.ZERO)) {
|
||||
totalBuyAmount.div(totalBuyQuantity)
|
||||
} else {
|
||||
BigDecimal.ZERO
|
||||
}
|
||||
|
||||
// 卖出统计
|
||||
// 使用 SellMatchDetail 计算总卖出金额,确保准确性
|
||||
// 因为每个明细都记录了准确的匹配数量和卖出价格
|
||||
val totalSellQuantity = sellRecords.sumOf { it.totalMatchedQuantity.toSafeBigDecimal() }
|
||||
val totalSellAmount = matchDetails.sumOf { it.matchedQuantity.toSafeBigDecimal().multi(it.sellPrice) }
|
||||
val totalSellOrders = sellRecords.size.toLong()
|
||||
|
||||
// 持仓统计
|
||||
val currentPositionQuantity = buyOrders.sumOf { it.remainingQuantity.toSafeBigDecimal() }
|
||||
|
||||
// 已实现盈亏
|
||||
val totalRealizedPnl = matchDetails.sumOf { it.realizedPnl.toSafeBigDecimal() }
|
||||
|
||||
return StatisticsData(
|
||||
totalBuyQuantity = totalBuyQuantity.toString(),
|
||||
totalBuyOrders = totalBuyOrders,
|
||||
totalBuyAmount = totalBuyAmount.toString(),
|
||||
avgBuyPrice = avgBuyPrice.toString(),
|
||||
totalSellQuantity = totalSellQuantity.toString(),
|
||||
totalSellOrders = totalSellOrders,
|
||||
totalSellAmount = totalSellAmount.toString(),
|
||||
currentPositionQuantity = currentPositionQuantity.toString(),
|
||||
totalRealizedPnl = totalRealizedPnl.toString()
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算盈亏百分比(仅基于已实现盈亏)
|
||||
*/
|
||||
private fun calculatePnlPercentOnlyRealized(
|
||||
totalBuyAmount: String,
|
||||
totalRealizedPnl: String
|
||||
): String {
|
||||
val buyAmount = totalBuyAmount.toSafeBigDecimal()
|
||||
if (buyAmount.lte(BigDecimal.ZERO)) return "0"
|
||||
|
||||
val percent = totalRealizedPnl.toSafeBigDecimal().div(buyAmount).multi(100)
|
||||
|
||||
return percent.setScale(2, RoundingMode.HALF_UP).toString()
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取全局统计
|
||||
*/
|
||||
@@ -556,21 +538,6 @@ class CopyTradingStatisticsService(
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* 统计数据结构
|
||||
*/
|
||||
private data class StatisticsData(
|
||||
val totalBuyQuantity: String,
|
||||
val totalBuyOrders: Long,
|
||||
val totalBuyAmount: String,
|
||||
val avgBuyPrice: String,
|
||||
val totalSellQuantity: String,
|
||||
val totalSellOrders: Long,
|
||||
val totalSellAmount: String,
|
||||
val currentPositionQuantity: String,
|
||||
val totalRealizedPnl: String
|
||||
)
|
||||
|
||||
/**
|
||||
* 获取按市场分组的买入订单列表
|
||||
*/
|
||||
|
||||
+147
@@ -0,0 +1,147 @@
|
||||
package com.wrbug.polymarketbot.service.copytrading.statistics
|
||||
|
||||
import com.wrbug.polymarketbot.entity.CopyOrderTracking
|
||||
import com.wrbug.polymarketbot.entity.SellMatchDetail
|
||||
import com.wrbug.polymarketbot.entity.SellMatchRecord
|
||||
import org.junit.jupiter.api.Assertions.assertEquals
|
||||
import org.junit.jupiter.api.Test
|
||||
import java.math.BigDecimal
|
||||
|
||||
class CopyTradingPnlCalculatorTest {
|
||||
|
||||
@Test
|
||||
fun `marks open positions with current prices and combines realized and unrealized pnl`() {
|
||||
val buyOrders = listOf(
|
||||
buyOrder(
|
||||
id = 1,
|
||||
marketId = "market-a",
|
||||
outcomeIndex = 0,
|
||||
quantity = "10",
|
||||
price = "0.60",
|
||||
matchedQuantity = "6",
|
||||
remainingQuantity = "4"
|
||||
),
|
||||
buyOrder(
|
||||
id = 2,
|
||||
marketId = "market-b",
|
||||
outcomeIndex = 1,
|
||||
quantity = "5",
|
||||
price = "0.20",
|
||||
matchedQuantity = "0",
|
||||
remainingQuantity = "5"
|
||||
)
|
||||
)
|
||||
val sellRecords = listOf(
|
||||
sellRecord(quantity = "6", price = "0.85", pnl = "1.50")
|
||||
)
|
||||
val matchDetails = listOf(
|
||||
matchDetail(trackingId = 1, buyOrderId = "buy-1", quantity = "6", buyPrice = "0.60", sellPrice = "0.85", pnl = "1.50")
|
||||
)
|
||||
val quotes = listOf(
|
||||
PositionValuationQuote(marketId = "market-a", outcomeIndex = 0, side = "0", currentPrice = bd("0.40")),
|
||||
PositionValuationQuote(marketId = "market-b", outcomeIndex = 1, side = "1", currentPrice = bd("0.05"))
|
||||
)
|
||||
|
||||
val stats = CopyTradingPnlCalculator.calculate(buyOrders, sellRecords, matchDetails, quotes)
|
||||
|
||||
assertEquals("3.40", stats.currentPositionCost.toPlainString())
|
||||
assertEquals("1.85", stats.currentPositionValue.toPlainString())
|
||||
assertEquals("-1.55", stats.totalUnrealizedPnl.toPlainString())
|
||||
assertEquals("1.50", stats.totalRealizedPnl.toPlainString())
|
||||
assertEquals("-0.05", stats.totalPnl.toPlainString())
|
||||
assertEquals("-0.71", stats.totalPnlPercent.toPlainString())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `treats tracked open positions without a quote as zero current value`() {
|
||||
val buyOrders = listOf(
|
||||
buyOrder(
|
||||
id = 1,
|
||||
marketId = "expired-market",
|
||||
outcomeIndex = 0,
|
||||
quantity = "8",
|
||||
price = "0.25",
|
||||
matchedQuantity = "0",
|
||||
remainingQuantity = "8"
|
||||
)
|
||||
)
|
||||
|
||||
val stats = CopyTradingPnlCalculator.calculate(
|
||||
buyOrders = buyOrders,
|
||||
sellRecords = emptyList(),
|
||||
matchDetails = emptyList(),
|
||||
quotes = emptyList()
|
||||
)
|
||||
|
||||
assertEquals("2.00", stats.currentPositionCost.toPlainString())
|
||||
assertEquals("0", stats.currentPositionValue.toPlainString())
|
||||
assertEquals("-2.00", stats.totalUnrealizedPnl.toPlainString())
|
||||
assertEquals("-2.00", stats.totalPnl.toPlainString())
|
||||
assertEquals("-100.00", stats.totalPnlPercent.toPlainString())
|
||||
}
|
||||
|
||||
private fun buyOrder(
|
||||
id: Long,
|
||||
marketId: String,
|
||||
outcomeIndex: Int?,
|
||||
quantity: String,
|
||||
price: String,
|
||||
matchedQuantity: String,
|
||||
remainingQuantity: String
|
||||
) = CopyOrderTracking(
|
||||
id = id,
|
||||
copyTradingId = 1,
|
||||
accountId = 1,
|
||||
leaderId = 1,
|
||||
marketId = marketId,
|
||||
side = outcomeIndex?.toString() ?: "YES",
|
||||
outcomeIndex = outcomeIndex,
|
||||
buyOrderId = "buy-$id",
|
||||
leaderBuyTradeId = "leader-buy-$id",
|
||||
leaderBuyQuantity = null,
|
||||
quantity = bd(quantity),
|
||||
price = bd(price),
|
||||
matchedQuantity = bd(matchedQuantity),
|
||||
remainingQuantity = bd(remainingQuantity),
|
||||
status = if (bd(remainingQuantity).signum() == 0) "fully_matched" else "filled",
|
||||
source = "test",
|
||||
createdAt = id,
|
||||
updatedAt = id
|
||||
)
|
||||
|
||||
private fun sellRecord(quantity: String, price: String, pnl: String) = SellMatchRecord(
|
||||
id = 1,
|
||||
copyTradingId = 1,
|
||||
sellOrderId = "sell-1",
|
||||
leaderSellTradeId = "leader-sell-1",
|
||||
marketId = "market-a",
|
||||
side = "0",
|
||||
outcomeIndex = 0,
|
||||
totalMatchedQuantity = bd(quantity),
|
||||
sellPrice = bd(price),
|
||||
totalRealizedPnl = bd(pnl),
|
||||
priceUpdated = true,
|
||||
createdAt = 1
|
||||
)
|
||||
|
||||
private fun matchDetail(
|
||||
trackingId: Long,
|
||||
buyOrderId: String,
|
||||
quantity: String,
|
||||
buyPrice: String,
|
||||
sellPrice: String,
|
||||
pnl: String
|
||||
) = SellMatchDetail(
|
||||
id = trackingId,
|
||||
matchRecordId = 1,
|
||||
trackingId = trackingId,
|
||||
buyOrderId = buyOrderId,
|
||||
matchedQuantity = bd(quantity),
|
||||
buyPrice = bd(buyPrice),
|
||||
sellPrice = bd(sellPrice),
|
||||
realizedPnl = bd(pnl),
|
||||
createdAt = 1
|
||||
)
|
||||
|
||||
private fun bd(value: String) = BigDecimal(value)
|
||||
}
|
||||
Reference in New Issue
Block a user