Skip to content

Commit 353d740

Browse files
committed
feat!: v0.5.0 — multi-symbol quote APIs, from_config constructors, public call_* methods
Breaking changes: - QuoteClient/TradeClient now accept ClientConfig directly via from_config(); lifetime parameter <'a> removed, HttpClient is now owned (not borrowed) - call_into / call_optional / call_into_items / call_into_versioned / call_into_list_or_object / call_optional_versioned are now pub - get_kline / get_option_expiration accept &[&str] instead of &str - get_option_chain accepts &[(&str, &str)] (symbol+expiry pairs) instead of two &str - get_option_kline accepts &[&str] identifiers instead of single &str - BarsByPageRequest.symbol renamed to symbols: Option<Vec<String>>
1 parent f4aa27f commit 353d740

9 files changed

Lines changed: 416 additions & 141 deletions

File tree

CHANGELOG.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,19 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [0.5.0] - 2026-07-07
9+
10+
### Breaking Changes
11+
12+
- **`QuoteClient` / `TradeClient` 构造方式变更**:不再需要用户手动创建 `HttpClient`;新增 `from_config(config: ClientConfig)` 构造器,直接接受 `ClientConfig`,内部自动选择 trade/quote server。旧的 `new(http_client)` / `with_secret_key(http_client, ...)` 构造器继续可用,但参数从 `&HttpClient` 改为拥有所有权的 `HttpClient`(移除了 lifetime 参数 `<'a>`)。
13+
- **`call_*` 系列方法改为 `pub`**`call_into``call_into_versioned``call_into_items``call_into_list_or_object``call_optional``call_optional_versioned` 现在均为 `pub`,可直接用于自定义请求。
14+
- **多 symbol 支持(行情接口签名变更)**:下列接口参数由单 symbol 改为 slice。调用方需更新:
15+
- `get_kline(symbol: &str, ...)``get_kline(symbols: &[&str], ...)`
16+
- `get_option_expiration(symbol: &str)``get_option_expiration(symbols: &[&str])`
17+
- `get_option_chain(symbol: &str, expiry: &str)``get_option_chain(items: &[(&str, &str)])`(每项为 `(symbol, expiry)` 对)
18+
- `get_option_kline(identifier: &str, period: &str)``get_option_kline(identifiers: &[&str], period: &str)`
19+
- `BarsByPageRequest.symbol: Option<String>``symbols: Option<Vec<String>>`
20+
821
## [0.4.4] - 2026-07-03
922

1023
### Fixed

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "tigeropen"
3-
version = "0.4.4"
3+
version = "0.5.0"
44
edition = "2021"
55
rust-version = "1.70"
66
description = "老虎证券 OpenAPI Rust SDK"

examples/quote_example.rs

Lines changed: 75 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,10 @@
99
//!
1010
//! Run: `TIGER_CONFIG_PATH=~/.tigeropen/tiger_openapi_config.properties cargo run --example quote_example`
1111
12-
use tigeropen::client::http_client::HttpClient;
1312
use tigeropen::config::ClientConfig;
1413
use tigeropen::model::quote::{
15-
CorporateActionRequest, FinancialDailyRequest, FinancialReportRequest, FutureKlineRequest,
16-
MarketScannerRequest,
14+
Brief, CorporateActionRequest, FinancialDailyRequest, FinancialReportRequest, FutureKlineRequest,
15+
MarketScannerRequest, MarketState,
1716
};
1817
use tigeropen::model::quote_requests::{
1918
AllFutureContractsRequest, BarsRequest, BriefRequest, DepthQuoteRequest,
@@ -96,8 +95,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
9695
};
9796
println!("tiger_id={} account={}\n", config.tiger_id, config.account);
9897

99-
let http = HttpClient::with_quote_server(config);
100-
let qc = QuoteClient::new(&http);
98+
let qc = QuoteClient::from_config(config);
10199

