feat: 添加价格区间过滤功能并优化UI
- 添加价格区间过滤功能(minPrice/maxPrice) - 数据库迁移:添加价格区间字段到 copy_trading 和 copy_trading_templates 表 - 后端:实体类、DTO、服务层支持价格区间配置和过滤 - 前端:模板和配置页面添加价格区间配置UI - 过滤逻辑:在订单创建前检查价格是否在配置区间内 - 优化价格区间文案,使其更易懂 - 明确说明是 Leader 交易价格 - 详细说明三种配置方式(区间、只填最低价、只填最高价) - 补充多语言翻译 - 为 zh-CN、zh-TW、en 添加价格区间相关翻译 - 添加价格区间过滤类型翻译 - 添加通用翻译(prev、next、items、total) - 修复复制模板功能 - 补充缺失的过滤条件字段(minOrderDepth、maxSpread、minOrderbookDepth、minPrice、maxPrice) - 在复制模板表单中添加所有过滤条件配置项 - 已过滤订单列表优化 - 添加移动端卡片样式布局 - 在筛选下拉菜单中添加价格区间过滤类型选项 - 订单页面返回按钮优化 - 买入订单、卖出订单、匹配关系页面的返回按钮改为返回上一页(navigate(-1)) - 使用多语言支持返回按钮文本
This commit is contained in:
@@ -32,7 +32,9 @@ data class CopyTradingCreateRequest(
|
|||||||
// 过滤条件
|
// 过滤条件
|
||||||
val minOrderDepth: String? = null, // 最小订单深度(USDC金额),NULL表示不启用
|
val minOrderDepth: String? = null, // 最小订单深度(USDC金额),NULL表示不启用
|
||||||
val maxSpread: String? = null, // 最大价差(绝对价格),NULL表示不启用
|
val maxSpread: String? = null, // 最大价差(绝对价格),NULL表示不启用
|
||||||
val minOrderbookDepth: String? = null // 最小订单簿深度(USDC金额),NULL表示不启用
|
val minOrderbookDepth: String? = null, // 最小订单簿深度(USDC金额),NULL表示不启用
|
||||||
|
val minPrice: String? = null, // 最低价格(可选),NULL表示不限制最低价
|
||||||
|
val maxPrice: String? = null // 最高价格(可选),NULL表示不限制最高价
|
||||||
)
|
)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -59,7 +61,9 @@ data class CopyTradingUpdateRequest(
|
|||||||
// 过滤条件
|
// 过滤条件
|
||||||
val minOrderDepth: String? = null,
|
val minOrderDepth: String? = null,
|
||||||
val maxSpread: String? = null,
|
val maxSpread: String? = null,
|
||||||
val minOrderbookDepth: String? = null
|
val minOrderbookDepth: String? = null,
|
||||||
|
val minPrice: String? = null, // 最低价格(可选),NULL表示不限制最低价
|
||||||
|
val maxPrice: String? = null // 最高价格(可选),NULL表示不限制最高价
|
||||||
)
|
)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -124,6 +128,8 @@ data class CopyTradingDto(
|
|||||||
val minOrderDepth: String?,
|
val minOrderDepth: String?,
|
||||||
val maxSpread: String?,
|
val maxSpread: String?,
|
||||||
val minOrderbookDepth: String?,
|
val minOrderbookDepth: String?,
|
||||||
|
val minPrice: String?, // 最低价格(可选),NULL表示不限制最低价
|
||||||
|
val maxPrice: String?, // 最高价格(可选),NULL表示不限制最高价
|
||||||
val createdAt: Long,
|
val createdAt: Long,
|
||||||
val updatedAt: Long
|
val updatedAt: Long
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -22,7 +22,9 @@ data class TemplateCreateRequest(
|
|||||||
// 过滤条件
|
// 过滤条件
|
||||||
val minOrderDepth: String? = null, // 最小订单深度(USDC金额),NULL表示不启用
|
val minOrderDepth: String? = null, // 最小订单深度(USDC金额),NULL表示不启用
|
||||||
val maxSpread: String? = null, // 最大价差(绝对价格),NULL表示不启用
|
val maxSpread: String? = null, // 最大价差(绝对价格),NULL表示不启用
|
||||||
val minOrderbookDepth: String? = null // 最小订单簿深度(USDC金额),NULL表示不启用
|
val minOrderbookDepth: String? = null, // 最小订单簿深度(USDC金额),NULL表示不启用
|
||||||
|
val minPrice: String? = null, // 最低价格(可选),NULL表示不限制最低价
|
||||||
|
val maxPrice: String? = null // 最高价格(可选),NULL表示不限制最高价
|
||||||
)
|
)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -48,7 +50,9 @@ data class TemplateUpdateRequest(
|
|||||||
// 过滤条件
|
// 过滤条件
|
||||||
val minOrderDepth: String? = null, // 最小订单深度(USDC金额),NULL表示不启用
|
val minOrderDepth: String? = null, // 最小订单深度(USDC金额),NULL表示不启用
|
||||||
val maxSpread: String? = null, // 最大价差(绝对价格),NULL表示不启用
|
val maxSpread: String? = null, // 最大价差(绝对价格),NULL表示不启用
|
||||||
val minOrderbookDepth: String? = null // 最小订单簿深度(USDC金额),NULL表示不启用
|
val minOrderbookDepth: String? = null, // 最小订单簿深度(USDC金额),NULL表示不启用
|
||||||
|
val minPrice: String? = null, // 最低价格(可选),NULL表示不限制最低价
|
||||||
|
val maxPrice: String? = null // 最高价格(可选),NULL表示不限制最高价
|
||||||
)
|
)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -81,7 +85,9 @@ data class TemplateCopyRequest(
|
|||||||
// 过滤条件
|
// 过滤条件
|
||||||
val minOrderDepth: String? = null, // 最小订单深度(USDC金额),NULL表示不启用
|
val minOrderDepth: String? = null, // 最小订单深度(USDC金额),NULL表示不启用
|
||||||
val maxSpread: String? = null, // 最大价差(绝对价格),NULL表示不启用
|
val maxSpread: String? = null, // 最大价差(绝对价格),NULL表示不启用
|
||||||
val minOrderbookDepth: String? = null // 最小订单簿深度(USDC金额),NULL表示不启用
|
val minOrderbookDepth: String? = null, // 最小订单簿深度(USDC金额),NULL表示不启用
|
||||||
|
val minPrice: String? = null, // 最低价格(可选),NULL表示不限制最低价
|
||||||
|
val maxPrice: String? = null // 最高价格(可选),NULL表示不限制最高价
|
||||||
)
|
)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -115,6 +121,8 @@ data class TemplateDto(
|
|||||||
val minOrderDepth: String?,
|
val minOrderDepth: String?,
|
||||||
val maxSpread: String?,
|
val maxSpread: String?,
|
||||||
val minOrderbookDepth: String?,
|
val minOrderbookDepth: String?,
|
||||||
|
val minPrice: String?, // 最低价格(可选),NULL表示不限制最低价
|
||||||
|
val maxPrice: String?, // 最高价格(可选),NULL表示不限制最高价
|
||||||
val createdAt: Long,
|
val createdAt: Long,
|
||||||
val updatedAt: Long
|
val updatedAt: Long
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -81,6 +81,12 @@ data class CopyTrading(
|
|||||||
@Column(name = "min_orderbook_depth", precision = 20, scale = 8)
|
@Column(name = "min_orderbook_depth", precision = 20, scale = 8)
|
||||||
val minOrderbookDepth: BigDecimal? = null, // 最小订单簿深度(USDC金额),NULL表示不启用
|
val minOrderbookDepth: BigDecimal? = null, // 最小订单簿深度(USDC金额),NULL表示不启用
|
||||||
|
|
||||||
|
@Column(name = "min_price", precision = 20, scale = 8)
|
||||||
|
val minPrice: BigDecimal? = null, // 最低价格(可选),NULL表示不限制最低价
|
||||||
|
|
||||||
|
@Column(name = "max_price", precision = 20, scale = 8)
|
||||||
|
val maxPrice: BigDecimal? = null, // 最高价格(可选),NULL表示不限制最高价
|
||||||
|
|
||||||
@Column(name = "created_at", nullable = false)
|
@Column(name = "created_at", nullable = false)
|
||||||
val createdAt: Long = System.currentTimeMillis(),
|
val createdAt: Long = System.currentTimeMillis(),
|
||||||
|
|
||||||
|
|||||||
@@ -69,6 +69,12 @@ data class CopyTradingTemplate(
|
|||||||
@Column(name = "min_orderbook_depth", precision = 20, scale = 8)
|
@Column(name = "min_orderbook_depth", precision = 20, scale = 8)
|
||||||
val minOrderbookDepth: BigDecimal? = null, // 最小订单簿深度(USDC金额),NULL表示不启用
|
val minOrderbookDepth: BigDecimal? = null, // 最小订单簿深度(USDC金额),NULL表示不启用
|
||||||
|
|
||||||
|
@Column(name = "min_price", precision = 20, scale = 8)
|
||||||
|
val minPrice: BigDecimal? = null, // 最低价格(可选),NULL表示不限制最低价
|
||||||
|
|
||||||
|
@Column(name = "max_price", precision = 20, scale = 8)
|
||||||
|
val maxPrice: BigDecimal? = null, // 最高价格(可选),NULL表示不限制最高价
|
||||||
|
|
||||||
@Column(name = "created_at", nullable = false)
|
@Column(name = "created_at", nullable = false)
|
||||||
val createdAt: Long = System.currentTimeMillis(),
|
val createdAt: Long = System.currentTimeMillis(),
|
||||||
|
|
||||||
|
|||||||
@@ -208,7 +208,9 @@ class CopyOrderTrackingService(
|
|||||||
val tokenId = tokenIdResult.getOrNull() ?: continue
|
val tokenId = tokenIdResult.getOrNull() ?: continue
|
||||||
|
|
||||||
// 过滤条件检查(在计算订单参数之前)
|
// 过滤条件检查(在计算订单参数之前)
|
||||||
val filterCheck = filterService.checkFilters(copyTrading, tokenId, isBuyOrder = true)
|
// 传入 Leader 交易价格,用于价格区间检查
|
||||||
|
val tradePrice = trade.price.toSafeBigDecimal()
|
||||||
|
val filterCheck = filterService.checkFilters(copyTrading, tokenId, isBuyOrder = true, tradePrice = tradePrice)
|
||||||
if (!filterCheck.first) {
|
if (!filterCheck.first) {
|
||||||
logger.warn("过滤条件检查失败,跳过创建订单: copyTradingId=${copyTrading.id}, reason=${filterCheck.second}")
|
logger.warn("过滤条件检查失败,跳过创建订单: copyTradingId=${copyTrading.id}, reason=${filterCheck.second}")
|
||||||
|
|
||||||
@@ -1139,6 +1141,7 @@ class CopyOrderTrackingService(
|
|||||||
*/
|
*/
|
||||||
private fun extractFilterType(filterReason: String): String {
|
private fun extractFilterType(filterReason: String): String {
|
||||||
return when {
|
return when {
|
||||||
|
filterReason.contains("价格低于最低限制", ignoreCase = true) || filterReason.contains("价格高于最高限制", ignoreCase = true) -> "PRICE_RANGE"
|
||||||
filterReason.contains("订单深度不足", ignoreCase = true) -> "ORDER_DEPTH"
|
filterReason.contains("订单深度不足", ignoreCase = true) -> "ORDER_DEPTH"
|
||||||
filterReason.contains("价差过大", ignoreCase = true) -> "SPREAD"
|
filterReason.contains("价差过大", ignoreCase = true) -> "SPREAD"
|
||||||
filterReason.contains("订单簿深度不足", ignoreCase = true) -> "ORDERBOOK_DEPTH"
|
filterReason.contains("订单簿深度不足", ignoreCase = true) -> "ORDERBOOK_DEPTH"
|
||||||
|
|||||||
+43
-6
@@ -30,12 +30,21 @@ class CopyTradingFilterService(
|
|||||||
suspend fun checkFilters(
|
suspend fun checkFilters(
|
||||||
copyTrading: CopyTrading,
|
copyTrading: CopyTrading,
|
||||||
tokenId: String,
|
tokenId: String,
|
||||||
isBuyOrder: Boolean
|
isBuyOrder: Boolean,
|
||||||
|
tradePrice: BigDecimal? = null // Leader 交易价格,用于价格区间检查
|
||||||
): Pair<Boolean, String> {
|
): Pair<Boolean, String> {
|
||||||
// 1. 价格合理性检查(基础检查,无需配置)
|
// 1. 价格区间检查(如果配置了价格区间)
|
||||||
|
if (tradePrice != null) {
|
||||||
|
val priceRangeCheck = checkPriceRange(copyTrading, tradePrice)
|
||||||
|
if (!priceRangeCheck.first) {
|
||||||
|
return priceRangeCheck
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 价格合理性检查(基础检查,无需配置)
|
||||||
// 这个检查在获取订单簿时进行,如果价格不在 0.01-0.99 范围内,订单簿获取会失败
|
// 这个检查在获取订单簿时进行,如果价格不在 0.01-0.99 范围内,订单簿获取会失败
|
||||||
|
|
||||||
// 2. 获取订单簿
|
// 3. 获取订单簿
|
||||||
val orderbookResult = clobService.getOrderbookByTokenId(tokenId)
|
val orderbookResult = clobService.getOrderbookByTokenId(tokenId)
|
||||||
if (!orderbookResult.isSuccess) {
|
if (!orderbookResult.isSuccess) {
|
||||||
val error = orderbookResult.exceptionOrNull()
|
val error = orderbookResult.exceptionOrNull()
|
||||||
@@ -47,19 +56,19 @@ class CopyTradingFilterService(
|
|||||||
return Pair(false, "订单簿为空")
|
return Pair(false, "订单簿为空")
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. 买一卖一价差过滤
|
// 4. 买一卖一价差过滤
|
||||||
val spreadCheck = checkSpread(copyTrading, orderbook)
|
val spreadCheck = checkSpread(copyTrading, orderbook)
|
||||||
if (!spreadCheck.first) {
|
if (!spreadCheck.first) {
|
||||||
return spreadCheck
|
return spreadCheck
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4. 订单深度过滤
|
// 5. 订单深度过滤
|
||||||
val depthCheck = checkOrderDepth(copyTrading, orderbook, isBuyOrder)
|
val depthCheck = checkOrderDepth(copyTrading, orderbook, isBuyOrder)
|
||||||
if (!depthCheck.first) {
|
if (!depthCheck.first) {
|
||||||
return depthCheck
|
return depthCheck
|
||||||
}
|
}
|
||||||
|
|
||||||
// 5. 最小订单簿深度过滤(可选)
|
// 6. 最小订单簿深度过滤(可选)
|
||||||
val orderbookDepthCheck = checkOrderbookDepth(copyTrading, orderbook, isBuyOrder)
|
val orderbookDepthCheck = checkOrderbookDepth(copyTrading, orderbook, isBuyOrder)
|
||||||
if (!orderbookDepthCheck.first) {
|
if (!orderbookDepthCheck.first) {
|
||||||
return orderbookDepthCheck
|
return orderbookDepthCheck
|
||||||
@@ -68,6 +77,34 @@ class CopyTradingFilterService(
|
|||||||
return Pair(true, "")
|
return Pair(true, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查价格区间
|
||||||
|
* @param copyTrading 跟单配置
|
||||||
|
* @param tradePrice Leader 交易价格
|
||||||
|
* @return Pair<是否通过, 失败原因>
|
||||||
|
*/
|
||||||
|
private fun checkPriceRange(
|
||||||
|
copyTrading: CopyTrading,
|
||||||
|
tradePrice: BigDecimal
|
||||||
|
): Pair<Boolean, String> {
|
||||||
|
// 如果未配置价格区间,直接通过
|
||||||
|
if (copyTrading.minPrice == null && copyTrading.maxPrice == null) {
|
||||||
|
return Pair(true, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查最低价格
|
||||||
|
if (copyTrading.minPrice != null && tradePrice.lt(copyTrading.minPrice)) {
|
||||||
|
return Pair(false, "价格低于最低限制: $tradePrice < ${copyTrading.minPrice}")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查最高价格
|
||||||
|
if (copyTrading.maxPrice != null && tradePrice.gt(copyTrading.maxPrice)) {
|
||||||
|
return Pair(false, "价格高于最高限制: $tradePrice > ${copyTrading.maxPrice}")
|
||||||
|
}
|
||||||
|
|
||||||
|
return Pair(true, "")
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 检查买一卖一价差
|
* 检查买一卖一价差
|
||||||
* bestBid: 买盘中的最高价格(最大值)
|
* bestBid: 买盘中的最高价格(最大值)
|
||||||
|
|||||||
@@ -76,7 +76,9 @@ class CopyTradingService(
|
|||||||
supportSell = request.supportSell ?: template.supportSell,
|
supportSell = request.supportSell ?: template.supportSell,
|
||||||
minOrderDepth = request.minOrderDepth?.toSafeBigDecimal() ?: template.minOrderDepth,
|
minOrderDepth = request.minOrderDepth?.toSafeBigDecimal() ?: template.minOrderDepth,
|
||||||
maxSpread = request.maxSpread?.toSafeBigDecimal() ?: template.maxSpread,
|
maxSpread = request.maxSpread?.toSafeBigDecimal() ?: template.maxSpread,
|
||||||
minOrderbookDepth = request.minOrderbookDepth?.toSafeBigDecimal() ?: template.minOrderbookDepth
|
minOrderbookDepth = request.minOrderbookDepth?.toSafeBigDecimal() ?: template.minOrderbookDepth,
|
||||||
|
minPrice = request.minPrice?.toSafeBigDecimal() ?: template.minPrice,
|
||||||
|
maxPrice = request.maxPrice?.toSafeBigDecimal() ?: template.maxPrice
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
// 手动输入(所有字段必须提供)
|
// 手动输入(所有字段必须提供)
|
||||||
@@ -101,7 +103,9 @@ class CopyTradingService(
|
|||||||
supportSell = request.supportSell ?: true,
|
supportSell = request.supportSell ?: true,
|
||||||
minOrderDepth = request.minOrderDepth?.toSafeBigDecimal(),
|
minOrderDepth = request.minOrderDepth?.toSafeBigDecimal(),
|
||||||
maxSpread = request.maxSpread?.toSafeBigDecimal(),
|
maxSpread = request.maxSpread?.toSafeBigDecimal(),
|
||||||
minOrderbookDepth = request.minOrderbookDepth?.toSafeBigDecimal()
|
minOrderbookDepth = request.minOrderbookDepth?.toSafeBigDecimal(),
|
||||||
|
minPrice = request.minPrice?.toSafeBigDecimal(),
|
||||||
|
maxPrice = request.maxPrice?.toSafeBigDecimal()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -126,7 +130,9 @@ class CopyTradingService(
|
|||||||
supportSell = config.supportSell,
|
supportSell = config.supportSell,
|
||||||
minOrderDepth = config.minOrderDepth,
|
minOrderDepth = config.minOrderDepth,
|
||||||
maxSpread = config.maxSpread,
|
maxSpread = config.maxSpread,
|
||||||
minOrderbookDepth = config.minOrderbookDepth
|
minOrderbookDepth = config.minOrderbookDepth,
|
||||||
|
minPrice = config.minPrice,
|
||||||
|
maxPrice = config.maxPrice
|
||||||
)
|
)
|
||||||
|
|
||||||
val saved = copyTradingRepository.save(copyTrading)
|
val saved = copyTradingRepository.save(copyTrading)
|
||||||
@@ -178,6 +184,8 @@ class CopyTradingService(
|
|||||||
minOrderDepth = request.minOrderDepth?.toSafeBigDecimal() ?: copyTrading.minOrderDepth,
|
minOrderDepth = request.minOrderDepth?.toSafeBigDecimal() ?: copyTrading.minOrderDepth,
|
||||||
maxSpread = request.maxSpread?.toSafeBigDecimal() ?: copyTrading.maxSpread,
|
maxSpread = request.maxSpread?.toSafeBigDecimal() ?: copyTrading.maxSpread,
|
||||||
minOrderbookDepth = request.minOrderbookDepth?.toSafeBigDecimal() ?: copyTrading.minOrderbookDepth,
|
minOrderbookDepth = request.minOrderbookDepth?.toSafeBigDecimal() ?: copyTrading.minOrderbookDepth,
|
||||||
|
minPrice = request.minPrice?.toSafeBigDecimal() ?: copyTrading.minPrice,
|
||||||
|
maxPrice = request.maxPrice?.toSafeBigDecimal() ?: copyTrading.maxPrice,
|
||||||
updatedAt = System.currentTimeMillis()
|
updatedAt = System.currentTimeMillis()
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -380,6 +388,8 @@ class CopyTradingService(
|
|||||||
minOrderDepth = copyTrading.minOrderDepth?.toPlainString(),
|
minOrderDepth = copyTrading.minOrderDepth?.toPlainString(),
|
||||||
maxSpread = copyTrading.maxSpread?.toPlainString(),
|
maxSpread = copyTrading.maxSpread?.toPlainString(),
|
||||||
minOrderbookDepth = copyTrading.minOrderbookDepth?.toPlainString(),
|
minOrderbookDepth = copyTrading.minOrderbookDepth?.toPlainString(),
|
||||||
|
minPrice = copyTrading.minPrice?.toPlainString(),
|
||||||
|
maxPrice = copyTrading.maxPrice?.toPlainString(),
|
||||||
createdAt = copyTrading.createdAt,
|
createdAt = copyTrading.createdAt,
|
||||||
updatedAt = copyTrading.updatedAt
|
updatedAt = copyTrading.updatedAt
|
||||||
)
|
)
|
||||||
@@ -405,6 +415,8 @@ class CopyTradingService(
|
|||||||
val supportSell: Boolean,
|
val supportSell: Boolean,
|
||||||
val minOrderDepth: BigDecimal?,
|
val minOrderDepth: BigDecimal?,
|
||||||
val maxSpread: BigDecimal?,
|
val maxSpread: BigDecimal?,
|
||||||
val minOrderbookDepth: BigDecimal?
|
val minOrderbookDepth: BigDecimal?,
|
||||||
|
val minPrice: BigDecimal?,
|
||||||
|
val maxPrice: BigDecimal?
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
+74
-15
@@ -29,7 +29,8 @@ class CopyTradingStatisticsService(
|
|||||||
private val sellMatchDetailRepository: SellMatchDetailRepository,
|
private val sellMatchDetailRepository: SellMatchDetailRepository,
|
||||||
private val accountRepository: AccountRepository,
|
private val accountRepository: AccountRepository,
|
||||||
private val leaderRepository: LeaderRepository,
|
private val leaderRepository: LeaderRepository,
|
||||||
private val accountService: AccountService
|
private val accountService: AccountService,
|
||||||
|
private val blockchainService: com.wrbug.polymarketbot.service.BlockchainService
|
||||||
) {
|
) {
|
||||||
|
|
||||||
private val logger = LoggerFactory.getLogger(CopyTradingStatisticsService::class.java)
|
private val logger = LoggerFactory.getLogger(CopyTradingStatisticsService::class.java)
|
||||||
@@ -59,11 +60,14 @@ class CopyTradingStatisticsService(
|
|||||||
// 6. 计算统计信息
|
// 6. 计算统计信息
|
||||||
val statistics = calculateStatistics(buyOrders, sellRecords, matchDetails)
|
val statistics = calculateStatistics(buyOrders, sellRecords, matchDetails)
|
||||||
|
|
||||||
// 7. 获取当前市场价格(用于计算未实现盈亏)
|
// 7. 获取链上实际持仓(用于准确计算未实现盈亏,考虑手动卖出的情况)
|
||||||
|
val actualPositions = getActualPositions(account)
|
||||||
|
|
||||||
|
// 8. 获取当前市场价格(用于计算未实现盈亏)
|
||||||
val currentPrice = getCurrentMarketPrice(buyOrders)
|
val currentPrice = getCurrentMarketPrice(buyOrders)
|
||||||
|
|
||||||
// 8. 计算未实现盈亏
|
// 9. 计算未实现盈亏(使用链上实际持仓,而不是 remainingQuantity)
|
||||||
val unrealizedPnl = calculateUnrealizedPnl(buyOrders, currentPrice)
|
val unrealizedPnl = calculateUnrealizedPnl(buyOrders, currentPrice, actualPositions)
|
||||||
|
|
||||||
// 9. 构建响应
|
// 9. 构建响应
|
||||||
val response = CopyTradingStatisticsResponse(
|
val response = CopyTradingStatisticsResponse(
|
||||||
@@ -314,49 +318,104 @@ class CopyTradingStatisticsService(
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取当前市场价格
|
* 获取当前市场价格
|
||||||
|
* 按 (marketId, outcomeIndex) 组合获取价格,支持多元市场
|
||||||
*/
|
*/
|
||||||
private suspend fun getCurrentMarketPrice(buyOrders: List<CopyOrderTracking>): Map<String, String> {
|
private suspend fun getCurrentMarketPrice(buyOrders: List<CopyOrderTracking>): Map<String, String> {
|
||||||
val prices = mutableMapOf<String, String>()
|
val prices = mutableMapOf<String, String>()
|
||||||
|
|
||||||
// 获取所有不同的市场ID
|
// 获取所有不同的 (marketId, outcomeIndex) 组合
|
||||||
val marketIds = buyOrders.map { it.marketId }.distinct()
|
val marketOutcomePairs = buyOrders
|
||||||
|
.filter { it.outcomeIndex != null }
|
||||||
|
.map { Pair(it.marketId, it.outcomeIndex!!) }
|
||||||
|
.distinct()
|
||||||
|
|
||||||
for (marketId in marketIds) {
|
for ((marketId, outcomeIndex) in marketOutcomePairs) {
|
||||||
try {
|
try {
|
||||||
val result = accountService.getMarketPrice(marketId)
|
// 传递 outcomeIndex 参数,确保获取对应 outcome 的价格
|
||||||
|
val result = accountService.getMarketPrice(marketId, outcomeIndex)
|
||||||
result.onSuccess { response ->
|
result.onSuccess { response ->
|
||||||
// 使用中间价,如果没有则使用最后价格
|
// 使用中间价,如果没有则使用最后价格
|
||||||
val price = response.midpoint ?: response.lastPrice
|
val price = response.midpoint ?: response.lastPrice
|
||||||
if (price != null) {
|
if (price != null) {
|
||||||
prices[marketId] = price
|
// 使用 "marketId:outcomeIndex" 作为 key
|
||||||
|
val key = "$marketId:$outcomeIndex"
|
||||||
|
prices[key] = price
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
logger.warn("获取市场价格失败: marketId=$marketId", e)
|
logger.warn("获取市场价格失败: marketId=$marketId, outcomeIndex=$outcomeIndex", e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return prices
|
return prices
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取链上实际持仓
|
||||||
|
* 按 (marketId, outcomeIndex) 组合返回实际持仓数量
|
||||||
|
*/
|
||||||
|
private suspend fun getActualPositions(account: com.wrbug.polymarketbot.entity.Account?): Map<String, BigDecimal> {
|
||||||
|
val positions = mutableMapOf<String, BigDecimal>()
|
||||||
|
|
||||||
|
if (account == null || account.proxyAddress.isBlank()) {
|
||||||
|
return positions
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
val positionsResult = blockchainService.getPositions(account.proxyAddress)
|
||||||
|
if (positionsResult.isSuccess) {
|
||||||
|
val positionList = positionsResult.getOrNull() ?: emptyList()
|
||||||
|
for (pos in positionList) {
|
||||||
|
// 只处理有 conditionId 和 outcomeIndex 的仓位
|
||||||
|
if (pos.conditionId != null && pos.outcomeIndex != null && pos.size != null) {
|
||||||
|
val key = "${pos.conditionId}:${pos.outcomeIndex}"
|
||||||
|
val size = pos.size.toSafeBigDecimal()
|
||||||
|
// 如果 size > 0,表示有持仓;如果 size < 0,表示做空(取绝对值)
|
||||||
|
positions[key] = size.abs()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
logger.warn("获取链上持仓失败: accountId=${account.id}, error=${e.message}", e)
|
||||||
|
}
|
||||||
|
|
||||||
|
return positions
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 计算未实现盈亏
|
* 计算未实现盈亏
|
||||||
|
* 使用链上实际持仓数量,而不是 remainingQuantity(考虑手动卖出的情况)
|
||||||
*/
|
*/
|
||||||
private fun calculateUnrealizedPnl(
|
private fun calculateUnrealizedPnl(
|
||||||
buyOrders: List<CopyOrderTracking>,
|
buyOrders: List<CopyOrderTracking>,
|
||||||
currentPrices: Map<String, String>
|
currentPrices: Map<String, String>,
|
||||||
|
actualPositions: Map<String, BigDecimal>
|
||||||
): String {
|
): String {
|
||||||
var totalUnrealizedPnl = BigDecimal.ZERO
|
var totalUnrealizedPnl = BigDecimal.ZERO
|
||||||
|
|
||||||
for (order in buyOrders) {
|
for (order in buyOrders) {
|
||||||
val remainingQty = order.remainingQuantity.toSafeBigDecimal()
|
// 如果没有 outcomeIndex,跳过(无法确定价格和持仓)
|
||||||
if (remainingQty.lte(BigDecimal.ZERO)) continue
|
if (order.outcomeIndex == null) {
|
||||||
|
logger.warn("订单缺少 outcomeIndex,跳过未实现盈亏计算: orderId=${order.buyOrderId}, marketId=${order.marketId}")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
val currentPrice = currentPrices[order.marketId]?.toSafeBigDecimal()
|
// 使用 "marketId:outcomeIndex" 作为 key
|
||||||
|
val key = "${order.marketId}:${order.outcomeIndex}"
|
||||||
|
|
||||||
|
// 获取链上实际持仓数量(如果存在),否则使用 remainingQuantity
|
||||||
|
val actualQty = actualPositions[key] ?: order.remainingQuantity.toSafeBigDecimal()
|
||||||
|
|
||||||
|
// 如果实际持仓 <= 0,说明已全部卖出(包括手动卖出),跳过未实现盈亏计算
|
||||||
|
if (actualQty.lte(BigDecimal.ZERO)) continue
|
||||||
|
|
||||||
|
// 获取当前市场价格
|
||||||
|
val currentPrice = currentPrices[key]?.toSafeBigDecimal()
|
||||||
?: continue // 如果没有当前价格,跳过
|
?: continue // 如果没有当前价格,跳过
|
||||||
|
|
||||||
val buyPrice = order.price.toSafeBigDecimal()
|
val buyPrice = order.price.toSafeBigDecimal()
|
||||||
val unrealizedPnl = currentPrice.subtract(buyPrice).multi(remainingQty)
|
// 使用实际持仓数量计算未实现盈亏
|
||||||
|
val unrealizedPnl = currentPrice.subtract(buyPrice).multi(actualQty)
|
||||||
totalUnrealizedPnl = totalUnrealizedPnl.add(unrealizedPnl)
|
totalUnrealizedPnl = totalUnrealizedPnl.add(unrealizedPnl)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+10
-2
@@ -61,7 +61,9 @@ class CopyTradingTemplateService(
|
|||||||
supportSell = request.supportSell ?: true,
|
supportSell = request.supportSell ?: true,
|
||||||
minOrderDepth = request.minOrderDepth?.toSafeBigDecimal(),
|
minOrderDepth = request.minOrderDepth?.toSafeBigDecimal(),
|
||||||
maxSpread = request.maxSpread?.toSafeBigDecimal(),
|
maxSpread = request.maxSpread?.toSafeBigDecimal(),
|
||||||
minOrderbookDepth = request.minOrderbookDepth?.toSafeBigDecimal()
|
minOrderbookDepth = request.minOrderbookDepth?.toSafeBigDecimal(),
|
||||||
|
minPrice = request.minPrice?.toSafeBigDecimal(),
|
||||||
|
maxPrice = request.maxPrice?.toSafeBigDecimal()
|
||||||
)
|
)
|
||||||
|
|
||||||
val saved = templateRepository.save(template)
|
val saved = templateRepository.save(template)
|
||||||
@@ -119,6 +121,8 @@ class CopyTradingTemplateService(
|
|||||||
minOrderDepth = request.minOrderDepth?.toSafeBigDecimal() ?: template.minOrderDepth,
|
minOrderDepth = request.minOrderDepth?.toSafeBigDecimal() ?: template.minOrderDepth,
|
||||||
maxSpread = request.maxSpread?.toSafeBigDecimal() ?: template.maxSpread,
|
maxSpread = request.maxSpread?.toSafeBigDecimal() ?: template.maxSpread,
|
||||||
minOrderbookDepth = request.minOrderbookDepth?.toSafeBigDecimal() ?: template.minOrderbookDepth,
|
minOrderbookDepth = request.minOrderbookDepth?.toSafeBigDecimal() ?: template.minOrderbookDepth,
|
||||||
|
minPrice = request.minPrice?.toSafeBigDecimal() ?: template.minPrice,
|
||||||
|
maxPrice = request.maxPrice?.toSafeBigDecimal() ?: template.maxPrice,
|
||||||
updatedAt = System.currentTimeMillis()
|
updatedAt = System.currentTimeMillis()
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -183,7 +187,9 @@ class CopyTradingTemplateService(
|
|||||||
supportSell = request.supportSell ?: sourceTemplate.supportSell,
|
supportSell = request.supportSell ?: sourceTemplate.supportSell,
|
||||||
minOrderDepth = request.minOrderDepth?.toSafeBigDecimal() ?: sourceTemplate.minOrderDepth,
|
minOrderDepth = request.minOrderDepth?.toSafeBigDecimal() ?: sourceTemplate.minOrderDepth,
|
||||||
maxSpread = request.maxSpread?.toSafeBigDecimal() ?: sourceTemplate.maxSpread,
|
maxSpread = request.maxSpread?.toSafeBigDecimal() ?: sourceTemplate.maxSpread,
|
||||||
minOrderbookDepth = request.minOrderbookDepth?.toSafeBigDecimal() ?: sourceTemplate.minOrderbookDepth
|
minOrderbookDepth = request.minOrderbookDepth?.toSafeBigDecimal() ?: sourceTemplate.minOrderbookDepth,
|
||||||
|
minPrice = request.minPrice?.toSafeBigDecimal() ?: sourceTemplate.minPrice,
|
||||||
|
maxPrice = request.maxPrice?.toSafeBigDecimal() ?: sourceTemplate.maxPrice
|
||||||
)
|
)
|
||||||
|
|
||||||
val saved = templateRepository.save(newTemplate)
|
val saved = templateRepository.save(newTemplate)
|
||||||
@@ -256,6 +262,8 @@ class CopyTradingTemplateService(
|
|||||||
minOrderDepth = template.minOrderDepth?.toPlainString(),
|
minOrderDepth = template.minOrderDepth?.toPlainString(),
|
||||||
maxSpread = template.maxSpread?.toPlainString(),
|
maxSpread = template.maxSpread?.toPlainString(),
|
||||||
minOrderbookDepth = template.minOrderbookDepth?.toPlainString(),
|
minOrderbookDepth = template.minOrderbookDepth?.toPlainString(),
|
||||||
|
minPrice = template.minPrice?.toPlainString(),
|
||||||
|
maxPrice = template.maxPrice?.toPlainString(),
|
||||||
createdAt = template.createdAt,
|
createdAt = template.createdAt,
|
||||||
updatedAt = template.updatedAt
|
updatedAt = template.updatedAt
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
-- ============================================
|
||||||
|
-- V6: 添加价格区间过滤字段
|
||||||
|
-- 用于配置价格区间,仅在指定价格区间内的订单才会下单
|
||||||
|
-- ============================================
|
||||||
|
|
||||||
|
-- 添加价格区间字段到跟单配置表
|
||||||
|
ALTER TABLE copy_trading
|
||||||
|
ADD COLUMN min_price DECIMAL(20, 8) NULL COMMENT '最低价格(可选),NULL表示不限制最低价',
|
||||||
|
ADD COLUMN max_price DECIMAL(20, 8) NULL COMMENT '最高价格(可选),NULL表示不限制最高价';
|
||||||
|
|
||||||
|
-- 添加价格区间字段到跟单模板表
|
||||||
|
ALTER TABLE copy_trading_templates
|
||||||
|
ADD COLUMN min_price DECIMAL(20, 8) NULL COMMENT '最低价格(可选),NULL表示不限制最低价',
|
||||||
|
ADD COLUMN max_price DECIMAL(20, 8) NULL COMMENT '最高价格(可选),NULL表示不限制最高价';
|
||||||
|
|
||||||
@@ -84,6 +84,11 @@ const Layout: React.FC<LayoutProps> = ({ children }) => {
|
|||||||
icon: <AppstoreOutlined />,
|
icon: <AppstoreOutlined />,
|
||||||
label: t('menu.copyTrading'),
|
label: t('menu.copyTrading'),
|
||||||
children: [
|
children: [
|
||||||
|
{
|
||||||
|
key: '/copy-trading',
|
||||||
|
icon: <LinkOutlined />,
|
||||||
|
label: t('menu.copyTradingConfig')
|
||||||
|
},
|
||||||
{
|
{
|
||||||
key: '/leaders',
|
key: '/leaders',
|
||||||
icon: <UserOutlined />,
|
icon: <UserOutlined />,
|
||||||
@@ -93,11 +98,6 @@ const Layout: React.FC<LayoutProps> = ({ children }) => {
|
|||||||
key: '/templates',
|
key: '/templates',
|
||||||
icon: <FileTextOutlined />,
|
icon: <FileTextOutlined />,
|
||||||
label: t('menu.templates')
|
label: t('menu.templates')
|
||||||
},
|
|
||||||
{
|
|
||||||
key: '/copy-trading',
|
|
||||||
icon: <LinkOutlined />,
|
|
||||||
label: t('menu.copyTradingConfig')
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -26,7 +26,11 @@
|
|||||||
"disabled": "Disabled",
|
"disabled": "Disabled",
|
||||||
"noData": "No Data",
|
"noData": "No Data",
|
||||||
"saveConfig": "Save Config",
|
"saveConfig": "Save Config",
|
||||||
"refreshConfig": "Refresh Config"
|
"refreshConfig": "Refresh Config",
|
||||||
|
"total": "Total",
|
||||||
|
"items": "items",
|
||||||
|
"prev": "Previous",
|
||||||
|
"next": "Next"
|
||||||
},
|
},
|
||||||
"account": {
|
"account": {
|
||||||
"title": "Account Management",
|
"title": "Account Management",
|
||||||
@@ -478,6 +482,11 @@
|
|||||||
"minOrderbookDepth": "Min Orderbook Depth (USDC)",
|
"minOrderbookDepth": "Min Orderbook Depth (USDC)",
|
||||||
"minOrderbookDepthTooltip": "Minimum orderbook depth (USDC amount), NULL means this filter is not enabled. Check depth of first N levels",
|
"minOrderbookDepthTooltip": "Minimum orderbook depth (USDC amount), NULL means this filter is not enabled. Check depth of first N levels",
|
||||||
"minOrderbookDepthPlaceholder": "For example: 50 (optional, leave empty to disable)",
|
"minOrderbookDepthPlaceholder": "For example: 50 (optional, leave empty to disable)",
|
||||||
|
"priceRangeFilter": "Price Range Filter",
|
||||||
|
"priceRange": "Price Range",
|
||||||
|
"priceRangeTooltip": "Only copy orders where Leader's trade price is within the specified range. Leave empty to disable. Examples: Fill 0.11 and 0.89 means only copy orders with price between 0.11 and 0.89; Fill only max price 0.89 means only copy orders with price below 0.89; Fill only min price 0.11 means only copy orders with price above 0.11.",
|
||||||
|
"minPricePlaceholder": "Min Price (leave empty for no limit)",
|
||||||
|
"maxPricePlaceholder": "Max Price (leave empty for no limit)",
|
||||||
"supportSell": "Support Sell",
|
"supportSell": "Support Sell",
|
||||||
"supportSellTooltip": "Whether to copy Leader's sell orders. Enabled: copy both Leader's buy and sell orders; Disabled: only copy Leader's buy orders, ignore sell orders.",
|
"supportSellTooltip": "Whether to copy Leader's sell orders. Enabled: copy both Leader's buy and sell orders; Disabled: only copy Leader's buy orders, ignore sell orders.",
|
||||||
"create": "Create Template",
|
"create": "Create Template",
|
||||||
@@ -530,6 +539,11 @@
|
|||||||
"minOrderbookDepth": "Min Orderbook Depth (USDC)",
|
"minOrderbookDepth": "Min Orderbook Depth (USDC)",
|
||||||
"minOrderbookDepthTooltip": "Minimum orderbook depth (USDC amount), NULL means this filter is not enabled. Check depth of first N levels",
|
"minOrderbookDepthTooltip": "Minimum orderbook depth (USDC amount), NULL means this filter is not enabled. Check depth of first N levels",
|
||||||
"minOrderbookDepthPlaceholder": "For example: 50 (optional, leave empty to disable)",
|
"minOrderbookDepthPlaceholder": "For example: 50 (optional, leave empty to disable)",
|
||||||
|
"priceRangeFilter": "Price Range Filter",
|
||||||
|
"priceRange": "Price Range",
|
||||||
|
"priceRangeTooltip": "Only copy orders where Leader's trade price is within the specified range. Leave empty to disable. Examples: Fill 0.11 and 0.89 means only copy orders with price between 0.11 and 0.89; Fill only max price 0.89 means only copy orders with price below 0.89; Fill only min price 0.11 means only copy orders with price above 0.11.",
|
||||||
|
"minPricePlaceholder": "Min Price (leave empty for no limit)",
|
||||||
|
"maxPricePlaceholder": "Max Price (leave empty for no limit)",
|
||||||
"supportSell": "Support Sell",
|
"supportSell": "Support Sell",
|
||||||
"supportSellTooltip": "Whether to copy Leader's sell orders. Enabled: copy both Leader's buy and sell orders; Disabled: only copy Leader's buy orders, ignore sell orders.",
|
"supportSellTooltip": "Whether to copy Leader's sell orders. Enabled: copy both Leader's buy and sell orders; Disabled: only copy Leader's buy orders, ignore sell orders.",
|
||||||
"invalidNumber": "Please enter a valid number"
|
"invalidNumber": "Please enter a valid number"
|
||||||
@@ -590,6 +604,11 @@
|
|||||||
"minOrderbookDepth": "Min Orderbook Depth (USDC)",
|
"minOrderbookDepth": "Min Orderbook Depth (USDC)",
|
||||||
"minOrderbookDepthTooltip": "Minimum orderbook depth (USDC amount), NULL means this filter is not enabled. Check depth of first N levels",
|
"minOrderbookDepthTooltip": "Minimum orderbook depth (USDC amount), NULL means this filter is not enabled. Check depth of first N levels",
|
||||||
"minOrderbookDepthPlaceholder": "For example: 50 (optional, leave empty to disable)",
|
"minOrderbookDepthPlaceholder": "For example: 50 (optional, leave empty to disable)",
|
||||||
|
"priceRangeFilter": "Price Range Filter",
|
||||||
|
"priceRange": "Price Range",
|
||||||
|
"priceRangeTooltip": "Only copy orders where Leader's trade price is within the specified range. Leave empty to disable. Examples: Fill 0.11 and 0.89 means only copy orders with price between 0.11 and 0.89; Fill only max price 0.89 means only copy orders with price below 0.89; Fill only min price 0.11 means only copy orders with price above 0.11.",
|
||||||
|
"minPricePlaceholder": "Min Price (leave empty for no limit)",
|
||||||
|
"maxPricePlaceholder": "Max Price (leave empty for no limit)",
|
||||||
"supportSell": "Support Sell",
|
"supportSell": "Support Sell",
|
||||||
"supportSellTooltip": "Whether to copy Leader's sell orders",
|
"supportSellTooltip": "Whether to copy Leader's sell orders",
|
||||||
"create": "Create Copy Trading Config",
|
"create": "Create Copy Trading Config",
|
||||||
@@ -648,6 +667,11 @@
|
|||||||
"minOrderbookDepth": "Min Orderbook Depth (USDC)",
|
"minOrderbookDepth": "Min Orderbook Depth (USDC)",
|
||||||
"minOrderbookDepthTooltip": "Minimum orderbook depth (USDC amount), NULL means this filter is not enabled",
|
"minOrderbookDepthTooltip": "Minimum orderbook depth (USDC amount), NULL means this filter is not enabled",
|
||||||
"minOrderbookDepthPlaceholder": "For example: 50 (optional, leave empty to disable)",
|
"minOrderbookDepthPlaceholder": "For example: 50 (optional, leave empty to disable)",
|
||||||
|
"priceRangeFilter": "Price Range Filter",
|
||||||
|
"priceRange": "Price Range",
|
||||||
|
"priceRangeTooltip": "Only copy orders where Leader's trade price is within the specified range. Leave empty to disable. Examples: Fill 0.11 and 0.89 means only copy orders with price between 0.11 and 0.89; Fill only max price 0.89 means only copy orders with price below 0.89; Fill only min price 0.11 means only copy orders with price above 0.11.",
|
||||||
|
"minPricePlaceholder": "Min Price (leave empty for no limit)",
|
||||||
|
"maxPricePlaceholder": "Max Price (leave empty for no limit)",
|
||||||
"supportSell": "Support Sell",
|
"supportSell": "Support Sell",
|
||||||
"supportSellTooltip": "Whether to copy Leader's sell orders",
|
"supportSellTooltip": "Whether to copy Leader's sell orders",
|
||||||
"save": "Save",
|
"save": "Save",
|
||||||
@@ -677,8 +701,10 @@
|
|||||||
"marketStatus": "Market Not Tradable",
|
"marketStatus": "Market Not Tradable",
|
||||||
"orderbookError": "Orderbook Fetch Failed",
|
"orderbookError": "Orderbook Fetch Failed",
|
||||||
"orderbookEmpty": "Orderbook Empty",
|
"orderbookEmpty": "Orderbook Empty",
|
||||||
|
"priceRange": "Price Range Mismatch",
|
||||||
"unknown": "Unknown Reason"
|
"unknown": "Unknown Reason"
|
||||||
}
|
},
|
||||||
|
"noData": "No filtered orders"
|
||||||
},
|
},
|
||||||
"copyTradingList": {
|
"copyTradingList": {
|
||||||
"title": "Copy Trading Config Management",
|
"title": "Copy Trading Config Management",
|
||||||
|
|||||||
@@ -23,7 +23,11 @@
|
|||||||
"loading": "加载中",
|
"loading": "加载中",
|
||||||
"noData": "暂无数据",
|
"noData": "暂无数据",
|
||||||
"saveConfig": "保存配置",
|
"saveConfig": "保存配置",
|
||||||
"refreshConfig": "刷新配置"
|
"refreshConfig": "刷新配置",
|
||||||
|
"total": "共",
|
||||||
|
"items": "条",
|
||||||
|
"prev": "上一页",
|
||||||
|
"next": "下一页"
|
||||||
},
|
},
|
||||||
"login": {
|
"login": {
|
||||||
"title": "登录",
|
"title": "登录",
|
||||||
@@ -390,6 +394,11 @@
|
|||||||
"minOrderbookDepth": "最小订单簿深度 (USDC)",
|
"minOrderbookDepth": "最小订单簿深度 (USDC)",
|
||||||
"minOrderbookDepthTooltip": "最小订单簿深度(USDC金额),NULL表示不启用此过滤。检查前 N 档的深度",
|
"minOrderbookDepthTooltip": "最小订单簿深度(USDC金额),NULL表示不启用此过滤。检查前 N 档的深度",
|
||||||
"minOrderbookDepthPlaceholder": "例如:50(可选,不填写表示不启用)",
|
"minOrderbookDepthPlaceholder": "例如:50(可选,不填写表示不启用)",
|
||||||
|
"priceRangeFilter": "价格区间过滤",
|
||||||
|
"priceRange": "价格区间",
|
||||||
|
"priceRangeTooltip": "仅跟单 Leader 交易价格在指定区间内的订单。不填写表示不限制。示例:填写 0.11 和 0.89 表示仅跟单价格在 0.11 到 0.89 之间的订单;只填写最高价 0.89 表示仅跟单价格在 0.89 以下的订单;只填写最低价 0.11 表示仅跟单价格在 0.11 以上的订单。",
|
||||||
|
"minPricePlaceholder": "最低价(留空不限制)",
|
||||||
|
"maxPricePlaceholder": "最高价(留空不限制)",
|
||||||
"supportSell": "跟单卖出",
|
"supportSell": "跟单卖出",
|
||||||
"supportSellTooltip": "是否跟单 Leader 的卖出订单。开启:跟单 Leader 的买入和卖出订单;关闭:只跟单 Leader 的买入订单,忽略卖出订单。",
|
"supportSellTooltip": "是否跟单 Leader 的卖出订单。开启:跟单 Leader 的买入和卖出订单;关闭:只跟单 Leader 的买入订单,忽略卖出订单。",
|
||||||
"create": "创建模板",
|
"create": "创建模板",
|
||||||
@@ -442,6 +451,11 @@
|
|||||||
"minOrderbookDepth": "最小订单簿深度 (USDC)",
|
"minOrderbookDepth": "最小订单簿深度 (USDC)",
|
||||||
"minOrderbookDepthTooltip": "最小订单簿深度(USDC金额),NULL表示不启用此过滤。检查前 N 档的深度",
|
"minOrderbookDepthTooltip": "最小订单簿深度(USDC金额),NULL表示不启用此过滤。检查前 N 档的深度",
|
||||||
"minOrderbookDepthPlaceholder": "例如:50(可选,不填写表示不启用)",
|
"minOrderbookDepthPlaceholder": "例如:50(可选,不填写表示不启用)",
|
||||||
|
"priceRangeFilter": "价格区间过滤",
|
||||||
|
"priceRange": "价格区间",
|
||||||
|
"priceRangeTooltip": "仅跟单 Leader 交易价格在指定区间内的订单。不填写表示不限制。示例:填写 0.11 和 0.89 表示仅跟单价格在 0.11 到 0.89 之间的订单;只填写最高价 0.89 表示仅跟单价格在 0.89 以下的订单;只填写最低价 0.11 表示仅跟单价格在 0.11 以上的订单。",
|
||||||
|
"minPricePlaceholder": "最低价(留空不限制)",
|
||||||
|
"maxPricePlaceholder": "最高价(留空不限制)",
|
||||||
"supportSell": "跟单卖出",
|
"supportSell": "跟单卖出",
|
||||||
"supportSellTooltip": "是否跟单 Leader 的卖出订单。开启:跟单 Leader 的买入和卖出订单;关闭:只跟单 Leader 的买入订单,忽略卖出订单。",
|
"supportSellTooltip": "是否跟单 Leader 的卖出订单。开启:跟单 Leader 的买入和卖出订单;关闭:只跟单 Leader 的买入订单,忽略卖出订单。",
|
||||||
"invalidNumber": "请输入有效的数字"
|
"invalidNumber": "请输入有效的数字"
|
||||||
@@ -502,6 +516,11 @@
|
|||||||
"minOrderbookDepth": "最小订单簿深度 (USDC)",
|
"minOrderbookDepth": "最小订单簿深度 (USDC)",
|
||||||
"minOrderbookDepthTooltip": "最小订单簿深度(USDC金额),NULL表示不启用此过滤。检查前 N 档的深度",
|
"minOrderbookDepthTooltip": "最小订单簿深度(USDC金额),NULL表示不启用此过滤。检查前 N 档的深度",
|
||||||
"minOrderbookDepthPlaceholder": "例如:50(可选,不填写表示不启用)",
|
"minOrderbookDepthPlaceholder": "例如:50(可选,不填写表示不启用)",
|
||||||
|
"priceRangeFilter": "价格区间过滤",
|
||||||
|
"priceRange": "价格区间",
|
||||||
|
"priceRangeTooltip": "仅跟单 Leader 交易价格在指定区间内的订单。不填写表示不限制。示例:填写 0.11 和 0.89 表示仅跟单价格在 0.11 到 0.89 之间的订单;只填写最高价 0.89 表示仅跟单价格在 0.89 以下的订单;只填写最低价 0.11 表示仅跟单价格在 0.11 以上的订单。",
|
||||||
|
"minPricePlaceholder": "最低价(留空不限制)",
|
||||||
|
"maxPricePlaceholder": "最高价(留空不限制)",
|
||||||
"supportSell": "跟单卖出",
|
"supportSell": "跟单卖出",
|
||||||
"supportSellTooltip": "是否跟单 Leader 的卖出订单",
|
"supportSellTooltip": "是否跟单 Leader 的卖出订单",
|
||||||
"create": "创建跟单配置",
|
"create": "创建跟单配置",
|
||||||
@@ -560,6 +579,11 @@
|
|||||||
"minOrderbookDepth": "最小订单簿深度 (USDC)",
|
"minOrderbookDepth": "最小订单簿深度 (USDC)",
|
||||||
"minOrderbookDepthTooltip": "最小订单簿深度(USDC金额),NULL表示不启用此过滤",
|
"minOrderbookDepthTooltip": "最小订单簿深度(USDC金额),NULL表示不启用此过滤",
|
||||||
"minOrderbookDepthPlaceholder": "例如:50(可选,不填写表示不启用)",
|
"minOrderbookDepthPlaceholder": "例如:50(可选,不填写表示不启用)",
|
||||||
|
"priceRangeFilter": "价格区间过滤",
|
||||||
|
"priceRange": "价格区间",
|
||||||
|
"priceRangeTooltip": "仅跟单 Leader 交易价格在指定区间内的订单。不填写表示不限制。示例:填写 0.11 和 0.89 表示仅跟单价格在 0.11 到 0.89 之间的订单;只填写最高价 0.89 表示仅跟单价格在 0.89 以下的订单;只填写最低价 0.11 表示仅跟单价格在 0.11 以上的订单。",
|
||||||
|
"minPricePlaceholder": "最低价(留空不限制)",
|
||||||
|
"maxPricePlaceholder": "最高价(留空不限制)",
|
||||||
"supportSell": "跟单卖出",
|
"supportSell": "跟单卖出",
|
||||||
"supportSellTooltip": "是否跟单 Leader 的卖出订单",
|
"supportSellTooltip": "是否跟单 Leader 的卖出订单",
|
||||||
"save": "保存",
|
"save": "保存",
|
||||||
@@ -569,8 +593,8 @@
|
|||||||
"invalidNumber": "请输入有效的数字"
|
"invalidNumber": "请输入有效的数字"
|
||||||
},
|
},
|
||||||
"filteredOrdersList": {
|
"filteredOrdersList": {
|
||||||
"title": "被过滤订单列表",
|
"title": "已过滤订单列表",
|
||||||
"fetchFailed": "获取被过滤订单列表失败",
|
"fetchFailed": "获取已过滤订单列表失败",
|
||||||
"market": "市场",
|
"market": "市场",
|
||||||
"outcome": "方向",
|
"outcome": "方向",
|
||||||
"price": "价格",
|
"price": "价格",
|
||||||
@@ -589,8 +613,10 @@
|
|||||||
"marketStatus": "市场状态不可交易",
|
"marketStatus": "市场状态不可交易",
|
||||||
"orderbookError": "订单簿获取失败",
|
"orderbookError": "订单簿获取失败",
|
||||||
"orderbookEmpty": "订单簿为空",
|
"orderbookEmpty": "订单簿为空",
|
||||||
|
"priceRange": "价格区间不符",
|
||||||
"unknown": "未知原因"
|
"unknown": "未知原因"
|
||||||
}
|
},
|
||||||
|
"noData": "暂无已过滤订单"
|
||||||
},
|
},
|
||||||
"copyTradingList": {
|
"copyTradingList": {
|
||||||
"title": "跟单配置管理",
|
"title": "跟单配置管理",
|
||||||
@@ -611,7 +637,7 @@
|
|||||||
"buyOrders": "买入订单",
|
"buyOrders": "买入订单",
|
||||||
"sellOrders": "卖出订单",
|
"sellOrders": "卖出订单",
|
||||||
"matchedOrders": "匹配关系",
|
"matchedOrders": "匹配关系",
|
||||||
"filteredOrders": "被过滤订单",
|
"filteredOrders": "已过滤订单",
|
||||||
"filterWallet": "筛选钱包",
|
"filterWallet": "筛选钱包",
|
||||||
"filterTemplate": "筛选模板",
|
"filterTemplate": "筛选模板",
|
||||||
"filterLeader": "筛选 Leader",
|
"filterLeader": "筛选 Leader",
|
||||||
|
|||||||
@@ -26,7 +26,11 @@
|
|||||||
"disabled": "禁用",
|
"disabled": "禁用",
|
||||||
"noData": "暫無數據",
|
"noData": "暫無數據",
|
||||||
"saveConfig": "保存配置",
|
"saveConfig": "保存配置",
|
||||||
"refreshConfig": "刷新配置"
|
"refreshConfig": "刷新配置",
|
||||||
|
"total": "共",
|
||||||
|
"items": "條",
|
||||||
|
"prev": "上一頁",
|
||||||
|
"next": "下一頁"
|
||||||
},
|
},
|
||||||
"account": {
|
"account": {
|
||||||
"title": "賬戶管理",
|
"title": "賬戶管理",
|
||||||
@@ -478,6 +482,11 @@
|
|||||||
"minOrderbookDepth": "最小訂單簿深度 (USDC)",
|
"minOrderbookDepth": "最小訂單簿深度 (USDC)",
|
||||||
"minOrderbookDepthTooltip": "最小訂單簿深度(USDC金額),NULL表示不啟用此過濾。檢查前 N 檔的深度",
|
"minOrderbookDepthTooltip": "最小訂單簿深度(USDC金額),NULL表示不啟用此過濾。檢查前 N 檔的深度",
|
||||||
"minOrderbookDepthPlaceholder": "例如:50(可選,不填寫表示不啟用)",
|
"minOrderbookDepthPlaceholder": "例如:50(可選,不填寫表示不啟用)",
|
||||||
|
"priceRangeFilter": "價格區間過濾",
|
||||||
|
"priceRange": "價格區間",
|
||||||
|
"priceRangeTooltip": "僅跟單 Leader 交易價格在指定區間內的訂單。不填寫表示不限制。示例:填寫 0.11 和 0.89 表示僅跟單價格在 0.11 到 0.89 之間的訂單;只填寫最高價 0.89 表示僅跟單價格在 0.89 以下的訂單;只填寫最低價 0.11 表示僅跟單價格在 0.11 以上的訂單。",
|
||||||
|
"minPricePlaceholder": "最低價(留空不限制)",
|
||||||
|
"maxPricePlaceholder": "最高價(留空不限制)",
|
||||||
"supportSell": "跟單賣出",
|
"supportSell": "跟單賣出",
|
||||||
"supportSellTooltip": "是否跟單 Leader 的賣出訂單。開啟:跟單 Leader 的買入和賣出訂單;關閉:只跟單 Leader 的買入訂單,忽略賣出訂單。",
|
"supportSellTooltip": "是否跟單 Leader 的賣出訂單。開啟:跟單 Leader 的買入和賣出訂單;關閉:只跟單 Leader 的買入訂單,忽略賣出訂單。",
|
||||||
"create": "創建模板",
|
"create": "創建模板",
|
||||||
@@ -530,6 +539,11 @@
|
|||||||
"minOrderbookDepth": "最小訂單簿深度 (USDC)",
|
"minOrderbookDepth": "最小訂單簿深度 (USDC)",
|
||||||
"minOrderbookDepthTooltip": "最小訂單簿深度(USDC金額),NULL表示不啟用此過濾。檢查前 N 檔的深度",
|
"minOrderbookDepthTooltip": "最小訂單簿深度(USDC金額),NULL表示不啟用此過濾。檢查前 N 檔的深度",
|
||||||
"minOrderbookDepthPlaceholder": "例如:50(可選,不填寫表示不啟用)",
|
"minOrderbookDepthPlaceholder": "例如:50(可選,不填寫表示不啟用)",
|
||||||
|
"priceRangeFilter": "價格區間過濾",
|
||||||
|
"priceRange": "價格區間",
|
||||||
|
"priceRangeTooltip": "僅跟單 Leader 交易價格在指定區間內的訂單。不填寫表示不限制。示例:填寫 0.11 和 0.89 表示僅跟單價格在 0.11 到 0.89 之間的訂單;只填寫最高價 0.89 表示僅跟單價格在 0.89 以下的訂單;只填寫最低價 0.11 表示僅跟單價格在 0.11 以上的訂單。",
|
||||||
|
"minPricePlaceholder": "最低價(留空不限制)",
|
||||||
|
"maxPricePlaceholder": "最高價(留空不限制)",
|
||||||
"supportSell": "跟單賣出",
|
"supportSell": "跟單賣出",
|
||||||
"supportSellTooltip": "是否跟單 Leader 的賣出訂單。開啟:跟單 Leader 的買入和賣出訂單;關閉:只跟單 Leader 的買入訂單,忽略賣出訂單。",
|
"supportSellTooltip": "是否跟單 Leader 的賣出訂單。開啟:跟單 Leader 的買入和賣出訂單;關閉:只跟單 Leader 的買入訂單,忽略賣出訂單。",
|
||||||
"invalidNumber": "請輸入有效的數字"
|
"invalidNumber": "請輸入有效的數字"
|
||||||
@@ -590,6 +604,11 @@
|
|||||||
"minOrderbookDepth": "最小訂單簿深度 (USDC)",
|
"minOrderbookDepth": "最小訂單簿深度 (USDC)",
|
||||||
"minOrderbookDepthTooltip": "最小訂單簿深度(USDC金額),NULL表示不啟用此過濾。檢查前 N 檔的深度",
|
"minOrderbookDepthTooltip": "最小訂單簿深度(USDC金額),NULL表示不啟用此過濾。檢查前 N 檔的深度",
|
||||||
"minOrderbookDepthPlaceholder": "例如:50(可選,不填寫表示不啟用)",
|
"minOrderbookDepthPlaceholder": "例如:50(可選,不填寫表示不啟用)",
|
||||||
|
"priceRangeFilter": "價格區間過濾",
|
||||||
|
"priceRange": "價格區間",
|
||||||
|
"priceRangeTooltip": "僅跟單 Leader 交易價格在指定區間內的訂單。不填寫表示不限制。示例:填寫 0.11 和 0.89 表示僅跟單價格在 0.11 到 0.89 之間的訂單;只填寫最高價 0.89 表示僅跟單價格在 0.89 以下的訂單;只填寫最低價 0.11 表示僅跟單價格在 0.11 以上的訂單。",
|
||||||
|
"minPricePlaceholder": "最低價(留空不限制)",
|
||||||
|
"maxPricePlaceholder": "最高價(留空不限制)",
|
||||||
"supportSell": "跟單賣出",
|
"supportSell": "跟單賣出",
|
||||||
"supportSellTooltip": "是否跟單 Leader 的賣出訂單",
|
"supportSellTooltip": "是否跟單 Leader 的賣出訂單",
|
||||||
"create": "創建跟單配置",
|
"create": "創建跟單配置",
|
||||||
@@ -648,6 +667,11 @@
|
|||||||
"minOrderbookDepth": "最小訂單簿深度 (USDC)",
|
"minOrderbookDepth": "最小訂單簿深度 (USDC)",
|
||||||
"minOrderbookDepthTooltip": "最小訂單簿深度(USDC金額),NULL表示不啟用此過濾",
|
"minOrderbookDepthTooltip": "最小訂單簿深度(USDC金額),NULL表示不啟用此過濾",
|
||||||
"minOrderbookDepthPlaceholder": "例如:50(可選,不填寫表示不啟用)",
|
"minOrderbookDepthPlaceholder": "例如:50(可選,不填寫表示不啟用)",
|
||||||
|
"priceRangeFilter": "價格區間過濾",
|
||||||
|
"priceRange": "價格區間",
|
||||||
|
"priceRangeTooltip": "僅跟單 Leader 交易價格在指定區間內的訂單。不填寫表示不限制。示例:填寫 0.11 和 0.89 表示僅跟單價格在 0.11 到 0.89 之間的訂單;只填寫最高價 0.89 表示僅跟單價格在 0.89 以下的訂單;只填寫最低價 0.11 表示僅跟單價格在 0.11 以上的訂單。",
|
||||||
|
"minPricePlaceholder": "最低價(留空不限制)",
|
||||||
|
"maxPricePlaceholder": "最高價(留空不限制)",
|
||||||
"supportSell": "跟單賣出",
|
"supportSell": "跟單賣出",
|
||||||
"supportSellTooltip": "是否跟單 Leader 的賣出訂單",
|
"supportSellTooltip": "是否跟單 Leader 的賣出訂單",
|
||||||
"save": "保存",
|
"save": "保存",
|
||||||
@@ -677,8 +701,10 @@
|
|||||||
"marketStatus": "市場狀態不可交易",
|
"marketStatus": "市場狀態不可交易",
|
||||||
"orderbookError": "訂單簿獲取失敗",
|
"orderbookError": "訂單簿獲取失敗",
|
||||||
"orderbookEmpty": "訂單簿為空",
|
"orderbookEmpty": "訂單簿為空",
|
||||||
|
"priceRange": "價格區間不符",
|
||||||
"unknown": "未知原因"
|
"unknown": "未知原因"
|
||||||
}
|
},
|
||||||
|
"noData": "暫無已過濾訂單"
|
||||||
},
|
},
|
||||||
"copyTradingList": {
|
"copyTradingList": {
|
||||||
"title": "跟單配置管理",
|
"title": "跟單配置管理",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { useEffect, useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
import { useNavigate } from 'react-router-dom'
|
import { useNavigate } from 'react-router-dom'
|
||||||
import { Card, Form, Button, Switch, message, Typography, Space, Radio, InputNumber, Modal, Table, Select } from 'antd'
|
import { Card, Form, Button, Switch, message, Typography, Space, Radio, InputNumber, Modal, Table, Select, Divider, Input } from 'antd'
|
||||||
import { ArrowLeftOutlined, SaveOutlined, FileTextOutlined } from '@ant-design/icons'
|
import { ArrowLeftOutlined, SaveOutlined, FileTextOutlined } from '@ant-design/icons'
|
||||||
import { apiService } from '../services/api'
|
import { apiService } from '../services/api'
|
||||||
import { useAccountStore } from '../store/accountStore'
|
import { useAccountStore } from '../store/accountStore'
|
||||||
@@ -63,7 +63,9 @@ const CopyTradingAdd: React.FC = () => {
|
|||||||
supportSell: template.supportSell,
|
supportSell: template.supportSell,
|
||||||
minOrderDepth: template.minOrderDepth ? parseFloat(template.minOrderDepth) : undefined,
|
minOrderDepth: template.minOrderDepth ? parseFloat(template.minOrderDepth) : undefined,
|
||||||
maxSpread: template.maxSpread ? parseFloat(template.maxSpread) : undefined,
|
maxSpread: template.maxSpread ? parseFloat(template.maxSpread) : undefined,
|
||||||
minOrderbookDepth: template.minOrderbookDepth ? parseFloat(template.minOrderbookDepth) : undefined
|
minOrderbookDepth: template.minOrderbookDepth ? parseFloat(template.minOrderbookDepth) : undefined,
|
||||||
|
minPrice: template.minPrice ? parseFloat(template.minPrice) : undefined,
|
||||||
|
maxPrice: template.maxPrice ? parseFloat(template.maxPrice) : undefined
|
||||||
})
|
})
|
||||||
setCopyMode(template.copyMode)
|
setCopyMode(template.copyMode)
|
||||||
setTemplateModalVisible(false)
|
setTemplateModalVisible(false)
|
||||||
@@ -110,7 +112,9 @@ const CopyTradingAdd: React.FC = () => {
|
|||||||
supportSell: values.supportSell !== false,
|
supportSell: values.supportSell !== false,
|
||||||
minOrderDepth: values.minOrderDepth?.toString(),
|
minOrderDepth: values.minOrderDepth?.toString(),
|
||||||
maxSpread: values.maxSpread?.toString(),
|
maxSpread: values.maxSpread?.toString(),
|
||||||
minOrderbookDepth: values.minOrderbookDepth?.toString()
|
minOrderbookDepth: values.minOrderbookDepth?.toString(),
|
||||||
|
minPrice: values.minPrice?.toString(),
|
||||||
|
maxPrice: values.maxPrice?.toString()
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = await apiService.copyTrading.create(request)
|
const response = await apiService.copyTrading.create(request)
|
||||||
@@ -408,6 +412,38 @@ const CopyTradingAdd: React.FC = () => {
|
|||||||
/>
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
|
<Divider>{t('copyTradingAdd.priceRangeFilter') || '价格区间过滤'}</Divider>
|
||||||
|
|
||||||
|
<Form.Item
|
||||||
|
label={t('copyTradingAdd.priceRange') || '价格区间'}
|
||||||
|
name="priceRange"
|
||||||
|
tooltip={t('copyTradingAdd.priceRangeTooltip') || '配置价格区间,仅在指定价格区间内的订单才会下单。例如:0.11-0.89 表示区间在0.11和0.89之间;-0.89 表示0.89以下都可以;0.11- 表示0.11以上都可以'}
|
||||||
|
>
|
||||||
|
<Input.Group compact style={{ display: 'flex' }}>
|
||||||
|
<Form.Item name="minPrice" noStyle>
|
||||||
|
<InputNumber
|
||||||
|
min={0.01}
|
||||||
|
max={0.99}
|
||||||
|
step={0.0001}
|
||||||
|
precision={4}
|
||||||
|
style={{ width: '50%' }}
|
||||||
|
placeholder={t('copyTradingAdd.minPricePlaceholder') || '最低价(可选)'}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
<span style={{ display: 'inline-block', width: '20px', textAlign: 'center', lineHeight: '32px' }}>-</span>
|
||||||
|
<Form.Item name="maxPrice" noStyle>
|
||||||
|
<InputNumber
|
||||||
|
min={0.01}
|
||||||
|
max={0.99}
|
||||||
|
step={0.0001}
|
||||||
|
precision={4}
|
||||||
|
style={{ width: '50%' }}
|
||||||
|
placeholder={t('copyTradingAdd.maxPricePlaceholder') || '最高价(可选)'}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
</Input.Group>
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
{/* 跟单卖出 - 表单最底部 */}
|
{/* 跟单卖出 - 表单最底部 */}
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label={t('copyTradingAdd.supportSell') || '跟单卖出'}
|
label={t('copyTradingAdd.supportSell') || '跟单卖出'}
|
||||||
|
|||||||
@@ -5,11 +5,13 @@ import { LeftOutlined } from '@ant-design/icons'
|
|||||||
import { apiService } from '../services/api'
|
import { apiService } from '../services/api'
|
||||||
import { formatUSDC } from '../utils'
|
import { formatUSDC } from '../utils'
|
||||||
import { useMediaQuery } from 'react-responsive'
|
import { useMediaQuery } from 'react-responsive'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
import type { BuyOrderInfo, OrderTrackingRequest, OrderTrackingListResponse } from '../types'
|
import type { BuyOrderInfo, OrderTrackingRequest, OrderTrackingListResponse } from '../types'
|
||||||
|
|
||||||
const { Option } = Select
|
const { Option } = Select
|
||||||
|
|
||||||
const CopyTradingBuyOrdersPage: React.FC = () => {
|
const CopyTradingBuyOrdersPage: React.FC = () => {
|
||||||
|
const { t } = useTranslation()
|
||||||
const { copyTradingId } = useParams<{ copyTradingId: string }>()
|
const { copyTradingId } = useParams<{ copyTradingId: string }>()
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
const isMobile = useMediaQuery({ maxWidth: 768 })
|
const isMobile = useMediaQuery({ maxWidth: 768 })
|
||||||
@@ -199,8 +201,8 @@ const CopyTradingBuyOrdersPage: React.FC = () => {
|
|||||||
<Card>
|
<Card>
|
||||||
<div style={{ marginBottom: 16, display: 'flex', justifyContent: 'space-between', alignItems: 'center', flexWrap: 'wrap', gap: 16 }}>
|
<div style={{ marginBottom: 16, display: 'flex', justifyContent: 'space-between', alignItems: 'center', flexWrap: 'wrap', gap: 16 }}>
|
||||||
<div style={{ display: 'flex', alignItems: 'center', gap: 16 }}>
|
<div style={{ display: 'flex', alignItems: 'center', gap: 16 }}>
|
||||||
<Button icon={<LeftOutlined />} onClick={() => navigate(`/copy-trading/statistics/${copyTradingId}`)}>
|
<Button icon={<LeftOutlined />} onClick={() => navigate(-1)}>
|
||||||
返回统计
|
{t('common.back') || '返回'}
|
||||||
</Button>
|
</Button>
|
||||||
<h2 style={{ margin: 0 }}>买入订单列表</h2>
|
<h2 style={{ margin: 0 }}>买入订单列表</h2>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { useEffect, useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
import { useNavigate, useParams } from 'react-router-dom'
|
import { useNavigate, useParams } from 'react-router-dom'
|
||||||
import { Card, Form, Button, Switch, message, Typography, Space, Radio, InputNumber, Divider, Spin, Select } from 'antd'
|
import { Card, Form, Button, Switch, message, Typography, Space, Radio, InputNumber, Divider, Spin, Select, Input } from 'antd'
|
||||||
import { ArrowLeftOutlined, SaveOutlined } from '@ant-design/icons'
|
import { ArrowLeftOutlined, SaveOutlined } from '@ant-design/icons'
|
||||||
import { apiService } from '../services/api'
|
import { apiService } from '../services/api'
|
||||||
import type { CopyTrading, CopyTradingUpdateRequest } from '../types'
|
import type { CopyTrading, CopyTradingUpdateRequest } from '../types'
|
||||||
@@ -56,7 +56,9 @@ const CopyTradingEdit: React.FC = () => {
|
|||||||
supportSell: found.supportSell,
|
supportSell: found.supportSell,
|
||||||
minOrderDepth: found.minOrderDepth ? parseFloat(found.minOrderDepth) : undefined,
|
minOrderDepth: found.minOrderDepth ? parseFloat(found.minOrderDepth) : undefined,
|
||||||
maxSpread: found.maxSpread ? parseFloat(found.maxSpread) : undefined,
|
maxSpread: found.maxSpread ? parseFloat(found.maxSpread) : undefined,
|
||||||
minOrderbookDepth: found.minOrderbookDepth ? parseFloat(found.minOrderbookDepth) : undefined
|
minOrderbookDepth: found.minOrderbookDepth ? parseFloat(found.minOrderbookDepth) : undefined,
|
||||||
|
minPrice: found.minPrice ? parseFloat(found.minPrice) : undefined,
|
||||||
|
maxPrice: found.maxPrice ? parseFloat(found.maxPrice) : undefined
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
message.error(t('copyTradingEdit.fetchFailed') || '跟单配置不存在')
|
message.error(t('copyTradingEdit.fetchFailed') || '跟单配置不存在')
|
||||||
@@ -118,7 +120,9 @@ const CopyTradingEdit: React.FC = () => {
|
|||||||
supportSell: values.supportSell,
|
supportSell: values.supportSell,
|
||||||
minOrderDepth: values.minOrderDepth?.toString(),
|
minOrderDepth: values.minOrderDepth?.toString(),
|
||||||
maxSpread: values.maxSpread?.toString(),
|
maxSpread: values.maxSpread?.toString(),
|
||||||
minOrderbookDepth: values.minOrderbookDepth?.toString()
|
minOrderbookDepth: values.minOrderbookDepth?.toString(),
|
||||||
|
minPrice: values.minPrice?.toString(),
|
||||||
|
maxPrice: values.maxPrice?.toString()
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = await apiService.copyTrading.update(request)
|
const response = await apiService.copyTrading.update(request)
|
||||||
@@ -397,6 +401,38 @@ const CopyTradingEdit: React.FC = () => {
|
|||||||
/>
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
|
<Divider>{t('copyTradingEdit.priceRangeFilter') || '价格区间过滤'}</Divider>
|
||||||
|
|
||||||
|
<Form.Item
|
||||||
|
label={t('copyTradingEdit.priceRange') || '价格区间'}
|
||||||
|
name="priceRange"
|
||||||
|
tooltip={t('copyTradingEdit.priceRangeTooltip') || '配置价格区间,仅在指定价格区间内的订单才会下单。例如:0.11-0.89 表示区间在0.11和0.89之间;-0.89 表示0.89以下都可以;0.11- 表示0.11以上都可以'}
|
||||||
|
>
|
||||||
|
<Input.Group compact style={{ display: 'flex' }}>
|
||||||
|
<Form.Item name="minPrice" noStyle>
|
||||||
|
<InputNumber
|
||||||
|
min={0.01}
|
||||||
|
max={0.99}
|
||||||
|
step={0.0001}
|
||||||
|
precision={4}
|
||||||
|
style={{ width: '50%' }}
|
||||||
|
placeholder={t('copyTradingEdit.minPricePlaceholder') || '最低价(可选)'}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
<span style={{ display: 'inline-block', width: '20px', textAlign: 'center', lineHeight: '32px' }}>-</span>
|
||||||
|
<Form.Item name="maxPrice" noStyle>
|
||||||
|
<InputNumber
|
||||||
|
min={0.01}
|
||||||
|
max={0.99}
|
||||||
|
step={0.0001}
|
||||||
|
precision={4}
|
||||||
|
style={{ width: '50%' }}
|
||||||
|
placeholder={t('copyTradingEdit.maxPricePlaceholder') || '最高价(可选)'}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
</Input.Group>
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
{/* 跟单卖出 - 表单最底部 */}
|
{/* 跟单卖出 - 表单最底部 */}
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label={t('copyTradingEdit.supportSell') || '跟单卖出'}
|
label={t('copyTradingEdit.supportSell') || '跟单卖出'}
|
||||||
|
|||||||
@@ -264,12 +264,6 @@ const CopyTradingList: React.FC = () => {
|
|||||||
{
|
{
|
||||||
type: 'divider'
|
type: 'divider'
|
||||||
},
|
},
|
||||||
{
|
|
||||||
key: 'statistics',
|
|
||||||
label: t('copyTradingList.viewStatistics') || '查看统计',
|
|
||||||
icon: <BarChartOutlined />,
|
|
||||||
onClick: () => navigate(`/copy-trading/statistics/${record.id}`)
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
key: 'buyOrders',
|
key: 'buyOrders',
|
||||||
label: t('copyTradingList.buyOrders') || '买入订单',
|
label: t('copyTradingList.buyOrders') || '买入订单',
|
||||||
@@ -290,7 +284,7 @@ const CopyTradingList: React.FC = () => {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'filteredOrders',
|
key: 'filteredOrders',
|
||||||
label: t('copyTradingList.filteredOrders') || '被过滤订单',
|
label: t('copyTradingList.filteredOrders') || '已过滤订单',
|
||||||
icon: <UnorderedListOutlined />,
|
icon: <UnorderedListOutlined />,
|
||||||
onClick: () => navigate(`/copy-trading/filtered-orders/${record.id}`)
|
onClick: () => navigate(`/copy-trading/filtered-orders/${record.id}`)
|
||||||
},
|
},
|
||||||
@@ -550,39 +544,47 @@ const CopyTradingList: React.FC = () => {
|
|||||||
<div style={{ display: 'flex', gap: '8px', flexWrap: 'wrap' }}>
|
<div style={{ display: 'flex', gap: '8px', flexWrap: 'wrap' }}>
|
||||||
<Button
|
<Button
|
||||||
type="primary"
|
type="primary"
|
||||||
|
size="small"
|
||||||
|
icon={<EditOutlined />}
|
||||||
|
onClick={() => navigate(`/copy-trading/edit/${record.id}`)}
|
||||||
|
style={{ flex: 1, minWidth: '80px' }}
|
||||||
|
>
|
||||||
|
{t('common.edit') || '编辑'}
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
size="small"
|
size="small"
|
||||||
icon={<BarChartOutlined />}
|
icon={<BarChartOutlined />}
|
||||||
onClick={() => navigate(`/copy-trading/statistics/${record.id}`)}
|
onClick={() => navigate(`/copy-trading/statistics/${record.id}`)}
|
||||||
style={{ flex: 1, minWidth: '80px' }}
|
style={{ flex: 1, minWidth: '80px' }}
|
||||||
>
|
>
|
||||||
统计
|
{t('copyTradingList.statistics') || '统计'}
|
||||||
</Button>
|
</Button>
|
||||||
<Dropdown
|
<Dropdown
|
||||||
menu={{
|
menu={{
|
||||||
items: [
|
items: [
|
||||||
{
|
|
||||||
key: 'statistics',
|
|
||||||
label: '查看统计',
|
|
||||||
icon: <BarChartOutlined />,
|
|
||||||
onClick: () => navigate(`/copy-trading/statistics/${record.id}`)
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
key: 'buyOrders',
|
key: 'buyOrders',
|
||||||
label: '买入订单',
|
label: t('copyTradingList.buyOrders') || '买入订单',
|
||||||
icon: <UnorderedListOutlined />,
|
icon: <UnorderedListOutlined />,
|
||||||
onClick: () => navigate(`/copy-trading/orders/buy/${record.id}`)
|
onClick: () => navigate(`/copy-trading/orders/buy/${record.id}`)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'sellOrders',
|
key: 'sellOrders',
|
||||||
label: '卖出订单',
|
label: t('copyTradingList.sellOrders') || '卖出订单',
|
||||||
icon: <UnorderedListOutlined />,
|
icon: <UnorderedListOutlined />,
|
||||||
onClick: () => navigate(`/copy-trading/orders/sell/${record.id}`)
|
onClick: () => navigate(`/copy-trading/orders/sell/${record.id}`)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'matchedOrders',
|
key: 'matchedOrders',
|
||||||
label: '匹配关系',
|
label: t('copyTradingList.matchedOrders') || '匹配关系',
|
||||||
icon: <UnorderedListOutlined />,
|
icon: <UnorderedListOutlined />,
|
||||||
onClick: () => navigate(`/copy-trading/orders/matched/${record.id}`)
|
onClick: () => navigate(`/copy-trading/orders/matched/${record.id}`)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'filteredOrders',
|
||||||
|
label: t('copyTradingList.filteredOrders') || '已过滤订单',
|
||||||
|
icon: <UnorderedListOutlined />,
|
||||||
|
onClick: () => navigate(`/copy-trading/filtered-orders/${record.id}`)
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}}
|
}}
|
||||||
@@ -593,14 +595,14 @@ const CopyTradingList: React.FC = () => {
|
|||||||
icon={<UnorderedListOutlined />}
|
icon={<UnorderedListOutlined />}
|
||||||
style={{ flex: 1, minWidth: '80px' }}
|
style={{ flex: 1, minWidth: '80px' }}
|
||||||
>
|
>
|
||||||
订单
|
{t('copyTradingList.orders') || '订单'}
|
||||||
</Button>
|
</Button>
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
<Popconfirm
|
<Popconfirm
|
||||||
title="确定要删除这个跟单关系吗?"
|
title={t('copyTradingList.deleteConfirm') || '确定要删除这个跟单关系吗?'}
|
||||||
onConfirm={() => handleDelete(record.id)}
|
onConfirm={() => handleDelete(record.id)}
|
||||||
okText="确定"
|
okText={t('common.confirm') || '确定'}
|
||||||
cancelText="取消"
|
cancelText={t('common.cancel') || '取消'}
|
||||||
>
|
>
|
||||||
<Button
|
<Button
|
||||||
danger
|
danger
|
||||||
@@ -608,7 +610,7 @@ const CopyTradingList: React.FC = () => {
|
|||||||
icon={<DeleteOutlined />}
|
icon={<DeleteOutlined />}
|
||||||
style={{ flex: 1, minWidth: '80px' }}
|
style={{ flex: 1, minWidth: '80px' }}
|
||||||
>
|
>
|
||||||
删除
|
{t('common.delete') || '删除'}
|
||||||
</Button>
|
</Button>
|
||||||
</Popconfirm>
|
</Popconfirm>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -5,9 +5,11 @@ import { LeftOutlined } from '@ant-design/icons'
|
|||||||
import { apiService } from '../services/api'
|
import { apiService } from '../services/api'
|
||||||
import { formatUSDC } from '../utils'
|
import { formatUSDC } from '../utils'
|
||||||
import { useMediaQuery } from 'react-responsive'
|
import { useMediaQuery } from 'react-responsive'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
import type { MatchedOrderInfo, OrderTrackingRequest, OrderTrackingListResponse } from '../types'
|
import type { MatchedOrderInfo, OrderTrackingRequest, OrderTrackingListResponse } from '../types'
|
||||||
|
|
||||||
const CopyTradingMatchedOrdersPage: React.FC = () => {
|
const CopyTradingMatchedOrdersPage: React.FC = () => {
|
||||||
|
const { t } = useTranslation()
|
||||||
const { copyTradingId } = useParams<{ copyTradingId: string }>()
|
const { copyTradingId } = useParams<{ copyTradingId: string }>()
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
const isMobile = useMediaQuery({ maxWidth: 768 })
|
const isMobile = useMediaQuery({ maxWidth: 768 })
|
||||||
@@ -153,8 +155,8 @@ const CopyTradingMatchedOrdersPage: React.FC = () => {
|
|||||||
<Card>
|
<Card>
|
||||||
<div style={{ marginBottom: 16, display: 'flex', justifyContent: 'space-between', alignItems: 'center', flexWrap: 'wrap', gap: 16 }}>
|
<div style={{ marginBottom: 16, display: 'flex', justifyContent: 'space-between', alignItems: 'center', flexWrap: 'wrap', gap: 16 }}>
|
||||||
<div style={{ display: 'flex', alignItems: 'center', gap: 16 }}>
|
<div style={{ display: 'flex', alignItems: 'center', gap: 16 }}>
|
||||||
<Button icon={<LeftOutlined />} onClick={() => navigate(`/copy-trading/statistics/${copyTradingId}`)}>
|
<Button icon={<LeftOutlined />} onClick={() => navigate(-1)}>
|
||||||
返回统计
|
{t('common.back') || '返回'}
|
||||||
</Button>
|
</Button>
|
||||||
<h2 style={{ margin: 0 }}>匹配关系列表</h2>
|
<h2 style={{ margin: 0 }}>匹配关系列表</h2>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -5,11 +5,13 @@ import { LeftOutlined } from '@ant-design/icons'
|
|||||||
import { apiService } from '../services/api'
|
import { apiService } from '../services/api'
|
||||||
import { formatUSDC } from '../utils'
|
import { formatUSDC } from '../utils'
|
||||||
import { useMediaQuery } from 'react-responsive'
|
import { useMediaQuery } from 'react-responsive'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
import type { SellOrderInfo, OrderTrackingRequest, OrderTrackingListResponse } from '../types'
|
import type { SellOrderInfo, OrderTrackingRequest, OrderTrackingListResponse } from '../types'
|
||||||
|
|
||||||
const { Option } = Select
|
const { Option } = Select
|
||||||
|
|
||||||
const CopyTradingSellOrdersPage: React.FC = () => {
|
const CopyTradingSellOrdersPage: React.FC = () => {
|
||||||
|
const { t } = useTranslation()
|
||||||
const { copyTradingId } = useParams<{ copyTradingId: string }>()
|
const { copyTradingId } = useParams<{ copyTradingId: string }>()
|
||||||
const navigate = useNavigate()
|
const navigate = useNavigate()
|
||||||
const isMobile = useMediaQuery({ maxWidth: 768 })
|
const isMobile = useMediaQuery({ maxWidth: 768 })
|
||||||
@@ -184,8 +186,8 @@ const CopyTradingSellOrdersPage: React.FC = () => {
|
|||||||
<Card>
|
<Card>
|
||||||
<div style={{ marginBottom: 16, display: 'flex', justifyContent: 'space-between', alignItems: 'center', flexWrap: 'wrap', gap: 16 }}>
|
<div style={{ marginBottom: 16, display: 'flex', justifyContent: 'space-between', alignItems: 'center', flexWrap: 'wrap', gap: 16 }}>
|
||||||
<div style={{ display: 'flex', alignItems: 'center', gap: 16 }}>
|
<div style={{ display: 'flex', alignItems: 'center', gap: 16 }}>
|
||||||
<Button icon={<LeftOutlined />} onClick={() => navigate(`/copy-trading/statistics/${copyTradingId}`)}>
|
<Button icon={<LeftOutlined />} onClick={() => navigate(-1)}>
|
||||||
返回统计
|
{t('common.back') || '返回'}
|
||||||
</Button>
|
</Button>
|
||||||
<h2 style={{ margin: 0 }}>卖出订单列表</h2>
|
<h2 style={{ margin: 0 }}>卖出订单列表</h2>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -110,7 +110,7 @@ const CopyTradingStatisticsPage: React.FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Col>
|
</Col>
|
||||||
<Col xs={24} sm={12} md={6}>
|
<Col xs={24} sm={12} md={8}>
|
||||||
<div>
|
<div>
|
||||||
<div style={{ color: '#999', fontSize: 14, marginBottom: 4 }}>Leader 名称</div>
|
<div style={{ color: '#999', fontSize: 14, marginBottom: 4 }}>Leader 名称</div>
|
||||||
<div style={{ fontSize: 16, fontWeight: 500 }}>
|
<div style={{ fontSize: 16, fontWeight: 500 }}>
|
||||||
@@ -118,15 +118,7 @@ const CopyTradingStatisticsPage: React.FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</Col>
|
</Col>
|
||||||
<Col xs={24} sm={12} md={6}>
|
<Col xs={24} sm={12} md={8}>
|
||||||
<div>
|
|
||||||
<div style={{ color: '#999', fontSize: 14, marginBottom: 4 }}>模板名称</div>
|
|
||||||
<div style={{ fontSize: 16, fontWeight: 500 }}>
|
|
||||||
{statistics.templateName || `模板 ${statistics.templateId}`}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Col>
|
|
||||||
<Col xs={24} sm={12} md={6}>
|
|
||||||
<div>
|
<div>
|
||||||
<div style={{ color: '#999', fontSize: 14, marginBottom: 4 }}>跟单状态</div>
|
<div style={{ color: '#999', fontSize: 14, marginBottom: 4 }}>跟单状态</div>
|
||||||
<div>
|
<div>
|
||||||
@@ -203,21 +195,14 @@ const CopyTradingStatisticsPage: React.FC = () => {
|
|||||||
{/* 持仓统计卡片 */}
|
{/* 持仓统计卡片 */}
|
||||||
<Card title="持仓统计" style={{ marginBottom: 16 }}>
|
<Card title="持仓统计" style={{ marginBottom: 16 }}>
|
||||||
<Row gutter={[16, 16]}>
|
<Row gutter={[16, 16]}>
|
||||||
<Col xs={24} sm={12} md={8}>
|
<Col xs={24} sm={12} md={12}>
|
||||||
<Statistic
|
<Statistic
|
||||||
title="当前持仓数量"
|
title="当前持仓数量"
|
||||||
value={formatUSDC(statistics.currentPositionQuantity)}
|
value={formatUSDC(statistics.currentPositionQuantity)}
|
||||||
suffix=""
|
suffix=""
|
||||||
/>
|
/>
|
||||||
</Col>
|
</Col>
|
||||||
<Col xs={24} sm={12} md={8}>
|
<Col xs={24} sm={12} md={12}>
|
||||||
<Statistic
|
|
||||||
title="当前持仓价值"
|
|
||||||
value={formatUSDC(statistics.currentPositionValue)}
|
|
||||||
suffix="USDC"
|
|
||||||
/>
|
|
||||||
</Col>
|
|
||||||
<Col xs={24} sm={12} md={8}>
|
|
||||||
<Statistic
|
<Statistic
|
||||||
title="平均买入价格"
|
title="平均买入价格"
|
||||||
value={formatUSDC(statistics.avgBuyPrice)}
|
value={formatUSDC(statistics.avgBuyPrice)}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { useEffect, useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
import { useNavigate, useParams } from 'react-router-dom'
|
import { useNavigate, useParams } from 'react-router-dom'
|
||||||
import { Card, Table, Button, Tag, Select, Space, message } from 'antd'
|
import { Card, Table, Button, Tag, Select, Space, message, Divider, Spin } from 'antd'
|
||||||
import { ArrowLeftOutlined } from '@ant-design/icons'
|
import { ArrowLeftOutlined } from '@ant-design/icons'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { apiService } from '../services/api'
|
import { apiService } from '../services/api'
|
||||||
@@ -64,6 +64,7 @@ const FilteredOrdersList: React.FC = () => {
|
|||||||
'MARKET_STATUS': { color: 'blue', label: t('filteredOrdersList.filterTypes.marketStatus') || '市场状态不可交易' },
|
'MARKET_STATUS': { color: 'blue', label: t('filteredOrdersList.filterTypes.marketStatus') || '市场状态不可交易' },
|
||||||
'ORDERBOOK_ERROR': { color: 'default', label: t('filteredOrdersList.filterTypes.orderbookError') || '订单簿获取失败' },
|
'ORDERBOOK_ERROR': { color: 'default', label: t('filteredOrdersList.filterTypes.orderbookError') || '订单簿获取失败' },
|
||||||
'ORDERBOOK_EMPTY': { color: 'default', label: t('filteredOrdersList.filterTypes.orderbookEmpty') || '订单簿为空' },
|
'ORDERBOOK_EMPTY': { color: 'default', label: t('filteredOrdersList.filterTypes.orderbookEmpty') || '订单簿为空' },
|
||||||
|
'PRICE_RANGE': { color: 'purple', label: t('filteredOrdersList.filterTypes.priceRange') || '价格区间不符' },
|
||||||
'UNKNOWN': { color: 'default', label: t('filteredOrdersList.filterTypes.unknown') || '未知原因' }
|
'UNKNOWN': { color: 'default', label: t('filteredOrdersList.filterTypes.unknown') || '未知原因' }
|
||||||
}
|
}
|
||||||
const config = typeMap[type] || typeMap['UNKNOWN']
|
const config = typeMap[type] || typeMap['UNKNOWN']
|
||||||
@@ -210,26 +211,186 @@ const FilteredOrdersList: React.FC = () => {
|
|||||||
<Option value="MARKET_STATUS">{t('filteredOrdersList.filterTypes.marketStatus') || '市场状态不可交易'}</Option>
|
<Option value="MARKET_STATUS">{t('filteredOrdersList.filterTypes.marketStatus') || '市场状态不可交易'}</Option>
|
||||||
<Option value="ORDERBOOK_ERROR">{t('filteredOrdersList.filterTypes.orderbookError') || '订单簿获取失败'}</Option>
|
<Option value="ORDERBOOK_ERROR">{t('filteredOrdersList.filterTypes.orderbookError') || '订单簿获取失败'}</Option>
|
||||||
<Option value="ORDERBOOK_EMPTY">{t('filteredOrdersList.filterTypes.orderbookEmpty') || '订单簿为空'}</Option>
|
<Option value="ORDERBOOK_EMPTY">{t('filteredOrdersList.filterTypes.orderbookEmpty') || '订单簿为空'}</Option>
|
||||||
|
<Option value="PRICE_RANGE">{t('filteredOrdersList.filterTypes.priceRange') || '价格区间不符'}</Option>
|
||||||
</Select>
|
</Select>
|
||||||
</Space>
|
</Space>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Table
|
{isMobile ? (
|
||||||
columns={columns}
|
// 移动端卡片布局
|
||||||
dataSource={filteredOrders}
|
<div>
|
||||||
rowKey="id"
|
{loading ? (
|
||||||
loading={loading}
|
<div style={{ textAlign: 'center', padding: '40px' }}>
|
||||||
pagination={{
|
<Spin size="large" />
|
||||||
current: page,
|
</div>
|
||||||
pageSize: limit,
|
) : filteredOrders.length === 0 ? (
|
||||||
total: total,
|
<div style={{ textAlign: 'center', padding: '40px', color: '#999' }}>
|
||||||
showSizeChanger: false,
|
{t('filteredOrdersList.noData') || '暂无已过滤订单'}
|
||||||
showTotal: (total) => t('common.total') + `: ${total}`,
|
</div>
|
||||||
onChange: (page) => setPage(page)
|
) : (
|
||||||
}}
|
<div style={{ display: 'flex', flexDirection: 'column', gap: '12px' }}>
|
||||||
scroll={{ x: isMobile ? 800 : 'auto' }}
|
{filteredOrders.map((order) => {
|
||||||
size={isMobile ? 'small' : 'middle'}
|
const date = new Date(order.createdAt)
|
||||||
/>
|
const formattedDate = date.toLocaleString('zh-CN', {
|
||||||
|
year: 'numeric',
|
||||||
|
month: '2-digit',
|
||||||
|
day: '2-digit',
|
||||||
|
hour: '2-digit',
|
||||||
|
minute: '2-digit'
|
||||||
|
})
|
||||||
|
const marketLink = getMarketLink(order)
|
||||||
|
const marketTitle = order.marketTitle || order.marketId.slice(0, 10) + '...'
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card
|
||||||
|
key={order.id}
|
||||||
|
style={{
|
||||||
|
borderRadius: '12px',
|
||||||
|
boxShadow: '0 2px 8px rgba(0,0,0,0.08)',
|
||||||
|
border: '1px solid #e8e8e8'
|
||||||
|
}}
|
||||||
|
bodyStyle={{ padding: '16px' }}
|
||||||
|
>
|
||||||
|
{/* 市场信息 */}
|
||||||
|
<div style={{ marginBottom: '12px' }}>
|
||||||
|
<div style={{
|
||||||
|
fontSize: '16px',
|
||||||
|
fontWeight: 'bold',
|
||||||
|
marginBottom: '8px',
|
||||||
|
color: '#1890ff'
|
||||||
|
}}>
|
||||||
|
{marketLink ? (
|
||||||
|
<a href={marketLink} target="_blank" rel="noopener noreferrer" style={{ color: '#1890ff' }}>
|
||||||
|
{marketTitle}
|
||||||
|
</a>
|
||||||
|
) : (
|
||||||
|
marketTitle
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div style={{ display: 'flex', flexWrap: 'wrap', gap: '6px', alignItems: 'center' }}>
|
||||||
|
<Tag color={order.side === 'BUY' ? 'green' : 'red'}>
|
||||||
|
{order.side === 'BUY' ? (t('order.buy') || '买入') : (t('order.sell') || '卖出')}
|
||||||
|
</Tag>
|
||||||
|
{getFilterTypeTag(order.filterType)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Divider style={{ margin: '12px 0' }} />
|
||||||
|
|
||||||
|
{/* 订单详情 */}
|
||||||
|
<div style={{ marginBottom: '12px' }}>
|
||||||
|
<div style={{ fontSize: '12px', color: '#666', marginBottom: '4px' }}>
|
||||||
|
{t('filteredOrdersList.outcome') || '市场方向'}
|
||||||
|
</div>
|
||||||
|
<div style={{ fontSize: '14px', fontWeight: '500' }}>
|
||||||
|
{order.outcome || (order.outcomeIndex !== undefined ? `Index ${order.outcomeIndex}` : '-')}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style={{ marginBottom: '12px' }}>
|
||||||
|
<div style={{ fontSize: '12px', color: '#666', marginBottom: '4px' }}>
|
||||||
|
{t('filteredOrdersList.price') || '价格'}
|
||||||
|
</div>
|
||||||
|
<div style={{ fontSize: '14px', fontWeight: '500' }}>
|
||||||
|
{order.price}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style={{ marginBottom: '12px' }}>
|
||||||
|
<div style={{ fontSize: '12px', color: '#666', marginBottom: '4px' }}>
|
||||||
|
{t('filteredOrdersList.size') || 'Leader数量'}
|
||||||
|
</div>
|
||||||
|
<div style={{ fontSize: '14px', fontWeight: '500' }}>
|
||||||
|
{formatUSDC(order.size)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{order.calculatedQuantity && (
|
||||||
|
<div style={{ marginBottom: '12px' }}>
|
||||||
|
<div style={{ fontSize: '12px', color: '#666', marginBottom: '4px' }}>
|
||||||
|
{t('filteredOrdersList.calculatedQuantity') || '计算数量'}
|
||||||
|
</div>
|
||||||
|
<div style={{ fontSize: '14px', fontWeight: '500' }}>
|
||||||
|
{formatUSDC(order.calculatedQuantity)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div style={{ marginBottom: '12px' }}>
|
||||||
|
<div style={{ fontSize: '12px', color: '#666', marginBottom: '4px' }}>
|
||||||
|
{t('filteredOrdersList.filterReason') || '过滤原因'}
|
||||||
|
</div>
|
||||||
|
<div style={{ fontSize: '13px', color: '#333', wordBreak: 'break-word' }}>
|
||||||
|
{order.filterReason}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* 时间 */}
|
||||||
|
<div style={{ marginBottom: '12px' }}>
|
||||||
|
<div style={{ fontSize: '12px', color: '#999' }}>
|
||||||
|
{t('filteredOrdersList.createdAt') || '时间'}: {formattedDate}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* 移动端分页 */}
|
||||||
|
{filteredOrders.length > 0 && (
|
||||||
|
<div style={{
|
||||||
|
marginTop: '16px',
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'space-between',
|
||||||
|
alignItems: 'center',
|
||||||
|
flexWrap: 'wrap',
|
||||||
|
gap: '8px'
|
||||||
|
}}>
|
||||||
|
<div style={{ fontSize: '14px', color: '#666' }}>
|
||||||
|
{t('common.total') || '共'} {total} {t('common.items') || '条'}
|
||||||
|
</div>
|
||||||
|
<div style={{ display: 'flex', gap: '8px' }}>
|
||||||
|
<Button
|
||||||
|
size="small"
|
||||||
|
disabled={page === 1}
|
||||||
|
onClick={() => setPage(page - 1)}
|
||||||
|
>
|
||||||
|
{t('common.prev') || '上一页'}
|
||||||
|
</Button>
|
||||||
|
<span style={{ lineHeight: '32px', fontSize: '14px' }}>
|
||||||
|
{page} / {Math.ceil(total / limit)}
|
||||||
|
</span>
|
||||||
|
<Button
|
||||||
|
size="small"
|
||||||
|
disabled={page >= Math.ceil(total / limit)}
|
||||||
|
onClick={() => setPage(page + 1)}
|
||||||
|
>
|
||||||
|
{t('common.next') || '下一页'}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
// 桌面端表格布局
|
||||||
|
<Table
|
||||||
|
columns={columns}
|
||||||
|
dataSource={filteredOrders}
|
||||||
|
rowKey="id"
|
||||||
|
loading={loading}
|
||||||
|
pagination={{
|
||||||
|
current: page,
|
||||||
|
pageSize: limit,
|
||||||
|
total: total,
|
||||||
|
showSizeChanger: false,
|
||||||
|
showTotal: (total) => t('common.total') + `: ${total}`,
|
||||||
|
onChange: (page) => setPage(page)
|
||||||
|
}}
|
||||||
|
scroll={{ x: 'auto' }}
|
||||||
|
size="middle"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
import { useNavigate } from 'react-router-dom'
|
import { useNavigate } from 'react-router-dom'
|
||||||
import { Card, Form, Input, Button, Radio, InputNumber, Switch, message, Typography, Space } from 'antd'
|
import { Card, Form, Input, Button, Radio, InputNumber, Switch, message, Typography, Space, Divider } from 'antd'
|
||||||
import { ArrowLeftOutlined, SaveOutlined } from '@ant-design/icons'
|
import { ArrowLeftOutlined, SaveOutlined } from '@ant-design/icons'
|
||||||
import { apiService } from '../services/api'
|
import { apiService } from '../services/api'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
@@ -54,7 +54,9 @@ const TemplateAdd: React.FC = () => {
|
|||||||
supportSell: values.supportSell !== false,
|
supportSell: values.supportSell !== false,
|
||||||
minOrderDepth: values.minOrderDepth?.toString(),
|
minOrderDepth: values.minOrderDepth?.toString(),
|
||||||
maxSpread: values.maxSpread?.toString(),
|
maxSpread: values.maxSpread?.toString(),
|
||||||
minOrderbookDepth: values.minOrderbookDepth?.toString()
|
minOrderbookDepth: values.minOrderbookDepth?.toString(),
|
||||||
|
minPrice: values.minPrice?.toString(),
|
||||||
|
maxPrice: values.maxPrice?.toString()
|
||||||
})
|
})
|
||||||
|
|
||||||
if (response.data.code === 0) {
|
if (response.data.code === 0) {
|
||||||
@@ -295,6 +297,38 @@ const TemplateAdd: React.FC = () => {
|
|||||||
/>
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
|
<Divider>{t('templateAdd.priceRangeFilter') || '价格区间过滤'}</Divider>
|
||||||
|
|
||||||
|
<Form.Item
|
||||||
|
label={t('templateAdd.priceRange') || '价格区间'}
|
||||||
|
name="priceRange"
|
||||||
|
tooltip={t('templateAdd.priceRangeTooltip') || '配置价格区间,仅在指定价格区间内的订单才会下单。例如:0.11-0.89 表示区间在0.11和0.89之间;-0.89 表示0.89以下都可以;0.11- 表示0.11以上都可以'}
|
||||||
|
>
|
||||||
|
<Input.Group compact style={{ display: 'flex' }}>
|
||||||
|
<Form.Item name="minPrice" noStyle>
|
||||||
|
<InputNumber
|
||||||
|
min={0.01}
|
||||||
|
max={0.99}
|
||||||
|
step={0.0001}
|
||||||
|
precision={4}
|
||||||
|
style={{ width: '50%' }}
|
||||||
|
placeholder={t('templateAdd.minPricePlaceholder') || '最低价(可选)'}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
<span style={{ display: 'inline-block', width: '20px', textAlign: 'center', lineHeight: '32px' }}>-</span>
|
||||||
|
<Form.Item name="maxPrice" noStyle>
|
||||||
|
<InputNumber
|
||||||
|
min={0.01}
|
||||||
|
max={0.99}
|
||||||
|
step={0.0001}
|
||||||
|
precision={4}
|
||||||
|
style={{ width: '50%' }}
|
||||||
|
placeholder={t('templateAdd.maxPricePlaceholder') || '最高价(可选)'}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
</Input.Group>
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
{/* 跟单卖出 - 表单最底部 */}
|
{/* 跟单卖出 - 表单最底部 */}
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label={t('templateAdd.supportSell') || '跟单卖出'}
|
label={t('templateAdd.supportSell') || '跟单卖出'}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { useEffect, useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
import { useNavigate, useParams } from 'react-router-dom'
|
import { useNavigate, useParams } from 'react-router-dom'
|
||||||
import { Card, Form, Input, Button, Radio, InputNumber, Switch, message, Typography, Space } from 'antd'
|
import { Card, Form, Input, Button, Radio, InputNumber, Switch, message, Typography, Space, Divider } from 'antd'
|
||||||
import { ArrowLeftOutlined, SaveOutlined } from '@ant-design/icons'
|
import { ArrowLeftOutlined, SaveOutlined } from '@ant-design/icons'
|
||||||
import { apiService } from '../services/api'
|
import { apiService } from '../services/api'
|
||||||
import type { CopyTradingTemplate } from '../types'
|
import type { CopyTradingTemplate } from '../types'
|
||||||
@@ -40,7 +40,9 @@ const TemplateEdit: React.FC = () => {
|
|||||||
priceTolerance: parseFloat(template.priceTolerance),
|
priceTolerance: parseFloat(template.priceTolerance),
|
||||||
minOrderDepth: template.minOrderDepth ? parseFloat(template.minOrderDepth) : undefined,
|
minOrderDepth: template.minOrderDepth ? parseFloat(template.minOrderDepth) : undefined,
|
||||||
maxSpread: template.maxSpread ? parseFloat(template.maxSpread) : undefined,
|
maxSpread: template.maxSpread ? parseFloat(template.maxSpread) : undefined,
|
||||||
minOrderbookDepth: template.minOrderbookDepth ? parseFloat(template.minOrderbookDepth) : undefined
|
minOrderbookDepth: template.minOrderbookDepth ? parseFloat(template.minOrderbookDepth) : undefined,
|
||||||
|
minPrice: template.minPrice ? parseFloat(template.minPrice) : undefined,
|
||||||
|
maxPrice: template.maxPrice ? parseFloat(template.maxPrice) : undefined
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
message.error(response.data.msg || t('templateEdit.fetchFailed') || '获取模板详情失败')
|
message.error(response.data.msg || t('templateEdit.fetchFailed') || '获取模板详情失败')
|
||||||
@@ -97,7 +99,9 @@ const TemplateEdit: React.FC = () => {
|
|||||||
supportSell: values.supportSell,
|
supportSell: values.supportSell,
|
||||||
minOrderDepth: values.minOrderDepth?.toString(),
|
minOrderDepth: values.minOrderDepth?.toString(),
|
||||||
maxSpread: values.maxSpread?.toString(),
|
maxSpread: values.maxSpread?.toString(),
|
||||||
minOrderbookDepth: values.minOrderbookDepth?.toString()
|
minOrderbookDepth: values.minOrderbookDepth?.toString(),
|
||||||
|
minPrice: values.minPrice?.toString(),
|
||||||
|
maxPrice: values.maxPrice?.toString()
|
||||||
})
|
})
|
||||||
|
|
||||||
if (response.data.code === 0) {
|
if (response.data.code === 0) {
|
||||||
@@ -330,6 +334,38 @@ const TemplateEdit: React.FC = () => {
|
|||||||
/>
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
|
<Divider>{t('templateEdit.priceRangeFilter') || '价格区间过滤'}</Divider>
|
||||||
|
|
||||||
|
<Form.Item
|
||||||
|
label={t('templateEdit.priceRange') || '价格区间'}
|
||||||
|
name="priceRange"
|
||||||
|
tooltip={t('templateEdit.priceRangeTooltip') || '配置价格区间,仅在指定价格区间内的订单才会下单。例如:0.11-0.89 表示区间在0.11和0.89之间;-0.89 表示0.89以下都可以;0.11- 表示0.11以上都可以'}
|
||||||
|
>
|
||||||
|
<Input.Group compact style={{ display: 'flex' }}>
|
||||||
|
<Form.Item name="minPrice" noStyle>
|
||||||
|
<InputNumber
|
||||||
|
min={0.01}
|
||||||
|
max={0.99}
|
||||||
|
step={0.0001}
|
||||||
|
precision={4}
|
||||||
|
style={{ width: '50%' }}
|
||||||
|
placeholder={t('templateEdit.minPricePlaceholder') || '最低价(可选)'}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
<span style={{ display: 'inline-block', width: '20px', textAlign: 'center', lineHeight: '32px' }}>-</span>
|
||||||
|
<Form.Item name="maxPrice" noStyle>
|
||||||
|
<InputNumber
|
||||||
|
min={0.01}
|
||||||
|
max={0.99}
|
||||||
|
step={0.0001}
|
||||||
|
precision={4}
|
||||||
|
style={{ width: '50%' }}
|
||||||
|
placeholder={t('templateEdit.maxPricePlaceholder') || '最高价(可选)'}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
</Input.Group>
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
{/* 跟单卖出 - 表单最底部 */}
|
{/* 跟单卖出 - 表单最底部 */}
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label={t('templateEdit.supportSell') || '跟单卖出'}
|
label={t('templateEdit.supportSell') || '跟单卖出'}
|
||||||
|
|||||||
@@ -71,7 +71,12 @@ const TemplateList: React.FC = () => {
|
|||||||
minOrderSize: template.minOrderSize ? parseFloat(template.minOrderSize) : undefined,
|
minOrderSize: template.minOrderSize ? parseFloat(template.minOrderSize) : undefined,
|
||||||
maxDailyOrders: template.maxDailyOrders,
|
maxDailyOrders: template.maxDailyOrders,
|
||||||
priceTolerance: parseFloat(template.priceTolerance),
|
priceTolerance: parseFloat(template.priceTolerance),
|
||||||
supportSell: template.supportSell
|
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
|
||||||
})
|
})
|
||||||
|
|
||||||
setCopyModalVisible(true)
|
setCopyModalVisible(true)
|
||||||
@@ -114,7 +119,12 @@ const TemplateList: React.FC = () => {
|
|||||||
minOrderSize: values.copyMode === 'RATIO' ? values.minOrderSize?.toString() : undefined,
|
minOrderSize: values.copyMode === 'RATIO' ? values.minOrderSize?.toString() : undefined,
|
||||||
maxDailyOrders: values.maxDailyOrders,
|
maxDailyOrders: values.maxDailyOrders,
|
||||||
priceTolerance: values.priceTolerance?.toString(),
|
priceTolerance: values.priceTolerance?.toString(),
|
||||||
supportSell: values.supportSell !== false
|
supportSell: values.supportSell !== false,
|
||||||
|
minOrderDepth: values.minOrderDepth?.toString(),
|
||||||
|
maxSpread: values.maxSpread?.toString(),
|
||||||
|
minOrderbookDepth: values.minOrderbookDepth?.toString(),
|
||||||
|
minPrice: values.minPrice?.toString(),
|
||||||
|
maxPrice: values.maxPrice?.toString()
|
||||||
})
|
})
|
||||||
|
|
||||||
if (response.data.code === 0) {
|
if (response.data.code === 0) {
|
||||||
@@ -598,6 +608,82 @@ const TemplateList: React.FC = () => {
|
|||||||
<Switch />
|
<Switch />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
|
<Divider>过滤条件(可选)</Divider>
|
||||||
|
|
||||||
|
<Form.Item
|
||||||
|
label="最小订单深度 (USDC)"
|
||||||
|
name="minOrderDepth"
|
||||||
|
tooltip="最小订单深度(USDC金额),NULL表示不启用此过滤。确保市场有足够的流动性"
|
||||||
|
>
|
||||||
|
<InputNumber
|
||||||
|
min={0}
|
||||||
|
step={0.0001}
|
||||||
|
precision={4}
|
||||||
|
style={{ width: '100%' }}
|
||||||
|
placeholder="例如:100(可选,不填写表示不启用)"
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
|
<Form.Item
|
||||||
|
label="最大价差(绝对价格)"
|
||||||
|
name="maxSpread"
|
||||||
|
tooltip="最大价差(绝对价格),NULL表示不启用此过滤。避免在价差过大的市场跟单"
|
||||||
|
>
|
||||||
|
<InputNumber
|
||||||
|
min={0}
|
||||||
|
step={0.0001}
|
||||||
|
precision={4}
|
||||||
|
style={{ width: '100%' }}
|
||||||
|
placeholder="例如:0.05(5美分,可选,不填写表示不启用)"
|
||||||
|
/>
|
||||||
|
</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
|
||||||
|
label="价格区间"
|
||||||
|
name="priceRange"
|
||||||
|
tooltip="仅跟单 Leader 交易价格在指定区间内的订单。不填写表示不限制。示例:填写 0.11 和 0.89 表示仅跟单价格在 0.11 到 0.89 之间的订单;只填写最高价 0.89 表示仅跟单价格在 0.89 以下的订单;只填写最低价 0.11 表示仅跟单价格在 0.11 以上的订单。"
|
||||||
|
>
|
||||||
|
<Input.Group compact style={{ display: 'flex' }}>
|
||||||
|
<Form.Item name="minPrice" noStyle>
|
||||||
|
<InputNumber
|
||||||
|
min={0.01}
|
||||||
|
max={0.99}
|
||||||
|
step={0.0001}
|
||||||
|
precision={4}
|
||||||
|
style={{ width: '50%' }}
|
||||||
|
placeholder="最低价(留空不限制)"
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
<span style={{ display: 'inline-block', width: '20px', textAlign: 'center', lineHeight: '32px' }}>-</span>
|
||||||
|
<Form.Item name="maxPrice" noStyle>
|
||||||
|
<InputNumber
|
||||||
|
min={0.01}
|
||||||
|
max={0.99}
|
||||||
|
step={0.0001}
|
||||||
|
precision={4}
|
||||||
|
style={{ width: '50%' }}
|
||||||
|
placeholder="最高价(留空不限制)"
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
</Input.Group>
|
||||||
|
</Form.Item>
|
||||||
|
|
||||||
<Form.Item shouldUpdate>
|
<Form.Item shouldUpdate>
|
||||||
{({ getFieldsError }) => {
|
{({ getFieldsError }) => {
|
||||||
const errors = getFieldsError()
|
const errors = getFieldsError()
|
||||||
|
|||||||
@@ -111,6 +111,8 @@ export interface CopyTradingTemplate {
|
|||||||
minOrderDepth?: string
|
minOrderDepth?: string
|
||||||
maxSpread?: string
|
maxSpread?: string
|
||||||
minOrderbookDepth?: string
|
minOrderbookDepth?: string
|
||||||
|
minPrice?: string // 最低价格(可选),NULL表示不限制最低价
|
||||||
|
maxPrice?: string // 最高价格(可选),NULL表示不限制最高价
|
||||||
createdAt: number
|
createdAt: number
|
||||||
updatedAt: number
|
updatedAt: number
|
||||||
}
|
}
|
||||||
@@ -201,6 +203,8 @@ export interface CopyTrading {
|
|||||||
minOrderDepth?: string
|
minOrderDepth?: string
|
||||||
maxSpread?: string
|
maxSpread?: string
|
||||||
minOrderbookDepth?: string
|
minOrderbookDepth?: string
|
||||||
|
minPrice?: string // 最低价格(可选),NULL表示不限制最低价
|
||||||
|
maxPrice?: string // 最高价格(可选),NULL表示不限制最高价
|
||||||
createdAt: number
|
createdAt: number
|
||||||
updatedAt: number
|
updatedAt: number
|
||||||
}
|
}
|
||||||
@@ -240,6 +244,8 @@ export interface CopyTradingCreateRequest {
|
|||||||
minOrderDepth?: string
|
minOrderDepth?: string
|
||||||
maxSpread?: string
|
maxSpread?: string
|
||||||
minOrderbookDepth?: string
|
minOrderbookDepth?: string
|
||||||
|
minPrice?: string // 最低价格(可选),NULL表示不限制最低价
|
||||||
|
maxPrice?: string // 最高价格(可选),NULL表示不限制最高价
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -267,6 +273,8 @@ export interface CopyTradingUpdateRequest {
|
|||||||
minOrderDepth?: string
|
minOrderDepth?: string
|
||||||
maxSpread?: string
|
maxSpread?: string
|
||||||
minOrderbookDepth?: string
|
minOrderbookDepth?: string
|
||||||
|
minPrice?: string // 最低价格(可选),NULL表示不限制最低价
|
||||||
|
maxPrice?: string // 最高价格(可选),NULL表示不限制最高价
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -570,8 +578,6 @@ export interface CopyTradingStatistics {
|
|||||||
accountName: string | null
|
accountName: string | null
|
||||||
leaderId: number
|
leaderId: number
|
||||||
leaderName: string | null
|
leaderName: string | null
|
||||||
templateId: number
|
|
||||||
templateName: string | null
|
|
||||||
enabled: boolean
|
enabled: boolean
|
||||||
|
|
||||||
// 买入统计
|
// 买入统计
|
||||||
@@ -587,7 +593,7 @@ export interface CopyTradingStatistics {
|
|||||||
|
|
||||||
// 持仓统计
|
// 持仓统计
|
||||||
currentPositionQuantity: string
|
currentPositionQuantity: string
|
||||||
currentPositionValue: string
|
currentPositionValue: string // 当前实现总是返回 "0",保留用于未来扩展
|
||||||
|
|
||||||
// 盈亏统计
|
// 盈亏统计
|
||||||
totalRealizedPnl: string
|
totalRealizedPnl: string
|
||||||
|
|||||||
Reference in New Issue
Block a user