review code.
This commit is contained in:
@@ -1,12 +1,26 @@
|
||||
package tushare
|
||||
|
||||
func (cli *TushareClient) StkFactorPro(ts_code string, tradeDate string) (*TushareRespData, error) {
|
||||
/*
|
||||
StkFactorPro 获取股票技术面因子(专业版)
|
||||
|
||||
ts_code: 股票代码,支持多个,逗号分隔
|
||||
trade_date: 交易日期,格式:YYYYMMDD
|
||||
start_date: 开始日期,格式:YYYYMMDD
|
||||
end_date: 结束日期,格式:YYYYMMDD
|
||||
*/
|
||||
func (cli *TushareClient) StkFactorPro(ts_code, trade_date, start_date, end_date string) (*TushareRespData, error) {
|
||||
params := map[string]any{}
|
||||
if ts_code != "" {
|
||||
params["ts_code"] = ts_code
|
||||
}
|
||||
if tradeDate != "" {
|
||||
params["trade_date"] = tradeDate
|
||||
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{
|
||||
@@ -14,10 +28,52 @@ func (cli *TushareClient) StkFactorPro(ts_code string, tradeDate string) (*Tusha
|
||||
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"},
|
||||
{"ts_code": "股票代码"},
|
||||
{"trade_date": "交易日期"},
|
||||
{"open": "开盘价"},
|
||||
{"high": "最高价"},
|
||||
{"low": "最低价"},
|
||||
{"close": "收盘价"},
|
||||
{"pre_close": "昨收价"},
|
||||
{"change": "涨跌额"},
|
||||
{"pct_chg": "涨跌幅%"},
|
||||
{"vol": "成交量(手)"},
|
||||
{"amount": "成交额(千元)"},
|
||||
{"turnover_rate": "换手率(%)"},
|
||||
{"turnover_rate_f": "换手率(自由流通股)"},
|
||||
{"volume_ratio": "量比"},
|
||||
{"pe": "市盈率"},
|
||||
{"pe_ttm": "市盈率TTM"},
|
||||
{"pb": "市净率"},
|
||||
{"ps": "市销率"},
|
||||
{"ps_ttm": "市销率TTM"},
|
||||
{"dv_ratio": "股息率(%)"},
|
||||
{"dv_ttm": "股息率TTM(%)"},
|
||||
{"total_share": "总股本(万股)"},
|
||||
{"float_share": "流通股本(万股)"},
|
||||
{"free_share": "自由流通股本(万股)"},
|
||||
{"total_mv": "总市值(万元)"},
|
||||
{"circ_mv": "流通市值(万元)"},
|
||||
{"adj_factor": "复权因子"},
|
||||
{"ma_bfq_5": "MA5不复权"},
|
||||
{"ma_bfq_10": "MA10不复权"},
|
||||
{"ma_bfq_20": "MA20不复权"},
|
||||
{"ma_bfq_60": "MA60不复权"},
|
||||
{"ema_bfq_5": "EMA5不复权"},
|
||||
{"ema_bfq_10": "EMA10不复权"},
|
||||
{"ema_bfq_20": "EMA20不复权"},
|
||||
{"macd_bfq": "MACD不复权"},
|
||||
{"macd_dif_bfq": "MACD DIF不复权"},
|
||||
{"macd_dea_bfq": "MACD DEA不复权"},
|
||||
{"rsi_bfq_6": "RSI6不复权"},
|
||||
{"rsi_bfq_12": "RSI12不复权"},
|
||||
{"rsi_bfq_24": "RSI24不复权"},
|
||||
{"kdj_k_bfq": "KDJ-K不复权"},
|
||||
{"kdj_d_bfq": "KDJ-D不复权"},
|
||||
{"kdj_bfq": "KDJ-J不复权"},
|
||||
{"boll_upper_bfq": "BOLL上轨不复权"},
|
||||
{"boll_mid_bfq": "BOLL中轨不复权"},
|
||||
{"boll_lower_bfq": "BOLL下轨不复权"},
|
||||
}
|
||||
|
||||
return cli.Do(req, fields)
|
||||
|
||||
131
tushare/new.go
131
tushare/new.go
@@ -17,6 +17,7 @@ import (
|
||||
type TushareClient struct {
|
||||
Token string `json:"token"` // API 访问令牌
|
||||
BaseUrl string `json:"base_url"` // API 基础 URL
|
||||
client *http.Client
|
||||
}
|
||||
|
||||
// TushareReq Tushare API 请求结构
|
||||
@@ -53,9 +54,40 @@ func NewClient(token string) *TushareClient {
|
||||
return &TushareClient{
|
||||
Token: token,
|
||||
BaseUrl: "http://api.tushare.pro",
|
||||
client: &http.Client{Timeout: 30 * time.Second},
|
||||
}
|
||||
}
|
||||
|
||||
func (c *TushareClient) httpDo() *http.Client {
|
||||
if c.client != nil {
|
||||
return c.client
|
||||
}
|
||||
return &http.Client{Timeout: 30 * time.Second}
|
||||
}
|
||||
|
||||
// postTushare 发送 JSON 请求并解析为 TushareResp(不含业务码以外的处理)。
|
||||
func (c *TushareClient) postTushare(reqBytes []byte) (*TushareResp, error) {
|
||||
resp, err := c.httpDo().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)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
return &tushareResp, nil
|
||||
}
|
||||
|
||||
// Do 执行 Tushare API 请求
|
||||
// req: 请求参数,包含 API 名称、参数等
|
||||
// fieldsVals: 字段配置列表,每个元素是一个 map,key 为字段名,value 为字段中文描述
|
||||
@@ -69,8 +101,12 @@ func (c *TushareClient) Do(req TushareReq, fieldsVals []map[string]string) (*Tus
|
||||
}
|
||||
|
||||
// 提取字段名和对应的中文表头
|
||||
var fields []string
|
||||
var headers []string
|
||||
n := 0
|
||||
for _, setting := range fieldsVals {
|
||||
n += len(setting)
|
||||
}
|
||||
fields := make([]string, 0, n)
|
||||
headers := make([]string, 0, n)
|
||||
for _, setting := range fieldsVals {
|
||||
for key, value := range setting {
|
||||
fields = append(fields, key)
|
||||
@@ -79,40 +115,15 @@ func (c *TushareClient) Do(req TushareReq, fieldsVals []map[string]string) (*Tus
|
||||
}
|
||||
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))
|
||||
tushareResp, err := c.postTushare(reqBytes)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("#100 发送请求失败:%w", err)
|
||||
return nil, 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
|
||||
}
|
||||
@@ -125,12 +136,22 @@ func (c *TushareClient) Do(req TushareReq, fieldsVals []map[string]string) (*Tus
|
||||
// Params: 请求参数
|
||||
// 返回:响应数据和错误信息
|
||||
func (c *TushareClient) Exec(ApiName string, fields string, Params map[string]any) (*TushareRespData, error) {
|
||||
fieldsList := strings.Split(fields, ",")
|
||||
fields = strings.TrimSpace(fields)
|
||||
if fields == "" {
|
||||
return nil, fmt.Errorf("#100 字段列表不能为空")
|
||||
}
|
||||
raw := strings.Split(fields, ",")
|
||||
fieldsList := make([]string, 0, len(raw))
|
||||
for _, f := range raw {
|
||||
f = strings.TrimSpace(f)
|
||||
if f != "" {
|
||||
fieldsList = append(fieldsList, f)
|
||||
}
|
||||
}
|
||||
if len(fieldsList) == 0 {
|
||||
return nil, fmt.Errorf("#100 字段列表不能为空")
|
||||
}
|
||||
|
||||
// 构建请求体
|
||||
payload := TushareReq{
|
||||
APIName: ApiName,
|
||||
Token: c.Token,
|
||||
@@ -138,51 +159,25 @@ func (c *TushareClient) Exec(ApiName string, fields string, Params map[string]an
|
||||
Fields: fieldsList,
|
||||
}
|
||||
|
||||
// 序列化请求体为 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))
|
||||
tushareResp, err := c.postTushare(reqBytes)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("#100 发送请求失败:%w", err)
|
||||
return nil, 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)
|
||||
}
|
||||
|
||||
// 设置表头信息
|
||||
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
|
||||
return make([]map[string]any, 0)
|
||||
}
|
||||
|
||||
result := make([]map[string]any, 0, len(c.Items))
|
||||
for _, item := range c.Items {
|
||||
rowMap := make(map[string]any, len(c.Fields))
|
||||
for idx, fn := range c.Fields {
|
||||
@@ -209,11 +204,17 @@ func (c *TushareRespData) Output(title string) table.Writer {
|
||||
tw := table.NewWriter()
|
||||
tw.SetStyle(table.StyleLight)
|
||||
tw.SetTitle(title)
|
||||
if c == nil {
|
||||
return tw
|
||||
}
|
||||
|
||||
// 构建表头行
|
||||
headerRow := make(table.Row, 0, len(c.Headers))
|
||||
for idx, header := range c.Headers {
|
||||
headerRow = append(headerRow, header+"("+c.Fields[idx]+")")
|
||||
n := len(c.Fields)
|
||||
if nh := len(c.Headers); nh < n {
|
||||
n = nh
|
||||
}
|
||||
headerRow := make(table.Row, 0, n)
|
||||
for idx := 0; idx < n; idx++ {
|
||||
headerRow = append(headerRow, c.Headers[idx]+"("+c.Fields[idx]+")")
|
||||
}
|
||||
tw.AppendHeader(headerRow)
|
||||
|
||||
|
||||
@@ -2,6 +2,8 @@ package tushare
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"git.apinb.com/quant/qsdk/conv"
|
||||
)
|
||||
|
||||
/*
|
||||
@@ -49,7 +51,10 @@ func (cli *TushareClient) ReturnLastTradeDay() string {
|
||||
return ""
|
||||
}
|
||||
cal := result.Map()
|
||||
return cal[0]["cal_date"].(string)
|
||||
if len(cal) == 0 {
|
||||
return ""
|
||||
}
|
||||
return conv.AnyToString(cal[0]["cal_date"])
|
||||
}
|
||||
|
||||
/*
|
||||
|
||||
Reference in New Issue
Block a user