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:

VariantStringCode
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 / PartiallyFilled removed (server never returns them). PendingSubmit added.

use tigeropen::model::enums::OrderStatus;

println!("{}", OrderStatus::Filled.code());        // 6
println!("{}", OrderStatus::PendingSubmit.code());  // 8

Order Model

v0.3.0 splits the order model into two types:

  • OrderRequest — request struct for place_order / preview_order / modify_order
  • Order — response struct returned by get_orders / get_active_orders / etc.

Helper factories

HelperOrder typeExtra args
market_orderMKT
limit_orderLMTlimit_price
stop_orderSTPaux_price
stop_limit_orderSTP_LMTlimit_price, aux_price
trail_orderTRAILtrailing_percent
auction_limit_orderALlimit_price
auction_market_orderAM

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_type cannot 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 Optionaccount defaults to the client's configured account.

FieldTypeDescription
accountOption<String>Account ID
sec_typeOption<String>"STK" / "OPT" / "FUT" etc.
marketOption<String>Market, e.g. "US" / "HK"
symbolOption<String>Symbol filter
start_dateOption<i64>Start time (ms timestamp)
end_dateOption<i64>End time (ms timestamp)
limitOption<i32>Max records
is_briefOption<bool>Return brief fields only
statesOption<Vec<String>>State filter — see OrderStatus table
sort_byOption<String>"LATEST_CREATED" / "LATEST_STATUS_UPDATED"
page_tokenOption<String>Pagination token
parent_idOption<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), now OrdersRequest with start_date / end_date fields.

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), now OrderTransactionsRequest with all fields optional.

⚠️

symbol is required on the wire — omitting it returns field 'symbol' cannot be empty. Always pass symbol even though the Rust type marks it Option<String>.

Parameters

NameTypeRequiredDescription
symbolOption<String>YesContract code (required on the wire despite Option type)
order_idOption<i64>NoOrder ID (Order.id, int64). In Rust this is precise — no precision loss.
sec_typeOption<String>NoSecurity 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 with get_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

ValueDescription
"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

FieldTypeDescription
contract_idOption<i64>Option contract ID
exercise_typeOption<String>"Exercise" / "Expire" (wire name: type)
quantityOption<f64>Exercise quantity (> 0)
executing_dateOption<String>Execution date yyyy-MM-dd (recommended for Exercise)
is_forceOption<bool>Whether to force exercise (recommended for Exercise)
itm_rateOption<i32>ITM rate threshold 0–10 (Expire only)
secret_keyOption<String>Trader key for institutional/Prime accounts

Return fields (OptionExerciseCheckResult)

FieldTypeDescription
available_quantityf64Exercisable quantity
positionf64Current option position
stk_positionf64Current underlying stock position
stk_position_changef64Stock position change after exercise
stk_position_beforef64Stock position before exercise
stk_position_afterf64Stock position after exercise
symbolStringUnderlying 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

FieldTypeDescription
exercise_typeOption<String>"Exercise" / "Expire" (wire name: type)
secret_keyOption<String>Trader key for institutional/Prime accounts

Return fields (OptionExercisePositionPageResult)

FieldTypeDescription
itemsVec<OptionExercisePosition>Position list
page_numi32Current page number
page_sizei32Page size
item_counti32Total record count
page_counti32Total page count

OptionExercisePosition fields

FieldTypeDescription
contract_idi64Option contract ID
symbolStringOption contract symbol
stk_symbolStringUnderlying stock symbol
expire_dateStringExpiry date yyyy-MM-dd
strikeStringStrike price
call_putStringCALL / PUT
marketStringMarket
account_idi64Account ID
positionf64Position quantity
available_quantityf64Exercisable 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_check first to confirm the position impact. executing_date and is_force are only applicable to Exercise type; itm_rate is only applicable to Expire type.

Request fields

FieldTypeDescription
contract_idOption<i64>Option contract ID
exercise_typeOption<String>"Exercise" / "Expire" (wire name: type)
quantityOption<f64>Exercise quantity (> 0)
executing_dateOption<String>Execution date (required for Exercise)
is_forceOption<bool>Force exercise flag (required for Exercise)
itm_rateOption<i32>ITM rate threshold 0–10 (Expire only)
secret_keyOption<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

FieldTypeDescription
pageOption<i32>Page number, starts from 1 (default 1)
sizeOption<i32>Page size, 1–100 (default 20)
statusOption<String>Status filter: New / Cancel / Success / Fail
exercise_typeOption<String>Type filter: Exercise / Expire (wire name: type)
symbolOption<String>Underlying symbol filter
order_byOption<String>Sort field: symbol / expire_date / strike / is_call
secret_keyOption<String>Trader key for institutional/Prime accounts

Return fields (OptionExerciseRecordPageResult)

FieldTypeDescription
itemsVec<OptionExerciseRecord>Record list
item_counti32Total record count
page_numi32Current page number
page_sizei32Page size
page_counti32Total page count

OptionExerciseRecord fields

FieldTypeDescription
idi64Record ID
contract_idi64Option contract ID
symbolStringOption contract symbol
stk_symbolStringUnderlying stock symbol
expire_dateStringExpiry date
strikeStringStrike price
call_putStringCALL / PUT
exercise_typeStringExercise / Expire (wire name: type)
request_quantityf64Requested quantity
quantityf64Actual exercised quantity
statusStringNew / Cancel / Success / Fail
executing_dateStringExecution date
itm_ratei32ITM rate
is_forceboolForce exercise flag
reasonStringRejection reason (if any)
account_idi64Account 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

FieldTypeDescription
idOption<i64>Exercise record ID (from get_option_exercise_records)
secret_keyOption<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);