refactor: 移除未实现盈亏计算以优化跟单关系统计性能

- 移除未实现盈亏和持仓价值的计算
- 总盈亏现在仅包含已实现盈亏
- 删除不再使用的 getCurrentMarketPrice、getActualPositions、calculateUnrealizedPnl、calculatePositionValue 方法
- 简化盈亏百分比计算,仅基于已实现盈亏
- 移除不必要的依赖注入(accountService、blockchainService、retrofitFactory)
- 性能提升:从 1+N 次网络请求减少到 0 次
This commit is contained in:
WrBug
2026-01-11 14:53:28 +08:00
parent b58bb267a1
commit 90fa487d1b
2 changed files with 11 additions and 341 deletions
-137
View File
@@ -1,137 +0,0 @@
# 🎉 PolyHermes v1.1.7 发布公告
**发布日期:2026年1月7日**
---
## ✨ 新功能
### 💰 支持 Maker Rebates Program 费率
我们新增了对 Polymarket Maker Rebates Program 的支持!系统现在会自动获取并应用最新的费率,帮助您享受更优惠的交易成本。
**这意味着什么?**
- 系统会自动查询并应用最新的费率
- 所有订单(买入、卖出)都会使用正确的费率
- 无需手动配置,系统会自动处理
### 🔧 Docker 部署更灵活
现在您可以通过环境变量轻松配置日志级别,无需修改配置文件!
**新增配置项:**
- `LOG_LEVEL_ROOT` - 系统日志级别(默认:INFO
- `LOG_LEVEL_APP` - 应用日志级别(默认:DEBUG)
只需在 `.env` 文件中添加这些配置,重启服务即可生效。
---
## 🐛 问题修复
### 修复市场价格查询问题
修复了某些市场无法正确查询价格的问题。现在系统会:
- 优先从链上查询市场价格
- 如果链上查询失败,自动降级到其他数据源
- 提高系统的稳定性和容错性
### 修复自动卖出误判问题
修复了在某些情况下系统会误判市场已卖出,导致创建错误记录的问题。现在系统会:
- 更准确地判断市场状态
- 避免误判导致的错误记录
- 提高仓位管理的准确性
---
## 📝 文档更新
### 更新联系方式
- **Telegram 群组**https://t.me/polyhermes
- 欢迎加入我们的 Telegram 群组,获取最新资讯和技术支持!
### 版本信息更清晰
- README 中新增了 Docker 版本徽章
- 可以一目了然地看到最新的 Docker 镜像版本
---
## 📊 本次更新统计
- **5 个提交**
- **16 个文件变更**
- **主要改进**:费率支持、错误修复、部署优化
---
## 🚀 如何升级
### Docker 部署用户(推荐)
```bash
# 1. 停止当前服务
docker-compose -f docker-compose.prod.yml down
# 2. 拉取最新镜像
docker pull wrbug/polyhermes:latest
# 3. 重新启动服务
docker-compose -f docker-compose.prod.yml up -d
# 4. 查看日志确认升级成功
docker-compose -f docker-compose.prod.yml logs -f
```
### 本地构建用户
```bash
# 1. 拉取最新代码
git pull origin dev
# 2. 切换到 v1.1.7 标签
git checkout v1.1.7
# 3. 重新构建并启动
./deploy.sh
```
---
## ⚠️ 重要提示
### 数据库迁移
**本次更新无需数据库迁移**,可以直接升级,不会影响现有数据。
### 配置变更
- 新增的日志级别配置为**可选配置**
- 如果不配置,系统会使用默认值(INFO/DEBUG)
- 不影响现有功能
---
## 🙏 感谢
感谢所有用户的支持和反馈!如果您在使用过程中遇到任何问题,欢迎:
- 📧 提交 Issuehttps://github.com/WrBug/PolyHermes/issues
- 💬 加入 Telegram 群组:https://t.me/polyhermes
- 🐦 关注 Twitter@polyhermes
---
## 📚 相关链接
- **GitHub 仓库**https://github.com/WrBug/PolyHermes
- **完整更新日志**https://github.com/WrBug/PolyHermes/releases/tag/v1.1.7
- **Docker Hub**https://hub.docker.com/r/wrbug/polyhermes
---
**祝您交易顺利!** 🚀
@@ -31,8 +31,6 @@ class CopyTradingStatisticsService(
private val sellMatchDetailRepository: SellMatchDetailRepository,
private val accountRepository: AccountRepository,
private val leaderRepository: LeaderRepository,
private val accountService: AccountService,
private val blockchainService: BlockchainService,
private val marketService: com.wrbug.polymarketbot.service.common.MarketService
) {
@@ -63,19 +61,12 @@ class CopyTradingStatisticsService(
// 6. 计算统计信息
val statistics = calculateStatistics(buyOrders, sellRecords, matchDetails)
// 7. 获取链上实际持仓(用于准确计算未实现盈亏,考虑手动卖出的情况
val actualPositions = getActualPositions(account)
// 7. 不再计算未实现盈亏和持仓价值(优化性能
// 未实现盈亏计算需要查询链上持仓和市场价格,性能开销大
val unrealizedPnl = "0"
val positionValue = "0"
// 8. 获取当前市场价格(用于计算未实现盈亏)
val currentPrice = getCurrentMarketPrice(buyOrders)
// 9. 计算未实现盈亏(使用链上实际持仓,而不是 remainingQuantity
val unrealizedPnl = calculateUnrealizedPnl(buyOrders, currentPrice, actualPositions)
// 10. 计算持仓价值(使用链上实际持仓和当前价格)
val positionValue = calculatePositionValue(buyOrders, currentPrice, actualPositions)
// 11. 构建响应
// 8. 构建响应(总盈亏 = 已实现盈亏)
val response = CopyTradingStatisticsResponse(
copyTradingId = copyTradingId,
accountId = copyTrading.accountId,
@@ -94,8 +85,8 @@ class CopyTradingStatisticsService(
currentPositionValue = positionValue,
totalRealizedPnl = statistics.totalRealizedPnl,
totalUnrealizedPnl = unrealizedPnl,
totalPnl = (statistics.totalRealizedPnl.toSafeBigDecimal().add(unrealizedPnl.toSafeBigDecimal())).toString(),
totalPnlPercent = calculatePnlPercent(statistics.totalBuyAmount, statistics.totalRealizedPnl, unrealizedPnl)
totalPnl = statistics.totalRealizedPnl,
totalPnlPercent = calculatePnlPercentOnlyRealized(statistics.totalBuyAmount, statistics.totalRealizedPnl)
)
Result.success(response)
@@ -358,200 +349,16 @@ class CopyTradingStatisticsService(
}
/**
* 获取当前市场价格
* 按 (marketId, outcomeIndex) 组合获取价格,支持多元市场
* 计算盈亏百分比(仅基于已实现盈亏)
*/
private suspend fun getCurrentMarketPrice(buyOrders: List<CopyOrderTracking>): Map<String, String> {
val prices = mutableMapOf<String, String>()
// 获取所有不同的 (marketId, outcomeIndex) 组合
val marketOutcomePairs = buyOrders
.filter { it.outcomeIndex != null }
.map { Pair(it.marketId, it.outcomeIndex!!) }
.distinct()
for ((marketId, outcomeIndex) in marketOutcomePairs) {
try {
// 传递 outcomeIndex 参数,确保获取对应 outcome 的价格
val result = accountService.getMarketPrice(marketId, outcomeIndex)
result.onSuccess { response ->
// 使用当前价格
val price = response.currentPrice
if (price.isNotBlank() && price != "0") {
// 使用 "marketId:outcomeIndex" 作为 key
val key = "$marketId:$outcomeIndex"
prices[key] = price
}
}
} catch (e: Exception) {
logger.warn("获取市场价格失败: marketId=$marketId, outcomeIndex=$outcomeIndex", e)
}
}
return prices
}
/**
* 获取链上实际持仓
* 按 (marketId, outcomeIndex) 组合返回实际持仓数量
*/
private suspend fun getActualPositions(account: Account?): Map<String, BigDecimal> {
val positions = mutableMapOf<String, BigDecimal>()
if (account == null || account.proxyAddress.isBlank()) {
return positions
}
try {
val positionsResult = blockchainService.getPositions(account.proxyAddress)
if (positionsResult.isSuccess) {
val positionList = positionsResult.getOrNull() ?: emptyList()
for (pos in positionList) {
// 只处理有 conditionId 和 outcomeIndex 的仓位
if (pos.conditionId != null && pos.outcomeIndex != null && pos.size != null) {
val key = "${pos.conditionId}:${pos.outcomeIndex}"
val size = pos.size.toSafeBigDecimal()
// 如果 size > 0,表示有持仓;如果 size < 0,表示做空(取绝对值)
positions[key] = size.abs()
}
}
}
} catch (e: Exception) {
logger.warn("获取链上持仓失败: accountId=${account.id}, error=${e.message}", e)
}
return positions
}
/**
* 计算未实现盈亏
* 使用链上实际持仓数量,而不是 remainingQuantity(考虑手动卖出的情况)
* 按市场聚合订单,计算加权平均买入价格,避免重复计算
*/
private fun calculateUnrealizedPnl(
buyOrders: List<CopyOrderTracking>,
currentPrices: Map<String, String>,
actualPositions: Map<String, BigDecimal>
): String {
var totalUnrealizedPnl = BigDecimal.ZERO
// 按市场聚合订单,计算加权平均买入价格
val marketAggregates = mutableMapOf<String, Pair<BigDecimal, BigDecimal>>() // key -> (总持仓, 总成本)
for (order in buyOrders) {
// 如果没有 outcomeIndex,跳过(无法确定价格和持仓)
if (order.outcomeIndex == null) {
logger.warn("订单缺少 outcomeIndex,跳过未实现盈亏计算: orderId=${order.buyOrderId}, marketId=${order.marketId}")
continue
}
// 使用 "marketId:outcomeIndex" 作为 key
val key = "${order.marketId}:${order.outcomeIndex}"
// 获取订单的持仓数量(使用 remainingQuantity,因为这是该订单的持仓)
val orderQty = order.remainingQuantity.toSafeBigDecimal()
// 如果订单持仓 <= 0,跳过
if (orderQty.lte(BigDecimal.ZERO)) continue
val buyPrice = order.price.toSafeBigDecimal()
val orderCost = orderQty.multi(buyPrice)
// 聚合同一市场的订单
val existing = marketAggregates[key]
if (existing != null) {
val totalQty = existing.first.add(orderQty)
val totalCost = existing.second.add(orderCost)
marketAggregates[key] = Pair(totalQty, totalCost)
} else {
marketAggregates[key] = Pair(orderQty, orderCost)
}
}
// 计算每个市场的未实现盈亏
for ((key, aggregate) in marketAggregates) {
val (totalQty, totalCost) = aggregate
// 获取链上实际持仓数量(如果存在),否则使用聚合的持仓数量
val actualQty = actualPositions[key] ?: totalQty
// 如果实际持仓 <= 0,说明已全部卖出(包括手动卖出),跳过未实现盈亏计算
if (actualQty.lte(BigDecimal.ZERO)) continue
// 获取当前市场价格
val currentPrice = currentPrices[key]?.toSafeBigDecimal()
?: continue // 如果没有当前价格,跳过
// 计算加权平均买入价格
val avgBuyPrice = if (totalQty.gt(BigDecimal.ZERO)) {
totalCost.div(totalQty)
} else {
continue
}
// 使用实际持仓数量和加权平均买入价格计算未实现盈亏
val unrealizedPnl = currentPrice.subtract(avgBuyPrice).multi(actualQty)
totalUnrealizedPnl = totalUnrealizedPnl.add(unrealizedPnl)
}
return totalUnrealizedPnl.toString()
}
/**
* 计算持仓价值
* 使用链上实际持仓数量和当前市场价格计算
* 按市场聚合,避免重复计算
*/
private fun calculatePositionValue(
buyOrders: List<CopyOrderTracking>,
currentPrices: Map<String, String>,
actualPositions: Map<String, BigDecimal>
): String {
var totalPositionValue = BigDecimal.ZERO
// 按市场聚合,获取所有不同的市场
val marketKeys = buyOrders
.filter { it.outcomeIndex != null }
.map { "${it.marketId}:${it.outcomeIndex}" }
.distinct()
for (key in marketKeys) {
// 获取链上实际持仓数量(如果存在)
val actualQty = actualPositions[key]
// 如果没有链上持仓,计算该市场的总持仓(所有订单的 remainingQuantity 之和)
val totalQty = actualQty ?: buyOrders
.filter { it.outcomeIndex != null && "${it.marketId}:${it.outcomeIndex}" == key }
.sumOf { it.remainingQuantity.toSafeBigDecimal() }
// 如果持仓 <= 0,跳过
if (totalQty.lte(BigDecimal.ZERO)) continue
// 获取当前市场价格
val currentPrice = currentPrices[key]?.toSafeBigDecimal()
?: continue // 如果没有当前价格,跳过
// 计算持仓价值:持仓数量 × 当前价格
val positionValue = totalQty.multi(currentPrice)
totalPositionValue = totalPositionValue.add(positionValue)
}
return totalPositionValue.toString()
}
/**
* 计算盈亏百分比
*/
private fun calculatePnlPercent(
private fun calculatePnlPercentOnlyRealized(
totalBuyAmount: String,
totalRealizedPnl: String,
totalUnrealizedPnl: String
totalRealizedPnl: String
): String {
val buyAmount = totalBuyAmount.toSafeBigDecimal()
if (buyAmount.lte(BigDecimal.ZERO)) return "0"
val totalPnl = totalRealizedPnl.toSafeBigDecimal().add(totalUnrealizedPnl.toSafeBigDecimal())
val percent = totalPnl.div(buyAmount).multi(100)
val percent = totalRealizedPnl.toSafeBigDecimal().div(buyAmount).multi(100)
return percent.setScale(2, RoundingMode.HALF_UP).toString()
}