feat: 实现 Polymarket Maker Rebates Program 费率支持

- 添加费率查询 API 接口 (getFeeRate)
- 修正 API 返回字段名:使用 base_fee 而非 fee_rate_bps(与 TypeScript clob-client 一致)
- 在所有订单创建处动态获取费率:
  * 跟单买入订单 (processBuyTrade)
  * 跟单卖出订单 (matchSellOrder)
  * 账户卖出订单 (sellPosition)
- 费率获取失败时降级到默认值 "0",确保系统可用性
- 添加详细的日志记录,便于监控和调试

参考文档: https://docs.polymarket.com/developers/market-makers/maker-rebates-program
This commit is contained in:
WrBug
2026-01-07 00:31:07 +08:00
parent e48149a19b
commit f257457ad3
5 changed files with 95 additions and 843 deletions
@@ -149,6 +149,19 @@ interface PolymarketClobApi {
@GET("/auth/derive-api-key")
suspend fun deriveApiKey(): Response<ApiKeyResponse>
/**
* 获取费率
* 文档: https://docs.polymarket.com/developers/market-makers/maker-rebates-program#1-fetch-the-fee-rate
* 端点: GET /fee-rate
*
* @param tokenId Token ID
* @return 费率响应
*/
@GET("/fee-rate")
suspend fun getFeeRate(
@Query("token_id") tokenId: String
): Response<FeeRateResponse>
/**
* 获取服务器时间
* 端点: /time
@@ -357,6 +370,18 @@ data class ServerTimeResponse(
val timestamp: Long
)
/**
* 费率响应
* 文档: https://docs.polymarket.com/developers/market-makers/maker-rebates-program#1-fetch-the-fee-rate
*
* 注意:根据 TypeScript clob-client 源码,API 返回的字段名是 base_fee,而不是文档中的 fee_rate_bps
* 参考: clob-client/src/client.ts:312
*/
data class FeeRateResponse(
@SerializedName("base_fee")
val baseFee: Int // 费率基点(0 表示无费率,1000 表示 10%)
)
/**
* 最新价响应(从订单表获取)
*/
@@ -863,6 +863,15 @@ class AccountService(
// 7. 解密私钥
val decryptedPrivateKey = decryptPrivateKey(account)
// 获取费率(根据 Polymarket Maker Rebates Program 要求)
val feeRateResult = clobService.getFeeRate(tokenId)
val feeRateBps = if (feeRateResult.isSuccess) {
feeRateResult.getOrNull()?.toString() ?: "0"
} else {
logger.warn("获取费率失败,使用默认值 0: tokenId=$tokenId, error=${feeRateResult.exceptionOrNull()?.message}")
"0"
}
// 11. 创建并签名订单(使用计算后的卖出数量)
val signedOrder = try {
orderSigningService.createAndSignOrder(
@@ -874,7 +883,7 @@ class AccountService(
size = sellQuantity.toPlainString(), // 使用计算后的卖出数量
signatureType = 2, // Browser Wallet(与正确订单数据一致)
nonce = "0",
feeRateBps = "0",
feeRateBps = feeRateBps, // 使用动态获取的费率
expiration = expiration
)
} catch (e: Exception) {
@@ -401,5 +401,37 @@ class PolymarketClobService(
Result.failure(e)
}
}
/**
* 获取费率
* 文档: https://docs.polymarket.com/developers/market-makers/maker-rebates-program#1-fetch-the-fee-rate
*
* 注意:根据 TypeScript clob-client 源码,API 返回的字段名是 base_fee,而不是文档中的 fee_rate_bps
* 参考: clob-client/src/client.ts:312
*
* @param tokenId Token ID
* @return 费率基点(0 表示无费率,1000 表示 10%)
*/
suspend fun getFeeRate(tokenId: String): Result<Int> {
return try {
val response = clobApi.getFeeRate(tokenId)
if (response.isSuccessful && response.body() != null) {
val baseFee = response.body()!!.baseFee
logger.debug("获取费率成功: tokenId=$tokenId, baseFee=$baseFee")
Result.success(baseFee)
} else {
val errorBody = try {
response.errorBody()?.string()
} catch (e: Exception) {
null
}
logger.error("获取费率失败: tokenId=$tokenId, code=${response.code()}, errorBody=$errorBody")
Result.failure(Exception("获取费率失败: ${response.code()} ${response.message()}"))
}
} catch (e: Exception) {
logger.error("获取费率异常: tokenId=$tokenId, error=${e.message}", e)
Result.failure(e)
}
}
}
@@ -515,7 +515,16 @@ open class CopyOrderTrackingService(
// 解密私钥
val decryptedPrivateKey = decryptPrivateKey(account)
logger.info("准备创建买入订单: copyTradingId=${copyTrading.id}, tradeId=${trade.id}, leaderPrice=${trade.price}, tolerance=${copyTrading.priceTolerance}, calculatedPrice=$buyPrice, quantity=$finalBuyQuantity")
// 获取费率(根据 Polymarket Maker Rebates Program 要求)
val feeRateResult = clobService.getFeeRate(tokenId)
val feeRateBps = if (feeRateResult.isSuccess) {
feeRateResult.getOrNull()?.toString() ?: "0"
} else {
logger.warn("获取费率失败,使用默认值 0: tokenId=$tokenId, error=${feeRateResult.exceptionOrNull()?.message}")
"0"
}
logger.info("准备创建买入订单: copyTradingId=${copyTrading.id}, tradeId=${trade.id}, leaderPrice=${trade.price}, tolerance=${copyTrading.priceTolerance}, calculatedPrice=$buyPrice, quantity=$finalBuyQuantity, baseFee=$feeRateBps")
// 调用API创建订单(带重试机制)
// 重试策略:最多重试 MAX_RETRY_ATTEMPTS 次,每次重试前等待 RETRY_DELAY_MS 毫秒
@@ -530,7 +539,8 @@ open class CopyOrderTrackingService(
size = finalBuyQuantity.toString(),
owner = account.apiKey,
copyTradingId = copyTrading.id!!,
tradeId = trade.id
tradeId = trade.id,
feeRateBps = feeRateBps
)
// 处理订单创建失败
@@ -959,6 +969,15 @@ open class CopyOrderTrackingService(
// 8. 解密私钥(在方法开始时解密一次,后续复用)
val decryptedPrivateKey = decryptPrivateKey(account)
// 获取费率(根据 Polymarket Maker Rebates Program 要求)
val feeRateResult = clobService.getFeeRate(tokenId)
val feeRateBps = if (feeRateResult.isSuccess) {
feeRateResult.getOrNull()?.toString() ?: "0"
} else {
logger.warn("获取费率失败,使用默认值 0: tokenId=$tokenId, error=${feeRateResult.exceptionOrNull()?.message}")
"0"
}
// 9. 创建并签名卖出订单
val signedOrder = try {
orderSigningService.createAndSignOrder(
@@ -970,7 +989,7 @@ open class CopyOrderTrackingService(
size = totalMatched.toString(),
signatureType = 2, // Browser Wallet
nonce = "0",
feeRateBps = "0",
feeRateBps = feeRateBps, // 使用动态获取的费率
expiration = "0"
)
} catch (e: Exception) {
@@ -1008,7 +1027,8 @@ open class CopyOrderTrackingService(
size = totalMatched.toString(),
owner = account.apiKey,
copyTradingId = copyTrading.id,
tradeId = leaderSellTrade.id
tradeId = leaderSellTrade.id,
feeRateBps = feeRateBps
)
if (createOrderResult.isFailure) {
@@ -1100,6 +1120,7 @@ open class CopyOrderTrackingService(
* @param owner API Key(用于owner字段)
* @param copyTradingId 跟单配置ID(用于日志)
* @param tradeId Leader 交易ID(用于日志)
* @param feeRateBps 费率基点(从API动态获取)
* @return 成功返回订单ID,失败返回异常
*/
private suspend fun createOrderWithRetry(
@@ -1112,7 +1133,8 @@ open class CopyOrderTrackingService(
size: String,
owner: String,
copyTradingId: Long,
tradeId: String
tradeId: String,
feeRateBps: String
): Result<String> {
var lastError: Exception? = null
@@ -1129,7 +1151,7 @@ open class CopyOrderTrackingService(
size = size,
signatureType = 2, // Browser Wallet
nonce = "0",
feeRateBps = "0",
feeRateBps = feeRateBps, // 使用动态获取的费率
expiration = "0"
)