package service import ( "level_go/internal/model" "math" "sort" "gorm.io/gorm" ) type StockService struct { DB *gorm.DB } func roundTo4(val float64) float64 { return math.Round(val*10000) / 10000 } func (s *StockService) AnalyzePerformance(startDate, endDate string) ([]model.AnalysisResponse, error) { var results []model.AnalysisResponse var limits []struct { TsCode string Name string Open float64 High float64 } // 1. 获取选定日期当天涨停的股票 query := ` SELECT l.ts_code, l.name, d.open, d.high FROM stock_limit_data l LEFT JOIN stock_daily d ON l.ts_code = d.ts_code AND l.trade_date = d.trade_date WHERE l.trade_date = ? AND l.status = 1` if err := s.DB.Raw(query, startDate).Scan(&limits).Error; err != nil { return nil, err } for _, stock := range limits { var dailies []model.StockDaily s.DB.Where("ts_code = ? AND trade_date BETWEEN ? AND ?", stock.TsCode, startDate, endDate). Order("trade_date ASC").Find(&dailies) if len(dailies) == 0 { continue } // --- 步骤 1:寻找全区间的最高点 (MaxHigh) --- maxPrice := -1.0 maxIdx := 0 for i, d := range dailies { if d.High > maxPrice { maxPrice = d.High maxIdx = i } } var dropPct, reboundPct *float64 var postMinLow float64 maxDateStr := dailies[maxIdx].TradeDate.Format("2006-01-02") // --- 步骤 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 } } 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 } } results = append(results, model.AnalysisResponse{ Code: stock.TsCode, Name: stock.Name, DayOpen: stock.Open, DayHigh: stock.High, MaxHigh: maxPrice, MaxDate: maxDateStr, PostMinLow: postMinLow, DropPct: dropPct, ReboundPct: reboundPct, }) } // 5. 基础排序(仅按回落幅度降序,不处理 null 特殊逻辑) sort.Slice(results, func(i, j int) bool { 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 }