Files
go/internal/service/stock_service.go
T
2026-04-26 21:26:15 +08:00

187 lines
4.6 KiB
Go

package service
import (
"gorm.io/gorm"
"level_go/internal/model"
"math"
"sort"
)
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
}