1、新增二板炸板情况统计;
2、更新项目结构;
This commit is contained in:
@@ -0,0 +1,241 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"math"
|
||||
"strings"
|
||||
|
||||
"level_go/internal/model"
|
||||
)
|
||||
|
||||
type limitBreakNextPerformanceRow struct {
|
||||
TradeDate string `gorm:"column:trade_date"`
|
||||
PreviousTradeDate string `gorm:"column:previous_trade_date"`
|
||||
PreviousTwoTradeDate string `gorm:"column:previous_two_trade_date"`
|
||||
NextTradeDate string `gorm:"column:next_trade_date"`
|
||||
Symbol string `gorm:"column:symbol"`
|
||||
TsCode string `gorm:"column:ts_code"`
|
||||
Name string `gorm:"column:name"`
|
||||
Market string `gorm:"column:market"`
|
||||
Industry string `gorm:"column:industry"`
|
||||
PreviousClose *float64 `gorm:"column:previous_close"`
|
||||
TPreClose *float64 `gorm:"column:t_pre_close"`
|
||||
TOpen *float64 `gorm:"column:t_open"`
|
||||
THigh *float64 `gorm:"column:t_high"`
|
||||
TClose *float64 `gorm:"column:t_close"`
|
||||
TPctChg *float64 `gorm:"column:t_pct_chg"`
|
||||
FirstTime *string `gorm:"column:first_time"`
|
||||
HasTushareBreakData bool `gorm:"column:has_tushare_break_data"`
|
||||
NextOpen *float64 `gorm:"column:next_open"`
|
||||
NextHigh *float64 `gorm:"column:next_high"`
|
||||
NextLow *float64 `gorm:"column:next_low"`
|
||||
NextClose *float64 `gorm:"column:next_close"`
|
||||
}
|
||||
|
||||
func (s *LimitBoardService) GetLimitBreakNextPerformance(req model.LimitBreakNextPerformanceRequest) (model.LimitBreakNextPerformanceResponse, error) {
|
||||
resp := model.LimitBreakNextPerformanceResponse{
|
||||
TradeStartDate: req.TradeStartDate,
|
||||
TradeEndDate: req.TradeEndDate,
|
||||
Page: req.Page,
|
||||
PageSize: req.PageSize,
|
||||
List: []model.LimitBreakNextPerformanceStock{},
|
||||
}
|
||||
|
||||
items, nextTradeDataCount, err := s.ListLimitBreakNextPerformanceStocks(req)
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
|
||||
resp.Total = len(items)
|
||||
if resp.Total > 0 {
|
||||
resp.TotalPages = (resp.Total + req.PageSize - 1) / req.PageSize
|
||||
}
|
||||
resp.Summary = model.LimitBreakNextPerformanceSummary{
|
||||
BreakCount: resp.Total,
|
||||
NextTradeDataCount: nextTradeDataCount,
|
||||
}
|
||||
|
||||
start := (req.Page - 1) * req.PageSize
|
||||
if start > resp.Total {
|
||||
start = resp.Total
|
||||
}
|
||||
end := start + req.PageSize
|
||||
if end > resp.Total {
|
||||
end = resp.Total
|
||||
}
|
||||
resp.List = items[start:end]
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (s *LimitBoardService) ListLimitBreakNextPerformanceStocks(req model.LimitBreakNextPerformanceRequest) ([]model.LimitBreakNextPerformanceStock, int, error) {
|
||||
rows, err := s.loadLimitBreakNextPerformanceRows(req.TradeStartDate, req.TradeEndDate)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
items := make([]model.LimitBreakNextPerformanceStock, 0, len(rows))
|
||||
nextTradeDataCount := 0
|
||||
for _, row := range rows {
|
||||
item := buildLimitBreakNextPerformanceStock(row)
|
||||
if req.TOpenGainLt101 != nil && item.TOpenGainLt101 != *req.TOpenGainLt101 {
|
||||
continue
|
||||
}
|
||||
if req.BreakTimeBefore1100 != nil && item.BreakTimeBefore1100 != *req.BreakTimeBefore1100 {
|
||||
continue
|
||||
}
|
||||
if item.NextOpen != nil || item.NextHigh != nil || item.NextLow != nil || item.NextClose != nil {
|
||||
nextTradeDataCount++
|
||||
}
|
||||
items = append(items, item)
|
||||
}
|
||||
return items, nextTradeDataCount, nil
|
||||
}
|
||||
|
||||
func (s *LimitBoardService) loadLimitBreakNextPerformanceRows(startDate, endDate string) ([]limitBreakNextPerformanceRow, 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
|
||||
),
|
||||
candidate AS (
|
||||
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,
|
||||
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.symbol,
|
||||
l.ts_code,
|
||||
l.name,
|
||||
COALESCE(NULLIF(l.market, ''), '') AS market,
|
||||
COALESCE(NULLIF(l.tushare_industry, ''), NULLIF(sb.industry, ''), '') AS industry,
|
||||
COALESCE(d.pre_close, l.calc_pre_close, pd.close, pl.calc_close, pl.tushare_close) AS previous_close,
|
||||
COALESCE(d.pre_close, l.calc_pre_close, pd.close, pl.calc_close, pl.tushare_close) AS t_pre_close,
|
||||
d.open AS t_open,
|
||||
d.high AS t_high,
|
||||
COALESCE(l.calc_close, l.tushare_close, d.close) AS t_close,
|
||||
COALESCE(l.tushare_pct_chg, d.pct_chg) AS t_pct_chg,
|
||||
CASE
|
||||
WHEN l.tushare_limit_type = 'Z'
|
||||
THEN NULLIF(NULLIF(TRIM(l.tushare_first_time), ''), 'None')
|
||||
ELSE NULL
|
||||
END AS first_time,
|
||||
(l.tushare_limit_type = 'Z') AS has_tushare_break_data,
|
||||
nd.open AS next_open,
|
||||
nd.high AS next_high,
|
||||
nd.low AS next_low,
|
||||
nd.close AS next_close
|
||||
FROM open_calendar 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
|
||||
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
|
||||
LEFT JOIN stock_daily d
|
||||
ON 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
|
||||
LEFT JOIN stock_daily nd
|
||||
ON 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
|
||||
AND c.previous_two_trade_date IS NOT NULL
|
||||
AND ppl.ts_code IS NULL
|
||||
)
|
||||
SELECT *
|
||||
FROM 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
|
||||
return rows, err
|
||||
}
|
||||
|
||||
func buildLimitBreakNextPerformanceStock(row limitBreakNextPerformanceRow) model.LimitBreakNextPerformanceStock {
|
||||
item := model.LimitBreakNextPerformanceStock{
|
||||
TradeDate: row.TradeDate,
|
||||
Symbol: row.Symbol,
|
||||
TsCode: row.TsCode,
|
||||
Name: row.Name,
|
||||
Market: row.Market,
|
||||
Industry: row.Industry,
|
||||
TOpen: row.TOpen,
|
||||
THigh: row.THigh,
|
||||
TClose: row.TClose,
|
||||
TPctChg: row.TPctChg,
|
||||
HasTushareBreakData: row.HasTushareBreakData,
|
||||
NextOpen: row.NextOpen,
|
||||
NextHigh: row.NextHigh,
|
||||
NextLow: row.NextLow,
|
||||
NextClose: row.NextClose,
|
||||
}
|
||||
if row.FirstTime != nil {
|
||||
item.FirstTime = formatLimitTime(*row.FirstTime)
|
||||
}
|
||||
item.BreakTimeBefore1100 = isLimitBreakTimeBeforeEleven(item.FirstTime)
|
||||
if row.TPreClose != nil && *row.TPreClose > 0 {
|
||||
item.TOpenPctChg = pctChangePtr(row.TOpen, *row.TPreClose)
|
||||
if row.TOpen != nil {
|
||||
item.TOpenGainLt101 = *row.TOpen < *row.TPreClose*1.01
|
||||
}
|
||||
}
|
||||
if row.TClose != nil && *row.TClose > 0 {
|
||||
item.NextOpenPctChg = pctChangePtr(row.NextOpen, *row.TClose)
|
||||
item.NextHighProfitPct = pctChangePtr(row.NextHigh, *row.TClose)
|
||||
item.NextLowLossPct = pctChangePtr(row.NextLow, *row.TClose)
|
||||
item.NextClosePctChg = pctChangePtr(row.NextClose, *row.TClose)
|
||||
if row.NextHigh != nil && row.THigh != nil {
|
||||
nextIntradayProfitable := *row.NextHigh >= *row.THigh
|
||||
item.NextIntradayProfitable = &nextIntradayProfitable
|
||||
}
|
||||
}
|
||||
return item
|
||||
}
|
||||
|
||||
func pctChangePtr(value *float64, base float64) *float64 {
|
||||
if value == nil || base == 0 {
|
||||
return nil
|
||||
}
|
||||
result := roundLimitBreakPct((*value - base) / base * 100)
|
||||
return &result
|
||||
}
|
||||
|
||||
func roundLimitBreakPct(value float64) float64 {
|
||||
return math.Round(value*100) / 100
|
||||
}
|
||||
|
||||
func formatLimitTime(value string) string {
|
||||
timeText := strings.TrimSpace(value)
|
||||
if timeText == "" || strings.EqualFold(timeText, "none") || strings.EqualFold(timeText, "null") {
|
||||
return ""
|
||||
}
|
||||
if strings.Contains(timeText, ":") {
|
||||
return timeText
|
||||
}
|
||||
switch len(timeText) {
|
||||
case 4:
|
||||
timeText += "00"
|
||||
case 5:
|
||||
timeText = "0" + timeText
|
||||
case 6:
|
||||
default:
|
||||
return value
|
||||
}
|
||||
return timeText[0:2] + ":" + timeText[2:4] + ":" + timeText[4:6]
|
||||
}
|
||||
|
||||
func isLimitBreakTimeBeforeEleven(limitTime string) bool {
|
||||
if strings.TrimSpace(limitTime) == "" {
|
||||
return true
|
||||
}
|
||||
return isBeforeEleven(&limitTime)
|
||||
}
|
||||
Reference in New Issue
Block a user