210 lines
6.4 KiB
Go
210 lines
6.4 KiB
Go
package api
|
|
|
|
import (
|
|
"errors"
|
|
"net/http"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"level_go/internal/model"
|
|
"level_go/internal/service"
|
|
)
|
|
|
|
// LimitBoardHandler 承载涨停板相关 HTTP 接口。
|
|
type LimitBoardHandler struct {
|
|
Service *service.LimitBoardService // 涨停板服务,封装统计、分组和导出逻辑。
|
|
}
|
|
|
|
// NewLimitBoardHandler 创建涨停板接口 Handler。
|
|
func NewLimitBoardHandler(s *service.LimitBoardService) *LimitBoardHandler {
|
|
return &LimitBoardHandler{Service: s}
|
|
}
|
|
|
|
// GetOverview 返回指定交易日的涨停股连板概览。
|
|
func (h *LimitBoardHandler) GetOverview(c *gin.Context) {
|
|
var req model.LimitBoardRequest
|
|
if err := c.ShouldBindQuery(&req); err != nil || req.TradeDate == "" {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "trade_date 参数不能为空"})
|
|
return
|
|
}
|
|
|
|
resp, err := h.Service.GetLimitBoardOverview(req.TradeDate)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, resp)
|
|
}
|
|
|
|
// GetOpenLowerGroups 返回昨日涨停且今日继续涨停股票的开盘对比分组。
|
|
func (h *LimitBoardHandler) GetOpenLowerGroups(c *gin.Context) {
|
|
var req model.LimitBoardRequest
|
|
if err := c.ShouldBindQuery(&req); err != nil || req.TradeDate == "" {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "trade_date 参数不能为空"})
|
|
return
|
|
}
|
|
|
|
resp, err := h.Service.GetLimitOpenCompareGroups(req.TradeDate)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, resp)
|
|
}
|
|
|
|
// GetBreakNextPerformance 返回首板后次日炸板股票的 T+1 表现分页结果。
|
|
func (h *LimitBoardHandler) GetBreakNextPerformance(c *gin.Context) {
|
|
req, err := parseLimitBreakNextPerformanceRequest(c, true)
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
resp, err := h.Service.GetLimitBreakNextPerformance(req)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, resp)
|
|
}
|
|
|
|
// GetSecondCal 返回日期范围内的二板日历统计。
|
|
func (h *LimitBoardHandler) GetSecondCal(c *gin.Context) {
|
|
req, err := parseSecondCalRequest(c)
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
resp, err := h.Service.GetSecondCal(req)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, resp)
|
|
}
|
|
|
|
// parseSecondCalRequest 解析二板日历接口的日期范围参数。
|
|
func parseSecondCalRequest(c *gin.Context) (model.SecondCalRequest, error) {
|
|
req := model.SecondCalRequest{
|
|
TradeStartDate: strings.TrimSpace(c.DefaultQuery("tradeStartDate", c.Query("start_date"))),
|
|
TradeEndDate: strings.TrimSpace(c.DefaultQuery("tradeEndDate", c.Query("end_date"))),
|
|
}
|
|
if req.TradeStartDate == "" {
|
|
return req, errors.New("tradeStartDate 参数不能为空")
|
|
}
|
|
if req.TradeEndDate == "" {
|
|
return req, errors.New("tradeEndDate 参数不能为空")
|
|
}
|
|
if err := validateSecondProfitDateRange(req.TradeStartDate, req.TradeEndDate); err != nil {
|
|
return req, err
|
|
}
|
|
return req, nil
|
|
}
|
|
|
|
// parseLimitBreakNextPerformanceRequest 解析炸板次日表现接口的查询参数。
|
|
//
|
|
// includePagination 为 false 时跳过分页参数,供 CSV 导出复用同一套日期和过滤条件解析逻辑。
|
|
func parseLimitBreakNextPerformanceRequest(c *gin.Context, includePagination bool) (model.LimitBreakNextPerformanceRequest, error) {
|
|
req := model.LimitBreakNextPerformanceRequest{
|
|
TradeStartDate: firstNonEmptyQuery(c, "tradeStartDate", "trade_start_date", "start_date"),
|
|
TradeEndDate: firstNonEmptyQuery(c, "tradeEndDate", "trade_end_date", "end_date"),
|
|
Page: 1,
|
|
PageSize: 20,
|
|
}
|
|
|
|
if req.TradeStartDate == "" {
|
|
return req, errors.New("tradeStartDate 参数不能为空")
|
|
}
|
|
if req.TradeEndDate == "" {
|
|
return req, errors.New("tradeEndDate 参数不能为空")
|
|
}
|
|
if err := validateSecondProfitDateRange(req.TradeStartDate, req.TradeEndDate); err != nil {
|
|
return req, err
|
|
}
|
|
|
|
if includePagination {
|
|
pageText := strings.TrimSpace(c.DefaultQuery("page", "1"))
|
|
page, err := strconv.Atoi(pageText)
|
|
if err != nil || page <= 0 {
|
|
return req, errors.New("page 必须为正整数")
|
|
}
|
|
req.Page = page
|
|
|
|
pageSizeText := strings.TrimSpace(c.DefaultQuery("pageSize", "20"))
|
|
pageSize, err := strconv.Atoi(pageSizeText)
|
|
if err != nil || pageSize <= 0 {
|
|
return req, errors.New("pageSize 必须为正整数")
|
|
}
|
|
req.PageSize = pageSize
|
|
}
|
|
|
|
tOpenGainLt101, err := parseOptionalBoolQuery(c, "tOpenGainLt101", "tOpenLessThan101", "t_open_gain_lt_101")
|
|
if err != nil {
|
|
return req, errors.New("tOpenGainLt101 必须为布尔值")
|
|
}
|
|
req.TOpenGainLt101 = tOpenGainLt101
|
|
|
|
breakTimeBefore1100, err := parseOptionalBoolQuery(c, "breakTimeBefore1100", "tBreakTimeBefore1100", "break_time_before_1100")
|
|
if err != nil {
|
|
return req, errors.New("breakTimeBefore1100 必须为布尔值")
|
|
}
|
|
req.BreakTimeBefore1100 = breakTimeBefore1100
|
|
|
|
return req, nil
|
|
}
|
|
|
|
// firstNonEmptyQuery 按优先级读取第一个非空查询参数。
|
|
func firstNonEmptyQuery(c *gin.Context, keys ...string) string {
|
|
for _, key := range keys {
|
|
if value := strings.TrimSpace(c.Query(key)); value != "" {
|
|
return value
|
|
}
|
|
}
|
|
return ""
|
|
}
|
|
|
|
// parseOptionalBoolQuery 从多个兼容参数名中读取可选布尔值。
|
|
//
|
|
// 返回 nil 表示用户没有传入该过滤项;返回 *bool 表示需要按 true/false 过滤。
|
|
func parseOptionalBoolQuery(c *gin.Context, keys ...string) (*bool, error) {
|
|
for _, key := range keys {
|
|
valueText, exists := c.GetQuery(key)
|
|
if !exists {
|
|
continue
|
|
}
|
|
valueText = strings.TrimSpace(valueText)
|
|
if valueText == "" {
|
|
continue
|
|
}
|
|
value, err := strconv.ParseBool(valueText)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &value, nil
|
|
}
|
|
return nil, nil
|
|
}
|
|
|
|
// GetOpenThresholdGroups 返回昨日涨停股今日开盘是否低于前收 101% 的分组。
|
|
func (h *LimitBoardHandler) GetOpenThresholdGroups(c *gin.Context) {
|
|
var req model.LimitBoardRequest
|
|
if err := c.ShouldBindQuery(&req); err != nil || req.TradeDate == "" {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "trade_date 参数不能为空"})
|
|
return
|
|
}
|
|
|
|
resp, err := h.Service.GetLimitOpenThresholdGroups(req.TradeDate)
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, resp)
|
|
}
|