优化跟单过滤逻辑和卖出价格计算
- 优化订单簿请求:仅在需要时请求,避免不必要的API调用 - 卖出价格改为市价卖出:优先使用订单簿bestBid,失败则使用Leader价格,固定按90%计算 - 价格容忍度默认值:如果为0,自动使用5% - 使用枚举和数据类优化checkFilters方法: - 新增FilterResult数据类和FilterStatus枚举 - 移除无用字段isBuyOrder参数 - 使用类型安全的枚举替代字符串判断 - 优化代码结构:减少if-else嵌套,使用链式调用 - 移除最小订单簿深度功能(minOrderbookDepth) - 优化最小订单深度逻辑:检查所有方向(买盘+卖盘)的总深度
This commit is contained in:
@@ -32,7 +32,6 @@ data class CopyTradingCreateRequest(
|
||||
// 过滤条件
|
||||
val minOrderDepth: String? = null, // 最小订单深度(USDC金额),NULL表示不启用
|
||||
val maxSpread: String? = null, // 最大价差(绝对价格),NULL表示不启用
|
||||
val minOrderbookDepth: String? = null, // 最小订单簿深度(USDC金额),NULL表示不启用
|
||||
val minPrice: String? = null, // 最低价格(可选),NULL表示不限制最低价
|
||||
val maxPrice: String? = null, // 最高价格(可选),NULL表示不限制最高价
|
||||
// 新增配置字段
|
||||
@@ -64,7 +63,6 @@ data class CopyTradingUpdateRequest(
|
||||
// 过滤条件
|
||||
val minOrderDepth: String? = null,
|
||||
val maxSpread: String? = null,
|
||||
val minOrderbookDepth: String? = null,
|
||||
val minPrice: String? = null, // 最低价格(可选),NULL表示不限制最低价
|
||||
val maxPrice: String? = null, // 最高价格(可选),NULL表示不限制最高价
|
||||
// 新增配置字段
|
||||
@@ -133,7 +131,6 @@ data class CopyTradingDto(
|
||||
// 过滤条件
|
||||
val minOrderDepth: String?,
|
||||
val maxSpread: String?,
|
||||
val minOrderbookDepth: String?,
|
||||
val minPrice: String?, // 最低价格(可选),NULL表示不限制最低价
|
||||
val maxPrice: String?, // 最高价格(可选),NULL表示不限制最高价
|
||||
// 新增配置字段
|
||||
|
||||
@@ -22,7 +22,6 @@ data class TemplateCreateRequest(
|
||||
// 过滤条件
|
||||
val minOrderDepth: String? = null, // 最小订单深度(USDC金额),NULL表示不启用
|
||||
val maxSpread: String? = null, // 最大价差(绝对价格),NULL表示不启用
|
||||
val minOrderbookDepth: String? = null, // 最小订单簿深度(USDC金额),NULL表示不启用
|
||||
val minPrice: String? = null, // 最低价格(可选),NULL表示不限制最低价
|
||||
val maxPrice: String? = null // 最高价格(可选),NULL表示不限制最高价
|
||||
)
|
||||
@@ -50,7 +49,6 @@ data class TemplateUpdateRequest(
|
||||
// 过滤条件
|
||||
val minOrderDepth: String? = null, // 最小订单深度(USDC金额),NULL表示不启用
|
||||
val maxSpread: String? = null, // 最大价差(绝对价格),NULL表示不启用
|
||||
val minOrderbookDepth: String? = null, // 最小订单簿深度(USDC金额),NULL表示不启用
|
||||
val minPrice: String? = null, // 最低价格(可选),NULL表示不限制最低价
|
||||
val maxPrice: String? = null // 最高价格(可选),NULL表示不限制最高价
|
||||
)
|
||||
@@ -85,7 +83,6 @@ data class TemplateCopyRequest(
|
||||
// 过滤条件
|
||||
val minOrderDepth: String? = null, // 最小订单深度(USDC金额),NULL表示不启用
|
||||
val maxSpread: String? = null, // 最大价差(绝对价格),NULL表示不启用
|
||||
val minOrderbookDepth: String? = null, // 最小订单簿深度(USDC金额),NULL表示不启用
|
||||
val minPrice: String? = null, // 最低价格(可选),NULL表示不限制最低价
|
||||
val maxPrice: String? = null // 最高价格(可选),NULL表示不限制最高价
|
||||
)
|
||||
@@ -120,7 +117,6 @@ data class TemplateDto(
|
||||
// 过滤条件
|
||||
val minOrderDepth: String?,
|
||||
val maxSpread: String?,
|
||||
val minOrderbookDepth: String?,
|
||||
val minPrice: String?, // 最低价格(可选),NULL表示不限制最低价
|
||||
val maxPrice: String?, // 最高价格(可选),NULL表示不限制最高价
|
||||
val createdAt: Long,
|
||||
|
||||
@@ -78,9 +78,6 @@ data class CopyTrading(
|
||||
@Column(name = "max_spread", precision = 20, scale = 8)
|
||||
val maxSpread: BigDecimal? = null, // 最大价差(绝对价格),NULL表示不启用
|
||||
|
||||
@Column(name = "min_orderbook_depth", precision = 20, scale = 8)
|
||||
val minOrderbookDepth: BigDecimal? = null, // 最小订单簿深度(USDC金额),NULL表示不启用
|
||||
|
||||
@Column(name = "min_price", precision = 20, scale = 8)
|
||||
val minPrice: BigDecimal? = null, // 最低价格(可选),NULL表示不限制最低价
|
||||
|
||||
|
||||
@@ -66,9 +66,6 @@ data class CopyTradingTemplate(
|
||||
@Column(name = "max_spread", precision = 20, scale = 8)
|
||||
val maxSpread: BigDecimal? = null, // 最大价差(绝对价格),NULL表示不启用
|
||||
|
||||
@Column(name = "min_orderbook_depth", precision = 20, scale = 8)
|
||||
val minOrderbookDepth: BigDecimal? = null, // 最小订单簿深度(USDC金额),NULL表示不启用
|
||||
|
||||
@Column(name = "min_price", precision = 20, scale = 8)
|
||||
val minPrice: BigDecimal? = null, // 最低价格(可选),NULL表示不限制最低价
|
||||
|
||||
|
||||
+63
-89
@@ -25,85 +25,86 @@ class CopyTradingFilterService(
|
||||
* 检查过滤条件
|
||||
* @param copyTrading 跟单配置
|
||||
* @param tokenId token ID(用于获取订单簿)
|
||||
* @param isBuyOrder 是否为买入订单(true=买入,false=卖出)
|
||||
* @return Pair<是否通过, 失败原因>
|
||||
* @param tradePrice Leader 交易价格,用于价格区间检查
|
||||
* @return 过滤结果
|
||||
*/
|
||||
suspend fun checkFilters(
|
||||
copyTrading: CopyTrading,
|
||||
tokenId: String,
|
||||
isBuyOrder: Boolean,
|
||||
tradePrice: BigDecimal? = null // Leader 交易价格,用于价格区间检查
|
||||
): Pair<Boolean, String> {
|
||||
): FilterResult {
|
||||
// 1. 价格区间检查(如果配置了价格区间)
|
||||
if (tradePrice != null) {
|
||||
val priceRangeCheck = checkPriceRange(copyTrading, tradePrice)
|
||||
if (!priceRangeCheck.first) {
|
||||
return priceRangeCheck
|
||||
if (!priceRangeCheck.isPassed) {
|
||||
return FilterResult.priceRangeFailed(priceRangeCheck.reason)
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 价格合理性检查(基础检查,无需配置)
|
||||
// 这个检查在获取订单簿时进行,如果价格不在 0.01-0.99 范围内,订单簿获取会失败
|
||||
// 2. 检查是否需要获取订单簿
|
||||
// 只有在配置了需要订单簿的过滤条件时才获取
|
||||
val needOrderbook = copyTrading.maxSpread != null || copyTrading.minOrderDepth != null
|
||||
|
||||
// 3. 获取订单簿
|
||||
if (!needOrderbook) {
|
||||
// 不需要订单簿,直接通过
|
||||
return FilterResult.passed()
|
||||
}
|
||||
|
||||
// 3. 获取订单簿(仅在需要时,只请求一次)
|
||||
val orderbookResult = clobService.getOrderbookByTokenId(tokenId)
|
||||
if (!orderbookResult.isSuccess) {
|
||||
val error = orderbookResult.exceptionOrNull()
|
||||
return Pair(false, "获取订单簿失败: ${error?.message ?: "未知错误"}")
|
||||
return FilterResult.orderbookError("获取订单簿失败: ${error?.message ?: "未知错误"}")
|
||||
}
|
||||
|
||||
val orderbook = orderbookResult.getOrNull()
|
||||
if (orderbook == null) {
|
||||
return Pair(false, "订单簿为空")
|
||||
?: return FilterResult.orderbookEmpty()
|
||||
|
||||
// 4. 买一卖一价差过滤(如果配置了)
|
||||
if (copyTrading.maxSpread != null) {
|
||||
val spreadCheck = checkSpread(copyTrading, orderbook)
|
||||
if (!spreadCheck.isPassed) {
|
||||
return FilterResult.spreadFailed(spreadCheck.reason, orderbook)
|
||||
}
|
||||
}
|
||||
|
||||
// 4. 买一卖一价差过滤
|
||||
val spreadCheck = checkSpread(copyTrading, orderbook)
|
||||
if (!spreadCheck.first) {
|
||||
return spreadCheck
|
||||
// 5. 订单深度过滤(如果配置了,检查所有方向)
|
||||
if (copyTrading.minOrderDepth != null) {
|
||||
val depthCheck = checkOrderDepth(copyTrading, orderbook)
|
||||
if (!depthCheck.isPassed) {
|
||||
return FilterResult.orderDepthFailed(depthCheck.reason, orderbook)
|
||||
}
|
||||
}
|
||||
|
||||
// 5. 订单深度过滤
|
||||
val depthCheck = checkOrderDepth(copyTrading, orderbook, isBuyOrder)
|
||||
if (!depthCheck.first) {
|
||||
return depthCheck
|
||||
}
|
||||
|
||||
// 6. 最小订单簿深度过滤(可选)
|
||||
val orderbookDepthCheck = checkOrderbookDepth(copyTrading, orderbook, isBuyOrder)
|
||||
if (!orderbookDepthCheck.first) {
|
||||
return orderbookDepthCheck
|
||||
}
|
||||
|
||||
return Pair(true, "")
|
||||
return FilterResult.passed(orderbook)
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查价格区间
|
||||
* @param copyTrading 跟单配置
|
||||
* @param tradePrice Leader 交易价格
|
||||
* @return Pair<是否通过, 失败原因>
|
||||
* @return 过滤结果
|
||||
*/
|
||||
private fun checkPriceRange(
|
||||
copyTrading: CopyTrading,
|
||||
tradePrice: BigDecimal
|
||||
): Pair<Boolean, String> {
|
||||
): FilterResult {
|
||||
// 如果未配置价格区间,直接通过
|
||||
if (copyTrading.minPrice == null && copyTrading.maxPrice == null) {
|
||||
return Pair(true, "")
|
||||
return FilterResult.passed()
|
||||
}
|
||||
|
||||
// 检查最低价格
|
||||
if (copyTrading.minPrice != null && tradePrice.lt(copyTrading.minPrice)) {
|
||||
return Pair(false, "价格低于最低限制: $tradePrice < ${copyTrading.minPrice}")
|
||||
return FilterResult.priceRangeFailed("价格低于最低限制: $tradePrice < ${copyTrading.minPrice}")
|
||||
}
|
||||
|
||||
// 检查最高价格
|
||||
if (copyTrading.maxPrice != null && tradePrice.gt(copyTrading.maxPrice)) {
|
||||
return Pair(false, "价格高于最高限制: $tradePrice > ${copyTrading.maxPrice}")
|
||||
return FilterResult.priceRangeFailed("价格高于最高限制: $tradePrice > ${copyTrading.maxPrice}")
|
||||
}
|
||||
|
||||
return Pair(true, "")
|
||||
return FilterResult.passed()
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -114,10 +115,10 @@ class CopyTradingFilterService(
|
||||
private fun checkSpread(
|
||||
copyTrading: CopyTrading,
|
||||
orderbook: OrderbookResponse
|
||||
): Pair<Boolean, String> {
|
||||
): FilterResult {
|
||||
// 如果未启用价差过滤,直接通过
|
||||
if (copyTrading.maxSpread == null) {
|
||||
return Pair(true, "")
|
||||
return FilterResult.passed()
|
||||
}
|
||||
|
||||
// 获取买盘中的最高价格(bestBid = bids 中的最大值)
|
||||
@@ -131,84 +132,57 @@ class CopyTradingFilterService(
|
||||
.minOrNull()
|
||||
|
||||
if (bestBid == null || bestAsk == null) {
|
||||
return Pair(false, "订单簿缺少买一或卖一价格")
|
||||
return FilterResult.spreadFailed("订单簿缺少买一或卖一价格", orderbook)
|
||||
}
|
||||
|
||||
// 计算价差(绝对价格)
|
||||
val spread = bestAsk.subtract(bestBid)
|
||||
|
||||
if (spread.gt(copyTrading.maxSpread)) {
|
||||
return Pair(false, "价差过大: $spread > ${copyTrading.maxSpread}")
|
||||
return FilterResult.spreadFailed("价差过大: $spread > ${copyTrading.maxSpread}", orderbook)
|
||||
}
|
||||
|
||||
return Pair(true, "")
|
||||
return FilterResult.passed()
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查订单深度
|
||||
* 检查订单深度(检查所有方向:买盘和卖盘的总深度)
|
||||
*/
|
||||
private fun checkOrderDepth(
|
||||
copyTrading: CopyTrading,
|
||||
orderbook: OrderbookResponse,
|
||||
isBuyOrder: Boolean
|
||||
): Pair<Boolean, String> {
|
||||
orderbook: OrderbookResponse
|
||||
): FilterResult {
|
||||
// 如果未启用订单深度过滤,直接通过
|
||||
if (copyTrading.minOrderDepth == null) {
|
||||
return Pair(true, "")
|
||||
return FilterResult.passed()
|
||||
}
|
||||
|
||||
// 对于买入订单,检查卖盘(asks)深度
|
||||
// 对于卖出订单,检查买盘(bids)深度
|
||||
val orders = if (isBuyOrder) orderbook.asks else orderbook.bids
|
||||
|
||||
// 计算总深度(累计订单金额)
|
||||
var totalDepth = BigDecimal.ZERO
|
||||
for (order in orders) {
|
||||
// 计算买盘(bids)总深度
|
||||
var bidsDepth = BigDecimal.ZERO
|
||||
for (order in orderbook.bids) {
|
||||
val price = order.price.toSafeBigDecimal()
|
||||
val size = order.size.toSafeBigDecimal()
|
||||
val orderAmount = price.multi(size)
|
||||
totalDepth = totalDepth.add(orderAmount)
|
||||
bidsDepth = bidsDepth.add(orderAmount)
|
||||
}
|
||||
|
||||
// 计算卖盘(asks)总深度
|
||||
var asksDepth = BigDecimal.ZERO
|
||||
for (order in orderbook.asks) {
|
||||
val price = order.price.toSafeBigDecimal()
|
||||
val size = order.size.toSafeBigDecimal()
|
||||
val orderAmount = price.multi(size)
|
||||
asksDepth = asksDepth.add(orderAmount)
|
||||
}
|
||||
|
||||
// 计算总深度(买盘 + 卖盘)
|
||||
val totalDepth = bidsDepth.add(asksDepth)
|
||||
|
||||
if (totalDepth.lt(copyTrading.minOrderDepth)) {
|
||||
return Pair(false, "订单深度不足: $totalDepth < ${copyTrading.minOrderDepth}")
|
||||
return FilterResult.orderDepthFailed("订单深度不足: $totalDepth < ${copyTrading.minOrderDepth}", orderbook)
|
||||
}
|
||||
|
||||
return Pair(true, "")
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查最小订单簿深度(前 N 档深度)
|
||||
*/
|
||||
private fun checkOrderbookDepth(
|
||||
copyTrading: CopyTrading,
|
||||
orderbook: OrderbookResponse,
|
||||
isBuyOrder: Boolean
|
||||
): Pair<Boolean, String> {
|
||||
// 如果未启用最小订单簿深度过滤,直接通过
|
||||
if (copyTrading.minOrderbookDepth == null) {
|
||||
return Pair(true, "")
|
||||
}
|
||||
|
||||
// 对于买入订单,检查卖盘(asks)前 3 档深度
|
||||
// 对于卖出订单,检查买盘(bids)前 3 档深度
|
||||
val orders = if (isBuyOrder) orderbook.asks else orderbook.bids
|
||||
val topNOrders = orders.take(3) // 前 3 档
|
||||
|
||||
// 计算前 N 档总深度
|
||||
var totalDepth = BigDecimal.ZERO
|
||||
for (order in topNOrders) {
|
||||
val price = order.price.toSafeBigDecimal()
|
||||
val size = order.size.toSafeBigDecimal()
|
||||
val orderAmount = price.multi(size)
|
||||
totalDepth = totalDepth.add(orderAmount)
|
||||
}
|
||||
|
||||
if (totalDepth.lt(copyTrading.minOrderbookDepth)) {
|
||||
return Pair(false, "订单簿深度不足: $totalDepth < ${copyTrading.minOrderbookDepth}")
|
||||
}
|
||||
|
||||
return Pair(true, "")
|
||||
return FilterResult.passed()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
-6
@@ -85,7 +85,6 @@ class CopyTradingService(
|
||||
supportSell = request.supportSell ?: template.supportSell,
|
||||
minOrderDepth = request.minOrderDepth?.toSafeBigDecimal() ?: template.minOrderDepth,
|
||||
maxSpread = request.maxSpread?.toSafeBigDecimal() ?: template.maxSpread,
|
||||
minOrderbookDepth = request.minOrderbookDepth?.toSafeBigDecimal() ?: template.minOrderbookDepth,
|
||||
minPrice = request.minPrice?.toSafeBigDecimal() ?: template.minPrice,
|
||||
maxPrice = request.maxPrice?.toSafeBigDecimal() ?: template.maxPrice
|
||||
)
|
||||
@@ -112,7 +111,6 @@ class CopyTradingService(
|
||||
supportSell = request.supportSell ?: true,
|
||||
minOrderDepth = request.minOrderDepth?.toSafeBigDecimal(),
|
||||
maxSpread = request.maxSpread?.toSafeBigDecimal(),
|
||||
minOrderbookDepth = request.minOrderbookDepth?.toSafeBigDecimal(),
|
||||
minPrice = request.minPrice?.toSafeBigDecimal(),
|
||||
maxPrice = request.maxPrice?.toSafeBigDecimal()
|
||||
)
|
||||
@@ -139,7 +137,6 @@ class CopyTradingService(
|
||||
supportSell = config.supportSell,
|
||||
minOrderDepth = config.minOrderDepth,
|
||||
maxSpread = config.maxSpread,
|
||||
minOrderbookDepth = config.minOrderbookDepth,
|
||||
minPrice = config.minPrice,
|
||||
maxPrice = config.maxPrice,
|
||||
configName = configName,
|
||||
@@ -205,7 +202,6 @@ class CopyTradingService(
|
||||
supportSell = request.supportSell ?: copyTrading.supportSell,
|
||||
minOrderDepth = request.minOrderDepth?.toSafeBigDecimal() ?: copyTrading.minOrderDepth,
|
||||
maxSpread = request.maxSpread?.toSafeBigDecimal() ?: copyTrading.maxSpread,
|
||||
minOrderbookDepth = request.minOrderbookDepth?.toSafeBigDecimal() ?: copyTrading.minOrderbookDepth,
|
||||
minPrice = request.minPrice?.toSafeBigDecimal() ?: copyTrading.minPrice,
|
||||
maxPrice = request.maxPrice?.toSafeBigDecimal() ?: copyTrading.maxPrice,
|
||||
configName = configName,
|
||||
@@ -411,7 +407,6 @@ class CopyTradingService(
|
||||
supportSell = copyTrading.supportSell,
|
||||
minOrderDepth = copyTrading.minOrderDepth?.toPlainString(),
|
||||
maxSpread = copyTrading.maxSpread?.toPlainString(),
|
||||
minOrderbookDepth = copyTrading.minOrderbookDepth?.toPlainString(),
|
||||
minPrice = copyTrading.minPrice?.toPlainString(),
|
||||
maxPrice = copyTrading.maxPrice?.toPlainString(),
|
||||
configName = copyTrading.configName,
|
||||
@@ -441,7 +436,6 @@ class CopyTradingService(
|
||||
val supportSell: Boolean,
|
||||
val minOrderDepth: BigDecimal?,
|
||||
val maxSpread: BigDecimal?,
|
||||
val minOrderbookDepth: BigDecimal?,
|
||||
val minPrice: BigDecimal?,
|
||||
val maxPrice: BigDecimal?
|
||||
)
|
||||
|
||||
+78
@@ -0,0 +1,78 @@
|
||||
package com.wrbug.polymarketbot.service.copytrading.configs
|
||||
|
||||
import com.wrbug.polymarketbot.api.OrderbookResponse
|
||||
|
||||
/**
|
||||
* 过滤结果状态枚举
|
||||
*/
|
||||
enum class FilterStatus {
|
||||
/** 通过 */
|
||||
PASSED,
|
||||
/** 失败:价格区间 */
|
||||
FAILED_PRICE_RANGE,
|
||||
/** 失败:订单簿获取失败 */
|
||||
FAILED_ORDERBOOK_ERROR,
|
||||
/** 失败:订单簿为空 */
|
||||
FAILED_ORDERBOOK_EMPTY,
|
||||
/** 失败:价差过大 */
|
||||
FAILED_SPREAD,
|
||||
/** 失败:订单深度不足 */
|
||||
FAILED_ORDER_DEPTH
|
||||
}
|
||||
|
||||
/**
|
||||
* 过滤结果
|
||||
*/
|
||||
data class FilterResult(
|
||||
/** 过滤状态 */
|
||||
val status: FilterStatus,
|
||||
/** 失败原因(仅在失败时有效) */
|
||||
val reason: String = "",
|
||||
/** 订单簿(仅在需要时返回) */
|
||||
val orderbook: OrderbookResponse? = null
|
||||
) {
|
||||
/** 是否通过 */
|
||||
val isPassed: Boolean
|
||||
get() = status == FilterStatus.PASSED
|
||||
|
||||
companion object {
|
||||
/** 通过 */
|
||||
fun passed(orderbook: OrderbookResponse? = null) = FilterResult(
|
||||
status = FilterStatus.PASSED,
|
||||
orderbook = orderbook
|
||||
)
|
||||
|
||||
/** 价格区间失败 */
|
||||
fun priceRangeFailed(reason: String) = FilterResult(
|
||||
status = FilterStatus.FAILED_PRICE_RANGE,
|
||||
reason = reason
|
||||
)
|
||||
|
||||
/** 订单簿获取失败 */
|
||||
fun orderbookError(reason: String) = FilterResult(
|
||||
status = FilterStatus.FAILED_ORDERBOOK_ERROR,
|
||||
reason = reason
|
||||
)
|
||||
|
||||
/** 订单簿为空 */
|
||||
fun orderbookEmpty() = FilterResult(
|
||||
status = FilterStatus.FAILED_ORDERBOOK_EMPTY,
|
||||
reason = "订单簿为空"
|
||||
)
|
||||
|
||||
/** 价差过大 */
|
||||
fun spreadFailed(reason: String, orderbook: OrderbookResponse) = FilterResult(
|
||||
status = FilterStatus.FAILED_SPREAD,
|
||||
reason = reason,
|
||||
orderbook = orderbook
|
||||
)
|
||||
|
||||
/** 订单深度不足 */
|
||||
fun orderDepthFailed(reason: String, orderbook: OrderbookResponse) = FilterResult(
|
||||
status = FilterStatus.FAILED_ORDER_DEPTH,
|
||||
reason = reason,
|
||||
orderbook = orderbook
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
+60
-28
@@ -11,8 +11,10 @@ import kotlinx.coroutines.*
|
||||
import org.slf4j.LoggerFactory
|
||||
import org.springframework.dao.DataIntegrityViolationException
|
||||
import com.wrbug.polymarketbot.service.copytrading.configs.CopyTradingFilterService
|
||||
import com.wrbug.polymarketbot.service.copytrading.configs.FilterStatus
|
||||
import com.wrbug.polymarketbot.service.copytrading.orders.OrderSigningService
|
||||
import com.wrbug.polymarketbot.service.common.BlockchainService
|
||||
import com.wrbug.polymarketbot.service.common.PolymarketClobService
|
||||
import com.wrbug.polymarketbot.service.system.TelegramNotificationService
|
||||
import com.wrbug.polymarketbot.util.CryptoUtils
|
||||
import org.springframework.stereotype.Service
|
||||
@@ -38,6 +40,7 @@ class CopyOrderTrackingService(
|
||||
private val leaderRepository: LeaderRepository,
|
||||
private val orderSigningService: OrderSigningService,
|
||||
private val blockchainService: BlockchainService,
|
||||
private val clobService: PolymarketClobService,
|
||||
private val retrofitFactory: RetrofitFactory,
|
||||
private val cryptoUtils: CryptoUtils,
|
||||
private val telegramNotificationService: TelegramNotificationService? = null // 可选,避免循环依赖
|
||||
@@ -215,10 +218,12 @@ class CopyOrderTrackingService(
|
||||
|
||||
// 过滤条件检查(在计算订单参数之前)
|
||||
// 传入 Leader 交易价格,用于价格区间检查
|
||||
// 订单簿只请求一次,返回给后续逻辑使用
|
||||
val tradePrice = trade.price.toSafeBigDecimal()
|
||||
val filterCheck = filterService.checkFilters(copyTrading, tokenId, isBuyOrder = true, tradePrice = tradePrice)
|
||||
if (!filterCheck.first) {
|
||||
logger.warn("过滤条件检查失败,跳过创建订单: copyTradingId=${copyTrading.id}, reason=${filterCheck.second}")
|
||||
val filterResult = filterService.checkFilters(copyTrading, tokenId, tradePrice = tradePrice)
|
||||
val orderbook = filterResult.orderbook // 获取订单簿(如果需要)
|
||||
if (!filterResult.isPassed) {
|
||||
logger.warn("过滤条件检查失败,跳过创建订单: copyTradingId=${copyTrading.id}, reason=${filterResult.reason}")
|
||||
|
||||
// 记录被过滤的订单并发送通知(异步,不阻塞)
|
||||
notificationScope.launch {
|
||||
@@ -242,8 +247,8 @@ class CopyOrderTrackingService(
|
||||
val marketTitle = marketInfo?.question ?: trade.market
|
||||
val marketSlug = marketInfo?.slug
|
||||
|
||||
// 从 filterReason 中提取 filterType
|
||||
val filterType = extractFilterType(filterCheck.second)
|
||||
// 从过滤结果中提取 filterType
|
||||
val filterType = extractFilterType(filterResult.status, filterResult.reason)
|
||||
|
||||
// 计算买入数量(用于记录,即使被过滤也记录)
|
||||
val calculatedQuantity = try {
|
||||
@@ -268,7 +273,7 @@ class CopyOrderTrackingService(
|
||||
price = trade.price.toSafeBigDecimal(),
|
||||
size = trade.size.toSafeBigDecimal(),
|
||||
calculatedQuantity = calculatedQuantity,
|
||||
filterReason = filterCheck.second,
|
||||
filterReason = filterResult.reason,
|
||||
filterType = filterType
|
||||
)
|
||||
|
||||
@@ -294,7 +299,7 @@ class CopyOrderTrackingService(
|
||||
outcome = trade.outcome,
|
||||
price = trade.price,
|
||||
size = trade.size,
|
||||
filterReason = filterCheck.second,
|
||||
filterReason = filterResult.reason,
|
||||
filterType = filterType,
|
||||
accountName = account.accountName,
|
||||
walletAddress = account.walletAddress,
|
||||
@@ -736,8 +741,16 @@ class CopyOrderTrackingService(
|
||||
}
|
||||
val tokenId = tokenIdResult.getOrNull() ?: return
|
||||
|
||||
// 6. 计算卖出价格(应用价格容忍度)
|
||||
val sellPrice = calculateAdjustedPrice(leaderSellTrade.price.toSafeBigDecimal(), copyTrading, isBuy = false)
|
||||
// 6. 计算卖出价格(优先使用订单簿 bestBid,失败则使用 Leader 价格,固定按90%计算)
|
||||
val leaderPrice = leaderSellTrade.price.toSafeBigDecimal()
|
||||
val sellPrice = runCatching {
|
||||
clobService.getOrderbookByTokenId(tokenId)
|
||||
.getOrNull()
|
||||
?.let { calculateMarketSellPrice(it) }
|
||||
}
|
||||
.onFailure { e -> logger.warn("获取订单簿或计算 bestBid 失败,使用 Leader 价格: tokenId=$tokenId, error=${e.message}") }
|
||||
.getOrNull()
|
||||
?: calculateFallbackSellPrice(leaderPrice)
|
||||
|
||||
// 7. 解密私钥(在方法开始时解密一次,后续复用)
|
||||
val decryptedPrivateKey = decryptPrivateKey(account)
|
||||
@@ -1127,19 +1140,22 @@ class CopyOrderTrackingService(
|
||||
|
||||
/**
|
||||
* 计算调整后的价格(应用价格容忍度)
|
||||
* 如果价格容忍度为0,使用默认值5%
|
||||
*/
|
||||
private fun calculateAdjustedPrice(
|
||||
originalPrice: BigDecimal,
|
||||
copyTrading: CopyTrading,
|
||||
isBuy: Boolean
|
||||
): BigDecimal {
|
||||
// 如果价格容忍度为0,直接返回原价格
|
||||
if (copyTrading.priceTolerance.eq(BigDecimal.ZERO)) {
|
||||
return originalPrice
|
||||
// 如果价格容忍度为0,使用默认值5%
|
||||
val tolerance = if (copyTrading.priceTolerance.eq(BigDecimal.ZERO)) {
|
||||
BigDecimal("5")
|
||||
} else {
|
||||
copyTrading.priceTolerance
|
||||
}
|
||||
|
||||
// 计算价格调整范围(百分比)
|
||||
val tolerancePercent = copyTrading.priceTolerance.div(100)
|
||||
val tolerancePercent = tolerance.div(100)
|
||||
val adjustment = originalPrice.multi(tolerancePercent)
|
||||
|
||||
return if (isBuy) {
|
||||
@@ -1152,23 +1168,39 @@ class CopyOrderTrackingService(
|
||||
}
|
||||
|
||||
/**
|
||||
* 从过滤原因中提取过滤类型
|
||||
* 计算市价卖出价格(使用订单簿的 bestBid,固定按90%计算)
|
||||
*/
|
||||
private fun extractFilterType(filterReason: String): String {
|
||||
return when {
|
||||
filterReason.contains("价格低于最低限制", ignoreCase = true) || filterReason.contains("价格高于最高限制", ignoreCase = true) -> "PRICE_RANGE"
|
||||
filterReason.contains("订单深度不足", ignoreCase = true) -> "ORDER_DEPTH"
|
||||
filterReason.contains("价差过大", ignoreCase = true) -> "SPREAD"
|
||||
filterReason.contains("订单簿深度不足", ignoreCase = true) -> "ORDERBOOK_DEPTH"
|
||||
filterReason.contains("价格", ignoreCase = true) && filterReason.contains(
|
||||
"合理",
|
||||
ignoreCase = true
|
||||
) -> "PRICE_VALIDITY"
|
||||
private fun calculateMarketSellPrice(
|
||||
orderbook: com.wrbug.polymarketbot.api.OrderbookResponse
|
||||
): BigDecimal {
|
||||
// 获取 bestBid(最高买入价)
|
||||
val bestBid = orderbook.bids
|
||||
.mapNotNull { it.price.toSafeBigDecimal() }
|
||||
.maxOrNull()
|
||||
?: throw IllegalStateException("订单簿 bids 为空,无法获取 bestBid")
|
||||
|
||||
filterReason.contains("市场状态", ignoreCase = true) -> "MARKET_STATUS"
|
||||
filterReason.contains("获取订单簿失败", ignoreCase = true) -> "ORDERBOOK_ERROR"
|
||||
filterReason.contains("订单簿为空", ignoreCase = true) -> "ORDERBOOK_EMPTY"
|
||||
else -> "UNKNOWN"
|
||||
// 卖出:bestBid * 0.9(固定按90%计算,确保能立即成交)
|
||||
return calculateFallbackSellPrice(bestBid)
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算降级卖出价格(固定按90%计算)
|
||||
*/
|
||||
private fun calculateFallbackSellPrice(price: BigDecimal): BigDecimal {
|
||||
return price.multi(BigDecimal("0.9")).coerceAtLeast(BigDecimal("0.01"))
|
||||
}
|
||||
|
||||
/**
|
||||
* 从过滤结果中提取过滤类型
|
||||
*/
|
||||
private fun extractFilterType(status: FilterStatus, reason: String): String {
|
||||
return when (status) {
|
||||
FilterStatus.PASSED -> "PASSED"
|
||||
FilterStatus.FAILED_PRICE_RANGE -> "PRICE_RANGE"
|
||||
FilterStatus.FAILED_ORDERBOOK_ERROR -> "ORDERBOOK_ERROR"
|
||||
FilterStatus.FAILED_ORDERBOOK_EMPTY -> "ORDERBOOK_EMPTY"
|
||||
FilterStatus.FAILED_SPREAD -> "SPREAD"
|
||||
FilterStatus.FAILED_ORDER_DEPTH -> "ORDER_DEPTH"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
-4
@@ -61,7 +61,6 @@ class CopyTradingTemplateService(
|
||||
supportSell = request.supportSell ?: true,
|
||||
minOrderDepth = request.minOrderDepth?.toSafeBigDecimal(),
|
||||
maxSpread = request.maxSpread?.toSafeBigDecimal(),
|
||||
minOrderbookDepth = request.minOrderbookDepth?.toSafeBigDecimal(),
|
||||
minPrice = request.minPrice?.toSafeBigDecimal(),
|
||||
maxPrice = request.maxPrice?.toSafeBigDecimal()
|
||||
)
|
||||
@@ -120,7 +119,6 @@ class CopyTradingTemplateService(
|
||||
supportSell = request.supportSell ?: template.supportSell,
|
||||
minOrderDepth = request.minOrderDepth?.toSafeBigDecimal() ?: template.minOrderDepth,
|
||||
maxSpread = request.maxSpread?.toSafeBigDecimal() ?: template.maxSpread,
|
||||
minOrderbookDepth = request.minOrderbookDepth?.toSafeBigDecimal() ?: template.minOrderbookDepth,
|
||||
minPrice = request.minPrice?.toSafeBigDecimal() ?: template.minPrice,
|
||||
maxPrice = request.maxPrice?.toSafeBigDecimal() ?: template.maxPrice,
|
||||
updatedAt = System.currentTimeMillis()
|
||||
@@ -187,7 +185,6 @@ class CopyTradingTemplateService(
|
||||
supportSell = request.supportSell ?: sourceTemplate.supportSell,
|
||||
minOrderDepth = request.minOrderDepth?.toSafeBigDecimal() ?: sourceTemplate.minOrderDepth,
|
||||
maxSpread = request.maxSpread?.toSafeBigDecimal() ?: sourceTemplate.maxSpread,
|
||||
minOrderbookDepth = request.minOrderbookDepth?.toSafeBigDecimal() ?: sourceTemplate.minOrderbookDepth,
|
||||
minPrice = request.minPrice?.toSafeBigDecimal() ?: sourceTemplate.minPrice,
|
||||
maxPrice = request.maxPrice?.toSafeBigDecimal() ?: sourceTemplate.maxPrice
|
||||
)
|
||||
@@ -261,7 +258,6 @@ class CopyTradingTemplateService(
|
||||
supportSell = template.supportSell,
|
||||
minOrderDepth = template.minOrderDepth?.toPlainString(),
|
||||
maxSpread = template.maxSpread?.toPlainString(),
|
||||
minOrderbookDepth = template.minOrderbookDepth?.toPlainString(),
|
||||
minPrice = template.minPrice?.toPlainString(),
|
||||
maxPrice = template.maxPrice?.toPlainString(),
|
||||
createdAt = template.createdAt,
|
||||
|
||||
@@ -597,10 +597,10 @@
|
||||
"priceToleranceTooltip": "Allowed adjustment range for copy price based on Leader price, used to adjust price within Leader price ± tolerance range to improve fill rate. For example: set to 5%, Leader price is 0.5, then copy price can be in 0.475-0.525 range.",
|
||||
"priceTolerancePlaceholder": "Default 5% (optional)",
|
||||
"minOrderDepth": "Min Order Depth (USDC)",
|
||||
"minOrderDepthTooltip": "Minimum order depth (USDC amount), NULL means this filter is not enabled. Ensures market has sufficient liquidity",
|
||||
"minOrderDepthTooltip": "Check total order amount (bids + asks) in orderbook to ensure sufficient liquidity. Leave empty to disable",
|
||||
"minOrderDepthPlaceholder": "For example: 100 (optional, leave empty to disable)",
|
||||
"maxSpread": "Max Spread (Absolute Price)",
|
||||
"maxSpreadTooltip": "Maximum spread (absolute price), NULL means this filter is not enabled. Avoid copying in markets with excessive spreads",
|
||||
"maxSpreadTooltip": "Maximum spread (absolute price). Avoid copying in markets with excessive spreads. Leave empty to disable",
|
||||
"maxSpreadPlaceholder": "For example: 0.05 (5 cents, optional, leave empty to disable)",
|
||||
"minOrderbookDepth": "Min Orderbook Depth (USDC)",
|
||||
"minOrderbookDepthTooltip": "Minimum orderbook depth (USDC amount), NULL means this filter is not enabled. Check depth of first N levels",
|
||||
@@ -654,10 +654,10 @@
|
||||
"priceToleranceTooltip": "Allowed adjustment range for copy price based on Leader price, used to adjust price within Leader price ± tolerance range to improve fill rate. For example: set to 5%, Leader price is 0.5, then copy price can be in 0.475-0.525 range.",
|
||||
"priceTolerancePlaceholder": "Default 5% (optional)",
|
||||
"minOrderDepth": "Min Order Depth (USDC)",
|
||||
"minOrderDepthTooltip": "Minimum order depth (USDC amount), NULL means this filter is not enabled. Ensures market has sufficient liquidity",
|
||||
"minOrderDepthTooltip": "Check total order amount (bids + asks) in orderbook to ensure sufficient liquidity. Leave empty to disable",
|
||||
"minOrderDepthPlaceholder": "For example: 100 (optional, leave empty to disable)",
|
||||
"maxSpread": "Max Spread (Absolute Price)",
|
||||
"maxSpreadTooltip": "Maximum spread (absolute price), NULL means this filter is not enabled. Avoid copying in markets with excessive spreads",
|
||||
"maxSpreadTooltip": "Maximum spread (absolute price). Avoid copying in markets with excessive spreads. Leave empty to disable",
|
||||
"maxSpreadPlaceholder": "For example: 0.05 (5 cents, optional, leave empty to disable)",
|
||||
"minOrderbookDepth": "Min Orderbook Depth (USDC)",
|
||||
"minOrderbookDepthTooltip": "Minimum orderbook depth (USDC amount), NULL means this filter is not enabled. Check depth of first N levels",
|
||||
@@ -719,10 +719,10 @@
|
||||
"delaySecondsPlaceholder": "Default 0 (copy immediately)",
|
||||
"filterConditions": "Filter Conditions (Optional)",
|
||||
"minOrderDepth": "Min Order Depth (USDC)",
|
||||
"minOrderDepthTooltip": "Minimum order depth (USDC amount), NULL means this filter is not enabled. Ensures market has sufficient liquidity",
|
||||
"minOrderDepthTooltip": "Check total order amount (bids + asks) in orderbook to ensure sufficient liquidity. Leave empty to disable",
|
||||
"minOrderDepthPlaceholder": "For example: 100 (optional, leave empty to disable)",
|
||||
"maxSpread": "Max Spread (Absolute Price)",
|
||||
"maxSpreadTooltip": "Maximum spread (absolute price), NULL means this filter is not enabled. Avoid copying in markets with excessive spreads",
|
||||
"maxSpreadTooltip": "Maximum spread (absolute price). Avoid copying in markets with excessive spreads. Leave empty to disable",
|
||||
"maxSpreadPlaceholder": "For example: 0.05 (5 cents, optional, leave empty to disable)",
|
||||
"minOrderbookDepth": "Min Orderbook Depth (USDC)",
|
||||
"minOrderbookDepthTooltip": "Minimum orderbook depth (USDC amount), NULL means this filter is not enabled. Check depth of first N levels",
|
||||
@@ -794,10 +794,10 @@
|
||||
"delaySecondsPlaceholder": "Default 0 (copy immediately)",
|
||||
"filterConditions": "Filter Conditions (Optional)",
|
||||
"minOrderDepth": "Min Order Depth (USDC)",
|
||||
"minOrderDepthTooltip": "Minimum order depth (USDC amount), NULL means this filter is not enabled",
|
||||
"minOrderDepthTooltip": "Check total order amount (bids + asks) in orderbook to ensure sufficient liquidity. Leave empty to disable",
|
||||
"minOrderDepthPlaceholder": "For example: 100 (optional, leave empty to disable)",
|
||||
"maxSpread": "Max Spread (Absolute Price)",
|
||||
"maxSpreadTooltip": "Maximum spread (absolute price), NULL means this filter is not enabled",
|
||||
"maxSpreadTooltip": "Maximum spread (absolute price). Avoid copying in markets with excessive spreads. Leave empty to disable",
|
||||
"maxSpreadPlaceholder": "For example: 0.05 (5 cents, optional, leave empty to disable)",
|
||||
"minOrderbookDepth": "Min Orderbook Depth (USDC)",
|
||||
"minOrderbookDepthTooltip": "Minimum orderbook depth (USDC amount), NULL means this filter is not enabled",
|
||||
|
||||
@@ -512,10 +512,10 @@
|
||||
"priceToleranceTooltip": "允许跟单价格在 Leader 价格基础上的调整范围,用于在 Leader 价格 ± 容忍度范围内调整价格,提高成交率。例如:设置为 5%,Leader 价格为 0.5,则跟单价格可在 0.475-0.525 范围内。",
|
||||
"priceTolerancePlaceholder": "默认 5%(可选)",
|
||||
"minOrderDepth": "最小订单深度 (USDC)",
|
||||
"minOrderDepthTooltip": "最小订单深度(USDC金额),NULL表示不启用此过滤。确保市场有足够的流动性",
|
||||
"minOrderDepthTooltip": "检查订单簿的总订单金额(买盘+卖盘),确保市场有足够的流动性。不填写则不启用此过滤",
|
||||
"minOrderDepthPlaceholder": "例如:100(可选,不填写表示不启用)",
|
||||
"maxSpread": "最大价差(绝对价格)",
|
||||
"maxSpreadTooltip": "最大价差(绝对价格),NULL表示不启用此过滤。避免在价差过大的市场跟单",
|
||||
"maxSpreadTooltip": "最大价差(绝对价格)。避免在价差过大的市场跟单。不填写则不启用此过滤",
|
||||
"maxSpreadPlaceholder": "例如:0.05(5美分,可选,不填写表示不启用)",
|
||||
"minOrderbookDepth": "最小订单簿深度 (USDC)",
|
||||
"minOrderbookDepthTooltip": "最小订单簿深度(USDC金额),NULL表示不启用此过滤。检查前 N 档的深度",
|
||||
@@ -569,10 +569,10 @@
|
||||
"priceToleranceTooltip": "允许跟单价格在 Leader 价格基础上的调整范围,用于在 Leader 价格 ± 容忍度范围内调整价格,提高成交率。例如:设置为 5%,Leader 价格为 0.5,则跟单价格可在 0.475-0.525 范围内。",
|
||||
"priceTolerancePlaceholder": "默认 5%(可选)",
|
||||
"minOrderDepth": "最小订单深度 (USDC)",
|
||||
"minOrderDepthTooltip": "最小订单深度(USDC金额),NULL表示不启用此过滤。确保市场有足够的流动性",
|
||||
"minOrderDepthTooltip": "检查订单簿的总订单金额(买盘+卖盘),确保市场有足够的流动性。不填写则不启用此过滤",
|
||||
"minOrderDepthPlaceholder": "例如:100(可选,不填写表示不启用)",
|
||||
"maxSpread": "最大价差(绝对价格)",
|
||||
"maxSpreadTooltip": "最大价差(绝对价格),NULL表示不启用此过滤。避免在价差过大的市场跟单",
|
||||
"maxSpreadTooltip": "最大价差(绝对价格)。避免在价差过大的市场跟单。不填写则不启用此过滤",
|
||||
"maxSpreadPlaceholder": "例如:0.05(5美分,可选,不填写表示不启用)",
|
||||
"minOrderbookDepth": "最小订单簿深度 (USDC)",
|
||||
"minOrderbookDepthTooltip": "最小订单簿深度(USDC金额),NULL表示不启用此过滤。检查前 N 档的深度",
|
||||
@@ -646,10 +646,10 @@
|
||||
"delaySecondsPlaceholder": "默认 0(立即跟单)",
|
||||
"filterConditions": "过滤条件(可选)",
|
||||
"minOrderDepth": "最小订单深度 (USDC)",
|
||||
"minOrderDepthTooltip": "最小订单深度(USDC金额),NULL表示不启用此过滤。确保市场有足够的流动性",
|
||||
"minOrderDepthTooltip": "检查订单簿的总订单金额(买盘+卖盘),确保市场有足够的流动性。不填写则不启用此过滤",
|
||||
"minOrderDepthPlaceholder": "例如:100(可选,不填写表示不启用)",
|
||||
"maxSpread": "最大价差(绝对价格)",
|
||||
"maxSpreadTooltip": "最大价差(绝对价格),NULL表示不启用此过滤。避免在价差过大的市场跟单",
|
||||
"maxSpreadTooltip": "最大价差(绝对价格)。避免在价差过大的市场跟单。不填写则不启用此过滤",
|
||||
"maxSpreadPlaceholder": "例如:0.05(5美分,可选,不填写表示不启用)",
|
||||
"minOrderbookDepth": "最小订单簿深度 (USDC)",
|
||||
"minOrderbookDepthTooltip": "最小订单簿深度(USDC金额),NULL表示不启用此过滤。检查前 N 档的深度",
|
||||
@@ -721,10 +721,10 @@
|
||||
"delaySecondsPlaceholder": "默认 0(立即跟单)",
|
||||
"filterConditions": "过滤条件(可选)",
|
||||
"minOrderDepth": "最小订单深度 (USDC)",
|
||||
"minOrderDepthTooltip": "最小订单深度(USDC金额),NULL表示不启用此过滤",
|
||||
"minOrderDepthTooltip": "检查订单簿的总订单金额(买盘+卖盘),确保市场有足够的流动性。不填写则不启用此过滤",
|
||||
"minOrderDepthPlaceholder": "例如:100(可选,不填写表示不启用)",
|
||||
"maxSpread": "最大价差(绝对价格)",
|
||||
"maxSpreadTooltip": "最大价差(绝对价格),NULL表示不启用此过滤",
|
||||
"maxSpreadTooltip": "最大价差(绝对价格)。避免在价差过大的市场跟单。不填写则不启用此过滤",
|
||||
"maxSpreadPlaceholder": "例如:0.05(5美分,可选,不填写表示不启用)",
|
||||
"minOrderbookDepth": "最小订单簿深度 (USDC)",
|
||||
"minOrderbookDepthTooltip": "最小订单簿深度(USDC金额),NULL表示不启用此过滤",
|
||||
|
||||
@@ -597,10 +597,10 @@
|
||||
"priceToleranceTooltip": "允許跟單價格在 Leader 價格基礎上的調整範圍,用於在 Leader 價格 ± 容忍度範圍內調整價格,提高成交率。例如:設置為 5%,Leader 價格為 0.5,則跟單價格可在 0.475-0.525 範圍內。",
|
||||
"priceTolerancePlaceholder": "默認 5%(可選)",
|
||||
"minOrderDepth": "最小訂單深度 (USDC)",
|
||||
"minOrderDepthTooltip": "最小訂單深度(USDC金額),NULL表示不啟用此過濾。確保市場有足夠的流動性",
|
||||
"minOrderDepthTooltip": "檢查訂單簿的總訂單金額(買盤+賣盤),確保市場有足夠的流動性。不填寫則不啟用此過濾",
|
||||
"minOrderDepthPlaceholder": "例如:100(可選,不填寫表示不啟用)",
|
||||
"maxSpread": "最大價差(絕對價格)",
|
||||
"maxSpreadTooltip": "最大價差(絕對價格),NULL表示不啟用此過濾。避免在價差過大的市場跟單",
|
||||
"maxSpreadTooltip": "最大價差(絕對價格)。避免在價差過大的市場跟單。不填寫則不啟用此過濾",
|
||||
"maxSpreadPlaceholder": "例如:0.05(5美分,可選,不填寫表示不啟用)",
|
||||
"minOrderbookDepth": "最小訂單簿深度 (USDC)",
|
||||
"minOrderbookDepthTooltip": "最小訂單簿深度(USDC金額),NULL表示不啟用此過濾。檢查前 N 檔的深度",
|
||||
@@ -654,10 +654,10 @@
|
||||
"priceToleranceTooltip": "允許跟單價格在 Leader 價格基礎上的調整範圍,用於在 Leader 價格 ± 容忍度範圍內調整價格,提高成交率。例如:設置為 5%,Leader 價格為 0.5,則跟單價格可在 0.475-0.525 範圍內。",
|
||||
"priceTolerancePlaceholder": "默認 5%(可選)",
|
||||
"minOrderDepth": "最小訂單深度 (USDC)",
|
||||
"minOrderDepthTooltip": "最小訂單深度(USDC金額),NULL表示不啟用此過濾。確保市場有足夠的流動性",
|
||||
"minOrderDepthTooltip": "檢查訂單簿的總訂單金額(買盤+賣盤),確保市場有足夠的流動性。不填寫則不啟用此過濾",
|
||||
"minOrderDepthPlaceholder": "例如:100(可選,不填寫表示不啟用)",
|
||||
"maxSpread": "最大價差(絕對價格)",
|
||||
"maxSpreadTooltip": "最大價差(絕對價格),NULL表示不啟用此過濾。避免在價差過大的市場跟單",
|
||||
"maxSpreadTooltip": "最大價差(絕對價格)。避免在價差過大的市場跟單。不填寫則不啟用此過濾",
|
||||
"maxSpreadPlaceholder": "例如:0.05(5美分,可選,不填寫表示不啟用)",
|
||||
"minOrderbookDepth": "最小訂單簿深度 (USDC)",
|
||||
"minOrderbookDepthTooltip": "最小訂單簿深度(USDC金額),NULL表示不啟用此過濾。檢查前 N 檔的深度",
|
||||
@@ -719,10 +719,10 @@
|
||||
"delaySecondsPlaceholder": "默認 0(立即跟單)",
|
||||
"filterConditions": "過濾條件(可選)",
|
||||
"minOrderDepth": "最小訂單深度 (USDC)",
|
||||
"minOrderDepthTooltip": "最小訂單深度(USDC金額),NULL表示不啟用此過濾。確保市場有足夠的流動性",
|
||||
"minOrderDepthTooltip": "檢查訂單簿的總訂單金額(買盤+賣盤),確保市場有足夠的流動性。不填寫則不啟用此過濾",
|
||||
"minOrderDepthPlaceholder": "例如:100(可選,不填寫表示不啟用)",
|
||||
"maxSpread": "最大價差(絕對價格)",
|
||||
"maxSpreadTooltip": "最大價差(絕對價格),NULL表示不啟用此過濾。避免在價差過大的市場跟單",
|
||||
"maxSpreadTooltip": "最大價差(絕對價格)。避免在價差過大的市場跟單。不填寫則不啟用此過濾",
|
||||
"maxSpreadPlaceholder": "例如:0.05(5美分,可選,不填寫表示不啟用)",
|
||||
"minOrderbookDepth": "最小訂單簿深度 (USDC)",
|
||||
"minOrderbookDepthTooltip": "最小訂單簿深度(USDC金額),NULL表示不啟用此過濾。檢查前 N 檔的深度",
|
||||
@@ -794,10 +794,10 @@
|
||||
"delaySecondsPlaceholder": "默認 0(立即跟單)",
|
||||
"filterConditions": "過濾條件(可選)",
|
||||
"minOrderDepth": "最小訂單深度 (USDC)",
|
||||
"minOrderDepthTooltip": "最小訂單深度(USDC金額),NULL表示不啟用此過濾",
|
||||
"minOrderDepthTooltip": "檢查訂單簿的總訂單金額(買盤+賣盤),確保市場有足夠的流動性。不填寫則不啟用此過濾",
|
||||
"minOrderDepthPlaceholder": "例如:100(可選,不填寫表示不啟用)",
|
||||
"maxSpread": "最大價差(絕對價格)",
|
||||
"maxSpreadTooltip": "最大價差(絕對價格),NULL表示不啟用此過濾",
|
||||
"maxSpreadTooltip": "最大價差(絕對價格)。避免在價差過大的市場跟單。不填寫則不啟用此過濾",
|
||||
"maxSpreadPlaceholder": "例如:0.05(5美分,可選,不填寫表示不啟用)",
|
||||
"minOrderbookDepth": "最小訂單簿深度 (USDC)",
|
||||
"minOrderbookDepthTooltip": "最小訂單簿深度(USDC金額),NULL表示不啟用此過濾",
|
||||
|
||||
@@ -84,7 +84,6 @@ const CopyTradingAdd: React.FC = () => {
|
||||
supportSell: template.supportSell,
|
||||
minOrderDepth: template.minOrderDepth ? parseFloat(template.minOrderDepth) : undefined,
|
||||
maxSpread: template.maxSpread ? parseFloat(template.maxSpread) : undefined,
|
||||
minOrderbookDepth: template.minOrderbookDepth ? parseFloat(template.minOrderbookDepth) : undefined,
|
||||
minPrice: template.minPrice ? parseFloat(template.minPrice) : undefined,
|
||||
maxPrice: template.maxPrice ? parseFloat(template.maxPrice) : undefined
|
||||
})
|
||||
@@ -133,7 +132,6 @@ const CopyTradingAdd: React.FC = () => {
|
||||
supportSell: values.supportSell !== false,
|
||||
minOrderDepth: values.minOrderDepth?.toString(),
|
||||
maxSpread: values.maxSpread?.toString(),
|
||||
minOrderbookDepth: values.minOrderbookDepth?.toString(),
|
||||
minPrice: values.minPrice?.toString(),
|
||||
maxPrice: values.maxPrice?.toString(),
|
||||
configName: values.configName?.trim(),
|
||||
@@ -412,7 +410,7 @@ const CopyTradingAdd: React.FC = () => {
|
||||
<Form.Item
|
||||
label={t('copyTradingAdd.minOrderDepth') || '最小订单深度 (USDC)'}
|
||||
name="minOrderDepth"
|
||||
tooltip={t('copyTradingAdd.minOrderDepthTooltip') || '最小订单深度(USDC金额),NULL表示不启用此过滤。确保市场有足够的流动性'}
|
||||
tooltip={t('copyTradingAdd.minOrderDepthTooltip') || '检查订单簿的总订单金额(买盘+卖盘),确保市场有足够的流动性。不填写则不启用此过滤'}
|
||||
>
|
||||
<InputNumber
|
||||
min={0}
|
||||
@@ -426,7 +424,7 @@ const CopyTradingAdd: React.FC = () => {
|
||||
<Form.Item
|
||||
label={t('copyTradingAdd.maxSpread') || '最大价差(绝对价格)'}
|
||||
name="maxSpread"
|
||||
tooltip={t('copyTradingAdd.maxSpreadTooltip') || '最大价差(绝对价格),NULL表示不启用此过滤。避免在价差过大的市场跟单'}
|
||||
tooltip={t('copyTradingAdd.maxSpreadTooltip') || '最大价差(绝对价格)。避免在价差过大的市场跟单。不填写则不启用此过滤'}
|
||||
>
|
||||
<InputNumber
|
||||
min={0}
|
||||
@@ -437,20 +435,6 @@ const CopyTradingAdd: React.FC = () => {
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
label={t('copyTradingAdd.minOrderbookDepth') || '最小订单簿深度 (USDC)'}
|
||||
name="minOrderbookDepth"
|
||||
tooltip={t('copyTradingAdd.minOrderbookDepthTooltip') || '最小订单簿深度(USDC金额),NULL表示不启用此过滤。检查前 N 档的深度'}
|
||||
>
|
||||
<InputNumber
|
||||
min={0}
|
||||
step={0.0001}
|
||||
precision={4}
|
||||
style={{ width: '100%' }}
|
||||
placeholder={t('copyTradingAdd.minOrderbookDepthPlaceholder') || '例如:50(可选,不填写表示不启用)'}
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<Divider>{t('copyTradingAdd.priceRangeFilter') || '价格区间过滤'}</Divider>
|
||||
|
||||
<Form.Item
|
||||
|
||||
@@ -56,7 +56,6 @@ const CopyTradingEdit: React.FC = () => {
|
||||
supportSell: found.supportSell,
|
||||
minOrderDepth: found.minOrderDepth ? parseFloat(found.minOrderDepth) : undefined,
|
||||
maxSpread: found.maxSpread ? parseFloat(found.maxSpread) : undefined,
|
||||
minOrderbookDepth: found.minOrderbookDepth ? parseFloat(found.minOrderbookDepth) : undefined,
|
||||
minPrice: found.minPrice ? parseFloat(found.minPrice) : undefined,
|
||||
maxPrice: found.maxPrice ? parseFloat(found.maxPrice) : undefined,
|
||||
configName: found.configName || '',
|
||||
@@ -122,7 +121,6 @@ const CopyTradingEdit: React.FC = () => {
|
||||
supportSell: values.supportSell,
|
||||
minOrderDepth: values.minOrderDepth?.toString(),
|
||||
maxSpread: values.maxSpread?.toString(),
|
||||
minOrderbookDepth: values.minOrderbookDepth?.toString(),
|
||||
minPrice: values.minPrice?.toString(),
|
||||
maxPrice: values.maxPrice?.toString(),
|
||||
configName: values.configName?.trim() || undefined,
|
||||
@@ -381,7 +379,7 @@ const CopyTradingEdit: React.FC = () => {
|
||||
<Form.Item
|
||||
label={t('copyTradingEdit.minOrderDepth') || '最小订单深度 (USDC)'}
|
||||
name="minOrderDepth"
|
||||
tooltip={t('copyTradingEdit.minOrderDepthTooltip') || '最小订单深度(USDC金额),NULL表示不启用此过滤'}
|
||||
tooltip={t('copyTradingEdit.minOrderDepthTooltip') || '检查订单簿的总订单金额(买盘+卖盘),确保市场有足够的流动性。不填写则不启用此过滤'}
|
||||
>
|
||||
<InputNumber
|
||||
min={0}
|
||||
@@ -395,7 +393,7 @@ const CopyTradingEdit: React.FC = () => {
|
||||
<Form.Item
|
||||
label={t('copyTradingEdit.maxSpread') || '最大价差(绝对价格)'}
|
||||
name="maxSpread"
|
||||
tooltip={t('copyTradingEdit.maxSpreadTooltip') || '最大价差(绝对价格),NULL表示不启用此过滤'}
|
||||
tooltip={t('copyTradingEdit.maxSpreadTooltip') || '最大价差(绝对价格)。避免在价差过大的市场跟单。不填写则不启用此过滤'}
|
||||
>
|
||||
<InputNumber
|
||||
min={0}
|
||||
@@ -406,20 +404,6 @@ const CopyTradingEdit: React.FC = () => {
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
label={t('copyTradingEdit.minOrderbookDepth') || '最小订单簿深度 (USDC)'}
|
||||
name="minOrderbookDepth"
|
||||
tooltip={t('copyTradingEdit.minOrderbookDepthTooltip') || '最小订单簿深度(USDC金额),NULL表示不启用此过滤'}
|
||||
>
|
||||
<InputNumber
|
||||
min={0}
|
||||
step={0.0001}
|
||||
precision={4}
|
||||
style={{ width: '100%' }}
|
||||
placeholder={t('copyTradingEdit.minOrderbookDepthPlaceholder') || '例如:50(可选,不填写表示不启用)'}
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<Divider>{t('copyTradingEdit.priceRangeFilter') || '价格区间过滤'}</Divider>
|
||||
|
||||
<Form.Item
|
||||
|
||||
@@ -54,7 +54,6 @@ const TemplateAdd: React.FC = () => {
|
||||
supportSell: values.supportSell !== false,
|
||||
minOrderDepth: values.minOrderDepth?.toString(),
|
||||
maxSpread: values.maxSpread?.toString(),
|
||||
minOrderbookDepth: values.minOrderbookDepth?.toString(),
|
||||
minPrice: values.minPrice?.toString(),
|
||||
maxPrice: values.maxPrice?.toString()
|
||||
})
|
||||
@@ -258,7 +257,7 @@ const TemplateAdd: React.FC = () => {
|
||||
<Form.Item
|
||||
label={t('templateAdd.minOrderDepth') || '最小订单深度 (USDC)'}
|
||||
name="minOrderDepth"
|
||||
tooltip={t('templateAdd.minOrderDepthTooltip') || '最小订单深度(USDC金额),NULL表示不启用此过滤。确保市场有足够的流动性'}
|
||||
tooltip={t('templateAdd.minOrderDepthTooltip') || '检查订单簿的总订单金额(买盘+卖盘),确保市场有足够的流动性。不填写则不启用此过滤'}
|
||||
>
|
||||
<InputNumber
|
||||
min={0}
|
||||
@@ -272,7 +271,7 @@ const TemplateAdd: React.FC = () => {
|
||||
<Form.Item
|
||||
label={t('templateAdd.maxSpread') || '最大价差(绝对价格)'}
|
||||
name="maxSpread"
|
||||
tooltip={t('templateAdd.maxSpreadTooltip') || '最大价差(绝对价格),NULL表示不启用此过滤。避免在价差过大的市场跟单'}
|
||||
tooltip={t('templateAdd.maxSpreadTooltip') || '最大价差(绝对价格)。避免在价差过大的市场跟单。不填写则不启用此过滤'}
|
||||
>
|
||||
<InputNumber
|
||||
min={0}
|
||||
@@ -283,20 +282,6 @@ const TemplateAdd: React.FC = () => {
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
label={t('templateAdd.minOrderbookDepth') || '最小订单簿深度 (USDC)'}
|
||||
name="minOrderbookDepth"
|
||||
tooltip={t('templateAdd.minOrderbookDepthTooltip') || '最小订单簿深度(USDC金额),NULL表示不启用此过滤。检查前 N 档的深度'}
|
||||
>
|
||||
<InputNumber
|
||||
min={0}
|
||||
step={0.0001}
|
||||
precision={4}
|
||||
style={{ width: '100%' }}
|
||||
placeholder={t('templateAdd.minOrderbookDepthPlaceholder') || '例如:50(可选,不填写表示不启用)'}
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<Divider>{t('templateAdd.priceRangeFilter') || '价格区间过滤'}</Divider>
|
||||
|
||||
<Form.Item
|
||||
|
||||
@@ -40,7 +40,6 @@ const TemplateEdit: React.FC = () => {
|
||||
priceTolerance: parseFloat(template.priceTolerance),
|
||||
minOrderDepth: template.minOrderDepth ? parseFloat(template.minOrderDepth) : undefined,
|
||||
maxSpread: template.maxSpread ? parseFloat(template.maxSpread) : undefined,
|
||||
minOrderbookDepth: template.minOrderbookDepth ? parseFloat(template.minOrderbookDepth) : undefined,
|
||||
minPrice: template.minPrice ? parseFloat(template.minPrice) : undefined,
|
||||
maxPrice: template.maxPrice ? parseFloat(template.maxPrice) : undefined
|
||||
})
|
||||
@@ -99,7 +98,6 @@ const TemplateEdit: React.FC = () => {
|
||||
supportSell: values.supportSell,
|
||||
minOrderDepth: values.minOrderDepth?.toString(),
|
||||
maxSpread: values.maxSpread?.toString(),
|
||||
minOrderbookDepth: values.minOrderbookDepth?.toString(),
|
||||
minPrice: values.minPrice?.toString(),
|
||||
maxPrice: values.maxPrice?.toString()
|
||||
})
|
||||
@@ -295,7 +293,7 @@ const TemplateEdit: React.FC = () => {
|
||||
<Form.Item
|
||||
label={t('templateEdit.minOrderDepth') || '最小订单深度 (USDC)'}
|
||||
name="minOrderDepth"
|
||||
tooltip={t('templateEdit.minOrderDepthTooltip') || '最小订单深度(USDC金额),NULL表示不启用此过滤。确保市场有足够的流动性'}
|
||||
tooltip={t('templateEdit.minOrderDepthTooltip') || '检查订单簿的总订单金额(买盘+卖盘),确保市场有足够的流动性。不填写则不启用此过滤'}
|
||||
>
|
||||
<InputNumber
|
||||
min={0}
|
||||
@@ -309,7 +307,7 @@ const TemplateEdit: React.FC = () => {
|
||||
<Form.Item
|
||||
label={t('templateEdit.maxSpread') || '最大价差(绝对价格)'}
|
||||
name="maxSpread"
|
||||
tooltip={t('templateEdit.maxSpreadTooltip') || '最大价差(绝对价格),NULL表示不启用此过滤。避免在价差过大的市场跟单'}
|
||||
tooltip={t('templateEdit.maxSpreadTooltip') || '最大价差(绝对价格)。避免在价差过大的市场跟单。不填写则不启用此过滤'}
|
||||
>
|
||||
<InputNumber
|
||||
min={0}
|
||||
@@ -320,20 +318,6 @@ const TemplateEdit: React.FC = () => {
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
label={t('templateEdit.minOrderbookDepth') || '最小订单簿深度 (USDC)'}
|
||||
name="minOrderbookDepth"
|
||||
tooltip={t('templateEdit.minOrderbookDepthTooltip') || '最小订单簿深度(USDC金额),NULL表示不启用此过滤。检查前 N 档的深度'}
|
||||
>
|
||||
<InputNumber
|
||||
min={0}
|
||||
step={0.0001}
|
||||
precision={4}
|
||||
style={{ width: '100%' }}
|
||||
placeholder={t('templateEdit.minOrderbookDepthPlaceholder') || '例如:50(可选,不填写表示不启用)'}
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<Divider>{t('templateEdit.priceRangeFilter') || '价格区间过滤'}</Divider>
|
||||
|
||||
<Form.Item
|
||||
|
||||
@@ -74,7 +74,6 @@ const TemplateList: React.FC = () => {
|
||||
supportSell: template.supportSell,
|
||||
minOrderDepth: template.minOrderDepth ? parseFloat(template.minOrderDepth) : undefined,
|
||||
maxSpread: template.maxSpread ? parseFloat(template.maxSpread) : undefined,
|
||||
minOrderbookDepth: template.minOrderbookDepth ? parseFloat(template.minOrderbookDepth) : undefined,
|
||||
minPrice: template.minPrice ? parseFloat(template.minPrice) : undefined,
|
||||
maxPrice: template.maxPrice ? parseFloat(template.maxPrice) : undefined
|
||||
})
|
||||
@@ -122,7 +121,6 @@ const TemplateList: React.FC = () => {
|
||||
supportSell: values.supportSell !== false,
|
||||
minOrderDepth: values.minOrderDepth?.toString(),
|
||||
maxSpread: values.maxSpread?.toString(),
|
||||
minOrderbookDepth: values.minOrderbookDepth?.toString(),
|
||||
minPrice: values.minPrice?.toString(),
|
||||
maxPrice: values.maxPrice?.toString()
|
||||
})
|
||||
@@ -613,7 +611,7 @@ const TemplateList: React.FC = () => {
|
||||
<Form.Item
|
||||
label="最小订单深度 (USDC)"
|
||||
name="minOrderDepth"
|
||||
tooltip="最小订单深度(USDC金额),NULL表示不启用此过滤。确保市场有足够的流动性"
|
||||
tooltip="检查订单簿的总订单金额(买盘+卖盘),确保市场有足够的流动性。不填写则不启用此过滤"
|
||||
>
|
||||
<InputNumber
|
||||
min={0}
|
||||
@@ -627,7 +625,7 @@ const TemplateList: React.FC = () => {
|
||||
<Form.Item
|
||||
label="最大价差(绝对价格)"
|
||||
name="maxSpread"
|
||||
tooltip="最大价差(绝对价格),NULL表示不启用此过滤。避免在价差过大的市场跟单"
|
||||
tooltip="最大价差(绝对价格)。避免在价差过大的市场跟单。不填写则不启用此过滤"
|
||||
>
|
||||
<InputNumber
|
||||
min={0}
|
||||
@@ -638,20 +636,6 @@ const TemplateList: React.FC = () => {
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
label="最小订单簿深度 (USDC)"
|
||||
name="minOrderbookDepth"
|
||||
tooltip="最小订单簿深度(USDC金额),NULL表示不启用此过滤。检查前 N 档的深度"
|
||||
>
|
||||
<InputNumber
|
||||
min={0}
|
||||
step={0.0001}
|
||||
precision={4}
|
||||
style={{ width: '100%' }}
|
||||
placeholder="例如:50(可选,不填写表示不启用)"
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<Divider>价格区间过滤</Divider>
|
||||
|
||||
<Form.Item
|
||||
|
||||
@@ -112,7 +112,6 @@ export interface CopyTradingTemplate {
|
||||
// 过滤条件
|
||||
minOrderDepth?: string
|
||||
maxSpread?: string
|
||||
minOrderbookDepth?: string
|
||||
minPrice?: string // 最低价格(可选),NULL表示不限制最低价
|
||||
maxPrice?: string // 最高价格(可选),NULL表示不限制最高价
|
||||
createdAt: number
|
||||
@@ -204,7 +203,6 @@ export interface CopyTrading {
|
||||
// 过滤条件
|
||||
minOrderDepth?: string
|
||||
maxSpread?: string
|
||||
minOrderbookDepth?: string
|
||||
minPrice?: string // 最低价格(可选),NULL表示不限制最低价
|
||||
maxPrice?: string // 最高价格(可选),NULL表示不限制最高价
|
||||
// 新增配置字段
|
||||
@@ -248,7 +246,6 @@ export interface CopyTradingCreateRequest {
|
||||
// 过滤条件
|
||||
minOrderDepth?: string
|
||||
maxSpread?: string
|
||||
minOrderbookDepth?: string
|
||||
minPrice?: string // 最低价格(可选),NULL表示不限制最低价
|
||||
maxPrice?: string // 最高价格(可选),NULL表示不限制最高价
|
||||
// 新增配置字段
|
||||
@@ -280,7 +277,6 @@ export interface CopyTradingUpdateRequest {
|
||||
// 过滤条件
|
||||
minOrderDepth?: string
|
||||
maxSpread?: string
|
||||
minOrderbookDepth?: string
|
||||
minPrice?: string // 最低价格(可选),NULL表示不限制最低价
|
||||
maxPrice?: string // 最高价格(可选),NULL表示不限制最高价
|
||||
// 新增配置字段
|
||||
|
||||
Reference in New Issue
Block a user