# 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 { 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> { 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, positions: List ): 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, sellTrades: List ): 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, sellTrades: List ): Double { // 类似胜率计算,分别计算盈利和亏损的平均金额 val buyByMarket = buyTrades.groupBy { it.conditionId } val sellByMarket = sellTrades.groupBy { it.conditionId } val profits = mutableListOf() val losses = mutableListOf() 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, days: Int = 90 ): Result> { 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> { 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 { // 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> { // 实现逻辑 } ``` **请求参数**: ```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> { // 实现逻辑 } ``` **请求参数**: ```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. **多维度分析**:增加更多分析维度(如市场类型、时间分布等)