From aa8a1190f04230a93d89bb39be605af92cd7fefe Mon Sep 17 00:00:00 2001 From: yanweidong Date: Fri, 1 May 2026 11:03:19 +0800 Subject: [PATCH] ok --- conv/conv.go | 76 ++++++++++ day/ymd.go | 13 ++ go.mod | 31 ++++ go.sum | 60 ++++++++ markdown/builder.go | 83 +++++++++++ schema/blocks_index.go | 28 ++++ schema/blocks_member.go | 28 ++++ schema/money_total.go | 31 ++++ schema/pledge_stat.go | 30 ++++ schema/register.go | 19 +++ schema/scopes.go | 107 ++++++++++++++ schema/stock_basic.go | 44 ++++++ schema/stock_daily.go | 58 ++++++++ schema/stock_fina_indicator.go | 226 +++++++++++++++++++++++++++++ schema/stock_indicator.go | 49 +++++++ tushare/board.go | 141 ++++++++++++++++++ tushare/fina.go | 254 +++++++++++++++++++++++++++++++++ tushare/finance.go | 206 ++++++++++++++++++++++++++ tushare/index.go | 213 +++++++++++++++++++++++++++ tushare/indicator.go | 24 ++++ tushare/margin.go | 209 +++++++++++++++++++++++++++ tushare/new.go | 168 ++++++++++++++++++++++ tushare/stock.go | 241 +++++++++++++++++++++++++++++++ tushare/ths.go | 213 +++++++++++++++++++++++++++ 24 files changed, 2552 insertions(+) create mode 100644 conv/conv.go create mode 100644 day/ymd.go create mode 100644 go.mod create mode 100644 go.sum create mode 100644 markdown/builder.go create mode 100644 schema/blocks_index.go create mode 100644 schema/blocks_member.go create mode 100644 schema/money_total.go create mode 100644 schema/pledge_stat.go create mode 100644 schema/register.go create mode 100644 schema/scopes.go create mode 100644 schema/stock_basic.go create mode 100644 schema/stock_daily.go create mode 100644 schema/stock_fina_indicator.go create mode 100644 schema/stock_indicator.go create mode 100644 tushare/board.go create mode 100644 tushare/fina.go create mode 100644 tushare/finance.go create mode 100644 tushare/index.go create mode 100644 tushare/indicator.go create mode 100644 tushare/margin.go create mode 100644 tushare/new.go create mode 100644 tushare/stock.go create mode 100644 tushare/ths.go diff --git a/conv/conv.go b/conv/conv.go new file mode 100644 index 0000000..30d5cc9 --- /dev/null +++ b/conv/conv.go @@ -0,0 +1,76 @@ +package conv + +import ( + "reflect" + "strconv" +) + +// AnyToString 任意类型转字符串 +// in: 输入值 +// 返回: 转换后的字符串 +func AnyToString(in any) (s string) { + if in == nil { + return "" + } + + return in.(string) +} + +// AnyToInt 将动态类型转为 int(两仓库 internal 中逻辑一致,此处合并分支)。 +func AnyToInt(v any) int { + switch val := v.(type) { + case int: + return val + case int8: + return int(val) + case int16: + return int(val) + case int32: + return int(val) + case int64: + return int(val) + case uint, uint8, uint16, uint32, uint64: + return int(reflect.ValueOf(val).Uint()) + case float32: + return int(val) + case float64: + return int(val) + case string: + i, err := strconv.Atoi(val) + if err != nil { + return 0 + } + return i + default: + return 0 + } +} + +// AnyToFloat64 将动态类型转为 float64(合并 stock 对 int/uint 等分支与 gostock 的 string 分支)。 +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 uint: + return float64(val) + default: + return 0 + } +} + +// String2Float64 字符串转Float64 +// floatStr: 小数点数字的字符串 +// 返回: 转换后的64位浮点数,转换失败返回0 +func String2Float64(floatStr string) (floatNum float64) { + floatNum, err := strconv.ParseFloat(floatStr, 64) + if err != nil { + return 0 + } + return +} diff --git a/day/ymd.go b/day/ymd.go new file mode 100644 index 0000000..c075786 --- /dev/null +++ b/day/ymd.go @@ -0,0 +1,13 @@ +package day + +import ( + "time" + + "git.apinb.com/bsm-sdk/core/utils" +) + +// TodayYmd 返回本地日期的 YYYYMMDD 整数(gostock internal/models/query.GetYmd)。 +func TodayYmd() int { + ymd := time.Now().Format("20060102") + return utils.String2Int(ymd) +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..9e1726e --- /dev/null +++ b/go.mod @@ -0,0 +1,31 @@ +module git.apinb.com/quant/qsdk + +go 1.26.1 + +require ( + git.apinb.com/bsm-sdk/core v0.1.9 + github.com/jedib0t/go-pretty/v6 v6.7.9 + gorm.io/gorm v1.31.1 +) + +require ( + filippo.io/edwards25519 v1.1.0 // indirect + github.com/go-sql-driver/mysql v1.8.1 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/jackc/pgpassfile v1.0.0 // indirect + github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect + github.com/jackc/pgx/v5 v5.6.0 // indirect + github.com/jackc/puddle/v2 v2.2.2 // indirect + github.com/jinzhu/inflection v1.0.0 // indirect + github.com/jinzhu/now v1.1.5 // indirect + github.com/mattn/go-runewidth v0.0.16 // indirect + github.com/oklog/ulid/v2 v2.1.1 // indirect + github.com/rivo/uniseg v0.4.7 // indirect + github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e // indirect + golang.org/x/crypto v0.31.0 // indirect + golang.org/x/sync v0.11.0 // indirect + golang.org/x/sys v0.30.0 // indirect + golang.org/x/text v0.22.0 // indirect + gorm.io/driver/mysql v1.6.0 // indirect + gorm.io/driver/postgres v1.6.0 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..0c6daa8 --- /dev/null +++ b/go.sum @@ -0,0 +1,60 @@ +filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= +filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= +git.apinb.com/bsm-sdk/core v0.1.9 h1:Pp0zpSeX7OnFb3JQdXdJHR74EvU02HnMauk4wI0/gVg= +git.apinb.com/bsm-sdk/core v0.1.9/go.mod h1:R5Rm/Ep4D3mJ97dL6sLRi3jtfQxi2ftekp2E3frts/8= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= +github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= +github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= +github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= +github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +github.com/jackc/pgx/v5 v5.6.0 h1:SWJzexBzPL5jb0GEsrPMLIsi/3jOo7RHlzTjcAeDrPY= +github.com/jackc/pgx/v5 v5.6.0/go.mod h1:DNZ/vlrUnhWCoFGxHAG8U2ljioxukquj7utPDgtQdTw= +github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= +github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= +github.com/jedib0t/go-pretty/v6 v6.7.9 h1:frarzQWmkZd97syT81+TH8INKPpzoxQnk+Mk5EIHSrM= +github.com/jedib0t/go-pretty/v6 v6.7.9/go.mod h1:YwC5CE4fJ1HFUDeivSV1r//AmANFHyqczZk+U6BDALU= +github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= +github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= +github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= +github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= +github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/oklog/ulid/v2 v2.1.1 h1:suPZ4ARWLOJLegGFiZZ1dFAkqzhMjL3J1TzI+5wHz8s= +github.com/oklog/ulid/v2 v2.1.1/go.mod h1:rcEKHmBBKfef9DhnvX7y1HZBYxjXb0cP5ExxNsTT1QQ= +github.com/pborman/getopt v0.0.0-20170112200414-7148bc3a4c30/go.mod h1:85jBQOZwpVEaDAr341tbn15RS4fCAsIst0qp7i8ex1o= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= +github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0= +github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= +golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= +golang.org/x/sync v0.11.0 h1:GGz8+XQP4FvTTrjZPzNKTMFtSXH80RAzG+5ghFPgK9w= +golang.org/x/sync v0.11.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= +golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/text v0.22.0 h1:bofq7m3/HAFvbF51jz3Q9wLg3jkvSPuiZu/pD1XwgtM= +golang.org/x/text v0.22.0/go.mod h1:YRoo4H8PVmsu+E3Ou7cqLVH8oXWIHVoX0jqUWALQhfY= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gorm.io/driver/mysql v1.6.0 h1:eNbLmNTpPpTOVZi8MMxCi2aaIm0ZpInbORNXDwyLGvg= +gorm.io/driver/mysql v1.6.0/go.mod h1:D/oCC2GWK3M/dqoLxnOlaNKmXz8WNTfcS9y5ovaSqKo= +gorm.io/driver/postgres v1.6.0 h1:2dxzU8xJ+ivvqTRph34QX+WrRaJlmfyPqXmoGVjMBa4= +gorm.io/driver/postgres v1.6.0/go.mod h1:vUw0mrGgrTK+uPHEhAdV4sfFELrByKVGnaVRkXDhtWo= +gorm.io/gorm v1.31.1 h1:7CA8FTFz/gRfgqgpeKIBcervUn3xSyPUmr6B2WXJ7kg= +gorm.io/gorm v1.31.1/go.mod h1:XyQVbO2k6YkOis7C2437jSit3SsDK72s7n7rsSHd+Gs= diff --git a/markdown/builder.go b/markdown/builder.go new file mode 100644 index 0000000..351193c --- /dev/null +++ b/markdown/builder.go @@ -0,0 +1,83 @@ +package markdown + +import ( + "bytes" + "io" + "os" + "strings" +) + +// Builder 用于拼接 Markdown 文本(原 stock internal/logic/a、gostock internal/logic/libs 中重复实现)。 +type Builder struct { + buf bytes.Buffer +} + +// MarkdownBuilder 兼容旧项目中的类型名。 +type MarkdownBuilder = Builder + +func NewBuilder() *Builder { + return &Builder{} +} + +// NewMarkdownBuilder 兼容旧项目中的构造函数名。 +func NewMarkdownBuilder() *Builder { + return NewBuilder() +} + +func (m *Builder) Title(title string) { + m.buf.WriteString("# " + title + "\n\n") +} + +func (m *Builder) Catalog(text string) { + m.buf.WriteString("## " + text + "\n\n") +} + +func (m *Builder) Text(text string) { + m.buf.WriteString(text + "\n\n") +} + +func (m *Builder) KvText(key, val string) { + m.buf.WriteString(key + " :" + val + " \n") +} + +func (m *Builder) BR() { + m.buf.WriteString("\n") +} + +func (m *Builder) Code(lang, code string) { + m.buf.WriteString("```" + lang + "\n") + m.buf.WriteString(code + "\n") + m.buf.WriteString("```\n\n") +} + +// Table 生成 Markdown 表格。 +func (m *Builder) Table(catalog string, headers []string, rows [][]string) { + if catalog != "" { + m.buf.WriteString("## " + catalog + "\n") + } + m.buf.WriteString("| " + strings.Join(headers, " | ") + " |\n") + + m.buf.WriteString("| ") + for _, header := range headers { + m.buf.WriteString(strings.Repeat("-", len(header)) + "|") + } + m.buf.WriteString("\n") + for _, row := range rows { + m.buf.WriteString("| " + strings.Join(row, " | ") + " |\n") + } +} + +func (m *Builder) Build() string { + return m.buf.String() +} + +func (m *Builder) SaveToFile(filePath string) error { + rf, err := os.OpenFile(filePath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, os.ModePerm) + if err != nil { + return err + } + defer rf.Close() + + _, err = io.Copy(rf, &m.buf) + return err +} diff --git a/schema/blocks_index.go b/schema/blocks_index.go new file mode 100644 index 0000000..85e3713 --- /dev/null +++ b/schema/blocks_index.go @@ -0,0 +1,28 @@ +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 +} diff --git a/schema/blocks_member.go b/schema/blocks_member.go new file mode 100644 index 0000000..23be117 --- /dev/null +++ b/schema/blocks_member.go @@ -0,0 +1,28 @@ +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 +} diff --git a/schema/money_total.go b/schema/money_total.go new file mode 100644 index 0000000..8f2578c --- /dev/null +++ b/schema/money_total.go @@ -0,0 +1,31 @@ +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 +} diff --git a/schema/pledge_stat.go b/schema/pledge_stat.go new file mode 100644 index 0000000..b6f63a5 --- /dev/null +++ b/schema/pledge_stat.go @@ -0,0 +1,30 @@ +package schema + +// PledgeStat 股权质押统计(表 pledge_stat;dataset/stock 中原为 PledgeStatModel,业务层 Save 请留在各应用内)。 +type PledgeStat 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"` + PledgeCount float64 `json:"pledge_count"` + UnrestPledge float64 `json:"unrest_pledge"` + RestPledge float64 `json:"rest_pledge"` + TotalShare float64 `json:"total_share"` + PledgeRatio float64 `json:"pledge_ratio"` +} + +func (PledgeStat) TableName() string { + return "pledge_stat" +} + +// Key 与唯一约束 uq_pledge_stat_ts_code 一致:ts_code。 +func (p *PledgeStat) Key() string { + return p.TsCode +} + +// HasPledgeFacts 是否具备任一质押统计字段(比例、次数或股本)。 +func (p *PledgeStat) HasPledgeFacts() bool { + return p.PledgeRatio > 0 || p.PledgeCount > 0 || p.TotalShare > 0 +} + +// PledgeStatModel 兼容 dataset/stock 中的类型名。 +type PledgeStatModel = PledgeStat diff --git a/schema/register.go b/schema/register.go new file mode 100644 index 0000000..99c32cd --- /dev/null +++ b/schema/register.go @@ -0,0 +1,19 @@ +package schema + +import "git.apinb.com/bsm-sdk/core/database" + +// RegisterAutoMigrate 将本包内与 stock/gostock 共用的表注册到 bsm-sdk 的迁移列表(可选;也可在各应用 init 中自行 AppendMigrate)。 +func RegisterAutoMigrate() { + for _, t := range []any{ + &StockBasic{}, + &StockDaily{}, + &BlocksIndex{}, + &BlocksMember{}, + &MoneyTotal{}, + &PledgeStat{}, + &StockIndicator{}, + &StockFinaIndicator{}, + } { + database.AppendMigrate(t) + } +} diff --git a/schema/scopes.go b/schema/scopes.go new file mode 100644 index 0000000..ef3385c --- /dev/null +++ b/schema/scopes.go @@ -0,0 +1,107 @@ +package schema + +import "gorm.io/gorm" + +// ScopeTsCode 按 ts_code 精确过滤;空字符串则不加条件。 +func ScopeTsCode(tsCode string) func(*gorm.DB) *gorm.DB { + return func(db *gorm.DB) *gorm.DB { + if tsCode == "" { + return db + } + return db.Where("ts_code = ?", tsCode) + } +} + +// ScopeTsCodes 按 ts_code IN (...);nil 或空切片不加条件。 +func ScopeTsCodes(tsCodes []string) func(*gorm.DB) *gorm.DB { + return func(db *gorm.DB) *gorm.DB { + if len(tsCodes) == 0 { + return db + } + return db.Where("ts_code IN ?", tsCodes) + } +} + +// ScopeTradeDateEQ 按 trade_date 等于;0 表示不加条件。 +func ScopeTradeDateEQ(tradeDate int) func(*gorm.DB) *gorm.DB { + return func(db *gorm.DB) *gorm.DB { + if tradeDate == 0 { + return db + } + return db.Where("trade_date = ?", tradeDate) + } +} + +// ScopeTradeDateBetween trade_date 区间 [start,end];仅传一侧时做单边约束;均为 0 不加条件。 +func ScopeTradeDateBetween(start, end int) func(*gorm.DB) *gorm.DB { + return func(db *gorm.DB) *gorm.DB { + switch { + case start > 0 && end > 0: + return db.Where("trade_date BETWEEN ? AND ?", start, end) + case start > 0: + return db.Where("trade_date >= ?", start) + case end > 0: + return db.Where("trade_date <= ?", end) + default: + return db + } + } +} + +// ScopeStockDailyTsDate 日线:ts_code + 交易日(任一为空则该项不限制)。 +func ScopeStockDailyTsDate(tsCode string, tradeDate int) func(*gorm.DB) *gorm.DB { + return func(db *gorm.DB) *gorm.DB { + return db.Scopes(ScopeTsCode(tsCode), ScopeTradeDateEQ(tradeDate)) + } +} + +// ScopeStockIndicatorTsDate 指标表:ts_code + trade_date。 +func ScopeStockIndicatorTsDate(tsCode string, tradeDate int) func(*gorm.DB) *gorm.DB { + return func(db *gorm.DB) *gorm.DB { + return db.Scopes(ScopeTsCode(tsCode), ScopeTradeDateEQ(tradeDate)) + } +} + +// ScopeFinaTsPeriod 财务指标:ts_code + period(与 uniqueIndex un_fi_code_date 一致);period 为 0 时不限制 period。 +func ScopeFinaTsPeriod(tsCode string, period int) func(*gorm.DB) *gorm.DB { + return func(db *gorm.DB) *gorm.DB { + db = db.Scopes(ScopeTsCode(tsCode)) + if period != 0 { + db = db.Where("period = ?", period) + } + return db + } +} + +// ScopeBlocksIndexCode 板块 code。 +func ScopeBlocksIndexCode(code string) func(*gorm.DB) *gorm.DB { + return func(db *gorm.DB) *gorm.DB { + if code == "" { + return db + } + return db.Where("code = ?", code) + } +} + +// ScopeBlocksMemberPair 板块 ti_code + 成分 stock_code。 +func ScopeBlocksMemberPair(tiCode, stockCode string) func(*gorm.DB) *gorm.DB { + return func(db *gorm.DB) *gorm.DB { + if tiCode != "" { + db = db.Where("ti_code = ?", tiCode) + } + if stockCode != "" { + db = db.Where("stock_code = ?", stockCode) + } + return db + } +} + +// ScopeMoneyTotalCode 资金流 code。 +func ScopeMoneyTotalCode(code string) func(*gorm.DB) *gorm.DB { + return func(db *gorm.DB) *gorm.DB { + if code == "" { + return db + } + return db.Where("code = ?", code) + } +} diff --git a/schema/stock_basic.go b/schema/stock_basic.go new file mode 100644 index 0000000..8e789d1 --- /dev/null +++ b/schema/stock_basic.go @@ -0,0 +1,44 @@ +package schema + +import "gorm.io/gorm" + +// StockBasic 股票基本信息表(合并 dataset/stock 与 gostock 字段;gostock 独有 Level、Desc)。 +type StockBasic struct { + gorm.Model + TsCode string `gorm:"type:varchar(50);not null;index;comment:TS代码"` + Symbol string `gorm:"type:varchar(50);not null;comment:股票代码"` + Name string `gorm:"type:varchar(50);not null;comment:股票名称"` + Area string `gorm:"type:varchar(50);not null;default:'';comment:地域"` + Industry string `gorm:"type:varchar(50);not null;default:'';comment:所属行业"` + FullName string `gorm:"type:varchar(500);comment:股票全称"` + EnName string `gorm:"type:varchar(200);comment:英文全称"` + CnSpell string `gorm:"type:varchar(50);not null;default:'';comment:拼音缩写"` + Market string `gorm:"type:varchar(50);not null;comment:市场类型(主板/创业板/科创板/CDR)"` + Exchange string `gorm:"type:varchar(50);comment:交易所代码"` + ListDate string `gorm:"type:varchar(50);not null;comment:上市日期"` + IsHS string `gorm:"type:varchar(2);default:'N';comment:是否沪深港通标的,N否 H沪股通 S深股通"` + ActName string `gorm:"type:varchar(500);not null;default:'';comment:实控人名称"` + ActEntType string `gorm:"type:varchar(50);not null;default:'';comment:实控人企业性质"` +} + +func (StockBasic) TableName() string { + return "stock_basic" +} + +// Key 业务主键:TS 代码。 +func (s *StockBasic) Key() string { + return s.TsCode +} + +// DisplaySymbol 展示用代码:有 symbol 用 symbol,否则用 ts_code。 +func (s *StockBasic) DisplaySymbol() string { + if s.Symbol != "" { + return s.Symbol + } + return s.TsCode +} + +// IsNorthbound 是否沪深港通标的(H 沪股通 / S 深股通)。 +func (s *StockBasic) IsNorthbound() bool { + return s.IsHS == "H" || s.IsHS == "S" +} diff --git a/schema/stock_daily.go b/schema/stock_daily.go new file mode 100644 index 0000000..eaa240a --- /dev/null +++ b/schema/stock_daily.go @@ -0,0 +1,58 @@ +package schema + +import "strconv" + +// StockDaily 股票日线数据(两仓库结构一致)。 +type StockDaily 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"` + Open float64 `gorm:"type:decimal(10,4);comment:开盘价" json:"open"` + High float64 `gorm:"type:decimal(10,4);comment:最高价" json:"high"` + Low float64 `gorm:"type:decimal(10,4);comment:最低价" json:"low"` + Close float64 `gorm:"type:decimal(10,4);comment:收盘价" json:"close"` + PreClose float64 `gorm:"type:decimal(10,4);comment:昨收价(除权价)" json:"pre_close"` + Change float64 `gorm:"type:decimal(10,4);comment:涨跌额" json:"change"` + PctChg float64 `gorm:"type:decimal(10,6);comment:涨跌幅(%)" json:"pct_chg"` + DayChg float64 `gorm:"type:decimal(6,2);comment:日均振幅(%)" json:"day_chg"` + Vol float64 `gorm:"type:decimal(15,2);comment:成交量(手)" json:"vol"` + Amount float64 `gorm:"type:decimal(20,2);comment:成交额(千元)" json:"amount"` +} + +func (StockDaily) TableName() string { + return "stock_daily" +} + +// Key 业务主键:ts_code + 交易日。 +func (d *StockDaily) Key() string { + if d.TsCode == "" && d.TradeDate == 0 { + return "" + } + return d.TsCode + "#" + strconv.Itoa(d.TradeDate) +} + +// IsRising 是否收涨(昨收有效且收盘高于昨收)。 +func (d *StockDaily) IsRising() bool { + return d.PreClose > 0 && d.Close > d.PreClose +} + +// IsFalling 是否收跌。 +func (d *StockDaily) IsFalling() bool { + return d.PreClose > 0 && d.Close < d.PreClose +} + +// PctChangeFromPre 由昨收计算的涨跌幅(%);昨收无效时返回 0。 +func (d *StockDaily) PctChangeFromPre() float64 { + if d.PreClose <= 0 { + return 0 + } + return (d.Close - d.PreClose) / d.PreClose * 100 +} + +// AmplitudePct 振幅(相对昨收,%):(最高-最低)/昨收*100。 +func (d *StockDaily) AmplitudePct() float64 { + if d.PreClose <= 0 { + return 0 + } + return (d.High - d.Low) / d.PreClose * 100 +} diff --git a/schema/stock_fina_indicator.go b/schema/stock_fina_indicator.go new file mode 100644 index 0000000..cfb57ea --- /dev/null +++ b/schema/stock_fina_indicator.go @@ -0,0 +1,226 @@ +package schema + +import ( + "strconv" + + "gorm.io/gorm" +) + +// StockFinaIndicator 财务指标模型 +type StockFinaIndicator 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:报告期数"` + AnnDate string `gorm:"index:idx_ann_date;comment:公告日期"` + EndDate string `gorm:"index:idx_end_date;comment:报告期"` + + // 每股指标 + Eps float64 `gorm:"type:decimal(20,4);comment:基本每股收益"` + DtEps float64 `gorm:"type:decimal(20,4);comment:稀释每股收益"` + TotalRevenuePs float64 `gorm:"type:decimal(20,4);comment:每股营业总收入"` + RevenuePs float64 `gorm:"type:decimal(20,4);comment:每股营业收入"` + CapitalResePs float64 `gorm:"type:decimal(20,4);comment:每股资本公积"` + SurplusResePs float64 `gorm:"type:decimal(20,4);comment:每股盈余公积"` + UndistProfitPs float64 `gorm:"type:decimal(20,4);comment:每股未分配利润"` + Diluted2Eps float64 `gorm:"type:decimal(20,4);comment:期末摊薄每股收益"` + Bps float64 `gorm:"type:decimal(20,4);comment:每股净资产"` + Ocfps float64 `gorm:"type:decimal(20,4);comment:每股经营活动产生的现金流量净额"` + Retainedps float64 `gorm:"type:decimal(20,4);comment:每股留存收益"` + Cfps float64 `gorm:"type:decimal(20,4);comment:每股现金流量净额"` + EbitPs float64 `gorm:"type:decimal(20,4);comment:每股息税前利润"` + FcffPs float64 `gorm:"type:decimal(20,4);comment:每股企业自由现金流量"` + FcfePs float64 `gorm:"type:decimal(20,4);comment:每股股东自由现金流量"` + + // 利润表相关 + ExtraItem float64 `gorm:"type:decimal(20,4);comment:非经常性损益"` + ProfitDedt float64 `gorm:"type:decimal(20,4);comment:扣除非经常性损益后的净利润"` + GrossMargin float64 `gorm:"type:decimal(20,4);comment:毛利"` + OpIncome float64 `gorm:"type:decimal(20,4);comment:经营活动净收益"` + ValuechangeIncome float64 `gorm:"type:decimal(20,4);comment:价值变动净收益"` + InterstIncome float64 `gorm:"type:decimal(20,4);comment:利息费用"` + Daa float64 `gorm:"type:decimal(20,4);comment:折旧与摊销"` + Ebit float64 `gorm:"type:decimal(20,4);comment:息税前利润"` + Ebitda float64 `gorm:"type:decimal(20,4);comment:息税折旧摊销前利润"` + Fcff float64 `gorm:"type:decimal(20,4);comment:企业自由现金流量"` + Fcfe float64 `gorm:"type:decimal(20,4);comment:股权自由现金流量"` + RdExp float64 `gorm:"type:decimal(20,4);comment:研发费用"` + FixedAssets float64 `gorm:"type:decimal(20,4);comment:固定资产合计"` + ProfitPrefinExp float64 `gorm:"type:decimal(20,4);comment:扣除财务费用前营业利润"` + NonOpProfit float64 `gorm:"type:decimal(20,4);comment:非营业利润"` + + // 资产负债表相关 + CurrentExint float64 `gorm:"type:decimal(20,4);comment:无息流动负债"` + NoncurrentExint float64 `gorm:"type:decimal(20,4);comment:无息非流动负债"` + Interestdebt float64 `gorm:"type:decimal(20,4);comment:带息债务"` + Netdebt float64 `gorm:"type:decimal(20,4);comment:净债务"` + TangibleAsset float64 `gorm:"type:decimal(20,4);comment:有形资产"` + WorkingCapital float64 `gorm:"type:decimal(20,4);comment:营运资金"` + NetworkingCapital float64 `gorm:"type:decimal(20,4);comment:营运流动资本"` + InvestCapital float64 `gorm:"type:decimal(20,4);comment:全部投入资本"` + RetainedEarnings float64 `gorm:"type:decimal(20,4);comment:留存收益"` + + // 偿债能力指标 + CurrentRatio float64 `gorm:"type:decimal(20,4);comment:流动比率"` + QuickRatio float64 `gorm:"type:decimal(20,4);comment:速动比率"` + CashRatio float64 `gorm:"type:decimal(20,4);comment:保守速动比率"` + DebtToAssets float64 `gorm:"type:decimal(20,4);comment:资产负债率"` + AssetsToEqt float64 `gorm:"type:decimal(20,4);comment:权益乘数"` + DpAssetsToEqt float64 `gorm:"type:decimal(20,4);comment:权益乘数(杜邦分析)"` + DebtToEqt float64 `gorm:"type:decimal(20,4);comment:产权比率"` + EqtToDebt float64 `gorm:"type:decimal(20,4);comment:归属于母公司的股东权益/负债合计"` + OcfToShortdebt float64 `gorm:"type:decimal(20,4);comment:经营活动产生的现金流量净额/流动负债"` + EbitToInterest float64 `gorm:"type:decimal(20,4);comment:已获利息倍数"` + + // 运营能力指标 + InvturnDays float64 `gorm:"type:decimal(20,4);comment:存货周转天数"` + ArturnDays float64 `gorm:"type:decimal(20,4);comment:应收账款周转天数"` + InvTurn float64 `gorm:"type:decimal(20,4);comment:存货周转率"` + ArTurn float64 `gorm:"type:decimal(20,4);comment:应收账款周转率"` + CaTurn float64 `gorm:"type:decimal(20,4);comment:流动资产周转率"` + FaTurn float64 `gorm:"type:decimal(20,4);comment:固定资产周转率"` + AssetsTurn float64 `gorm:"type:decimal(20,4);comment:总资产周转率"` + TurnDays float64 `gorm:"type:decimal(20,4);comment:营业周期"` + TotalFaTrun float64 `gorm:"type:decimal(20,4);comment:固定资产合计周转率"` + + // 盈利能力指标 + NetprofitMargin float64 `gorm:"type:decimal(20,4);comment:销售净利率"` + GrossprofitMargin float64 `gorm:"type:decimal(20,4);comment:销售毛利率"` + CogsOfSales float64 `gorm:"type:decimal(20,4);comment:销售成本率"` + ExpenseOfSales float64 `gorm:"type:decimal(20,4);comment:销售期间费用率"` + Roe float64 `gorm:"type:decimal(20,4);comment:净资产收益率"` + RoeWaa float64 `gorm:"type:decimal(20,4);comment:加权平均净资产收益率"` + RoeDt float64 `gorm:"type:decimal(20,4);comment:净资产收益率(扣除非经常损益)"` + Roa float64 `gorm:"type:decimal(20,4);comment:总资产报酬率"` + Npta float64 `gorm:"type:decimal(20,4);comment:总资产净利润"` + Roic float64 `gorm:"type:decimal(20,4);comment:投入资本回报率"` + RoaDp float64 `gorm:"type:decimal(20,4);comment:总资产净利率(杜邦分析)"` + + // 结构指标 + CaToAssets float64 `gorm:"type:decimal(20,4);comment:流动资产/总资产"` + NcaToAssets float64 `gorm:"type:decimal(20,4);comment:非流动资产/总资产"` + TbassetsToTotalassets float64 `gorm:"type:decimal(20,4);comment:有形资产/总资产"` + IntToTalcap float64 `gorm:"type:decimal(20,4);comment:带息债务/全部投入资本"` + EqtToTalcapital float64 `gorm:"type:decimal(20,4);comment:归属于母公司的股东权益/全部投入资本"` + CurrentdebtToDebt float64 `gorm:"type:decimal(20,4);comment:流动负债/负债合计"` + LongdebToDebt float64 `gorm:"type:decimal(20,4);comment:非流动负债/负债合计"` + TangibleassetToDebt float64 `gorm:"type:decimal(20,4);comment:有形资产/负债合计"` + + // 单季度指标 + QOpincome float64 `gorm:"type:decimal(20,4);comment:经营活动单季度净收益"` + QInvestincome float64 `gorm:"type:decimal(20,4);comment:价值变动单季度净收益"` + QDtprofit float64 `gorm:"type:decimal(20,4);comment:扣除非经常损益后的单季度净利润"` + QEps float64 `gorm:"type:decimal(20,4);comment:每股收益(单季度)"` + QNetprofitMargin float64 `gorm:"type:decimal(20,4);comment:销售净利率(单季度)"` + QGscaleprofitMargin float64 `gorm:"type:decimal(20,4);comment:销售毛利率(单季度)"` + QExpToSales float64 `gorm:"type:decimal(20,4);comment:销售期间费用率(单季度)"` + QRoe float64 `gorm:"type:decimal(20,4);comment:净资产收益率(单季度)"` + QDtRoe float64 `gorm:"type:decimal(20,4);comment:净资产单季度收益率(扣除非经常损益)"` + QNpta float64 `gorm:"type:decimal(20,4);comment:总资产净利润(单季度)"` + + // 同比增长率 + BasicEpsYoy float64 `gorm:"type:decimal(20,4);comment:基本每股收益同比增长率(%)"` + DtEpsYoy float64 `gorm:"type:decimal(20,4);comment:稀释每股收益同比增长率(%)"` + CfpsYoy float64 `gorm:"type:decimal(20,4);comment:每股经营活动产生的现金流量净额同比增长率(%)"` + OpYoy float64 `gorm:"type:decimal(20,4);comment:营业利润同比增长率(%)"` + EbtYoy float64 `gorm:"type:decimal(20,4);comment:利润总额同比增长率(%)"` + NetprofitYoy float64 `gorm:"type:decimal(20,4);comment:归属母公司股东的净利润同比增长率(%)"` + DtNetprofitYoy float64 `gorm:"type:decimal(20,4);comment:归属母公司股东的净利润-扣除非经常损益同比增长率(%)"` + OcfYoy float64 `gorm:"type:decimal(20,4);comment:经营活动产生的现金流量净额同比增长率(%)"` + RoeYoy float64 `gorm:"type:decimal(20,4);comment:净资产收益率(摊薄)同比增长率(%)"` + BpsYoy float64 `gorm:"type:decimal(20,4);comment:每股净资产相对年初增长率(%)"` + AssetsYoy float64 `gorm:"type:decimal(20,4);comment:资产总计相对年初增长率(%)"` + EqtYoy float64 `gorm:"type:decimal(20,4);comment:归属母公司的股东权益相对年初增长率(%)"` + TrYoy float64 `gorm:"type:decimal(20,4);comment:营业总收入同比增长率(%)"` + OrYoy float64 `gorm:"type:decimal(20,4);comment:营业收入同比增长率(%)"` + EquityYoy float64 `gorm:"type:decimal(20,4);comment:净资产同比增长率"` + + // 其他比率指标 + ProfitToGr float64 `gorm:"type:decimal(20,4);comment:净利润/营业总收入"` + SaleexpToGr float64 `gorm:"type:decimal(20,4);comment:销售费用/营业总收入"` + AdminexpOfGr float64 `gorm:"type:decimal(20,4);comment:管理费用/营业总收入"` + FinaexpOfGr float64 `gorm:"type:decimal(20,4);comment:财务费用/营业总收入"` + ImpaiTtm float64 `gorm:"type:decimal(20,4);comment:资产减值损失/营业总收入"` + GcOfGr float64 `gorm:"type:decimal(20,4);comment:营业总成本/营业总收入"` + OpOfGr float64 `gorm:"type:decimal(20,4);comment:营业利润/营业总收入"` + EbitOfGr float64 `gorm:"type:decimal(20,4);comment:息税前利润/营业总收入"` + OpincomeOfEbt float64 `gorm:"type:decimal(20,4);comment:经营活动净收益/利润总额"` + InvestincomeOfEbt float64 `gorm:"type:decimal(20,4);comment:价值变动净收益/利润总额"` + NOpProfitOfEbt float64 `gorm:"type:decimal(20,4);comment:营业外收支净额/利润总额"` + TaxToEbt float64 `gorm:"type:decimal(20,4);comment:所得税/利润总额"` + DtprofitToProfit float64 `gorm:"type:decimal(20,4);comment:扣除非经常损益后的净利润/净利润"` + SalescashToOr float64 `gorm:"type:decimal(20,4);comment:销售商品提供劳务收到的现金/营业收入"` + OcfToOr float64 `gorm:"type:decimal(20,4);comment:经营活动产生的现金流量净额/营业收入"` + OcfToOpincome float64 `gorm:"type:decimal(20,4);comment:经营活动产生的现金流量净额/经营活动净收益"` + CapitalizedToDa float64 `gorm:"type:decimal(20,4);comment:资本支出/折旧和摊销"` + OcfToDebt float64 `gorm:"type:decimal(20,4);comment:经营活动产生的现金流量净额/负债合计"` + OcfToInterestdebt float64 `gorm:"type:decimal(20,4);comment:经营活动产生的现金流量净额/带息债务"` + OcfToNetdebt float64 `gorm:"type:decimal(20,4);comment:经营活动产生的现金流量净额/净债务"` + LongdebtToWorkingcapital float64 `gorm:"type:decimal(20,4);comment:长期债务与营运资金比率"` + EbitdaToDebt float64 `gorm:"type:decimal(20,4);comment:息税折旧摊销前利润/负债合计"` + OpToEbt float64 `gorm:"type:decimal(20,4);comment:营业利润/利润总额"` + NopToEbt float64 `gorm:"type:decimal(20,4);comment:非营业利润/利润总额"` + OcfToProfit float64 `gorm:"type:decimal(20,4);comment:经营活动产生的现金流量净额/营业利润"` + CashToLiqdebt float64 `gorm:"type:decimal(20,4);comment:货币资金/流动负债"` + CashToLiqdebtWithinterest float64 `gorm:"type:decimal(20,4);comment:货币资金/带息流动负债"` + OpToLiqdebt float64 `gorm:"type:decimal(20,4);comment:营业利润/流动负债"` + OpToDebt float64 `gorm:"type:decimal(20,4);comment:营业利润/负债合计"` + ProfitToOp float64 `gorm:"type:decimal(20,4);comment:利润总额/营业收入"` + + // 年度化指标 + RoeYearly float64 `gorm:"type:decimal(20,4);comment:年化净资产收益率"` + Roa2Yearly float64 `gorm:"type:decimal(20,4);comment:年化总资产报酬率"` + RoaYearly float64 `gorm:"type:decimal(20,4);comment:年化总资产净利率"` + RoicYearly float64 `gorm:"type:decimal(20,4);comment:年化投入资本回报率"` + RoeAvg float64 `gorm:"type:decimal(20,4);comment:平均净资产收益率(增发条件)"` + + // 单季度增长比率 + QGrYoy float64 `gorm:"type:decimal(20,4);comment:营业总收入同比增长率(%)(单季度)"` + QGrQoq float64 `gorm:"type:decimal(20,4);comment:营业总收入环比增长率(%)(单季度)"` + QSalesYoy float64 `gorm:"type:decimal(20,4);comment:营业收入同比增长率(%)(单季度)"` + QSalesQoq float64 `gorm:"type:decimal(20,4);comment:营业收入环比增长率(%)(单季度)"` + QOpYoy float64 `gorm:"type:decimal(20,4);comment:营业利润同比增长率(%)(单季度)"` + QOpQoq float64 `gorm:"type:decimal(20,4);comment:营业利润环比增长率(%)(单季度)"` + QProfitYoy float64 `gorm:"type:decimal(20,4);comment:净利润同比增长率(%)(单季度)"` + QProfitQoq float64 `gorm:"type:decimal(20,4);comment:净利润环比增长率(%)(单季度)"` + QNetprofitYoy float64 `gorm:"type:decimal(20,4);comment:归属母公司股东的净利润同比增长率(%)(单季度)"` + QNetprofitQoq float64 `gorm:"type:decimal(20,4);comment:归属母公司股东的净利润环比增长率(%)(单季度)"` + + // 单季度比率指标 + QProfitToGr float64 `gorm:"type:decimal(20,4);comment:净利润/营业总收入(单季度)"` + QSaleexpToGr float64 `gorm:"type:decimal(20,4);comment:销售费用/营业总收入 (单季度)"` + QAdminexpToGr float64 `gorm:"type:decimal(20,4);comment:管理费用/营业总收入 (单季度)"` + QFinaexpToGr float64 `gorm:"type:decimal(20,4);comment:财务费用/营业总收入 (单季度)"` + QImpairToGrTtm float64 `gorm:"type:decimal(20,4);comment:资产减值损失/营业总收入(单季度)"` + QGcToGr float64 `gorm:"type:decimal(20,4);comment:营业总成本/营业总收入 (单季度)"` + QOpToGr float64 `gorm:"type:decimal(20,4);comment:营业利润/营业总收入(单季度)"` + QOpincomeToEbt float64 `gorm:"type:decimal(20,4);comment:经营活动净收益/利润总额(单季度)"` + QInvestincomeToEbt float64 `gorm:"type:decimal(20,4);comment:价值变动净收益/利润总额(单季度)"` + QDtprofitToProfit float64 `gorm:"type:decimal(20,4);comment:扣除非经常损益后的净利润/净利润(单季度)"` + QSalescashToOr float64 `gorm:"type:decimal(20,4);comment:销售商品提供劳务收到的现金/营业收入(单季度)"` + QOcfToSales float64 `gorm:"type:decimal(20,4);comment:经营活动产生的现金流量净额/营业收入(单季度)"` + QOcfToOr float64 `gorm:"type:decimal(20,4);comment:经营活动产生的现金流量净额/经营活动净收益(单季度)"` + + // 其他 + UpdateFlag string `gorm:"type:varchar(1);comment:更新标识"` +} + +// TableName 设置表名 +func (StockFinaIndicator) TableName() string { + return "fina_indicator" +} + +// Key 与表 uniqueIndex un_fi_code_date 一致:ts_code + period。 +func (f *StockFinaIndicator) Key() string { + if f.TsCode == "" && f.Period == 0 { + return "" + } + return f.TsCode + "#" + strconv.Itoa(f.Period) +} + +// RowLabel 便于日志/调试:ts_code + 报告期 end_date + 公告 ann_date。 +func (f *StockFinaIndicator) RowLabel() string { + if f.EndDate != "" || f.AnnDate != "" { + return f.TsCode + " end=" + f.EndDate + " ann=" + f.AnnDate + } + return f.Key() +} diff --git a/schema/stock_indicator.go b/schema/stock_indicator.go new file mode 100644 index 0000000..0675871 --- /dev/null +++ b/schema/stock_indicator.go @@ -0,0 +1,49 @@ +package schema + +import "strconv" + +// StockIndicator 每日基本面指标(采用 dataset/stock 的 decimal 定义,与采集端迁移一致)。 +type StockIndicator 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"` + Close float64 `gorm:"type:decimal(20,4);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:流通市值(万元)"` + Roe float64 `gorm:"type:decimal(20,4);comment:ROE(%) 净利润/股本"` +} + +func (StockIndicator) TableName() string { + return "stock_indicator" +} + +// Key 业务主键:ts_code + 交易日。 +func (s *StockIndicator) Key() string { + if s.TsCode == "" && s.TradeDate == 0 { + return "" + } + return s.TsCode + "#" + strconv.Itoa(s.TradeDate) +} + +// HasTotalMV 总市值是否已填充(大于 0)。 +func (s *StockIndicator) HasTotalMV() bool { + return s.TotalMv > 0 +} + +// HasCircMV 流通市值是否已填充。 +func (s *StockIndicator) HasCircMV() bool { + return s.CircMv > 0 +} diff --git a/tushare/board.go b/tushare/board.go new file mode 100644 index 0000000..76cfa91 --- /dev/null +++ b/tushare/board.go @@ -0,0 +1,141 @@ +package tushare + +/* +LimitCptList 获取限板板块列表 + + trade_date: 交易日,格式:YYYYMMDD + ts_code: 板块代码 + start_date: 开始日期,格式:YYYYMMDD + end_date: 结束日期,格式:YYYYMMDD +*/ +func (cli *TushareClient) LimitCptList(trade_date, ts_code, start_date, end_date string) (*TushareRespData, error) { + params := map[string]any{} + if trade_date != "" { + params["trade_date"] = trade_date + } + + if ts_code != "" { + params["ts_code"] = ts_code + } + if start_date != "" { + params["start_date"] = start_date + } + if end_date != "" { + params["end_date"] = end_date + } + + req := TushareReq{ + APIName: "limit_cpt_list", + Params: params, + } + fields := []map[string]string{ + {"rank": "排名"}, + {"ts_code": "板块代码"}, + {"name": "板块名称"}, + {"pct_chg": "涨跌幅%"}, + {"up_stat": "连板高度"}, + {"days": "上榜天数"}, + {"up_nums": "涨停标的数"}, + {"cons_nums": "连板标的数"}, + } + + return cli.Do(req, fields) +} + +/* +LimitListD 获取涨跌停列表(新) + + trade_date: 交易日期,格式:YYYYMMDD + ts_code: 股票代码 + limit_type: 涨跌停类型,U 涨停 D 跌停 Z 炸板 + exchange: 交易所,SH 上交所 SZ 深交所 BJ 北交所 + start_date: 开始日期,格式:YYYYMMDD + end_date: 结束日期,格式:YYYYMMDD +*/ +func (cli *TushareClient) LimitListD(trade_date, ts_code, limit_type, exchange, start_date, end_date string) (*TushareRespData, error) { + params := map[string]any{} + + if trade_date != "" { + params["trade_date"] = trade_date + } + if ts_code != "" { + params["ts_code"] = ts_code + } + if limit_type != "" { + params["limit_type"] = limit_type + } + if exchange != "" { + params["exchange"] = exchange + } + if start_date != "" { + params["start_date"] = start_date + } + if end_date != "" { + params["end_date"] = end_date + } + + req := TushareReq{ + APIName: "limit_list_d", + Params: params, + } + + fields := []map[string]string{ + {"trade_date": "交易日期"}, + {"ts_code": "股票代码"}, + {"industry": "所属行业"}, + {"name": "股票名称"}, + {"close": "收盘价"}, + {"pct_chg": "涨跌幅%"}, + {"amount": "成交额"}, + {"limit_amount": "板上成交金额"}, + {"float_mv": "流通市值"}, + {"total_mv": "总市值"}, + {"turnover_ratio": "换手率%"}, + {"fd_amount": "封单金额"}, + {"first_time": "首次封板时间"}, + {"last_time": "最后封板时间"}, + {"open_times": "炸板次数"}, + {"up_stat": "涨停统计"}, + {"limit_times": "连板数"}, + {"limit": "涨跌停类型"}, + } + + return cli.Do(req, fields) +} + +/* +LimitU 获取涨停股票列表 + + trade_date: 交易日期,格式:YYYYMMDD + ts_code: 股票代码 + start_date: 开始日期,格式:YYYYMMDD + end_date: 结束日期,格式:YYYYMMDD +*/ +func (cli *TushareClient) LimitU(trade_date, ts_code, start_date, end_date string) (*TushareRespData, error) { + return cli.LimitListD(trade_date, ts_code, "U", "", start_date, end_date) +} + +/* +LimitD 获取跌停股票列表 + + trade_date: 交易日期,格式:YYYYMMDD + ts_code: 股票代码 + start_date: 开始日期,格式:YYYYMMDD + end_date: 结束日期,格式:YYYYMMDD +*/ +func (cli *TushareClient) LimitD(trade_date, ts_code, start_date, end_date string) (*TushareRespData, error) { + return cli.LimitListD(trade_date, ts_code, "D", "", start_date, end_date) +} + +/* +LimitZ 获取炸板股票列表 + + trade_date: 交易日期,格式:YYYYMMDD + ts_code: 股票代码 + start_date: 开始日期,格式:YYYYMMDD + end_date: 结束日期,格式:YYYYMMDD +*/ +func (cli *TushareClient) LimitZ(trade_date, ts_code, start_date, end_date string) (*TushareRespData, error) { + return cli.LimitListD(trade_date, ts_code, "Z", "", start_date, end_date) +} + diff --git a/tushare/fina.go b/tushare/fina.go new file mode 100644 index 0000000..4c17298 --- /dev/null +++ b/tushare/fina.go @@ -0,0 +1,254 @@ +package tushare + +/* +Income 获取利润表 + + ts_code: 股票代码,支持多个,逗号分隔 + ann_date: 公告日期,格式:YYYYMMDD + start_date: 开始日期,格式:YYYYMMDD + end_date: 结束日期,格式:YYYYMMDD + report_type: 报表类型,1:合并报表,2:母公司报表 +*/ +func (cli *TushareClient) Income(ts_code, ann_date, start_date, end_date, report_type string) (*TushareRespData, error) { + params := map[string]any{} + + if ts_code != "" { + params["ts_code"] = ts_code + } + if ann_date != "" { + params["ann_date"] = ann_date + } + if start_date != "" { + params["start_date"] = start_date + } + if end_date != "" { + params["end_date"] = end_date + } + if report_type != "" { + params["report_type"] = report_type + } + + req := TushareReq{ + APIName: "income", + Params: params, + } + + fields := []map[string]string{ + {"ts_code": "股票代码"}, + {"ann_date": "公告日期"}, + {"f_ann_date": "实际公告日期"}, + {"end_date": "报告期"}, + {"basic_eps": "基本每股收益"}, + {"diluted_eps": "稀释每股收益"}, + {"total_revenue": "营业总收入"}, + {"revenue": "营业收入"}, + {"total_profit": "利润总额"}, + {"net_profit": "净利润"}, + {"net_profit_attr_sh": "归属于母公司所有者的净利润"}, + {"operating_expense": "营业总成本"}, + {"operating_cost": "营业成本"}, + } + + return cli.Do(req, fields) +} + +/* +Balancesheet 获取资产负债表 + + ts_code: 股票代码,支持多个,逗号分隔 + ann_date: 公告日期,格式:YYYYMMDD + start_date: 开始日期,格式:YYYYMMDD + end_date: 结束日期,格式:YYYYMMDD + report_type: 报表类型,1:合并报表,2:母公司报表 +*/ +func (cli *TushareClient) Balancesheet(ts_code, ann_date, start_date, end_date, report_type string) (*TushareRespData, error) { + params := map[string]any{} + + if ts_code != "" { + params["ts_code"] = ts_code + } + if ann_date != "" { + params["ann_date"] = ann_date + } + if start_date != "" { + params["start_date"] = start_date + } + if end_date != "" { + params["end_date"] = end_date + } + if report_type != "" { + params["report_type"] = report_type + } + + req := TushareReq{ + APIName: "balancesheet", + Params: params, + } + + fields := []map[string]string{ + {"ts_code": "股票代码"}, + {"ann_date": "公告日期"}, + {"f_ann_date": "实际公告日期"}, + {"end_date": "报告期"}, + {"total_assets": "资产总计"}, + {"total_liab": "负债合计"}, + {"total_hldr_eqy_exc_min_int": "股东权益合计"}, + {"total_hldr_eqy_inc_min_int": "股东权益合计 (含少数股东权益)"}, + {"total_share_capital": "股本"}, + {"cap_rsrv": "资本公积"}, + {"surplus_rsrv": "盈余公积"}, + {"undist_prft": "未分配利润"}, + {"monetary_cap": "货币资金"}, + {"total_current_assets": "流动资产合计"}, + {"total_non_current_assets": "非流动资产合计"}, + } + + return cli.Do(req, fields) +} + +/* +Cashflow 获取现金流量表 + + ts_code: 股票代码,支持多个,逗号分隔 + ann_date: 公告日期,格式:YYYYMMDD + start_date: 开始日期,格式:YYYYMMDD + end_date: 结束日期,格式:YYYYMMDD + report_type: 报表类型,1:合并报表,2:母公司报表 +*/ +func (cli *TushareClient) Cashflow(ts_code, ann_date, start_date, end_date, report_type string) (*TushareRespData, error) { + params := map[string]any{} + + if ts_code != "" { + params["ts_code"] = ts_code + } + if ann_date != "" { + params["ann_date"] = ann_date + } + if start_date != "" { + params["start_date"] = start_date + } + if end_date != "" { + params["end_date"] = end_date + } + if report_type != "" { + params["report_type"] = report_type + } + + req := TushareReq{ + APIName: "cashflow", + Params: params, + } + + fields := []map[string]string{ + {"ts_code": "股票代码"}, + {"ann_date": "公告日期"}, + {"f_ann_date": "实际公告日期"}, + {"end_date": "报告期"}, + {"net_cash_invest_act": "投资活动产生的现金流量净额"}, + {"net_cash_financing_act": "筹资活动产生的现金流量净额"}, + {"net_cash_oper_act": "经营活动产生的现金流量净额"}, + {"cash_equivalents_end": "现金及现金等价物期末余额"}, + {"cash_equivalents_begin": "现金及现金等价物期初余额"}, + {"net_increase_cash": "现金及现金等价物净增加额"}, + } + + return cli.Do(req, fields) +} + +/* +FinaIndicator 获取财务指标 + + ts_code: 股票代码,支持多个,逗号分隔 + ann_date: 公告日期,格式:YYYYMMDD + start_date: 开始日期,格式:YYYYMMDD + end_date: 结束日期,格式:YYYYMMDD +*/ +func (cli *TushareClient) FinaIndicator(ts_code, ann_date, start_date, end_date string) (*TushareRespData, error) { + params := map[string]any{} + + if ts_code != "" { + params["ts_code"] = ts_code + } + if ann_date != "" { + params["ann_date"] = ann_date + } + if start_date != "" { + params["start_date"] = start_date + } + if end_date != "" { + params["end_date"] = end_date + } + + req := TushareReq{ + APIName: "fina_indicator", + Params: params, + } + + fields := []map[string]string{ + {"ts_code": "股票代码"}, + {"ann_date": "公告日期"}, + {"end_date": "报告期"}, + {"eps_basic": "基本每股收益"}, + {"eps_diluted": "稀释每股收益"}, + {"roe": "净资产收益率%"}, + {"roe_wa": "净资产收益率 (加权)%"}, + {"roa": "总资产净利率%"}, + {"gross_margin": "销售毛利率%"}, + {"net_profit_margin": "销售净利率%"}, + {"current_ratio": "流动比率"}, + {"quick_ratio": "速动比率"}, + {"debt_to_assets": "资产负债率%"}, + {"turnover_days": "存货周转天数"}, + {"receivables_turnover": "应收账款周转率"}, + } + + return cli.Do(req, fields) +} + +/* +Forecast 获取业绩预告 + + ts_code: 股票代码,支持多个,逗号分隔 + ann_date: 公告日期,格式:YYYYMMDD + start_date: 开始日期,格式:YYYYMMDD + end_date: 结束日期,格式:YYYYMMDD + type: 预告类型,预增/预减/扭亏/续盈/首亏/略增/略减 +*/ +func (cli *TushareClient) Forecast(ts_code, ann_date, start_date, end_date, forecast_type string) (*TushareRespData, error) { + params := map[string]any{} + + if ts_code != "" { + params["ts_code"] = ts_code + } + if ann_date != "" { + params["ann_date"] = ann_date + } + if start_date != "" { + params["start_date"] = start_date + } + if end_date != "" { + params["end_date"] = end_date + } + if forecast_type != "" { + params["type"] = forecast_type + } + + req := TushareReq{ + APIName: "forecast", + Params: params, + } + + fields := []map[string]string{ + {"ts_code": "股票代码"}, + {"ann_date": "公告日期"}, + {"end_date": "报告期"}, + {"type": "预告类型"}, + {"net_profit_min": "净利润下限 (万元)"}, + {"net_profit_max": "净利润上限 (万元)"}, + {"parent_netprofit_min": "归母净利润下限 (万元)"}, + {"parent_netprofit_max": "归母净利润上限 (万元)"}, + {"summary": "业绩预告摘要"}, + } + + return cli.Do(req, fields) +} diff --git a/tushare/finance.go b/tushare/finance.go new file mode 100644 index 0000000..060f529 --- /dev/null +++ b/tushare/finance.go @@ -0,0 +1,206 @@ +package tushare + +/* +DailyBasic 获取股票日线指标 + + ts_code: 股票代码,支持多个,逗号分隔 + trade_date: 交易日期,格式:YYYYMMDD + start_date: 开始日期,格式:YYYYMMDD + end_date: 结束日期,格式:YYYYMMDD +*/ +func (cli *TushareClient) DailyBasic(ts_code, trade_date, start_date, end_date string) (*TushareRespData, error) { + params := map[string]any{} + + if ts_code != "" { + params["ts_code"] = ts_code + } + 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{ + APIName: "daily_basic", + Params: params, + } + + fields := []map[string]string{ + {"ts_code": "股票代码"}, + {"trade_date": "交易日期"}, + {"close": "收盘价"}, + {"turnover_rate": "换手率%"}, + {"turnover_rate_f": "换手率% (自由流通股本)"}, + {"volume_ratio": "量比"}, + {"pe": "市盈率"}, + {"pe_ttm": "市盈率 TTM"}, + {"pb": "市净率"}, + {"ps": "市销率"}, + {"ps_ttm": "市销率 TTM"}, + {"dv_ratio": "股息率%"}, + {"total_mv": "总市值"}, + {"circ_mv": "流通市值"}, + } + + return cli.Do(req, fields) +} + +/* +AdjFactor 获取复权因子 + + ts_code: 股票代码,支持多个,逗号分隔 + trade_date: 交易日期,格式:YYYYMMDD + start_date: 开始日期,格式:YYYYMMDD + end_date: 结束日期,格式:YYYYMMDD +*/ +func (cli *TushareClient) AdjFactor(ts_code, trade_date, start_date, end_date string) (*TushareRespData, error) { + params := map[string]any{} + + if ts_code != "" { + params["ts_code"] = ts_code + } + 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{ + APIName: "adj_factor", + Params: params, + } + + fields := []map[string]string{ + {"ts_code": "股票代码"}, + {"trade_date": "交易日期"}, + {"adj_factor": "复权因子"}, + } + + return cli.Do(req, fields) +} + +/* +Moneyflow 获取个股资金流向 + + ts_code: 股票代码,支持多个,逗号分隔 + trade_date: 交易日期,格式:YYYYMMDD + start_date: 开始日期,格式:YYYYMMDD + end_date: 结束日期,格式:YYYYMMDD +*/ +func (cli *TushareClient) Moneyflow(ts_code, trade_date, start_date, end_date string) (*TushareRespData, error) { + params := map[string]any{} + + if ts_code != "" { + params["ts_code"] = ts_code + } + 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{ + APIName: "moneyflow", + Params: params, + } + + fields := []map[string]string{ + {"ts_code": "股票代码"}, + {"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": "净流入金额 (千元)"}, + } + + return cli.Do(req, fields) +} + +/* +SuspendList 获取停牌股票列表 + + suspend_type: 停牌类型,1:盘中停牌,2:盘中临时停牌,3:全天停牌 + start_date: 开始日期,格式:YYYYMMDD + end_date: 结束日期,格式:YYYYMMDD +*/ +func (cli *TushareClient) SuspendList(suspend_type, start_date, end_date string) (*TushareRespData, error) { + params := map[string]any{} + + if suspend_type != "" { + params["suspend_type"] = suspend_type + } + if start_date != "" { + params["start_date"] = start_date + } + if end_date != "" { + params["end_date"] = end_date + } + + req := TushareReq{ + APIName: "suspend_list", + Params: params, + } + + fields := []map[string]string{ + {"ts_code": "股票代码"}, + {"name": "股票名称"}, + {"suspend_type": "停牌类型"}, + {"suspend_start": "停牌起始日"}, + {"suspend_end": "停牌结束日"}, + {"reason": "停牌原因"}, + } + + return cli.Do(req, fields) +} + +/* +RealtimeQuote 获取实时行情 + + ts_code: 股票代码,支持多个,逗号分隔 +*/ +func (cli *TushareClient) RealtimeQuote(ts_code string) (*TushareRespData, error) { + params := map[string]any{} + + if ts_code != "" { + params["ts_code"] = ts_code + } + + req := TushareReq{ + APIName: "realtime_quote", + Params: params, + } + + fields := []map[string]string{ + {"ts_code": "股票代码"}, + {"price": "最新价"}, + {"open": "开盘价"}, + {"high": "最高价"}, + {"low": "最低价"}, + {"pre_close": "昨收价"}, + {"vol": "成交量 (手)"}, + {"amount": "成交额 (千元)"}, + {"buy_vol": "买总量 (手)"}, + {"sell_vol": "卖总量 (手)"}, + {"buy_amount": "买总额 (千元)"}, + {"sell_amount": "卖总额 (千元)"}, + } + + return cli.Do(req, fields) +} diff --git a/tushare/index.go b/tushare/index.go new file mode 100644 index 0000000..0a89bb3 --- /dev/null +++ b/tushare/index.go @@ -0,0 +1,213 @@ +package tushare + +/* +IndexBasic 获取大盘指数基本信息 + + ts_code: 指数代码,支持多个,逗号分隔 + exchange: 交易所代码,SSE 上交所,SZSE 深交所 +*/ +func (cli *TushareClient) IndexBasic(ts_code, exchange string) (*TushareRespData, error) { + params := map[string]any{} + + if ts_code != "" { + params["ts_code"] = ts_code + } + if exchange != "" { + params["exchange"] = exchange + } + + req := TushareReq{ + APIName: "index_basic", + Params: params, + } + + fields := []map[string]string{ + {"ts_code": "指数代码"}, + {"name": "指数名称"}, + {"market": "市场类别"}, + {"publisher": "发布方"}, + {"category": "指数类别"}, + {"base_date": "基日"}, + {"base_point": "基点"}, + {"list_date": "发布日期"}, + } + + return cli.Do(req, fields) +} + +/* +IndexWeight 获取指数成分和权重 + + index_code: 指数代码,如 000300.SH(沪深 300) + trade_date: 交易日期,格式:YYYYMMDD + start_date: 开始日期,格式:YYYYMMDD + end_date: 结束日期,格式:YYYYMMDD +*/ +func (cli *TushareClient) IndexWeight(index_code, trade_date, start_date, end_date string) (*TushareRespData, error) { + params := map[string]any{} + + if index_code != "" { + params["index_code"] = index_code + } + 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{ + APIName: "index_weight", + Params: params, + } + + fields := []map[string]string{ + {"index_code": "指数代码"}, + {"trade_date": "交易日期"}, + {"ts_code": "成分股票代码"}, + {"weight": "权重%"}, + } + + return cli.Do(req, fields) +} + +/* +IndexClassify 获取指数分类信息 + + level: 指数级别,L1/L2/L3/L4 + src: 指数来源,SW 申万,ZJW 证监会,ICS iFinD + parent_code: 父级代码 +*/ +func (cli *TushareClient) IndexClassify(level, src, parent_code string) (*TushareRespData, error) { + params := map[string]any{} + + if level != "" { + params["level"] = level + } + if src != "" { + params["src"] = src + } + if parent_code != "" { + params["parent_code"] = parent_code + } + + req := TushareReq{ + APIName: "index_classify", + Params: params, + } + + fields := []map[string]string{ + {"index_code": "指数代码"}, + {"industry_name": "行业名称"}, + {"industry_code": "行业代码"}, + {"parent_code": "父级代码"}, + {"level": "级别"}, + {"src": "来源"}, + } + + return cli.Do(req, fields) +} + +/* +IndexMember 获取指数成分股 + + index_code: 指数代码,如 000300.SH(沪深 300) + trade_date: 交易日期,格式:YYYYMMDD + start_date: 开始日期,格式:YYYYMMDD + end_date: 结束日期,格式:YYYYMMDD +*/ +func (cli *TushareClient) IndexMember(index_code, trade_date, start_date, end_date string) (*TushareRespData, error) { + params := map[string]any{} + + if index_code != "" { + params["index_code"] = index_code + } + 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{ + APIName: "index_member", + Params: params, + } + + fields := []map[string]string{ + {"index_code": "指数代码"}, + {"trade_date": "交易日期"}, + {"ts_code": "成分股票代码"}, + {"symbol": "成分股票代码"}, + {"name": "成分股票名称"}, + {"is_new": "是否新增"}, + {"in_date": "纳入日期"}, + {"out_date": "剔除日期"}, + } + + return cli.Do(req, fields) +} + +/* +Concept 获取概念板块信息 + + ts_code: 板块代码,支持多个,逗号分隔 + name: 板块名称,支持模糊匹配 +*/ +func (cli *TushareClient) Concept(ts_code, name string) (*TushareRespData, error) { + params := map[string]any{} + + if ts_code != "" { + params["ts_code"] = ts_code + } + if name != "" { + params["name"] = name + } + + req := TushareReq{ + APIName: "concept", + Params: params, + } + + fields := []map[string]string{ + {"ts_code": "板块代码"}, + {"name": "板块名称"}, + {"src": "来源"}, + {"pub_date": "发布日期"}, + } + + return cli.Do(req, fields) +} + +/* +ConceptDetail 获取概念板块详情 + + ts_code: 板块代码 +*/ +func (cli *TushareClient) ConceptDetail(ts_code string) (*TushareRespData, error) { + params := map[string]any{} + + if ts_code != "" { + params["ts_code"] = ts_code + } + + req := TushareReq{ + APIName: "concept_detail", + Params: params, + } + + fields := []map[string]string{ + {"ts_code": "板块代码"}, + {"name": "板块名称"}, + {"concept_name": "概念名称"}, + {"stock_count": "成分股票数量"}, + } + + return cli.Do(req, fields) +} diff --git a/tushare/indicator.go b/tushare/indicator.go new file mode 100644 index 0000000..0e8454b --- /dev/null +++ b/tushare/indicator.go @@ -0,0 +1,24 @@ +package tushare + +func (cli *TushareClient) StkFactorPro(ts_code string, tradeDate string) (*TushareRespData, error) { + params := map[string]any{} + if ts_code != "" { + params["ts_code"] = ts_code + } + if tradeDate != "" { + params["trade_date"] = tradeDate + } + + req := TushareReq{ + APIName: "stk_factor_pro", + 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"}, + } + + return cli.Do(req, fields) +} diff --git a/tushare/margin.go b/tushare/margin.go new file mode 100644 index 0000000..59efdc8 --- /dev/null +++ b/tushare/margin.go @@ -0,0 +1,209 @@ +package tushare + +/* +HkHold 获取沪深股通持股明细 + + ts_code: 股票代码 + trade_date: 交易日期,格式:YYYYMMDD + start_date: 开始日期,格式:YYYYMMDD + end_date: 结束日期,格式:YYYYMMDD +*/ +func (cli *TushareClient) HkHold(ts_code, trade_date, start_date, end_date string) (*TushareRespData, error) { + params := map[string]any{} + + if ts_code != "" { + params["ts_code"] = ts_code + } + 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{ + APIName: "hk_hold", + Params: params, + } + + fields := []map[string]string{ + {"ts_code": "股票代码"}, + {"trade_date": "交易日期"}, + {"holding_sh": "沪股通持股数量 (股)"}, + {"holding_sz": "深股通持股数量 (股)"}, + {"holding_total": "沪深股通持股总量 (股)"}, + {"ratio_sh": "沪股通持股比例%"}, + {"ratio_sz": "深股通持股比例%"}, + {"ratio_total": "沪深股通持股比例合计%"}, + } + + return cli.Do(req, fields) +} + +/* +MarginDetail 获取融资融券明细 + + trade_date: 交易日期,格式:YYYYMMDD + start_date: 开始日期,格式:YYYYMMDD + end_date: 结束日期,格式:YYYYMMDD + exchange: 交易所代码,SSE 上交所,SZSE 深交所 +*/ +func (cli *TushareClient) MarginDetail(trade_date, start_date, end_date, exchange string) (*TushareRespData, error) { + params := map[string]any{} + + if trade_date != "" { + params["trade_date"] = trade_date + } + if start_date != "" { + params["start_date"] = start_date + } + if end_date != "" { + params["end_date"] = end_date + } + if exchange != "" { + params["exchange"] = exchange + } + + req := TushareReq{ + APIName: "margin_detail", + Params: params, + } + + fields := []map[string]string{ + {"trade_date": "交易日期"}, + {"ts_code": "股票代码"}, + {"buy_value": "融资买入额 (元)"}, + {"buy_repay_value": "融资偿还额 (元)"}, + {"buy_bal": "融资余额 (元)"}, + {"sell_value": "融券卖出量 (股)"}, + {"sell_repay_value": "融券偿还量 (股)"}, + {"sell_bal": "融券余量 (股)"}, + {"sell_amount": "融券余量金额 (元)"}, + } + + return cli.Do(req, fields) +} + +/* +TopList 获取龙虎榜数据 + + trade_date: 交易日期,格式:YYYYMMDD + start_date: 开始日期,格式:YYYYMMDD + end_date: 结束日期,格式:YYYYMMDD +*/ +func (cli *TushareClient) TopList(trade_date, start_date, end_date string) (*TushareRespData, error) { + params := map[string]any{} + + 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{ + APIName: "top_list", + Params: params, + } + + fields := []map[string]string{ + {"trade_date": "交易日期"}, + {"ts_code": "股票代码"}, + {"name": "股票名称"}, + {"close": "收盘价"}, + {"pct_chg": "涨跌幅%"}, + {"turnover_rate": "换手率%"}, + {"total_value": "成交总额 (万元)"}, + {"net_value": "净额 (万元)"}, + {"buy_value": "买入总额 (万元)"}, + {"sell_value": "卖出总额 (万元)"}, + {"reason": "上榜原因"}, + } + + return cli.Do(req, fields) +} + +/* +TopInst 获取龙虎榜机构席位数据 + + trade_date: 交易日期,格式:YYYYMMDD + start_date: 开始日期,格式:YYYYMMDD + end_date: 结束日期,格式:YYYYMMDD +*/ +func (cli *TushareClient) TopInst(trade_date, start_date, end_date string) (*TushareRespData, error) { + params := map[string]any{} + + 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{ + APIName: "top_inst", + Params: params, + } + + fields := []map[string]string{ + {"trade_date": "交易日期"}, + {"ts_code": "股票代码"}, + {"name": "股票名称"}, + {"buy_value": "机构买入总额 (万元)"}, + {"buy_count": "机构买入次数"}, + {"sell_value": "机构卖出总额 (万元)"}, + {"sell_count": "机构卖出次数"}, + {"net_value": "机构净买入额 (万元)"}, + } + + return cli.Do(req, fields) +} + +/* +BlockTrade 获取大宗交易数据 + + trade_date: 交易日期,格式:YYYYMMDD + start_date: 开始日期,格式:YYYYMMDD + end_date: 结束日期,格式:YYYYMMDD +*/ +func (cli *TushareClient) BlockTrade(trade_date, start_date, end_date string) (*TushareRespData, error) { + params := map[string]any{} + + 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{ + APIName: "block_trade", + Params: params, + } + + fields := []map[string]string{ + {"trade_date": "交易日期"}, + {"ts_code": "股票代码"}, + {"name": "证券简称"}, + {"price": "成交价"}, + {"vol": "成交量 (万股)"}, + {"amount": "成交额 (万元)"}, + {"buyer": "买方营业部"}, + {"seller": "卖方营业部"}, + {"premium_rate": "溢价率%"}, + } + + return cli.Do(req, fields) +} diff --git a/tushare/new.go b/tushare/new.go new file mode 100644 index 0000000..f5b4769 --- /dev/null +++ b/tushare/new.go @@ -0,0 +1,168 @@ +package tushare + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "net/http" + "os" + "time" + + "github.com/jedib0t/go-pretty/v6/table" +) + +// TushareClient Tushare API 客户端 +type TushareClient struct { + Token string `json:"token"` // API 访问令牌 + BaseUrl string `json:"base_url"` // API 基础 URL +} + +// TushareReq Tushare API 请求结构 +type TushareReq struct { + APIName string `json:"api_name"` // API 接口名称 + Token string `json:"token"` // API 访问令牌 + Params map[string]any `json:"params"` // 请求参数 + Fields []string `json:"fields"` // 返回字段列表 +} + +// TushareResp Tushare API 响应结构 +type TushareResp struct { + RequestID string `json:"request_id"` // 请求 ID + Code int `json:"code"` // 响应码 (0 表示成功) + Data *TushareRespData `json:"data"` // 响应数据 + Msg string `json:"msg"` // 响应消息 +} + +// TushareRespData Tushare API 响应数据结构 +type TushareRespData struct { + Fields []string `json:"fields"` // 字段列表 + Headers []string `json:"headers"` // 表头(中文描述) + Items [][]any `json:"items"` // 数据项(二维数组) + HasMore bool `json:"has_more"` // 是否有更多数据 + Count int `json:"count"` // 返回数据条数 +} + +// NewClient 创建并初始化 Tushare 客户端 +// 返回配置好 Token 和基础 URL 的客户端实例 +func NewClient(token string) *TushareClient { + if token == "" { + token = os.Getenv("TUSHARE_TOKEN") + } + return &TushareClient{ + Token: token, + BaseUrl: "http://api.tushare.pro", + } +} + +// Do 执行 Tushare API 请求 +// req: 请求参数,包含 API 名称、参数等 +// fieldsVals: 字段配置列表,每个元素是一个 map,key 为字段名,value 为字段中文描述 +// 返回:响应数据和错误信息 +func (c *TushareClient) Do(req TushareReq, fieldsVals []map[string]string) (*TushareRespData, error) { + // 构建请求体 + payload := TushareReq{ + APIName: req.APIName, + Token: c.Token, + Params: req.Params, + } + + // 提取字段名和对应的中文表头 + var fields []string + var headers []string + for _, setting := range fieldsVals { + for key, value := range setting { + fields = append(fields, key) + headers = append(headers, value) + } + } + 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)) + 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) + } + + // 解析响应 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 +} + +// 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 + } + + for _, item := range c.Items { + rowMap := make(map[string]any, len(c.Fields)) + for idx, fn := range c.Fields { + if idx < len(item) { + rowMap[fn] = item[idx] + } + } + result = append(result, rowMap) + } + return result +} + +// Json 将响应数据转换为 JSON 字节数组 +// 返回:JSON 格式的字节数据和错误信息 +func (c *TushareRespData) Json() ([]byte, error) { + data := c.Map() + return json.MarshalIndent(data, "", " ") +} + +// Output 将响应数据格式化为表格输出 +// title: 表格标题 +// 返回:格式化的表格写入器 +func (c *TushareRespData) Output(title string) table.Writer { + tw := table.NewWriter() + tw.SetStyle(table.StyleLight) + tw.SetTitle(title) + + // 构建表头行 + headerRow := make(table.Row, 0, len(c.Headers)) + for idx, header := range c.Headers { + headerRow = append(headerRow, header+"("+c.Fields[idx]+")") + } + tw.AppendHeader(headerRow) + + // 添加数据行 + for _, item := range c.Items { + tw.AppendRow(item) + } + return tw +} diff --git a/tushare/stock.go b/tushare/stock.go new file mode 100644 index 0000000..cdb15ad --- /dev/null +++ b/tushare/stock.go @@ -0,0 +1,241 @@ +package tushare + +import ( + "time" +) + +/* +TradeCal 获取交易日历 + + exchange: 交易所代码,SSE 上交所,SZSE 深交所,BSE 北交所,空字符串代表所有 + start_date: 开始日期,格式:YYYYMMDD + end_date: 结束日期,格式:YYYYMMDD + is_open: 是否开盘,'0'休市,'1'交易,空字符串代表所有 +*/ +func (cli *TushareClient) TradeCal(exchange, start_date, end_date, is_open string) (*TushareRespData, error) { + params := map[string]any{} + + if exchange != "" { + params["exchange"] = exchange + } + if start_date != "" { + params["start_date"] = start_date + } + if end_date != "" { + params["end_date"] = end_date + } + if is_open != "" { + params["is_open"] = is_open + } + + req := TushareReq{ + APIName: "trade_cal", + Params: params, + } + + fields := []map[string]string{ + {"exchange": "交易所代码"}, + {"cal_date": "日历日期"}, + {"is_open": "是否开盘"}, + {"pretrade_date": "上一交易日"}, + } + + return cli.Do(req, fields) +} + +func (cli *TushareClient) ReturnLastTradeDay() string { + result, err := cli.TradeCal("SSE", time.Now().AddDate(0, 0, -15).Format("20060102"), time.Now().Format("20060102"), "1") + if err != nil { + return "" + } + cal := result.Map() + return cal[0]["cal_date"].(string) +} + +/* +StockBasic 获取股票列表 + + ts_code: 股票代码,支持多个,如"000002.SZ,000001.SZ" + exchange: 交易所代码,SSE 上交所,SZSE 深交所 + is_shsc: 是否在沪股通或深港通范围内,0:否;1:沪股通;2:深股通 + start_date: 开始日期,格式:YYYYMMDD + end_date: 结束日期,格式:YYYYMMDD +*/ +func (cli *TushareClient) StockBasic(ts_code, exchange, is_shsc, start_date, end_date string) (*TushareRespData, error) { + params := map[string]any{} + + if ts_code != "" { + params["ts_code"] = ts_code + } + if exchange != "" { + params["exchange"] = exchange + } + if is_shsc != "" { + params["is_shsc"] = is_shsc + } + if start_date != "" { + params["start_date"] = start_date + } + if end_date != "" { + params["end_date"] = end_date + } + + req := TushareReq{ + APIName: "stock_basic", + Params: params, + } + + fields := []map[string]string{ + {"ts_code": "TS 代码"}, + {"symbol": "股票交易代码"}, + {"name": "证券简称"}, + {"area": "地区"}, + {"industry": "所属行业"}, + {"market": "市场类别"}, + {"list_status": "上市状态"}, + {"list_date": "上市日期"}, + {"delist_date": "退市日期"}, + {"is_shsc": "是否沪股通或深股通"}, + } + + return cli.Do(req, fields) +} + +/* +Daily 获取日线行情 + + ts_code: 股票代码,格式:000001.SZ 或 600000.SH + trade_date: 交易日期,格式:YYYYMMDD + start_date: 开始日期,格式:YYYYMMDD + end_date: 结束日期,格式:YYYYMMDD +*/ +func (cli *TushareClient) Daily(ts_code, trade_date, start_date, end_date string) (*TushareRespData, error) { + params := map[string]any{} + + if ts_code != "" { + params["ts_code"] = ts_code + } + 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{ + APIName: "daily", + Params: params, + } + + fields := []map[string]string{ + {"ts_code": "股票代码"}, + {"trade_date": "交易日期"}, + {"open": "开盘价"}, + {"high": "最高价"}, + {"low": "最低价"}, + {"close": "收盘价"}, + {"pre_close": "昨收价"}, + {"change": "涨跌额"}, + {"pct_chg": "涨跌幅%"}, + {"vol": "成交量 (手)"}, + {"amount": "成交额 (千元)"}, + } + + return cli.Do(req, fields) +} + +/* +Min 获取分钟线行情 + + ts_code: 股票代码,格式:000001.SZ 或 600000.SH + trade_date: 交易日期,格式:YYYYMMDD + minute: 分钟类型,1/5/15/30/60 分钟 + start_time: 开始时间,格式:HHmmss + end_time: 结束时间,格式:HHmmss +*/ +func (cli *TushareClient) Min(ts_code, trade_date, minute, start_time, end_time string) (*TushareRespData, error) { + params := map[string]any{} + + if ts_code != "" { + params["ts_code"] = ts_code + } + if trade_date != "" { + params["trade_date"] = trade_date + } + if minute != "" { + params["minute"] = minute + } + if start_time != "" { + params["start_time"] = start_time + } + if end_time != "" { + params["end_time"] = end_time + } + + req := TushareReq{ + APIName: "min", + Params: params, + } + + fields := []map[string]string{ + {"ts_code": "股票代码"}, + {"trade_time": "交易时间"}, + {"open": "开盘价"}, + {"high": "最高价"}, + {"low": "最低价"}, + {"close": "收盘价"}, + {"vol": "成交量 (手)"}, + {"amount": "成交额 (千元)"}, + } + + return cli.Do(req, fields) +} + +/* +IndexDaily 获取大盘指数日线行情 + + ts_code: 指数代码,如 000001.SH(上证指数),399001.SZ(深证成指) + trade_date: 交易日期,格式:YYYYMMDD + start_date: 开始日期,格式:YYYYMMDD + end_date: 结束日期,格式:YYYYMMDD +*/ +func (cli *TushareClient) IndexDaily(ts_code, trade_date, start_date, end_date string) (*TushareRespData, error) { + params := map[string]any{} + + if ts_code != "" { + params["ts_code"] = ts_code + } + 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{ + APIName: "index_daily", + Params: params, + } + + fields := []map[string]string{ + {"ts_code": "指数代码"}, + {"trade_date": "交易日期"}, + {"close": "收盘点位"}, + {"open": "开盘点位"}, + {"high": "最高点位"}, + {"low": "最低点位"}, + {"pre_close": "昨收点位"}, + {"change": "涨跌额"}, + {"pct_chg": "涨跌幅%"}, + {"vol": "成交量 (手)"}, + {"amount": "成交额 (千元)"}, + } + + return cli.Do(req, fields) +} diff --git a/tushare/ths.go b/tushare/ths.go new file mode 100644 index 0000000..03d6330 --- /dev/null +++ b/tushare/ths.go @@ -0,0 +1,213 @@ +package tushare + +/* +ThsHot 同花顺热榜 + + trade_date str N 交易日期 + ts_code str N TS代码 + market str N 热榜类型(热股、ETF、可转债、行业板块、概念板块、期货、港股、热基、美股) + is_new str N 是否最新(默认Y,如果为N则为盘中和盘后阶段采集,具体时间可参考rank_time字段,状态N每小时更新一次,状态Y更新时间为22:30) +*/ +func (cli *TushareClient) ThsHot(trade_date, ts_code, market, is_new string) (*TushareRespData, error) { + params := map[string]any{} + if trade_date != "" { + params["trade_date"] = trade_date + } + + if ts_code != "" { + params["ts_code"] = ts_code + } + if market != "" { + params["market"] = market + } + if is_new != "" { + params["is_new"] = is_new + } + + req := TushareReq{ + APIName: "ths_hot", + Params: params, + } + /* + trade_date str Y 交易日期 + data_type str Y 数据类型 + ts_code str Y 股票代码 + ts_name str Y 股票名称 + rank int Y 排行 + pct_change float Y 涨跌幅% + current_price float Y 当前价格 + concept str Y 标签 + rank_reason str Y 上榜解读 + hot float Y 热度值 + rank_time str Y 排行榜获取时间 + */ + fields := []map[string]string{ + {"trade_date": "交易日期"}, + {"data_type": "数据类型"}, + {"ts_code": "TS代码"}, + {"ts_name": "股票名称"}, + {"rank": "排行"}, + {"pct_change": "涨跌幅%"}, + {"current_price": "当前价格"}, + {"concept": "标签"}, + {"rank_reason": "上榜解读"}, + {"hot": "热度值"}, + {"rank_time": "排行榜获取时间"}, + } + + return cli.Do(req, fields) +} + +/* +ThsIndex 同花顺概念和行业指数 + +接口说明: +- 描述:获取同花顺板块指数,包括概念、行业、特色指数 +- 权限:本接口需有6000积分,单次最大返回5000行数据,一次可提取全部数据,请勿循环提取 +- 注意事项:数据版权归属同花顺,如做商业用途,请主动联系同花顺 + +输入参数: +- ts_code: 指数代码(可选) +- exchange: 市场类型,A-a股 HK-港股 US-美股(可选) +- type: 指数类型,N-概念指数 I-行业指数 R-地域指数 S-同花顺特色指数 ST-同花顺风格指数 TH-同花顺主题指数 BB-同花顺宽基指数(可选) + +输出参数: +- ts_code: 代码 +- name: 名称 +- count: 成分个数 +- exchange: 交易所 +- list_date: 上市日期 +- type: N概念指数S特色指数 +*/ +func (cli *TushareClient) ThsIndex(ts_code, exchange, typ string) (*TushareRespData, error) { + params := map[string]any{} + if ts_code != "" { + params["ts_code"] = ts_code + } + if exchange != "" { + params["exchange"] = exchange + } + if typ != "" { + params["type"] = typ + } + + req := TushareReq{ + APIName: "ths_index", + Params: params, + } + + fields := []map[string]string{ + {"ts_code": "代码"}, + {"name": "名称"}, + {"count": "成分个数"}, + {"exchange": "交易所"}, + {"list_date": "上市日期"}, + {"type": "类型"}, + } + + return cli.Do(req, fields) +} + +/* +ThsDaily 同花顺板块指数行情 + +接口说明: +- 描述:获取同花顺板块指数行情 +- 限量:单次最大3000行数据(需6000积分),可根据指数代码、日期参数循环提取 +- 注意事项:数据版权归属同花顺,如做商业用途,请主动联系同花顺 + +输入参数: +- ts_code: 指数代码(可选) +- trade_date: 交易日期(YYYYMMDD格式)(可选) +- start_date: 开始日期(YYYYMMDD格式)(可选) +- end_date: 结束日期(YYYYMMDD格式)(可选) + +输出参数: +- ts_code: TS指数代码 +- trade_date: 交易日 +- close: 收盘点位 +- open: 开盘点位 +- high: 最高点位 +- low: 最低点位 +- pre_close: 昨日收盘点 +- avg_price: 平均价 +- change: 涨跌点位 +- pct_change: 涨跌幅 +- vol: 成交量(手) +- turnover_rate: 换手率(%) +- total_mv: 总市值(元) +- float_mv: 流通市值(元) +*/ +func (cli *TushareClient) ThsDaily(ts_code, trade_date, start_date, end_date string) (*TushareRespData, error) { + params := map[string]any{} + if ts_code != "" { + params["ts_code"] = ts_code + } + 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{ + APIName: "ths_daily", + Params: params, + } + + fields := []map[string]string{ + {"ts_code": "TS指数代码"}, + {"trade_date": "交易日"}, + {"close": "收盘点位"}, + {"open": "开盘点位"}, + {"high": "最高点位"}, + {"low": "最低点位"}, + {"pre_close": "昨日收盘点"}, + {"avg_price": "平均价"}, + {"change": "涨跌点位"}, + {"pct_change": "涨跌幅"}, + {"vol": "成交量(手)"}, + {"turnover_rate": "换手率(%)"}, + {"total_mv": "总市值(元)"}, + {"float_mv": "流通市值(元)"}, + } + + return cli.Do(req, fields) +} + +/* +ThsMember 获取同花顺概念板块成分 + + ts_code: 板块指数代码 + con_code: 股票代码 +*/ +func (cli *TushareClient) ThsMember(ts_code, con_code string) (*TushareRespData, error) { + params := map[string]any{} + + if ts_code != "" { + params["ts_code"] = ts_code + } + if con_code != "" { + params["con_code"] = con_code + } + + req := TushareReq{ + APIName: "ths_member", + Params: params, + } + + fields := []map[string]string{ + {"ts_code": "指数代码"}, + {"con_code": "股票代码"}, + {"con_name": "股票名称"}, + {"weight": "权重"}, + {"in_date": "纳入日期"}, + {"out_date": "剔除日期"}, + {"is_new": "是否最新"}, + } + + return cli.Do(req, fields) +}