Files
qsdk/schema/dataset_summary.go
2026-05-02 13:00:06 +08:00

326 lines
12 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package schema
import (
"errors"
"strconv"
"strings"
"time"
"gorm.io/gorm"
)
// DatasetSummary 个股重要指标总览(每股一行,由 Basic / Daily / Indicator / IndicatorPro /
// FinaIndicator / MoneyTotal / PledgeStat / BlocksMember 等表聚合写入)。
type DatasetSummary struct {
ID uint `gorm:"primarykey"`
TsCode string `gorm:"type:varchar(20);not null;uniqueIndex:uq_dataset_summary_ts_code;index;comment:TS代码" json:"ts_code"`
UpdateYmd int `gorm:"index;comment:更新日期" json:"update_ymd"`
// --- DatasetBasic身份与分类 ---
Name string `gorm:"type:varchar(50);not null;default:'';comment:股票名称" json:"name"`
Industry string `gorm:"type:varchar(50);not null;default:'';comment:所属行业" json:"industry"`
Area string `gorm:"type:varchar(50);not null;default:'';comment:地域" json:"area"`
Market string `gorm:"type:varchar(50);not null;default:'';comment:市场类型" json:"market"`
ListDate string `gorm:"type:varchar(50);not null;default:'';comment:上市日期" json:"list_date"`
IsHS string `gorm:"type:varchar(2);default:'N';comment:沪深港通 N/H/S" json:"is_hs"`
IsSt string `gorm:"type:varchar(2);default:'N';comment:是否是ST" json:"is_st"`
// --- DatasetDaily / DatasetIndicator / DatasetIndicatorPro最近交易日行情与估值 ---
Close float64 `gorm:"type:decimal(20,4);comment:收盘价" json:"close"`
PctChg float64 `gorm:"type:decimal(20,6);comment:涨跌幅(%)" json:"pct_chg"`
Vol float64 `gorm:"type:decimal(20,2);comment:成交量(手)" json:"vol"`
Amount float64 `gorm:"type:decimal(20,2);comment:成交额(千元)" json:"amount"`
TurnoverRate float64 `gorm:"type:decimal(20,4);comment:换手率(%)" json:"turnover_rate"`
TurnoverRateF float64 `gorm:"type:decimal(20,4);comment:换手率(自由流通股%)" json:"turnover_rate_f"`
VolumeRatio float64 `gorm:"type:decimal(20,4);comment:量比" json:"volume_ratio"`
Pe float64 `gorm:"type:decimal(20,4);comment:市盈率" json:"pe"`
PeTtm float64 `gorm:"type:decimal(20,4);comment:市盈率TTM" json:"pe_ttm"`
Pb float64 `gorm:"type:decimal(20,4);comment:市净率" json:"pb"`
PsTtm float64 `gorm:"type:decimal(20,4);comment:市销率TTM" json:"ps_ttm"`
DvTtm float64 `gorm:"type:decimal(20,4);comment:股息率TTM(%)" json:"dv_ttm"`
TotalShare float64 `gorm:"type:decimal(20,4);comment:总股本(万股)" json:"total_share"`
FloatShare float64 `gorm:"type:decimal(20,4);comment:流通股本(万股)" json:"float_share"`
FreeShare float64 `gorm:"type:decimal(20,4);comment:自由流通股本(万)" json:"free_share"`
TotalMv float64 `gorm:"type:decimal(20,4);comment:总市值(万元)" json:"total_mv"`
CircMv float64 `gorm:"type:decimal(20,4);comment:流通市值(万元)" json:"circ_mv"`
AdjFactor float64 `gorm:"type:decimal(20,6);comment:复权因子(来自因子表)" json:"adj_factor"`
MaQfq5 float64 `gorm:"type:decimal(20,6);comment:MA5前复权" json:"ma_bfq5"`
MaQfq20 float64 `gorm:"type:decimal(20,6);comment:MA20前复权" json:"ma_bfq20"`
MacdDifQfq float64 `gorm:"type:decimal(20,6);comment:MACD DIF前复权" json:"macd_dif_bfq"`
RsiQfq12 float64 `gorm:"type:decimal(20,6);comment:RSI12前复权" json:"rsi_bfq12"`
// --- DatasetFinaIndicator最近一期财务核心 ---
FinaPeriod int `gorm:"index;comment:财务报告期数(与 fina 表 period 一致)" json:"fina_period"`
FinaAnnDate string `gorm:"type:varchar(32);default:'';comment:财报公告日" json:"fina_ann_date"`
FinaEndDate string `gorm:"type:varchar(32);default:'';comment:财报报告期" json:"fina_end_date"`
Eps float64 `gorm:"type:decimal(20,4);comment:基本每股收益" json:"eps"`
Bps float64 `gorm:"type:decimal(20,4);comment:每股净资产" json:"bps"`
Ocfps float64 `gorm:"type:decimal(20,4);comment:每股经营活动现金流净额" json:"ocfps"`
Roe float64 `gorm:"type:decimal(20,4);comment:净资产收益率" json:"roe"`
RoeWaa float64 `gorm:"type:decimal(20,4);comment:加权平均净资产收益率" json:"roe_waa"`
Roa float64 `gorm:"type:decimal(20,4);comment:总资产报酬率" json:"roa"`
DebtToAssets float64 `gorm:"type:decimal(20,4);comment:资产负债率" json:"debt_to_assets"`
GrossprofitMargin float64 `gorm:"type:decimal(20,4);comment:销售毛利率" json:"grossprofit_margin"`
NetprofitYoy float64 `gorm:"type:decimal(20,4);comment:归母净利润同比(%)" json:"netprofit_yoy"`
OrYoy float64 `gorm:"type:decimal(20,4);comment:营业收入同比(%)" json:"or_yoy"`
TrYoy float64 `gorm:"type:decimal(20,4);comment:营业总收入同比(%)" json:"tr_yoy"`
// --- DatasetMoneyTotalmoneyflow近 1/3 交易日主力净流入(万元) 与成交额,由源表按 update_ymd 倒序聚合 ---
Last1DayMfAmount float64 `gorm:"type:decimal(20,4);comment:最近1个交易日主力净流入(万元)" json:"last_1day_mf_amount"`
Last3DayMfAmount float64 `gorm:"type:decimal(20,4);comment:最近3个交易日主力净流入合计(万元)" json:"last_3day_mf_amount"`
Last1DayTotalAmount float64 `gorm:"type:decimal(20,4);comment:最近1个交易日成交额(万元,来自日线千元换算)" json:"last_1day_total_amount"`
Last3DayTotalAmount float64 `gorm:"type:decimal(20,4);comment:最近3个交易日成交额合计(万元)" json:"last_3day_total_amount"`
IsGreaterPervious bool `gorm:"comment:主力净流入是否大于上一交易日" json:"is_greater_pervious"`
// --- DatasetPledgeStat股权质押 ---
PledgeEndDate int `gorm:"comment:质押统计截止日期" json:"pledge_end_date"`
PledgeRatio float64 `gorm:"type:decimal(20,4);comment:质押比例" json:"pledge_ratio"`
PledgeCount float64 `gorm:"type:decimal(20,4);comment:质押次数" json:"pledge_count"`
// --- DatasetBlocksMember板块覆盖度(成分条数) ---
BlockMemberCount int `gorm:"not null;default:0;comment:所属板块/指数成分条数" json:"block_member_count"`
}
func (DatasetSummary) TableName() string {
return "dataset_summary"
}
// UpdateByTsCode 根据 ts_code 从各源表聚合并写入或更新本表对应行。
func (DatasetSummary) UpdateByTsCode(db *gorm.DB, tsCode string) error {
sum, err := buildDatasetSummary(db, tsCode)
if err != nil {
return err
}
return upsertDatasetSummary(db, sum)
}
// UpdateAll 遍历 dataset_basic 中全部 ts_code逐只聚合并写入或更新本表。
func (DatasetSummary) UpdateAll(db *gorm.DB) error {
var codes []string
if err := db.Model(&DatasetBasic{}).Pluck("ts_code", &codes).Error; err != nil {
return err
}
var z DatasetSummary
for _, c := range codes {
if c == "" {
continue
}
if err := z.UpdateByTsCode(db, c); err != nil {
return err
}
}
return nil
}
func upsertDatasetSummary(db *gorm.DB, sum *DatasetSummary) error {
if sum == nil || sum.TsCode == "" {
return errors.New("schema: invalid dataset_summary row")
}
var cur DatasetSummary
err := db.Where("ts_code = ?", sum.TsCode).Take(&cur).Error
if errors.Is(err, gorm.ErrRecordNotFound) {
return db.Create(sum).Error
}
if err != nil {
return err
}
sum.ID = cur.ID
return db.Save(sum).Error
}
func buildDatasetSummary(db *gorm.DB, tsCode string) (*DatasetSummary, error) {
if tsCode == "" {
return nil, errors.New("schema: empty ts_code")
}
var basic DatasetBasic
if err := db.Where("ts_code = ?", tsCode).Take(&basic).Error; err != nil {
return nil, err
}
var dip DatasetIndicatorPro
hasDip := db.Where("ts_code = ?", tsCode).Order("trade_date DESC").Take(&dip).Error == nil
var day DatasetDaily
hasDay := db.Where("ts_code = ?", tsCode).Order("trade_date DESC").Take(&day).Error == nil
var ind DatasetIndicator
hasInd := db.Where("ts_code = ?", tsCode).Order("trade_date DESC").Take(&ind).Error == nil
var fina DatasetFinaIndicator
hasFina := db.Where("ts_code = ?", tsCode).Order("period DESC").Take(&fina).Error == nil
last1Mf, last3Mf, last1Tot, last3Tot, mfGreater := aggregateMoneyForSummary(db, tsCode)
var pledge DatasetPledgeStat
hasPledge := db.Where("ts_code = ?", tsCode).Take(&pledge).Error == nil
var blockCnt int64
if err := db.Model(&DatasetBlocksMember{}).Where("ts_code = ?", tsCode).Count(&blockCnt).Error; err != nil {
return nil, err
}
updateYmd := todayYmd()
switch {
case hasDip && dip.TradeDate > 0:
updateYmd = dip.TradeDate
case hasDay && day.TradeDate > 0:
updateYmd = day.TradeDate
case hasInd && ind.TradeDate > 0:
updateYmd = ind.TradeDate
}
s := &DatasetSummary{
TsCode: tsCode,
UpdateYmd: updateYmd,
Name: basic.Name,
Industry: basic.Industry,
Area: basic.Area,
Market: basic.Market,
ListDate: basic.ListDate,
IsHS: basic.IsHS,
IsSt: isStFlag(basic.Name),
BlockMemberCount: int(blockCnt),
Last1DayMfAmount: last1Mf,
Last3DayMfAmount: last3Mf,
Last1DayTotalAmount: last1Tot,
Last3DayTotalAmount: last3Tot,
IsGreaterPervious: mfGreater,
}
if hasPledge {
s.PledgeEndDate = pledge.EndDate
s.PledgeRatio = pledge.PledgeRatio
s.PledgeCount = pledge.PledgeCount
}
if hasFina {
s.FinaPeriod = fina.Period
s.FinaAnnDate = fina.AnnDate
s.FinaEndDate = fina.EndDate
s.Eps = fina.Eps
s.Bps = fina.Bps
s.Ocfps = fina.Ocfps
s.Roe = fina.Roe
s.RoeWaa = fina.RoeWaa
s.Roa = fina.Roa
s.DebtToAssets = fina.DebtToAssets
s.GrossprofitMargin = fina.GrossprofitMargin
s.NetprofitYoy = fina.NetprofitYoy
s.OrYoy = fina.OrYoy
s.TrYoy = fina.TrYoy
}
switch {
case hasDip:
s.Close = dip.Close
s.PctChg = dip.PctChg
s.Vol = dip.Vol
s.Amount = dip.Amount
s.TurnoverRate = dip.TurnoverRate
s.TurnoverRateF = dip.TurnoverRateF
s.VolumeRatio = dip.VolumeRatio
s.Pe = dip.Pe
s.PeTtm = dip.PeTtm
s.Pb = dip.Pb
s.PsTtm = dip.PsTtm
s.DvTtm = dip.DvTtm
s.TotalShare = dip.TotalShare
s.FloatShare = dip.FloatShare
s.FreeShare = dip.FreeShare
s.TotalMv = dip.TotalMv
s.CircMv = dip.CircMv
s.AdjFactor = dip.AdjFactor
s.MaQfq5 = dip.MaQfq5
s.MaQfq20 = dip.MaQfq20
s.MacdDifQfq = dip.MacdDifQfq
s.RsiQfq12 = dip.RsiQfq12
case hasDay:
s.Close = day.Close
s.PctChg = day.PctChg
s.Vol = day.Vol
s.Amount = day.Amount
if hasInd {
s.TurnoverRate = ind.TurnoverRate
s.TurnoverRateF = ind.TurnoverRateF
s.VolumeRatio = ind.VolumeRatio
s.Pe = ind.Pe
s.PeTtm = ind.PeTtm
s.Pb = ind.Pb
s.PsTtm = ind.PsTtm
s.DvTtm = ind.DvTtm
s.TotalShare = ind.TotalShare
s.FloatShare = ind.FloatShare
s.FreeShare = ind.FreeShare
s.TotalMv = ind.TotalMv
s.CircMv = ind.CircMv
}
case hasInd:
s.Close = ind.Close
s.TurnoverRate = ind.TurnoverRate
s.TurnoverRateF = ind.TurnoverRateF
s.VolumeRatio = ind.VolumeRatio
s.Pe = ind.Pe
s.PeTtm = ind.PeTtm
s.Pb = ind.Pb
s.PsTtm = ind.PsTtm
s.DvTtm = ind.DvTtm
s.TotalShare = ind.TotalShare
s.FloatShare = ind.FloatShare
s.FreeShare = ind.FreeShare
s.TotalMv = ind.TotalMv
s.CircMv = ind.CircMv
}
return s, nil
}
// aggregateMoneyForSummary 由 dataset_money_totalmoneyflow与 dataset_daily 聚合近 1/3 日指标。
// 主力净流入取源表 MainForceNetWan万元按 update_ymd 从新到旧取至多 3 行。成交额取日线 amount千元换算为万元。
func aggregateMoneyForSummary(db *gorm.DB, tsCode string) (last1Mf, last3Mf, last1Tot, last3Tot float64, greater bool) {
var mfRows []DatasetMoney
_ = db.Where("ts_code = ?", tsCode).Order("update_ymd DESC, id DESC").Limit(3).Find(&mfRows).Error
for i, r := range mfRows {
v := r.MainForceNetWan
if i == 0 {
last1Mf = v
}
if i < 3 {
last3Mf += v
}
}
if len(mfRows) >= 2 {
greater = mfRows[0].MainForceNetWan > mfRows[1].MainForceNetWan
}
var dayRows []DatasetDaily
_ = db.Where("ts_code = ?", tsCode).Order("trade_date DESC").Limit(3).Find(&dayRows).Error
for i, d := range dayRows {
wan := d.Amount / 10 // 千元 -> 万元
if i == 0 {
last1Tot = wan
}
if i < 3 {
last3Tot += wan
}
}
return
}
func todayYmd() int {
n, err := strconv.Atoi(time.Now().Format("20060102"))
if err != nil {
return 0
}
return n
}
func isStFlag(name string) string {
s := strings.TrimSpace(name)
if len(s) >= 3 && strings.HasPrefix(s, "*ST") {
return "Y"
}
if len(s) >= 2 && strings.HasPrefix(s, "ST") {
return "Y"
}
return "N"
}