package tushare import ( "context" "fmt" "strings" "time" "market-data-service/adapter" ) // Adapter Tushare数据源适配器 type Adapter struct { client *Client config map[string]string } // NewAdapter 创建Tushare适配器 func NewAdapter() *Adapter { return &Adapter{} } // Connect 建立连接 func (a *Adapter) Connect(config map[string]string) error { token, ok := config["token"] if !ok || token == "" { return fmt.Errorf("tushare token is required") } a.client = NewClient(token) if baseURL, ok := config["base_url"]; ok && baseURL != "" { a.client.SetBaseURL(baseURL) } a.config = config return nil } // SubscribeTicks 订阅实时Tick(Tushare不支持实时推送,返回错误) func (a *Adapter) SubscribeTicks(symbols []string, callback adapter.TickCallback) error { return fmt.Errorf("tushare does not support real-time tick subscription") } // FetchKLines 拉取历史K线 func (a *Adapter) FetchKLines(symbol, start, end, freq string) ([]adapter.KLineData, error) { // 判断是股票还是期货 if strings.Contains(symbol, ".SH") || strings.Contains(symbol, ".SZ") || strings.Contains(symbol, ".BJ") { return a.fetchStockKLines(symbol, start, end, freq) } return a.fetchFuturesKLines(symbol, start, end, freq) } // fetchStockKLines 获取股票K线 func (a *Adapter) fetchStockKLines(symbol, start, end, freq string) ([]adapter.KLineData, error) { // 转换symbol格式: 000001.SZ -> 000001.SZ tsCode := symbol switch freq { case "1d", "", "day": return a.fetchStockDaily(tsCode, start, end) case "1m", "5m", "15m", "30m", "60m": return a.fetchStockMinute(tsCode, start, end, freq) default: return nil, fmt.Errorf("unsupported frequency: %s", freq) } } // fetchStockDaily 获取股票日线 func (a *Adapter) fetchStockDaily(tsCode, start, end string) ([]adapter.KLineData, error) { data, err := a.client.GetStockDaily(tsCode, start, end) if err != nil { return nil, err } result := make([]adapter.KLineData, len(data)) for i, d := range data { tradeDate, _ := time.Parse("20060102", d.TradeDate) result[i] = adapter.KLineData{ Symbol: d.TSCode, Time: tradeDate.Unix(), Open: d.Open, High: d.High, Low: d.Low, Close: d.Close, Volume: int64(d.Volume * 100), // 手 -> 股 Amount: d.Amount * 1000, // 千元 -> 元 } } return result, nil } // fetchStockMinute 获取股票分钟线 func (a *Adapter) fetchStockMinute(tsCode, start, end, freq string) ([]adapter.KLineData, error) { // 去掉'm'后缀 freqNum := strings.TrimSuffix(freq, "m") data, err := a.client.GetStockMinute(tsCode, start, end, freqNum) if err != nil { return nil, err } result := make([]adapter.KLineData, len(data)) for i, d := range data { // 解析时间格式: 2025-03-07 09:31:00 tradeTime, _ := time.Parse("2006-01-02 15:04:05", d.TradeTime) result[i] = adapter.KLineData{ Symbol: d.TSCode, Time: tradeTime.Unix(), Open: d.Open, High: d.High, Low: d.Low, Close: d.Close, Volume: int64(d.Volume * 100), Amount: d.Amount * 1000, } } return result, nil } // fetchFuturesKLines 获取期货K线 func (a *Adapter) fetchFuturesKLines(symbol, start, end, freq string) ([]adapter.KLineData, error) { switch freq { case "1d", "", "day": return a.fetchFuturesDaily(symbol, start, end) case "1m", "5m", "15m", "30m", "60m": return a.fetchFuturesMinute(symbol, start, end, freq) default: return nil, fmt.Errorf("unsupported frequency: %s", freq) } } // fetchFuturesDaily 获取期货日线 func (a *Adapter) fetchFuturesDaily(tsCode, start, end string) ([]adapter.KLineData, error) { data, err := a.client.GetFuturesDaily(tsCode, start, end) if err != nil { return nil, err } result := make([]adapter.KLineData, len(data)) for i, d := range data { tradeDate, _ := time.Parse("20060102", d.TradeDate) result[i] = adapter.KLineData{ Symbol: d.TSCode, Time: tradeDate.Unix(), Open: d.Open, High: d.High, Low: d.Low, Close: d.Close, Volume: int64(d.Volume), Amount: d.Amount * 10000, // 万元 -> 元 OpenInterest: int64(d.OpenInterest), } } return result, nil } // fetchFuturesMinute 获取期货分钟线 func (a *Adapter) fetchFuturesMinute(tsCode, start, end, freq string) ([]adapter.KLineData, error) { freqNum := strings.TrimSuffix(freq, "m") data, err := a.client.GetFuturesMinute(tsCode, start, end, freqNum) if err != nil { return nil, err } result := make([]adapter.KLineData, len(data)) for i, d := range data { tradeTime, _ := time.Parse("2006-01-02 15:04:05", d.TradeTime) result[i] = adapter.KLineData{ Symbol: d.TSCode, Time: tradeTime.Unix(), Open: d.Open, High: d.High, Low: d.Low, Close: d.Close, Volume: int64(d.Volume), Amount: d.Amount * 10000, OpenInterest: int64(d.OpenInterest), } } return result, nil } // FetchSymbols 获取标的列表 func (a *Adapter) FetchSymbols(assetType string) ([]adapter.SymbolInfo, error) { switch assetType { case "stock": return a.fetchStockSymbols() case "futures": return a.fetchFuturesSymbols() default: return nil, fmt.Errorf("unsupported asset type: %s", assetType) } } // fetchStockSymbols 获取股票列表 func (a *Adapter) fetchStockSymbols() ([]adapter.SymbolInfo, error) { data, err := a.client.GetStockBasic() if err != nil { return nil, err } result := make([]adapter.SymbolInfo, 0, len(data)) for _, d := range data { // 只返回上市状态的 if d.ListStatus != "L" { continue } result = append(result, adapter.SymbolInfo{ SymbolID: d.TSCode, Name: d.Name, Exchange: d.Exchange, }) } return result, nil } // fetchFuturesSymbols 获取期货列表 func (a *Adapter) fetchFuturesSymbols() ([]adapter.SymbolInfo, error) { data, err := a.client.GetFuturesBasic("") if err != nil { return nil, err } result := make([]adapter.SymbolInfo, len(data)) for i, d := range data { result[i] = adapter.SymbolInfo{ SymbolID: d.TSCode, Name: d.Name, Exchange: d.Exchange, } } return result, nil } // FetchTradingCalendar 获取交易日历 func (a *Adapter) FetchTradingCalendar(exchange, start, end string) ([]adapter.TradeCalData, error) { // Tushare交易所代码映射 exchangeMap := map[string]string{ "SH": "SSE", "SZ": "SZSE", "SHFE": "SHFE", "DCE": "DCE", "CZCE": "CZCE", "CFFEX": "CFFEX", "INE": "INE", } tsExchange, ok := exchangeMap[exchange] if !ok { tsExchange = "SSE" } data, err := a.client.GetTradeCal(tsExchange, start, end, -1) if err != nil { return nil, err } result := make([]adapter.TradeCalData, len(data)) for i, d := range data { calDate, _ := time.Parse("20060102", d.CalDate) result[i] = adapter.TradeCalData{ Date: calDate, IsTradingDay: d.IsOpen == 1, } } return result, nil } // HealthCheck 健康检查 func (a *Adapter) HealthCheck() error { if a.client == nil { return fmt.Errorf("client not initialized") } // 尝试获取交易日历作为健康检查 now := time.Now() start := now.AddDate(0, 0, -7).Format("20060102") end := now.Format("20060102") _, err := a.client.GetTradeCal("SSE", start, end, 1) return err } // Close 关闭连接 func (a *Adapter) Close() error { // Tushare是HTTP接口,无需关闭 return nil } // GetClient 获取底层客户端(用于直接调用Tushare API) func (a *Adapter) GetClient() *Client { return a.client }