适配mysql

This commit is contained in:
2026-05-13 17:46:03 +08:00
parent 81dc55baaf
commit 61cc29d7b3
12 changed files with 456 additions and 114 deletions
+76 -59
View File
@@ -3,6 +3,7 @@ package service
import (
"math"
"level_go/internal/dbcompat"
"level_go/internal/model"
)
@@ -23,6 +24,8 @@ type secondCalRow struct {
TClose *float64 `gorm:"column:t_close"` // T 日收盘价。
TPctChg *float64 `gorm:"column:t_pct_chg"` // T 日收盘涨跌幅。
Status string `gorm:"column:status"` // T 日二板状态。
FirstLimitTime *string `gorm:"column:first_limit_time"` // T 日首次涨停时间。
OpenTimes *int `gorm:"column:open_times"` // T 日开板次数。
NextHigh *float64 `gorm:"column:next_high"` // T+1 日最高价。
NextClose *float64 `gorm:"column:next_close"` // T+1 日收盘价。
}
@@ -85,29 +88,19 @@ func (s *LimitBoardService) GetSecondCal(req model.SecondCalRequest) (model.Seco
// loadSecondCalDays 查询日期范围内的开市交易日和相邻交易日。
func (s *LimitBoardService) loadSecondCalDays(startDate, endDate string) ([]secondCalDaySeed, map[string]int, error) {
query := `
WITH open_calendar AS (
SELECT
cal_date AS trade_date,
LAG(cal_date, 1) OVER (ORDER BY cal_date) AS previous_trade_date,
LAG(cal_date, 2) OVER (ORDER BY cal_date) AS previous_two_trade_date,
LEAD(cal_date, 1) OVER (ORDER BY cal_date) AS next_trade_date
FROM trade_calendar
WHERE exchange = 'SSE' AND is_open = 1
)
SELECT
DATE_FORMAT(trade_date, '%Y-%m-%d') AS trade_date,
COALESCE(DATE_FORMAT(previous_trade_date, '%Y-%m-%d'), '') AS previous_trade_date,
COALESCE(DATE_FORMAT(previous_two_trade_date, '%Y-%m-%d'), '') AS previous_two_trade_date,
COALESCE(DATE_FORMAT(next_trade_date, '%Y-%m-%d'), '') AS next_trade_date
FROM open_calendar
WHERE trade_date BETWEEN ? AND ?
ORDER BY trade_date ASC`
var days []secondCalDaySeed
if err := s.DB.Raw(query, startDate, endDate).Scan(&days).Error; err != nil {
seeds, err := dbcompat.LoadSSEOpenCalendarSeeds(s.DB, startDate, endDate)
if err != nil {
return nil, nil, err
}
days := make([]secondCalDaySeed, 0, len(seeds))
for _, seed := range seeds {
days = append(days, secondCalDaySeed{
TradeDate: seed.TradeDate,
PreviousTradeDate: seed.PreviousTradeDate,
PreviousTwoTradeDate: seed.PreviousTwoTradeDate,
NextTradeDate: seed.NextTradeDate,
})
}
// 构建交易日到响应数组下标的映射,后续聚合明细时可以 O(1) 定位。
dayIndex := make(map[string]int, len(days))
@@ -119,17 +112,41 @@ ORDER BY trade_date ASC`
// loadSecondCalRows 查询 T-1 涨停、T-2 未涨停、T 日再次触及涨停价的二板股票。
func (s *LimitBoardService) loadSecondCalRows(startDate, endDate string) ([]secondCalRow, error) {
seeds, err := dbcompat.LoadSSEOpenCalendarSeeds(s.DB, startDate, endDate)
if err != nil {
return nil, err
}
if len(seeds) == 0 {
return []secondCalRow{}, nil
}
calendarTable, calendarArgs := dbcompat.CalendarSeedTable(seeds)
stockDailyTsCodeCollation, err := dbcompat.ColumnCollation(s.DB, "stock_daily", "ts_code")
if err != nil {
return nil, err
}
query := `
WITH open_calendar AS (
SELECT
cal_date AS trade_date,
LAG(cal_date, 1) OVER (ORDER BY cal_date) AS previous_trade_date,
LAG(cal_date, 2) OVER (ORDER BY cal_date) AS previous_two_trade_date,
LEAD(cal_date, 1) OVER (ORDER BY cal_date) AS next_trade_date
FROM trade_calendar
WHERE exchange = 'SSE' AND is_open = 1
),
candidate AS (
SELECT
DATE_FORMAT(trade_date, '%Y-%m-%d') AS trade_date,
DATE_FORMAT(previous_trade_date, '%Y-%m-%d') AS previous_trade_date,
DATE_FORMAT(previous_two_trade_date, '%Y-%m-%d') AS previous_two_trade_date,
COALESCE(DATE_FORMAT(next_trade_date, '%Y-%m-%d'), '') AS next_trade_date,
COALESCE(symbol, '') AS symbol,
ts_code,
name,
market,
industry,
previous_close,
limit_price,
t_high,
t_close,
t_pct_chg,
status,
first_limit_time,
open_times,
next_high,
next_close
FROM (
SELECT
c.trade_date,
c.previous_trade_date,
@@ -159,45 +176,41 @@ candidate AS (
WHEN d.close < d.high THEN 'break'
ELSE 'unknown'
END AS status,
CASE
WHEN ABS(d.close - d.high) < 0.0001
THEN NULLIF(NULLIF(TRIM(tlu.first_time), ''), 'None')
WHEN d.close < d.high
THEN NULLIF(NULLIF(TRIM(tlz.first_time), ''), 'None')
ELSE NULL
END AS first_limit_time,
CASE
WHEN ABS(d.close - d.high) < 0.0001 THEN tlu.open_times
WHEN d.close < d.high THEN tlz.open_times
ELSE NULL
END AS open_times,
nd.high AS next_high,
nd.close AS next_close
FROM open_calendar c
FROM (` + calendarTable + `) c
INNER JOIN limit_list_d pl
ON pl.trade_date = c.previous_trade_date AND pl.` + "`limit`" + ` = 'U'
LEFT JOIN limit_list_d ppl
ON ppl.ts_code = pl.ts_code AND ppl.trade_date = c.previous_two_trade_date AND ppl.` + "`limit`" + ` = 'U'
ON ` + dbcompat.TsCodeEqual("ppl.ts_code", "pl.ts_code") + ` AND ppl.trade_date = c.previous_two_trade_date AND ppl.` + "`limit`" + ` = 'U'
INNER JOIN stock_daily d
ON d.ts_code = pl.ts_code COLLATE utf8mb4_0900_ai_ci AND d.trade_date = c.trade_date
ON ` + dbcompat.TsCodeEqualRightCollation("d.ts_code", "pl.ts_code", stockDailyTsCodeCollation) + ` AND d.trade_date = c.trade_date
LEFT JOIN stock_daily nd
ON nd.ts_code = pl.ts_code COLLATE utf8mb4_0900_ai_ci AND nd.trade_date = c.next_trade_date
ON ` + dbcompat.TsCodeEqualRightCollation("nd.ts_code", "pl.ts_code", stockDailyTsCodeCollation) + ` AND nd.trade_date = c.next_trade_date
LEFT JOIN limit_list_d tlu
ON ` + dbcompat.TsCodeEqual("tlu.ts_code", "pl.ts_code") + ` AND tlu.trade_date = c.trade_date AND tlu.` + "`limit`" + ` = 'U'
LEFT JOIN limit_list_d tlz
ON ` + dbcompat.TsCodeEqual("tlz.ts_code", "pl.ts_code") + ` AND tlz.trade_date = c.trade_date AND tlz.` + "`limit`" + ` = 'Z'
LEFT JOIN stock_basic sb
ON sb.ts_code = pl.ts_code COLLATE utf8mb4_unicode_ci
WHERE c.trade_date BETWEEN ? AND ?
AND c.previous_trade_date IS NOT NULL
ON ` + dbcompat.TsCodeEqual("sb.ts_code", "pl.ts_code") + `
WHERE c.previous_trade_date IS NOT NULL
AND c.previous_two_trade_date IS NOT NULL
AND ppl.ts_code IS NULL
AND d.high IS NOT NULL
AND d.close IS NOT NULL
)
SELECT
DATE_FORMAT(trade_date, '%Y-%m-%d') AS trade_date,
DATE_FORMAT(previous_trade_date, '%Y-%m-%d') AS previous_trade_date,
DATE_FORMAT(previous_two_trade_date, '%Y-%m-%d') AS previous_two_trade_date,
COALESCE(DATE_FORMAT(next_trade_date, '%Y-%m-%d'), '') AS next_trade_date,
COALESCE(symbol, '') AS symbol,
ts_code,
name,
market,
industry,
previous_close,
limit_price,
t_high,
t_close,
t_pct_chg,
status,
next_high,
next_close
FROM candidate
) candidate
WHERE previous_close IS NOT NULL
AND limit_price IS NOT NULL
AND limit_price > 0
@@ -206,7 +219,7 @@ WHERE previous_close IS NOT NULL
ORDER BY trade_date ASC, ts_code ASC`
var rows []secondCalRow
err := s.DB.Raw(query, startDate, endDate).Scan(&rows).Error
err = s.DB.Raw(query, calendarArgs...).Scan(&rows).Error
return rows, err
}
@@ -228,9 +241,13 @@ func buildSecondCalStock(row secondCalRow) model.SecondCalStock {
TClose: row.TClose,
TPctChg: row.TPctChg,
Status: row.Status,
OpenTimes: row.OpenTimes,
NextHigh: row.NextHigh,
NextClose: row.NextClose,
}
if row.FirstLimitTime != nil {
item.FirstLimitTime = formatLimitTime(*row.FirstLimitTime)
}
if row.THigh != nil && *row.THigh > 0 && row.NextHigh != nil {
// T+1 盘中盈利统一按 T+1 最高价是否高于 T 日最高价判断。