| Crates.io | tushare-api |
| lib.rs | tushare-api |
| version | 1.2.7 |
| created_at | 2025-07-29 03:39:11.319538+00 |
| updated_at | 2026-01-21 16:04:05.668347+00 |
| description | A comprehensive Rust client library for accessing Tushare financial data APIs |
| homepage | https://github.com/rock117/tushare-api |
| repository | https://github.com/rock117/tushare-api |
| max_upload_size | |
| id | 1771885 |
| size | 279,000 |
一个用于访问 Tushare 金融数据 API的Rust 客户端库。该库提供类型安全的异步访问所有 Tushare 数据接口,并提供高级功能,支持对API级别进行限流和重试。
rust_decimal、chrono、uuid 和 bigdecimal在 Cargo.toml 中添加:
[dependencies]
tushare-api = "1.2.7"
# 可选:启用第三方类型支持
# tushare-api = { version = "1.2.7", features = ["rust_decimal", "chrono"] }
# 或启用所有第三方类型
# tushare-api = { version = "1.2.7", features = ["all_types"] }
# 可选:启用 tracing 支持
# tushare-api = { version = "1.2.7", features = ["tracing"] }
use tushare_api::TushareClient;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// 设置环境变量
std::env::set_var("TUSHARE_TOKEN", "your_token_here");
// 创建客户端
let client = TushareClient::from_env()?;
// 调用 API
let response = client
.call_api(
r#"
{
"api_name": "stock_basic",
"params": { "list_status": "L" },
"fields": ["ts_code", "name", "industry"]
}
"#,
)
.await?;
if let Some(data) = response.data {
println!("获取到 {} 条记录", data.items.len());
}
Ok(())
}
// 基础导入
use tushare_api::{TushareClient, TushareRequest, TushareResponse, Api, TushareResult};
// 便捷宏
use tushare_api::{params, fields, request};
// 日志配置
use tushare_api::{LogLevel, LogConfig, Logger};
// HTTP 客户端配置和连接池设置
use tushare_api::{TushareClientBuilder, HttpClientConfig};
// 自定义超时
use std::time::Duration;
库提供多种创建客户端的方式:
// 首先设置环境变量
std::env::set_var("TUSHARE_TOKEN", "your_token_here");
// 使用默认超时设置
let client = TushareClient::from_env()?;
// 使用自定义超时设置
let client = TushareClient::from_env_with_timeout(
Duration::from_secs(10), // 连接超时 10 秒
Duration::from_secs(60) // 请求超时 60 秒
)?;
// 使用默认超时设置
let client = TushareClient::new("your_token_here");
// 使用自定义超时设置
let client = TushareClient::with_timeout(
"your_token_here",
Duration::from_secs(5), // 连接超时 5 秒
Duration::from_secs(60) // 请求超时 60 秒
);
// 基础构建器,包含超时和日志
let client = TushareClient::builder()
.with_token("your_token_here")
.with_connect_timeout(Duration::from_secs(10))
.with_timeout(Duration::from_secs(60))
.with_log_level(LogLevel::Debug)
.log_requests(true)
.log_responses(false)
.log_sensitive_data(false)
.log_performance(true)
.build()?;
// 高级构建器,包含连接池设置
let client = TushareClient::builder()
.with_token("your_token_here")
.with_connect_timeout(Duration::from_secs(5))
.with_timeout(Duration::from_secs(60))
.with_pool_max_idle_per_host(20) // 每个主机最多 20 个空闲连接
.with_pool_idle_timeout(Duration::from_secs(60)) // 连接保持 60 秒
.with_log_level(LogLevel::Info)
.build()?;
// 使用 HttpClientConfig 进行高级 HTTP 设置
let http_config = HttpClientConfig::new()
.with_connect_timeout(Duration::from_secs(3))
.with_timeout(Duration::from_secs(30))
.with_pool_max_idle_per_host(15)
.with_pool_idle_timeout(Duration::from_secs(45));
let client = TushareClient::builder()
.with_token("your_token_here")
.with_http_config(http_config)
.with_log_level(LogLevel::Debug)
.build()?;
TushareClientEx 是对 TushareClient 的包装,用于提供额外能力(如按 API 的最小间隔限流、失败重试等)。
use std::time::Duration;
use tushare_api::{Api, TushareClient, TushareClientEx, TushareEntityList, request};
use tushare_api::client_ex::RetryConfig;
use tushare_api::DeriveFromTushareData;
#[derive(Debug, Clone, DeriveFromTushareData)]
struct Stock {
ts_code: String,
name: String,
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let inner = TushareClient::from_env()?;
let client = TushareClientEx::new(inner)
.with_api_min_interval(Api::Daily, Duration::from_secs(10))
.with_retry_config(RetryConfig {
max_retries: 3,
base_delay: Duration::from_millis(200),
max_delay: Duration::from_secs(5),
});
let req = request!(Api::StockBasic, {
"list_status" => "L"
}, [
"ts_code", "name"
]);
let stocks: TushareEntityList<Stock> = client.call_api_as(&req).await?;
println!("{}", stocks.len());
Ok(())
}
use tushare_api::{request, Api};
// 带参数和字段的单个 API 调用
let response = client.call_api(request!(Api::StockBasic, {
"list_status" => "L",
"exchange" => "SSE"
}, [
"ts_code", "name", "industry", "area"
])).await?;
// 无参数的 API 调用
let response = client.call_api(request!(Api::TradeCal, {}, [
"exchange", "cal_date", "is_open"
])).await?;
// 无字段的 API 调用(获取所有字段)
let response = client.call_api(request!(Api::StockBasic, {
"list_status" => "L"
}, [])).await?;
use tushare_api::{TushareRequest, Api, params, fields};
let request = TushareRequest {
api_name: Api::StockBasic,
params: params!("list_status" => "L", "exchange" => "SSE"),
fields: fields!("ts_code", "name", "industry"),
};
let response = client.call_api(request).await?;
use std::collections::HashMap;
let mut params = HashMap::new();
params.insert("list_status".to_string(), "L".to_string());
let request = TushareRequest {
api_name: Api::StockBasic,
params,
fields: vec!["ts_code".to_string(), "name".to_string()],
};
let response = client.call_api(request).await?;
// 直接传 JSON 字符串(适合快速调试/复制粘贴 API 请求)
let response = client
.call_api(
r#"
{
"api_name": "stock_basic",
"params": { "list_status": "L", "exchange": "SSE" },
"fields": ["ts_code", "name", "industry", "area"]
}
"#,
)
.await?;
TryInto<TushareRequest> 的类型都可以作为参数call_api 的签名是泛型的:
pub async fn call_api<T>(&self, request: T) -> TushareResult<TushareResponse>
where
T: TryInto<TushareRequest>,
<T as TryInto<TushareRequest>>::Error: Into<TushareError>,
所以你可以直接传入:
// 1) 直接传 TushareRequest
let req = TushareRequest {
api_name: Api::StockBasic,
params: params!("list_status" => "L"),
fields: fields!("ts_code", "name"),
};
let response = client.call_api(req).await?;
// 2) 直接传 &str
let response = client.call_api(r#"{
"api_name": "stock_basic",
"params": { "list_status": "L" },
"fields": ["ts_code", "name"]
}"#).await?;
// 3) 直接传 String
let json = r#"{
"api_name": "stock_basic",
"params": { "list_status": "L" },
"fields": ["ts_code", "name"]
}"#.to_string();
let response = client.call_api(json).await?;
该库提供了强大的过程宏,可以自动将 Tushare API 响应转换为强类型的 Rust 结构体,无需手动解析。
use tushare_api::{TushareClient, Api, request, TushareEntityList};
use tushare_api::DeriveFromTushareData;
// 使用自动转换定义您的结构体
#[derive(Debug, Clone, DeriveFromTushareData)]
pub struct Stock {
pub ts_code: String,
pub symbol: String,
pub name: String,
pub area: Option<String>,
pub industry: Option<String>,
pub market: String,
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let client = TushareClient::from_env()?;
// 使用 call_api_as 进行直接转换到 TushareEntityList<Stock>
let stocks: TushareEntityList<Stock> = client.call_api_as(request!(Api::StockBasic, {
"list_status" => "L",
"exchange" => "SSE"
}, [
"ts_code", "symbol", "name", "area", "industry", "market"
])).await?;
// 直接访问数据
println!("找到 {} 只股票:", stocks.len());
for stock in stocks.iter().take(5) {
println!(" {}: {} ({})", stock.ts_code, stock.name, stock.market);
}
// 访问分页信息
println!("当前页面: {} 条记录", stocks.len());
println!("总记录数: {}", stocks.count());
println!("是否还有更多页面: {}", stocks.has_more());
Ok(())
}
use tushare_api::DeriveFromTushareData;
// 带字段映射和可选字段的高级结构体
#[derive(Debug, Clone, DeriveFromTushareData)]
pub struct StockInfo {
pub ts_code: String,
// 将 API 字段 "symbol" 映射到结构体字段 "stock_symbol"
#[tushare(field = "symbol")]
pub stock_symbol: String,
pub name: String,
// 可选字段会自动处理
pub area: Option<String>,
pub industry: Option<String>,
// 跳过 API 响应中不存在的字段
#[tushare(skip)]
pub calculated_value: f64,
}
// 实现 Default 以便使用
impl Default for StockInfo {
fn default() -> Self {
Self {
ts_code: String::new(),
stock_symbol: String::new(),
name: String::new(),
area: None,
industry: None,
calculated_value: 0.0,
}
}
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let client = TushareClient::from_env()?;
let stock_info: TushareEntityList<StockInfo> = client.call_api_as(request!(Api::StockBasic, {
"list_status" => "L"
}, [
"ts_code", "symbol", "name", "area", "industry"
])).await?;
for info in stock_info.iter().take(3) {
println!("股票: {} ({}) - 行业: {:?}",
info.name, info.stock_symbol, info.industry);
}
Ok(())
}
当您使用新的泛型分页容器时,您会得到一个清晰、类型安全的接口:
// 您的原始结构体
#[derive(Debug, Clone, DeriveFromTushareData)]
pub struct Stock {
pub ts_code: String,
pub name: String,
pub area: Option<String>,
}
// 使用泛型 TushareEntityList<T> 容器:
// TushareEntityList<Stock> {
// pub items: Vec<Stock>, // 您的数据项
// pub has_more: bool, // 分页:是否还有更多页面?
// pub count: i64, // 分页:总记录数
// }
当您调用:
let stocks: TushareEntityList<Stock> = client.call_api_as(request).await?;
// 或者
let stocks = client.call_api_as::<Stock>(request).await?;
您会得到一个 TushareEntityList<Stock> 结构体,包含:
items - Vec<Stock> 包含实际转换后的数据has_more - bool 表示是否还有更多页面可获取count - i64 显示可用的总记录数以及这些自动生成的方法:
stocks.len() - 当前页面的项目数量stocks.is_empty() - 当前页面是否为空stocks.items() - 获取项目切片stocks.has_more() - 检查是否还有更多页面stocks.count() - 获取总记录数stocks.iter() - 遍历项目(通过 Deref)for stock in &stocks { ... } - 直接迭代支持TushareEntityList<T> 容器提供内置分页支持,具有清晰直观的接口:
items: Vec<T> - 实际的数据项has_more: bool - 是否还有更多页面可用count: i64 - 总记录数use tushare_api::{TushareClient, Api, request, TushareEntityList};
use tushare_api::DeriveFromTushareData;
#[derive(Debug, Clone, DeriveFromTushareData)]
pub struct Stock {
pub ts_code: String,
pub name: String,
pub area: Option<String>,
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let client = TushareClient::from_env()?;
// 获取分页结果
let stocks: TushareEntityList<Stock> = client.call_api_as(request!(Api::StockBasic, {
"list_status" => "L",
"limit" => "100",
"offset" => "0"
}, [
"ts_code", "name", "area"
])).await?;
// 访问分页信息
println!("当前页面: {} 只股票", stocks.len());
println!("总可用数量: {} 只股票", stocks.count());
println!("是否还有更多页面: {}", stocks.has_more());
// 遍历当前页面的项目
for stock in &stocks {
println!("{}: {} ({})",
stock.ts_code,
stock.name,
stock.area.as_deref().unwrap_or("未知"));
}
// 直接访问项目
let first_stock = &stocks.items()[0];
println!("第一只股票: {}", first_stock.name);
Ok(())
}
过程宏支持以下 Rust 类型:
String - 必需的字符串字段Option<String> - 可选的字符串字段f64 - 必需的浮点数Option<f64> - 可选的浮点数i64 - 必需的整数Option<i64> - 可选的整数bool - 必需的布尔值Option<bool> - 可选的布尔值库支持使用 #[tushare(date_format = "...")] 属性进行自定义日期格式解析。这在处理返回非标准日期格式的 API 时特别有用。
use tushare_api::{TushareClient, Api, request, TushareEntityList, DeriveFromTushareData};
#[derive(Debug, Clone, DeriveFromTushareData)]
pub struct CustomDateFormats {
#[tushare(field = "ts_code")]
pub stock_code: String,
// 标准日期格式(自动检测:YYYYMMDD、YYYY-MM-DD 等)
#[tushare(field = "trade_date")]
pub trade_date: chrono::NaiveDate,
// 欧洲日期格式:DD/MM/YYYY
#[tushare(field = "european_date", date_format = "%d/%m/%Y")]
pub european_date: chrono::NaiveDate,
// 美国日期格式:MM-DD-YYYY
#[tushare(field = "us_date", date_format = "%m-%d-%Y")]
pub us_date: chrono::NaiveDate,
// 德国日期格式:DD.MM.YYYY
#[tushare(field = "german_date", date_format = "%d.%m.%Y")]
pub german_date: Option<chrono::NaiveDate>,
// 自定义日期时间格式:YYYY/MM/DD HH:MM
#[tushare(field = "custom_datetime", date_format = "%Y/%m/%d %H:%M")]
pub custom_datetime: chrono::NaiveDateTime,
// 中文日期格式:YYYY年MM月DD日
#[tushare(field = "chinese_date", date_format = "%Y年%m月%d日")]
pub chinese_date: Option<chrono::NaiveDate>,
// UTC 日期时间格式:YYYY-MM-DD HH:MM:SS +ZZZZ
#[tushare(field = "utc_datetime", date_format = "%Y-%m-%d %H:%M:%S %z")]
pub utc_datetime: chrono::DateTime<chrono::Utc>,
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let client = TushareClient::from_env()?;
// API 调用示例(注意:实际 API 可能不返回这些确切格式)
let data: TushareEntityList<CustomDateFormats> = client.call_api_as(request!(
Api::StockBasic, {
"list_status" => "L",
"limit" => "10"
}, [
"ts_code", "trade_date", "european_date", "us_date",
"german_date", "custom_datetime", "chinese_date", "utc_datetime"
]
)).await?;
for record in data.iter() {
println!("股票: {} - 交易日期: {}", record.stock_code, record.trade_date);
println!(" 欧洲格式: {}", record.european_date);
println!(" 美国格式: {}", record.us_date);
println!(" 德国格式: {:?}", record.german_date);
println!(" 自定义日期时间: {:?}", record.custom_datetime);
println!(" 中文格式: {:?}", record.chinese_date);
println!(" UTC 时间: {}", record.utc_datetime);
println!("---");
}
Ok(())
}
| 格式字符串 | 示例输入 | 说明 |
|---|---|---|
"%Y-%m-%d" |
"2024-03-15" |
ISO 日期格式 |
"%d/%m/%Y" |
"15/03/2024" |
欧洲格式 |
"%m-%d-%Y" |
"03-15-2024" |
美国格式 |
"%d.%m.%Y" |
"15.03.2024" |
德国格式 |
"%Y年%m月%d日" |
"2024年03月15日" |
中文格式 |
"%Y%m%d" |
"20240315" |
紧凑格式 |
"%Y-%m-%d %H:%M:%S" |
"2024-03-15 14:30:00" |
日期时间格式 |
"%Y/%m/%d %H:%M" |
"2024/03/15 14:30" |
自定义日期时间 |
"%Y-%m-%d %H:%M:%S %z" |
"2024-03-15 14:30:00 +0800" |
带时区格式 |
库通过可选的特性标志为流行的第三方类型提供内置支持。这对于需要高精度算术或日期/时间处理的金融应用程序特别有用。
在您的 Cargo.toml 中添加所需的特性:
[dependencies]
# 启用特定类型
tushare-api = { version = "1.2.7", features = ["rust_decimal", "chrono"] }
# 或启用所有第三方类型
tushare-api = { version = "1.2.7", features = ["all_types"] }
use tushare_api::{TushareClient, Api, request, TushareEntityList, DeriveFromTushareData};
#[derive(Debug, Clone, DeriveFromTushareData)]
pub struct FinancialData {
#[tushare(field = "ts_code")]
pub stock_code: String,
#[tushare(field = "trade_date")]
pub date: String,
// 用于金融计算的高精度小数
#[tushare(field = "close")]
pub close_price: rust_decimal::Decimal,
#[tushare(field = "vol")]
pub volume: Option<rust_decimal::Decimal>,
#[tushare(field = "amount")]
pub amount: Option<rust_decimal::Decimal>,
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let client = TushareClient::from_env()?;
let data: TushareEntityList<FinancialData> = client.call_api_as(request!(
Api::Daily, {
"ts_code" => "000001.SZ",
"trade_date" => "20240315"
}, [
"ts_code", "trade_date", "close", "vol", "amount"
]
)).await?;
for record in data.iter() {
println!("股票: {} - 价格: {} 日期: {}",
record.stock_code,
record.close_price,
record.date);
}
Ok(())
}
use tushare_api::{TushareClient, Api, request, TushareEntityList, DeriveFromTushareData};
#[derive(Debug, Clone, DeriveFromTushareData)]
pub struct DateTimeData {
#[tushare(field = "ts_code")]
pub stock_code: String,
// 从 YYYYMMDD 格式自动解析
#[tushare(field = "trade_date")]
pub trade_date: chrono::NaiveDate,
// 可选的日期时间字段
#[tushare(field = "update_time")]
pub update_time: Option<chrono::NaiveDateTime>,
// 高精度价格
#[tushare(field = "close")]
pub close_price: rust_decimal::Decimal,
}
| 类型 | 特性标志 | 说明 | 示例值 |
|---|---|---|---|
rust_decimal::Decimal |
rust_decimal |
高精度小数 | "123.456", 123.456 |
bigdecimal::BigDecimal |
bigdecimal |
任意精度 | "999999999999999999999.123" |
chrono::NaiveDate |
chrono |
无时区日期 | "20240315", "2024-03-15" |
chrono::NaiveDateTime |
chrono |
无时区日期时间 | "2024-03-15 14:30:00" |
chrono::DateTime<Utc> |
chrono |
UTC 日期时间 | RFC3339 格式 |
uuid::Uuid |
uuid |
UUID 类型 | "550e8400-e29b-41d4-a716-446655440000" |
详细文档和示例请参阅 第三方类型指南。
如果您不想使用过程宏,仍然可以使用手动方法:
use tushare_api::{TushareClient, Api, request, utils::response_to_vec, traits::FromTushareData};
use tushare_api::error::TushareError;
use serde_json::Value;
#[derive(Debug, Clone)]
pub struct Stock {
pub ts_code: String,
pub name: String,
pub area: Option<String>,
}
// 手动实现 FromTushareData
impl FromTushareData for Stock {
fn from_row(fields: &[String], values: &[Value]) -> Result<Self, TushareError> {
let ts_code_idx = fields.iter().position(|f| f == "ts_code")
.ok_or_else(|| TushareError::ParseError("缺少 ts_code 字段".to_string()))?;
let name_idx = fields.iter().position(|f| f == "name")
.ok_or_else(|| TushareError::ParseError("缺少 name 字段".to_string()))?;
let area_idx = fields.iter().position(|f| f == "area");
Ok(Stock {
ts_code: values[ts_code_idx].as_str()
.ok_or_else(|| TushareError::ParseError("无效的 ts_code".to_string()))?
.to_string(),
name: values[name_idx].as_str()
.ok_or_else(|| TushareError::ParseError("无效的 name".to_string()))?
.to_string(),
area: area_idx.and_then(|idx| values[idx].as_str().map(|s| s.to_string())),
})
}
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let client = TushareClient::from_env()?;
// 获取原始响应
let response = client.call_api(request!(Api::StockBasic, {
"list_status" => "L"
}, [
"ts_code", "name", "area"
])).await?;
// 转换为 Vec<Stock>
let stocks = response_to_vec::<Stock>(response)?;
println!("找到 {} 只股票", stocks.len());
for stock in stocks.iter().take(3) {
println!(" {}: {} - 地区: {:?}", stock.ts_code, stock.name, stock.area);
}
Ok(())
}
env_logger// 设置日志级别并初始化日志器
std::env::set_var("RUST_LOG", "tushare_api=debug");
env_logger::init();
// 创建带日志配置的客户端
let client = TushareClient::builder()
.with_token("your_token_here")
.with_log_level(LogLevel::Debug)
.log_requests(true) // 记录请求详情
.log_responses(false) // 不记录响应内容(可能很大)
.log_sensitive_data(false) // 不记录敏感数据如 token
.log_performance(true) // 记录性能指标
.build()?;
tracing(可选特性)首先,在您的 Cargo.toml 中启用 tracing 特性:
[dependencies]
tushare-api = { version = "1.2.7", features = ["tracing"] }
tracing = "0.1"
tracing-subscriber = "0.3"
然后在您的代码中:
use tracing_subscriber;
// 初始化 tracing 订阅器
std::env::set_var("RUST_LOG", "tushare_api=trace");
tracing_subscriber::fmt()
.with_env_filter(tracing_subscriber::EnvFilter::from_default_env())
.init();
// 客户端配置保持不变
let client = TushareClient::builder()
.with_token("your_token_here")
.with_log_level(LogLevel::Trace)
.build()?;
tracing-log 桥接use tracing_subscriber;
use tracing_log::LogTracer;
// 初始化 log-to-tracing 桥接
LogTracer::init()?;
// 设置 tracing 订阅器
std::env::set_var("RUST_LOG", "tushare_api=debug");
tracing_subscriber::fmt()
.with_env_filter(tracing_subscriber::EnvFilter::from_default_env())
.init();
LogLevel::Off:无日志LogLevel::Error:仅错误LogLevel::Warn:错误和警告LogLevel::Info:基本 API 调用信息(默认)LogLevel::Debug:详细的请求/响应信息LogLevel::Trace:所有信息,包括原始响应内容示例日志输出:
INFO [abc123] Starting Tushare API call: stock_basic, params count: 2, fields count: 3
DEBUG [abc123] API request details - API: stock_basic, params: {...}, fields: [...]
DEBUG [abc123] Sending HTTP request to Tushare API
DEBUG [abc123] Received HTTP response, status code: 200
INFO [abc123] API call successful, duration: 245ms, data rows returned: 100
# 设置您的 token
export TUSHARE_TOKEN="your_token_here"
# 运行基础示例
cargo run --example basic_usage
# 运行日志示例
cargo run --example logging_example
# 运行 tracing 示例(需要 tracing 特性)
cargo run --example tracing_example --features tracing
本项目采用 MIT 许可证 - 详情请参阅 LICENSE 文件。