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

141 lines
4.5 KiB
Go
Raw Permalink 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 东财数据源: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
}