- fix: correct copyRatio scaling (divide by 100) - fix: enforce minimum order size with round-up logic - feat: customize Telegram notification icons - feat: optimize frontend order list (active loading, refresh buttons) - refactor: optimize OnChain WebSocket connection management
837 lines
25 KiB
Markdown
837 lines
25 KiB
Markdown
# Polymarket 聪明钱分析方案
|
||
|
||
## 1. 概述
|
||
|
||
聪明钱(Smart Money)分析是指识别和跟踪在 Polymarket 平台上表现优异的交易者,通过分析他们的交易行为、持仓和盈亏表现,来辅助投资决策。
|
||
|
||
## 2. 核心分析维度
|
||
|
||
### 2.1 交易表现指标
|
||
|
||
#### 2.1.1 胜率(Win Rate)
|
||
- **定义**:盈利交易数 / 总交易数
|
||
- **计算方式**:
|
||
- 通过 `getUserActivity` API 获取用户历史交易
|
||
- 筛选 `type = "TRADE"` 的活动
|
||
- 计算每笔交易的盈亏(通过买入价和卖出价)
|
||
- 统计盈利交易数和总交易数
|
||
|
||
#### 2.1.2 平均盈亏比(Average PnL Ratio)
|
||
- **定义**:平均盈利金额 / 平均亏损金额
|
||
- **计算方式**:
|
||
- 分别计算盈利交易和亏损交易的平均金额
|
||
- 计算比值
|
||
|
||
#### 2.1.3 总盈亏(Total PnL)
|
||
- **定义**:所有已实现盈亏的总和
|
||
- **数据来源**:
|
||
- 通过 `getPositions` API 获取 `realizedPnl`
|
||
- 或通过 `getUserActivity` API 计算历史交易的累计盈亏
|
||
|
||
#### 2.1.4 未实现盈亏(Unrealized PnL)
|
||
- **定义**:当前持仓的浮动盈亏
|
||
- **数据来源**:
|
||
- 通过 `getPositions` API 获取 `cashPnl`(当前盈亏)
|
||
- 或通过 `currentValue - initialValue` 计算
|
||
|
||
#### 2.1.5 收益率(Return Rate)
|
||
- **定义**:总盈亏 / 总投入
|
||
- **计算方式**:
|
||
- 总投入 = 所有买入交易的总金额
|
||
- 总盈亏 = 已实现盈亏 + 未实现盈亏
|
||
- 收益率 = 总盈亏 / 总投入
|
||
|
||
### 2.2 交易行为指标
|
||
|
||
#### 2.2.1 交易频率(Trading Frequency)
|
||
- **定义**:单位时间内的交易次数
|
||
- **计算方式**:
|
||
- 通过 `getUserActivity` API 获取指定时间范围内的交易数
|
||
- 计算日均/周均交易次数
|
||
|
||
#### 2.2.2 持仓周期(Holding Period)
|
||
- **定义**:平均持仓时间
|
||
- **计算方式**:
|
||
- 跟踪每笔买入和对应的卖出时间
|
||
- 计算平均持仓天数
|
||
|
||
#### 2.2.3 市场偏好(Market Preference)
|
||
- **定义**:交易者偏好的市场类型
|
||
- **计算方式**:
|
||
- 统计交易者在不同分类(sports、crypto)的交易分布
|
||
- 统计交易者偏好的市场主题
|
||
|
||
#### 2.2.4 仓位规模(Position Size)
|
||
- **定义**:平均单笔交易金额
|
||
- **计算方式**:
|
||
- 通过 `getUserActivity` API 获取 `usdcSize`
|
||
- 计算平均交易金额
|
||
|
||
### 2.3 风险指标
|
||
|
||
#### 2.3.1 最大回撤(Maximum Drawdown)
|
||
- **定义**:从峰值到谷值的最大跌幅
|
||
- **计算方式**:
|
||
- 跟踪账户价值的时序变化
|
||
- 计算每个峰值的回撤幅度
|
||
- 取最大值
|
||
|
||
#### 2.3.2 夏普比率(Sharpe Ratio)
|
||
- **定义**:风险调整后的收益率
|
||
- **计算方式**:
|
||
- 收益率标准差 / 平均收益率
|
||
- 需要足够的历史数据
|
||
|
||
#### 2.3.3 胜率稳定性(Win Rate Stability)
|
||
- **定义**:不同时间段胜率的一致性
|
||
- **计算方式**:
|
||
- 按时间段(如每月)计算胜率
|
||
- 计算胜率的方差或标准差
|
||
|
||
## 3. 数据收集方法
|
||
|
||
### 3.1 使用 Polymarket Data API
|
||
|
||
#### 3.1.1 获取用户仓位
|
||
```kotlin
|
||
// 接口:GET /positions
|
||
// 参数:
|
||
// - user: 用户钱包地址(必需)
|
||
// - market: 市场ID(可选)
|
||
// - limit: 限制数量(可选)
|
||
// - offset: 偏移量(可选)
|
||
// - sortBy: 排序字段(可选,如 "currentValue")
|
||
// - sortDirection: 排序方向(可选,如 "desc")
|
||
|
||
val positions = dataApi.getPositions(
|
||
user = walletAddress,
|
||
limit = 100,
|
||
sortBy = "currentValue",
|
||
sortDirection = "desc"
|
||
)
|
||
```
|
||
|
||
**返回数据包含**:
|
||
- `currentValue`: 当前仓位价值
|
||
- `cashPnl`: 当前盈亏(未实现)
|
||
- `realizedPnl`: 已实现盈亏
|
||
- `percentPnl`: 盈亏百分比
|
||
- `avgPrice`: 平均买入价
|
||
- `curPrice`: 当前价格
|
||
|
||
#### 3.1.2 获取用户活动(交易历史)
|
||
```kotlin
|
||
// 接口:GET /activity
|
||
// 参数:
|
||
// - user: 用户钱包地址(必需)
|
||
// - type: 活动类型(可选,如 ["TRADE"])
|
||
// - side: 交易方向(可选,如 "BUY" 或 "SELL")
|
||
// - start: 开始时间戳(可选)
|
||
// - end: 结束时间戳(可选)
|
||
// - limit: 限制数量(可选)
|
||
// - offset: 偏移量(可选)
|
||
|
||
val activities = dataApi.getUserActivity(
|
||
user = walletAddress,
|
||
type = listOf("TRADE"),
|
||
side = "BUY",
|
||
start = startTimestamp,
|
||
end = endTimestamp,
|
||
limit = 1000
|
||
)
|
||
```
|
||
|
||
**返回数据包含**:
|
||
- `type`: 活动类型(TRADE、SPLIT、MERGE、REDEEM等)
|
||
- `side`: 交易方向(BUY、SELL)
|
||
- `size`: 交易数量
|
||
- `usdcSize`: 交易金额(USDC)
|
||
- `price`: 交易价格
|
||
- `timestamp`: 交易时间戳
|
||
- `title`: 市场标题
|
||
- `slug`: 市场标识
|
||
|
||
#### 3.1.3 获取仓位总价值
|
||
```kotlin
|
||
// 接口:GET /value
|
||
// 参数:
|
||
// - user: 用户钱包地址(必需)
|
||
// - market: 市场ID列表(可选)
|
||
|
||
val totalValue = dataApi.getTotalValue(
|
||
user = walletAddress,
|
||
market = listOf("market1", "market2")
|
||
)
|
||
```
|
||
|
||
### 3.2 使用 Polymarket CLOB API
|
||
|
||
#### 3.2.1 获取交易记录
|
||
```kotlin
|
||
// 接口:GET /data/trades
|
||
// 参数:
|
||
// - maker_address: 交易者地址(可选)
|
||
// - market: 市场ID(可选)
|
||
// - before: 之前的时间戳(可选,用于分页)
|
||
// - after: 之后的时间戳(可选,用于分页)
|
||
// - next_cursor: 分页游标(可选)
|
||
|
||
val trades = clobApi.getTrades(
|
||
maker_address = walletAddress,
|
||
market = marketId,
|
||
after = startTimestamp.toString()
|
||
)
|
||
```
|
||
|
||
**返回数据包含**:
|
||
- `id`: 交易ID
|
||
- `market`: 市场ID
|
||
- `side`: 交易方向(BUY、SELL)
|
||
- `price`: 交易价格
|
||
- `size`: 交易数量
|
||
- `timestamp`: 交易时间戳
|
||
- `user`: 交易者地址
|
||
|
||
## 4. 聪明钱识别算法
|
||
|
||
### 4.1 基础筛选条件
|
||
|
||
#### 4.1.1 最低交易次数
|
||
- **条件**:总交易数 >= 50
|
||
- **目的**:确保有足够的数据进行统计分析
|
||
|
||
#### 4.1.2 最低胜率
|
||
- **条件**:胜率 >= 55%
|
||
- **目的**:筛选出表现优于随机交易者
|
||
|
||
#### 4.1.3 最低总盈亏
|
||
- **条件**:总盈亏 >= 1000 USDC
|
||
- **目的**:筛选出有实际盈利能力的交易者
|
||
|
||
#### 4.1.4 最低收益率
|
||
- **条件**:收益率 >= 20%
|
||
- **目的**:筛选出有良好回报的交易者
|
||
|
||
### 4.2 综合评分算法
|
||
|
||
```kotlin
|
||
// 聪明钱评分算法
|
||
fun calculateSmartMoneyScore(
|
||
winRate: Double, // 胜率(0-1)
|
||
totalPnl: Double, // 总盈亏(USDC)
|
||
returnRate: Double, // 收益率(0-1)
|
||
tradeCount: Int, // 交易次数
|
||
avgPnlRatio: Double // 平均盈亏比
|
||
): Double {
|
||
// 权重配置
|
||
val winRateWeight = 0.3
|
||
val totalPnlWeight = 0.25
|
||
val returnRateWeight = 0.25
|
||
val tradeCountWeight = 0.1
|
||
val avgPnlRatioWeight = 0.1
|
||
|
||
// 归一化处理
|
||
val normalizedWinRate = winRate * 100 // 转换为百分比
|
||
val normalizedTotalPnl = min(totalPnl / 10000, 1.0) * 100 // 归一化到0-100
|
||
val normalizedReturnRate = returnRate * 100 // 转换为百分比
|
||
val normalizedTradeCount = min(tradeCount / 200, 1.0) * 100 // 归一化到0-100
|
||
val normalizedAvgPnlRatio = min(avgPnlRatio / 3.0, 1.0) * 100 // 归一化到0-100
|
||
|
||
// 加权求和
|
||
val score = normalizedWinRate * winRateWeight +
|
||
normalizedTotalPnl * totalPnlWeight +
|
||
normalizedReturnRate * returnRateWeight +
|
||
normalizedTradeCount * tradeCountWeight +
|
||
normalizedAvgPnlRatio * avgPnlRatioWeight
|
||
|
||
return score
|
||
}
|
||
```
|
||
|
||
### 4.3 排名算法
|
||
|
||
1. **按综合评分排序**:计算所有候选交易者的综合评分,按降序排列
|
||
2. **按分类排名**:分别计算 sports 和 crypto 分类的排名
|
||
3. **按时间段排名**:分别计算最近7天、30天、90天的排名
|
||
|
||
## 5. 实时监控方案
|
||
|
||
### 5.1 监控目标
|
||
|
||
1. **新交易**:监控聪明钱交易者的新买入/卖出交易
|
||
2. **持仓变化**:监控聪明钱交易者的持仓变化
|
||
3. **市场关注**:监控聪明钱交易者关注的新市场
|
||
|
||
### 5.2 实现方式
|
||
|
||
#### 5.2.1 使用 WebSocket(推荐)
|
||
- 订阅 Polymarket WebSocket 的 User Channel
|
||
- 监听 `event_type = "trade"` 事件
|
||
- 过滤出聪明钱交易者的交易
|
||
|
||
#### 5.2.2 使用轮询
|
||
- 定期调用 `getUserActivity` API(如每5分钟)
|
||
- 比较时间戳,识别新交易
|
||
- 使用 `after` 参数只获取新数据
|
||
|
||
### 5.3 跟单集成
|
||
|
||
聪明钱分析可以与现有的跟单系统集成:
|
||
|
||
1. **自动添加 Leader**:识别到聪明钱交易者后,自动添加到 Leader 列表
|
||
2. **智能跟单**:根据聪明钱交易者的表现,动态调整跟单比例
|
||
3. **风险控制**:根据聪明钱交易者的风险指标,设置跟单限制
|
||
|
||
## 6. 实现示例
|
||
|
||
### 6.1 聪明钱分析服务
|
||
|
||
```kotlin
|
||
@Service
|
||
class SmartMoneyAnalysisService(
|
||
private val retrofitFactory: RetrofitFactory,
|
||
private val blockchainService: BlockchainService
|
||
) {
|
||
private val logger = LoggerFactory.getLogger(SmartMoneyAnalysisService::class.java)
|
||
private val dataApi = retrofitFactory.createDataApi()
|
||
|
||
/**
|
||
* 分析单个交易者的表现
|
||
*/
|
||
suspend fun analyzeTrader(walletAddress: String, days: Int = 90): Result<TraderAnalysis> {
|
||
return try {
|
||
val endTime = System.currentTimeMillis()
|
||
val startTime = endTime - (days * 24 * 60 * 60 * 1000L)
|
||
|
||
// 1. 获取交易历史
|
||
val activitiesResult = getTradeActivities(walletAddress, startTime, endTime)
|
||
if (activitiesResult.isFailure) {
|
||
return Result.failure(activitiesResult.exceptionOrNull() ?: Exception("获取交易历史失败"))
|
||
}
|
||
val activities = activitiesResult.getOrNull() ?: emptyList()
|
||
|
||
// 2. 获取当前仓位
|
||
val positionsResult = blockchainService.getPositions(walletAddress)
|
||
val positions = if (positionsResult.isSuccess) {
|
||
positionsResult.getOrNull() ?: emptyList()
|
||
} else {
|
||
emptyList()
|
||
}
|
||
|
||
// 3. 计算指标
|
||
val metrics = calculateMetrics(activities, positions)
|
||
|
||
// 4. 计算综合评分
|
||
val score = calculateSmartMoneyScore(
|
||
winRate = metrics.winRate,
|
||
totalPnl = metrics.totalPnl,
|
||
returnRate = metrics.returnRate,
|
||
tradeCount = metrics.tradeCount,
|
||
avgPnlRatio = metrics.avgPnlRatio
|
||
)
|
||
|
||
Result.success(
|
||
TraderAnalysis(
|
||
walletAddress = walletAddress,
|
||
metrics = metrics,
|
||
score = score,
|
||
positions = positions.size,
|
||
lastTradeTime = activities.maxByOrNull { it.timestamp }?.timestamp
|
||
)
|
||
)
|
||
} catch (e: Exception) {
|
||
logger.error("分析交易者失败: ${e.message}", e)
|
||
Result.failure(e)
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 获取交易活动
|
||
*/
|
||
private suspend fun getTradeActivities(
|
||
walletAddress: String,
|
||
startTime: Long,
|
||
endTime: Long
|
||
): Result<List<UserActivityResponse>> {
|
||
return try {
|
||
val response = dataApi.getUserActivity(
|
||
user = walletAddress,
|
||
type = listOf("TRADE"),
|
||
start = startTime,
|
||
end = endTime,
|
||
limit = 1000,
|
||
sortBy = "timestamp",
|
||
sortDirection = "desc"
|
||
)
|
||
|
||
if (response.isSuccessful && response.body() != null) {
|
||
Result.success(response.body()!!)
|
||
} else {
|
||
Result.failure(Exception("获取交易活动失败: ${response.code()} ${response.message()}"))
|
||
}
|
||
} catch (e: Exception) {
|
||
logger.error("获取交易活动异常: ${e.message}", e)
|
||
Result.failure(e)
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 计算交易指标
|
||
*/
|
||
private fun calculateMetrics(
|
||
activities: List<UserActivityResponse>,
|
||
positions: List<PositionResponse>
|
||
): TraderMetrics {
|
||
// 分离买入和卖出交易
|
||
val buyTrades = activities.filter { it.side == "BUY" }
|
||
val sellTrades = activities.filter { it.side == "SELL" }
|
||
|
||
// 计算总交易数
|
||
val tradeCount = activities.size
|
||
|
||
// 计算总投入(买入金额总和)
|
||
val totalInvested = buyTrades.sumOf { it.usdcSize ?: 0.0 }
|
||
|
||
// 计算已实现盈亏(从仓位数据)
|
||
val realizedPnl = positions.sumOf { it.realizedPnl ?: 0.0 }
|
||
|
||
// 计算未实现盈亏(从仓位数据)
|
||
val unrealizedPnl = positions.sumOf { it.cashPnl ?: 0.0 }
|
||
|
||
// 计算总盈亏
|
||
val totalPnl = realizedPnl + unrealizedPnl
|
||
|
||
// 计算收益率
|
||
val returnRate = if (totalInvested > 0) {
|
||
totalPnl / totalInvested
|
||
} else {
|
||
0.0
|
||
}
|
||
|
||
// 计算胜率(需要匹配买入和卖出交易)
|
||
val winRate = calculateWinRate(buyTrades, sellTrades)
|
||
|
||
// 计算平均盈亏比
|
||
val avgPnlRatio = calculateAvgPnlRatio(buyTrades, sellTrades)
|
||
|
||
return TraderMetrics(
|
||
tradeCount = tradeCount,
|
||
totalInvested = totalInvested,
|
||
totalPnl = totalPnl,
|
||
realizedPnl = realizedPnl,
|
||
unrealizedPnl = unrealizedPnl,
|
||
returnRate = returnRate,
|
||
winRate = winRate,
|
||
avgPnlRatio = avgPnlRatio
|
||
)
|
||
}
|
||
|
||
/**
|
||
* 计算胜率
|
||
* 通过匹配买入和卖出交易来计算
|
||
*/
|
||
private fun calculateWinRate(
|
||
buyTrades: List<UserActivityResponse>,
|
||
sellTrades: List<UserActivityResponse>
|
||
): Double {
|
||
// 按市场分组买入和卖出交易
|
||
val buyByMarket = buyTrades.groupBy { it.conditionId }
|
||
val sellByMarket = sellTrades.groupBy { it.conditionId }
|
||
|
||
var winCount = 0
|
||
var totalCount = 0
|
||
|
||
// 遍历每个市场
|
||
buyByMarket.forEach { (marketId, buys) ->
|
||
val sells = sellByMarket[marketId] ?: emptyList()
|
||
|
||
// 简单匹配:按时间顺序匹配买入和卖出
|
||
// 实际应该使用更精确的匹配算法(如 FIFO)
|
||
var buyIndex = 0
|
||
var sellIndex = 0
|
||
|
||
while (buyIndex < buys.size && sellIndex < sells.size) {
|
||
val buy = buys[buyIndex]
|
||
val sell = sells[sellIndex]
|
||
|
||
// 计算盈亏
|
||
val buyPrice = buy.price ?: 0.0
|
||
val sellPrice = sell.price ?: 0.0
|
||
val pnl = (sellPrice - buyPrice) * (buy.size ?: 0.0)
|
||
|
||
if (pnl > 0) {
|
||
winCount++
|
||
}
|
||
totalCount++
|
||
|
||
buyIndex++
|
||
sellIndex++
|
||
}
|
||
}
|
||
|
||
return if (totalCount > 0) {
|
||
winCount.toDouble() / totalCount
|
||
} else {
|
||
0.0
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 计算平均盈亏比
|
||
*/
|
||
private fun calculateAvgPnlRatio(
|
||
buyTrades: List<UserActivityResponse>,
|
||
sellTrades: List<UserActivityResponse>
|
||
): Double {
|
||
// 类似胜率计算,分别计算盈利和亏损的平均金额
|
||
val buyByMarket = buyTrades.groupBy { it.conditionId }
|
||
val sellByMarket = sellTrades.groupBy { it.conditionId }
|
||
|
||
val profits = mutableListOf<Double>()
|
||
val losses = mutableListOf<Double>()
|
||
|
||
buyByMarket.forEach { (marketId, buys) ->
|
||
val sells = sellByMarket[marketId] ?: emptyList()
|
||
|
||
var buyIndex = 0
|
||
var sellIndex = 0
|
||
|
||
while (buyIndex < buys.size && sellIndex < sells.size) {
|
||
val buy = buys[buyIndex]
|
||
val sell = sells[sellIndex]
|
||
|
||
val buyPrice = buy.price ?: 0.0
|
||
val sellPrice = sell.price ?: 0.0
|
||
val pnl = (sellPrice - buyPrice) * (buy.size ?: 0.0)
|
||
|
||
if (pnl > 0) {
|
||
profits.add(pnl)
|
||
} else if (pnl < 0) {
|
||
losses.add(-pnl)
|
||
}
|
||
|
||
buyIndex++
|
||
sellIndex++
|
||
}
|
||
}
|
||
|
||
val avgProfit = if (profits.isNotEmpty()) {
|
||
profits.average()
|
||
} else {
|
||
0.0
|
||
}
|
||
|
||
val avgLoss = if (losses.isNotEmpty()) {
|
||
losses.average()
|
||
} else {
|
||
0.0
|
||
}
|
||
|
||
return if (avgLoss > 0) {
|
||
avgProfit / avgLoss
|
||
} else {
|
||
if (avgProfit > 0) Double.MAX_VALUE else 0.0
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 计算聪明钱评分
|
||
*/
|
||
private fun calculateSmartMoneyScore(
|
||
winRate: Double,
|
||
totalPnl: Double,
|
||
returnRate: Double,
|
||
tradeCount: Int,
|
||
avgPnlRatio: Double
|
||
): Double {
|
||
val winRateWeight = 0.3
|
||
val totalPnlWeight = 0.25
|
||
val returnRateWeight = 0.25
|
||
val tradeCountWeight = 0.1
|
||
val avgPnlRatioWeight = 0.1
|
||
|
||
val normalizedWinRate = winRate * 100
|
||
val normalizedTotalPnl = min(totalPnl / 10000, 1.0) * 100
|
||
val normalizedReturnRate = returnRate * 100
|
||
val normalizedTradeCount = min(tradeCount / 200.0, 1.0) * 100
|
||
val normalizedAvgPnlRatio = min(avgPnlRatio / 3.0, 1.0) * 100
|
||
|
||
val score = normalizedWinRate * winRateWeight +
|
||
normalizedTotalPnl * totalPnlWeight +
|
||
normalizedReturnRate * returnRateWeight +
|
||
normalizedTradeCount * tradeCountWeight +
|
||
normalizedAvgPnlRatio * avgPnlRatioWeight
|
||
|
||
return score
|
||
}
|
||
|
||
/**
|
||
* 批量分析交易者
|
||
*/
|
||
suspend fun analyzeTraders(
|
||
walletAddresses: List<String>,
|
||
days: Int = 90
|
||
): Result<List<TraderAnalysis>> {
|
||
return try {
|
||
val analyses = walletAddresses.mapNotNull { address ->
|
||
analyzeTrader(address, days).getOrNull()
|
||
}
|
||
Result.success(analyses.sortedByDescending { it.score })
|
||
} catch (e: Exception) {
|
||
logger.error("批量分析交易者失败: ${e.message}", e)
|
||
Result.failure(e)
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 交易者分析结果
|
||
*/
|
||
data class TraderAnalysis(
|
||
val walletAddress: String,
|
||
val metrics: TraderMetrics,
|
||
val score: Double,
|
||
val positions: Int,
|
||
val lastTradeTime: Long?
|
||
)
|
||
|
||
/**
|
||
* 交易者指标
|
||
*/
|
||
data class TraderMetrics(
|
||
val tradeCount: Int,
|
||
val totalInvested: Double,
|
||
val totalPnl: Double,
|
||
val realizedPnl: Double,
|
||
val unrealizedPnl: Double,
|
||
val returnRate: Double,
|
||
val winRate: Double,
|
||
val avgPnlRatio: Double
|
||
)
|
||
```
|
||
|
||
### 6.2 聪明钱排名服务
|
||
|
||
```kotlin
|
||
@Service
|
||
class SmartMoneyRankingService(
|
||
private val smartMoneyAnalysisService: SmartMoneyAnalysisService
|
||
) {
|
||
private val logger = LoggerFactory.getLogger(SmartMoneyRankingService::class.java)
|
||
|
||
/**
|
||
* 获取聪明钱排名
|
||
*/
|
||
suspend fun getRankings(
|
||
category: String? = null, // sports 或 crypto
|
||
days: Int = 90,
|
||
limit: Int = 100
|
||
): Result<List<TraderRanking>> {
|
||
return try {
|
||
// 1. 获取候选交易者列表
|
||
// 这里需要从某个数据源获取(如数据库、API等)
|
||
val candidates = getCandidateTraders(category)
|
||
|
||
// 2. 批量分析交易者
|
||
val analysesResult = smartMoneyAnalysisService.analyzeTraders(candidates, days)
|
||
if (analysesResult.isFailure) {
|
||
return Result.failure(analysesResult.exceptionOrNull() ?: Exception("分析失败"))
|
||
}
|
||
val analyses = analysesResult.getOrNull() ?: emptyList()
|
||
|
||
// 3. 筛选和排序
|
||
val rankings = analyses
|
||
.filter { it.metrics.tradeCount >= 50 } // 最低交易次数
|
||
.filter { it.metrics.winRate >= 0.55 } // 最低胜率
|
||
.filter { it.metrics.totalPnl >= 1000 } // 最低总盈亏
|
||
.sortedByDescending { it.score }
|
||
.take(limit)
|
||
.mapIndexed { index, analysis ->
|
||
TraderRanking(
|
||
rank = index + 1,
|
||
walletAddress = analysis.walletAddress,
|
||
score = analysis.score,
|
||
metrics = analysis.metrics,
|
||
positions = analysis.positions,
|
||
lastTradeTime = analysis.lastTradeTime
|
||
)
|
||
}
|
||
|
||
Result.success(rankings)
|
||
} catch (e: Exception) {
|
||
logger.error("获取排名失败: ${e.message}", e)
|
||
Result.failure(e)
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 获取候选交易者列表
|
||
* 这里需要实现具体的获取逻辑(如从数据库、API等)
|
||
*/
|
||
private suspend fun getCandidateTraders(category: String?): List<String> {
|
||
// TODO: 实现获取候选交易者的逻辑
|
||
// 可以从以下来源获取:
|
||
// 1. 数据库中的 Leader 列表
|
||
// 2. Polymarket 的公开数据
|
||
// 3. 用户提交的交易者地址
|
||
return emptyList()
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 交易者排名
|
||
*/
|
||
data class TraderRanking(
|
||
val rank: Int,
|
||
val walletAddress: String,
|
||
val score: Double,
|
||
val metrics: TraderMetrics,
|
||
val positions: Int,
|
||
val lastTradeTime: Long?
|
||
)
|
||
```
|
||
|
||
## 7. 数据存储建议
|
||
|
||
### 7.1 数据库表设计
|
||
|
||
```sql
|
||
-- 聪明钱交易者表
|
||
CREATE TABLE smart_money_traders (
|
||
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||
wallet_address VARCHAR(42) NOT NULL UNIQUE,
|
||
score DOUBLE NOT NULL,
|
||
win_rate DOUBLE NOT NULL,
|
||
total_pnl DECIMAL(20, 8) NOT NULL,
|
||
return_rate DOUBLE NOT NULL,
|
||
trade_count INT NOT NULL,
|
||
category VARCHAR(20), -- sports 或 crypto
|
||
last_analysis_time BIGINT NOT NULL,
|
||
created_at BIGINT NOT NULL,
|
||
updated_at BIGINT NOT NULL,
|
||
INDEX idx_score (score DESC),
|
||
INDEX idx_category (category),
|
||
INDEX idx_last_analysis_time (last_analysis_time)
|
||
);
|
||
|
||
-- 交易者历史指标表(用于追踪指标变化)
|
||
CREATE TABLE trader_metrics_history (
|
||
id BIGINT AUTO_INCREMENT PRIMARY KEY,
|
||
wallet_address VARCHAR(42) NOT NULL,
|
||
win_rate DOUBLE NOT NULL,
|
||
total_pnl DECIMAL(20, 8) NOT NULL,
|
||
return_rate DOUBLE NOT NULL,
|
||
trade_count INT NOT NULL,
|
||
recorded_at BIGINT NOT NULL,
|
||
INDEX idx_wallet_address (wallet_address),
|
||
INDEX idx_recorded_at (recorded_at)
|
||
);
|
||
```
|
||
|
||
### 7.2 缓存策略
|
||
|
||
- **Redis 缓存**:缓存聪明钱排名列表,减少数据库查询
|
||
- **缓存过期时间**:建议 1 小时
|
||
- **缓存键**:`smart_money:rankings:{category}:{days}`
|
||
|
||
## 8. API 接口设计
|
||
|
||
### 8.1 获取聪明钱排名
|
||
|
||
```kotlin
|
||
@PostMapping("/smart-money/rankings")
|
||
fun getRankings(@RequestBody request: SmartMoneyRankingsRequest): ResponseEntity<ApiResponse<SmartMoneyRankingsResponse>> {
|
||
// 实现逻辑
|
||
}
|
||
```
|
||
|
||
**请求参数**:
|
||
```json
|
||
{
|
||
"category": "sports", // 可选:sports 或 crypto
|
||
"days": 90, // 可选:分析时间范围(天)
|
||
"limit": 100, // 可选:返回数量
|
||
"minScore": 50 // 可选:最低评分
|
||
}
|
||
```
|
||
|
||
**响应数据**:
|
||
```json
|
||
{
|
||
"code": 0,
|
||
"data": {
|
||
"rankings": [
|
||
{
|
||
"rank": 1,
|
||
"walletAddress": "0x...",
|
||
"score": 85.5,
|
||
"metrics": {
|
||
"tradeCount": 150,
|
||
"winRate": 0.65,
|
||
"totalPnl": 5000.0,
|
||
"returnRate": 0.35,
|
||
"avgPnlRatio": 2.5
|
||
},
|
||
"positions": 10,
|
||
"lastTradeTime": 1234567890
|
||
}
|
||
],
|
||
"total": 100
|
||
},
|
||
"msg": ""
|
||
}
|
||
```
|
||
|
||
### 8.2 分析单个交易者
|
||
|
||
```kotlin
|
||
@PostMapping("/smart-money/analyze")
|
||
fun analyzeTrader(@RequestBody request: SmartMoneyAnalyzeRequest): ResponseEntity<ApiResponse<TraderAnalysisDto>> {
|
||
// 实现逻辑
|
||
}
|
||
```
|
||
|
||
**请求参数**:
|
||
```json
|
||
{
|
||
"walletAddress": "0x...",
|
||
"days": 90
|
||
}
|
||
```
|
||
|
||
## 9. 注意事项
|
||
|
||
### 9.1 API 限制
|
||
|
||
- **Data API 速率限制**:注意 API 调用频率,避免触发限流
|
||
- **数据延迟**:Data API 的数据可能有延迟,不是实时的
|
||
- **数据完整性**:某些历史数据可能不完整,需要处理缺失数据
|
||
|
||
### 9.2 计算精度
|
||
|
||
- **价格精度**:Polymarket 使用 0.01-0.99 的价格范围,注意精度问题
|
||
- **金额精度**:使用 `BigDecimal` 进行金额计算,避免浮点数误差
|
||
- **时间精度**:注意时间戳的精度(毫秒 vs 秒)
|
||
|
||
### 9.3 性能优化
|
||
|
||
- **批量查询**:尽量批量查询多个交易者的数据
|
||
- **缓存策略**:缓存分析结果,避免重复计算
|
||
- **异步处理**:使用异步任务处理大量数据分析
|
||
|
||
### 9.4 数据质量
|
||
|
||
- **数据验证**:验证 API 返回的数据完整性
|
||
- **异常处理**:处理 API 调用失败的情况
|
||
- **数据清洗**:清洗异常数据(如价格为 0、数量为负数等)
|
||
|
||
## 10. 后续优化方向
|
||
|
||
1. **机器学习模型**:使用机器学习模型预测交易者未来表现
|
||
2. **实时监控**:集成 WebSocket 实现实时监控聪明钱交易
|
||
3. **跟单推荐**:根据聪明钱分析结果,推荐适合跟单的交易者
|
||
4. **风险预警**:监控聪明钱交易者的风险指标,及时预警
|
||
5. **多维度分析**:增加更多分析维度(如市场类型、时间分布等)
|
||
|
||
|