Trading
The Rust SDK provides an async TradeClient built on Tokio. Every method returns a typed Result<T, TigerError> (e.g. Vec<Order>, Vec<Position>, Option<PlaceOrderResult>). v0.4.0 migrates 8 query methods to Request struct signatures (breaking) and adds 13 new methods. v0.5.0 adds from_config() constructors to avoid manually creating HttpClient.
Quick Start
use tigeropen::config::ClientConfig;
use tigeropen::model::order::limit_order;
use tigeropen::model::trade_requests::{OrdersRequest, PositionsRequest};
use tigeropen::trade::TradeClient;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let config = ClientConfig::builder().build()?;
let account = config.account.clone();
// v0.5.0+: use from_config() directly — no need to create HttpClient manually
let tc = TradeClient::from_config(config, &account);
// Query positions (v0.4.0: Request struct)
let positions = tc.get_positions(PositionsRequest::default()).await?;
println!("positions={}", positions.len());
// Build a limit order via helper
let mut order = limit_order(&account, "AAPL", "STK", "BUY", 100, 150.0);
order.market = Some("US".into());
order.currency = Some("USD".into());
// Preview before placing
if let Some(preview) = tc.preview_order(order.clone()).await? {
println!("commission={} initMargin={}", preview.commission, preview.init_margin);
}
// Place the order
if let Some(placed) = tc.place_order(order).await? {
println!("id={} orderId={}", placed.id, placed.order_id);
}
Ok(())
}OrderStatus (v0.4.0 aligned with Java)
v0.4.0 aligns OrderStatus with the Java SDK — 8 values. Numeric codes returned by the server are automatically deserialized to the corresponding string variant:
| Variant | String | Code |
|---|---|---|
OrderStatus::Invalid | "Invalid" | -2 |
OrderStatus::Initial | "Initial" | -1 |
OrderStatus::PendingCancel | "PendingCancel" | 3 |
OrderStatus::Cancelled | "Cancelled" | 4 |
OrderStatus::Submitted | "Submitted" | 5 |
OrderStatus::Filled | "Filled" | 6 |
OrderStatus::Inactive | "Inactive" | 7 |
OrderStatus::PendingSubmit | "PendingSubmit" | 8 |
Breaking: Python-derived
PendingNew/PartiallyFilledremoved (server never returns them).PendingSubmitadded.
use tigeropen::model::enums::OrderStatus;
println!("{}", OrderStatus::Filled.code()); // 6
println!("{}", OrderStatus::PendingSubmit.code()); // 8Order Model
v0.3.0 splits the order model into two types:
OrderRequest— request struct forplace_order/preview_order/modify_orderOrder— response struct returned byget_orders/get_active_orders/ etc.
Helper factories
| Helper | Order type | Extra args |
|---|---|---|
market_order | MKT | — |
limit_order | LMT | limit_price |
stop_order | STP | aux_price |
stop_limit_order | STP_LMT | limit_price, aux_price |
trail_order | TRAIL | trailing_percent |
auction_limit_order | AL | limit_price |
auction_market_order | AM | — |
Contract Query
get_contract
pub async fn get_contract(&self, symbol: &str, sec_type: &str) -> Result<Vec<Contract>, TigerError>let contracts = tc.get_contract("AAPL", "STK").await?;get_contracts
pub async fn get_contracts(&self, symbols: &[&str], sec_type: &str) -> Result<Vec<Contract>, TigerError>let contracts = tc.get_contracts(&["AAPL", "TSLA", "MSFT"], "STK").await?;get_quote_contract
pub async fn get_quote_contract(&self, symbol: &str, sec_type: &str, expiry: &str) -> Result<Vec<Contract>, TigerError>let contracts = tc.get_quote_contract("AAPL", "OPT", "20260619").await?;Order Management
place_order
pub async fn place_order(&self, order: OrderRequest) -> Result<Option<PlaceOrderResult>, TigerError>if let Some(placed) = tc.place_order(req).await? {
println!("id={} orderId={}", placed.id, placed.order_id);
}Iceberg Order Example
use tigeropen::model::order::{iceberg_order, iceberg_order_full};
// Basic iceberg order (defaults to LIMIT_PRICE mode)
let order = iceberg_order("YOUR_ACCOUNT", "AAPL", "STK", "BUY", 1000, 180.0, 100);
// Full parameters
// price_type: "LIMIT_PRICE" / "ASK_PRICE" / "BID_PRICE" / "LATEST_PRICE"
let order = iceberg_order_full(
"YOUR_ACCOUNT", "AAPL", "STK", "BUY", 1000, 180.0,
100, Some(50), Some(30), Some("LIMIT_PRICE"), Some(start_time), Some(end_time));
if let Some(placed) = tc.place_order(order).await? {
println!("iceberg id={}", placed.id);
}preview_order
pub async fn preview_order(&self, order: OrderRequest) -> Result<Option<PreviewResult>, TigerError>modify_order
pub async fn modify_order(&self, id: i64, order: OrderRequest) -> Result<Option<OrderIdResult>, TigerError>Iceberg order modification: Supports modifying
total_quantity,limit_price,display_size,min_display_size,check_intervals,start_time,end_time.price_typecannot be modified.
cancel_order
pub async fn cancel_order(&self, id: i64) -> Result<Option<OrderIdResult>, TigerError>Order Query
OrdersRequest
All four order-query methods share OrdersRequest. Every field is Option — account defaults to the client's configured account.
| Field | Type | Description |
|---|---|---|
| account | Option<String> | Account ID |
| sec_type | Option<String> | "STK" / "OPT" / "FUT" etc. |
| market | Option<String> | Market, e.g. "US" / "HK" |
| symbol | Option<String> | Symbol filter |
| start_date | Option<i64> | Start time (ms timestamp) |
| end_date | Option<i64> | End time (ms timestamp) |
| limit | Option<i32> | Max records |
| is_brief | Option<bool> | Return brief fields only |
| states | Option<Vec<String>> | State filter — see OrderStatus table |
| sort_by | Option<String> | "LATEST_CREATED" / "LATEST_STATUS_UPDATED" |
| page_token | Option<String> | Pagination token |
| parent_id | Option<i64> | Parent order ID (get_active_orders only) |
get_orders
async fn get_orders(&self, req: OrdersRequest) -> Result<Vec<Order>, TigerError>
v0.4.0 Breaking: changed from no-arg to
OrdersRequest.
use tigeropen::model::trade_requests::OrdersRequest;
// Simple call
let orders = tc.get_orders(OrdersRequest::default()).await?;
// Filtered: last 100 filled US stock orders
let filtered = tc.get_orders(OrdersRequest {
market: Some("US".to_string()),
sec_type: Some("STK".to_string()),
states: Some(vec!["Filled".to_string()]),
limit: Some(100),
..Default::default()
}).await?;get_order
async fn get_order(&self, req: GetOrderRequest) -> Result<Option<Order>, TigerError>
Query a single order by ID. Pass id (global) or order_id (account-level); at least one is required.
use tigeropen::model::trade_requests::GetOrderRequest;
let order = tc.get_order(GetOrderRequest {
id: Some(123456789),
..Default::default()
}).await?;get_active_orders
async fn get_active_orders(&self, req: OrdersRequest) -> Result<Vec<Order>, TigerError>
v0.4.0 Breaking: changed from no-arg to
OrdersRequest.
let active = tc.get_active_orders(OrdersRequest::default()).await?;get_inactive_orders
async fn get_inactive_orders(&self, req: OrdersRequest) -> Result<Vec<Order>, TigerError>
v0.4.0 Breaking: changed from no-arg to
OrdersRequest.
let inactive = tc.get_inactive_orders(OrdersRequest::default()).await?;get_filled_orders
async fn get_filled_orders(&self, req: OrdersRequest) -> Result<Vec<Order>, TigerError>
v0.4.0 Breaking: was
(start_ms: i64, end_ms: i64), nowOrdersRequestwithstart_date/end_datefields.
use std::time::{SystemTime, UNIX_EPOCH};
let now = SystemTime::now().duration_since(UNIX_EPOCH).map(|d| d.as_millis() as i64).unwrap_or(0);
let recent = tc.get_filled_orders(OrdersRequest {
start_date: Some(now - 30 * 24 * 3600 * 1000),
end_date: Some(now),
limit: Some(200),
..Default::default()
}).await?;get_order_transactions
async fn get_order_transactions(&self, req: OrderTransactionsRequest) -> Result<Vec<Transaction>, TigerError>
v0.4.0 Breaking: was
(order_id, symbol, sec_type), nowOrderTransactionsRequestwith all fields optional.
symbolis required on the wire — omitting it returnsfield 'symbol' cannot be empty. Always passsymboleven though the Rust type marks itOption<String>.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
| symbol | Option<String> | Yes | Contract code (required on the wire despite Option type) |
| order_id | Option<i64> | No | Order ID (Order.id, int64). In Rust this is precise — no precision loss. |
| sec_type | Option<String> | No | Security type (e.g. "STK") |
use tigeropen::model::trade_requests::OrderTransactionsRequest;
let txs = tc.get_order_transactions(OrderTransactionsRequest {
symbol: Some("AAPL".to_string()), // required on the wire
sec_type: Some("STK".to_string()),
..Default::default()
}).await?;Positions and Assets
get_positions
async fn get_positions(&self, req: PositionsRequest) -> Result<Vec<Position>, TigerError>
v0.4.0 Breaking: changed from no-arg to
PositionsRequest.
use tigeropen::model::trade_requests::PositionsRequest;
let positions = tc.get_positions(PositionsRequest::default()).await?;
// Filter to US stocks only
let us = tc.get_positions(PositionsRequest {
market: Some("US".to_string()),
sec_type: Some("STK".to_string()),
..Default::default()
}).await?;get_assets
async fn get_assets(&self, req: AssetsRequest) -> Result<Vec<Asset>, TigerError>
v0.4.0 Breaking: changed from no-arg to
AssetsRequest.
use tigeropen::model::trade_requests::AssetsRequest;
let assets = tc.get_assets(AssetsRequest::default()).await?;get_prime_assets
async fn get_prime_assets(&self, req: AssetsRequest) -> Result<Option<PrimeAsset>, TigerError>
v0.4.0 Breaking: changed from no-arg to
AssetsRequest(shared withget_assets).
if let Some(pa) = tc.get_prime_assets(AssetsRequest::default()).await? {
println!("account={} segments={}", pa.account_id, pa.segments.len());
}Account Management
get_managed_accounts
async fn get_managed_accounts(&self, req: ManagedAccountsRequest) -> Result<Vec<ManagedAccount>, TigerError>
Query the list of managed sub-accounts under the current master account. Wire method: accounts.
use tigeropen::model::trade_requests::ManagedAccountsRequest;
let subs = tc.get_managed_accounts(ManagedAccountsRequest::default()).await?;
for s in &subs {
println!("{} type={:?} status={:?}", s.account, s.account_type, s.status);
}get_derivative_contracts
async fn get_derivative_contracts(&self, req: DerivativeContractsRequest) -> Result<Vec<Contract>, TigerError>
Batch query derivative contracts (options / warrants / IOPT). Wire method: quote_contract.
use tigeropen::model::trade_requests::DerivativeContractsRequest;
let cs = tc.get_derivative_contracts(DerivativeContractsRequest {
symbols: vec!["AAPL".to_string()],
sec_type: "OPT".to_string(),
expiry: Some("20260619".to_string()),
..Default::default()
}).await?;Asset Analytics
get_analytics_asset
async fn get_analytics_asset(&self, req: AnalyticsAssetRequest) -> Result<Vec<AnalyticsAsset>, TigerError>
Daily account asset analytics (holding value, cash balance, P&L, net value index). Wire method: analytics_asset.
use tigeropen::model::trade_requests::AnalyticsAssetRequest;
let rows = tc.get_analytics_asset(AnalyticsAssetRequest {
start_date: Some("2025-01-01".to_string()),
end_date: Some("2025-12-31".to_string()),
..Default::default()
}).await?;get_aggregate_assets
async fn get_aggregate_assets(&self, req: AggregateAssetsRequest) -> Result<Option<AggregateAssets>, TigerError>
Aggregate multi-currency assets in a single base currency view. Wire method: aggregate_assets.
Institutional accounts only. Individual and paper (PAPER) accounts are not supported.
use tigeropen::model::trade_requests::AggregateAssetsRequest;
if let Some(agg) = tc.get_aggregate_assets(AggregateAssetsRequest {
base_currency: Some("USD".to_string()),
..Default::default()
}).await? {
println!("netLiq={:?} currencies={}", agg.net_liquidation, agg.currency_assets.len());
}get_estimate_tradable_quantity
async fn get_estimate_tradable_quantity(&self, req: EstimateTradableQuantityRequest) -> Result<Option<EstimateTradableQuantity>, TigerError>
Estimate max tradable quantity based on available margin and current price. Wire method: estimate_tradable_quantity.
use tigeropen::model::trade_requests::EstimateTradableQuantityRequest;
if let Some(est) = tc.get_estimate_tradable_quantity(EstimateTradableQuantityRequest {
symbol: "AAPL".to_string(),
sec_type: "STK".to_string(),
action: "BUY".to_string(),
order_type: Some("LMT".to_string()),
limit_price: Some(150.0),
..Default::default()
}).await? {
println!("tradable={:?}", est.tradable_quantity);
}Fund Details and Transfers
get_fund_details
async fn get_fund_details(&self, req: FundDetailsRequest) -> Result<Vec<FundDetails>, TigerError>
Query fund flow records (deposits, withdrawals, fees, interest, dividends). Wire method: fund_details.
use tigeropen::model::trade_requests::FundDetailsRequest;
let rows = tc.get_fund_details(FundDetailsRequest {
currency: Some("USD".to_string()),
limit: Some(100),
..Default::default()
}).await?;get_funding_history
async fn get_funding_history(&self, req: FundingHistoryRequest) -> Result<Vec<FundingHistoryItem>, TigerError>
Query transfer fund history. Wire method: transfer_fund.
use tigeropen::model::trade_requests::FundingHistoryRequest;
let rows = tc.get_funding_history(FundingHistoryRequest::default()).await?;place_forex_order
async fn place_forex_order(&self, req: ForexOrderRequest) -> Result<Option<ForexOrderResult>, TigerError>
Submit a forex conversion order. Wire method: place_forex_order.
use tigeropen::model::trade_requests::ForexOrderRequest;
if let Some(res) = tc.place_forex_order(ForexOrderRequest {
source_currency: "USD".to_string(),
source_amount: Some(10000.0),
target_currency: "HKD".to_string(),
..Default::default()
}).await? {
println!("rate={:?} target={:?}", res.rate, res.target_amount);
}get_segment_fund_available
async fn get_segment_fund_available(&self, req: SegmentFundRequest) -> Result<Vec<SegmentFund>, TigerError>
Query available transfer amount between sub-account segments. Wire method: segment_fund_available.
get_segment_fund_history
async fn get_segment_fund_history(&self, req: SegmentFundRequest) -> Result<Vec<SegmentFundHistoryItem>, TigerError>
transfer_segment_fund
async fn transfer_segment_fund(&self, req: SegmentFundRequest) -> Result<Option<SegmentFund>, TigerError>
Transfer funds between sub-account segments. Wire method: transfer_segment_fund.
cancel_segment_fund
async fn cancel_segment_fund(&self, req: SegmentFundRequest) -> Result<Option<SegmentFund>, TigerError>
Cancel a pending segment fund transfer. Wire method: cancel_segment_fund.
Option Exercise
As of v0.4.2, TradeClient provides 5 option exercise methods.
Exercise Types
| Value | Description |
|---|---|
"Exercise" | Early exercise — exercise the option before expiry |
"Expire" | Early waive — voluntarily waive the right to exercise before expiry |
option_exercise_check
pub async fn option_exercise_check(
&self,
req: OptionExerciseCheckRequest,
) -> Result<Option<OptionExerciseCheckResult>, TigerError>Estimates the position change in the underlying stock after exercise or waive. Call this before submitting to confirm the expected result.
Request fields
| Field | Type | Description |
|---|---|---|
contract_id | Option<i64> | Option contract ID |
exercise_type | Option<String> | "Exercise" / "Expire" (wire name: type) |
quantity | Option<f64> | Exercise quantity (> 0) |
executing_date | Option<String> | Execution date yyyy-MM-dd (recommended for Exercise) |
is_force | Option<bool> | Whether to force exercise (recommended for Exercise) |
itm_rate | Option<i32> | ITM rate threshold 0–10 (Expire only) |
secret_key | Option<String> | Trader key for institutional/Prime accounts |
Return fields (OptionExerciseCheckResult)
| Field | Type | Description |
|---|---|---|
available_quantity | f64 | Exercisable quantity |
position | f64 | Current option position |
stk_position | f64 | Current underlying stock position |
stk_position_change | f64 | Stock position change after exercise |
stk_position_before | f64 | Stock position before exercise |
stk_position_after | f64 | Stock position after exercise |
symbol | String | Underlying stock symbol |
use tigeropen::trade::request::OptionExerciseCheckRequest;
let result = tc.option_exercise_check(OptionExerciseCheckRequest {
contract_id: Some(1234567890),
exercise_type: Some("Exercise".into()),
quantity: Some(1.0),
executing_date: Some("2025-06-20".into()),
is_force: Some(false),
..Default::default()
}).await?;
if let Some(r) = result {
println!("symbol={} availableQty={} stkBefore={} stkAfter={}",
r.symbol, r.available_quantity, r.stk_position_before, r.stk_position_after);
}
// Expire type — set ITM rate
let result2 = tc.option_exercise_check(OptionExerciseCheckRequest {
contract_id: Some(1234567890),
exercise_type: Some("Expire".into()),
quantity: Some(1.0),
itm_rate: Some(5),
..Default::default()
}).await?;get_option_exercise_positions
pub async fn get_option_exercise_positions(
&self,
req: OptionExercisePositionRequest,
) -> Result<Option<OptionExercisePositionPageResult>, TigerError>Returns the list of option positions that can be exercised or waived.
Request fields
| Field | Type | Description |
|---|---|---|
exercise_type | Option<String> | "Exercise" / "Expire" (wire name: type) |
secret_key | Option<String> | Trader key for institutional/Prime accounts |
Return fields (OptionExercisePositionPageResult)
| Field | Type | Description |
|---|---|---|
items | Vec<OptionExercisePosition> | Position list |
page_num | i32 | Current page number |
page_size | i32 | Page size |
item_count | i32 | Total record count |
page_count | i32 | Total page count |
OptionExercisePosition fields
| Field | Type | Description |
|---|---|---|
contract_id | i64 | Option contract ID |
symbol | String | Option contract symbol |
stk_symbol | String | Underlying stock symbol |
expire_date | String | Expiry date yyyy-MM-dd |
strike | String | Strike price |
call_put | String | CALL / PUT |
market | String | Market |
account_id | i64 | Account ID |
position | f64 | Position quantity |
available_quantity | f64 | Exercisable quantity |
use tigeropen::trade::request::OptionExercisePositionRequest;
if let Some(page) = tc.get_option_exercise_positions(OptionExercisePositionRequest {
exercise_type: Some("Exercise".into()),
..Default::default()
}).await? {
println!("itemCount={} pageCount={}", page.item_count, page.page_count);
for pos in &page.items {
println!("contractId={} symbol={} availableQty={}",
pos.contract_id, pos.symbol, pos.available_quantity);
}
}submit_option_exercise
pub async fn submit_option_exercise(
&self,
req: OptionExerciseSubmitRequest,
) -> Result<Option<bool>, TigerError>Submits an early exercise or early waive request. Returns Some(true) on success.
Note: Call
option_exercise_checkfirst to confirm the position impact.executing_dateandis_forceare only applicable toExercisetype;itm_rateis only applicable toExpiretype.
Request fields
| Field | Type | Description |
|---|---|---|
contract_id | Option<i64> | Option contract ID |
exercise_type | Option<String> | "Exercise" / "Expire" (wire name: type) |
quantity | Option<f64> | Exercise quantity (> 0) |
executing_date | Option<String> | Execution date (required for Exercise) |
is_force | Option<bool> | Force exercise flag (required for Exercise) |
itm_rate | Option<i32> | ITM rate threshold 0–10 (Expire only) |
secret_key | Option<String> | Trader key for institutional/Prime accounts |
use tigeropen::trade::request::OptionExerciseSubmitRequest;
// Early exercise
let ok = tc.submit_option_exercise(OptionExerciseSubmitRequest {
contract_id: Some(1234567890),
exercise_type: Some("Exercise".into()),
quantity: Some(1.0),
executing_date: Some("2025-06-20".into()),
is_force: Some(false),
..Default::default()
}).await?;
println!("submitted: {:?}", ok);
// Early waive
let ok2 = tc.submit_option_exercise(OptionExerciseSubmitRequest {
contract_id: Some(1234567890),
exercise_type: Some("Expire".into()),
quantity: Some(1.0),
..Default::default()
}).await?;get_option_exercise_records
pub async fn get_option_exercise_records(
&self,
req: OptionExerciseRecordsRequest,
) -> Result<Option<OptionExerciseRecordPageResult>, TigerError>Returns paginated exercise/waive request records.
Request fields
| Field | Type | Description |
|---|---|---|
page | Option<i32> | Page number, starts from 1 (default 1) |
size | Option<i32> | Page size, 1–100 (default 20) |
status | Option<String> | Status filter: New / Cancel / Success / Fail |
exercise_type | Option<String> | Type filter: Exercise / Expire (wire name: type) |
symbol | Option<String> | Underlying symbol filter |
order_by | Option<String> | Sort field: symbol / expire_date / strike / is_call |
secret_key | Option<String> | Trader key for institutional/Prime accounts |
Return fields (OptionExerciseRecordPageResult)
| Field | Type | Description |
|---|---|---|
items | Vec<OptionExerciseRecord> | Record list |
item_count | i32 | Total record count |
page_num | i32 | Current page number |
page_size | i32 | Page size |
page_count | i32 | Total page count |
OptionExerciseRecord fields
| Field | Type | Description |
|---|---|---|
id | i64 | Record ID |
contract_id | i64 | Option contract ID |
symbol | String | Option contract symbol |
stk_symbol | String | Underlying stock symbol |
expire_date | String | Expiry date |
strike | String | Strike price |
call_put | String | CALL / PUT |
exercise_type | String | Exercise / Expire (wire name: type) |
request_quantity | f64 | Requested quantity |
quantity | f64 | Actual exercised quantity |
status | String | New / Cancel / Success / Fail |
executing_date | String | Execution date |
itm_rate | i32 | ITM rate |
is_force | bool | Force exercise flag |
reason | String | Rejection reason (if any) |
account_id | i64 | Account ID |
use tigeropen::trade::request::OptionExerciseRecordsRequest;
if let Some(page) = tc.get_option_exercise_records(OptionExerciseRecordsRequest {
page: Some(1),
size: Some(20),
..Default::default()
}).await? {
println!("itemCount={} pageCount={}", page.item_count, page.page_count);
for r in &page.items {
println!("id={} type={} status={}", r.id, r.exercise_type, r.status);
}
}cancel_option_exercise
pub async fn cancel_option_exercise(
&self,
req: OptionExerciseCancelRequest,
) -> Result<Option<bool>, TigerError>Cancels a pending exercise request by its record ID.
Request fields
| Field | Type | Description |
|---|---|---|
id | Option<i64> | Exercise record ID (from get_option_exercise_records) |
secret_key | Option<String> | Trader key for institutional/Prime accounts |
use tigeropen::trade::request::OptionExerciseCancelRequest;
let ok = tc.cancel_option_exercise(OptionExerciseCancelRequest {
id: Some(record_id),
..Default::default()
}).await?;
println!("cancelled: {:?}", ok);