Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 165423e668 | |||
| ea69c515d2 | |||
| a2b101621b | |||
| 7f3117fd88 | |||
| ba41a6af76 | |||
| 94de0de8c7 | |||
| 4f4781aad3 | |||
| 5673e5c6ca | |||
| b3565ac6d7 | |||
| 01e5af3a0f |
84
conv/conv.go
84
conv/conv.go
@@ -1,19 +1,67 @@
|
||||
package conv
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
// AnyToString 任意类型转字符串
|
||||
// in: 输入值
|
||||
// 返回: 转换后的字符串
|
||||
func AnyToString(in any) (s string) {
|
||||
// AnyToString 将任意值转为可读的字符串:nil 为空串;标量与 []byte 用 strconv;指针会解引用;其余走 fmt.Sprint。
|
||||
func AnyToString(in any) string {
|
||||
for in != nil {
|
||||
rv := reflect.ValueOf(in)
|
||||
if rv.Kind() != reflect.Ptr {
|
||||
break
|
||||
}
|
||||
if rv.IsNil() {
|
||||
return ""
|
||||
}
|
||||
in = rv.Elem().Interface()
|
||||
}
|
||||
if in == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
return in.(string)
|
||||
switch v := in.(type) {
|
||||
case string:
|
||||
return v
|
||||
case []byte:
|
||||
return string(v)
|
||||
case bool:
|
||||
return strconv.FormatBool(v)
|
||||
case int:
|
||||
return strconv.Itoa(v)
|
||||
case int8:
|
||||
return strconv.FormatInt(int64(v), 10)
|
||||
case int16:
|
||||
return strconv.FormatInt(int64(v), 10)
|
||||
case int32:
|
||||
return strconv.FormatInt(int64(v), 10)
|
||||
case int64:
|
||||
return strconv.FormatInt(v, 10)
|
||||
case uint:
|
||||
return strconv.FormatUint(uint64(v), 10)
|
||||
case uint8:
|
||||
return strconv.FormatUint(uint64(v), 10)
|
||||
case uint16:
|
||||
return strconv.FormatUint(uint64(v), 10)
|
||||
case uint32:
|
||||
return strconv.FormatUint(uint64(v), 10)
|
||||
case uint64:
|
||||
return strconv.FormatUint(v, 10)
|
||||
case float32:
|
||||
return strconv.FormatFloat(float64(v), 'f', -1, 32)
|
||||
case float64:
|
||||
return strconv.FormatFloat(v, 'f', -1, 64)
|
||||
case json.Number:
|
||||
return string(v)
|
||||
default:
|
||||
if s, ok := in.(fmt.Stringer); ok {
|
||||
return s.String()
|
||||
}
|
||||
return fmt.Sprint(in)
|
||||
}
|
||||
}
|
||||
|
||||
// AnyToInt 将动态类型转为 int(两仓库 internal 中逻辑一致,此处合并分支)。
|
||||
@@ -51,14 +99,36 @@ func AnyToFloat64(v any) float64 {
|
||||
switch val := v.(type) {
|
||||
case float64:
|
||||
return val
|
||||
case string:
|
||||
return String2Float64(val)
|
||||
case float32:
|
||||
return float64(val)
|
||||
case int:
|
||||
return float64(val)
|
||||
case int8:
|
||||
return float64(val)
|
||||
case int16:
|
||||
return float64(val)
|
||||
case int32:
|
||||
return float64(val)
|
||||
case int64:
|
||||
return float64(val)
|
||||
case uint:
|
||||
return float64(val)
|
||||
case uint8:
|
||||
return float64(val)
|
||||
case uint16:
|
||||
return float64(val)
|
||||
case uint32:
|
||||
return float64(val)
|
||||
case uint64:
|
||||
return float64(val)
|
||||
case string:
|
||||
return String2Float64(val)
|
||||
case json.Number:
|
||||
f, err := val.Float64()
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
return f
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
|
||||
@@ -72,7 +72,7 @@ func (m *Builder) Build() string {
|
||||
}
|
||||
|
||||
func (m *Builder) SaveToFile(filePath string) error {
|
||||
rf, err := os.OpenFile(filePath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, os.ModePerm)
|
||||
rf, err := os.OpenFile(filePath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0o644)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
package schema
|
||||
|
||||
import "gorm.io/gorm"
|
||||
|
||||
// BlocksIndex 板块索引(采用 dataset/stock 的 code 唯一约束)。
|
||||
type BlocksIndex struct {
|
||||
gorm.Model
|
||||
Code string `gorm:"type:varchar(50);not null;default:'';comment:板块代码;uniqueIndex:uq_blocks_index_code" json:"code"`
|
||||
Name string `gorm:"type:varchar(50);not null;default:'';comment:板块名称" json:"name"`
|
||||
IsRecommend bool `gorm:"type:bool;not null;default:false;comment:是否推荐" json:"is_recommend"`
|
||||
}
|
||||
|
||||
func (BlocksIndex) TableName() string {
|
||||
return "blocks_index"
|
||||
}
|
||||
|
||||
// Key 板块业务主键:code。
|
||||
func (b *BlocksIndex) Key() string {
|
||||
return b.Code
|
||||
}
|
||||
|
||||
// DisplayName 展示名称:非空 name,否则回退 code。
|
||||
func (b *BlocksIndex) DisplayName() string {
|
||||
if b.Name != "" {
|
||||
return b.Name
|
||||
}
|
||||
return b.Code
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
package schema
|
||||
|
||||
import "gorm.io/gorm"
|
||||
|
||||
// BlocksMember 板块成分股。
|
||||
type BlocksMember struct {
|
||||
gorm.Model
|
||||
TiCode string `gorm:"type:varchar(50);not null;default:'';comment:板块代码;index" json:"ti_code"`
|
||||
StockCode string `gorm:"type:varchar(50);not null;default:'';comment:股票代码;index" json:"stock_code"`
|
||||
Weight float64 `gorm:"type:float;not null;default:0;comment:权重" json:"weight"`
|
||||
}
|
||||
|
||||
func (BlocksMember) TableName() string {
|
||||
return "blocks_member"
|
||||
}
|
||||
|
||||
// Key 成分在板块内的键:ti_code + stock_code。
|
||||
func (b *BlocksMember) Key() string {
|
||||
if b.TiCode == "" && b.StockCode == "" {
|
||||
return ""
|
||||
}
|
||||
return b.TiCode + "/" + b.StockCode
|
||||
}
|
||||
|
||||
// HasWeight 是否带权重量化成分。
|
||||
func (b *BlocksMember) HasWeight() bool {
|
||||
return b.Weight > 0
|
||||
}
|
||||
@@ -2,8 +2,8 @@ package schema
|
||||
|
||||
import "gorm.io/gorm"
|
||||
|
||||
// StockBasic 股票基本信息表(合并 dataset/stock 与 gostock 字段;gostock 独有 Level、Desc)。
|
||||
type StockBasic struct {
|
||||
// DatasetBasic 股票基本信息表(合并 dataset/stock 与 gostock 字段;gostock 独有 Level、Desc)。
|
||||
type DatasetBasic struct {
|
||||
gorm.Model
|
||||
TsCode string `gorm:"type:varchar(50);not null;index;comment:TS代码"`
|
||||
Symbol string `gorm:"type:varchar(50);not null;comment:股票代码"`
|
||||
@@ -21,17 +21,17 @@ type StockBasic struct {
|
||||
ActEntType string `gorm:"type:varchar(50);not null;default:'';comment:实控人企业性质"`
|
||||
}
|
||||
|
||||
func (StockBasic) TableName() string {
|
||||
return "stock_basic"
|
||||
func (DatasetBasic) TableName() string {
|
||||
return "dataset_basic"
|
||||
}
|
||||
|
||||
// Key 业务主键:TS 代码。
|
||||
func (s *StockBasic) Key() string {
|
||||
func (s *DatasetBasic) Key() string {
|
||||
return s.TsCode
|
||||
}
|
||||
|
||||
// DisplaySymbol 展示用代码:有 symbol 用 symbol,否则用 ts_code。
|
||||
func (s *StockBasic) DisplaySymbol() string {
|
||||
func (s *DatasetBasic) DisplaySymbol() string {
|
||||
if s.Symbol != "" {
|
||||
return s.Symbol
|
||||
}
|
||||
@@ -39,6 +39,6 @@ func (s *StockBasic) DisplaySymbol() string {
|
||||
}
|
||||
|
||||
// IsNorthbound 是否沪深港通标的(H 沪股通 / S 深股通)。
|
||||
func (s *StockBasic) IsNorthbound() bool {
|
||||
func (s *DatasetBasic) IsNorthbound() bool {
|
||||
return s.IsHS == "H" || s.IsHS == "S"
|
||||
}
|
||||
28
schema/dataset_blocks_index.go
Normal file
28
schema/dataset_blocks_index.go
Normal file
@@ -0,0 +1,28 @@
|
||||
package schema
|
||||
|
||||
import "gorm.io/gorm"
|
||||
|
||||
// DatasetBlocksIndex 板块索引(采用 dataset/stock 的 code 唯一约束)。
|
||||
type DatasetBlocksIndex struct {
|
||||
gorm.Model
|
||||
TiCode string `gorm:"type:varchar(50);not null;default:'';comment:指数代码;uniqueIndex:uq_blocks_index_code" json:"ti_code"`
|
||||
Name string `gorm:"type:varchar(50);not null;default:'';comment:板块名称" json:"name"`
|
||||
IsRecommend bool `gorm:"type:bool;not null;default:false;comment:是否推荐" json:"is_recommend"`
|
||||
}
|
||||
|
||||
func (DatasetBlocksIndex) TableName() string {
|
||||
return "dataset_blocks_index"
|
||||
}
|
||||
|
||||
// Key 板块业务主键:code。
|
||||
func (b *DatasetBlocksIndex) Key() string {
|
||||
return b.TiCode
|
||||
}
|
||||
|
||||
// DisplayName 展示名称:非空 name,否则回退 code。
|
||||
func (b *DatasetBlocksIndex) DisplayName() string {
|
||||
if b.Name != "" {
|
||||
return b.Name
|
||||
}
|
||||
return b.TiCode
|
||||
}
|
||||
26
schema/dataset_blocks_member.go
Normal file
26
schema/dataset_blocks_member.go
Normal file
@@ -0,0 +1,26 @@
|
||||
package schema
|
||||
|
||||
// DatasetBlocksMember 板块成分股。
|
||||
type DatasetBlocksMember struct {
|
||||
ID uint `gorm:"primarykey"`
|
||||
TiCode string `gorm:"type:varchar(50);not null;default:'';comment:板块代码;index" json:"ti_code"`
|
||||
TsCode string `gorm:"type:varchar(50);not null;default:'';comment:股票代码;index" json:"ts_code"`
|
||||
Weight float64 `gorm:"type:float;not null;default:0;comment:权重" json:"weight"`
|
||||
}
|
||||
|
||||
func (DatasetBlocksMember) TableName() string {
|
||||
return "dataset_blocks_member"
|
||||
}
|
||||
|
||||
// Key 成分在板块内的键:ti_code + ts_code。
|
||||
func (b *DatasetBlocksMember) Key() string {
|
||||
if b.TiCode == "" && b.TsCode == "" {
|
||||
return ""
|
||||
}
|
||||
return b.TiCode + "/" + b.TsCode
|
||||
}
|
||||
|
||||
// HasWeight 是否带权重量化成分。
|
||||
func (b *DatasetBlocksMember) HasWeight() bool {
|
||||
return b.Weight > 0
|
||||
}
|
||||
@@ -2,8 +2,8 @@ package schema
|
||||
|
||||
import "strconv"
|
||||
|
||||
// StockDaily 股票日线数据(两仓库结构一致)。
|
||||
type StockDaily struct {
|
||||
// DatasetDaily 股票日线数据(两仓库结构一致)。
|
||||
type DatasetDaily struct {
|
||||
ID uint `gorm:"primarykey;autoIncrement" json:"id"`
|
||||
TsCode string `gorm:"type:varchar(20);not null;index:idx_ts_code;uniqueIndex:un_code_date;comment:股票代码" json:"ts_code"`
|
||||
TradeDate int `gorm:"index:idx_trade_date;uniqueIndex:un_code_date;comment:交易日期" json:"trade_date"`
|
||||
@@ -19,12 +19,12 @@ type StockDaily struct {
|
||||
Amount float64 `gorm:"type:decimal(20,2);comment:成交额(千元)" json:"amount"`
|
||||
}
|
||||
|
||||
func (StockDaily) TableName() string {
|
||||
return "stock_daily"
|
||||
func (DatasetDaily) TableName() string {
|
||||
return "dataset_daily"
|
||||
}
|
||||
|
||||
// Key 业务主键:ts_code + 交易日。
|
||||
func (d *StockDaily) Key() string {
|
||||
func (d *DatasetDaily) Key() string {
|
||||
if d.TsCode == "" && d.TradeDate == 0 {
|
||||
return ""
|
||||
}
|
||||
@@ -32,17 +32,17 @@ func (d *StockDaily) Key() string {
|
||||
}
|
||||
|
||||
// IsRising 是否收涨(昨收有效且收盘高于昨收)。
|
||||
func (d *StockDaily) IsRising() bool {
|
||||
func (d *DatasetDaily) IsRising() bool {
|
||||
return d.PreClose > 0 && d.Close > d.PreClose
|
||||
}
|
||||
|
||||
// IsFalling 是否收跌。
|
||||
func (d *StockDaily) IsFalling() bool {
|
||||
func (d *DatasetDaily) IsFalling() bool {
|
||||
return d.PreClose > 0 && d.Close < d.PreClose
|
||||
}
|
||||
|
||||
// PctChangeFromPre 由昨收计算的涨跌幅(%);昨收无效时返回 0。
|
||||
func (d *StockDaily) PctChangeFromPre() float64 {
|
||||
func (d *DatasetDaily) PctChangeFromPre() float64 {
|
||||
if d.PreClose <= 0 {
|
||||
return 0
|
||||
}
|
||||
@@ -50,7 +50,7 @@ func (d *StockDaily) PctChangeFromPre() float64 {
|
||||
}
|
||||
|
||||
// AmplitudePct 振幅(相对昨收,%):(最高-最低)/昨收*100。
|
||||
func (d *StockDaily) AmplitudePct() float64 {
|
||||
func (d *DatasetDaily) AmplitudePct() float64 {
|
||||
if d.PreClose <= 0 {
|
||||
return 0
|
||||
}
|
||||
@@ -6,8 +6,8 @@ import (
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// StockFinaIndicator 财务指标模型
|
||||
type StockFinaIndicator struct {
|
||||
// DatasetFinaIndicator 财务指标模型
|
||||
type DatasetFinaIndicator struct {
|
||||
gorm.Model
|
||||
TsCode string `gorm:"type:varchar(20);not null;index:fi_ts_code;uniqueIndex:un_fi_code_date;comment:TS代码"`
|
||||
Period int `gorm:"index:idx_period;uniqueIndex:un_fi_code_date;comment:报告期数"`
|
||||
@@ -205,12 +205,12 @@ type StockFinaIndicator struct {
|
||||
}
|
||||
|
||||
// TableName 设置表名
|
||||
func (StockFinaIndicator) TableName() string {
|
||||
return "fina_indicator"
|
||||
func (DatasetFinaIndicator) TableName() string {
|
||||
return "dataset_fina_indicator"
|
||||
}
|
||||
|
||||
// Key 与表 uniqueIndex un_fi_code_date 一致:ts_code + period。
|
||||
func (f *StockFinaIndicator) Key() string {
|
||||
func (f *DatasetFinaIndicator) Key() string {
|
||||
if f.TsCode == "" && f.Period == 0 {
|
||||
return ""
|
||||
}
|
||||
@@ -218,7 +218,7 @@ func (f *StockFinaIndicator) Key() string {
|
||||
}
|
||||
|
||||
// RowLabel 便于日志/调试:ts_code + 报告期 end_date + 公告 ann_date。
|
||||
func (f *StockFinaIndicator) RowLabel() string {
|
||||
func (f *DatasetFinaIndicator) RowLabel() string {
|
||||
if f.EndDate != "" || f.AnnDate != "" {
|
||||
return f.TsCode + " end=" + f.EndDate + " ann=" + f.AnnDate
|
||||
}
|
||||
@@ -2,8 +2,8 @@ package schema
|
||||
|
||||
import "strconv"
|
||||
|
||||
// StockIndicator 每日基本面指标(采用 dataset/stock 的 decimal 定义,与采集端迁移一致)。
|
||||
type StockIndicator struct {
|
||||
// DatasetIndicator 每日基本面指标(采用 dataset/stock 的 decimal 定义,与采集端迁移一致)。
|
||||
type DatasetIndicator struct {
|
||||
ID uint `gorm:"primarykey;autoIncrement"`
|
||||
TsCode string `gorm:"type:varchar(20);not null;index:si_ts_code;uniqueIndex:un_si_code_date;comment:股票代码" json:"ts_code"`
|
||||
TradeDate int `gorm:"index:si_trade_date;uniqueIndex:un_si_code_date;comment:交易日期" json:"trade_date"`
|
||||
@@ -23,15 +23,14 @@ type StockIndicator struct {
|
||||
FreeShare float64 `gorm:"type:decimal(20,4);comment:自由流通股本(万)"`
|
||||
TotalMv float64 `gorm:"type:decimal(20,4);comment:总市值(万元)"`
|
||||
CircMv float64 `gorm:"type:decimal(20,4);comment:流通市值(万元)"`
|
||||
Roe float64 `gorm:"type:decimal(20,4);comment:ROE(%) 净利润/股本"`
|
||||
}
|
||||
|
||||
func (StockIndicator) TableName() string {
|
||||
return "stock_indicator"
|
||||
func (DatasetIndicator) TableName() string {
|
||||
return "dataset_indicator"
|
||||
}
|
||||
|
||||
// Key 业务主键:ts_code + 交易日。
|
||||
func (s *StockIndicator) Key() string {
|
||||
func (s *DatasetIndicator) Key() string {
|
||||
if s.TsCode == "" && s.TradeDate == 0 {
|
||||
return ""
|
||||
}
|
||||
@@ -39,11 +38,11 @@ func (s *StockIndicator) Key() string {
|
||||
}
|
||||
|
||||
// HasTotalMV 总市值是否已填充(大于 0)。
|
||||
func (s *StockIndicator) HasTotalMV() bool {
|
||||
func (s *DatasetIndicator) HasTotalMV() bool {
|
||||
return s.TotalMv > 0
|
||||
}
|
||||
|
||||
// HasCircMV 流通市值是否已填充。
|
||||
func (s *StockIndicator) HasCircMV() bool {
|
||||
func (s *DatasetIndicator) HasCircMV() bool {
|
||||
return s.CircMv > 0
|
||||
}
|
||||
66
schema/dataset_indicator_pro.go
Normal file
66
schema/dataset_indicator_pro.go
Normal file
@@ -0,0 +1,66 @@
|
||||
package schema
|
||||
|
||||
import "strconv"
|
||||
|
||||
// DatasetIndicatorPro 对应 Tushare stk_factor_pro 当前请求字段集(见 tushare/indicator.go 的 fields 列表)。
|
||||
type DatasetIndicatorPro struct {
|
||||
ID uint `gorm:"primarykey;autoIncrement"`
|
||||
TsCode string `gorm:"type:varchar(20);not null;index:dip_ts_code;uniqueIndex:un_dip_code_date;comment:股票代码" json:"ts_code"`
|
||||
TradeDate int `gorm:"index:dip_trade_date;uniqueIndex:un_dip_code_date;comment:交易日期" json:"trade_date"`
|
||||
Open float64 `gorm:"type:decimal(20,4);comment:开盘价"`
|
||||
High float64 `gorm:"type:decimal(20,4);comment:最高价"`
|
||||
Low float64 `gorm:"type:decimal(20,4);comment:最低价"`
|
||||
Close float64 `gorm:"type:decimal(20,4);comment:收盘价"`
|
||||
PreClose float64 `gorm:"type:decimal(20,4);comment:昨收价"`
|
||||
Change float64 `gorm:"type:decimal(20,4);comment:涨跌额"`
|
||||
PctChg float64 `gorm:"type:decimal(20,6);comment:涨跌幅%"`
|
||||
Vol float64 `gorm:"type:decimal(20,2);comment:成交量(手)"`
|
||||
Amount float64 `gorm:"type:decimal(20,2);comment:成交额(千元)"`
|
||||
TurnoverRate float64 `gorm:"type:decimal(20,4);comment:换手率(%)"`
|
||||
TurnoverRateF float64 `gorm:"type:decimal(20,4);comment:换手率(自由流通股)"`
|
||||
VolumeRatio float64 `gorm:"type:decimal(20,4);comment:量比"`
|
||||
Pe float64 `gorm:"type:decimal(20,4);comment:市盈率"`
|
||||
PeTtm float64 `gorm:"type:decimal(20,4);comment:市盈率TTM"`
|
||||
Pb float64 `gorm:"type:decimal(20,4);comment:市净率"`
|
||||
Ps float64 `gorm:"type:decimal(20,4);comment:市销率"`
|
||||
PsTtm float64 `gorm:"type:decimal(20,4);comment:市销率TTM"`
|
||||
DvRatio float64 `gorm:"type:decimal(20,4);comment:股息率(%)"`
|
||||
DvTtm float64 `gorm:"type:decimal(20,4);comment:股息率TTM(%)"`
|
||||
TotalShare float64 `gorm:"type:decimal(20,4);comment:总股本(万股)"`
|
||||
FloatShare float64 `gorm:"type:decimal(20,4);comment:流通股本(万股)"`
|
||||
FreeShare float64 `gorm:"type:decimal(20,4);comment:自由流通股本(万股)"`
|
||||
TotalMv float64 `gorm:"type:decimal(20,4);comment:总市值(万元)"`
|
||||
CircMv float64 `gorm:"type:decimal(20,4);comment:流通市值(万元)"`
|
||||
AdjFactor float64 `gorm:"type:decimal(20,6);comment:复权因子"`
|
||||
MaQfq5 float64 `gorm:"type:decimal(20,6);comment:MA5前复权"`
|
||||
MaQfq10 float64 `gorm:"type:decimal(20,6);comment:MA10前复权"`
|
||||
MaQfq20 float64 `gorm:"type:decimal(20,6);comment:MA20前复权"`
|
||||
MaQfq60 float64 `gorm:"type:decimal(20,6);comment:MA60前复权"`
|
||||
EmaQfq5 float64 `gorm:"type:decimal(20,6);comment:EMA5前复权"`
|
||||
EmaQfq10 float64 `gorm:"type:decimal(20,6);comment:EMA10前复权"`
|
||||
EmaQfq20 float64 `gorm:"type:decimal(20,6);comment:EMA20前复权"`
|
||||
MacdQfq float64 `gorm:"type:decimal(20,6);comment:MACD前复权"`
|
||||
MacdDifQfq float64 `gorm:"type:decimal(20,6);comment:MACD DIF前复权"`
|
||||
MacdDeaQfq float64 `gorm:"type:decimal(20,6);comment:MACD DEA前复权"`
|
||||
RsiQfq6 float64 `gorm:"type:decimal(20,6);comment:RSI6前复权"`
|
||||
RsiQfq12 float64 `gorm:"type:decimal(20,6);comment:RSI12前复权"`
|
||||
RsiQfq24 float64 `gorm:"type:decimal(20,6);comment:RSI24前复权"`
|
||||
KdjKQfq float64 `gorm:"type:decimal(20,6);comment:KDJ-K前复权"`
|
||||
KdjDQfq float64 `gorm:"type:decimal(20,6);comment:KDJ-D前复权"`
|
||||
KdjQfq float64 `gorm:"type:decimal(20,6);comment:KDJ-J前复权"`
|
||||
BollUpperQfq float64 `gorm:"type:decimal(20,6);comment:BOLL上轨前复权"`
|
||||
BollMidQfq float64 `gorm:"type:decimal(20,6);comment:BOLL中轨前复权"`
|
||||
BollLowerQfq float64 `gorm:"type:decimal(20,6);comment:BOLL下轨前复权"`
|
||||
}
|
||||
|
||||
func (DatasetIndicatorPro) TableName() string {
|
||||
return "dataset_indicator_pro"
|
||||
}
|
||||
|
||||
// Key 业务主键:ts_code + 交易日。
|
||||
func (s *DatasetIndicatorPro) Key() string {
|
||||
if s.TsCode == "" && s.TradeDate == 0 {
|
||||
return ""
|
||||
}
|
||||
return s.TsCode + "#" + strconv.Itoa(s.TradeDate)
|
||||
}
|
||||
33
schema/dataset_money.go
Normal file
33
schema/dataset_money.go
Normal file
@@ -0,0 +1,33 @@
|
||||
package schema
|
||||
|
||||
// DatasetMoney个股资金流向(与 Tushare moneyflow 接口字段一致,每行 ts_code+trade_date)。
|
||||
// 金额单位为万元,量单位为手;分类阈值见 Tushare 文档(小单 5 万以下、中单 5~20 万等)。
|
||||
type DatasetMoney struct {
|
||||
ID uint `gorm:"primarykey;autoIncrement"`
|
||||
TsCode string `gorm:"type:varchar(20);not null;uniqueIndex:un_money_ts_date;index;comment:TS代码" json:"ts_code"`
|
||||
UpdateYmd int `gorm:"uniqueIndex:un_money_ts_date;index;comment:更新日期" json:"update_ymd"`
|
||||
|
||||
BuySmVol int64 `gorm:"comment:小单买入量(手)" json:"buy_sm_vol"`
|
||||
BuySmAmount float64 `gorm:"type:decimal(20,4);comment:小单买入金额(万元)" json:"buy_sm_amount"`
|
||||
SellSmVol int64 `gorm:"comment:小单卖出量(手)" json:"sell_sm_vol"`
|
||||
SellSmAmount float64 `gorm:"type:decimal(20,4);comment:小单卖出金额(万元)" json:"sell_sm_amount"`
|
||||
BuyMdVol int64 `gorm:"comment:中单买入量(手)" json:"buy_md_vol"`
|
||||
BuyMdAmount float64 `gorm:"type:decimal(20,4);comment:中单买入金额(万元)" json:"buy_md_amount"`
|
||||
SellMdVol int64 `gorm:"comment:中单卖出量(手)" json:"sell_md_vol"`
|
||||
SellMdAmount float64 `gorm:"type:decimal(20,4);comment:中单卖出金额(万元)" json:"sell_md_amount"`
|
||||
BuyLgVol int64 `gorm:"comment:大单买入量(手)" json:"buy_lg_vol"`
|
||||
BuyLgAmount float64 `gorm:"type:decimal(20,4);comment:大单买入金额(万元)" json:"buy_lg_amount"`
|
||||
SellLgVol int64 `gorm:"comment:大单卖出量(手)" json:"sell_lg_vol"`
|
||||
SellLgAmount float64 `gorm:"type:decimal(20,4);comment:大单卖出金额(万元)" json:"sell_lg_amount"`
|
||||
BuyElgVol int64 `gorm:"comment:特大单买入量(手)" json:"buy_elg_vol"`
|
||||
BuyElgAmount float64 `gorm:"type:decimal(20,4);comment:特大单买入金额(万元)" json:"buy_elg_amount"`
|
||||
SellElgVol int64 `gorm:"comment:特大单卖出量(手)" json:"sell_elg_vol"`
|
||||
SellElgAmount float64 `gorm:"type:decimal(20,4);comment:特大单卖出金额(万元)" json:"sell_elg_amount"`
|
||||
NetMfVol int64 `gorm:"comment:净流入量(手)" json:"net_mf_vol"`
|
||||
NetMfAmount float64 `gorm:"type:decimal(20,4);comment:净流入额(万元)" json:"net_mf_amount"`
|
||||
MainForceNetWan float64 `gorm:"type:decimal(20,4);comment:主力净流入(万元)" json:"main_force_net_wan"`
|
||||
}
|
||||
|
||||
func (DatasetMoney) TableName() string {
|
||||
return "dataset_money"
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
package schema
|
||||
|
||||
// PledgeStat 股权质押统计(表 pledge_stat;dataset/stock 中原为 PledgeStatModel,业务层 Save 请留在各应用内)。
|
||||
type PledgeStat struct {
|
||||
// DatasetPledgeStat 股权质押统计(表 dataset_pledge_stat;dataset/stock 中原为 PledgeStatModel,业务层 Save 请留在各应用内)。
|
||||
type DatasetPledgeStat struct {
|
||||
ID uint `gorm:"primarykey" json:"id"`
|
||||
TsCode string `gorm:"type:varchar(50);not null;default:'';comment:股票代码;uniqueIndex:uq_pledge_stat_ts_code" json:"ts_code"`
|
||||
EndDate int `gorm:"type:int;not null;default:0;comment:截止日期" json:"end_date"`
|
||||
@@ -12,19 +12,19 @@ type PledgeStat struct {
|
||||
PledgeRatio float64 `json:"pledge_ratio"`
|
||||
}
|
||||
|
||||
func (PledgeStat) TableName() string {
|
||||
return "pledge_stat"
|
||||
func (DatasetPledgeStat) TableName() string {
|
||||
return "dataset_pledge_stat"
|
||||
}
|
||||
|
||||
// Key 与唯一约束 uq_pledge_stat_ts_code 一致:ts_code。
|
||||
func (p *PledgeStat) Key() string {
|
||||
func (p *DatasetPledgeStat) Key() string {
|
||||
return p.TsCode
|
||||
}
|
||||
|
||||
// HasPledgeFacts 是否具备任一质押统计字段(比例、次数或股本)。
|
||||
func (p *PledgeStat) HasPledgeFacts() bool {
|
||||
func (p *DatasetPledgeStat) HasPledgeFacts() bool {
|
||||
return p.PledgeRatio > 0 || p.PledgeCount > 0 || p.TotalShare > 0
|
||||
}
|
||||
|
||||
// PledgeStatModel 兼容 dataset/stock 中的类型名。
|
||||
type PledgeStatModel = PledgeStat
|
||||
// DatasetPledgeStatModel 兼容 dataset/stock 中的类型名。
|
||||
type DatasetPledgeStatModel = DatasetPledgeStat
|
||||
325
schema/dataset_summary.go
Normal file
325
schema/dataset_summary.go
Normal file
@@ -0,0 +1,325 @@
|
||||
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:"uniqueIndex:uq_dataset_summary_ts_code;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"`
|
||||
|
||||
// --- DatasetMoneyTotal(moneyflow):近 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_total(moneyflow)与 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"
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
package schema
|
||||
|
||||
// MoneyTotal 资金流汇总(采用 dataset/stock 的索引定义)。
|
||||
type MoneyTotal struct {
|
||||
ID uint `gorm:"primarykey"`
|
||||
Code string `gorm:"type:varchar(20);not null;uniqueIndex:uq_money_total_code"`
|
||||
Last1DayMfAmount float64
|
||||
Last3DayMfAmount float64
|
||||
Last1DayTotalAmount float64
|
||||
Last3DayTotalAmount float64
|
||||
IsGreaterPervious bool
|
||||
}
|
||||
|
||||
func (MoneyTotal) TableName() string {
|
||||
return "money_total"
|
||||
}
|
||||
|
||||
// Key 与唯一索引 uq_money_total_code 一致。
|
||||
func (m *MoneyTotal) Key() string {
|
||||
return m.Code
|
||||
}
|
||||
|
||||
// NetFlow1Day 最近 1 日主力净流入(万元),与字段语义一致。
|
||||
func (m *MoneyTotal) NetFlow1Day() float64 {
|
||||
return m.Last1DayMfAmount
|
||||
}
|
||||
|
||||
// NetFlow3Day 最近 3 日主力净流入(万元)。
|
||||
func (m *MoneyTotal) NetFlow3Day() float64 {
|
||||
return m.Last3DayMfAmount
|
||||
}
|
||||
@@ -2,17 +2,19 @@ package schema
|
||||
|
||||
import "git.apinb.com/bsm-sdk/core/database"
|
||||
|
||||
// RegisterAutoMigrate 将本包内与 stock/gostock 共用的表注册到 bsm-sdk 的迁移列表(可选;也可在各应用 init 中自行 AppendMigrate)。
|
||||
// RegisterAutoMigrate 共用的表注册到 bsm-sdk 的迁移列表(可选;也可在各应用 init 中自行 AppendMigrate)。
|
||||
func RegisterAutoMigrate() {
|
||||
for _, t := range []any{
|
||||
&StockBasic{},
|
||||
&StockDaily{},
|
||||
&BlocksIndex{},
|
||||
&BlocksMember{},
|
||||
&MoneyTotal{},
|
||||
&PledgeStat{},
|
||||
&StockIndicator{},
|
||||
&StockFinaIndicator{},
|
||||
&DatasetBasic{},
|
||||
&DatasetDaily{},
|
||||
&DatasetBlocksIndex{},
|
||||
&DatasetBlocksMember{},
|
||||
&DatasetMoney{},
|
||||
&DatasetPledgeStat{},
|
||||
&DatasetIndicator{},
|
||||
&DatasetIndicatorPro{},
|
||||
&DatasetFinaIndicator{},
|
||||
&DatasetSummary{},
|
||||
} {
|
||||
database.AppendMigrate(t)
|
||||
}
|
||||
|
||||
@@ -117,17 +117,26 @@ func (cli *TushareClient) Moneyflow(ts_code, trade_date, start_date, end_date st
|
||||
}
|
||||
|
||||
fields := []map[string]string{
|
||||
{"ts_code": "股票代码"},
|
||||
{"ts_code": "TS代码"},
|
||||
{"trade_date": "交易日期"},
|
||||
{"buy_sm_amount": "小单买入金额 (千元)"},
|
||||
{"sell_sm_amount": "小单卖出金额 (千元)"},
|
||||
{"buy_md_amount": "中单买入金额 (千元)"},
|
||||
{"sell_md_amount": "中单卖出金额 (千元)"},
|
||||
{"buy_lg_amount": "大单买入金额 (千元)"},
|
||||
{"sell_lg_amount": "大单卖出金额 (千元)"},
|
||||
{"buy_elg_amount": "特大单买入金额 (千元)"},
|
||||
{"sell_elg_amount": "特大单卖出金额 (千元)"},
|
||||
{"net_mf_amount": "净流入金额 (千元)"},
|
||||
{"buy_sm_vol": "小单买入量 (手)"},
|
||||
{"buy_sm_amount": "小单买入金额 (万元)"},
|
||||
{"sell_sm_vol": "小单卖出量 (手)"},
|
||||
{"sell_sm_amount": "小单卖出金额 (万元)"},
|
||||
{"buy_md_vol": "中单买入量 (手)"},
|
||||
{"buy_md_amount": "中单买入金额 (万元)"},
|
||||
{"sell_md_vol": "中单卖出量 (手)"},
|
||||
{"sell_md_amount": "中单卖出金额 (万元)"},
|
||||
{"buy_lg_vol": "大单买入量 (手)"},
|
||||
{"buy_lg_amount": "大单买入金额 (万元)"},
|
||||
{"sell_lg_vol": "大单卖出量 (手)"},
|
||||
{"sell_lg_amount": "大单卖出金额 (万元)"},
|
||||
{"buy_elg_vol": "特大单买入量 (手)"},
|
||||
{"buy_elg_amount": "特大单买入金额 (万元)"},
|
||||
{"sell_elg_vol": "特大单卖出量 (手)"},
|
||||
{"sell_elg_amount": "特大单卖出金额 (万元)"},
|
||||
{"net_mf_vol": "净流入量 (手)"},
|
||||
{"net_mf_amount": "净流入额 (万元)"},
|
||||
}
|
||||
|
||||
return cli.Do(req, fields)
|
||||
|
||||
@@ -1,12 +1,26 @@
|
||||
package tushare
|
||||
|
||||
func (cli *TushareClient) StkFactorPro(ts_code string, tradeDate string) (*TushareRespData, error) {
|
||||
/*
|
||||
StkFactorPro 获取股票技术面因子(专业版)
|
||||
|
||||
ts_code: 股票代码,支持多个,逗号分隔
|
||||
trade_date: 交易日期,格式:YYYYMMDD
|
||||
start_date: 开始日期,格式:YYYYMMDD
|
||||
end_date: 结束日期,格式:YYYYMMDD
|
||||
*/
|
||||
func (cli *TushareClient) StkFactorPro(ts_code, trade_date, start_date, end_date string) (*TushareRespData, error) {
|
||||
params := map[string]any{}
|
||||
if ts_code != "" {
|
||||
params["ts_code"] = ts_code
|
||||
}
|
||||
if tradeDate != "" {
|
||||
params["trade_date"] = tradeDate
|
||||
if trade_date != "" {
|
||||
params["trade_date"] = trade_date
|
||||
}
|
||||
if start_date != "" {
|
||||
params["start_date"] = start_date
|
||||
}
|
||||
if end_date != "" {
|
||||
params["end_date"] = end_date
|
||||
}
|
||||
|
||||
req := TushareReq{
|
||||
@@ -14,10 +28,52 @@ func (cli *TushareClient) StkFactorPro(ts_code string, tradeDate string) (*Tusha
|
||||
Params: params,
|
||||
}
|
||||
fields := []map[string]string{
|
||||
{"trade_date": "trade_date"},
|
||||
{"rsi_bfq_24": "rsi_bfq_24"},
|
||||
{"rsi_hfq_24": "rsi_hfq_24"},
|
||||
{"rsi_qfq_24": "rsi_qfq_24"},
|
||||
{"ts_code": "股票代码"},
|
||||
{"trade_date": "交易日期"},
|
||||
{"open": "开盘价"},
|
||||
{"high": "最高价"},
|
||||
{"low": "最低价"},
|
||||
{"close": "收盘价"},
|
||||
{"pre_close": "昨收价"},
|
||||
{"change": "涨跌额"},
|
||||
{"pct_chg": "涨跌幅%"},
|
||||
{"vol": "成交量(手)"},
|
||||
{"amount": "成交额(千元)"},
|
||||
{"turnover_rate": "换手率(%)"},
|
||||
{"turnover_rate_f": "换手率(自由流通股)"},
|
||||
{"volume_ratio": "量比"},
|
||||
{"pe": "市盈率"},
|
||||
{"pe_ttm": "市盈率TTM"},
|
||||
{"pb": "市净率"},
|
||||
{"ps": "市销率"},
|
||||
{"ps_ttm": "市销率TTM"},
|
||||
{"dv_ratio": "股息率(%)"},
|
||||
{"dv_ttm": "股息率TTM(%)"},
|
||||
{"total_share": "总股本(万股)"},
|
||||
{"float_share": "流通股本(万股)"},
|
||||
{"free_share": "自由流通股本(万股)"},
|
||||
{"total_mv": "总市值(万元)"},
|
||||
{"circ_mv": "流通市值(万元)"},
|
||||
{"adj_factor": "复权因子"},
|
||||
{"ma_qfq_5": "MA5前复权"},
|
||||
{"ma_qfq_10": "MA10前复权"},
|
||||
{"ma_qfq_20": "MA20前复权"},
|
||||
{"ma_qfq_60": "MA60前复权"},
|
||||
{"ema_qfq_5": "EMA5前复权"},
|
||||
{"ema_qfq_10": "EMA10前复权"},
|
||||
{"ema_qfq_20": "EMA20前复权"},
|
||||
{"macd_qfq": "MACD前复权"},
|
||||
{"macd_dif_qfq": "MACD DIF前复权"},
|
||||
{"macd_dea_qfq": "MACD DEA前复权"},
|
||||
{"rsi_qfq_6": "RSI6前复权"},
|
||||
{"rsi_qfq_12": "RSI12前复权"},
|
||||
{"rsi_qfq_24": "RSI24前复权"},
|
||||
{"kdj_k_qfq": "KDJ-K前复权"},
|
||||
{"kdj_d_qfq": "KDJ-D前复权"},
|
||||
{"kdj_qfq": "KDJ-J前复权"},
|
||||
{"boll_upper_qfq": "BOLL上轨前复权"},
|
||||
{"boll_mid_qfq": "BOLL中轨前复权"},
|
||||
{"boll_lower_qfq": "BOLL下轨前复权"},
|
||||
}
|
||||
|
||||
return cli.Do(req, fields)
|
||||
|
||||
138
tushare/new.go
138
tushare/new.go
@@ -17,6 +17,7 @@ import (
|
||||
type TushareClient struct {
|
||||
Token string `json:"token"` // API 访问令牌
|
||||
BaseUrl string `json:"base_url"` // API 基础 URL
|
||||
client *http.Client
|
||||
}
|
||||
|
||||
// TushareReq Tushare API 请求结构
|
||||
@@ -53,9 +54,40 @@ func NewClient(token string) *TushareClient {
|
||||
return &TushareClient{
|
||||
Token: token,
|
||||
BaseUrl: "http://api.tushare.pro",
|
||||
client: &http.Client{Timeout: 30 * time.Second},
|
||||
}
|
||||
}
|
||||
|
||||
func (c *TushareClient) httpDo() *http.Client {
|
||||
if c.client != nil {
|
||||
return c.client
|
||||
}
|
||||
return &http.Client{Timeout: 30 * time.Second}
|
||||
}
|
||||
|
||||
// postTushare 发送 JSON 请求并解析为 TushareResp(不含业务码以外的处理)。
|
||||
func (c *TushareClient) postTushare(reqBytes []byte) (*TushareResp, error) {
|
||||
resp, err := c.httpDo().Post(c.BaseUrl, "application/json", bytes.NewReader(reqBytes))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("#100 发送请求失败:%w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("#101 读取响应失败:%w", err)
|
||||
}
|
||||
|
||||
var tushareResp TushareResp
|
||||
if err := json.Unmarshal(body, &tushareResp); err != nil {
|
||||
return nil, fmt.Errorf("#101 响应解析失败:%v - %s", err, string(body))
|
||||
}
|
||||
if tushareResp.Code != 0 {
|
||||
return nil, fmt.Errorf("#102 API 返回错误:%s", tushareResp.Msg)
|
||||
}
|
||||
return &tushareResp, nil
|
||||
}
|
||||
|
||||
// Do 执行 Tushare API 请求
|
||||
// req: 请求参数,包含 API 名称、参数等
|
||||
// fieldsVals: 字段配置列表,每个元素是一个 map,key 为字段名,value 为字段中文描述
|
||||
@@ -69,8 +101,12 @@ func (c *TushareClient) Do(req TushareReq, fieldsVals []map[string]string) (*Tus
|
||||
}
|
||||
|
||||
// 提取字段名和对应的中文表头
|
||||
var fields []string
|
||||
var headers []string
|
||||
n := 0
|
||||
for _, setting := range fieldsVals {
|
||||
n += len(setting)
|
||||
}
|
||||
fields := make([]string, 0, n)
|
||||
headers := make([]string, 0, n)
|
||||
for _, setting := range fieldsVals {
|
||||
for key, value := range setting {
|
||||
fields = append(fields, key)
|
||||
@@ -79,57 +115,43 @@ func (c *TushareClient) Do(req TushareReq, fieldsVals []map[string]string) (*Tus
|
||||
}
|
||||
payload.Fields = fields
|
||||
|
||||
// 序列化请求体为 JSON
|
||||
reqBytes, err := json.Marshal(payload)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("#100 请求序列化失败:%w", err)
|
||||
}
|
||||
|
||||
// 创建 HTTP 请求
|
||||
client := &http.Client{
|
||||
Timeout: 30 * time.Second, // 设置 30 秒超时
|
||||
}
|
||||
resp, err := client.Post(c.BaseUrl, "application/json", bytes.NewReader(reqBytes))
|
||||
tushareResp, err := c.postTushare(reqBytes)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("#100 发送请求失败:%w", err)
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// 读取响应体
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("#101 读取响应失败:%w", err)
|
||||
}
|
||||
|
||||
// 解析响应 JSON
|
||||
var tushareResp TushareResp
|
||||
if err := json.Unmarshal(body, &tushareResp); err != nil {
|
||||
return nil, fmt.Errorf("#101 响应解析失败:%v - %s", err, string(body))
|
||||
}
|
||||
|
||||
// 检查响应码
|
||||
if tushareResp.Code != 0 {
|
||||
return nil, fmt.Errorf("#102 API 返回错误:%s", tushareResp.Msg)
|
||||
}
|
||||
|
||||
// 设置表头信息
|
||||
if tushareResp.Data != nil {
|
||||
tushareResp.Data.Headers = headers
|
||||
}
|
||||
return tushareResp.Data, nil
|
||||
}
|
||||
|
||||
// Do 执行 Tushare API 请求
|
||||
// req: 请求参数,包含 API 名称、参数等
|
||||
// fieldsVals: 字段配置列表,每个元素是一个 map,key 为字段名,value 为字段中文描述
|
||||
// Exec 执行 Tushare API 请求
|
||||
// ApiName: API 名称
|
||||
// fields: 字段列表,用逗号分隔
|
||||
// Params: 请求参数
|
||||
// 返回:响应数据和错误信息
|
||||
func (c *TushareClient) Exec(ApiName string, fields string, Params map[string]any) (*TushareRespData, error) {
|
||||
fieldsList := strings.Split(fields, ",")
|
||||
fields = strings.TrimSpace(fields)
|
||||
if fields == "" {
|
||||
return nil, fmt.Errorf("#100 字段列表不能为空")
|
||||
}
|
||||
raw := strings.Split(fields, ",")
|
||||
fieldsList := make([]string, 0, len(raw))
|
||||
for _, f := range raw {
|
||||
f = strings.TrimSpace(f)
|
||||
if f != "" {
|
||||
fieldsList = append(fieldsList, f)
|
||||
}
|
||||
}
|
||||
if len(fieldsList) == 0 {
|
||||
return nil, fmt.Errorf("#100 字段列表不能为空")
|
||||
}
|
||||
|
||||
// 构建请求体
|
||||
payload := TushareReq{
|
||||
APIName: ApiName,
|
||||
Token: c.Token,
|
||||
@@ -137,51 +159,25 @@ func (c *TushareClient) Exec(ApiName string, fields string, Params map[string]an
|
||||
Fields: fieldsList,
|
||||
}
|
||||
|
||||
// 序列化请求体为 JSON
|
||||
reqBytes, err := json.Marshal(payload)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("#100 请求序列化失败:%w", err)
|
||||
}
|
||||
|
||||
// 创建 HTTP 请求
|
||||
client := &http.Client{
|
||||
Timeout: 30 * time.Second, // 设置 30 秒超时
|
||||
}
|
||||
resp, err := client.Post(c.BaseUrl, "application/json", bytes.NewReader(reqBytes))
|
||||
tushareResp, err := c.postTushare(reqBytes)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("#100 发送请求失败:%w", err)
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// 读取响应体
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("#101 读取响应失败:%w", err)
|
||||
}
|
||||
|
||||
// 解析响应 JSON
|
||||
var tushareResp TushareResp
|
||||
if err := json.Unmarshal(body, &tushareResp); err != nil {
|
||||
return nil, fmt.Errorf("#101 响应解析失败:%v - %s", err, string(body))
|
||||
}
|
||||
|
||||
// 检查响应码
|
||||
if tushareResp.Code != 0 {
|
||||
return nil, fmt.Errorf("#102 API 返回错误:%s", tushareResp.Msg)
|
||||
}
|
||||
|
||||
// 设置表头信息
|
||||
return tushareResp.Data, nil
|
||||
}
|
||||
|
||||
// Map 将响应数据转换为 map 切片
|
||||
// 每个 map 代表一行数据,key 为字段名,value 为对应的值
|
||||
func (c *TushareRespData) Map() []map[string]any {
|
||||
result := make([]map[string]any, 0)
|
||||
if c == nil || len(c.Items) == 0 || len(c.Fields) == 0 {
|
||||
return result
|
||||
return make([]map[string]any, 0)
|
||||
}
|
||||
|
||||
result := make([]map[string]any, 0, len(c.Items))
|
||||
for _, item := range c.Items {
|
||||
rowMap := make(map[string]any, len(c.Fields))
|
||||
for idx, fn := range c.Fields {
|
||||
@@ -208,11 +204,17 @@ func (c *TushareRespData) Output(title string) table.Writer {
|
||||
tw := table.NewWriter()
|
||||
tw.SetStyle(table.StyleLight)
|
||||
tw.SetTitle(title)
|
||||
if c == nil {
|
||||
return tw
|
||||
}
|
||||
|
||||
// 构建表头行
|
||||
headerRow := make(table.Row, 0, len(c.Headers))
|
||||
for idx, header := range c.Headers {
|
||||
headerRow = append(headerRow, header+"("+c.Fields[idx]+")")
|
||||
n := len(c.Fields)
|
||||
if nh := len(c.Headers); nh < n {
|
||||
n = nh
|
||||
}
|
||||
headerRow := make(table.Row, 0, n)
|
||||
for idx := 0; idx < n; idx++ {
|
||||
headerRow = append(headerRow, c.Headers[idx]+"("+c.Fields[idx]+")")
|
||||
}
|
||||
tw.AppendHeader(headerRow)
|
||||
|
||||
|
||||
@@ -2,6 +2,8 @@ package tushare
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"git.apinb.com/quant/qsdk/conv"
|
||||
)
|
||||
|
||||
/*
|
||||
@@ -49,7 +51,10 @@ func (cli *TushareClient) ReturnLastTradeDay() string {
|
||||
return ""
|
||||
}
|
||||
cal := result.Map()
|
||||
return cal[0]["cal_date"].(string)
|
||||
if len(cal) == 0 {
|
||||
return ""
|
||||
}
|
||||
return conv.AnyToString(cal[0]["cal_date"])
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
Reference in New Issue
Block a user