适配mysql
This commit is contained in:
@@ -0,0 +1,149 @@
|
||||
ALTER DATABASE `level` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||
|
||||
CREATE TABLE `limit_list_d` (
|
||||
`id` bigint NOT NULL AUTO_INCREMENT,
|
||||
`trade_date` date NOT NULL COMMENT '交易日期',
|
||||
`ts_code` varchar(20) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '股票代码',
|
||||
`industry` varchar(100) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '所属行业',
|
||||
`name` varchar(50) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '股票名称',
|
||||
`close` decimal(16,4) DEFAULT NULL COMMENT '收盘价',
|
||||
`pct_chg` decimal(10,4) DEFAULT NULL COMMENT '涨跌幅',
|
||||
`amount` decimal(24,4) DEFAULT NULL COMMENT '成交额',
|
||||
`limit_amount` decimal(24,4) DEFAULT NULL COMMENT '板上成交金额',
|
||||
`float_mv` decimal(24,4) DEFAULT NULL COMMENT '流通市值',
|
||||
`total_mv` decimal(24,4) DEFAULT NULL COMMENT '总市值',
|
||||
`turnover_ratio` decimal(10,4) DEFAULT NULL COMMENT '换手率',
|
||||
`fd_amount` decimal(24,4) DEFAULT NULL COMMENT '封单金额',
|
||||
`first_time` varchar(20) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '首次封板时间',
|
||||
`last_time` varchar(20) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '最后封板时间',
|
||||
`open_times` int DEFAULT NULL COMMENT '打开次数',
|
||||
`up_stat` varchar(50) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '涨停统计',
|
||||
`limit_times` int DEFAULT NULL COMMENT '连板数',
|
||||
`limit` varchar(1) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT 'D跌停 U涨停 Z炸板',
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `uniq_trade_code_limit` (`trade_date`,`ts_code`,`limit`),
|
||||
KEY `idx_trade_date` (`trade_date`),
|
||||
KEY `idx_ts_code` (`ts_code`),
|
||||
KEY `idx_limit` (`limit`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
|
||||
CREATE TABLE `stock_basic` (
|
||||
`ts_code` text COLLATE utf8mb4_unicode_ci,
|
||||
`symbol` text COLLATE utf8mb4_unicode_ci,
|
||||
`name` text COLLATE utf8mb4_unicode_ci,
|
||||
`area` text COLLATE utf8mb4_unicode_ci,
|
||||
`industry` text COLLATE utf8mb4_unicode_ci,
|
||||
`fullname` text COLLATE utf8mb4_unicode_ci,
|
||||
`enname` text COLLATE utf8mb4_unicode_ci,
|
||||
`cnspell` text COLLATE utf8mb4_unicode_ci,
|
||||
`market` text COLLATE utf8mb4_unicode_ci,
|
||||
`exchange` text COLLATE utf8mb4_unicode_ci,
|
||||
`curr_type` text COLLATE utf8mb4_unicode_ci,
|
||||
`list_status` text COLLATE utf8mb4_unicode_ci,
|
||||
`list_date` text COLLATE utf8mb4_unicode_ci,
|
||||
`delist_date` text COLLATE utf8mb4_unicode_ci,
|
||||
`is_hs` text COLLATE utf8mb4_unicode_ci,
|
||||
`act_name` text COLLATE utf8mb4_unicode_ci,
|
||||
`act_ent_type` text COLLATE utf8mb4_unicode_ci
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
|
||||
CREATE TABLE `stock_daily` (
|
||||
`id` bigint NOT NULL AUTO_INCREMENT,
|
||||
`ts_code` varchar(20) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`symbol` varchar(10) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`trade_date` date NOT NULL,
|
||||
`open` decimal(16,4) DEFAULT NULL,
|
||||
`high` decimal(16,4) DEFAULT NULL,
|
||||
`low` decimal(16,4) DEFAULT NULL,
|
||||
`close` decimal(16,4) DEFAULT NULL,
|
||||
`pre_close` decimal(16,4) DEFAULT NULL,
|
||||
`change_val` decimal(16,4) DEFAULT NULL,
|
||||
`pct_chg` decimal(10,4) DEFAULT NULL,
|
||||
`vol` bigint DEFAULT NULL,
|
||||
`amount` bigint DEFAULT NULL,
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `uniq_code_date` (`ts_code`,`trade_date`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
|
||||
CREATE TABLE `stock_limit_combined_data` (
|
||||
`id` bigint NOT NULL AUTO_INCREMENT,
|
||||
`trade_date` date NOT NULL COMMENT '交易日期',
|
||||
`symbol` varchar(10) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '股票代码,以stock_daily为准',
|
||||
`ts_code` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT 'TS股票代码,以stock_daily为准',
|
||||
`name` varchar(50) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '股票名称,以stock_daily关联基础表为准',
|
||||
`final_status` tinyint NOT NULL COMMENT '融合状态:1-涨停,2-炸板',
|
||||
`source_count` int NOT NULL DEFAULT '0' COMMENT '命中的数据源数量',
|
||||
`source_flags` varchar(50) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '命中的数据源:calc,tushare',
|
||||
`calc_status` tinyint DEFAULT NULL COMMENT '本地行情计算状态:1-涨停,2-炸板',
|
||||
`calc_limit_price` decimal(16,4) DEFAULT NULL COMMENT '本地行情计算涨停价',
|
||||
`calc_high` decimal(16,4) DEFAULT NULL COMMENT 'stock_daily最高价',
|
||||
`calc_close` decimal(16,4) DEFAULT NULL COMMENT 'stock_daily收盘价',
|
||||
`calc_pre_close` decimal(16,4) DEFAULT NULL COMMENT 'stock_daily前收盘价',
|
||||
`calc_vol` bigint DEFAULT NULL COMMENT 'stock_daily成交量',
|
||||
`market` varchar(20) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '所属板块',
|
||||
`tushare_limit_type` varchar(1) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT 'Tushare-D跌停U涨停Z炸板',
|
||||
`tushare_industry` varchar(100) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT 'Tushare-所属行业',
|
||||
`tushare_name` varchar(50) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT 'Tushare-名称',
|
||||
`tushare_close` decimal(16,4) DEFAULT NULL COMMENT 'Tushare-收盘价',
|
||||
`tushare_pct_chg` decimal(10,4) DEFAULT NULL COMMENT 'Tushare-涨跌幅',
|
||||
`tushare_amount` decimal(24,4) DEFAULT NULL COMMENT 'Tushare-成交额',
|
||||
`tushare_limit_amount` decimal(24,4) DEFAULT NULL COMMENT 'Tushare-板上成交金额',
|
||||
`tushare_float_mv` decimal(24,4) DEFAULT NULL COMMENT 'Tushare-流通市值',
|
||||
`tushare_total_mv` decimal(24,4) DEFAULT NULL COMMENT 'Tushare-总市值',
|
||||
`tushare_turnover_ratio` decimal(10,4) DEFAULT NULL COMMENT 'Tushare-换手率',
|
||||
`tushare_fd_amount` decimal(24,4) DEFAULT NULL COMMENT 'Tushare-封单金额',
|
||||
`tushare_first_time` varchar(20) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT 'Tushare-首次封板时间',
|
||||
`tushare_last_time` varchar(20) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT 'Tushare-最后封板时间',
|
||||
`tushare_open_times` int DEFAULT NULL COMMENT 'Tushare-炸板/开板次数',
|
||||
`tushare_up_stat` varchar(50) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT 'Tushare-涨停统计',
|
||||
`tushare_limit_times` int DEFAULT NULL COMMENT 'Tushare-连板数',
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `uniq_date_code` (`trade_date`,`ts_code`),
|
||||
KEY `idx_trade_date` (`trade_date`),
|
||||
KEY `idx_symbol` (`symbol`),
|
||||
KEY `idx_trade_date_status` (`trade_date`,`final_status`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
|
||||
CREATE TABLE `trade_calendar` (
|
||||
`cal_date` date NOT NULL,
|
||||
`exchange` varchar(10) COLLATE utf8mb4_unicode_ci NOT NULL,
|
||||
`is_open` tinyint DEFAULT NULL,
|
||||
`pretrade_date` date DEFAULT NULL,
|
||||
PRIMARY KEY (`cal_date`,`exchange`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
|
||||
CREATE TABLE `stock_limit_data` (
|
||||
`id` bigint NOT NULL AUTO_INCREMENT,
|
||||
`trade_date` date NOT NULL COMMENT '交易日期',
|
||||
`symbol` varchar(10) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '股票代码',
|
||||
`ts_code` varchar(20) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT 'TS股票代码',
|
||||
`name` varchar(50) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '股票名称',
|
||||
`status` tinyint NOT NULL COMMENT '状态:1-涨停,2-炸板',
|
||||
`limit_price` decimal(16,4) DEFAULT NULL COMMENT '计算出的理论涨停价',
|
||||
`close` decimal(16,4) DEFAULT NULL COMMENT '实际收盘价',
|
||||
`vol` bigint DEFAULT NULL COMMENT '成交量',
|
||||
`market` varchar(20) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '所属板块',
|
||||
`ak_code` varchar(10) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT 'AKShare-代码',
|
||||
`ak_name` varchar(50) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT 'AKShare-名称',
|
||||
`ak_pct_chg` decimal(10,4) DEFAULT NULL COMMENT 'AKShare-涨跌幅',
|
||||
`ak_latest_price` decimal(16,4) DEFAULT NULL COMMENT 'AKShare-最新价',
|
||||
`ak_amount` decimal(24,4) DEFAULT NULL COMMENT 'AKShare-成交额',
|
||||
`ak_float_market_value` decimal(24,4) DEFAULT NULL COMMENT 'AKShare-流通市值',
|
||||
`ak_total_market_value` decimal(24,4) DEFAULT NULL COMMENT 'AKShare-总市值',
|
||||
`ak_turnover_rate` decimal(10,4) DEFAULT NULL COMMENT 'AKShare-换手率',
|
||||
`ak_seal_amount` decimal(24,4) DEFAULT NULL COMMENT 'AKShare-封板资金',
|
||||
`ak_first_limit_time` varchar(20) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT 'AKShare-首次封板时间',
|
||||
`ak_last_limit_time` varchar(20) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT 'AKShare-最后封板时间',
|
||||
`ak_break_limit_count` int DEFAULT NULL COMMENT 'AKShare-炸板次数',
|
||||
`ak_limit_up_stat` varchar(50) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT 'AKShare-涨停统计',
|
||||
`ak_limit_up_days` int DEFAULT NULL COMMENT 'AKShare-连板数',
|
||||
`ak_industry` varchar(100) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT 'AKShare-所属行业',
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `uniq_date_code` (`trade_date`,`ts_code`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
ALTER DATABASE `level` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||
|
||||
ALTER TABLE `limit_list_d`
|
||||
CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||
|
||||
ALTER TABLE `stock_basic`
|
||||
CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||
|
||||
ALTER TABLE `stock_daily`
|
||||
CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||
|
||||
ALTER TABLE `stock_limit_combined_data`
|
||||
CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||
|
||||
ALTER TABLE `trade_calendar`
|
||||
CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||
|
||||
ALTER TABLE `stock_limit_data`
|
||||
CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
|
||||
@@ -32,6 +32,7 @@ GET /api/v1/limit-board/second-cal?tradeStartDate=2026-05-07&tradeEndDate=2026-0
|
||||
- 涨停价比例按板块计算:北交所 30%,创业板/科创板 20%,其余名称包含 ST 的股票 5%,其他股票 10%。
|
||||
- T 日 `close` 与 `high` 一致时,计为二板封板,`status = "limit"`。
|
||||
- T 日 `close` 小于 `high` 时,计为二板炸板,`status = "break"`。
|
||||
- `first_limit_time` 和 `open_times` 来自 T 日二板记录,封板取 `limit_list_d.limit = 'U'` 记录,炸板取 `limit_list_d.limit = 'Z'` 记录;时间格式化为 `HH:MM:SS`。
|
||||
- T+1 盘中盈利统一按 `T+1 high > T high` 判断,概率分母只统计具备 T 日最高价和 T+1 最高价的数据。
|
||||
|
||||
## 4. 响应字段
|
||||
@@ -75,6 +76,8 @@ GET /api/v1/limit-board/second-cal?tradeStartDate=2026-05-07&tradeEndDate=2026-0
|
||||
| `t_high` / `t_close` | `number \| null` | T 日最高价和收盘价 |
|
||||
| `t_pct_chg` | `number \| null` | T 日收盘涨跌幅 |
|
||||
| `status` | `string` | `limit` 表示封板,`break` 表示炸板 |
|
||||
| `first_limit_time` | `string` | T 日二板首次涨停时间,格式化为 `HH:MM:SS`;无数据时为空字符串 |
|
||||
| `open_times` | `int \| null` | T 日二板开板次数 |
|
||||
| `next_high` / `next_close` | `number \| null` | T+1 最高价和收盘价 |
|
||||
| `next_intraday_profitable` | `boolean \| null` | T+1 盘中是否盈利 |
|
||||
| `next_high_profit_pct` | `number \| null` | T+1 最高价相对 T 日最高价的收益率 |
|
||||
@@ -135,6 +138,8 @@ GET /api/v1/limit-board/second-cal?tradeStartDate=2026-05-07&tradeEndDate=2026-0
|
||||
"t_close": 11,
|
||||
"t_pct_chg": 10,
|
||||
"status": "limit",
|
||||
"first_limit_time": "09:35:12",
|
||||
"open_times": 0,
|
||||
"next_high": 11.2,
|
||||
"next_close": 11.1,
|
||||
"next_intraday_profitable": true,
|
||||
|
||||
@@ -11,6 +11,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"level_go/internal/dbcompat"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -336,21 +337,21 @@ func validateSecondProfitDateRange(startDate, endDate string) error {
|
||||
// SQL 先找出“前一日涨停、前两日未涨停、当日继续涨停”的股票,再补齐 T+1 行情;
|
||||
// Go 代码负责应用动态过滤条件并计算开盘、最高、最低、收盘相对 T 日收盘的收益率。
|
||||
func (h *LimitBoardHandler) loadSecondProfitRows(req secondProfitQuery) ([]secondProfitCSVRow, error) {
|
||||
seeds, err := dbcompat.LoadSSEOpenCalendarSeeds(h.Service.DB, req.TradeStartDate, req.TradeEndDate)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(seeds) == 0 {
|
||||
return []secondProfitCSVRow{}, nil
|
||||
}
|
||||
calendarTable, calendarArgs := dbcompat.CalendarSeedTable(seeds)
|
||||
|
||||
query := `
|
||||
WITH open_calendar AS (
|
||||
SELECT
|
||||
DATE_FORMAT(cal_date, '%Y-%m-%d') AS trade_date,
|
||||
LAG(DATE_FORMAT(cal_date, '%Y-%m-%d'), 1) OVER (ORDER BY cal_date) AS previous_trade_date,
|
||||
LAG(DATE_FORMAT(cal_date, '%Y-%m-%d'), 2) OVER (ORDER BY cal_date) AS previous_two_trade_date,
|
||||
LEAD(DATE_FORMAT(cal_date, '%Y-%m-%d'), 1) OVER (ORDER BY cal_date) AS next_trade_date
|
||||
FROM trade_calendar
|
||||
WHERE exchange = 'SSE' AND is_open = 1
|
||||
)
|
||||
SELECT
|
||||
c.trade_date,
|
||||
c.previous_trade_date,
|
||||
c.previous_two_trade_date,
|
||||
COALESCE(c.next_trade_date, '') AS next_trade_date,
|
||||
DATE_FORMAT(c.trade_date, '%Y-%m-%d') AS trade_date,
|
||||
DATE_FORMAT(c.previous_trade_date, '%Y-%m-%d') AS previous_trade_date,
|
||||
DATE_FORMAT(c.previous_two_trade_date, '%Y-%m-%d') AS previous_two_trade_date,
|
||||
COALESCE(DATE_FORMAT(c.next_trade_date, '%Y-%m-%d'), '') AS next_trade_date,
|
||||
l.ts_code,
|
||||
COALESCE(l.symbol, '') AS symbol,
|
||||
COALESCE(l.name, '') AS name,
|
||||
@@ -373,29 +374,28 @@ SELECT
|
||||
nd.high AS next_high,
|
||||
nd.low AS next_low,
|
||||
nd.close AS next_close
|
||||
FROM open_calendar c
|
||||
FROM (` + calendarTable + `) c
|
||||
INNER JOIN stock_limit_combined_data l
|
||||
ON l.trade_date = c.trade_date AND l.final_status = 1
|
||||
INNER JOIN stock_limit_combined_data pl
|
||||
ON pl.ts_code = l.ts_code AND pl.trade_date = c.previous_trade_date AND pl.final_status = 1
|
||||
ON ` + dbcompat.TsCodeEqual("pl.ts_code", "l.ts_code") + ` AND pl.trade_date = c.previous_trade_date AND pl.final_status = 1
|
||||
LEFT JOIN stock_limit_combined_data ppl
|
||||
ON ppl.ts_code = l.ts_code AND ppl.trade_date = c.previous_two_trade_date AND ppl.final_status = 1
|
||||
ON ` + dbcompat.TsCodeEqual("ppl.ts_code", "l.ts_code") + ` AND ppl.trade_date = c.previous_two_trade_date AND ppl.final_status = 1
|
||||
LEFT JOIN stock_daily d
|
||||
ON l.ts_code = d.ts_code AND d.trade_date = l.trade_date
|
||||
ON ` + dbcompat.TsCodeEqual("l.ts_code", "d.ts_code") + ` AND d.trade_date = l.trade_date
|
||||
LEFT JOIN stock_daily pd
|
||||
ON l.ts_code = pd.ts_code AND pd.trade_date = c.previous_trade_date
|
||||
ON ` + dbcompat.TsCodeEqual("l.ts_code", "pd.ts_code") + ` AND pd.trade_date = c.previous_trade_date
|
||||
LEFT JOIN stock_daily ppd
|
||||
ON l.ts_code = ppd.ts_code AND ppd.trade_date = c.previous_two_trade_date
|
||||
ON ` + dbcompat.TsCodeEqual("l.ts_code", "ppd.ts_code") + ` AND ppd.trade_date = c.previous_two_trade_date
|
||||
LEFT JOIN stock_daily nd
|
||||
ON l.ts_code = nd.ts_code AND nd.trade_date = c.next_trade_date
|
||||
WHERE c.trade_date BETWEEN ? AND ?
|
||||
AND c.previous_trade_date IS NOT NULL
|
||||
ON ` + dbcompat.TsCodeEqual("l.ts_code", "nd.ts_code") + ` AND nd.trade_date = c.next_trade_date
|
||||
WHERE c.previous_trade_date IS NOT NULL
|
||||
AND c.previous_two_trade_date IS NOT NULL
|
||||
AND ppl.ts_code IS NULL
|
||||
ORDER BY c.trade_date ASC, l.ts_code ASC`
|
||||
|
||||
var rawRows []secondProfitCSVRow
|
||||
if err := h.Service.DB.Raw(query, req.TradeStartDate, req.TradeEndDate).Scan(&rawRows).Error; err != nil {
|
||||
if err := h.Service.DB.Raw(query, calendarArgs...).Scan(&rawRows).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,147 @@
|
||||
package dbcompat
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// CalendarSeed is a MySQL-version-neutral replacement for LAG/LEAD over the
|
||||
// trade calendar. Keeping the adjacent dates in Go avoids depending on MySQL 8.
|
||||
type CalendarSeed struct {
|
||||
TradeDate string
|
||||
PreviousTradeDate string
|
||||
PreviousTwoTradeDate string
|
||||
NextTradeDate string
|
||||
}
|
||||
|
||||
type calendarDateRow struct {
|
||||
TradeDate string `gorm:"column:trade_date"`
|
||||
}
|
||||
|
||||
type columnCollationRow struct {
|
||||
CollationName string `gorm:"column:collation_name"`
|
||||
}
|
||||
|
||||
// LoadSSEOpenCalendarSeeds loads all SSE open dates and builds the date window
|
||||
// needed by range queries. The returned seeds are limited to [startDate, endDate].
|
||||
func LoadSSEOpenCalendarSeeds(db *gorm.DB, startDate, endDate string) ([]CalendarSeed, error) {
|
||||
var rows []calendarDateRow
|
||||
if err := db.Table("trade_calendar").
|
||||
Select("DATE_FORMAT(cal_date, '%Y-%m-%d') AS trade_date").
|
||||
Where("exchange = ? AND is_open = ?", "SSE", 1).
|
||||
Order("cal_date ASC").
|
||||
Scan(&rows).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
seeds := make([]CalendarSeed, 0)
|
||||
for i, row := range rows {
|
||||
if row.TradeDate < startDate || row.TradeDate > endDate {
|
||||
continue
|
||||
}
|
||||
|
||||
seed := CalendarSeed{TradeDate: row.TradeDate}
|
||||
if i > 0 {
|
||||
seed.PreviousTradeDate = rows[i-1].TradeDate
|
||||
}
|
||||
if i > 1 {
|
||||
seed.PreviousTwoTradeDate = rows[i-2].TradeDate
|
||||
}
|
||||
if i+1 < len(rows) {
|
||||
seed.NextTradeDate = rows[i+1].TradeDate
|
||||
}
|
||||
seeds = append(seeds, seed)
|
||||
}
|
||||
|
||||
return seeds, nil
|
||||
}
|
||||
|
||||
// CalendarSeedTable converts calendar seeds into a derived table that works on
|
||||
// MySQL 5.7, MySQL 8, and MariaDB without CTE/window-function support.
|
||||
func CalendarSeedTable(seeds []CalendarSeed) (string, []any) {
|
||||
if len(seeds) == 0 {
|
||||
return `SELECT
|
||||
CAST(NULL AS DATE) AS trade_date,
|
||||
CAST(NULL AS DATE) AS previous_trade_date,
|
||||
CAST(NULL AS DATE) AS previous_two_trade_date,
|
||||
CAST(NULL AS DATE) AS next_trade_date
|
||||
WHERE 1 = 0`, nil
|
||||
}
|
||||
|
||||
parts := make([]string, 0, len(seeds))
|
||||
args := make([]any, 0, len(seeds)*4)
|
||||
for _, seed := range seeds {
|
||||
parts = append(parts, `SELECT
|
||||
CAST(? AS DATE) AS trade_date,
|
||||
CAST(? AS DATE) AS previous_trade_date,
|
||||
CAST(? AS DATE) AS previous_two_trade_date,
|
||||
CAST(? AS DATE) AS next_trade_date`)
|
||||
args = append(args,
|
||||
nullableDate(seed.TradeDate),
|
||||
nullableDate(seed.PreviousTradeDate),
|
||||
nullableDate(seed.PreviousTwoTradeDate),
|
||||
nullableDate(seed.NextTradeDate),
|
||||
)
|
||||
}
|
||||
|
||||
return strings.Join(parts, "\nUNION ALL\n"), args
|
||||
}
|
||||
|
||||
// TsCodeEqual keeps joins free of hard-coded COLLATE clauses. This avoids
|
||||
// binding SQL to MySQL-8-only collations while preserving index-friendly joins.
|
||||
func TsCodeEqual(left, right string) string {
|
||||
return left + " = " + right
|
||||
}
|
||||
|
||||
// ColumnCollation returns the current database collation for one column.
|
||||
func ColumnCollation(db *gorm.DB, tableName, columnName string) (string, error) {
|
||||
var row columnCollationRow
|
||||
err := db.Raw(`
|
||||
SELECT COLLATION_NAME AS collation_name
|
||||
FROM information_schema.COLUMNS
|
||||
WHERE TABLE_SCHEMA = DATABASE()
|
||||
AND TABLE_NAME = ?
|
||||
AND COLUMN_NAME = ?
|
||||
LIMIT 1`, tableName, columnName).Scan(&row).Error
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return row.CollationName, nil
|
||||
}
|
||||
|
||||
// TsCodeEqualUnicode compares a utf8mb4_unicode_ci column with a column that
|
||||
// may be utf8mb4_0900_ai_ci. utf8mb4_unicode_ci exists on both MySQL 5.7 and 8.
|
||||
func TsCodeEqualUnicode(left, right string) string {
|
||||
return left + " = " + right + " COLLATE utf8mb4_unicode_ci"
|
||||
}
|
||||
|
||||
// TsCodeEqualRightCollation compares two stock-code columns using a collation
|
||||
// known to exist on the connected database. This avoids MySQL-version-specific
|
||||
// literals while preserving the fast side of joins such as stock_daily.ts_code.
|
||||
func TsCodeEqualRightCollation(left, right, collation string) string {
|
||||
if !safeCollationName(collation) {
|
||||
return TsCodeEqual(left, right)
|
||||
}
|
||||
return left + " = " + right + " COLLATE " + collation
|
||||
}
|
||||
|
||||
func safeCollationName(collation string) bool {
|
||||
if collation == "" {
|
||||
return false
|
||||
}
|
||||
for _, r := range collation {
|
||||
if (r >= 'a' && r <= 'z') || (r >= 'A' && r <= 'Z') || (r >= '0' && r <= '9') || r == '_' {
|
||||
continue
|
||||
}
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func nullableDate(date string) any {
|
||||
if strings.TrimSpace(date) == "" {
|
||||
return nil
|
||||
}
|
||||
return date
|
||||
}
|
||||
@@ -157,6 +157,8 @@ type SecondCalStock struct {
|
||||
TClose *float64 `json:"t_close"` // T 日收盘价。
|
||||
TPctChg *float64 `json:"t_pct_chg"` // T 日收盘涨跌幅。
|
||||
Status string `json:"status"` // T 日二板状态:limit 表示封板,break 表示炸板。
|
||||
FirstLimitTime string `json:"first_limit_time"` // T 日二板首次涨停时间,格式化为 HH:MM:SS。
|
||||
OpenTimes *int `json:"open_times"` // T 日二板开板次数。
|
||||
NextHigh *float64 `json:"next_high"` // T+1 日最高价。
|
||||
NextClose *float64 `json:"next_close"` // T+1 日收盘价。
|
||||
NextIntradayProfitable *bool `json:"next_intraday_profitable"` // T+1 盘中最高价是否高于 T 日最高价。
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"level_go/internal/dbcompat"
|
||||
"level_go/internal/model"
|
||||
)
|
||||
|
||||
@@ -104,17 +105,18 @@ func (s *LimitBoardService) ListLimitBreakNextPerformanceStocks(req model.LimitB
|
||||
//
|
||||
// 口径:前一交易日涨停、前两交易日未涨停、当前交易日 final_status = 2。
|
||||
func (s *LimitBoardService) loadLimitBreakNextPerformanceRows(startDate, endDate string) ([]limitBreakNextPerformanceRow, error) {
|
||||
seeds, err := dbcompat.LoadSSEOpenCalendarSeeds(s.DB, startDate, endDate)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(seeds) == 0 {
|
||||
return []limitBreakNextPerformanceRow{}, nil
|
||||
}
|
||||
calendarTable, calendarArgs := dbcompat.CalendarSeedTable(seeds)
|
||||
|
||||
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 *
|
||||
FROM (
|
||||
SELECT
|
||||
DATE_FORMAT(c.trade_date, '%Y-%m-%d') AS trade_date,
|
||||
DATE_FORMAT(c.previous_trade_date, '%Y-%m-%d') AS previous_trade_date,
|
||||
@@ -141,34 +143,31 @@ candidate AS (
|
||||
nd.high AS next_high,
|
||||
nd.low AS next_low,
|
||||
nd.close AS next_close
|
||||
FROM open_calendar c
|
||||
FROM (` + calendarTable + `) c
|
||||
INNER JOIN stock_limit_combined_data l
|
||||
ON l.trade_date = c.trade_date AND l.final_status = 2
|
||||
INNER JOIN stock_limit_combined_data pl
|
||||
ON pl.ts_code = l.ts_code AND pl.trade_date = c.previous_trade_date AND pl.final_status = 1
|
||||
ON ` + dbcompat.TsCodeEqual("pl.ts_code", "l.ts_code") + ` AND pl.trade_date = c.previous_trade_date AND pl.final_status = 1
|
||||
LEFT JOIN stock_limit_combined_data ppl
|
||||
ON ppl.ts_code = l.ts_code AND ppl.trade_date = c.previous_two_trade_date AND ppl.final_status = 1
|
||||
ON ` + dbcompat.TsCodeEqual("ppl.ts_code", "l.ts_code") + ` AND ppl.trade_date = c.previous_two_trade_date AND ppl.final_status = 1
|
||||
LEFT JOIN stock_daily d
|
||||
ON d.ts_code = l.ts_code AND d.trade_date = c.trade_date
|
||||
ON ` + dbcompat.TsCodeEqual("d.ts_code", "l.ts_code") + ` AND d.trade_date = c.trade_date
|
||||
LEFT JOIN stock_daily pd
|
||||
ON pd.ts_code = l.ts_code AND pd.trade_date = c.previous_trade_date
|
||||
ON ` + dbcompat.TsCodeEqual("pd.ts_code", "l.ts_code") + ` AND pd.trade_date = c.previous_trade_date
|
||||
LEFT JOIN stock_daily nd
|
||||
ON nd.ts_code = l.ts_code AND nd.trade_date = c.next_trade_date
|
||||
ON ` + dbcompat.TsCodeEqual("nd.ts_code", "l.ts_code") + ` AND nd.trade_date = c.next_trade_date
|
||||
LEFT JOIN stock_basic sb
|
||||
ON sb.ts_code = l.ts_code COLLATE utf8mb4_unicode_ci
|
||||
WHERE c.trade_date BETWEEN ? AND ?
|
||||
AND c.previous_trade_date IS NOT NULL
|
||||
ON ` + dbcompat.TsCodeEqualUnicode("sb.ts_code", "l.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
|
||||
)
|
||||
SELECT *
|
||||
FROM candidate
|
||||
) candidate
|
||||
WHERE previous_close IS NOT NULL
|
||||
AND t_close IS NOT NULL
|
||||
ORDER BY trade_date ASC, ts_code ASC`
|
||||
|
||||
var rows []limitBreakNextPerformanceRow
|
||||
err := s.DB.Raw(query, startDate, endDate).Scan(&rows).Error
|
||||
err = s.DB.Raw(query, calendarArgs...).Scan(&rows).Error
|
||||
return rows, err
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"level_go/internal/dbcompat"
|
||||
"level_go/internal/model"
|
||||
)
|
||||
|
||||
@@ -146,7 +147,7 @@ func (s *LimitBoardService) loadLimitOpenCompareStocks(tradeDate, previousTradeD
|
||||
(? <> '' AND NOT EXISTS (
|
||||
SELECT 1
|
||||
FROM stock_limit_combined_data ppl
|
||||
WHERE ppl.ts_code = l.ts_code AND ppl.trade_date = ? AND ppl.final_status = 1
|
||||
WHERE ` + dbcompat.TsCodeEqual("ppl.ts_code", "l.ts_code") + ` AND ppl.trade_date = ? AND ppl.final_status = 1
|
||||
)) AS is_first_to_second_board,
|
||||
COALESCE(l.calc_limit_price, 0) AS limit_price,
|
||||
COALESCE(l.tushare_pct_chg, d.pct_chg, 0) AS pct_chg,
|
||||
@@ -164,11 +165,11 @@ func (s *LimitBoardService) loadLimitOpenCompareStocks(tradeDate, previousTradeD
|
||||
l.calc_vol AS vol
|
||||
FROM stock_limit_combined_data l
|
||||
LEFT JOIN stock_daily d
|
||||
ON l.ts_code = d.ts_code AND d.trade_date = l.trade_date
|
||||
ON ` + dbcompat.TsCodeEqual("l.ts_code", "d.ts_code") + ` AND d.trade_date = l.trade_date
|
||||
LEFT JOIN stock_daily pd
|
||||
ON l.ts_code = pd.ts_code AND pd.trade_date = ?
|
||||
ON ` + dbcompat.TsCodeEqual("l.ts_code", "pd.ts_code") + ` AND pd.trade_date = ?
|
||||
INNER JOIN stock_limit_combined_data pl
|
||||
ON pl.ts_code = l.ts_code AND pl.trade_date = ? AND pl.final_status = 1
|
||||
ON ` + dbcompat.TsCodeEqual("pl.ts_code", "l.ts_code") + ` AND pl.trade_date = ? AND pl.final_status = 1
|
||||
WHERE l.trade_date = ? AND l.final_status = 1
|
||||
ORDER BY d.pct_chg DESC, l.ts_code ASC`
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ package service
|
||||
import (
|
||||
"sort"
|
||||
|
||||
"level_go/internal/dbcompat"
|
||||
"level_go/internal/model"
|
||||
)
|
||||
|
||||
@@ -93,11 +94,11 @@ func (s *LimitBoardService) loadLimitOpenThresholdStocks(tradeDate, previousTrad
|
||||
l.calc_vol AS vol
|
||||
FROM stock_limit_combined_data l
|
||||
LEFT JOIN stock_daily d
|
||||
ON l.ts_code = d.ts_code AND d.trade_date = l.trade_date
|
||||
ON ` + dbcompat.TsCodeEqual("l.ts_code", "d.ts_code") + ` AND d.trade_date = l.trade_date
|
||||
LEFT JOIN stock_daily pd
|
||||
ON l.ts_code = pd.ts_code AND pd.trade_date = ?
|
||||
ON ` + dbcompat.TsCodeEqual("l.ts_code", "pd.ts_code") + ` AND pd.trade_date = ?
|
||||
INNER JOIN stock_limit_combined_data pl
|
||||
ON pl.ts_code = l.ts_code AND pl.trade_date = ? AND pl.final_status = 1
|
||||
ON ` + dbcompat.TsCodeEqual("pl.ts_code", "l.ts_code") + ` AND pl.trade_date = ? AND pl.final_status = 1
|
||||
WHERE l.trade_date = ? AND l.final_status = 1
|
||||
ORDER BY d.pct_chg DESC, l.ts_code ASC`
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ package service
|
||||
import (
|
||||
"sort"
|
||||
|
||||
"level_go/internal/dbcompat"
|
||||
"level_go/internal/model"
|
||||
)
|
||||
|
||||
@@ -146,7 +147,7 @@ func (s *LimitBoardService) loadCurrentLimitStocks(tradeDate string) ([]model.Li
|
||||
l.calc_vol AS vol
|
||||
FROM stock_limit_combined_data l
|
||||
LEFT JOIN stock_daily d
|
||||
ON l.ts_code = d.ts_code AND d.trade_date = l.trade_date
|
||||
ON ` + dbcompat.TsCodeEqual("l.ts_code", "d.ts_code") + ` AND d.trade_date = l.trade_date
|
||||
WHERE l.trade_date = ? AND l.final_status = 1
|
||||
ORDER BY d.pct_chg DESC, l.ts_code ASC`
|
||||
|
||||
|
||||
@@ -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 (
|
||||
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 日最高价判断。
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"sort"
|
||||
|
||||
"gorm.io/gorm"
|
||||
"level_go/internal/dbcompat"
|
||||
"level_go/internal/model"
|
||||
)
|
||||
|
||||
@@ -45,7 +46,7 @@ func (s *StockService) AnalyzePerformance(startDate, endDate string) ([]model.An
|
||||
|
||||
// 查询 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
|
||||
LEFT JOIN stock_daily d ON ` + dbcompat.TsCodeEqual("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 {
|
||||
|
||||
Reference in New Issue
Block a user