102100
let mut results: Vec<RunResult> = Vec::new();
103101

@@ -133,7 +131,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
133131
Err(e) => fail(&mut results, "GetBrief", e),
134132
}
135133

136-
match qc.get_kline("AAPL", "day").await {
134+
match qc.get_kline(&["AAPL"], "day").await {
137135
Ok(klines) if !klines.is_empty() => ok(
138136
&mut results,
139137
"GetKline(AAPL day)",
@@ -346,7 +344,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
346344
let mut expiry_date = String::new();
347345
let mut opt_identifier = String::new();
348346

349-
match qc.get_option_expiration("AAPL").await {
347+
match qc.get_option_expiration(&["AAPL"]).await {
350348
Ok(exps) if !exps.is_empty() && !exps[0].dates.is_empty() => {
351349
ok(
352350
&mut results,
@@ -365,7 +363,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
365363
skip(&mut results, "GetOptionBrief", "no expiry available");
366364
skip(&mut results, "GetOptionKline", "no expiry available");
367365
} else {
368-
match qc.get_option_chain("AAPL", &expiry_date).await {
366+
match qc.get_option_chain(&[("AAPL", &expiry_date)]).await {
369367
Ok(chain) if !chain.is_empty() && !chain[0].items.is_empty() => {
370368
ok(
371369
&mut results,
@@ -401,7 +399,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
401399
Err(e) => fail(&mut results, "GetOptionBrief", e),
402400
}
403401

404-
match qc.get_option_kline(&opt_identifier, "day").await {
402+
match qc.get_option_kline(&[opt_identifier.as_str()], "day").await {
405403
Ok(ks) if !ks.is_empty() => ok(
406404
&mut results,
407405
"GetOptionKline",
@@ -852,6 +850,74 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
852850
Err(e) => fail(&mut results, "GetQuoteOvernight(AAPL)", e),
853851
}
854852

853+
// ── low-level call_* API ──────────────────────────────────────────────
854+
println!("\n=== Low-level call_* API ===");
855+
856+
// call_into: raw method name + params, deserializes data directly into T
857+
match qc
858+
.call_into::<Vec<MarketState>, _>("market_state", serde_json::json!({"market": "US"}))
859+
.await
860+
{
861+
Ok(states) if !states.is_empty() => ok(
862+
&mut results,
863+
"call_into(market_state)",
864+
format!("market={} status={}", states[0].market, states[0].market_status),
865+
),
866+
Ok(_) => ok(&mut results, "call_into(market_state)", "(empty)"),
867+
Err(e) => fail(&mut results, "call_into(market_state)", e),
868+
}
869+
870+
// call_into_versioned: same as call_into but with explicit API version
871+
match qc
872+
.call_into_versioned::<Vec<MarketState>, _>(
873+
"market_state",
874+
serde_json::json!({"market": "US"}),
875+
Some("2.0"),
876+
)
877+
.await
878+
{
879+
Ok(states) => ok(
880+
&mut results,
881+
"call_into_versioned(market_state, v2.0)",
882+
format!("count={}", states.len()),
883+
),
884+
Err(e) => fail(&mut results, "call_into_versioned(market_state, v2.0)", e),
885+
}
886+
887+
// call_into_items: unwraps {"items":[...]} envelope
888+
match qc
889+
.call_into_items::<Brief, _>(
890+
"brief",
891+
serde_json::json!({"symbols": ["AAPL"], "level": "basic"}),
892+
)
893+
.await
894+
{
895+
Ok(briefs) if !briefs.is_empty() => ok(
896+
&mut results,
897+
"call_into_items(brief)",
898+
format!("symbol={} price={:?}", briefs[0].symbol, briefs[0].latest_price),
899+
),
900+
Ok(_) => ok(&mut results, "call_into_items(brief)", "(empty)"),
901+
Err(e) => fail(&mut results, "call_into_items(brief)", e),
902+
}
903+
904+
// call_optional: returns None when data is absent
905+
match qc
906+
.call_optional::<Brief, _>(
907+
"brief",
908+
serde_json::json!({"symbols": ["AAPL"], "level": "basic"}),
909+
)
910+
.await
911+
{
912+
Ok(Some(b)) => ok(
913+
&mut results,
914+
"call_optional(brief)",
915+
format!("symbol={}", b.symbol),
916+
),
917+
Ok(None) => ok(&mut results, "call_optional(brief)", "(no data)"),
918+
Err(e) => fail(&mut results, "call_optional(brief)", e),
919+
}
920+
855921
print_summary(&results);
856922
Ok(())
857923
}

examples/trade_example.rs

Lines changed: 38 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,6 @@
1414
1515
use std::time::{SystemTime, UNIX_EPOCH};
1616

17-
use tigeropen::client::http_client::HttpClient;
1817
use tigeropen::config::ClientConfig;
1918
use tigeropen::model::order::limit_order;
2019
use tigeropen::model::trade_requests::{
@@ -95,11 +94,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
9594
println!("tiger_id={} account={}\n", config.tiger_id, config.account);
9695

9796
let account = config.account.clone();
98-
let http = HttpClient::new(config.clone());
99-
let tc = match &config.secret_key {
100-
Some(sk) => TradeClient::with_secret_key(&http, &account, sk),
101-
None => TradeClient::new(&http, &account),
102-
};
97+
let tc = TradeClient::from_config(config);
10398

10499
let mut results: Vec<RunResult> = Vec::new();
105100

@@ -517,6 +512,43 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
517512
skip(&mut results, "CheckOptionExercise", "no exercisable positions");
518513
}
519514

515+
// ── low-level call_* API ──────────────────────────────────────────────
516+
println!("\n=== Low-level call_* API ===");
517+
518+
// call_into_items: raw method name + arbitrary JSON params, returns Vec<T>
519+
match tc
520+
.call_into_items::<tigeropen::model::contract::Contract, _>(
521+
"contract",
522+
serde_json::json!({"account": account, "symbol": "AAPL", "sec_type": "STK"}),
523+
)
524+
.await
525+
{
526+
Ok(cs) if !cs.is_empty() => ok(
527+
&mut results,
528+
"call_into_items(contract)",
529+
format!("symbol={} secType={}", cs[0].symbol, cs[0].sec_type),
530+
),
531+
Ok(_) => ok(&mut results, "call_into_items(contract)", "(empty)"),
532+
Err(e) => fail(&mut results, "call_into_items(contract)", e),
533+
}
534+
535+
// call_optional: returns None when data is absent, Some(T) otherwise
536+
match tc
537+
.call_optional::<tigeropen::model::trade::PreviewResult, _>(
538+
"preview_order",
539+
tigeropen::model::order::limit_order(&account, "AAPL", "STK", "BUY", 1, 1.00),
540+
)
541+
.await
542+
{
543+
Ok(Some(r)) => ok(
544+
&mut results,
545+
"call_optional(preview_order)",
546+
format!("isPass={} commission={}", r.is_pass, r.commission),
547+
),
548+
Ok(None) => ok(&mut results, "call_optional(preview_order)", "(no data)"),
549+
Err(e) => fail(&mut results, "call_optional(preview_order)", e),
550+
}
551+
520552
print_summary(&results);
521553
Ok(())
522554
}

src/model/quote_requests.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ pub struct BarsRequest {
115115
#[derive(Debug, Clone, Serialize, Default)]
116116
pub struct BarsByPageRequest {
117117
#[serde(skip_serializing_if = "Option::is_none")]
118-
pub symbol: Option<String>,
118+
pub symbols: Option<Vec<String>>,
119119
#[serde(skip_serializing_if = "Option::is_none")]
120120
pub period: Option<String>,
121121
#[serde(skip_serializing_if = "Option::is_none")]

0 commit comments

Comments
 (0)