- 实时行情:东财push2主源 + 腾讯批量报价备用源(自动切换) - 历史K线:东财push2his主源 + 腾讯web子域备用源(含熔断机制) - 增量同步:支持 -incr/-since 参数按存档日期增量入库 - 策略回测:净值曲线/10指标/基准对比,含基本面过滤 - 用户认证:用户名密码 + 邮箱验证码双登录方式 - 前端:三Tab SPA(实时行情/策略回测/数据管理)
141 lines
4.5 KiB
Go
141 lines
4.5 KiB
Go
// Package eastmoney 东财数据源:datacenter(列表/基础信息)与 push2(实时/历史行情)。
|
||
package eastmoney
|
||
|
||
import (
|
||
"context"
|
||
"encoding/json"
|
||
"errors"
|
||
"net/url"
|
||
"strconv"
|
||
"time"
|
||
|
||
"stock-analysis/internal/model"
|
||
)
|
||
|
||
// datacenterHost 东财数据中心接口域名(与 push2 分属不同集群,限流相对宽松)。
|
||
const datacenterHost = "https://datacenter-web.eastmoney.com"
|
||
|
||
// datacenterResp 是 datacenter 列表接口响应结构。
|
||
type datacenterResp struct {
|
||
Result struct {
|
||
Data []struct {
|
||
SecurityCode string `json:"SECURITY_CODE"` // 转债代码
|
||
SecurityName string `json:"SECURITY_NAME_ABBR"` // 转债简称
|
||
BondStartDate string `json:"BOND_START_DATE"` // 发行日期
|
||
ListingDate string `json:"LISTING_DATE"` // 上市日期(部分记录缺失)
|
||
DelistDate string `json:"DELIST_DATE"` // 退市日期(未退市为空)
|
||
ConvertStockCode string `json:"CONVERT_STOCK_CODE"` // 正股代码
|
||
InitialTransferPice float64 `json:"INITIAL_TRANSFER_PRICE"` // 初始转股价
|
||
} `json:"data"`
|
||
} `json:"result"`
|
||
Success bool `json:"success"`
|
||
}
|
||
|
||
// FetchAllBondCodes 从 datacenter 分页拉取全量转债代码列表(含已退市,约 1000 只)。
|
||
// 相比 push2 clist 接口不受实时行情集群限流影响,适合批量抓取场景。
|
||
// 返回的 ConvertibleBond 含正股代码/初始转股价/上市退市日期。
|
||
func (c *Client) FetchAllBondCodes(ctx context.Context) ([]model.ConvertibleBond, error) {
|
||
const pageSize = 100
|
||
var bonds []model.ConvertibleBond
|
||
|
||
for page := 1; ; page++ {
|
||
params := url.Values{}
|
||
params.Set("reportName", "RPT_BOND_CB_LIST")
|
||
params.Set("columns", "SECURITY_CODE,SECURITY_NAME_ABBR,BOND_START_DATE,LISTING_DATE,DELIST_DATE,CONVERT_STOCK_CODE,INITIAL_TRANSFER_PRICE")
|
||
params.Set("pageNumber", strconv.Itoa(page))
|
||
params.Set("pageSize", strconv.Itoa(pageSize))
|
||
|
||
var resp datacenterResp
|
||
if err := c.getJSON(ctx, []string{datacenterHost}, "/api/data/v1/get", params, &resp); err != nil {
|
||
return nil, err
|
||
}
|
||
if len(resp.Result.Data) == 0 {
|
||
break
|
||
}
|
||
for _, d := range resp.Result.Data {
|
||
if d.SecurityCode == "" {
|
||
continue
|
||
}
|
||
listDate := d.ListingDate
|
||
if len(listDate) < 10 {
|
||
listDate = d.BondStartDate // 兜底:部分老债缺上市日期
|
||
}
|
||
delistDate := ""
|
||
if len(d.DelistDate) >= 10 {
|
||
delistDate = d.DelistDate[:10]
|
||
}
|
||
bonds = append(bonds, model.ConvertibleBond{
|
||
Code: d.SecurityCode,
|
||
Name: d.SecurityName,
|
||
StockCode: d.ConvertStockCode,
|
||
InitialConvertPrice: d.InitialTransferPice,
|
||
ListDate: listDate[:10],
|
||
DelistDate: delistDate,
|
||
})
|
||
}
|
||
if len(resp.Result.Data) < pageSize {
|
||
break // 最后一页
|
||
}
|
||
}
|
||
return bonds, nil
|
||
}
|
||
|
||
// FetchLatestBonds 获取最近上市(已上市且有行情数据)的 n 只可转债。
|
||
// 走 push2 clist 接口按上市日期(f26)倒序,过滤上市日期缺失及尚未上市(未来日期)的记录。
|
||
func (c *Client) FetchLatestBonds(ctx context.Context, n int) ([]model.ConvertibleBond, error) {
|
||
const fetchSize = 50 // 放宽单页数量,确保过滤后仍有足够样本
|
||
params := url.Values{}
|
||
params.Set("pn", "1")
|
||
params.Set("pz", strconv.Itoa(fetchSize))
|
||
params.Set("po", "1")
|
||
params.Set("np", "1")
|
||
params.Set("fltt", "2")
|
||
params.Set("invt", "2")
|
||
params.Set("fid", "f26") // 按上市日期排序
|
||
params.Set("fs", "b:MK0354")
|
||
params.Set("fields", "f12,f14,f26")
|
||
|
||
var resp clistResp
|
||
if err := c.getJSON(ctx, realTimeHosts, "/api/qt/clist/get", params, &resp); err != nil {
|
||
return nil, err
|
||
}
|
||
if len(resp.Data.Diff) == 0 {
|
||
return nil, errors.New("eastmoney: empty clist")
|
||
}
|
||
|
||
var items []json.RawMessage
|
||
if isJSONArray(resp.Data.Diff) {
|
||
if err := json.Unmarshal(resp.Data.Diff, &items); err != nil {
|
||
return nil, err
|
||
}
|
||
} else {
|
||
items = []json.RawMessage{resp.Data.Diff}
|
||
}
|
||
|
||
today := time.Now().Format("2006-01-02")
|
||
bonds := make([]model.ConvertibleBond, 0, n)
|
||
for _, raw := range items {
|
||
var b rawBond
|
||
if err := json.Unmarshal(raw, &b); err != nil {
|
||
continue
|
||
}
|
||
if b.F12 == "" {
|
||
continue
|
||
}
|
||
listDate := parseDate(b.F26)
|
||
// 跳过上市日期缺失或尚未上市(日期在未来)的转债。
|
||
if listDate == "" || listDate > today {
|
||
continue
|
||
}
|
||
bonds = append(bonds, model.ConvertibleBond{
|
||
Code: b.F12,
|
||
Name: b.F14,
|
||
ListDate: listDate,
|
||
})
|
||
if len(bonds) >= n {
|
||
break
|
||
}
|
||
}
|
||
return bonds, nil
|
||
}
|