fix(cryptotail): 结算用 activity 取成交并优先 usdcSize 更新投入金额,尾盘调度与执行整理
- CryptoTailSettlementService: 实际成交从 Data API getUserActivity 获取,优先用 activity.usdcSize 更新 amountUsdc;仅匹配 type=TRADE,排除 REDEEM;先修正 triggerPrice/amountUsdc 再算 realizedPnl 并一次性写库 - CryptoTailStrategyExecutionService: 与结算/调度相关的整理与精简 - CryptoTailStrategyScheduler: 新增策略变更后触发一轮检查的调度 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+73
-80
@@ -1,14 +1,13 @@
|
|||||||
package com.wrbug.polymarketbot.service.cryptotail
|
package com.wrbug.polymarketbot.service.cryptotail
|
||||||
|
|
||||||
import com.wrbug.polymarketbot.api.GammaEventBySlugResponse
|
import com.wrbug.polymarketbot.api.GammaEventBySlugResponse
|
||||||
|
import com.wrbug.polymarketbot.api.PolymarketDataApi
|
||||||
import com.wrbug.polymarketbot.entity.CryptoTailStrategy
|
import com.wrbug.polymarketbot.entity.CryptoTailStrategy
|
||||||
import com.wrbug.polymarketbot.entity.CryptoTailStrategyTrigger
|
import com.wrbug.polymarketbot.entity.CryptoTailStrategyTrigger
|
||||||
import com.wrbug.polymarketbot.repository.AccountRepository
|
import com.wrbug.polymarketbot.repository.AccountRepository
|
||||||
import com.wrbug.polymarketbot.repository.CryptoTailStrategyRepository
|
import com.wrbug.polymarketbot.repository.CryptoTailStrategyRepository
|
||||||
import com.wrbug.polymarketbot.repository.CryptoTailStrategyTriggerRepository
|
import com.wrbug.polymarketbot.repository.CryptoTailStrategyTriggerRepository
|
||||||
import com.wrbug.polymarketbot.service.common.BlockchainService
|
import com.wrbug.polymarketbot.service.common.BlockchainService
|
||||||
import com.wrbug.polymarketbot.service.common.PolymarketClobService
|
|
||||||
import com.wrbug.polymarketbot.util.CryptoUtils
|
|
||||||
import com.wrbug.polymarketbot.util.RetrofitFactory
|
import com.wrbug.polymarketbot.util.RetrofitFactory
|
||||||
import com.wrbug.polymarketbot.util.gt
|
import com.wrbug.polymarketbot.util.gt
|
||||||
import com.wrbug.polymarketbot.util.multi
|
import com.wrbug.polymarketbot.util.multi
|
||||||
@@ -29,7 +28,7 @@ import java.math.RoundingMode
|
|||||||
/**
|
/**
|
||||||
* 尾盘策略结算轮询服务
|
* 尾盘策略结算轮询服务
|
||||||
* 定时扫描「状态成功但未结算」的触发记录,通过 Gamma 获取 conditionId、链上查询结算结果,计算收益并回写。
|
* 定时扫描「状态成功但未结算」的触发记录,通过 Gamma 获取 conditionId、链上查询结算结果,计算收益并回写。
|
||||||
* 收益优先使用 CLOB API 订单详情的实际成交价(price)与成交量(size_matched)计算;API 失败时回退为触发时的 amountUsdc + 固定价 0.99。
|
* 实际成交价与成交量使用 Data API 的 activity 接口获取(getUserActivity),比 CLOB getOrder 更准确;失败时回退为触发时的 amountUsdc + 固定价 0.99。
|
||||||
*/
|
*/
|
||||||
@Service
|
@Service
|
||||||
class CryptoTailSettlementService(
|
class CryptoTailSettlementService(
|
||||||
@@ -37,9 +36,7 @@ class CryptoTailSettlementService(
|
|||||||
private val strategyRepository: CryptoTailStrategyRepository,
|
private val strategyRepository: CryptoTailStrategyRepository,
|
||||||
private val accountRepository: AccountRepository,
|
private val accountRepository: AccountRepository,
|
||||||
private val retrofitFactory: RetrofitFactory,
|
private val retrofitFactory: RetrofitFactory,
|
||||||
private val blockchainService: BlockchainService,
|
private val blockchainService: BlockchainService
|
||||||
private val clobService: PolymarketClobService,
|
|
||||||
private val cryptoUtils: CryptoUtils
|
|
||||||
) {
|
) {
|
||||||
|
|
||||||
private val logger = LoggerFactory.getLogger(CryptoTailSettlementService::class.java)
|
private val logger = LoggerFactory.getLogger(CryptoTailSettlementService::class.java)
|
||||||
@@ -103,22 +100,22 @@ class CryptoTailSettlementService(
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 处理单条触发记录:解析 conditionId -> 查链上结算 -> 若已结算则计算 pnl 并更新。
|
* 处理单条触发记录:解析 conditionId -> 查链上结算 -> 若已结算则计算 pnl 并更新。
|
||||||
* 通过 copy() 生成新实体再 save,不直接修改原实体;有订单信息时用实际成交价与投入金额更新 triggerPrice、amountUsdc。
|
* 通过 copy() 生成新实体再 save,不直接修改原实体;实际成交价与投入金额从 Data API activity 获取并更新 triggerPrice、amountUsdc。
|
||||||
* @return true 表示本条已结算并更新
|
* @return true 表示本条已结算并更新
|
||||||
*/
|
*/
|
||||||
private suspend fun settleOne(trigger: CryptoTailStrategyTrigger): Boolean {
|
private suspend fun settleOne(trigger: CryptoTailStrategyTrigger): Boolean {
|
||||||
if (trigger.resolved) return false
|
if (trigger.resolved) return false
|
||||||
val strategy = strategyRepository.findById(trigger.strategyId).orElse(null) ?: return false
|
val strategy = strategyRepository.findById(trigger.strategyId).orElse(null) ?: return false
|
||||||
val fill = fetchOrderFill(trigger, strategy)
|
val conditionId = resolveConditionId(strategy, trigger) ?: return false
|
||||||
val (newTriggerPrice, newAmountUsdc) = if (fill != null && fill.first.gt(BigDecimal.ZERO) && fill.second.gt(BigDecimal.ZERO)) {
|
val fill = fetchActivityFill(trigger, strategy, conditionId)
|
||||||
val price = fill.first
|
val (newTriggerPrice, newAmountUsdc) = if (fill != null && fill.price.gt(BigDecimal.ZERO) && fill.size.gt(BigDecimal.ZERO)) {
|
||||||
val cost = price.multi(fill.second).setScale(pnlScale, RoundingMode.HALF_UP)
|
val amountUsdc = fill.usdcSize?.takeIf { it.gt(BigDecimal.ZERO) }
|
||||||
Pair(price, cost)
|
?: fill.price.multi(fill.size).setScale(pnlScale, RoundingMode.HALF_UP)
|
||||||
|
Pair(fill.price, amountUsdc)
|
||||||
} else {
|
} else {
|
||||||
Pair(trigger.triggerPrice, trigger.amountUsdc)
|
Pair(trigger.triggerPrice, trigger.amountUsdc)
|
||||||
}
|
}
|
||||||
|
|
||||||
val conditionId = resolveConditionId(strategy, trigger) ?: return false
|
|
||||||
val (_, payouts) = blockchainService.getCondition(conditionId).getOrNull() ?: run {
|
val (_, payouts) = blockchainService.getCondition(conditionId).getOrNull() ?: run {
|
||||||
if (fill != null) {
|
if (fill != null) {
|
||||||
val updated = trigger.copy(triggerPrice = newTriggerPrice, amountUsdc = newAmountUsdc)
|
val updated = trigger.copy(triggerPrice = newTriggerPrice, amountUsdc = newAmountUsdc)
|
||||||
@@ -137,7 +134,12 @@ class CryptoTailSettlementService(
|
|||||||
if (winnerIndex < 0) return false
|
if (winnerIndex < 0) return false
|
||||||
|
|
||||||
val won = trigger.outcomeIndex == winnerIndex
|
val won = trigger.outcomeIndex == winnerIndex
|
||||||
val pnl = computePnlFromApiOrFallback(trigger, strategy, won)
|
val pnl = if (fill != null && fill.price.gt(BigDecimal.ZERO) && fill.size.gt(BigDecimal.ZERO)) {
|
||||||
|
if (won) newAmountUsdc.let { fill.size.subtract(it).setScale(pnlScale, RoundingMode.HALF_UP) }
|
||||||
|
else newAmountUsdc.negate().setScale(pnlScale, RoundingMode.HALF_UP)
|
||||||
|
} else {
|
||||||
|
computePnlFallback(trigger.amountUsdc, won)
|
||||||
|
}
|
||||||
val now = System.currentTimeMillis()
|
val now = System.currentTimeMillis()
|
||||||
|
|
||||||
val updated = trigger.copy(
|
val updated = trigger.copy(
|
||||||
@@ -179,82 +181,73 @@ class CryptoTailSettlementService(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 优先用 CLOB API 订单详情的实际成交价与成交量计算收益;失败则用触发时的 amountUsdc + 固定价 0.99。
|
* Activity 匹配到的一条 TRADE 的成交数据:价格、数量、实际投入 USDC(接口 usdcSize)。
|
||||||
*/
|
*/
|
||||||
private suspend fun computePnlFromApiOrFallback(
|
private data class ActivityFill(
|
||||||
trigger: CryptoTailStrategyTrigger,
|
val price: BigDecimal,
|
||||||
strategy: CryptoTailStrategy,
|
val size: BigDecimal,
|
||||||
won: Boolean
|
val usdcSize: BigDecimal?
|
||||||
): BigDecimal {
|
)
|
||||||
val fill = fetchOrderFill(trigger, strategy)
|
|
||||||
return if (fill != null) {
|
|
||||||
val (price, sizeMatched) = fill
|
|
||||||
if (price.gt(BigDecimal.ZERO) && sizeMatched.gt(BigDecimal.ZERO)) {
|
|
||||||
computePnlFromFill(price, sizeMatched, won)
|
|
||||||
} else {
|
|
||||||
computePnlFallback(trigger.amountUsdc, won)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
computePnlFallback(trigger.amountUsdc, won)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 通过 CLOB API 获取订单实际成交价与成交量;需 L2 认证(账户 API 凭证)。
|
* 通过 Data API activity 接口获取该触发对应的实际成交价、成交量与投入金额(比 CLOB getOrder 更准确)。
|
||||||
* 只有此接口成功返回有效 price/sizeMatched 时,结算才会更新 triggerPrice、amountUsdc(表现);
|
* 只有此接口返回匹配的 TRADE 且 price/size 有效时,结算才会更新 triggerPrice、amountUsdc(表现);投入金额优先用 activity 的 usdcSize。
|
||||||
* 否则只更新结算字段(resolved、realizedPnl 等),表现仍为触发时的值。
|
|
||||||
*/
|
*/
|
||||||
private suspend fun fetchOrderFill(
|
private suspend fun fetchActivityFill(
|
||||||
trigger: CryptoTailStrategyTrigger,
|
trigger: CryptoTailStrategyTrigger,
|
||||||
strategy: CryptoTailStrategy
|
strategy: CryptoTailStrategy,
|
||||||
): Pair<BigDecimal, BigDecimal>? {
|
conditionId: String
|
||||||
val orderId = trigger.orderId?.takeIf { it.isNotBlank() } ?: run {
|
): ActivityFill? {
|
||||||
logger.debug("尾盘结算未拉取订单: orderId 为空, triggerId=${trigger.id}")
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
val account = accountRepository.findById(strategy.accountId).orElse(null) ?: run {
|
val account = accountRepository.findById(strategy.accountId).orElse(null) ?: run {
|
||||||
logger.warn("尾盘结算未拉取订单: 账户不存在, triggerId=${trigger.id}, accountId=${strategy.accountId}")
|
logger.warn("尾盘结算未拉取 activity: 账户不存在, triggerId=${trigger.id}, accountId=${strategy.accountId}")
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
if (account.apiKey == null || account.apiSecret == null || account.apiPassphrase == null) {
|
val user = account.proxyAddress
|
||||||
logger.warn("尾盘结算未拉取订单: 账户未配置 API 凭证, triggerId=${trigger.id}, accountId=${account.id}")
|
val triggerTimeSeconds = trigger.createdAt / 1000
|
||||||
return null
|
val start = triggerTimeSeconds - 120
|
||||||
}
|
val end = triggerTimeSeconds + 600
|
||||||
val apiSecret = try {
|
return try {
|
||||||
account.apiSecret?.let { cryptoUtils.decrypt(it) } ?: ""
|
val dataApi = retrofitFactory.createDataApi()
|
||||||
} catch (e: Exception) {
|
val response = dataApi.getUserActivity(
|
||||||
logger.debug("解密 apiSecret 失败: accountId=${account.id}", e)
|
user = user,
|
||||||
return null
|
type = listOf("TRADE"),
|
||||||
}
|
start = start,
|
||||||
val apiPassphrase = try {
|
end = end,
|
||||||
account.apiPassphrase?.let { cryptoUtils.decrypt(it) } ?: ""
|
limit = 50,
|
||||||
} catch (e: Exception) {
|
sortBy = "TIMESTAMP",
|
||||||
logger.debug("解密 apiPassphrase 失败: accountId=${account.id}", e)
|
sortDirection = "DESC"
|
||||||
return null
|
)
|
||||||
}
|
if (!response.isSuccessful || response.body() == null) {
|
||||||
val result = clobService.getOrder(
|
logger.warn("尾盘结算拉取 activity 失败: triggerId=${trigger.id}, code=${response.code()}")
|
||||||
orderId = orderId,
|
return null
|
||||||
apiKey = account.apiKey!!,
|
}
|
||||||
apiSecret = apiSecret,
|
val activities = response.body()!!
|
||||||
apiPassphrase = apiPassphrase,
|
// 只匹配 TRADE:返回里可能混有 REDEEM(outcomeIndex=999、price=0)等,需排除
|
||||||
walletAddress = account.walletAddress
|
val match = activities.firstOrNull { a ->
|
||||||
)
|
a.type == "TRADE" &&
|
||||||
return result.fold(
|
a.conditionId == conditionId &&
|
||||||
onSuccess = { order ->
|
a.outcomeIndex != null && a.outcomeIndex!! in 0..1 &&
|
||||||
val price = order.price.toSafeBigDecimal()
|
a.outcomeIndex == trigger.outcomeIndex &&
|
||||||
val sizeMatched = order.sizeMatched.toSafeBigDecimal()
|
a.side?.uppercase() == "BUY" &&
|
||||||
if (price.gt(BigDecimal.ZERO) && sizeMatched.gt(BigDecimal.ZERO)) {
|
a.price != null && a.price!! > 0 &&
|
||||||
Pair(price, sizeMatched)
|
a.size != null && a.size!! > 0
|
||||||
} else {
|
} ?: run {
|
||||||
logger.debug("尾盘结算订单无有效成交: triggerId=${trigger.id}, orderId=$orderId, price=$price, sizeMatched=$sizeMatched")
|
logger.debug("尾盘结算 activity 无匹配成交: triggerId=${trigger.id}, conditionId=$conditionId, outcomeIndex=${trigger.outcomeIndex}, 条数=${activities.size}")
|
||||||
null
|
return null
|
||||||
}
|
}
|
||||||
},
|
val price = match.price!!.toSafeBigDecimal()
|
||||||
onFailure = { e ->
|
val size = match.size!!.toSafeBigDecimal()
|
||||||
logger.warn("尾盘结算拉取历史订单失败,触发价/投入金额不会更新: triggerId=${trigger.id}, orderId=$orderId, error=${e.message}")
|
val usdcSize = match.usdcSize?.toSafeBigDecimal()?.takeIf { it.gt(BigDecimal.ZERO) }
|
||||||
|
if (price.gt(BigDecimal.ZERO) && size.gt(BigDecimal.ZERO)) {
|
||||||
|
ActivityFill(price = price, size = size, usdcSize = usdcSize)
|
||||||
|
} else {
|
||||||
|
logger.debug("尾盘结算 activity 成交数据无效: triggerId=${trigger.id}, price=$price, size=$size")
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
)
|
} catch (e: Exception) {
|
||||||
|
logger.warn("尾盘结算拉取 activity 异常,触发价/投入金额不会更新: triggerId=${trigger.id}, error=${e.message}")
|
||||||
|
null
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
+6
-74
@@ -15,8 +15,6 @@ 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
|
||||||
@@ -33,12 +31,6 @@ 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 直接提交预签订单。
|
||||||
@@ -258,10 +250,7 @@ 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(
|
submitOrderAndSaveRecord(ctx.clobApi, strategy, periodStartUnix, marketTitle, outcomeIndex, triggerPrice, amountUsdc, orderRequest)
|
||||||
ctx.clobApi, strategy, periodStartUnix, marketTitle, outcomeIndex, triggerPrice, amountUsdc, orderRequest,
|
|
||||||
ctx.account.apiKey, ctx.apiSecretDecrypted, ctx.apiPassphraseDecrypted, ctx.account.walletAddress
|
|
||||||
)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -287,10 +276,7 @@ class CryptoTailStrategyExecutionService(
|
|||||||
orderType = "FAK",
|
orderType = "FAK",
|
||||||
deferExec = false
|
deferExec = false
|
||||||
)
|
)
|
||||||
submitOrderAndSaveRecord(
|
submitOrderAndSaveRecord(ctx.clobApi, strategy, periodStartUnix, marketTitle, outcomeIndex, triggerPrice, amountUsdc, orderRequest)
|
||||||
ctx.clobApi, strategy, periodStartUnix, marketTitle, outcomeIndex, triggerPrice, amountUsdc, orderRequest,
|
|
||||||
ctx.account.apiKey, ctx.apiSecretDecrypted, ctx.apiPassphraseDecrypted, ctx.account.walletAddress
|
|
||||||
)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -299,9 +285,6 @@ 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,
|
||||||
@@ -310,11 +293,7 @@ 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) {
|
||||||
@@ -323,17 +302,8 @@ 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) {
|
||||||
val (savePrice, saveAmount) = resolveFillPriceAndAmount(
|
saveTriggerRecord(strategy, periodStartUnix, marketTitle, outcomeIndex, triggerPrice, amountUsdc, body.orderId, "success", null)
|
||||||
orderId = body.orderId,
|
logger.info("尾盘策略下单成功: strategyId=${strategy.id}, periodStartUnix=$periodStartUnix, outcomeIndex=$outcomeIndex, 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"
|
||||||
@@ -350,41 +320,6 @@ 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,
|
||||||
@@ -458,10 +393,7 @@ class CryptoTailStrategyExecutionService(
|
|||||||
orderType = "FAK",
|
orderType = "FAK",
|
||||||
deferExec = false
|
deferExec = false
|
||||||
)
|
)
|
||||||
submitOrderAndSaveRecord(
|
submitOrderAndSaveRecord(clobApi, strategy, periodStartUnix, marketTitle, outcomeIndex, triggerPrice, amountUsdc, orderRequest)
|
||||||
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> {
|
||||||
|
|||||||
+37
@@ -0,0 +1,37 @@
|
|||||||
|
package com.wrbug.polymarketbot.service.cryptotail
|
||||||
|
|
||||||
|
import com.wrbug.polymarketbot.event.CryptoTailStrategyChangedEvent
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.SupervisorJob
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
|
import org.slf4j.LoggerFactory
|
||||||
|
import org.springframework.context.event.EventListener
|
||||||
|
import org.springframework.stereotype.Component
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 尾盘策略:策略创建/更新/启用后立即触发一轮检查(由 WebSocket 订单簿持续监听,此处仅做创建/更新后的一次补充)。
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
class CryptoTailStrategyScheduler(
|
||||||
|
private val executionService: CryptoTailStrategyExecutionService
|
||||||
|
) {
|
||||||
|
|
||||||
|
private val logger = LoggerFactory.getLogger(CryptoTailStrategyScheduler::class.java)
|
||||||
|
|
||||||
|
private val scope = CoroutineScope(Dispatchers.Default + SupervisorJob())
|
||||||
|
|
||||||
|
@EventListener
|
||||||
|
fun onStrategyChanged(event: CryptoTailStrategyChangedEvent) {
|
||||||
|
scope.launch {
|
||||||
|
try {
|
||||||
|
runBlocking {
|
||||||
|
executionService.runCycle()
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
logger.error("尾盘策略变更后立即执行异常: ${e.message}", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user