Files
stock-analysis/internal/datasource/eastmoney/fundamental.go
WarrenZhou e28bfec957 Initial commit: 可转债轮动分析系统
- 实时行情:东财push2主源 + 腾讯批量报价备用源(自动切换)
- 历史K线:东财push2his主源 + 腾讯web子域备用源(含熔断机制)
- 增量同步:支持 -incr/-since 参数按存档日期增量入库
- 策略回测:净值曲线/10指标/基准对比,含基本面过滤
- 用户认证:用户名密码 + 邮箱验证码双登录方式
- 前端:三Tab SPA(实时行情/策略回测/数据管理)
2026-08-17 20:23:22 +08:00

116 lines
3.2 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package 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
}