Files
go/internal/service/analyzer.go
T
2026-04-25 15:11:19 +08:00

129 lines
3.0 KiB
Go

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
}