feat(cryptotail): AUTO 最小价差 100%→50% 动态系数,progress 按毫秒计算
- BinanceKlineAutoSpreadService: 缓存 100% 基准价差,新增 getAutoMinSpreadBase - CryptoTailStrategyExecutionService: 按窗口内毫秒进度算 coefficient,effectiveMinSpread = baseSpread × (1 - 0.5×progress) - 新增方案文档 docs/crypto-tail-auto-spread-dynamic-coefficient.md Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+13
-12
@@ -9,8 +9,8 @@ import java.math.RoundingMode
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
|
||||
/**
|
||||
* 自动最小价差:按周期计算。每个周期首次需要时,拉取该周期前的 20 根已收盘 K 线,按方向筛选、IQR 剔除后求平均 × 0.7,缓存 (interval, period)。
|
||||
* 不在保存策略时计算。
|
||||
* 自动最小价差:按周期计算。每个周期首次需要时,拉取该周期前的 20 根已收盘 K 线,按方向筛选、IQR 剔除后求平均,缓存 100% 基准值 (interval, period)。
|
||||
* 触发时由调用方按窗口进度计算动态系数(100%→50%)后得到有效最小价差。不在保存策略时计算。
|
||||
*/
|
||||
@Service
|
||||
class BinanceKlineAutoSpreadService(
|
||||
@@ -21,15 +21,15 @@ class BinanceKlineAutoSpreadService(
|
||||
|
||||
private val symbol = "BTCUSDC"
|
||||
private val historyLimit = 20
|
||||
private val autoSpreadCoefficient = BigDecimal("0.7")
|
||||
private val minSamplesAfterIqr = 3
|
||||
|
||||
/** (intervalSeconds, periodStartUnix) -> (minSpreadUp, minSpreadDown) */
|
||||
/** (intervalSeconds, periodStartUnix) -> (baseSpreadUp, baseSpreadDown),100% 基准价差 */
|
||||
private val cache = ConcurrentHashMap<String, Pair<BigDecimal, BigDecimal>>()
|
||||
|
||||
private fun cacheKey(intervalSeconds: Int, periodStartUnix: Long): String = "$intervalSeconds-$periodStartUnix"
|
||||
|
||||
fun getAutoMinSpread(intervalSeconds: Int, periodStartUnix: Long, outcomeIndex: Int): BigDecimal? {
|
||||
/** 返回该周期、该方向的 100% 基准价差,供调用方按窗口进度应用动态系数。 */
|
||||
fun getAutoMinSpreadBase(intervalSeconds: Int, periodStartUnix: Long, outcomeIndex: Int): BigDecimal? {
|
||||
val key = cacheKey(intervalSeconds, periodStartUnix)
|
||||
val (up, down) = cache[key] ?: run {
|
||||
computeAndCache(intervalSeconds, periodStartUnix) ?: return null
|
||||
@@ -37,6 +37,7 @@ class BinanceKlineAutoSpreadService(
|
||||
return if (outcomeIndex == 0) up else down
|
||||
}
|
||||
|
||||
/** 计算并缓存 100% 基准价差(IQR 平均,不乘系数)。预加载与触发时共用此缓存。 */
|
||||
fun computeAndCache(intervalSeconds: Int, periodStartUnix: Long): Pair<BigDecimal, BigDecimal>? {
|
||||
val intervalStr = if (intervalSeconds == 300) "5m" else "15m"
|
||||
val endTimeMs = periodStartUnix * 1000L
|
||||
@@ -50,15 +51,15 @@ class BinanceKlineAutoSpreadService(
|
||||
if (closeP > openP) spreadsUp.add(closeP.subtract(openP))
|
||||
if (closeP < openP) spreadsDown.add(openP.subtract(closeP))
|
||||
}
|
||||
val avgUp = averageAfterIqr(spreadsUp).multiply(autoSpreadCoefficient).setScale(8, RoundingMode.HALF_UP)
|
||||
val avgDown = averageAfterIqr(spreadsDown).multiply(autoSpreadCoefficient).setScale(8, RoundingMode.HALF_UP)
|
||||
cache[cacheKey(intervalSeconds, periodStartUnix)] = avgUp to avgDown
|
||||
val baseUp = averageAfterIqr(spreadsUp).setScale(8, RoundingMode.HALF_UP)
|
||||
val baseDown = averageAfterIqr(spreadsDown).setScale(8, RoundingMode.HALF_UP)
|
||||
cache[cacheKey(intervalSeconds, periodStartUnix)] = baseUp to baseDown
|
||||
logger.info(
|
||||
"尾盘自动价差已计算并缓存(按周期): interval=${intervalSeconds}s periodStartUnix=$periodStartUnix | " +
|
||||
"Up方向: 样本数=${spreadsUp.size}, minSpreadUp=${avgUp.toPlainString()} | " +
|
||||
"Down方向: 样本数=${spreadsDown.size}, minSpreadDown=${avgDown.toPlainString()}"
|
||||
"尾盘自动价差已计算并缓存(100%基准): interval=${intervalSeconds}s periodStartUnix=$periodStartUnix | " +
|
||||
"Up方向: 样本数=${spreadsUp.size}, baseSpreadUp=${baseUp.toPlainString()} | " +
|
||||
"Down方向: 样本数=${spreadsDown.size}, baseSpreadDown=${baseDown.toPlainString()}"
|
||||
)
|
||||
return avgUp to avgDown
|
||||
return baseUp to baseDown
|
||||
}
|
||||
|
||||
private fun fetchKlines(interval: String, limit: Int, endTime: Long? = null): List<List<Any>>? {
|
||||
|
||||
+26
-2
@@ -16,7 +16,9 @@ import com.wrbug.polymarketbot.service.common.PolymarketClobService
|
||||
import com.wrbug.polymarketbot.service.copytrading.orders.OrderSigningService
|
||||
import com.wrbug.polymarketbot.util.CryptoUtils
|
||||
import com.wrbug.polymarketbot.util.RetrofitFactory
|
||||
import com.wrbug.polymarketbot.util.div
|
||||
import com.wrbug.polymarketbot.util.fromJson
|
||||
import com.wrbug.polymarketbot.util.multi
|
||||
import com.wrbug.polymarketbot.util.toSafeBigDecimal
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
@@ -188,14 +190,36 @@ class CryptoTailStrategyExecutionService(
|
||||
val spreadAbs = closeP.subtract(openP).abs()
|
||||
val effectiveMinSpread = when (mode) {
|
||||
"FIXED" -> strategy.minSpreadValue?.takeIf { it > BigDecimal.ZERO }
|
||||
"AUTO" -> binanceKlineAutoSpreadService.getAutoMinSpread(strategy.intervalSeconds, periodStartUnix, outcomeIndex)
|
||||
?: binanceKlineAutoSpreadService.computeAndCache(strategy.intervalSeconds, periodStartUnix)?.let { if (outcomeIndex == 0) it.first else it.second }
|
||||
"AUTO" -> computeAutoEffectiveMinSpread(strategy, periodStartUnix, outcomeIndex)
|
||||
else -> null
|
||||
}
|
||||
if (effectiveMinSpread == null || effectiveMinSpread <= BigDecimal.ZERO) return true
|
||||
return spreadAbs >= effectiveMinSpread
|
||||
}
|
||||
|
||||
/**
|
||||
* AUTO 模式:取 100% 基准价差,按窗口内毫秒进度计算动态系数(100%→50%)得到有效最小价差。
|
||||
*/
|
||||
private fun computeAutoEffectiveMinSpread(strategy: CryptoTailStrategy, periodStartUnix: Long, outcomeIndex: Int): BigDecimal? {
|
||||
val baseSpread = binanceKlineAutoSpreadService.getAutoMinSpreadBase(strategy.intervalSeconds, periodStartUnix, outcomeIndex)
|
||||
?: binanceKlineAutoSpreadService.computeAndCache(strategy.intervalSeconds, periodStartUnix)?.let { if (outcomeIndex == 0) it.first else it.second }
|
||||
?: return null
|
||||
if (baseSpread <= BigDecimal.ZERO) return null
|
||||
val windowStartMs = (periodStartUnix + strategy.windowStartSeconds) * 1000L
|
||||
val windowEndMs = (periodStartUnix + strategy.windowEndSeconds) * 1000L
|
||||
val windowLenMs = windowEndMs - windowStartMs
|
||||
val coefficient = if (windowLenMs <= 0) {
|
||||
BigDecimal.ONE
|
||||
} else {
|
||||
val nowMs = System.currentTimeMillis()
|
||||
val elapsedMs = (nowMs - windowStartMs).toBigDecimal()
|
||||
val progress = elapsedMs.div(windowLenMs.toBigDecimal(), 18, RoundingMode.HALF_UP)
|
||||
.let { p -> maxOf(BigDecimal.ZERO, minOf(BigDecimal.ONE, p)) }
|
||||
BigDecimal.ONE.subtract(progress.multi("0.5"))
|
||||
}
|
||||
return baseSpread.multi(coefficient).setScale(8, RoundingMode.HALF_UP)
|
||||
}
|
||||
|
||||
private suspend fun placeOrderForTrigger(
|
||||
strategy: CryptoTailStrategy,
|
||||
periodStartUnix: Long,
|
||||
|
||||
Reference in New Issue
Block a user