新增注释
This commit is contained in:
@@ -5,10 +5,12 @@ import (
|
||||
"level_go/internal/model"
|
||||
)
|
||||
|
||||
// CommonService 提供通用基础数据查询能力。
|
||||
type CommonService struct {
|
||||
DB *gorm.DB
|
||||
DB *gorm.DB // 数据库连接。
|
||||
}
|
||||
|
||||
// GetTradeCalendar 查询上交所全部开市日期,并返回最新开市日期。
|
||||
func (s *CommonService) GetTradeCalendar() (model.CalendarResponse, error) {
|
||||
var results []struct {
|
||||
DateStr string `gorm:"column:ds"`
|
||||
@@ -24,6 +26,7 @@ func (s *CommonService) GetTradeCalendar() (model.CalendarResponse, error) {
|
||||
return resp, err
|
||||
}
|
||||
|
||||
// 按日期升序扫描,因此循环中最后一次赋值即为最大开市日期。
|
||||
for _, r := range results {
|
||||
resp.OpenDates = append(resp.OpenDates, r.DateStr)
|
||||
resp.MaxDate = r.DateStr
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"level_go/internal/model"
|
||||
)
|
||||
|
||||
// limitBreakNextPerformanceRow 是炸板股次日表现查询的数据库扫描结构。
|
||||
type limitBreakNextPerformanceRow struct {
|
||||
TradeDate string `gorm:"column:trade_date"`
|
||||
PreviousTradeDate string `gorm:"column:previous_trade_date"`
|
||||
@@ -31,6 +32,9 @@ type limitBreakNextPerformanceRow struct {
|
||||
NextClose *float64 `gorm:"column:next_close"`
|
||||
}
|
||||
|
||||
// GetLimitBreakNextPerformance 返回炸板股次日表现的分页响应。
|
||||
//
|
||||
// 该方法负责分页和汇总统计,实际查询与单股指标计算由 ListLimitBreakNextPerformanceStocks 完成。
|
||||
func (s *LimitBoardService) GetLimitBreakNextPerformance(req model.LimitBreakNextPerformanceRequest) (model.LimitBreakNextPerformanceResponse, error) {
|
||||
resp := model.LimitBreakNextPerformanceResponse{
|
||||
TradeStartDate: req.TradeStartDate,
|
||||
@@ -67,6 +71,9 @@ func (s *LimitBoardService) GetLimitBreakNextPerformance(req model.LimitBreakNex
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// ListLimitBreakNextPerformanceStocks 查询并计算全部命中的炸板股次日表现。
|
||||
//
|
||||
// 返回值中的 int 表示拥有任意 T+1 行情字段的记录数量。
|
||||
func (s *LimitBoardService) ListLimitBreakNextPerformanceStocks(req model.LimitBreakNextPerformanceRequest) ([]model.LimitBreakNextPerformanceStock, int, error) {
|
||||
rows, err := s.loadLimitBreakNextPerformanceRows(req.TradeStartDate, req.TradeEndDate)
|
||||
if err != nil {
|
||||
@@ -77,6 +84,7 @@ func (s *LimitBoardService) ListLimitBreakNextPerformanceStocks(req model.LimitB
|
||||
nextTradeDataCount := 0
|
||||
for _, row := range rows {
|
||||
item := buildLimitBreakNextPerformanceStock(row)
|
||||
// 可选布尔过滤在指标计算后执行,便于复用同一批基础查询结果。
|
||||
if req.TOpenGainLt101 != nil && item.TOpenGainLt101 != *req.TOpenGainLt101 {
|
||||
continue
|
||||
}
|
||||
@@ -91,6 +99,9 @@ func (s *LimitBoardService) ListLimitBreakNextPerformanceStocks(req model.LimitB
|
||||
return items, nextTradeDataCount, nil
|
||||
}
|
||||
|
||||
// loadLimitBreakNextPerformanceRows 查询“首板后次日炸板”的候选股票和 T+1 行情。
|
||||
//
|
||||
// 口径:前一交易日涨停、前两交易日未涨停、当前交易日 final_status = 2。
|
||||
func (s *LimitBoardService) loadLimitBreakNextPerformanceRows(startDate, endDate string) ([]limitBreakNextPerformanceRow, error) {
|
||||
query := `
|
||||
WITH open_calendar AS (
|
||||
@@ -160,6 +171,7 @@ ORDER BY trade_date ASC, ts_code ASC`
|
||||
return rows, err
|
||||
}
|
||||
|
||||
// buildLimitBreakNextPerformanceStock 根据数据库行计算前端需要的派生指标。
|
||||
func buildLimitBreakNextPerformanceStock(row limitBreakNextPerformanceRow) model.LimitBreakNextPerformanceStock {
|
||||
item := model.LimitBreakNextPerformanceStock{
|
||||
TradeDate: row.TradeDate,
|
||||
@@ -183,17 +195,20 @@ func buildLimitBreakNextPerformanceStock(row limitBreakNextPerformanceRow) model
|
||||
}
|
||||
item.BreakTimeBefore1100 = isLimitBreakTimeBeforeEleven(item.FirstTime)
|
||||
if row.TPreClose != nil && *row.TPreClose > 0 {
|
||||
// T 日开盘涨幅以 T 日前收为基准,低于 101% 用于筛选低开/小幅高开炸板。
|
||||
item.TOpenPctChg = pctChangePtr(row.TOpen, *row.TPreClose)
|
||||
if row.TOpen != nil {
|
||||
item.TOpenGainLt101 = *row.TOpen < *row.TPreClose*1.01
|
||||
}
|
||||
}
|
||||
if row.TClose != nil && *row.TClose > 0 {
|
||||
// T+1 的开高低收收益均以 T 日收盘价作为基准。
|
||||
item.NextOpenPctChg = pctChangePtr(row.NextOpen, *row.TClose)
|
||||
item.NextHighProfitPct = pctChangePtr(row.NextHigh, *row.TClose)
|
||||
item.NextLowLossPct = pctChangePtr(row.NextLow, *row.TClose)
|
||||
item.NextClosePctChg = pctChangePtr(row.NextClose, *row.TClose)
|
||||
if row.NextHigh != nil && row.THigh != nil {
|
||||
// 是否盘中可盈利按 T+1 最高价能否覆盖 T 日最高买入成本判断。
|
||||
nextIntradayProfitable := *row.NextHigh >= *row.THigh
|
||||
item.NextIntradayProfitable = &nextIntradayProfitable
|
||||
}
|
||||
@@ -201,6 +216,7 @@ func buildLimitBreakNextPerformanceStock(row limitBreakNextPerformanceRow) model
|
||||
return item
|
||||
}
|
||||
|
||||
// pctChangePtr 计算相对基准价的百分比变化,输入缺失时返回 nil。
|
||||
func pctChangePtr(value *float64, base float64) *float64 {
|
||||
if value == nil || base == 0 {
|
||||
return nil
|
||||
@@ -209,10 +225,12 @@ func pctChangePtr(value *float64, base float64) *float64 {
|
||||
return &result
|
||||
}
|
||||
|
||||
// roundLimitBreakPct 保留两位小数。
|
||||
func roundLimitBreakPct(value float64) float64 {
|
||||
return math.Round(value*100) / 100
|
||||
}
|
||||
|
||||
// formatLimitTime 将 TuShare 紧凑时间格式转换为 HH:MM:SS。
|
||||
func formatLimitTime(value string) string {
|
||||
timeText := strings.TrimSpace(value)
|
||||
if timeText == "" || strings.EqualFold(timeText, "none") || strings.EqualFold(timeText, "null") {
|
||||
@@ -233,6 +251,9 @@ func formatLimitTime(value string) string {
|
||||
return timeText[0:2] + ":" + timeText[2:4] + ":" + timeText[4:6]
|
||||
}
|
||||
|
||||
// isLimitBreakTimeBeforeEleven 判断炸板时间是否早于 11:00。
|
||||
//
|
||||
// 空时间在当前业务中视为满足条件,保持历史接口行为。
|
||||
func isLimitBreakTimeBeforeEleven(limitTime string) bool {
|
||||
if strings.TrimSpace(limitTime) == "" {
|
||||
return true
|
||||
|
||||
@@ -2,6 +2,9 @@ package service
|
||||
|
||||
import "testing"
|
||||
|
||||
// TestBuildLimitBreakNextPerformanceStockIntradayProfitableUsesTHigh 验证 T+1 盘中可盈利口径。
|
||||
//
|
||||
// 业务要求用 T+1 最高价覆盖 T 日最高价来判断是否可解套,而不是只和 T 日收盘价比较。
|
||||
func TestBuildLimitBreakNextPerformanceStockIntradayProfitableUsesTHigh(t *testing.T) {
|
||||
tClose := 9.50
|
||||
tHigh := 10.00
|
||||
@@ -52,10 +55,12 @@ func TestBuildLimitBreakNextPerformanceStockIntradayProfitableUsesTHigh(t *testi
|
||||
}
|
||||
}
|
||||
|
||||
// float64Ptr 是测试用指针辅助函数。
|
||||
func float64Ptr(value float64) *float64 {
|
||||
return &value
|
||||
}
|
||||
|
||||
// boolPtr 是测试用指针辅助函数。
|
||||
func boolPtr(value bool) *bool {
|
||||
return &value
|
||||
}
|
||||
|
||||
@@ -9,6 +9,9 @@ import (
|
||||
"level_go/internal/model"
|
||||
)
|
||||
|
||||
// GetLimitOpenCompareGroups 返回昨日涨停且今日继续涨停股票的开盘对比分组。
|
||||
//
|
||||
// 重点识别两类股票:今日开盘低于前收的股票,以及首板进二板且 11 点前首次涨停的股票。
|
||||
func (s *LimitBoardService) GetLimitOpenCompareGroups(tradeDate string) (model.LimitOpenCompareResponse, error) {
|
||||
resp := model.LimitOpenCompareResponse{TradeDate: tradeDate}
|
||||
|
||||
@@ -43,12 +46,14 @@ func (s *LimitBoardService) GetLimitOpenCompareGroups(tradeDate string) (model.L
|
||||
firstToSecondBoardCount := 0
|
||||
firstSecondBeforeElevenCount := 0
|
||||
for _, stock := range stocks {
|
||||
// previousTwoTradeDate 未涨停、previousTradeDate 涨停、tradeDate 涨停时,认为是首板进二板。
|
||||
if stock.IsFirstToSecondBoard {
|
||||
stock.HighlightLabel = "首板进二板"
|
||||
firstToSecondBoardCount++
|
||||
}
|
||||
isOpenLower := previousTradeDate != "" && stock.HasOpen && stock.HasPreviousClose && stock.Open < stock.PreviousClose
|
||||
if isOpenLower && stock.IsFirstToSecondBoard && isBeforeEleven(stock.AkFirstLimitTime) {
|
||||
// 首板进二板且 11 点前封板的低开股,前端会用更强提示标签展示。
|
||||
stock.IsFirstSecondBeforeEleven = true
|
||||
stock.HighlightLabel = "首二一一"
|
||||
firstSecondBeforeElevenCount++
|
||||
@@ -72,6 +77,9 @@ func (s *LimitBoardService) GetLimitOpenCompareGroups(tradeDate string) (model.L
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// isBeforeEleven 判断涨停时间是否早于 11:00。
|
||||
//
|
||||
// 兼容 HH:MM:SS、930、0930、93000、093000 等时间格式。
|
||||
func isBeforeEleven(limitTime *string) bool {
|
||||
if limitTime == nil || *limitTime == "" {
|
||||
return false
|
||||
@@ -119,6 +127,7 @@ func isBeforeEleven(limitTime *string) bool {
|
||||
return hour < 11
|
||||
}
|
||||
|
||||
// loadLimitOpenCompareStocks 查询昨日涨停且今日继续涨停的股票,并带出今日开盘和昨日收盘。
|
||||
func (s *LimitBoardService) loadLimitOpenCompareStocks(tradeDate, previousTradeDate, previousTwoTradeDate string) ([]model.LimitOpenCompareStock, error) {
|
||||
var stocks []model.LimitOpenCompareStock
|
||||
query := `SELECT
|
||||
@@ -171,6 +180,7 @@ func (s *LimitBoardService) loadLimitOpenCompareStocks(tradeDate, previousTradeD
|
||||
return stocks, err
|
||||
}
|
||||
|
||||
// buildLimitOpenCompareGroups 构造“开盘低于前收”和“其他涨停股”两个固定分组。
|
||||
func buildLimitOpenCompareGroups(openLowerStocks, otherStocks []model.LimitOpenCompareStock) []model.LimitOpenCompareGroup {
|
||||
if openLowerStocks == nil {
|
||||
openLowerStocks = []model.LimitOpenCompareStock{}
|
||||
@@ -198,6 +208,7 @@ func buildLimitOpenCompareGroups(openLowerStocks, otherStocks []model.LimitOpenC
|
||||
}
|
||||
}
|
||||
|
||||
// sortLimitOpenCompareStocks 按涨跌幅降序、股票代码升序稳定展示分组内股票。
|
||||
func sortLimitOpenCompareStocks(stocks []model.LimitOpenCompareStock) {
|
||||
sort.Slice(stocks, func(i, j int) bool {
|
||||
if stocks[i].PctChg == stocks[j].PctChg {
|
||||
|
||||
@@ -6,8 +6,10 @@ import (
|
||||
"level_go/internal/model"
|
||||
)
|
||||
|
||||
// GetLimitOpenThresholdGroups 返回昨日涨停股今日开盘是否低于前收 101% 的分组。
|
||||
func (s *LimitBoardService) GetLimitOpenThresholdGroups(tradeDate string) (model.LimitOpenThresholdResponse, error) {
|
||||
resp := model.LimitOpenThresholdResponse{TradeDate: tradeDate}
|
||||
// thresholdRatio 是开盘价阈值,1.01 表示前收的 101%。
|
||||
const thresholdRatio = 1.01
|
||||
|
||||
tradeDates, dateIndex, err := s.loadTradeCalendar(tradeDate)
|
||||
@@ -34,6 +36,7 @@ func (s *LimitBoardService) GetLimitOpenThresholdGroups(tradeDate string) (model
|
||||
withinThresholdStocks := make([]model.LimitOpenThresholdStock, 0)
|
||||
atOrAboveThresholdStocks := make([]model.LimitOpenThresholdStock, 0)
|
||||
for _, stock := range stocks {
|
||||
// 前收价格有效时才计算阈值价格和开盘/前收比值,避免除零和误导性数据。
|
||||
if stock.HasPreviousClose && stock.PreviousClose > 0 {
|
||||
stock.OpenThresholdPrice = stock.PreviousClose * thresholdRatio
|
||||
}
|
||||
@@ -59,6 +62,7 @@ func (s *LimitBoardService) GetLimitOpenThresholdGroups(tradeDate string) (model
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// loadLimitOpenThresholdStocks 查询昨日涨停且今日继续涨停的股票,并补齐今日开盘和前收。
|
||||
func (s *LimitBoardService) loadLimitOpenThresholdStocks(tradeDate, previousTradeDate string) ([]model.LimitOpenThresholdStock, error) {
|
||||
var stocks []model.LimitOpenThresholdStock
|
||||
query := `SELECT
|
||||
@@ -105,6 +109,7 @@ func (s *LimitBoardService) loadLimitOpenThresholdStocks(tradeDate, previousTrad
|
||||
return stocks, err
|
||||
}
|
||||
|
||||
// buildLimitOpenThresholdGroups 构造低于阈值和达到/超过阈值两个固定分组。
|
||||
func buildLimitOpenThresholdGroups(withinThresholdStocks, atOrAboveThresholdStocks []model.LimitOpenThresholdStock) []model.LimitOpenThresholdGroup {
|
||||
if withinThresholdStocks == nil {
|
||||
withinThresholdStocks = []model.LimitOpenThresholdStock{}
|
||||
@@ -132,6 +137,7 @@ func buildLimitOpenThresholdGroups(withinThresholdStocks, atOrAboveThresholdStoc
|
||||
}
|
||||
}
|
||||
|
||||
// sortLimitOpenThresholdStocks 按涨跌幅降序、股票代码升序稳定展示分组内股票。
|
||||
func sortLimitOpenThresholdStocks(stocks []model.LimitOpenThresholdStock) {
|
||||
sort.Slice(stocks, func(i, j int) bool {
|
||||
if stocks[i].PctChg == stocks[j].PctChg {
|
||||
|
||||
@@ -6,11 +6,15 @@ import (
|
||||
"level_go/internal/model"
|
||||
)
|
||||
|
||||
// limitBoardHistoryRow 是连板计算所需的最小历史涨停记录。
|
||||
type limitBoardHistoryRow struct {
|
||||
TsCode string `gorm:"column:ts_code"`
|
||||
TradeDate string `gorm:"column:trade_date"`
|
||||
TsCode string `gorm:"column:ts_code"` // 股票代码。
|
||||
TradeDate string `gorm:"column:trade_date"` // 涨停交易日。
|
||||
}
|
||||
|
||||
// GetLimitBoardOverview 返回指定交易日的涨停连板概览。
|
||||
//
|
||||
// 结果包括最近 30 个交易日最高连板数、当日涨停股按连板数分组后的列表和汇总统计。
|
||||
func (s *LimitBoardService) GetLimitBoardOverview(tradeDate string) (model.LimitBoardResponse, error) {
|
||||
resp := model.LimitBoardResponse{TradeDate: tradeDate}
|
||||
|
||||
@@ -51,6 +55,7 @@ func (s *LimitBoardService) GetLimitBoardOverview(tradeDate string) (model.Limit
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// buildRecentMaxBoardStats 计算最近最多 30 个交易日的每日最高连板数。
|
||||
func (s *LimitBoardService) buildRecentMaxBoardStats(tradeDates []string, dateIndex int) ([]model.LimitBoardDailyMax, error) {
|
||||
startIndex := 0
|
||||
if dateIndex >= 29 {
|
||||
@@ -83,6 +88,7 @@ func (s *LimitBoardService) buildRecentMaxBoardStats(tradeDates []string, dateIn
|
||||
maxBoardByDate[tradeDate] = 0
|
||||
}
|
||||
|
||||
// 对每只股票在窗口内逐日回看连续涨停天数,并更新当天最高连板数。
|
||||
for _, limitDates := range dateByCode {
|
||||
for idx, tradeDate := range windowDates {
|
||||
if _, ok := limitDates[tradeDate]; !ok {
|
||||
@@ -114,6 +120,7 @@ func (s *LimitBoardService) buildRecentMaxBoardStats(tradeDates []string, dateIn
|
||||
return stats, nil
|
||||
}
|
||||
|
||||
// loadCurrentLimitStocks 查询指定交易日所有最终状态为涨停的股票。
|
||||
func (s *LimitBoardService) loadCurrentLimitStocks(tradeDate string) ([]model.LimitBoardStock, error) {
|
||||
var stocks []model.LimitBoardStock
|
||||
query := `SELECT
|
||||
@@ -147,6 +154,7 @@ func (s *LimitBoardService) loadCurrentLimitStocks(tradeDate string) ([]model.Li
|
||||
return stocks, err
|
||||
}
|
||||
|
||||
// loadLimitHistory 查询指定股票集合在 tradeDate 之前及当天的涨停历史。
|
||||
func (s *LimitBoardService) loadLimitHistory(stocks []model.LimitBoardStock, tradeDate string) (map[string]map[string]struct{}, error) {
|
||||
codes := make([]string, 0, len(stocks))
|
||||
for _, stock := range stocks {
|
||||
@@ -172,6 +180,7 @@ func (s *LimitBoardService) loadLimitHistory(stocks []model.LimitBoardStock, tra
|
||||
return historyMap, nil
|
||||
}
|
||||
|
||||
// buildLimitBoardGroups 计算每只股票的连板数,并按连板数构造分组映射。
|
||||
func buildLimitBoardGroups(stocks []model.LimitBoardStock, tradeDates []string, dateIndex int, historyMap map[string]map[string]struct{}) (map[int][]model.LimitBoardStock, int) {
|
||||
groupMap := make(map[int][]model.LimitBoardStock)
|
||||
maxBoardCount := 0
|
||||
@@ -191,6 +200,7 @@ func buildLimitBoardGroups(stocks []model.LimitBoardStock, tradeDates []string,
|
||||
return groupMap, maxBoardCount
|
||||
}
|
||||
|
||||
// calcBoardCount 从目标交易日向前回溯,直到遇到非涨停日为止,得到连续涨停天数。
|
||||
func calcBoardCount(tradeDates []string, dateIndex int, limitDates map[string]struct{}) int {
|
||||
boardCount := 0
|
||||
for i := dateIndex; i >= 0; i-- {
|
||||
@@ -202,6 +212,7 @@ func calcBoardCount(tradeDates []string, dateIndex int, limitDates map[string]st
|
||||
return boardCount
|
||||
}
|
||||
|
||||
// sortLimitBoardGroups 将连板分组按连板数倒序排列,组内按涨跌幅降序排列。
|
||||
func sortLimitBoardGroups(groupMap map[int][]model.LimitBoardStock) []model.LimitBoardGroup {
|
||||
boardCounts := make([]int, 0, len(groupMap))
|
||||
for boardCount := range groupMap {
|
||||
|
||||
@@ -6,12 +6,15 @@ import (
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// LimitBoardService 提供涨停板概览、分组、收益统计等业务能力。
|
||||
type LimitBoardService struct {
|
||||
DB *gorm.DB
|
||||
DB *gorm.DB // 数据库连接。
|
||||
}
|
||||
|
||||
// stockLimitCombinedTable 是涨停合并数据表名,集中定义便于多个查询复用。
|
||||
const stockLimitCombinedTable = "stock_limit_combined_data"
|
||||
|
||||
// boardLabel 把连续涨停天数转换为中文连板标签。
|
||||
func boardLabel(boardCount int) string {
|
||||
switch boardCount {
|
||||
case 1:
|
||||
@@ -37,6 +40,9 @@ func boardLabel(boardCount int) string {
|
||||
}
|
||||
}
|
||||
|
||||
// loadTradeCalendar 查询不晚于 tradeDate 的上交所开市日,并返回 tradeDate 在切片中的索引。
|
||||
//
|
||||
// dateIndex 为 -1 表示传入日期不是当前交易日历中的开市日。
|
||||
func (s *LimitBoardService) loadTradeCalendar(tradeDate string) ([]string, int, error) {
|
||||
var tradeDates []string
|
||||
if err := s.DB.Table("trade_calendar").
|
||||
|
||||
@@ -8,10 +8,12 @@ import (
|
||||
"level_go/internal/model"
|
||||
)
|
||||
|
||||
// StockService 提供股票行情查询和区间表现分析能力。
|
||||
type StockService struct {
|
||||
DB *gorm.DB
|
||||
DB *gorm.DB // 数据库连接。
|
||||
}
|
||||
|
||||
// GetHistoryKline 查询指定股票自固定起点以来的日 K 线。
|
||||
func (s *StockService) GetHistoryKline(code string) ([]model.StockDaily, error) {
|
||||
var dailies []model.StockDaily
|
||||
startDate := "2020-01-02"
|
||||
@@ -24,10 +26,14 @@ func (s *StockService) GetHistoryKline(code string) ([]model.StockDaily, error)
|
||||
return dailies, err
|
||||
}
|
||||
|
||||
// roundTo2 把浮点数四舍五入到两位小数。
|
||||
func roundTo2(val float64) float64 {
|
||||
return math.Round(val*100) / 100
|
||||
}
|
||||
|
||||
// AnalyzePerformance 分析某个涨停日之后到结束日期之间的下跌和反弹表现。
|
||||
//
|
||||
// startDate 既是涨停筛选日期,也是行情区间起点;endDate 是行情区间终点。
|
||||
func (s *StockService) AnalyzePerformance(startDate, endDate string) ([]model.AnalysisResponse, error) {
|
||||
var results []model.AnalysisResponse
|
||||
var limits []struct {
|
||||
@@ -37,7 +43,7 @@ func (s *StockService) AnalyzePerformance(startDate, endDate string) ([]model.An
|
||||
High float64
|
||||
}
|
||||
|
||||
// 查询涨停股
|
||||
// 查询 startDate 当日涨停股,并优先使用涨停合并表中的计算最高价。
|
||||
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`
|
||||
@@ -75,7 +81,7 @@ func (s *StockService) AnalyzePerformance(startDate, endDate string) ([]model.An
|
||||
continue
|
||||
}
|
||||
|
||||
// A. 计算区间绝对最高价
|
||||
// A. 计算区间绝对最高价,用于前端展示真实区间高点。
|
||||
overallMax := dailies[0].High
|
||||
overallMaxDate := dailies[0].TradeDate.Format("2006-01-02")
|
||||
for _, d := range dailies {
|
||||
@@ -85,11 +91,11 @@ func (s *StockService) AnalyzePerformance(startDate, endDate string) ([]model.An
|
||||
}
|
||||
}
|
||||
|
||||
// B. 逻辑计算
|
||||
// B. 逻辑计算以涨停日最高价为分界,区分“未跌破涨停高点”和“跌破后反弹”两类形态。
|
||||
limitHigh := stock.High
|
||||
postDailies := dailies[1:]
|
||||
|
||||
// 找出涨停后的全局最低
|
||||
// 找出涨停后的全局最低,用来判断是否跌破涨停日最高价。
|
||||
minLowAfterLimit := postDailies[0].Low
|
||||
for _, d := range postDailies {
|
||||
if d.Low < minLowAfterLimit {
|
||||
@@ -102,7 +108,7 @@ func (s *StockService) AnalyzePerformance(startDate, endDate string) ([]model.An
|
||||
var logicMaxDate string
|
||||
|
||||
if minLowAfterLimit >= limitHigh {
|
||||
// 形态 1:强势。计算涨幅
|
||||
// 形态 1:强势,后续最低价仍高于涨停日最高价,因此只统计向上空间。
|
||||
currMax := limitHigh
|
||||
maxIdx := -1
|
||||
for i, d := range postDailies {
|
||||
@@ -120,7 +126,7 @@ func (s *StockService) AnalyzePerformance(startDate, endDate string) ([]model.An
|
||||
}
|
||||
dropPct = nil
|
||||
} else {
|
||||
// 形态 2:破位。计算降幅和反弹
|
||||
// 形态 2:破位,先找到破位最低点,再计算从此前最高点到最低点的降幅。
|
||||
absMinLow := postDailies[0].Low
|
||||
minIdx := 0
|
||||
for i, d := range postDailies {
|
||||
@@ -129,7 +135,7 @@ func (s *StockService) AnalyzePerformance(startDate, endDate string) ([]model.An
|
||||
minIdx = i
|
||||
}
|
||||
}
|
||||
// 找到涨停到该最低点之间的最高
|
||||
// 找到涨停日到最低点之间的最高价,作为本次回撤的逻辑高点。
|
||||
rangeDailies := dailies[:minIdx+2]
|
||||
rangeMax := dailies[0].High
|
||||
maxD := startDate
|
||||
@@ -150,6 +156,7 @@ func (s *StockService) AnalyzePerformance(startDate, endDate string) ([]model.An
|
||||
reboundPct = &rVal
|
||||
}
|
||||
|
||||
// 收盘价序列给前端画趋势图使用。
|
||||
var closePrices []float64
|
||||
for _, d := range dailies {
|
||||
closePrices = append(closePrices, d.Close)
|
||||
@@ -171,6 +178,7 @@ func (s *StockService) AnalyzePerformance(startDate, endDate string) ([]model.An
|
||||
})
|
||||
}
|
||||
|
||||
// 优先展示跌幅更大的破位股票;强势股票由于 DropPct 为 nil 排在后面。
|
||||
sort.Slice(results, func(i, j int) bool {
|
||||
if results[i].DropPct == nil && results[j].DropPct != nil {
|
||||
return false
|
||||
|
||||
Reference in New Issue
Block a user