彻底解决 sql 兼容性问题 #6
@@ -19,7 +19,7 @@
|
|||||||
| `tOpenGainLt101` | `bool` | 否 | 无 | 筛选 T 日开盘价是否小于 T-1 收盘价的 101%,支持别名 `tOpenLessThan101`、`t_open_gain_lt_101` |
|
| `tOpenGainLt101` | `bool` | 否 | 无 | 筛选 T 日开盘价是否小于 T-1 收盘价的 101%,支持别名 `tOpenLessThan101`、`t_open_gain_lt_101` |
|
||||||
| `breakTimeBefore1100` | `bool` | 否 | 无 | 筛选 T 日炸板时间是否在 `11:00:00` 前,空炸板时间按 `true` 处理,支持别名 `tBreakTimeBefore1100`、`break_time_before_1100` |
|
| `breakTimeBefore1100` | `bool` | 否 | 无 | 筛选 T 日炸板时间是否在 `11:00:00` 前,空炸板时间按 `true` 处理,支持别名 `tBreakTimeBefore1100`、`break_time_before_1100` |
|
||||||
|
|
||||||
兼容 `start_date`、`end_date` 作为日期参数别名。
|
兼容 `start_date`、`end_date`、`trade_start_date`、`trade_end_date` 作为日期参数别名。
|
||||||
|
|
||||||
示例:
|
示例:
|
||||||
|
|
||||||
|
|||||||
@@ -1,149 +0,0 @@
|
|||||||
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;
|
|
||||||
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
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;
|
|
||||||
@@ -112,8 +112,8 @@ func parseSecondCalRequest(c *gin.Context) (model.SecondCalRequest, error) {
|
|||||||
// includePagination 为 false 时跳过分页参数,供 CSV 导出复用同一套日期和过滤条件解析逻辑。
|
// includePagination 为 false 时跳过分页参数,供 CSV 导出复用同一套日期和过滤条件解析逻辑。
|
||||||
func parseLimitBreakNextPerformanceRequest(c *gin.Context, includePagination bool) (model.LimitBreakNextPerformanceRequest, error) {
|
func parseLimitBreakNextPerformanceRequest(c *gin.Context, includePagination bool) (model.LimitBreakNextPerformanceRequest, error) {
|
||||||
req := model.LimitBreakNextPerformanceRequest{
|
req := model.LimitBreakNextPerformanceRequest{
|
||||||
TradeStartDate: strings.TrimSpace(c.DefaultQuery("tradeStartDate", c.Query("start_date"))),
|
TradeStartDate: firstNonEmptyQuery(c, "tradeStartDate", "trade_start_date", "start_date"),
|
||||||
TradeEndDate: strings.TrimSpace(c.DefaultQuery("tradeEndDate", c.Query("end_date"))),
|
TradeEndDate: firstNonEmptyQuery(c, "tradeEndDate", "trade_end_date", "end_date"),
|
||||||
Page: 1,
|
Page: 1,
|
||||||
PageSize: 20,
|
PageSize: 20,
|
||||||
}
|
}
|
||||||
@@ -159,6 +159,16 @@ func parseLimitBreakNextPerformanceRequest(c *gin.Context, includePagination boo
|
|||||||
return req, nil
|
return req, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// firstNonEmptyQuery 按优先级读取第一个非空查询参数。
|
||||||
|
func firstNonEmptyQuery(c *gin.Context, keys ...string) string {
|
||||||
|
for _, key := range keys {
|
||||||
|
if value := strings.TrimSpace(c.Query(key)); value != "" {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
// parseOptionalBoolQuery 从多个兼容参数名中读取可选布尔值。
|
// parseOptionalBoolQuery 从多个兼容参数名中读取可选布尔值。
|
||||||
//
|
//
|
||||||
// 返回 nil 表示用户没有传入该过滤项;返回 *bool 表示需要按 true/false 过滤。
|
// 返回 nil 表示用户没有传入该过滤项;返回 *bool 表示需要按 true/false 过滤。
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"level_go/internal/dbcompat"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -337,16 +336,21 @@ func validateSecondProfitDateRange(startDate, endDate string) error {
|
|||||||
// SQL 先找出“前一日涨停、前两日未涨停、当日继续涨停”的股票,再补齐 T+1 行情;
|
// SQL 先找出“前一日涨停、前两日未涨停、当日继续涨停”的股票,再补齐 T+1 行情;
|
||||||
// Go 代码负责应用动态过滤条件并计算开盘、最高、最低、收盘相对 T 日收盘的收益率。
|
// Go 代码负责应用动态过滤条件并计算开盘、最高、最低、收盘相对 T 日收盘的收益率。
|
||||||
func (h *LimitBoardHandler) loadSecondProfitRows(req secondProfitQuery) ([]secondProfitCSVRow, error) {
|
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 := `
|
query := `
|
||||||
|
WITH calendar AS (
|
||||||
|
SELECT
|
||||||
|
cal_date AS trade_date,
|
||||||
|
LAG(cal_date) 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) OVER (ORDER BY cal_date) AS next_trade_date
|
||||||
|
FROM trade_calendar
|
||||||
|
WHERE exchange = 'SSE' AND is_open = 1
|
||||||
|
),
|
||||||
|
calendar_window AS (
|
||||||
|
SELECT *
|
||||||
|
FROM calendar
|
||||||
|
WHERE trade_date BETWEEN ? AND ?
|
||||||
|
)
|
||||||
SELECT
|
SELECT
|
||||||
DATE_FORMAT(c.trade_date, '%Y-%m-%d') AS 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_trade_date, '%Y-%m-%d') AS previous_trade_date,
|
||||||
@@ -374,28 +378,28 @@ SELECT
|
|||||||
nd.high AS next_high,
|
nd.high AS next_high,
|
||||||
nd.low AS next_low,
|
nd.low AS next_low,
|
||||||
nd.close AS next_close
|
nd.close AS next_close
|
||||||
FROM (` + calendarTable + `) c
|
FROM calendar_window c
|
||||||
INNER JOIN stock_limit_combined_data l
|
INNER JOIN stock_limit_combined_data l
|
||||||
ON l.trade_date = c.trade_date AND l.final_status = 1
|
ON l.trade_date = c.trade_date AND l.final_status = 1
|
||||||
INNER JOIN stock_limit_combined_data pl
|
INNER JOIN stock_limit_combined_data pl
|
||||||
ON ` + dbcompat.TsCodeEqual("pl.ts_code", "l.ts_code") + ` AND pl.trade_date = c.previous_trade_date AND pl.final_status = 1
|
ON 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
|
LEFT JOIN stock_limit_combined_data ppl
|
||||||
ON ` + dbcompat.TsCodeEqual("ppl.ts_code", "l.ts_code") + ` AND ppl.trade_date = c.previous_two_trade_date AND ppl.final_status = 1
|
ON 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
|
LEFT JOIN stock_daily d
|
||||||
ON ` + dbcompat.TsCodeEqual("l.ts_code", "d.ts_code") + ` AND d.trade_date = l.trade_date
|
ON l.ts_code = d.ts_code AND d.trade_date = l.trade_date
|
||||||
LEFT JOIN stock_daily pd
|
LEFT JOIN stock_daily pd
|
||||||
ON ` + dbcompat.TsCodeEqual("l.ts_code", "pd.ts_code") + ` AND pd.trade_date = c.previous_trade_date
|
ON l.ts_code = pd.ts_code AND pd.trade_date = c.previous_trade_date
|
||||||
LEFT JOIN stock_daily ppd
|
LEFT JOIN stock_daily ppd
|
||||||
ON ` + dbcompat.TsCodeEqual("l.ts_code", "ppd.ts_code") + ` AND ppd.trade_date = c.previous_two_trade_date
|
ON l.ts_code = ppd.ts_code AND ppd.trade_date = c.previous_two_trade_date
|
||||||
LEFT JOIN stock_daily nd
|
LEFT JOIN stock_daily nd
|
||||||
ON ` + dbcompat.TsCodeEqual("l.ts_code", "nd.ts_code") + ` AND nd.trade_date = c.next_trade_date
|
ON l.ts_code = nd.ts_code AND nd.trade_date = c.next_trade_date
|
||||||
WHERE c.previous_trade_date IS NOT NULL
|
WHERE c.previous_trade_date IS NOT NULL
|
||||||
AND c.previous_two_trade_date IS NOT NULL
|
AND c.previous_two_trade_date IS NOT NULL
|
||||||
AND ppl.ts_code IS NULL
|
AND ppl.ts_code IS NULL
|
||||||
ORDER BY c.trade_date ASC, l.ts_code ASC`
|
ORDER BY c.trade_date ASC, l.ts_code ASC`
|
||||||
|
|
||||||
var rawRows []secondProfitCSVRow
|
var rawRows []secondProfitCSVRow
|
||||||
if err := h.Service.DB.Raw(query, calendarArgs...).Scan(&rawRows).Error; err != nil {
|
if err := h.Service.DB.Raw(query, req.TradeStartDate, req.TradeEndDate).Scan(&rawRows).Error; err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,147 +0,0 @@
|
|||||||
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
|
|
||||||
}
|
|
||||||
@@ -5,7 +5,6 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"level_go/internal/dbcompat"
|
|
||||||
"level_go/internal/model"
|
"level_go/internal/model"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -105,16 +104,21 @@ func (s *LimitBoardService) ListLimitBreakNextPerformanceStocks(req model.LimitB
|
|||||||
//
|
//
|
||||||
// 口径:前一交易日涨停、前两交易日未涨停、当前交易日 final_status = 2。
|
// 口径:前一交易日涨停、前两交易日未涨停、当前交易日 final_status = 2。
|
||||||
func (s *LimitBoardService) loadLimitBreakNextPerformanceRows(startDate, endDate string) ([]limitBreakNextPerformanceRow, error) {
|
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 := `
|
query := `
|
||||||
|
WITH calendar AS (
|
||||||
|
SELECT
|
||||||
|
cal_date AS trade_date,
|
||||||
|
LAG(cal_date) 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) OVER (ORDER BY cal_date) AS next_trade_date
|
||||||
|
FROM trade_calendar
|
||||||
|
WHERE exchange = 'SSE' AND is_open = 1
|
||||||
|
),
|
||||||
|
calendar_window AS (
|
||||||
|
SELECT *
|
||||||
|
FROM calendar
|
||||||
|
WHERE trade_date BETWEEN ? AND ?
|
||||||
|
)
|
||||||
SELECT *
|
SELECT *
|
||||||
FROM (
|
FROM (
|
||||||
SELECT
|
SELECT
|
||||||
@@ -143,21 +147,21 @@ FROM (
|
|||||||
nd.high AS next_high,
|
nd.high AS next_high,
|
||||||
nd.low AS next_low,
|
nd.low AS next_low,
|
||||||
nd.close AS next_close
|
nd.close AS next_close
|
||||||
FROM (` + calendarTable + `) c
|
FROM calendar_window c
|
||||||
INNER JOIN stock_limit_combined_data l
|
INNER JOIN stock_limit_combined_data l
|
||||||
ON l.trade_date = c.trade_date AND l.final_status = 2
|
ON l.trade_date = c.trade_date AND l.final_status = 2
|
||||||
INNER JOIN stock_limit_combined_data pl
|
INNER JOIN stock_limit_combined_data pl
|
||||||
ON ` + dbcompat.TsCodeEqual("pl.ts_code", "l.ts_code") + ` AND pl.trade_date = c.previous_trade_date AND pl.final_status = 1
|
ON 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
|
LEFT JOIN stock_limit_combined_data ppl
|
||||||
ON ` + dbcompat.TsCodeEqual("ppl.ts_code", "l.ts_code") + ` AND ppl.trade_date = c.previous_two_trade_date AND ppl.final_status = 1
|
ON 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
|
LEFT JOIN stock_daily d
|
||||||
ON ` + dbcompat.TsCodeEqual("d.ts_code", "l.ts_code") + ` AND d.trade_date = c.trade_date
|
ON d.ts_code = l.ts_code AND d.trade_date = c.trade_date
|
||||||
LEFT JOIN stock_daily pd
|
LEFT JOIN stock_daily pd
|
||||||
ON ` + dbcompat.TsCodeEqual("pd.ts_code", "l.ts_code") + ` AND pd.trade_date = c.previous_trade_date
|
ON pd.ts_code = l.ts_code AND pd.trade_date = c.previous_trade_date
|
||||||
LEFT JOIN stock_daily nd
|
LEFT JOIN stock_daily nd
|
||||||
ON ` + dbcompat.TsCodeEqual("nd.ts_code", "l.ts_code") + ` AND nd.trade_date = c.next_trade_date
|
ON nd.ts_code = l.ts_code AND nd.trade_date = c.next_trade_date
|
||||||
LEFT JOIN stock_basic sb
|
LEFT JOIN stock_basic sb
|
||||||
ON ` + dbcompat.TsCodeEqualUnicode("sb.ts_code", "l.ts_code") + `
|
ON sb.ts_code = l.ts_code
|
||||||
WHERE c.previous_trade_date IS NOT NULL
|
WHERE c.previous_trade_date IS NOT NULL
|
||||||
AND c.previous_two_trade_date IS NOT NULL
|
AND c.previous_two_trade_date IS NOT NULL
|
||||||
AND ppl.ts_code IS NULL
|
AND ppl.ts_code IS NULL
|
||||||
@@ -167,7 +171,7 @@ WHERE previous_close IS NOT NULL
|
|||||||
ORDER BY trade_date ASC, ts_code ASC`
|
ORDER BY trade_date ASC, ts_code ASC`
|
||||||
|
|
||||||
var rows []limitBreakNextPerformanceRow
|
var rows []limitBreakNextPerformanceRow
|
||||||
err = s.DB.Raw(query, calendarArgs...).Scan(&rows).Error
|
err := s.DB.Raw(query, startDate, endDate).Scan(&rows).Error
|
||||||
return rows, err
|
return rows, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"level_go/internal/dbcompat"
|
|
||||||
"level_go/internal/model"
|
"level_go/internal/model"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -147,7 +146,7 @@ func (s *LimitBoardService) loadLimitOpenCompareStocks(tradeDate, previousTradeD
|
|||||||
(? <> '' AND NOT EXISTS (
|
(? <> '' AND NOT EXISTS (
|
||||||
SELECT 1
|
SELECT 1
|
||||||
FROM stock_limit_combined_data ppl
|
FROM stock_limit_combined_data ppl
|
||||||
WHERE ` + dbcompat.TsCodeEqual("ppl.ts_code", "l.ts_code") + ` AND ppl.trade_date = ? AND ppl.final_status = 1
|
WHERE ppl.ts_code = l.ts_code AND ppl.trade_date = ? AND ppl.final_status = 1
|
||||||
)) AS is_first_to_second_board,
|
)) AS is_first_to_second_board,
|
||||||
COALESCE(l.calc_limit_price, 0) AS limit_price,
|
COALESCE(l.calc_limit_price, 0) AS limit_price,
|
||||||
COALESCE(l.tushare_pct_chg, d.pct_chg, 0) AS pct_chg,
|
COALESCE(l.tushare_pct_chg, d.pct_chg, 0) AS pct_chg,
|
||||||
@@ -165,11 +164,11 @@ func (s *LimitBoardService) loadLimitOpenCompareStocks(tradeDate, previousTradeD
|
|||||||
l.calc_vol AS vol
|
l.calc_vol AS vol
|
||||||
FROM stock_limit_combined_data l
|
FROM stock_limit_combined_data l
|
||||||
LEFT JOIN stock_daily d
|
LEFT JOIN stock_daily d
|
||||||
ON ` + dbcompat.TsCodeEqual("l.ts_code", "d.ts_code") + ` AND d.trade_date = l.trade_date
|
ON l.ts_code = d.ts_code AND d.trade_date = l.trade_date
|
||||||
LEFT JOIN stock_daily pd
|
LEFT JOIN stock_daily pd
|
||||||
ON ` + dbcompat.TsCodeEqual("l.ts_code", "pd.ts_code") + ` AND pd.trade_date = ?
|
ON l.ts_code = pd.ts_code AND pd.trade_date = ?
|
||||||
INNER JOIN stock_limit_combined_data pl
|
INNER JOIN stock_limit_combined_data pl
|
||||||
ON ` + dbcompat.TsCodeEqual("pl.ts_code", "l.ts_code") + ` AND pl.trade_date = ? AND pl.final_status = 1
|
ON pl.ts_code = l.ts_code AND pl.trade_date = ? AND pl.final_status = 1
|
||||||
WHERE l.trade_date = ? AND l.final_status = 1
|
WHERE l.trade_date = ? AND l.final_status = 1
|
||||||
ORDER BY d.pct_chg DESC, l.ts_code ASC`
|
ORDER BY d.pct_chg DESC, l.ts_code ASC`
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package service
|
|||||||
import (
|
import (
|
||||||
"sort"
|
"sort"
|
||||||
|
|
||||||
"level_go/internal/dbcompat"
|
|
||||||
"level_go/internal/model"
|
"level_go/internal/model"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -94,11 +93,11 @@ func (s *LimitBoardService) loadLimitOpenThresholdStocks(tradeDate, previousTrad
|
|||||||
l.calc_vol AS vol
|
l.calc_vol AS vol
|
||||||
FROM stock_limit_combined_data l
|
FROM stock_limit_combined_data l
|
||||||
LEFT JOIN stock_daily d
|
LEFT JOIN stock_daily d
|
||||||
ON ` + dbcompat.TsCodeEqual("l.ts_code", "d.ts_code") + ` AND d.trade_date = l.trade_date
|
ON l.ts_code = d.ts_code AND d.trade_date = l.trade_date
|
||||||
LEFT JOIN stock_daily pd
|
LEFT JOIN stock_daily pd
|
||||||
ON ` + dbcompat.TsCodeEqual("l.ts_code", "pd.ts_code") + ` AND pd.trade_date = ?
|
ON l.ts_code = pd.ts_code AND pd.trade_date = ?
|
||||||
INNER JOIN stock_limit_combined_data pl
|
INNER JOIN stock_limit_combined_data pl
|
||||||
ON ` + dbcompat.TsCodeEqual("pl.ts_code", "l.ts_code") + ` AND pl.trade_date = ? AND pl.final_status = 1
|
ON pl.ts_code = l.ts_code AND pl.trade_date = ? AND pl.final_status = 1
|
||||||
WHERE l.trade_date = ? AND l.final_status = 1
|
WHERE l.trade_date = ? AND l.final_status = 1
|
||||||
ORDER BY d.pct_chg DESC, l.ts_code ASC`
|
ORDER BY d.pct_chg DESC, l.ts_code ASC`
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package service
|
|||||||
import (
|
import (
|
||||||
"sort"
|
"sort"
|
||||||
|
|
||||||
"level_go/internal/dbcompat"
|
|
||||||
"level_go/internal/model"
|
"level_go/internal/model"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -147,7 +146,7 @@ func (s *LimitBoardService) loadCurrentLimitStocks(tradeDate string) ([]model.Li
|
|||||||
l.calc_vol AS vol
|
l.calc_vol AS vol
|
||||||
FROM stock_limit_combined_data l
|
FROM stock_limit_combined_data l
|
||||||
LEFT JOIN stock_daily d
|
LEFT JOIN stock_daily d
|
||||||
ON ` + dbcompat.TsCodeEqual("l.ts_code", "d.ts_code") + ` AND d.trade_date = l.trade_date
|
ON l.ts_code = d.ts_code AND d.trade_date = l.trade_date
|
||||||
WHERE l.trade_date = ? AND l.final_status = 1
|
WHERE l.trade_date = ? AND l.final_status = 1
|
||||||
ORDER BY d.pct_chg DESC, l.ts_code ASC`
|
ORDER BY d.pct_chg DESC, l.ts_code ASC`
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package service
|
|||||||
import (
|
import (
|
||||||
"math"
|
"math"
|
||||||
|
|
||||||
"level_go/internal/dbcompat"
|
|
||||||
"level_go/internal/model"
|
"level_go/internal/model"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -88,19 +87,29 @@ func (s *LimitBoardService) GetSecondCal(req model.SecondCalRequest) (model.Seco
|
|||||||
|
|
||||||
// loadSecondCalDays 查询日期范围内的开市交易日和相邻交易日。
|
// loadSecondCalDays 查询日期范围内的开市交易日和相邻交易日。
|
||||||
func (s *LimitBoardService) loadSecondCalDays(startDate, endDate string) ([]secondCalDaySeed, map[string]int, error) {
|
func (s *LimitBoardService) loadSecondCalDays(startDate, endDate string) ([]secondCalDaySeed, map[string]int, error) {
|
||||||
seeds, err := dbcompat.LoadSSEOpenCalendarSeeds(s.DB, startDate, endDate)
|
query := `
|
||||||
if err != nil {
|
WITH calendar AS (
|
||||||
|
SELECT
|
||||||
|
cal_date AS trade_date,
|
||||||
|
LAG(cal_date) 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) 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 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 {
|
||||||
return nil, nil, err
|
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) 定位。
|
// 构建交易日到响应数组下标的映射,后续聚合明细时可以 O(1) 定位。
|
||||||
dayIndex := make(map[string]int, len(days))
|
dayIndex := make(map[string]int, len(days))
|
||||||
@@ -112,20 +121,21 @@ func (s *LimitBoardService) loadSecondCalDays(startDate, endDate string) ([]seco
|
|||||||
|
|
||||||
// loadSecondCalRows 查询 T-1 涨停、T-2 未涨停、T 日再次触及涨停价的二板股票。
|
// loadSecondCalRows 查询 T-1 涨停、T-2 未涨停、T 日再次触及涨停价的二板股票。
|
||||||
func (s *LimitBoardService) loadSecondCalRows(startDate, endDate string) ([]secondCalRow, error) {
|
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 := `
|
query := `
|
||||||
|
WITH calendar AS (
|
||||||
|
SELECT
|
||||||
|
cal_date AS trade_date,
|
||||||
|
LAG(cal_date) 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) OVER (ORDER BY cal_date) AS next_trade_date
|
||||||
|
FROM trade_calendar
|
||||||
|
WHERE exchange = 'SSE' AND is_open = 1
|
||||||
|
),
|
||||||
|
calendar_window AS (
|
||||||
|
SELECT *
|
||||||
|
FROM calendar
|
||||||
|
WHERE trade_date BETWEEN ? AND ?
|
||||||
|
)
|
||||||
SELECT
|
SELECT
|
||||||
DATE_FORMAT(trade_date, '%Y-%m-%d') AS trade_date,
|
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_trade_date, '%Y-%m-%d') AS previous_trade_date,
|
||||||
@@ -190,21 +200,21 @@ FROM (
|
|||||||
END AS open_times,
|
END AS open_times,
|
||||||
nd.high AS next_high,
|
nd.high AS next_high,
|
||||||
nd.close AS next_close
|
nd.close AS next_close
|
||||||
FROM (` + calendarTable + `) c
|
FROM calendar_window c
|
||||||
INNER JOIN limit_list_d pl
|
INNER JOIN limit_list_d pl
|
||||||
ON pl.trade_date = c.previous_trade_date AND pl.` + "`limit`" + ` = 'U'
|
ON pl.trade_date = c.previous_trade_date AND pl.` + "`limit`" + ` = 'U'
|
||||||
LEFT JOIN limit_list_d ppl
|
LEFT JOIN limit_list_d ppl
|
||||||
ON ` + dbcompat.TsCodeEqual("ppl.ts_code", "pl.ts_code") + ` AND ppl.trade_date = c.previous_two_trade_date AND ppl.` + "`limit`" + ` = 'U'
|
ON ppl.ts_code = pl.ts_code AND ppl.trade_date = c.previous_two_trade_date AND ppl.` + "`limit`" + ` = 'U'
|
||||||
INNER JOIN stock_daily d
|
INNER JOIN stock_daily d
|
||||||
ON ` + dbcompat.TsCodeEqualRightCollation("d.ts_code", "pl.ts_code", stockDailyTsCodeCollation) + ` AND d.trade_date = c.trade_date
|
ON d.ts_code = pl.ts_code AND d.trade_date = c.trade_date
|
||||||
LEFT JOIN stock_daily nd
|
LEFT JOIN stock_daily nd
|
||||||
ON ` + dbcompat.TsCodeEqualRightCollation("nd.ts_code", "pl.ts_code", stockDailyTsCodeCollation) + ` AND nd.trade_date = c.next_trade_date
|
ON nd.ts_code = pl.ts_code AND nd.trade_date = c.next_trade_date
|
||||||
LEFT JOIN limit_list_d tlu
|
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'
|
ON tlu.ts_code = pl.ts_code AND tlu.trade_date = c.trade_date AND tlu.` + "`limit`" + ` = 'U'
|
||||||
LEFT JOIN limit_list_d tlz
|
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'
|
ON tlz.ts_code = pl.ts_code AND tlz.trade_date = c.trade_date AND tlz.` + "`limit`" + ` = 'Z'
|
||||||
LEFT JOIN stock_basic sb
|
LEFT JOIN stock_basic sb
|
||||||
ON ` + dbcompat.TsCodeEqual("sb.ts_code", "pl.ts_code") + `
|
ON sb.ts_code = pl.ts_code
|
||||||
WHERE c.previous_trade_date IS NOT NULL
|
WHERE c.previous_trade_date IS NOT NULL
|
||||||
AND c.previous_two_trade_date IS NOT NULL
|
AND c.previous_two_trade_date IS NOT NULL
|
||||||
AND ppl.ts_code IS NULL
|
AND ppl.ts_code IS NULL
|
||||||
@@ -219,7 +229,7 @@ WHERE previous_close IS NOT NULL
|
|||||||
ORDER BY trade_date ASC, ts_code ASC`
|
ORDER BY trade_date ASC, ts_code ASC`
|
||||||
|
|
||||||
var rows []secondCalRow
|
var rows []secondCalRow
|
||||||
err = s.DB.Raw(query, calendarArgs...).Scan(&rows).Error
|
err := s.DB.Raw(query, startDate, endDate).Scan(&rows).Error
|
||||||
return rows, err
|
return rows, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import (
|
|||||||
"sort"
|
"sort"
|
||||||
|
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
"level_go/internal/dbcompat"
|
|
||||||
"level_go/internal/model"
|
"level_go/internal/model"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -46,7 +45,7 @@ func (s *StockService) AnalyzePerformance(startDate, endDate string) ([]model.An
|
|||||||
|
|
||||||
// 查询 startDate 当日涨停股,并优先使用涨停合并表中的计算最高价。
|
// 查询 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
|
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 ` + dbcompat.TsCodeEqual("l.ts_code", "d.ts_code") + ` AND l.trade_date = d.trade_date
|
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`
|
WHERE l.trade_date = ? AND l.final_status = 1`
|
||||||
|
|
||||||
if err := s.DB.Raw(query, startDate).Scan(&limits).Error; err != nil {
|
if err := s.DB.Raw(query, startDate).Scan(&limits).Error; err != nil {
|
||||||
|
|||||||
@@ -1,159 +0,0 @@
|
|||||||
macbook pro 本地
|
|
||||||
-- auto-generated definition
|
|
||||||
create table stock_basic
|
|
||||||
(
|
|
||||||
ts_code text null,
|
|
||||||
symbol text null,
|
|
||||||
name text null,
|
|
||||||
area text null,
|
|
||||||
industry text null,
|
|
||||||
fullname text null,
|
|
||||||
enname text null,
|
|
||||||
cnspell text null,
|
|
||||||
market text null,
|
|
||||||
exchange text null,
|
|
||||||
curr_type text null,
|
|
||||||
list_status text null,
|
|
||||||
list_date text null,
|
|
||||||
delist_date text null,
|
|
||||||
is_hs text null,
|
|
||||||
act_name text null,
|
|
||||||
act_ent_type text null
|
|
||||||
);
|
|
||||||
|
|
||||||
-- auto-generated definition
|
|
||||||
create table stock_daily
|
|
||||||
(
|
|
||||||
id bigint auto_increment
|
|
||||||
primary key,
|
|
||||||
ts_code varchar(20) not null,
|
|
||||||
symbol varchar(10) not null,
|
|
||||||
trade_date date not null,
|
|
||||||
open decimal(16, 4) null,
|
|
||||||
high decimal(16, 4) null,
|
|
||||||
low decimal(16, 4) null,
|
|
||||||
close decimal(16, 4) null,
|
|
||||||
pre_close decimal(16, 4) null,
|
|
||||||
change_val decimal(16, 4) null,
|
|
||||||
pct_chg decimal(10, 4) null,
|
|
||||||
vol bigint null,
|
|
||||||
amount bigint null,
|
|
||||||
constraint uniq_code_date
|
|
||||||
unique (ts_code, trade_date)
|
|
||||||
)
|
|
||||||
charset = utf8mb4;
|
|
||||||
|
|
||||||
-- auto-generated definition
|
|
||||||
create table stock_limit_combined_data
|
|
||||||
(
|
|
||||||
id bigint auto_increment
|
|
||||||
primary key,
|
|
||||||
trade_date date not null comment '交易日期',
|
|
||||||
symbol varchar(10) not null comment '股票代码,以stock_daily为准',
|
|
||||||
ts_code varchar(20) not null comment 'TS股票代码,以stock_daily为准',
|
|
||||||
name varchar(50) not null comment '股票名称,以stock_daily关联基础表为准',
|
|
||||||
final_status tinyint not null comment '融合状态:1-涨停,2-炸板',
|
|
||||||
source_count int default 0 not null comment '命中的数据源数量',
|
|
||||||
source_flags varchar(50) null comment '命中的数据源:calc,tushare',
|
|
||||||
calc_status tinyint null comment '本地行情计算状态:1-涨停,2-炸板',
|
|
||||||
calc_limit_price decimal(16, 4) null comment '本地行情计算涨停价',
|
|
||||||
calc_high decimal(16, 4) null comment 'stock_daily最高价',
|
|
||||||
calc_close decimal(16, 4) null comment 'stock_daily收盘价',
|
|
||||||
calc_pre_close decimal(16, 4) null comment 'stock_daily前收盘价',
|
|
||||||
calc_vol bigint null comment 'stock_daily成交量',
|
|
||||||
market varchar(20) null comment '所属板块',
|
|
||||||
tushare_limit_type varchar(1) null comment 'Tushare-D跌停U涨停Z炸板',
|
|
||||||
tushare_industry varchar(100) null comment 'Tushare-所属行业',
|
|
||||||
tushare_name varchar(50) null comment 'Tushare-名称',
|
|
||||||
tushare_close decimal(16, 4) null comment 'Tushare-收盘价',
|
|
||||||
tushare_pct_chg decimal(10, 4) null comment 'Tushare-涨跌幅',
|
|
||||||
tushare_amount decimal(24, 4) null comment 'Tushare-成交额',
|
|
||||||
tushare_limit_amount decimal(24, 4) null comment 'Tushare-板上成交金额',
|
|
||||||
tushare_float_mv decimal(24, 4) null comment 'Tushare-流通市值',
|
|
||||||
tushare_total_mv decimal(24, 4) null comment 'Tushare-总市值',
|
|
||||||
tushare_turnover_ratio decimal(10, 4) null comment 'Tushare-换手率',
|
|
||||||
tushare_fd_amount decimal(24, 4) null comment 'Tushare-封单金额',
|
|
||||||
tushare_first_time varchar(20) null comment 'Tushare-首次封板时间',
|
|
||||||
tushare_last_time varchar(20) null comment 'Tushare-最后封板时间',
|
|
||||||
tushare_open_times int null comment 'Tushare-炸板/开板次数',
|
|
||||||
tushare_up_stat varchar(50) null comment 'Tushare-涨停统计',
|
|
||||||
tushare_limit_times int null comment 'Tushare-连板数',
|
|
||||||
constraint uniq_date_code
|
|
||||||
unique (trade_date, ts_code)
|
|
||||||
);
|
|
||||||
|
|
||||||
create index idx_symbol
|
|
||||||
on stock_limit_combined_data (symbol);
|
|
||||||
|
|
||||||
create index idx_trade_date
|
|
||||||
on stock_limit_combined_data (trade_date);
|
|
||||||
|
|
||||||
create index idx_trade_date_status
|
|
||||||
on stock_limit_combined_data (trade_date, final_status);
|
|
||||||
|
|
||||||
-- auto-generated definition
|
|
||||||
create table trade_calendar
|
|
||||||
(
|
|
||||||
cal_date date not null,
|
|
||||||
exchange varchar(10) not null,
|
|
||||||
is_open tinyint null,
|
|
||||||
pretrade_date date null,
|
|
||||||
primary key (cal_date, exchange)
|
|
||||||
)
|
|
||||||
charset = utf8mb4;
|
|
||||||
|
|
||||||
阿里云
|
|
||||||
-- auto-generated definition
|
|
||||||
create table stock_basic
|
|
||||||
(
|
|
||||||
ts_code text null,
|
|
||||||
symbol text null,
|
|
||||||
name text null,
|
|
||||||
area text null,
|
|
||||||
industry text null,
|
|
||||||
fullname text null,
|
|
||||||
enname text null,
|
|
||||||
cnspell text null,
|
|
||||||
market text null,
|
|
||||||
exchange text null,
|
|
||||||
curr_type text null,
|
|
||||||
list_status text null,
|
|
||||||
list_date text null,
|
|
||||||
delist_date text null,
|
|
||||||
is_hs text null,
|
|
||||||
act_name text null,
|
|
||||||
act_ent_type text null
|
|
||||||
);
|
|
||||||
|
|
||||||
-- auto-generated definition
|
|
||||||
create table stock_daily
|
|
||||||
(
|
|
||||||
id bigint auto_increment
|
|
||||||
primary key,
|
|
||||||
ts_code varchar(20) not null,
|
|
||||||
symbol varchar(10) not null,
|
|
||||||
trade_date date not null,
|
|
||||||
open decimal(16, 4) null,
|
|
||||||
high decimal(16, 4) null,
|
|
||||||
low decimal(16, 4) null,
|
|
||||||
close decimal(16, 4) null,
|
|
||||||
pre_close decimal(16, 4) null,
|
|
||||||
change_val decimal(16, 4) null,
|
|
||||||
pct_chg decimal(10, 4) null,
|
|
||||||
vol bigint null,
|
|
||||||
amount bigint null,
|
|
||||||
constraint uniq_code_date
|
|
||||||
unique (ts_code, trade_date)
|
|
||||||
);
|
|
||||||
|
|
||||||
-- auto-generated definition
|
|
||||||
create table trade_calendar
|
|
||||||
(
|
|
||||||
cal_date date not null,
|
|
||||||
exchange varchar(10) not null,
|
|
||||||
is_open tinyint null,
|
|
||||||
pretrade_date date null,
|
|
||||||
primary key (cal_date, exchange)
|
|
||||||
)
|
|
||||||
charset = utf8mb4;
|
|
||||||
|
|
||||||
Reference in New Issue
Block a user