188 lines
4.6 KiB
Go
188 lines
4.6 KiB
Go
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 := "2020-01-02"
|
|
|
|
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, COALESCE(l.calc_high, d.high, 0) AS high FROM stock_limit_combined_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.final_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
|
|
}
|