diff --git a/internal/model/stock.go b/internal/model/stock.go index 2b2d8fa..354c661 100644 --- a/internal/model/stock.go +++ b/internal/model/stock.go @@ -14,13 +14,13 @@ type StockDaily struct { func (StockDaily) TableName() string { return "stock_daily" } type AnalysisResponse struct { - Code string `json:"code"` - Name string `json:"name"` - DayOpen float64 `json:"day_open"` - DayHigh float64 `json:"day_high"` - MaxHigh float64 `json:"max_high"` - MaxDate string `json:"max_date"` - PostMinLow float64 `json:"post_min_low"` - DropPct float64 `json:"drop_pct"` - ReboundPct float64 `json:"rebound_pct"` + Code string `json:"code"` + Name string `json:"name"` + DayOpen float64 `json:"day_open"` + DayHigh float64 `json:"day_high"` + MaxHigh float64 `json:"max_high"` + MaxDate string `json:"max_date"` + PostMinLow float64 `json:"post_min_low"` + DropPct *float64 `json:"drop_pct"` // 改为指针以支持 null + ReboundPct *float64 `json:"rebound_pct"` // 改为指针以支持 null } diff --git a/internal/service/analyzer.go b/internal/service/analyzer.go index e162604..d977d82 100644 --- a/internal/service/analyzer.go +++ b/internal/service/analyzer.go @@ -18,7 +18,6 @@ func roundTo4(val float64) float64 { func (s *StockService) AnalyzePerformance(startDate, endDate string) ([]model.AnalysisResponse, error) { var results []model.AnalysisResponse - var limits []struct { TsCode string Name string @@ -26,7 +25,7 @@ func (s *StockService) AnalyzePerformance(startDate, endDate string) ([]model.An High float64 } - // 1. 查找涨停股票 + // 1. 获取选定日期当天涨停的股票 query := ` SELECT l.ts_code, l.name, d.open, d.high FROM stock_limit_data l @@ -37,7 +36,6 @@ func (s *StockService) AnalyzePerformance(startDate, endDate string) ([]model.An return nil, err } - // 2. 分析区间数据 for _, stock := range limits { var dailies []model.StockDaily s.DB.Where("ts_code = ? AND trade_date BETWEEN ? AND ?", stock.TsCode, startDate, endDate). @@ -47,31 +45,57 @@ func (s *StockService) AnalyzePerformance(startDate, endDate string) ([]model.An continue } - // A. 找最高点 + // --- 步骤 1:寻找全区间的最高点 (MaxHigh) --- maxPrice := -1.0 maxIdx := 0 for i, d := range dailies { - if d.High >= maxPrice { + if d.High > maxPrice { maxPrice = d.High maxIdx = i } } - // B. 找最高点后的最低点 - postData := dailies[maxIdx+1:] - var minLow, dropPct, reboundPct float64 + var dropPct, reboundPct *float64 + var postMinLow float64 + maxDateStr := dailies[maxIdx].TradeDate.Format("2006-01-02") - if len(postData) > 0 { - minLow = postData[0].Low - for _, p := range postData { - if p.Low < minLow { - minLow = p.Low + // --- 步骤 2:寻找最高点之后的最低点 (PostMinLow) --- + if maxIdx < len(dailies)-1 { + postMaxData := dailies[maxIdx+1:] + minLow := postMaxData[0].Low + minLowIdx := 0 + + for i, d := range postMaxData { + if d.Low < minLow { + minLow = d.Low + minLowIdx = i } } - lastClose := postData[len(postData)-1].Close - dropPct = roundTo4(((maxPrice - minLow) / maxPrice) * 100) - if minLow > 0 { - reboundPct = roundTo4(((lastClose - minLow) / minLow) * 100) + postMinLow = minLow + + // 只要出现比 MaxHigh 低的价格,就计算回落幅度 + if minLow < maxPrice { + dVal := roundTo4(((maxPrice - minLow) / maxPrice) * 100) + dropPct = &dVal + + // --- 步骤 3:寻找最低点之后的反弹 --- + if minLowIdx < len(postMaxData)-1 { + lastClose := postMaxData[len(postMaxData)-1].Close + if lastClose > minLow { + rVal := roundTo4(((lastClose - minLow) / minLow) * 100) + reboundPct = &rVal + } + } + } + } + + // --- 步骤 4:处理特殊形态(如持续上涨无回落) --- + if dropPct == nil { + lastClose := dailies[len(dailies)-1].Close + firstDayHigh := dailies[0].High + if lastClose > firstDayHigh { + rVal := roundTo4(((lastClose - firstDayHigh) / firstDayHigh) * 100) + reboundPct = &rVal } } @@ -81,16 +105,23 @@ func (s *StockService) AnalyzePerformance(startDate, endDate string) ([]model.An DayOpen: stock.Open, DayHigh: stock.High, MaxHigh: maxPrice, - MaxDate: dailies[maxIdx].TradeDate.Format("2006-01-02"), - PostMinLow: minLow, + MaxDate: maxDateStr, + PostMinLow: postMinLow, DropPct: dropPct, ReboundPct: reboundPct, }) } - // 3. 按降幅排序 + // 5. 基础排序(仅按回落幅度降序,不处理 null 特殊逻辑) sort.Slice(results, func(i, j int) bool { - return results[i].DropPct > results[j].DropPct + valI, valJ := -1.0, -1.0 + if results[i].DropPct != nil { + valI = *results[i].DropPct + } + if results[j].DropPct != nil { + valJ = *results[j].DropPct + } + return valI > valJ }) return results, nil