新增首二一一模型
This commit is contained in:
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
- 服务前缀:`/api/v1`
|
- 服务前缀:`/api/v1`
|
||||||
- 响应格式:`application/json`
|
- 响应格式:`application/json`
|
||||||
- 当前项目接口总数:4
|
- 当前项目接口总数:5
|
||||||
|
|
||||||
## 2. 接口列表
|
## 2. 接口列表
|
||||||
|
|
||||||
@@ -14,6 +14,7 @@
|
|||||||
| 股票分析 | `POST` | `/api/v1/stock/analyze` | 分析指定涨停日股票后续表现 | [stock-analyze.md](./stock-analyze.md) |
|
| 股票分析 | `POST` | `/api/v1/stock/analyze` | 分析指定涨停日股票后续表现 | [stock-analyze.md](./stock-analyze.md) |
|
||||||
| 股票行情 | `GET` | `/api/v1/stock/kline/:code` | 获取指定股票历史 K 线 | [stock-kline.md](./stock-kline.md) |
|
| 股票行情 | `GET` | `/api/v1/stock/kline/:code` | 获取指定股票历史 K 线 | [stock-kline.md](./stock-kline.md) |
|
||||||
| 涨停连板 | `GET` | `/api/v1/limit-board/overview` | 获取交易日涨停分组与近 30 日最高板趋势 | [limit-board-overview.md](./limit-board-overview.md) |
|
| 涨停连板 | `GET` | `/api/v1/limit-board/overview` | 获取交易日涨停分组与近 30 日最高板趋势 | [limit-board-overview.md](./limit-board-overview.md) |
|
||||||
|
| 涨停低开分组 | `GET` | `/api/v1/limit-board/open-lower-groups` | 获取查询日和前一交易日均涨停的股票,并按是否低于前一交易日收盘价分组 | [limit-board-open-lower-groups.md](./limit-board-open-lower-groups.md) |
|
||||||
|
|
||||||
## 3. 错误响应约定
|
## 3. 错误响应约定
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,172 @@
|
|||||||
|
# 涨停低开分组接口文档
|
||||||
|
|
||||||
|
## 1. 接口说明
|
||||||
|
|
||||||
|
- 接口名称:涨停低开分组
|
||||||
|
- 请求方式:`GET`
|
||||||
|
- 请求路径:`/api/v1/limit-board/open-lower-groups`
|
||||||
|
- 接口用途:查询指定交易日涨停且前一交易日也涨停的股票,并按指定交易日开盘价是否低于前一交易日收盘价分为两组。
|
||||||
|
|
||||||
|
## 2. 请求参数
|
||||||
|
|
||||||
|
| 参数名 | 类型 | 必填 | 示例 | 说明 |
|
||||||
|
| --- | --- | --- | --- | --- |
|
||||||
|
| `trade_date` | `string` | 是 | `2026-04-24` | 交易日,格式固定为 `YYYY-MM-DD` |
|
||||||
|
|
||||||
|
请求示例:
|
||||||
|
|
||||||
|
```http
|
||||||
|
GET /api/v1/limit-board/open-lower-groups?trade_date=2026-04-24
|
||||||
|
```
|
||||||
|
|
||||||
|
## 3. 响应说明
|
||||||
|
|
||||||
|
### 3.1 顶层字段
|
||||||
|
|
||||||
|
| 字段名 | 类型 | 说明 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `trade_date` | `string` | 查询的交易日 |
|
||||||
|
| `previous_trade_date` | `string` | 前一交易日;如果查询日没有前一交易日则为空字符串 |
|
||||||
|
| `previous_two_trade_date` | `string` | 前二交易日;如果查询日没有前二交易日则为空字符串 |
|
||||||
|
| `summary` | `object` | 当日涨停摘要信息 |
|
||||||
|
| `groups` | `array` | 分组后的股票列表,固定返回两组 |
|
||||||
|
|
||||||
|
### 3.2 summary 字段
|
||||||
|
|
||||||
|
| 字段名 | 类型 | 说明 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `limit_count` | `int` | 指定交易日涨停且前一交易日也涨停的股票总数 |
|
||||||
|
| `open_lower_previous_close_count` | `int` | 指定交易日开盘价低于前一交易日收盘价的股票数量 |
|
||||||
|
| `others_count` | `int` | 样本池内其他股票数量 |
|
||||||
|
| `first_to_second_board_count` | `int` | 前一交易日是首板、指定交易日是二板的股票数量 |
|
||||||
|
| `first_second_before_eleven_count` | `int` | 开盘低于前收、首板进二板、且指定交易日首次涨停时间早于 11:00 的股票数量 |
|
||||||
|
|
||||||
|
### 3.3 groups 字段
|
||||||
|
|
||||||
|
| 字段名 | 类型 | 说明 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `key` | `string` | 分组标识,取值为 `open_lower_previous_close` 或 `others` |
|
||||||
|
| `label` | `string` | 分组展示文案 |
|
||||||
|
| `count` | `int` | 当前分组内股票数量 |
|
||||||
|
| `stocks` | `array` | 当前分组内的股票列表 |
|
||||||
|
|
||||||
|
### 3.4 groups[].stocks 字段
|
||||||
|
|
||||||
|
| 字段名 | 类型 | 说明 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `symbol` | `string` | 股票代码 |
|
||||||
|
| `ts_code` | `string` | TS 股票代码 |
|
||||||
|
| `name` | `string` | 股票名称 |
|
||||||
|
| `market` | `string` | 所属板块或市场信息 |
|
||||||
|
| `trade_date` | `string` | 交易日 |
|
||||||
|
| `open` | `number` | 当日开盘价 |
|
||||||
|
| `close` | `number` | 当日收盘价 |
|
||||||
|
| `previous_trade_date` | `string` | 前一交易日 |
|
||||||
|
| `previous_two_trade_date` | `string` | 前二交易日 |
|
||||||
|
| `previous_close` | `number` | 前一交易日收盘价 |
|
||||||
|
| `is_first_to_second_board` | `boolean` | 是否为前一交易日首板、指定交易日二板 |
|
||||||
|
| `is_first_second_before_eleven` | `boolean` | 是否满足开盘低于前收、首板进二板、首次涨停时间早于 11:00 |
|
||||||
|
| `highlight_label` | `string` | 重点标记文案;优先返回 `首二一一`,其次返回 `首板进二板`,否则为空字符串 |
|
||||||
|
| `limit_price` | `number` | 理论涨停价 |
|
||||||
|
| `pct_chg` | `number` | 当日涨跌幅 |
|
||||||
|
| `ak_amount` | `number \| null` | AKShare 成交额 |
|
||||||
|
| `ak_float_market_value` | `number \| null` | AKShare 流通市值 |
|
||||||
|
| `ak_total_market_value` | `number \| null` | AKShare 总市值 |
|
||||||
|
| `ak_turnover_rate` | `number \| null` | AKShare 换手率 |
|
||||||
|
| `ak_seal_amount` | `number \| null` | AKShare 封板资金 |
|
||||||
|
| `ak_first_limit_time` | `string \| null` | AKShare 首次封板时间 |
|
||||||
|
| `ak_last_limit_time` | `string \| null` | AKShare 最后封板时间 |
|
||||||
|
| `ak_break_limit_count` | `int \| null` | AKShare 炸板次数 |
|
||||||
|
| `ak_limit_up_stat` | `string \| null` | AKShare 涨停统计 |
|
||||||
|
| `ak_limit_up_days` | `int \| null` | AKShare 连板数 |
|
||||||
|
| `ak_industry` | `string \| null` | AKShare 所属行业 |
|
||||||
|
| `vol` | `int \| null` | 成交量 |
|
||||||
|
|
||||||
|
## 4. 响应示例
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"trade_date": "2026-04-24",
|
||||||
|
"previous_trade_date": "2026-04-23",
|
||||||
|
"previous_two_trade_date": "2026-04-22",
|
||||||
|
"summary": {
|
||||||
|
"limit_count": 23,
|
||||||
|
"open_lower_previous_close_count": 5,
|
||||||
|
"others_count": 18,
|
||||||
|
"first_to_second_board_count": 3,
|
||||||
|
"first_second_before_eleven_count": 2
|
||||||
|
},
|
||||||
|
"groups": [
|
||||||
|
{
|
||||||
|
"key": "open_lower_previous_close",
|
||||||
|
"label": "开盘低于前收",
|
||||||
|
"count": 5,
|
||||||
|
"stocks": [
|
||||||
|
{
|
||||||
|
"symbol": "000001",
|
||||||
|
"ts_code": "000001.SZ",
|
||||||
|
"name": "示例股份",
|
||||||
|
"market": "主板",
|
||||||
|
"trade_date": "2026-04-24",
|
||||||
|
"open": 11.1,
|
||||||
|
"close": 12.35,
|
||||||
|
"previous_trade_date": "2026-04-23",
|
||||||
|
"previous_two_trade_date": "2026-04-22",
|
||||||
|
"previous_close": 11.2,
|
||||||
|
"is_first_to_second_board": true,
|
||||||
|
"is_first_second_before_eleven": true,
|
||||||
|
"highlight_label": "首二一一",
|
||||||
|
"limit_price": 12.35,
|
||||||
|
"pct_chg": 10.01,
|
||||||
|
"ak_amount": 1523000000,
|
||||||
|
"ak_float_market_value": 8245000000,
|
||||||
|
"ak_total_market_value": 12678000000,
|
||||||
|
"ak_turnover_rate": 18.64,
|
||||||
|
"ak_seal_amount": 286000000,
|
||||||
|
"ak_first_limit_time": "09:35:12",
|
||||||
|
"ak_last_limit_time": "14:42:08",
|
||||||
|
"ak_break_limit_count": 2,
|
||||||
|
"ak_limit_up_stat": "1天1板",
|
||||||
|
"ak_limit_up_days": 1,
|
||||||
|
"ak_industry": "软件开发",
|
||||||
|
"vol": 123456789
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "others",
|
||||||
|
"label": "其他涨停股",
|
||||||
|
"count": 18,
|
||||||
|
"stocks": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 5. 业务规则
|
||||||
|
|
||||||
|
- 只统计指定交易日 `stock_limit_data.status = 1` 且前一交易日 `stock_limit_data.status = 1` 的股票。
|
||||||
|
- 前一交易日、前二交易日通过 `trade_calendar` 中 `exchange = 'SSE'` 且 `is_open = 1` 的交易日确定。
|
||||||
|
- 只有当日 `stock_daily.open` 和前一交易日 `stock_daily.close` 都存在,且 `open < previous_close` 时,股票才会进入 `open_lower_previous_close` 分组。
|
||||||
|
- 行情缺失、没有前一交易日、或不满足 `open < previous_close` 的股票都会进入 `others` 分组。
|
||||||
|
- 当指定交易日涨停、前一交易日涨停、前二交易日不涨停时,返回 `is_first_to_second_board = true`,并设置 `highlight_label = "首板进二板"`。
|
||||||
|
- 在 `open_lower_previous_close` 分组内,如果同时满足 `is_first_to_second_board = true` 且 `ak_first_limit_time` 早于 `11:00`,返回 `is_first_second_before_eleven = true`,并将 `highlight_label` 设为 `首二一一`。
|
||||||
|
- 每个分组内的 `stocks` 按 `pct_chg` 从大到小排序;如果 `pct_chg` 相同,则按 `ts_code` 升序排序。
|
||||||
|
|
||||||
|
## 6. 错误响应
|
||||||
|
|
||||||
|
当缺少 `trade_date` 参数时:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"error": "trade_date 参数不能为空"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
当服务端内部异常时:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"error": "具体错误信息"
|
||||||
|
}
|
||||||
|
```
|
||||||
@@ -31,3 +31,19 @@ func (h *LimitBoardHandler) GetOverview(c *gin.Context) {
|
|||||||
|
|
||||||
c.JSON(http.StatusOK, resp)
|
c.JSON(http.StatusOK, resp)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (h *LimitBoardHandler) GetOpenLowerGroups(c *gin.Context) {
|
||||||
|
var req model.LimitBoardRequest
|
||||||
|
if err := c.ShouldBindQuery(&req); err != nil || req.TradeDate == "" {
|
||||||
|
c.JSON(http.StatusBadRequest, gin.H{"error": "trade_date 参数不能为空"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := h.Service.GetLimitOpenCompareGroups(req.TradeDate)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(http.StatusOK, resp)
|
||||||
|
}
|
||||||
|
|||||||
@@ -30,6 +30,61 @@ type LimitBoardStock struct {
|
|||||||
DisplayStatus string `json:"display_status"`
|
DisplayStatus string `json:"display_status"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type LimitOpenCompareStock struct {
|
||||||
|
Symbol string `json:"symbol"`
|
||||||
|
TsCode string `json:"ts_code"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Market string `json:"market"`
|
||||||
|
TradeDate string `json:"trade_date"`
|
||||||
|
HasOpen bool `json:"-"`
|
||||||
|
Open float64 `json:"open"`
|
||||||
|
Close float64 `json:"close"`
|
||||||
|
PreviousTradeDate string `json:"previous_trade_date"`
|
||||||
|
PreviousTwoTradeDate string `json:"previous_two_trade_date"`
|
||||||
|
HasPreviousClose bool `json:"-"`
|
||||||
|
PreviousClose float64 `json:"previous_close"`
|
||||||
|
IsFirstToSecondBoard bool `json:"is_first_to_second_board"`
|
||||||
|
IsFirstSecondBeforeEleven bool `json:"is_first_second_before_eleven"`
|
||||||
|
HighlightLabel string `json:"highlight_label"`
|
||||||
|
LimitPrice float64 `json:"limit_price"`
|
||||||
|
PctChg float64 `json:"pct_chg"`
|
||||||
|
AkAmount *float64 `json:"ak_amount"`
|
||||||
|
AkFloatMarketValue *float64 `json:"ak_float_market_value"`
|
||||||
|
AkTotalMarketValue *float64 `json:"ak_total_market_value"`
|
||||||
|
AkTurnoverRate *float64 `json:"ak_turnover_rate"`
|
||||||
|
AkSealAmount *float64 `json:"ak_seal_amount"`
|
||||||
|
AkFirstLimitTime *string `json:"ak_first_limit_time"`
|
||||||
|
AkLastLimitTime *string `json:"ak_last_limit_time"`
|
||||||
|
AkBreakLimitCount *int `json:"ak_break_limit_count"`
|
||||||
|
AkLimitUpStat *string `json:"ak_limit_up_stat"`
|
||||||
|
AkLimitUpDays *int `json:"ak_limit_up_days"`
|
||||||
|
AkIndustry *string `json:"ak_industry"`
|
||||||
|
Vol *int64 `json:"vol"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type LimitOpenCompareGroup struct {
|
||||||
|
Key string `json:"key"`
|
||||||
|
Label string `json:"label"`
|
||||||
|
Count int `json:"count"`
|
||||||
|
Stocks []LimitOpenCompareStock `json:"stocks"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type LimitOpenCompareSummary struct {
|
||||||
|
LimitCount int `json:"limit_count"`
|
||||||
|
OpenLowerPreviousCloseCount int `json:"open_lower_previous_close_count"`
|
||||||
|
OthersCount int `json:"others_count"`
|
||||||
|
FirstToSecondBoardCount int `json:"first_to_second_board_count"`
|
||||||
|
FirstSecondBeforeElevenCount int `json:"first_second_before_eleven_count"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type LimitOpenCompareResponse struct {
|
||||||
|
TradeDate string `json:"trade_date"`
|
||||||
|
PreviousTradeDate string `json:"previous_trade_date"`
|
||||||
|
PreviousTwoTradeDate string `json:"previous_two_trade_date"`
|
||||||
|
Summary LimitOpenCompareSummary `json:"summary"`
|
||||||
|
Groups []LimitOpenCompareGroup `json:"groups"`
|
||||||
|
}
|
||||||
|
|
||||||
type LimitBoardGroup struct {
|
type LimitBoardGroup struct {
|
||||||
BoardCount int `json:"board_count"`
|
BoardCount int `json:"board_count"`
|
||||||
Label string `json:"label"`
|
Label string `json:"label"`
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ func NewRouter(commonApi *api.CommonHandler, stockApi *api.StockHandler, limitBo
|
|||||||
v1.POST("/stock/analyze", stockApi.Analyze)
|
v1.POST("/stock/analyze", stockApi.Analyze)
|
||||||
v1.GET("/stock/kline/:code", stockApi.GetKlineData)
|
v1.GET("/stock/kline/:code", stockApi.GetKlineData)
|
||||||
v1.GET("/limit-board/overview", limitBoardApi.GetOverview)
|
v1.GET("/limit-board/overview", limitBoardApi.GetOverview)
|
||||||
|
v1.GET("/limit-board/open-lower-groups", limitBoardApi.GetOpenLowerGroups)
|
||||||
}
|
}
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,8 @@ package service
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"sort"
|
"sort"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
"level_go/internal/model"
|
"level_go/internal/model"
|
||||||
@@ -82,6 +84,113 @@ func (s *LimitBoardService) GetLimitBoardOverview(tradeDate string) (model.Limit
|
|||||||
return resp, nil
|
return resp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *LimitBoardService) GetLimitOpenCompareGroups(tradeDate string) (model.LimitOpenCompareResponse, error) {
|
||||||
|
resp := model.LimitOpenCompareResponse{TradeDate: tradeDate}
|
||||||
|
|
||||||
|
tradeDates, dateIndex, err := s.loadTradeCalendar(tradeDate)
|
||||||
|
if err != nil {
|
||||||
|
return resp, err
|
||||||
|
}
|
||||||
|
if dateIndex == -1 {
|
||||||
|
resp.Groups = buildLimitOpenCompareGroups(nil, nil)
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
previousTradeDate := ""
|
||||||
|
if dateIndex > 0 {
|
||||||
|
previousTradeDate = tradeDates[dateIndex-1]
|
||||||
|
}
|
||||||
|
resp.PreviousTradeDate = previousTradeDate
|
||||||
|
|
||||||
|
previousTwoTradeDate := ""
|
||||||
|
if dateIndex > 1 {
|
||||||
|
previousTwoTradeDate = tradeDates[dateIndex-2]
|
||||||
|
}
|
||||||
|
resp.PreviousTwoTradeDate = previousTwoTradeDate
|
||||||
|
|
||||||
|
stocks, err := s.loadLimitOpenCompareStocks(tradeDate, previousTradeDate, previousTwoTradeDate)
|
||||||
|
if err != nil {
|
||||||
|
return resp, err
|
||||||
|
}
|
||||||
|
|
||||||
|
openLowerStocks := make([]model.LimitOpenCompareStock, 0)
|
||||||
|
otherStocks := make([]model.LimitOpenCompareStock, 0)
|
||||||
|
firstToSecondBoardCount := 0
|
||||||
|
firstSecondBeforeElevenCount := 0
|
||||||
|
for _, stock := range stocks {
|
||||||
|
if stock.IsFirstToSecondBoard {
|
||||||
|
stock.HighlightLabel = "首板进二板"
|
||||||
|
firstToSecondBoardCount++
|
||||||
|
}
|
||||||
|
isOpenLower := previousTradeDate != "" && stock.HasOpen && stock.HasPreviousClose && stock.Open < stock.PreviousClose
|
||||||
|
if isOpenLower && stock.IsFirstToSecondBoard && isBeforeEleven(stock.AkFirstLimitTime) {
|
||||||
|
stock.IsFirstSecondBeforeEleven = true
|
||||||
|
stock.HighlightLabel = "首二一一"
|
||||||
|
firstSecondBeforeElevenCount++
|
||||||
|
}
|
||||||
|
if isOpenLower {
|
||||||
|
openLowerStocks = append(openLowerStocks, stock)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
otherStocks = append(otherStocks, stock)
|
||||||
|
}
|
||||||
|
|
||||||
|
resp.Summary = model.LimitOpenCompareSummary{
|
||||||
|
LimitCount: len(stocks),
|
||||||
|
OpenLowerPreviousCloseCount: len(openLowerStocks),
|
||||||
|
OthersCount: len(otherStocks),
|
||||||
|
FirstToSecondBoardCount: firstToSecondBoardCount,
|
||||||
|
FirstSecondBeforeElevenCount: firstSecondBeforeElevenCount,
|
||||||
|
}
|
||||||
|
resp.Groups = buildLimitOpenCompareGroups(openLowerStocks, otherStocks)
|
||||||
|
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func isBeforeEleven(limitTime *string) bool {
|
||||||
|
if limitTime == nil || *limitTime == "" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
timeText := strings.TrimSpace(*limitTime)
|
||||||
|
hour := 0
|
||||||
|
minute := 0
|
||||||
|
second := 0
|
||||||
|
if strings.Contains(timeText, ":") {
|
||||||
|
if n, _ := fmt.Sscanf(timeText, "%d:%d:%d", &hour, &minute, &second); n < 2 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if len(timeText) < 4 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if len(timeText) == 4 {
|
||||||
|
timeText += "00"
|
||||||
|
}
|
||||||
|
if len(timeText) != 6 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
var err error
|
||||||
|
hour, err = strconv.Atoi(timeText[0:2])
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
minute, err = strconv.Atoi(timeText[2:4])
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
second, err = strconv.Atoi(timeText[4:6])
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if hour < 0 || hour > 23 || minute < 0 || minute > 59 || second < 0 || second > 59 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return hour < 11
|
||||||
|
}
|
||||||
|
|
||||||
func (s *LimitBoardService) buildRecentMaxBoardStats(tradeDates []string, dateIndex int) ([]model.LimitBoardDailyMax, error) {
|
func (s *LimitBoardService) buildRecentMaxBoardStats(tradeDates []string, dateIndex int) ([]model.LimitBoardDailyMax, error) {
|
||||||
startIndex := 0
|
startIndex := 0
|
||||||
if dateIndex >= 29 {
|
if dateIndex >= 29 {
|
||||||
@@ -199,6 +308,58 @@ func (s *LimitBoardService) loadCurrentLimitStocks(tradeDate string) ([]model.Li
|
|||||||
return stocks, err
|
return stocks, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *LimitBoardService) loadLimitOpenCompareStocks(tradeDate, previousTradeDate, previousTwoTradeDate string) ([]model.LimitOpenCompareStock, error) {
|
||||||
|
var stocks []model.LimitOpenCompareStock
|
||||||
|
query := `SELECT
|
||||||
|
l.symbol,
|
||||||
|
l.ts_code,
|
||||||
|
l.name,
|
||||||
|
COALESCE(l.market, '') AS market,
|
||||||
|
DATE_FORMAT(l.trade_date, '%Y-%m-%d') AS trade_date,
|
||||||
|
d.open IS NOT NULL AS has_open,
|
||||||
|
COALESCE(d.open, 0) AS open,
|
||||||
|
COALESCE(d.close, l.close, 0) AS close,
|
||||||
|
? AS previous_trade_date,
|
||||||
|
? AS previous_two_trade_date,
|
||||||
|
pd.close IS NOT NULL AS has_previous_close,
|
||||||
|
COALESCE(pd.close, 0) AS previous_close,
|
||||||
|
(? <> '' AND NOT EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM stock_limit_data ppl
|
||||||
|
WHERE ppl.ts_code = l.ts_code AND ppl.trade_date = ? AND ppl.status = 1
|
||||||
|
)) AS is_first_to_second_board,
|
||||||
|
COALESCE(l.limit_price, 0) AS limit_price,
|
||||||
|
COALESCE(d.pct_chg, 0) AS pct_chg,
|
||||||
|
l.ak_amount,
|
||||||
|
l.ak_float_market_value,
|
||||||
|
l.ak_total_market_value,
|
||||||
|
l.ak_turnover_rate,
|
||||||
|
l.ak_seal_amount,
|
||||||
|
l.ak_first_limit_time,
|
||||||
|
l.ak_last_limit_time,
|
||||||
|
l.ak_break_limit_count,
|
||||||
|
l.ak_limit_up_stat,
|
||||||
|
l.ak_limit_up_days,
|
||||||
|
l.ak_industry,
|
||||||
|
l.vol
|
||||||
|
FROM stock_limit_data l
|
||||||
|
LEFT JOIN stock_daily d
|
||||||
|
ON d.ts_code = l.ts_code AND d.trade_date = l.trade_date
|
||||||
|
LEFT JOIN stock_daily pd
|
||||||
|
ON pd.ts_code = l.ts_code AND pd.trade_date = ?
|
||||||
|
INNER JOIN stock_limit_data pl
|
||||||
|
ON pl.ts_code = l.ts_code AND pl.trade_date = ? AND pl.status = 1
|
||||||
|
WHERE l.trade_date = ? AND l.status = 1
|
||||||
|
ORDER BY d.pct_chg DESC, l.ts_code ASC`
|
||||||
|
|
||||||
|
if previousTradeDate == "" {
|
||||||
|
return stocks, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
err := s.DB.Raw(query, previousTradeDate, previousTwoTradeDate, previousTwoTradeDate, previousTwoTradeDate, previousTradeDate, previousTradeDate, tradeDate).Scan(&stocks).Error
|
||||||
|
return stocks, err
|
||||||
|
}
|
||||||
|
|
||||||
func (s *LimitBoardService) loadLimitHistory(stocks []model.LimitBoardStock, tradeDate string) (map[string]map[string]struct{}, error) {
|
func (s *LimitBoardService) loadLimitHistory(stocks []model.LimitBoardStock, tradeDate string) (map[string]map[string]struct{}, error) {
|
||||||
codes := make([]string, 0, len(stocks))
|
codes := make([]string, 0, len(stocks))
|
||||||
for _, stock := range stocks {
|
for _, stock := range stocks {
|
||||||
@@ -281,3 +442,39 @@ func sortLimitBoardGroups(groupMap map[int][]model.LimitBoardStock) []model.Limi
|
|||||||
|
|
||||||
return groups
|
return groups
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func buildLimitOpenCompareGroups(openLowerStocks, otherStocks []model.LimitOpenCompareStock) []model.LimitOpenCompareGroup {
|
||||||
|
if openLowerStocks == nil {
|
||||||
|
openLowerStocks = []model.LimitOpenCompareStock{}
|
||||||
|
}
|
||||||
|
if otherStocks == nil {
|
||||||
|
otherStocks = []model.LimitOpenCompareStock{}
|
||||||
|
}
|
||||||
|
|
||||||
|
sortLimitOpenCompareStocks(openLowerStocks)
|
||||||
|
sortLimitOpenCompareStocks(otherStocks)
|
||||||
|
|
||||||
|
return []model.LimitOpenCompareGroup{
|
||||||
|
{
|
||||||
|
Key: "open_lower_previous_close",
|
||||||
|
Label: "开盘低于前收",
|
||||||
|
Count: len(openLowerStocks),
|
||||||
|
Stocks: openLowerStocks,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Key: "others",
|
||||||
|
Label: "其他涨停股",
|
||||||
|
Count: len(otherStocks),
|
||||||
|
Stocks: otherStocks,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func sortLimitOpenCompareStocks(stocks []model.LimitOpenCompareStock) {
|
||||||
|
sort.Slice(stocks, func(i, j int) bool {
|
||||||
|
if stocks[i].PctChg == stocks[j].PctChg {
|
||||||
|
return stocks[i].TsCode < stocks[j].TsCode
|
||||||
|
}
|
||||||
|
return stocks[i].PctChg > stocks[j].PctChg
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user