新增当日开盘价低于前一交易日涨停价格 101% 的判断。新增下载csv 文件接口。
This commit is contained in:
@@ -14,6 +14,8 @@ type LimitBoardService struct {
|
||||
DB *gorm.DB
|
||||
}
|
||||
|
||||
const stockLimitCombinedTable = "stock_limit_combined_data"
|
||||
|
||||
type limitBoardHistoryRow struct {
|
||||
TsCode string `gorm:"column:ts_code"`
|
||||
TradeDate string `gorm:"column:trade_date"`
|
||||
@@ -147,6 +149,59 @@ func (s *LimitBoardService) GetLimitOpenCompareGroups(tradeDate string) (model.L
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (s *LimitBoardService) GetLimitOpenThresholdGroups(tradeDate string) (model.LimitOpenThresholdResponse, error) {
|
||||
resp := model.LimitOpenThresholdResponse{TradeDate: tradeDate}
|
||||
const thresholdRatio = 1.01
|
||||
|
||||
tradeDates, dateIndex, err := s.loadTradeCalendar(tradeDate)
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
if dateIndex == -1 {
|
||||
resp.Summary.ThresholdRatio = thresholdRatio
|
||||
resp.Groups = buildLimitOpenThresholdGroups(nil, nil)
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
previousTradeDate := ""
|
||||
if dateIndex > 0 {
|
||||
previousTradeDate = tradeDates[dateIndex-1]
|
||||
}
|
||||
resp.PreviousTradeDate = previousTradeDate
|
||||
|
||||
stocks, err := s.loadLimitOpenThresholdStocks(tradeDate, previousTradeDate)
|
||||
if err != nil {
|
||||
return resp, err
|
||||
}
|
||||
|
||||
withinThresholdStocks := make([]model.LimitOpenThresholdStock, 0)
|
||||
atOrAboveThresholdStocks := make([]model.LimitOpenThresholdStock, 0)
|
||||
for _, stock := range stocks {
|
||||
if stock.HasPreviousClose && stock.PreviousClose > 0 {
|
||||
stock.OpenThresholdPrice = stock.PreviousClose * thresholdRatio
|
||||
}
|
||||
if stock.HasOpen && stock.HasPreviousClose && stock.PreviousClose > 0 {
|
||||
stock.OpenPreviousCloseRatio = stock.Open / stock.PreviousClose
|
||||
}
|
||||
|
||||
if stock.HasOpen && stock.HasPreviousClose && stock.Open < stock.OpenThresholdPrice {
|
||||
withinThresholdStocks = append(withinThresholdStocks, stock)
|
||||
continue
|
||||
}
|
||||
atOrAboveThresholdStocks = append(atOrAboveThresholdStocks, stock)
|
||||
}
|
||||
|
||||
resp.Summary = model.LimitOpenThresholdSummary{
|
||||
LimitCount: len(stocks),
|
||||
OpenWithinThresholdCount: len(withinThresholdStocks),
|
||||
OpenAtOrAboveThresholdCount: len(atOrAboveThresholdStocks),
|
||||
ThresholdRatio: thresholdRatio,
|
||||
}
|
||||
resp.Groups = buildLimitOpenThresholdGroups(withinThresholdStocks, atOrAboveThresholdStocks)
|
||||
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func isBeforeEleven(limitTime *string) bool {
|
||||
if limitTime == nil || *limitTime == "" {
|
||||
return false
|
||||
@@ -164,10 +219,13 @@ func isBeforeEleven(limitTime *string) bool {
|
||||
if len(timeText) < 4 {
|
||||
return false
|
||||
}
|
||||
if len(timeText) == 4 {
|
||||
switch len(timeText) {
|
||||
case 4:
|
||||
timeText += "00"
|
||||
}
|
||||
if len(timeText) != 6 {
|
||||
case 5:
|
||||
timeText = "0" + timeText
|
||||
case 6:
|
||||
default:
|
||||
return false
|
||||
}
|
||||
var err error
|
||||
@@ -202,9 +260,9 @@ func (s *LimitBoardService) buildRecentMaxBoardStats(tradeDates []string, dateIn
|
||||
}
|
||||
|
||||
var history []limitBoardHistoryRow
|
||||
if err := s.DB.Table("stock_limit_data").
|
||||
if err := s.DB.Table(stockLimitCombinedTable).
|
||||
Select("ts_code, DATE_FORMAT(trade_date, '%Y-%m-%d') AS trade_date").
|
||||
Where("trade_date BETWEEN ? AND ? AND status = ?", windowDates[0], windowDates[len(windowDates)-1], 1).
|
||||
Where("trade_date BETWEEN ? AND ? AND final_status = ?", windowDates[0], windowDates[len(windowDates)-1], 1).
|
||||
Order("ts_code ASC, trade_date ASC").
|
||||
Scan(&history).Error; err != nil {
|
||||
return nil, err
|
||||
@@ -283,25 +341,25 @@ func (s *LimitBoardService) loadCurrentLimitStocks(tradeDate string) ([]model.Li
|
||||
l.name,
|
||||
COALESCE(l.market, '') AS market,
|
||||
DATE_FORMAT(l.trade_date, '%Y-%m-%d') AS trade_date,
|
||||
COALESCE(d.close, l.close, 0) AS close,
|
||||
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
|
||||
COALESCE(l.calc_close, l.tushare_close, d.close, 0) AS close,
|
||||
COALESCE(l.calc_limit_price, 0) AS limit_price,
|
||||
COALESCE(l.tushare_pct_chg, d.pct_chg, 0) AS pct_chg,
|
||||
l.tushare_amount AS ak_amount,
|
||||
l.tushare_float_mv AS ak_float_market_value,
|
||||
l.tushare_total_mv AS ak_total_market_value,
|
||||
l.tushare_turnover_ratio AS ak_turnover_rate,
|
||||
l.tushare_fd_amount AS ak_seal_amount,
|
||||
l.tushare_first_time AS ak_first_limit_time,
|
||||
l.tushare_last_time AS ak_last_limit_time,
|
||||
l.tushare_open_times AS ak_break_limit_count,
|
||||
l.tushare_up_stat AS ak_limit_up_stat,
|
||||
l.tushare_limit_times AS ak_limit_up_days,
|
||||
l.tushare_industry AS ak_industry,
|
||||
l.calc_vol AS vol
|
||||
FROM stock_limit_combined_data l
|
||||
LEFT JOIN stock_daily d
|
||||
ON d.ts_code = l.ts_code AND d.trade_date = l.trade_date
|
||||
WHERE l.trade_date = ? AND l.status = 1
|
||||
WHERE l.trade_date = ? AND l.final_status = 1
|
||||
ORDER BY d.pct_chg DESC, l.ts_code ASC`
|
||||
|
||||
err := s.DB.Raw(query, tradeDate).Scan(&stocks).Error
|
||||
@@ -318,38 +376,38 @@ func (s *LimitBoardService) loadLimitOpenCompareStocks(tradeDate, previousTradeD
|
||||
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,
|
||||
COALESCE(l.calc_close, l.tushare_close, d.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
|
||||
FROM stock_limit_combined_data ppl
|
||||
WHERE ppl.ts_code = l.ts_code AND ppl.trade_date = ? AND ppl.final_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
|
||||
COALESCE(l.calc_limit_price, 0) AS limit_price,
|
||||
COALESCE(l.tushare_pct_chg, d.pct_chg, 0) AS pct_chg,
|
||||
l.tushare_amount AS ak_amount,
|
||||
l.tushare_float_mv AS ak_float_market_value,
|
||||
l.tushare_total_mv AS ak_total_market_value,
|
||||
l.tushare_turnover_ratio AS ak_turnover_rate,
|
||||
l.tushare_fd_amount AS ak_seal_amount,
|
||||
l.tushare_first_time AS ak_first_limit_time,
|
||||
l.tushare_last_time AS ak_last_limit_time,
|
||||
l.tushare_open_times AS ak_break_limit_count,
|
||||
l.tushare_up_stat AS ak_limit_up_stat,
|
||||
l.tushare_limit_times AS ak_limit_up_days,
|
||||
l.tushare_industry AS ak_industry,
|
||||
l.calc_vol AS vol
|
||||
FROM stock_limit_combined_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
|
||||
INNER JOIN stock_limit_combined_data pl
|
||||
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
|
||||
ORDER BY d.pct_chg DESC, l.ts_code ASC`
|
||||
|
||||
if previousTradeDate == "" {
|
||||
@@ -360,6 +418,52 @@ func (s *LimitBoardService) loadLimitOpenCompareStocks(tradeDate, previousTradeD
|
||||
return stocks, err
|
||||
}
|
||||
|
||||
func (s *LimitBoardService) loadLimitOpenThresholdStocks(tradeDate, previousTradeDate string) ([]model.LimitOpenThresholdStock, error) {
|
||||
var stocks []model.LimitOpenThresholdStock
|
||||
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(l.calc_close, l.tushare_close, d.close, 0) AS close,
|
||||
? AS previous_trade_date,
|
||||
(pl.calc_close IS NOT NULL OR pl.tushare_close IS NOT NULL OR pd.close IS NOT NULL) AS has_previous_close,
|
||||
COALESCE(pl.calc_close, pl.tushare_close, pd.close, 0) AS previous_close,
|
||||
COALESCE(l.calc_limit_price, 0) AS limit_price,
|
||||
COALESCE(l.tushare_pct_chg, d.pct_chg, 0) AS pct_chg,
|
||||
l.tushare_amount AS ak_amount,
|
||||
l.tushare_float_mv AS ak_float_market_value,
|
||||
l.tushare_total_mv AS ak_total_market_value,
|
||||
l.tushare_turnover_ratio AS ak_turnover_rate,
|
||||
l.tushare_fd_amount AS ak_seal_amount,
|
||||
l.tushare_first_time AS ak_first_limit_time,
|
||||
l.tushare_last_time AS ak_last_limit_time,
|
||||
l.tushare_open_times AS ak_break_limit_count,
|
||||
l.tushare_up_stat AS ak_limit_up_stat,
|
||||
l.tushare_limit_times AS ak_limit_up_days,
|
||||
l.tushare_industry AS ak_industry,
|
||||
l.calc_vol AS vol
|
||||
FROM stock_limit_combined_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_combined_data pl
|
||||
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
|
||||
ORDER BY d.pct_chg DESC, l.ts_code ASC`
|
||||
|
||||
if previousTradeDate == "" {
|
||||
return stocks, nil
|
||||
}
|
||||
|
||||
err := s.DB.Raw(query, previousTradeDate, previousTradeDate, previousTradeDate, tradeDate).Scan(&stocks).Error
|
||||
return stocks, err
|
||||
}
|
||||
|
||||
func (s *LimitBoardService) loadLimitHistory(stocks []model.LimitBoardStock, tradeDate string) (map[string]map[string]struct{}, error) {
|
||||
codes := make([]string, 0, len(stocks))
|
||||
for _, stock := range stocks {
|
||||
@@ -367,9 +471,9 @@ func (s *LimitBoardService) loadLimitHistory(stocks []model.LimitBoardStock, tra
|
||||
}
|
||||
|
||||
var history []limitBoardHistoryRow
|
||||
if err := s.DB.Table("stock_limit_data").
|
||||
if err := s.DB.Table(stockLimitCombinedTable).
|
||||
Select("ts_code, DATE_FORMAT(trade_date, '%Y-%m-%d') AS trade_date").
|
||||
Where("ts_code IN ? AND trade_date <= ? AND status = ?", codes, tradeDate, 1).
|
||||
Where("ts_code IN ? AND trade_date <= ? AND final_status = ?", codes, tradeDate, 1).
|
||||
Scan(&history).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -470,6 +574,33 @@ func buildLimitOpenCompareGroups(openLowerStocks, otherStocks []model.LimitOpenC
|
||||
}
|
||||
}
|
||||
|
||||
func buildLimitOpenThresholdGroups(withinThresholdStocks, atOrAboveThresholdStocks []model.LimitOpenThresholdStock) []model.LimitOpenThresholdGroup {
|
||||
if withinThresholdStocks == nil {
|
||||
withinThresholdStocks = []model.LimitOpenThresholdStock{}
|
||||
}
|
||||
if atOrAboveThresholdStocks == nil {
|
||||
atOrAboveThresholdStocks = []model.LimitOpenThresholdStock{}
|
||||
}
|
||||
|
||||
sortLimitOpenThresholdStocks(withinThresholdStocks)
|
||||
sortLimitOpenThresholdStocks(atOrAboveThresholdStocks)
|
||||
|
||||
return []model.LimitOpenThresholdGroup{
|
||||
{
|
||||
Key: "open_within_previous_close_101",
|
||||
Label: "开盘价低于前收101%",
|
||||
Count: len(withinThresholdStocks),
|
||||
Stocks: withinThresholdStocks,
|
||||
},
|
||||
{
|
||||
Key: "open_at_or_above_previous_close_101",
|
||||
Label: "开盘价大于等于前收101%",
|
||||
Count: len(atOrAboveThresholdStocks),
|
||||
Stocks: atOrAboveThresholdStocks,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func sortLimitOpenCompareStocks(stocks []model.LimitOpenCompareStock) {
|
||||
sort.Slice(stocks, func(i, j int) bool {
|
||||
if stocks[i].PctChg == stocks[j].PctChg {
|
||||
@@ -478,3 +609,12 @@ func sortLimitOpenCompareStocks(stocks []model.LimitOpenCompareStock) {
|
||||
return stocks[i].PctChg > stocks[j].PctChg
|
||||
})
|
||||
}
|
||||
|
||||
func sortLimitOpenThresholdStocks(stocks []model.LimitOpenThresholdStock) {
|
||||
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
|
||||
})
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ type StockService struct {
|
||||
|
||||
func (s *StockService) GetHistoryKline(code string) ([]model.StockDaily, error) {
|
||||
var dailies []model.StockDaily
|
||||
startDate := "2024-09-24"
|
||||
startDate := "2020-01-02"
|
||||
|
||||
err := s.DB.Table("stock_daily").
|
||||
Where("ts_code = ? AND trade_date >= ?", code, startDate).
|
||||
@@ -38,9 +38,9 @@ func (s *StockService) AnalyzePerformance(startDate, endDate string) ([]model.An
|
||||
}
|
||||
|
||||
// 查询涨停股
|
||||
query := `SELECT l.ts_code, l.name, d.open, d.high FROM stock_limit_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 l.ts_code = d.ts_code AND l.trade_date = d.trade_date
|
||||
WHERE l.trade_date = ? AND l.status = 1`
|
||||
WHERE l.trade_date = ? AND l.final_status = 1`
|
||||
|
||||
if err := s.DB.Raw(query, startDate).Scan(&limits).Error; err != nil {
|
||||
return nil, err
|
||||
|
||||
Reference in New Issue
Block a user