fix(cryptotail): 下单成功后拉取实际成交价写库,结算拉取失败补日志
- 执行服务:createOrder 成功后 delay 800ms 再 getOrder 取实际 price/sizeMatched, 用真实触发价与投入金额写 trigger 记录,表现从首条即正确;两处调用传入 L2 凭证 - 结算服务:fetchOrderFill 失败时打日志(orderId 空、账户/凭证、getOrder 失败等), 便于排查表现未更新;注释说明表现依赖历史订单接口 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+30
-8
@@ -201,14 +201,25 @@ class CryptoTailSettlementService(
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 通过 CLOB API 获取订单实际成交价与成交量;需 L2 认证(账户 API 凭证)。
|
* 通过 CLOB API 获取订单实际成交价与成交量;需 L2 认证(账户 API 凭证)。
|
||||||
|
* 只有此接口成功返回有效 price/sizeMatched 时,结算才会更新 triggerPrice、amountUsdc(表现);
|
||||||
|
* 否则只更新结算字段(resolved、realizedPnl 等),表现仍为触发时的值。
|
||||||
*/
|
*/
|
||||||
private suspend fun fetchOrderFill(
|
private suspend fun fetchOrderFill(
|
||||||
trigger: CryptoTailStrategyTrigger,
|
trigger: CryptoTailStrategyTrigger,
|
||||||
strategy: CryptoTailStrategy
|
strategy: CryptoTailStrategy
|
||||||
): Pair<BigDecimal, BigDecimal>? {
|
): Pair<BigDecimal, BigDecimal>? {
|
||||||
val orderId = trigger.orderId?.takeIf { it.isNotBlank() } ?: return null
|
val orderId = trigger.orderId?.takeIf { it.isNotBlank() } ?: run {
|
||||||
val account = accountRepository.findById(strategy.accountId).orElse(null) ?: return null
|
logger.debug("尾盘结算未拉取订单: orderId 为空, triggerId=${trigger.id}")
|
||||||
if (account.apiKey == null || account.apiSecret == null || account.apiPassphrase == null) return null
|
return null
|
||||||
|
}
|
||||||
|
val account = accountRepository.findById(strategy.accountId).orElse(null) ?: run {
|
||||||
|
logger.warn("尾盘结算未拉取订单: 账户不存在, triggerId=${trigger.id}, accountId=${strategy.accountId}")
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
if (account.apiKey == null || account.apiSecret == null || account.apiPassphrase == null) {
|
||||||
|
logger.warn("尾盘结算未拉取订单: 账户未配置 API 凭证, triggerId=${trigger.id}, accountId=${account.id}")
|
||||||
|
return null
|
||||||
|
}
|
||||||
val apiSecret = try {
|
val apiSecret = try {
|
||||||
account.apiSecret?.let { cryptoUtils.decrypt(it) } ?: ""
|
account.apiSecret?.let { cryptoUtils.decrypt(it) } ?: ""
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
@@ -228,11 +239,22 @@ class CryptoTailSettlementService(
|
|||||||
apiPassphrase = apiPassphrase,
|
apiPassphrase = apiPassphrase,
|
||||||
walletAddress = account.walletAddress
|
walletAddress = account.walletAddress
|
||||||
)
|
)
|
||||||
return result.getOrNull()?.let { order ->
|
return result.fold(
|
||||||
val price = order.price.toSafeBigDecimal()
|
onSuccess = { order ->
|
||||||
val sizeMatched = order.sizeMatched.toSafeBigDecimal()
|
val price = order.price.toSafeBigDecimal()
|
||||||
Pair(price, sizeMatched)
|
val sizeMatched = order.sizeMatched.toSafeBigDecimal()
|
||||||
}
|
if (price.gt(BigDecimal.ZERO) && sizeMatched.gt(BigDecimal.ZERO)) {
|
||||||
|
Pair(price, sizeMatched)
|
||||||
|
} else {
|
||||||
|
logger.debug("尾盘结算订单无有效成交: triggerId=${trigger.id}, orderId=$orderId, price=$price, sizeMatched=$sizeMatched")
|
||||||
|
null
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onFailure = { e ->
|
||||||
|
logger.warn("尾盘结算拉取历史订单失败,触发价/投入金额不会更新: triggerId=${trigger.id}, orderId=$orderId, error=${e.message}")
|
||||||
|
null
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
+74
-6
@@ -15,6 +15,8 @@ import com.wrbug.polymarketbot.service.copytrading.orders.OrderSigningService
|
|||||||
import com.wrbug.polymarketbot.util.CryptoUtils
|
import com.wrbug.polymarketbot.util.CryptoUtils
|
||||||
import com.wrbug.polymarketbot.util.RetrofitFactory
|
import com.wrbug.polymarketbot.util.RetrofitFactory
|
||||||
import com.wrbug.polymarketbot.util.fromJson
|
import com.wrbug.polymarketbot.util.fromJson
|
||||||
|
import com.wrbug.polymarketbot.util.gt
|
||||||
|
import com.wrbug.polymarketbot.util.multi
|
||||||
import com.wrbug.polymarketbot.util.toSafeBigDecimal
|
import com.wrbug.polymarketbot.util.toSafeBigDecimal
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.sync.Mutex
|
import kotlinx.coroutines.sync.Mutex
|
||||||
@@ -31,6 +33,12 @@ private const val TRIGGER_FIXED_PRICE = "0.99"
|
|||||||
/** 数量小数位数,与 OrderSigningService 的 roundConfig.size 一致 */
|
/** 数量小数位数,与 OrderSigningService 的 roundConfig.size 一致 */
|
||||||
private const val SIZE_DECIMAL_SCALE = 2
|
private const val SIZE_DECIMAL_SCALE = 2
|
||||||
|
|
||||||
|
/** 下单成功后拉取订单成交数据的短暂延迟(毫秒),便于交易所更新订单状态 */
|
||||||
|
private const val FETCH_ORDER_AFTER_PLACE_DELAY_MS = 800L
|
||||||
|
|
||||||
|
/** 存库时投入金额/触发价小数精度,与结算服务一致 */
|
||||||
|
private const val FILL_AMOUNT_SCALE = 8
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 周期内预置上下文:账户、解密凭证、费率、签名类型、CLOB 客户端;FIXED 模式含预签订单。
|
* 周期内预置上下文:账户、解密凭证、费率、签名类型、CLOB 客户端;FIXED 模式含预签订单。
|
||||||
* 触发时 RATIO 仅算 size 并签名提交,FIXED 直接提交预签订单。
|
* 触发时 RATIO 仅算 size 并签名提交,FIXED 直接提交预签订单。
|
||||||
@@ -250,7 +258,10 @@ class CryptoTailStrategyExecutionService(
|
|||||||
ctx.preSignedOrderByOutcome != null -> {
|
ctx.preSignedOrderByOutcome != null -> {
|
||||||
val orderRequest = ctx.preSignedOrderByOutcome[outcomeIndex]
|
val orderRequest = ctx.preSignedOrderByOutcome[outcomeIndex]
|
||||||
if (orderRequest != null) {
|
if (orderRequest != null) {
|
||||||
submitOrderAndSaveRecord(ctx.clobApi, strategy, periodStartUnix, marketTitle, outcomeIndex, triggerPrice, amountUsdc, orderRequest)
|
submitOrderAndSaveRecord(
|
||||||
|
ctx.clobApi, strategy, periodStartUnix, marketTitle, outcomeIndex, triggerPrice, amountUsdc, orderRequest,
|
||||||
|
ctx.account.apiKey, ctx.apiSecretDecrypted, ctx.apiPassphraseDecrypted, ctx.account.walletAddress
|
||||||
|
)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -276,7 +287,10 @@ class CryptoTailStrategyExecutionService(
|
|||||||
orderType = "FAK",
|
orderType = "FAK",
|
||||||
deferExec = false
|
deferExec = false
|
||||||
)
|
)
|
||||||
submitOrderAndSaveRecord(ctx.clobApi, strategy, periodStartUnix, marketTitle, outcomeIndex, triggerPrice, amountUsdc, orderRequest)
|
submitOrderAndSaveRecord(
|
||||||
|
ctx.clobApi, strategy, periodStartUnix, marketTitle, outcomeIndex, triggerPrice, amountUsdc, orderRequest,
|
||||||
|
ctx.account.apiKey, ctx.apiSecretDecrypted, ctx.apiPassphraseDecrypted, ctx.account.walletAddress
|
||||||
|
)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -285,6 +299,9 @@ class CryptoTailStrategyExecutionService(
|
|||||||
placeOrderForTriggerSlowPath(strategy, periodStartUnix, marketTitle, tokenIds, outcomeIndex, triggerPrice)
|
placeOrderForTriggerSlowPath(strategy, periodStartUnix, marketTitle, tokenIds, outcomeIndex, triggerPrice)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 下单并写触发记录。若传入账户 L2 凭证,下单成功后会拉取订单实际成交价与成交量,用真实触发价与投入金额写库,表现从首条记录起即正确。
|
||||||
|
*/
|
||||||
private suspend fun submitOrderAndSaveRecord(
|
private suspend fun submitOrderAndSaveRecord(
|
||||||
clobApi: PolymarketClobApi,
|
clobApi: PolymarketClobApi,
|
||||||
strategy: CryptoTailStrategy,
|
strategy: CryptoTailStrategy,
|
||||||
@@ -293,7 +310,11 @@ class CryptoTailStrategyExecutionService(
|
|||||||
outcomeIndex: Int,
|
outcomeIndex: Int,
|
||||||
triggerPrice: BigDecimal,
|
triggerPrice: BigDecimal,
|
||||||
amountUsdc: BigDecimal,
|
amountUsdc: BigDecimal,
|
||||||
orderRequest: NewOrderRequest
|
orderRequest: NewOrderRequest,
|
||||||
|
apiKey: String? = null,
|
||||||
|
apiSecret: String? = null,
|
||||||
|
apiPassphrase: String? = null,
|
||||||
|
walletAddress: String? = null
|
||||||
) {
|
) {
|
||||||
var lastError: String? = null
|
var lastError: String? = null
|
||||||
for (attempt in 1..maxRetryAttempts) {
|
for (attempt in 1..maxRetryAttempts) {
|
||||||
@@ -302,8 +323,17 @@ class CryptoTailStrategyExecutionService(
|
|||||||
if (response.isSuccessful && response.body() != null) {
|
if (response.isSuccessful && response.body() != null) {
|
||||||
val body = response.body()!!
|
val body = response.body()!!
|
||||||
if (body.success && body.orderId != null) {
|
if (body.success && body.orderId != null) {
|
||||||
saveTriggerRecord(strategy, periodStartUnix, marketTitle, outcomeIndex, triggerPrice, amountUsdc, body.orderId, "success", null)
|
val (savePrice, saveAmount) = resolveFillPriceAndAmount(
|
||||||
logger.info("尾盘策略下单成功: strategyId=${strategy.id}, periodStartUnix=$periodStartUnix, outcomeIndex=$outcomeIndex, orderId=${body.orderId}")
|
orderId = body.orderId,
|
||||||
|
triggerPrice = triggerPrice,
|
||||||
|
amountUsdc = amountUsdc,
|
||||||
|
apiKey = apiKey,
|
||||||
|
apiSecret = apiSecret,
|
||||||
|
apiPassphrase = apiPassphrase,
|
||||||
|
walletAddress = walletAddress
|
||||||
|
)
|
||||||
|
saveTriggerRecord(strategy, periodStartUnix, marketTitle, outcomeIndex, savePrice, saveAmount, body.orderId, "success", null)
|
||||||
|
logger.info("尾盘策略下单成功: strategyId=${strategy.id}, periodStartUnix=$periodStartUnix, outcomeIndex=$outcomeIndex, orderId=${body.orderId}, triggerPrice=$savePrice")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
lastError = body.errorMsg ?: "unknown"
|
lastError = body.errorMsg ?: "unknown"
|
||||||
@@ -320,6 +350,41 @@ class CryptoTailStrategyExecutionService(
|
|||||||
logger.warn("尾盘策略下单失败(已重试${maxRetryAttempts}次): strategyId=${strategy.id}, periodStartUnix=$periodStartUnix, reason=$lastError")
|
logger.warn("尾盘策略下单失败(已重试${maxRetryAttempts}次): strategyId=${strategy.id}, periodStartUnix=$periodStartUnix, reason=$lastError")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 下单成功后拉取订单实际成交价与成交量;需 L2 凭证。失败或无效则返回传入的 triggerPrice、amountUsdc。
|
||||||
|
*/
|
||||||
|
private suspend fun resolveFillPriceAndAmount(
|
||||||
|
orderId: String,
|
||||||
|
triggerPrice: BigDecimal,
|
||||||
|
amountUsdc: BigDecimal,
|
||||||
|
apiKey: String?,
|
||||||
|
apiSecret: String?,
|
||||||
|
apiPassphrase: String?,
|
||||||
|
walletAddress: String?
|
||||||
|
): Pair<BigDecimal, BigDecimal> {
|
||||||
|
if (apiKey.isNullOrBlank() || apiSecret.isNullOrBlank() || apiPassphrase.isNullOrBlank() || walletAddress.isNullOrBlank()) {
|
||||||
|
return Pair(triggerPrice, amountUsdc)
|
||||||
|
}
|
||||||
|
delay(FETCH_ORDER_AFTER_PLACE_DELAY_MS)
|
||||||
|
val result = clobService.getOrder(
|
||||||
|
orderId = orderId,
|
||||||
|
apiKey = apiKey!!,
|
||||||
|
apiSecret = apiSecret!!,
|
||||||
|
apiPassphrase = apiPassphrase!!,
|
||||||
|
walletAddress = walletAddress!!
|
||||||
|
)
|
||||||
|
return result.getOrNull()?.let { order ->
|
||||||
|
val price = order.price.toSafeBigDecimal()
|
||||||
|
val sizeMatched = order.sizeMatched.toSafeBigDecimal()
|
||||||
|
if (price.gt(BigDecimal.ZERO) && sizeMatched.gt(BigDecimal.ZERO)) {
|
||||||
|
val cost = price.multi(sizeMatched).setScale(FILL_AMOUNT_SCALE, RoundingMode.HALF_UP)
|
||||||
|
Pair(price, cost)
|
||||||
|
} else {
|
||||||
|
Pair(triggerPrice, amountUsdc)
|
||||||
|
}
|
||||||
|
} ?: Pair(triggerPrice, amountUsdc)
|
||||||
|
}
|
||||||
|
|
||||||
/** 无预置上下文时的完整流程:固定价格 0.99,账户/解密/费率/签名在触发时执行 */
|
/** 无预置上下文时的完整流程:固定价格 0.99,账户/解密/费率/签名在触发时执行 */
|
||||||
private suspend fun placeOrderForTriggerSlowPath(
|
private suspend fun placeOrderForTriggerSlowPath(
|
||||||
strategy: CryptoTailStrategy,
|
strategy: CryptoTailStrategy,
|
||||||
@@ -393,7 +458,10 @@ class CryptoTailStrategyExecutionService(
|
|||||||
orderType = "FAK",
|
orderType = "FAK",
|
||||||
deferExec = false
|
deferExec = false
|
||||||
)
|
)
|
||||||
submitOrderAndSaveRecord(clobApi, strategy, periodStartUnix, marketTitle, outcomeIndex, triggerPrice, amountUsdc, orderRequest)
|
submitOrderAndSaveRecord(
|
||||||
|
clobApi, strategy, periodStartUnix, marketTitle, outcomeIndex, triggerPrice, amountUsdc, orderRequest,
|
||||||
|
account.apiKey, apiSecret, apiPassphrase, account.walletAddress
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun fetchEventBySlug(slug: String): Result<GammaEventBySlugResponse> {
|
private suspend fun fetchEventBySlug(slug: String): Result<GammaEventBySlugResponse> {
|
||||||
|
|||||||
Reference in New Issue
Block a user