Initial commit: 可转债轮动分析系统
- 实时行情:东财push2主源 + 腾讯批量报价备用源(自动切换) - 历史K线:东财push2his主源 + 腾讯web子域备用源(含熔断机制) - 增量同步:支持 -incr/-since 参数按存档日期增量入库 - 策略回测:净值曲线/10指标/基准对比,含基本面过滤 - 用户认证:用户名密码 + 邮箱验证码双登录方式 - 前端:三Tab SPA(实时行情/策略回测/数据管理)
This commit is contained in:
116
internal/datasource/eastmoney/fundamental.go
Normal file
116
internal/datasource/eastmoney/fundamental.go
Normal file
@@ -0,0 +1,116 @@
|
||||
package eastmoney
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"stock-analysis/internal/model"
|
||||
)
|
||||
|
||||
// datacenterFundamentalResp 是 datacenter 财务数据接口响应。
|
||||
type datacenterFundamentalResp struct {
|
||||
Result struct {
|
||||
Pages int `json:"pages"`
|
||||
Data []datacenterFundamentalItem `json:"data"`
|
||||
} `json:"result"`
|
||||
Success bool `json:"success"`
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
// datacenterFundamentalItem 是单条财务数据记录。
|
||||
type datacenterFundamentalItem struct {
|
||||
SecurityCode string `json:"SECURITY_CODE"`
|
||||
SecurityName string `json:"SECURITY_NAME_ABBR"`
|
||||
ROE *float64 `json:"ROEJQ"` // 加权净资产收益率(%)
|
||||
DebtRatio *float64 `json:"ZCFZL"` // 资产负债率(%)
|
||||
NetProfit *float64 `json:"PARENTNETPROFIT"` // 归属净利润(元)
|
||||
IsBZ string `json:"IS_BZ"` // ST状态:0=正常, 1=ST, 2=*ST
|
||||
ReportDate string `json:"REPORT_DATE"`
|
||||
}
|
||||
|
||||
// FetchStockFundamentals 批量拉取指定正股的最新年度基本面数据。
|
||||
// 使用 datacenter API(非 push2),按 REPORT_DATE 倒序取每只股票的最新一条年报。
|
||||
func (c *Client) FetchStockFundamentals(ctx context.Context, codes []string) ([]model.StockFundamental, error) {
|
||||
if len(codes) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
codeSet := make(map[string]bool, len(codes))
|
||||
for _, code := range codes {
|
||||
codeSet[code] = true
|
||||
}
|
||||
|
||||
const pageSize = 5000
|
||||
seen := make(map[string]bool, len(codes))
|
||||
var funds []model.StockFundamental
|
||||
|
||||
// 按 REPORT_DATE 倒序拉取,每只股票取第一条(最新年报)。
|
||||
for page := 1; page <= 3; page++ {
|
||||
params := url.Values{}
|
||||
params.Set("reportName", "RPT_F10_FINANCE_MAINFINADATA")
|
||||
params.Set("columns", "SECURITY_CODE,SECURITY_NAME_ABBR,ROEJQ,ZCFZL,PARENTNETPROFIT,IS_BZ,REPORT_DATE")
|
||||
params.Set("filter", `(REPORT_TYPE="年报")`)
|
||||
params.Set("pageNumber", strconv.Itoa(page))
|
||||
params.Set("pageSize", strconv.Itoa(pageSize))
|
||||
params.Set("sortColumns", "REPORT_DATE")
|
||||
params.Set("sortTypes", "-1")
|
||||
|
||||
var resp datacenterFundamentalResp
|
||||
if err := c.getJSON(ctx, []string{datacenterHost}, "/api/data/v1/get", params, &resp); err != nil {
|
||||
return nil, fmt.Errorf("datacenter page %d: %w", page, err)
|
||||
}
|
||||
if !resp.Success {
|
||||
return nil, fmt.Errorf("datacenter API error: %s", resp.Message)
|
||||
}
|
||||
if len(resp.Result.Data) == 0 {
|
||||
break
|
||||
}
|
||||
|
||||
for _, d := range resp.Result.Data {
|
||||
code := d.SecurityCode
|
||||
if code == "" || seen[code] || !codeSet[code] {
|
||||
continue
|
||||
}
|
||||
seen[code] = true
|
||||
|
||||
roe := 0.0
|
||||
if d.ROE != nil {
|
||||
roe = *d.ROE
|
||||
}
|
||||
debtRatio := 0.0
|
||||
if d.DebtRatio != nil {
|
||||
debtRatio = *d.DebtRatio
|
||||
}
|
||||
netProfit := 0.0
|
||||
if d.NetProfit != nil {
|
||||
netProfit = *d.NetProfit
|
||||
}
|
||||
status := 1
|
||||
if d.IsBZ == "1" || d.IsBZ == "2" {
|
||||
status = 2
|
||||
}
|
||||
|
||||
updateDate := strings.Split(d.ReportDate, " ")[0]
|
||||
|
||||
funds = append(funds, model.StockFundamental{
|
||||
Code: code,
|
||||
Name: d.SecurityName,
|
||||
ROE: roe,
|
||||
DebtRatio: debtRatio,
|
||||
NetProfit: netProfit,
|
||||
Status: status,
|
||||
UpdateDate: updateDate,
|
||||
})
|
||||
}
|
||||
|
||||
// 已找到全部目标股票,停止翻页。
|
||||
if len(seen) >= len(codes) {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return funds, nil
|
||||
}
|
||||
Reference in New Issue
Block a user