新增注释
This commit is contained in:
@@ -6,10 +6,12 @@ import (
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// CommonHandler 承载通用基础数据接口。
|
||||
type CommonHandler struct {
|
||||
Service *service.CommonService
|
||||
Service *service.CommonService // 通用数据服务,负责访问交易日历等基础表。
|
||||
}
|
||||
|
||||
// GetTradeCalendar 返回上交所开市日期列表和最新开市日期。
|
||||
func (h *CommonHandler) GetTradeCalendar(c *gin.Context) {
|
||||
resp, err := h.Service.GetTradeCalendar()
|
||||
if err != nil {
|
||||
|
||||
@@ -11,6 +11,9 @@ import (
|
||||
"level_go/internal/model"
|
||||
)
|
||||
|
||||
// DownloadBreakNextPerformanceCSV 导出炸板股 T+1 表现明细 CSV。
|
||||
//
|
||||
// 查询参数与 GetBreakNextPerformance 共用解析逻辑,但导出时不分页,直接输出全部命中记录。
|
||||
func (h *LimitBoardHandler) DownloadBreakNextPerformanceCSV(c *gin.Context) {
|
||||
req, err := parseLimitBreakNextPerformanceRequest(c, false)
|
||||
if err != nil {
|
||||
@@ -47,6 +50,7 @@ func (h *LimitBoardHandler) DownloadBreakNextPerformanceCSV(c *gin.Context) {
|
||||
}
|
||||
}
|
||||
|
||||
// breakNextPerformanceCSVHeaders 返回 CSV 表头,顺序必须与 breakNextPerformanceCSVRecord 保持一致。
|
||||
func breakNextPerformanceCSVHeaders() []string {
|
||||
return []string{
|
||||
"T日",
|
||||
@@ -75,6 +79,7 @@ func breakNextPerformanceCSVHeaders() []string {
|
||||
}
|
||||
}
|
||||
|
||||
// breakNextPerformanceCSVRecord 把单条炸板表现记录转换为 CSV 字段。
|
||||
func breakNextPerformanceCSVRecord(row model.LimitBreakNextPerformanceStock) []string {
|
||||
return []string{
|
||||
row.TradeDate,
|
||||
@@ -103,6 +108,7 @@ func breakNextPerformanceCSVRecord(row model.LimitBreakNextPerformanceStock) []s
|
||||
}
|
||||
}
|
||||
|
||||
// breakNextPerformanceFormatFloatPtr 格式化可空浮点数,nil 输出为空字段。
|
||||
func breakNextPerformanceFormatFloatPtr(value *float64) string {
|
||||
if value == nil {
|
||||
return ""
|
||||
@@ -110,6 +116,7 @@ func breakNextPerformanceFormatFloatPtr(value *float64) string {
|
||||
return strconv.FormatFloat(*value, 'f', -1, 64)
|
||||
}
|
||||
|
||||
// breakNextPerformanceBoolLabel 把布尔值转换为中文是否标签。
|
||||
func breakNextPerformanceBoolLabel(value bool) string {
|
||||
if value {
|
||||
return "是"
|
||||
@@ -117,6 +124,7 @@ func breakNextPerformanceBoolLabel(value bool) string {
|
||||
return "否"
|
||||
}
|
||||
|
||||
// breakNextPerformanceBoolPtrLabel 格式化可空布尔值,用于区分无次日行情和明确的是/否。
|
||||
func breakNextPerformanceBoolPtrLabel(value *bool) string {
|
||||
if value == nil {
|
||||
return "无次日数据"
|
||||
|
||||
@@ -11,14 +11,17 @@ import (
|
||||
"level_go/internal/service"
|
||||
)
|
||||
|
||||
// LimitBoardHandler 承载涨停板相关 HTTP 接口。
|
||||
type LimitBoardHandler struct {
|
||||
Service *service.LimitBoardService
|
||||
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 == "" {
|
||||
@@ -35,6 +38,7 @@ func (h *LimitBoardHandler) GetOverview(c *gin.Context) {
|
||||
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 == "" {
|
||||
@@ -51,6 +55,7 @@ func (h *LimitBoardHandler) GetOpenLowerGroups(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, resp)
|
||||
}
|
||||
|
||||
// GetBreakNextPerformance 返回首板后次日炸板股票的 T+1 表现分页结果。
|
||||
func (h *LimitBoardHandler) GetBreakNextPerformance(c *gin.Context) {
|
||||
req, err := parseLimitBreakNextPerformanceRequest(c, true)
|
||||
if err != nil {
|
||||
@@ -67,6 +72,9 @@ func (h *LimitBoardHandler) GetBreakNextPerformance(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, resp)
|
||||
}
|
||||
|
||||
// parseLimitBreakNextPerformanceRequest 解析炸板次日表现接口的查询参数。
|
||||
//
|
||||
// includePagination 为 false 时跳过分页参数,供 CSV 导出复用同一套日期和过滤条件解析逻辑。
|
||||
func parseLimitBreakNextPerformanceRequest(c *gin.Context, includePagination bool) (model.LimitBreakNextPerformanceRequest, error) {
|
||||
req := model.LimitBreakNextPerformanceRequest{
|
||||
TradeStartDate: strings.TrimSpace(c.DefaultQuery("tradeStartDate", c.Query("start_date"))),
|
||||
@@ -116,6 +124,9 @@ func parseLimitBreakNextPerformanceRequest(c *gin.Context, includePagination boo
|
||||
return req, nil
|
||||
}
|
||||
|
||||
// parseOptionalBoolQuery 从多个兼容参数名中读取可选布尔值。
|
||||
//
|
||||
// 返回 nil 表示用户没有传入该过滤项;返回 *bool 表示需要按 true/false 过滤。
|
||||
func parseOptionalBoolQuery(c *gin.Context, keys ...string) (*bool, error) {
|
||||
for _, key := range keys {
|
||||
valueText, exists := c.GetQuery(key)
|
||||
@@ -135,6 +146,7 @@ func parseOptionalBoolQuery(c *gin.Context, keys ...string) (*bool, error) {
|
||||
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 == "" {
|
||||
|
||||
@@ -14,10 +14,15 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
// defaultSecondProfitStartDate 是二板收益统计的默认起始日期。
|
||||
defaultSecondProfitStartDate = "2020-01-01"
|
||||
defaultSecondProfitEndDate = "2026-12-31"
|
||||
// defaultSecondProfitEndDate 是二板收益统计的默认结束日期。
|
||||
defaultSecondProfitEndDate = "2026-12-31"
|
||||
)
|
||||
|
||||
// secondProfitCSVRow 是二板收益查询的内部行结构。
|
||||
//
|
||||
// 前半部分字段直接由 SQL 扫描得到,后半部分字段在 loadSecondProfitRows 中根据行情计算。
|
||||
type secondProfitCSVRow struct {
|
||||
TradeDate string `gorm:"column:trade_date"`
|
||||
PreviousTradeDate string `gorm:"column:previous_trade_date"`
|
||||
@@ -57,6 +62,7 @@ type secondProfitCSVRow struct {
|
||||
NextIntradayProfitable string
|
||||
}
|
||||
|
||||
// secondProfitQuery 保存二板收益接口和 CSV 导出共用的查询条件。
|
||||
type secondProfitQuery struct {
|
||||
TradeStartDate string
|
||||
TradeEndDate string
|
||||
@@ -74,15 +80,20 @@ type secondProfitQuery struct {
|
||||
PageSize int
|
||||
}
|
||||
|
||||
// secondProfitCompareFilter 表示多个比较条件的组合过滤器。
|
||||
//
|
||||
// 当前逻辑使用 AND 关系:所有 Conditions 均满足时才认为匹配。
|
||||
type secondProfitCompareFilter struct {
|
||||
Conditions []secondProfitCompareCondition
|
||||
}
|
||||
|
||||
// secondProfitCompareCondition 表示一个形如 value < 10 或 value >= 09:30 的比较条件。
|
||||
type secondProfitCompareCondition struct {
|
||||
Op string
|
||||
Value float64
|
||||
Op string // 比较操作符,支持 <、>、<=、>=、=。
|
||||
Value float64 // 参与比较的标准化数值;时间会转换为秒数。
|
||||
}
|
||||
|
||||
// secondProfitListItem 是二板收益列表接口返回给前端的单行数据。
|
||||
type secondProfitListItem struct {
|
||||
TradeDate string `json:"trade_date"`
|
||||
TsCode string `json:"ts_code"`
|
||||
@@ -104,6 +115,9 @@ type secondProfitListItem struct {
|
||||
Amount *float64 `json:"amount"`
|
||||
}
|
||||
|
||||
// ListSecondBoardProfit 返回二板收益明细的分页 JSON 数据。
|
||||
//
|
||||
// 与 CSV 导出使用同一套查询和筛选逻辑,区别是这里会按 page/pageSize 截取结果。
|
||||
func (h *LimitBoardHandler) ListSecondBoardProfit(c *gin.Context) {
|
||||
req, err := parseSecondProfitQuery(c, true)
|
||||
if err != nil {
|
||||
@@ -156,6 +170,9 @@ func (h *LimitBoardHandler) ListSecondBoardProfit(c *gin.Context) {
|
||||
})
|
||||
}
|
||||
|
||||
// DownloadSecondBoardProfitCSV 导出二板收益明细 CSV。
|
||||
//
|
||||
// 输出 UTF-8 BOM,便于 Excel 直接打开中文表头和内容。
|
||||
func (h *LimitBoardHandler) DownloadSecondBoardProfitCSV(c *gin.Context) {
|
||||
req, err := parseSecondProfitQuery(c, false)
|
||||
if err != nil {
|
||||
@@ -192,6 +209,9 @@ func (h *LimitBoardHandler) DownloadSecondBoardProfitCSV(c *gin.Context) {
|
||||
}
|
||||
}
|
||||
|
||||
// parseSecondProfitQuery 解析二板收益接口查询参数。
|
||||
//
|
||||
// includePagination 为 false 时跳过分页解析,供 CSV 导出复用筛选条件。
|
||||
func parseSecondProfitQuery(c *gin.Context, includePagination bool) (secondProfitQuery, error) {
|
||||
startDate := c.DefaultQuery("tradeStartDate", c.DefaultQuery("start_date", defaultSecondProfitStartDate))
|
||||
endDate := c.DefaultQuery("tradeEndDate", c.DefaultQuery("end_date", defaultSecondProfitEndDate))
|
||||
@@ -261,6 +281,9 @@ func parseSecondProfitQuery(c *gin.Context, includePagination bool) (secondProfi
|
||||
return req, nil
|
||||
}
|
||||
|
||||
// secondProfitFilterParts 兼容两种过滤参数格式。
|
||||
//
|
||||
// 既支持 limitUpTimeFilterType1/Value1 这种具名参数,也支持 limitUpTimeFilter[]=... 数组格式。
|
||||
func secondProfitFilterParts(c *gin.Context, base string) (string, string, string, string) {
|
||||
type1 := strings.TrimSpace(c.Query(base + "Type1"))
|
||||
value1 := strings.TrimSpace(c.Query(base + "Value1"))
|
||||
@@ -284,6 +307,7 @@ func secondProfitFilterParts(c *gin.Context, base string) (string, string, strin
|
||||
return "", "", "", ""
|
||||
}
|
||||
|
||||
// secondProfitFilterArrayValue 安全读取数组参数中的指定位置。
|
||||
func secondProfitFilterArrayValue(values []string, index int) string {
|
||||
if index >= len(values) {
|
||||
return ""
|
||||
@@ -291,6 +315,7 @@ func secondProfitFilterArrayValue(values []string, index int) string {
|
||||
return strings.TrimSpace(values[index])
|
||||
}
|
||||
|
||||
// validateSecondProfitDateRange 校验查询日期格式和先后顺序。
|
||||
func validateSecondProfitDateRange(startDate, endDate string) error {
|
||||
start, err := time.Parse("2006-01-02", startDate)
|
||||
if err != nil {
|
||||
@@ -306,6 +331,10 @@ func validateSecondProfitDateRange(startDate, endDate string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// loadSecondProfitRows 查询并计算二板收益明细。
|
||||
//
|
||||
// SQL 先找出“前一日涨停、前两日未涨停、当日继续涨停”的股票,再补齐 T+1 行情;
|
||||
// Go 代码负责应用动态过滤条件并计算开盘、最高、最低、收盘相对 T 日收盘的收益率。
|
||||
func (h *LimitBoardHandler) loadSecondProfitRows(req secondProfitQuery) ([]secondProfitCSVRow, error) {
|
||||
query := `
|
||||
WITH open_calendar AS (
|
||||
@@ -372,6 +401,7 @@ ORDER BY c.trade_date ASC, l.ts_code ASC`
|
||||
|
||||
rows := make([]secondProfitCSVRow, 0, len(rawRows))
|
||||
for _, row := range rawRows {
|
||||
// 首次涨停时间过滤在内存中执行,便于兼容 0930、93000、09:30:00 等多种时间格式。
|
||||
if req.LimitUpTimeRule.Enabled() {
|
||||
limitTimeValue, ok := secondProfitTimeSeconds(row.FirstLimitTime.String)
|
||||
if !ok || !req.LimitUpTimeRule.Match(float64(limitTimeValue)) {
|
||||
@@ -379,6 +409,7 @@ ORDER BY c.trade_date ASC, l.ts_code ASC`
|
||||
}
|
||||
}
|
||||
|
||||
// 开盘涨跌幅依赖前收价格,只有行情完整且前收大于 0 时才计算。
|
||||
if row.Open.Valid && row.PreviousClose.Valid && row.PreviousClose.Float64 > 0 {
|
||||
row.OpenPreviousCloseRatio = row.Open.Float64 / row.PreviousClose.Float64
|
||||
row.OpenPctChg = sql.NullFloat64{
|
||||
@@ -396,6 +427,7 @@ ORDER BY c.trade_date ASC, l.ts_code ASC`
|
||||
continue
|
||||
}
|
||||
|
||||
// T+1 收益统一以 T 日收盘价为基准,缺失的次日行情会保留为 NullFloat64。
|
||||
if row.Close.Valid && row.Close.Float64 > 0 {
|
||||
row.NextOpenReturnPct = secondProfitPctReturn(row.NextOpen, row.Close.Float64)
|
||||
row.NextHighReturnPct = secondProfitPctReturn(row.NextHigh, row.Close.Float64)
|
||||
@@ -410,6 +442,7 @@ ORDER BY c.trade_date ASC, l.ts_code ASC`
|
||||
return rows, nil
|
||||
}
|
||||
|
||||
// secondProfitCSVHeaders 返回二板收益 CSV 表头。
|
||||
func secondProfitCSVHeaders() []string {
|
||||
return []string{
|
||||
"交易日", "股票代码", "股票简称", "市场", "行业",
|
||||
@@ -421,6 +454,7 @@ func secondProfitCSVHeaders() []string {
|
||||
}
|
||||
}
|
||||
|
||||
// secondProfitCSVRecord 把内部行结构转换为 CSV 字段。
|
||||
func secondProfitCSVRecord(row secondProfitCSVRow) []string {
|
||||
return []string{
|
||||
row.TradeDate,
|
||||
@@ -444,6 +478,7 @@ func secondProfitCSVRecord(row secondProfitCSVRow) []string {
|
||||
}
|
||||
}
|
||||
|
||||
// secondProfitBuildItem 把内部行结构转换为 JSON 列表项。
|
||||
func secondProfitBuildItem(row secondProfitCSVRow) secondProfitListItem {
|
||||
return secondProfitListItem{
|
||||
TradeDate: row.TradeDate,
|
||||
@@ -467,6 +502,7 @@ func secondProfitBuildItem(row secondProfitCSVRow) secondProfitListItem {
|
||||
}
|
||||
}
|
||||
|
||||
// parseSecondProfitCompareFilter 根据两组操作符和值构造比较过滤器。
|
||||
func parseSecondProfitCompareFilter(op1, value1, op2, value2 string, parseValue func(string) (float64, error), fieldName string) (secondProfitCompareFilter, error) {
|
||||
filter := secondProfitCompareFilter{}
|
||||
if err := appendSecondProfitCompareCondition(&filter, op1, value1, parseValue, fieldName, "第一组"); err != nil {
|
||||
@@ -478,6 +514,7 @@ func parseSecondProfitCompareFilter(op1, value1, op2, value2 string, parseValue
|
||||
return filter, nil
|
||||
}
|
||||
|
||||
// appendSecondProfitCompareCondition 校验并追加一个比较条件。
|
||||
func appendSecondProfitCompareCondition(filter *secondProfitCompareFilter, op, rawValue string, parseValue func(string) (float64, error), fieldName, groupName string) error {
|
||||
op = strings.TrimSpace(op)
|
||||
rawValue = strings.TrimSpace(rawValue)
|
||||
@@ -498,6 +535,7 @@ func appendSecondProfitCompareCondition(filter *secondProfitCompareFilter, op, r
|
||||
return nil
|
||||
}
|
||||
|
||||
// isSecondProfitCompareOp 判断操作符是否在白名单内。
|
||||
func isSecondProfitCompareOp(op string) bool {
|
||||
switch op {
|
||||
case "<", ">", "<=", ">=", "=":
|
||||
@@ -507,10 +545,12 @@ func isSecondProfitCompareOp(op string) bool {
|
||||
}
|
||||
}
|
||||
|
||||
// Enabled 表示过滤器中是否存在有效条件。
|
||||
func (f secondProfitCompareFilter) Enabled() bool {
|
||||
return len(f.Conditions) > 0
|
||||
}
|
||||
|
||||
// Match 判断输入值是否满足过滤器中的所有条件。
|
||||
func (f secondProfitCompareFilter) Match(value float64) bool {
|
||||
if !f.Enabled() {
|
||||
return true
|
||||
@@ -523,6 +563,7 @@ func (f secondProfitCompareFilter) Match(value float64) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// Match 判断输入值是否满足单个比较条件。
|
||||
func (c secondProfitCompareCondition) Match(value float64) bool {
|
||||
switch c.Op {
|
||||
case "<":
|
||||
@@ -540,6 +581,7 @@ func (c secondProfitCompareCondition) Match(value float64) bool {
|
||||
}
|
||||
}
|
||||
|
||||
// parseSecondProfitTimeValue 把时间字符串转换为当天秒数。
|
||||
func parseSecondProfitTimeValue(value string) (float64, error) {
|
||||
seconds, ok := secondProfitTimeSeconds(value)
|
||||
if !ok {
|
||||
@@ -548,10 +590,12 @@ func parseSecondProfitTimeValue(value string) (float64, error) {
|
||||
return float64(seconds), nil
|
||||
}
|
||||
|
||||
// parseSecondProfitFloatValue 解析普通浮点过滤值。
|
||||
func parseSecondProfitFloatValue(value string) (float64, error) {
|
||||
return strconv.ParseFloat(strings.TrimSpace(value), 64)
|
||||
}
|
||||
|
||||
// secondProfitTimeSeconds 兼容多种涨停时间格式并转换为当天秒数。
|
||||
func secondProfitTimeSeconds(limitTime string) (int, bool) {
|
||||
timeText := strings.TrimSpace(limitTime)
|
||||
if timeText == "" {
|
||||
@@ -596,6 +640,7 @@ func secondProfitTimeSeconds(limitTime string) (int, bool) {
|
||||
return hour*3600 + minute*60 + second, true
|
||||
}
|
||||
|
||||
// secondProfitFormatLimitTime 把紧凑时间格式格式化为 HH:MM:SS。
|
||||
func secondProfitFormatLimitTime(value string) string {
|
||||
timeText := strings.TrimSpace(value)
|
||||
if timeText == "" || strings.Contains(timeText, ":") {
|
||||
@@ -613,6 +658,7 @@ func secondProfitFormatLimitTime(value string) string {
|
||||
return timeText[0:2] + ":" + timeText[2:4] + ":" + timeText[4:6]
|
||||
}
|
||||
|
||||
// secondProfitPctReturn 计算相对基准价的百分比收益。
|
||||
func secondProfitPctReturn(value sql.NullFloat64, base float64) sql.NullFloat64 {
|
||||
if !value.Valid || base == 0 {
|
||||
return sql.NullFloat64{}
|
||||
@@ -620,6 +666,7 @@ func secondProfitPctReturn(value sql.NullFloat64, base float64) sql.NullFloat64
|
||||
return sql.NullFloat64{Float64: secondProfitRound2((value.Float64 - base) / base * 100), Valid: true}
|
||||
}
|
||||
|
||||
// secondProfitLabel 根据收益是否大于 0 输出中文盈利标签。
|
||||
func secondProfitLabel(value sql.NullFloat64) string {
|
||||
if !value.Valid {
|
||||
return "无次日数据"
|
||||
@@ -630,14 +677,17 @@ func secondProfitLabel(value sql.NullFloat64) string {
|
||||
return "否"
|
||||
}
|
||||
|
||||
// secondProfitRound2 保留两位小数。
|
||||
func secondProfitRound2(value float64) float64 {
|
||||
return math.Round(value*100) / 100
|
||||
}
|
||||
|
||||
// secondProfitFormatFloat 使用最短十进制格式输出浮点数。
|
||||
func secondProfitFormatFloat(value float64) string {
|
||||
return strconv.FormatFloat(value, 'f', -1, 64)
|
||||
}
|
||||
|
||||
// secondProfitFormatNullFloat 格式化可空浮点数,空值输出为空字符串。
|
||||
func secondProfitFormatNullFloat(value sql.NullFloat64) string {
|
||||
if !value.Valid {
|
||||
return ""
|
||||
@@ -645,6 +695,7 @@ func secondProfitFormatNullFloat(value sql.NullFloat64) string {
|
||||
return secondProfitFormatFloat(value.Float64)
|
||||
}
|
||||
|
||||
// secondProfitFormatNullInt 格式化可空整数,空值输出为空字符串。
|
||||
func secondProfitFormatNullInt(value sql.NullInt64) string {
|
||||
if !value.Valid {
|
||||
return ""
|
||||
@@ -652,6 +703,7 @@ func secondProfitFormatNullInt(value sql.NullInt64) string {
|
||||
return strconv.FormatInt(value.Int64, 10)
|
||||
}
|
||||
|
||||
// secondProfitNullFloatPtr 把 sql.NullFloat64 转换为 JSON 友好的 *float64。
|
||||
func secondProfitNullFloatPtr(value sql.NullFloat64) *float64 {
|
||||
if !value.Valid {
|
||||
return nil
|
||||
@@ -660,6 +712,7 @@ func secondProfitNullFloatPtr(value sql.NullFloat64) *float64 {
|
||||
return &v
|
||||
}
|
||||
|
||||
// secondProfitNullIntPtr 把 sql.NullInt64 转换为 JSON 友好的 *int64。
|
||||
func secondProfitNullIntPtr(value sql.NullInt64) *int64 {
|
||||
if !value.Valid {
|
||||
return nil
|
||||
|
||||
@@ -7,15 +7,19 @@ import (
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// StockHandler 承载股票行情和走势分析接口。
|
||||
type StockHandler struct {
|
||||
Service *service.StockService
|
||||
Service *service.StockService // 股票服务,封装行情查询和区间分析逻辑。
|
||||
}
|
||||
|
||||
// NewStockHandler 创建股票接口 Handler。
|
||||
func NewStockHandler(s *service.StockService) *StockHandler {
|
||||
return &StockHandler{Service: s}
|
||||
}
|
||||
|
||||
// GetKlineData 获取指定股票从2024-09-24至今的K线
|
||||
// GetKlineData 获取指定股票的历史 K 线数据。
|
||||
//
|
||||
// 股票代码来自路径参数 :code,返回按交易日升序排列的 stock_daily 记录。
|
||||
func (h *StockHandler) GetKlineData(c *gin.Context) {
|
||||
code := c.Param("code")
|
||||
if code == "" {
|
||||
@@ -32,6 +36,7 @@ func (h *StockHandler) GetKlineData(c *gin.Context) {
|
||||
c.JSON(http.StatusOK, data)
|
||||
}
|
||||
|
||||
// Analyze 根据前端传入的起止日期分析涨停后走势。
|
||||
func (h *StockHandler) Analyze(c *gin.Context) {
|
||||
var req model.AnalyzeRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
|
||||
Reference in New Issue
Block a user