Enforce minimum scan interval of three minutes

This commit is contained in:
simon
2025-11-05 11:50:46 +08:00
parent e832bada8e
commit fc63fe1835
3 changed files with 35 additions and 34 deletions

View File

@@ -88,7 +88,7 @@ func (s *Server) setupRoutes() {
// 系统提示词模板管理(无需认证) // 系统提示词模板管理(无需认证)
api.GET("/prompt-templates", s.handleGetPromptTemplates) api.GET("/prompt-templates", s.handleGetPromptTemplates)
api.GET("/prompt-templates/:name", s.handleGetPromptTemplate) api.GET("/prompt-templates/:name", s.handleGetPromptTemplate)
// 公开的竞赛数据(无需认证) // 公开的竞赛数据(无需认证)
api.GET("/traders", s.handlePublicTraderList) api.GET("/traders", s.handlePublicTraderList)
api.GET("/competition", s.handlePublicCompetition) api.GET("/competition", s.handlePublicCompetition)
@@ -168,7 +168,7 @@ func (s *Server) handleGetSystemConfig(c *gin.Context) {
if val, err := strconv.Atoi(altcoinLeverageStr); err == nil && val > 0 { if val, err := strconv.Atoi(altcoinLeverageStr); err == nil && val > 0 {
altcoinLeverage = val altcoinLeverage = val
} }
// 获取内测模式配置 // 获取内测模式配置
betaModeStr, _ := s.database.GetSystemConfig("beta_mode") betaModeStr, _ := s.database.GetSystemConfig("beta_mode")
betaMode := betaModeStr == "true" betaMode := betaModeStr == "true"
@@ -343,8 +343,8 @@ func (s *Server) handleCreateTrader(c *gin.Context) {
// 设置扫描间隔默认值 // 设置扫描间隔默认值
scanIntervalMinutes := req.ScanIntervalMinutes scanIntervalMinutes := req.ScanIntervalMinutes
if scanIntervalMinutes <= 0 { if scanIntervalMinutes < 3 {
scanIntervalMinutes = 3 // 默认3分钟 scanIntervalMinutes = 3 // 默认3分钟且不允许小于3
} }
// 创建交易员配置(数据库实体) // 创建交易员配置(数据库实体)
@@ -458,6 +458,8 @@ func (s *Server) handleUpdateTrader(c *gin.Context) {
scanIntervalMinutes := req.ScanIntervalMinutes scanIntervalMinutes := req.ScanIntervalMinutes
if scanIntervalMinutes <= 0 { if scanIntervalMinutes <= 0 {
scanIntervalMinutes = existingTrader.ScanIntervalMinutes // 保持原值 scanIntervalMinutes = existingTrader.ScanIntervalMinutes // 保持原值
} else if scanIntervalMinutes < 3 {
scanIntervalMinutes = 3
} }
// 更新交易员配置 // 更新交易员配置
@@ -531,14 +533,14 @@ func (s *Server) handleDeleteTrader(c *gin.Context) {
func (s *Server) handleStartTrader(c *gin.Context) { func (s *Server) handleStartTrader(c *gin.Context) {
userID := c.GetString("user_id") userID := c.GetString("user_id")
traderID := c.Param("id") traderID := c.Param("id")
// 校验交易员是否属于当前用户 // 校验交易员是否属于当前用户
_, _, _, err := s.database.GetTraderConfig(userID, traderID) _, _, _, err := s.database.GetTraderConfig(userID, traderID)
if err != nil { if err != nil {
c.JSON(http.StatusNotFound, gin.H{"error": "交易员不存在或无访问权限"}) c.JSON(http.StatusNotFound, gin.H{"error": "交易员不存在或无访问权限"})
return return
} }
trader, err := s.traderManager.GetTrader(traderID) trader, err := s.traderManager.GetTrader(traderID)
if err != nil { if err != nil {
c.JSON(http.StatusNotFound, gin.H{"error": "交易员不存在"}) c.JSON(http.StatusNotFound, gin.H{"error": "交易员不存在"})
@@ -574,14 +576,14 @@ func (s *Server) handleStartTrader(c *gin.Context) {
func (s *Server) handleStopTrader(c *gin.Context) { func (s *Server) handleStopTrader(c *gin.Context) {
userID := c.GetString("user_id") userID := c.GetString("user_id")
traderID := c.Param("id") traderID := c.Param("id")
// 校验交易员是否属于当前用户 // 校验交易员是否属于当前用户
_, _, _, err := s.database.GetTraderConfig(userID, traderID) _, _, _, err := s.database.GetTraderConfig(userID, traderID)
if err != nil { if err != nil {
c.JSON(http.StatusNotFound, gin.H{"error": "交易员不存在或无访问权限"}) c.JSON(http.StatusNotFound, gin.H{"error": "交易员不存在或无访问权限"})
return return
} }
trader, err := s.traderManager.GetTrader(traderID) trader, err := s.traderManager.GetTrader(traderID)
if err != nil { if err != nil {
c.JSON(http.StatusNotFound, gin.H{"error": "交易员不存在"}) c.JSON(http.StatusNotFound, gin.H{"error": "交易员不存在"})
@@ -1581,7 +1583,7 @@ func (s *Server) handlePublicCompetition(c *gin.Context) {
}) })
return return
} }
c.JSON(http.StatusOK, competition) c.JSON(http.StatusOK, competition)
} }
@@ -1594,7 +1596,7 @@ func (s *Server) handleTopTraders(c *gin.Context) {
}) })
return return
} }
c.JSON(http.StatusOK, topTraders) c.JSON(http.StatusOK, topTraders)
} }
@@ -1603,7 +1605,7 @@ func (s *Server) handleEquityHistoryBatch(c *gin.Context) {
var requestBody struct { var requestBody struct {
TraderIDs []string `json:"trader_ids"` TraderIDs []string `json:"trader_ids"`
} }
// 尝试解析POST请求的JSON body // 尝试解析POST请求的JSON body
if err := c.ShouldBindJSON(&requestBody); err != nil { if err := c.ShouldBindJSON(&requestBody); err != nil {
// 如果JSON解析失败尝试从query参数获取兼容GET请求 // 如果JSON解析失败尝试从query参数获取兼容GET请求
@@ -1617,13 +1619,13 @@ func (s *Server) handleEquityHistoryBatch(c *gin.Context) {
}) })
return return
} }
traders, ok := topTraders["traders"].([]map[string]interface{}) traders, ok := topTraders["traders"].([]map[string]interface{})
if !ok { if !ok {
c.JSON(http.StatusInternalServerError, gin.H{"error": "交易员数据格式错误"}) c.JSON(http.StatusInternalServerError, gin.H{"error": "交易员数据格式错误"})
return return
} }
// 提取trader IDs // 提取trader IDs
traderIDs := make([]string, 0, len(traders)) traderIDs := make([]string, 0, len(traders))
for _, trader := range traders { for _, trader := range traders {
@@ -1631,24 +1633,24 @@ func (s *Server) handleEquityHistoryBatch(c *gin.Context) {
traderIDs = append(traderIDs, traderID) traderIDs = append(traderIDs, traderID)
} }
} }
result := s.getEquityHistoryForTraders(traderIDs) result := s.getEquityHistoryForTraders(traderIDs)
c.JSON(http.StatusOK, result) c.JSON(http.StatusOK, result)
return return
} }
// 解析逗号分隔的trader IDs // 解析逗号分隔的trader IDs
requestBody.TraderIDs = strings.Split(traderIDsParam, ",") requestBody.TraderIDs = strings.Split(traderIDsParam, ",")
for i := range requestBody.TraderIDs { for i := range requestBody.TraderIDs {
requestBody.TraderIDs[i] = strings.TrimSpace(requestBody.TraderIDs[i]) requestBody.TraderIDs[i] = strings.TrimSpace(requestBody.TraderIDs[i])
} }
} }
// 限制最多20个交易员防止请求过大 // 限制最多20个交易员防止请求过大
if len(requestBody.TraderIDs) > 20 { if len(requestBody.TraderIDs) > 20 {
requestBody.TraderIDs = requestBody.TraderIDs[:20] requestBody.TraderIDs = requestBody.TraderIDs[:20]
} }
result := s.getEquityHistoryForTraders(requestBody.TraderIDs) result := s.getEquityHistoryForTraders(requestBody.TraderIDs)
c.JSON(http.StatusOK, result) c.JSON(http.StatusOK, result)
} }
@@ -1658,31 +1660,31 @@ func (s *Server) getEquityHistoryForTraders(traderIDs []string) map[string]inter
result := make(map[string]interface{}) result := make(map[string]interface{})
histories := make(map[string]interface{}) histories := make(map[string]interface{})
errors := make(map[string]string) errors := make(map[string]string)
for _, traderID := range traderIDs { for _, traderID := range traderIDs {
if traderID == "" { if traderID == "" {
continue continue
} }
trader, err := s.traderManager.GetTrader(traderID) trader, err := s.traderManager.GetTrader(traderID)
if err != nil { if err != nil {
errors[traderID] = "交易员不存在" errors[traderID] = "交易员不存在"
continue continue
} }
// 获取历史数据(用于对比展示,限制数据量) // 获取历史数据(用于对比展示,限制数据量)
records, err := trader.GetDecisionLogger().GetLatestRecords(500) records, err := trader.GetDecisionLogger().GetLatestRecords(500)
if err != nil { if err != nil {
errors[traderID] = fmt.Sprintf("获取历史数据失败: %v", err) errors[traderID] = fmt.Sprintf("获取历史数据失败: %v", err)
continue continue
} }
// 构建收益率历史数据 // 构建收益率历史数据
history := make([]map[string]interface{}, 0, len(records)) history := make([]map[string]interface{}, 0, len(records))
for _, record := range records { for _, record := range records {
// 计算总权益(余额+未实现盈亏) // 计算总权益(余额+未实现盈亏)
totalEquity := record.AccountState.TotalBalance + record.AccountState.TotalUnrealizedProfit totalEquity := record.AccountState.TotalBalance + record.AccountState.TotalUnrealizedProfit
history = append(history, map[string]interface{}{ history = append(history, map[string]interface{}{
"timestamp": record.Timestamp, "timestamp": record.Timestamp,
"total_equity": totalEquity, "total_equity": totalEquity,
@@ -1690,16 +1692,16 @@ func (s *Server) getEquityHistoryForTraders(traderIDs []string) map[string]inter
"balance": record.AccountState.TotalBalance, "balance": record.AccountState.TotalBalance,
}) })
} }
histories[traderID] = history histories[traderID] = history
} }
result["histories"] = histories result["histories"] = histories
result["count"] = len(histories) result["count"] = len(histories)
if len(errors) > 0 { if len(errors) > 0 {
result["errors"] = errors result["errors"] = errors
} }
return result return result
} }
@@ -1733,4 +1735,3 @@ func (s *Server) handleGetPublicTraderConfig(c *gin.Context) {
c.JSON(http.StatusOK, result) c.JSON(http.StatusOK, result)
} }

1
web/package-lock.json generated
View File

@@ -7177,7 +7177,6 @@
"integrity": "sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw==", "integrity": "sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw==",
"dev": true, "dev": true,
"license": "ISC", "license": "ISC",
"peer": true,
"bin": { "bin": {
"yaml": "bin.mjs" "yaml": "bin.mjs"
}, },

View File

@@ -374,14 +374,15 @@ export function TraderConfigModal({
<input <input
type="number" type="number"
value={formData.scan_interval_minutes} value={formData.scan_interval_minutes}
onChange={(e) => onChange={(e) => {
handleInputChange( const parsedValue = Number(e.target.value)
'scan_interval_minutes', const safeValue = Number.isFinite(parsedValue)
Number(e.target.value) ? Math.max(3, parsedValue)
) : 3
} handleInputChange('scan_interval_minutes', safeValue)
}}
className="w-full px-3 py-2 bg-[#0B0E11] border border-[#2B3139] rounded text-[#EAECEF] focus:border-[#F0B90B] focus:outline-none" className="w-full px-3 py-2 bg-[#0B0E11] border border-[#2B3139] rounded text-[#EAECEF] focus:border-[#F0B90B] focus:outline-none"
min="1" min="3"
max="60" max="60"
step="1" step="1"
/> />