package tushare import ( "bytes" "encoding/json" "fmt" "io" "net/http" "time" ) const ( DefaultBaseURL = "http://api.tushare.pro" ) // Client Tushare API客户端 type Client struct { token string baseURL string client *http.Client } // NewClient 创建Tushare客户端 func NewClient(token string) *Client { return &Client{ token: token, baseURL: DefaultBaseURL, client: &http.Client{Timeout: 30 * time.Second}, } } // SetBaseURL 设置基础URL(用于测试) func (c *Client) SetBaseURL(baseURL string) { c.baseURL = baseURL } // Request 通用请求结构 type Request struct { APIName string `json:"api_name"` Token string `json:"token"` Params map[string]interface{} `json:"params"` Fields string `json:"fields,omitempty"` } // Response 通用响应结构 type Response struct { Code int `json:"code"` Msg string `json:"msg"` Data *Data `json:"data"` } // Data 响应数据 type Data struct { Fields []string `json:"fields"` Items [][]interface{} `json:"items"` } // Error 实现error接口 func (r *Response) Error() string { return fmt.Sprintf("tushare error: code=%d, msg=%s", r.Code, r.Msg) } // IsSuccess 判断是否成功 func (r *Response) IsSuccess() bool { return r.Code == 0 } // Query 执行查询 func (c *Client) Query(apiName string, params map[string]interface{}, fields string) (*Response, error) { reqBody := Request{ APIName: apiName, Token: c.token, Params: params, Fields: fields, } jsonData, err := json.Marshal(reqBody) if err != nil { return nil, fmt.Errorf("marshal request failed: %w", err) } resp, err := c.client.Post(c.baseURL, "application/json", bytes.NewBuffer(jsonData)) if err != nil { return nil, fmt.Errorf("http request failed: %w", err) } defer resp.Body.Close() body, err := io.ReadAll(resp.Body) if err != nil { return nil, fmt.Errorf("read response body failed: %w", err) } var result Response if err := json.Unmarshal(body, &result); err != nil { return nil, fmt.Errorf("unmarshal response failed: %w", err) } if !result.IsSuccess() { return &result, result } return &result, nil } // ToMapList 将Data转换为map列表 func (d *Data) ToMapList() []map[string]interface{} { if d == nil || len(d.Fields) == 0 || len(d.Items) == 0 { return nil } result := make([]map[string]interface{}, len(d.Items)) for i, item := range d.Items { m := make(map[string]interface{}) for j, field := range d.Fields { if j < len(item) { m[field] = item[j] } } result[i] = m } return result } // StockDaily 股票日线数据 type StockDaily struct { TSCode string `json:"ts_code"` // 股票代码 TradeDate string `json:"trade_date"` // 交易日期 Open float64 `json:"open"` // 开盘价 High float64 `json:"high"` // 最高价 Low float64 `json:"low"` // 最低价 Close float64 `json:"close"` // 收盘价 PreClose float64 `json:"pre_close"` // 昨收价 Change float64 `json:"change"` // 涨跌额 PctChange float64 `json:"pct_chg"` // 涨跌幅 Volume float64 `json:"vol"` // 成交量(手) Amount float64 `json:"amount"` // 成交额(千元) } // GetStockDaily 获取股票日线数据 func (c *Client) GetStockDaily(tsCode, startDate, endDate string) ([]StockDaily, error) { params := map[string]interface{}{ "ts_code": tsCode, "start_date": startDate, "end_date": endDate, } resp, err := c.Query("daily", params, "") if err != nil { return nil, err } items := resp.Data.ToMapList() result := make([]StockDaily, len(items)) for i, item := range items { result[i] = StockDaily{ TSCode: getString(item, "ts_code"), TradeDate: getString(item, "trade_date"), Open: getFloat64(item, "open"), High: getFloat64(item, "high"), Low: getFloat64(item, "low"), Close: getFloat64(item, "close"), PreClose: getFloat64(item, "pre_close"), Change: getFloat64(item, "change"), PctChange: getFloat64(item, "pct_chg"), Volume: getFloat64(item, "vol"), Amount: getFloat64(item, "amount"), } } return result, nil } // StockMinute 股票分钟线数据 type StockMinute struct { TSCode string `json:"ts_code"` TradeTime string `json:"trade_time"` Open float64 `json:"open"` High float64 `json:"high"` Low float64 `json:"low"` Close float64 `json:"close"` Volume float64 `json:"vol"` Amount float64 `json:"amount"` } // GetStockMinute 获取股票分钟线数据 func (c *Client) GetStockMinute(tsCode, startDate, endDate string, freq string) ([]StockMinute, error) { apiName := "stk_mins" // 默认1分钟 switch freq { case "5": apiName = "stk_mins5" case "15": apiName = "stk_mins15" case "30": apiName = "stk_mins30" case "60": apiName = "stk_mins60" } params := map[string]interface{}{ "ts_code": tsCode, "start_date": startDate, "end_date": endDate, } resp, err := c.Query(apiName, params, "") if err != nil { return nil, err } items := resp.Data.ToMapList() result := make([]StockMinute, len(items)) for i, item := range items { result[i] = StockMinute{ TSCode: getString(item, "ts_code"), TradeTime: getString(item, "trade_time"), Open: getFloat64(item, "open"), High: getFloat64(item, "high"), Low: getFloat64(item, "low"), Close: getFloat64(item, "close"), Volume: getFloat64(item, "vol"), Amount: getFloat64(item, "amount"), } } return result, nil } // FuturesDaily 期货日线数据 type FuturesDaily struct { TSCode string `json:"ts_code"` TradeDate string `json:"trade_date"` Open float64 `json:"open"` High float64 `json:"high"` Low float64 `json:"low"` Close float64 `json:"close"` PreClose float64 `json:"pre_close"` Change float64 `json:"change"` PctChange float64 `json:"pct_chg"` Volume float64 `json:"vol"` Amount float64 `json:"amount"` OpenInterest float64 `json:"oi"` OiChange float64 `json:"oi_chg"` } // GetFuturesDaily 获取期货日线数据 func (c *Client) GetFuturesDaily(tsCode, startDate, endDate string) ([]FuturesDaily, error) { params := map[string]interface{}{ "ts_code": tsCode, "start_date": startDate, "end_date": endDate, } resp, err := c.Query("fut_daily", params, "") if err != nil { return nil, err } items := resp.Data.ToMapList() result := make([]FuturesDaily, len(items)) for i, item := range items { result[i] = FuturesDaily{ TSCode: getString(item, "ts_code"), TradeDate: getString(item, "trade_date"), Open: getFloat64(item, "open"), High: getFloat64(item, "high"), Low: getFloat64(item, "low"), Close: getFloat64(item, "close"), PreClose: getFloat64(item, "pre_close"), Change: getFloat64(item, "change"), PctChange: getFloat64(item, "pct_chg"), Volume: getFloat64(item, "vol"), Amount: getFloat64(item, "amount"), OpenInterest: getFloat64(item, "oi"), OiChange: getFloat64(item, "oi_chg"), } } return result, nil } // FuturesMinute 期货分钟线数据 type FuturesMinute struct { TSCode string `json:"ts_code"` TradeTime string `json:"trade_time"` Open float64 `json:"open"` High float64 `json:"high"` Low float64 `json:"low"` Close float64 `json:"close"` Volume float64 `json:"vol"` Amount float64 `json:"amount"` OpenInterest float64 `json:"oi"` } // GetFuturesMinute 获取期货分钟线数据 func (c *Client) GetFuturesMinute(tsCode, startDate, endDate string, freq string) ([]FuturesMinute, error) { apiName := "fut_mins" // 默认1分钟 switch freq { case "5": apiName = "fut_mins5" case "15": apiName = "fut_mins15" case "30": apiName = "fut_mins30" case "60": apiName = "fut_mins60" } params := map[string]interface{}{ "ts_code": tsCode, "start_date": startDate, "end_date": endDate, } resp, err := c.Query(apiName, params, "") if err != nil { return nil, err } items := resp.Data.ToMapList() result := make([]FuturesMinute, len(items)) for i, item := range items { result[i] = FuturesMinute{ TSCode: getString(item, "ts_code"), TradeTime: getString(item, "trade_time"), Open: getFloat64(item, "open"), High: getFloat64(item, "high"), Low: getFloat64(item, "low"), Close: getFloat64(item, "close"), Volume: getFloat64(item, "vol"), Amount: getFloat64(item, "amount"), OpenInterest: getFloat64(item, "oi"), } } return result, nil } // StockBasic 股票基础信息 type StockBasic struct { TSCode string `json:"ts_code"` Symbol string `json:"symbol"` Name string `json:"name"` Area string `json:"area"` Industry string `json:"industry"` FullName string `json:"fullname"` EnName string `json:"enname"` CNName string `json:"cnspell"` Market string `json:"market"` Exchange string `json:"exchange"` CurrType string `json:"curr_type"` ListStatus string `json:"list_status"` ListDate string `json:"list_date"` DelistDate string `json:"delist_date"` IsHS string `json:"is_hs"` } // GetStockBasic 获取股票基础信息 func (c *Client) GetStockBasic() ([]StockBasic, error) { resp, err := c.Query("stock_basic", map[string]interface{}{"list_status": "L"}, "") if err != nil { return nil, err } items := resp.Data.ToMapList() result := make([]StockBasic, len(items)) for i, item := range items { result[i] = StockBasic{ TSCode: getString(item, "ts_code"), Symbol: getString(item, "symbol"), Name: getString(item, "name"), Area: getString(item, "area"), Industry: getString(item, "industry"), FullName: getString(item, "fullname"), EnName: getString(item, "enname"), CNName: getString(item, "cnspell"), Market: getString(item, "market"), Exchange: getString(item, "exchange"), CurrType: getString(item, "curr_type"), ListStatus: getString(item, "list_status"), ListDate: getString(item, "list_date"), DelistDate: getString(item, "delist_date"), IsHS: getString(item, "is_hs"), } } return result, nil } // FuturesBasic 期货合约基础信息 type FuturesBasic struct { TSCode string `json:"ts_code"` Symbol string `json:"symbol"` Name string `json:"name"` Exchange string `json:"exchange"` FutCode string `json:"fut_code"` Multiplier float64 `json:"multiplier"` TradeUnit string `json:"trade_unit"` PerUnit float64 `json:"per_unit"` DeliveryDate string `json:"delivery_date"` ListDate string `json:"list_date"` DelistDate string `json:"delist_date"` } // GetFuturesBasic 获取期货合约基础信息 func (c *Client) GetFuturesBasic(exchange string) ([]FuturesBasic, error) { params := map[string]interface{}{} if exchange != "" { params["exchange"] = exchange } resp, err := c.Query("fut_basic", params, "") if err != nil { return nil, err } items := resp.Data.ToMapList() result := make([]FuturesBasic, len(items)) for i, item := range items { result[i] = FuturesBasic{ TSCode: getString(item, "ts_code"), Symbol: getString(item, "symbol"), Name: getString(item, "name"), Exchange: getString(item, "exchange"), FutCode: getString(item, "fut_code"), Multiplier: getFloat64(item, "multiplier"), TradeUnit: getString(item, "trade_unit"), PerUnit: getFloat64(item, "per_unit"), DeliveryDate: getString(item, "delivery_date"), ListDate: getString(item, "list_date"), DelistDate: getString(item, "delist_date"), } } return result, nil } // TradeCal 交易日历 type TradeCal struct { Exchange string `json:"exchange"` CalDate string `json:"cal_date"` IsOpen int `json:"is_open"` PretradeDate string `json:"pretrade_date"` } // GetTradeCal 获取交易日历 func (c *Client) GetTradeCal(exchange, startDate, endDate string, isOpen int) ([]TradeCal, error) { params := map[string]interface{}{ "exchange": exchange, "start_date": startDate, "end_date": endDate, } if isOpen >= 0 { params["is_open"] = isOpen } resp, err := c.Query("trade_cal", params, "") if err != nil { return nil, err } items := resp.Data.ToMapList() result := make([]TradeCal, len(items)) for i, item := range items { result[i] = TradeCal{ Exchange: getString(item, "exchange"), CalDate: getString(item, "cal_date"), IsOpen: getInt(item, "is_open"), PretradeDate: getString(item, "pretrade_date"), } } return result, nil } // FuturesHolding 期货持仓排名 type FuturesHolding struct { TSCode string `json:"ts_code"` TradeDate string `json:"trade_date"` Symbol string `json:"symbol"` Broker string `json:"broker"` Vol int64 `json:"vol"` VolChange int64 `json:"vol_chg"` LongHld int64 `json:"long_hld"` LongChange int64 `json:"long_chg"` ShortHld int64 `json:"short_hld"` ShortChange int64 `json:"short_chg"` } // 辅助函数 func getString(m map[string]interface{}, key string) string { if v, ok := m[key]; ok && v != nil { switch val := v.(type) { case string: return val case []byte: return string(val) default: return fmt.Sprintf("%v", v) } } return "" } func getFloat64(m map[string]interface{}, key string) float64 { if v, ok := m[key]; ok && v != nil { switch val := v.(type) { case float64: return val case int: return float64(val) case int64: return float64(val) case string: var f float64 fmt.Sscanf(val, "%f", &f) return f } } return 0 } func getInt(m map[string]interface{}, key string) int { if v, ok := m[key]; ok && v != nil { switch val := v.(type) { case int: return val case int64: return int(val) case float64: return int(val) case string: var i int fmt.Sscanf(val, "%d", &i) return i } } return 0 }