use serde::Serialize;
use serde_json::Value;
use crate::client::api_request::ApiRequest;
use crate::client::http_client::HttpClient;
use crate::error::TigerError;
use crate::model::contract::Contract;
use crate::model::order::{Order, OrderRequest};
use crate::model::position::Position;
use crate::model::trade::{
Asset, OrderIdResult, PlaceOrderResult, PreviewResult, PrimeAsset, Transaction,
};
pub struct TradeClient<'a> {
http_client: &'a HttpClient,
account: String,
}
impl<'a> TradeClient<'a> {
pub fn new(http_client: &'a HttpClient, account: impl Into<String>) -> Self {
Self {
http_client,
account: account.into(),
}
}
async fn call_optional<T, P>(&self, method: &str, params: P) -> Result<Option<T>, TigerError>
where
T: serde::de::DeserializeOwned,
P: Serialize,
{
let biz = serde_json::to_string(¶ms)
.map_err(|e| TigerError::Config(format!("serialize biz params failed: {}", e)))?;
let req = ApiRequest::new(method, biz);
let resp = self.http_client.execute_request(&req).await?;
match resp.data {
None => Ok(None),
Some(v) if v.is_null() => Ok(None),
Some(v) => {
let parsed: T = decode_value(v)?;
Ok(Some(parsed))
}
}
}
async fn call_into_items<T, P>(&self, method: &str, params: P) -> Result<Vec<T>, TigerError>
where
T: serde::de::DeserializeOwned,
P: Serialize,
{
let biz = serde_json::to_string(¶ms)
.map_err(|e| TigerError::Config(format!("serialize biz params failed: {}", e)))?;
let req = ApiRequest::new(method, biz);
let resp = self.http_client.execute_request(&req).await?;
let Some(v) = resp.data else { return Ok(Vec::new()); };
if v.is_null() {
return Ok(Vec::new());
}
let raw = match &v {
Value::String(s) => serde_json::from_str::<Value>(s).unwrap_or(v.clone()),
_ => v.clone(),
};
if let Value::Object(map) = &raw {
if let Some(items) = map.get("items") {
if items.is_null() {
return Ok(Vec::new());
}
return serde_json::from_value::<Vec<T>>(items.clone())
.map_err(|e| TigerError::Config(format!("decode items failed: {}", e)));
}
}
if raw.is_array() {
return serde_json::from_value::<Vec<T>>(raw)
.map_err(|e| TigerError::Config(format!("decode array data failed: {}", e)));
}
Ok(Vec::new())
}
pub async fn get_contract(&self, symbol: &str, sec_type: &str) -> Result<Vec<Contract>, TigerError> {
self.call_into_items(
"contract",
serde_json::json!({
"account": self.account,
"symbol": symbol,
"sec_type": sec_type,
}),
)
.await
}
pub async fn get_contracts(
&self,
symbols: &[&str],
sec_type: &str,
) -> Result<Vec<Contract>, TigerError> {
self.call_into_items(
"contracts",
serde_json::json!({
"account": self.account,
"symbols": symbols,
"sec_type": sec_type,
}),
)
.await
}
pub async fn get_quote_contract(
&self,
symbol: &str,
sec_type: &str,
expiry: &str,
) -> Result<Vec<Contract>, TigerError> {
self.call_into_items(
"quote_contract",
serde_json::json!({
"account": self.account,
"symbols": [symbol],
"sec_type": sec_type,
"expiry": expiry,
}),
)
.await
}
pub async fn preview_order(
&self,
order: OrderRequest,
) -> Result<Option<PreviewResult>, TigerError> {
let mut order = order;
order.account = Some(self.account.clone());
self.call_optional("preview_order", order).await
}
pub async fn place_order(
&self,
order: OrderRequest,
) -> Result<Option<PlaceOrderResult>, TigerError> {
let mut order = order;
order.account = Some(self.account.clone());
self.call_optional("place_order", order).await
}
pub async fn modify_order(
&self,
id: i64,
order: OrderRequest,
) -> Result<Option<OrderIdResult>, TigerError> {
let mut order = order;
order.account = Some(self.account.clone());
order.id = Some(id);
self.call_optional("modify_order", order).await
}
pub async fn cancel_order(&self, id: i64) -> Result<Option<OrderIdResult>, TigerError> {
self.call_optional(
"cancel_order",
serde_json::json!({ "account": self.account, "id": id }),
)
.await
}
pub async fn get_orders(&self) -> Result<Vec<Order>, TigerError> {
self.call_into_items("orders", serde_json::json!({ "account": self.account })).await
}
pub async fn get_active_orders(&self) -> Result<Vec<Order>, TigerError> {
self.call_into_items("active_orders", serde_json::json!({ "account": self.account })).await
}
pub async fn get_inactive_orders(&self) -> Result<Vec<Order>, TigerError> {
self.call_into_items("inactive_orders", serde_json::json!({ "account": self.account })).await
}
pub async fn get_filled_orders(
&self,
start_ms: i64,
end_ms: i64,
) -> Result<Vec<Order>, TigerError> {
self.call_into_items(
"filled_orders",
serde_json::json!({
"account": self.account,
"start_date": start_ms,
"end_date": end_ms,
}),
)
.await
}
pub async fn get_order_transactions(
&self,
order_id: i64,
symbol: &str,
sec_type: &str,
) -> Result<Vec<Transaction>, TigerError> {
self.call_into_items(
"order_transactions",
serde_json::json!({
"account": self.account,
"order_id": order_id,
"symbol": symbol,
"sec_type": sec_type,
}),
)
.await
}
pub async fn get_positions(&self) -> Result<Vec<Position>, TigerError> {
self.call_into_items("positions", serde_json::json!({ "account": self.account })).await
}
pub async fn get_assets(&self) -> Result<Vec<Asset>, TigerError> {
self.call_into_items("assets", serde_json::json!({ "account": self.account })).await
}
pub async fn get_prime_assets(&self) -> Result<Option<PrimeAsset>, TigerError> {
self.call_optional("prime_assets", serde_json::json!({ "account": self.account })).await
}
}
fn decode_value<T>(v: Value) -> Result<T, TigerError>
where
T: serde::de::DeserializeOwned,
{
match serde_json::from_value::<T>(v.clone()) {
Ok(out) => Ok(out),
Err(_) => {
if let Value::String(s) = &v {
return serde_json::from_str::<T>(s).map_err(|e| {
TigerError::Config(format!("decode data (double-encoded) failed: {}", e))
});
}
serde_json::from_value::<T>(v)
.map_err(|e| TigerError::Config(format!("decode data failed: {}", e)))
}
}
}
#[cfg(test)]
mod tests;