package service import ( "math" "sort" "gorm.io/gorm" "level_go/internal/model" ) type StockService struct { DB *gorm.DB } func (s *StockService) GetHistoryKline(code string) ([]model.StockDaily, error) { var dailies []model.StockDaily startDate := "2024-09-24" err := s.DB.Table("stock_daily"). Where("ts_code = ? AND trade_date >= ?", code, startDate). Order("trade_date ASC"). Find(&dailies).Error return dailies, err } func roundTo2(val float64) float64 { return math.Round(val*100) / 100 } 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 } // 查询涨停股 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 } if len(limits) == 0 { return results, nil } codes := make([]string, 0, len(limits)) for _, stock := range limits { codes = append(codes, stock.TsCode) } var allDailies []model.StockDaily if err := s.DB.Table("stock_daily"). Select("ts_code", "trade_date", "open", "high", "low", "close"). Where("ts_code IN ? AND trade_date BETWEEN ? AND ?", codes, startDate, endDate). Order("ts_code ASC, trade_date ASC"). Find(&allDailies).Error; err != nil { return nil, err } dailiesByCode := make(map[string][]model.StockDaily, len(codes)) for _, daily := range allDailies { dailiesByCode[daily.TsCode] = append(dailiesByCode[daily.TsCode], daily) } for _, stock := range limits { dailies := dailiesByCode[stock.TsCode] if len(dailies) <= 1 { continue } // A. 计算区间绝对最高价 overallMax := dailies[0].High overallMaxDate := dailies[0].TradeDate.Format("2006-01-02") for _, d := range dailies { if d.High > overallMax { overallMax = d.High overallMaxDate = d.TradeDate.Format("2006-01-02") } } // B. 逻辑计算 limitHigh := stock.High postDailies := dailies[1:] // 找出涨停后的全局最低 minLowAfterLimit := postDailies[0].Low for _, d := range postDailies { if d.Low < minLowAfterLimit { minLowAfterLimit = d.Low } } var dropPct, reboundPct *float64 var logicMaxHigh float64 var logicMaxDate string if minLowAfterLimit >= limitHigh { // 形态 1:强势。计算涨幅 currMax := limitHigh maxIdx := -1 for i, d := range postDailies { if d.High > currMax { currMax = d.High maxIdx = i } } logicMaxHigh = currMax logicMaxDate = startDate if maxIdx != -1 { logicMaxDate = postDailies[maxIdx].TradeDate.Format("2006-01-02") val := roundTo2(((logicMaxHigh - limitHigh) / limitHigh) * 100) reboundPct = &val } dropPct = nil } else { // 形态 2:破位。计算降幅和反弹 absMinLow := postDailies[0].Low minIdx := 0 for i, d := range postDailies { if d.Low < absMinLow { absMinLow = d.Low minIdx = i } } // 找到涨停到该最低点之间的最高 rangeDailies := dailies[:minIdx+2] rangeMax := dailies[0].High maxD := startDate for _, d := range rangeDailies { if d.High > rangeMax { rangeMax = d.High maxD = d.TradeDate.Format("2006-01-02") } } logicMaxHigh = rangeMax logicMaxDate = maxD dVal := roundTo2(((rangeMax - absMinLow) / rangeMax) * 100) dropPct = &dVal lastClose := postDailies[len(postDailies)-1].Close rVal := roundTo2(((lastClose - absMinLow) / absMinLow) * 100) reboundPct = &rVal } var closePrices []float64 for _, d := range dailies { closePrices = append(closePrices, d.Close) } results = append(results, model.AnalysisResponse{ Code: stock.TsCode, Name: stock.Name, DayOpen: stock.Open, DayHigh: stock.High, MaxHigh: logicMaxHigh, MaxDate: logicMaxDate, OverallMaxHigh: overallMax, OverallMaxDate: overallMaxDate, PostMinLow: minLowAfterLimit, DropPct: dropPct, ReboundPct: reboundPct, ClosePrices: closePrices, }) } sort.Slice(results, func(i, j int) bool { if results[i].DropPct == nil && results[j].DropPct != nil { return false } if results[i].DropPct != nil && results[j].DropPct == nil { return true } if results[i].DropPct != nil && results[j].DropPct != nil { return *results[i].DropPct > *results[j].DropPct } return false }) return results, nil }