/*
* Stocks Trading REST API
*
* REST APIs for Binance Stocks Trading. All endpoints under `/sapi/v1/equity/_*`.
*
* The version of the OpenAPI document: 1.0.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
#![allow(unused_imports)]
use async_trait::async_trait;
use derive_builder::Builder;
use reqwest;
use rust_decimal::prelude::*;
use serde::{Deserialize, Serialize};
use serde_json::{Value, json};
use std::collections::BTreeMap;
use crate::common::{
config::ConfigurationRestApi,
models::{ParamBuildError, RestApiResponse},
utils::send_request,
};
use crate::stocks::rest_api::models;
const HAS_TIME_UNIT: bool = false;
#[async_trait]
pub trait TradeApi: Send + Sync {
async fn cancel_all_equity_orders(
&self,
params: CancelAllEquityOrdersParams,
) -> anyhow::Result<RestApiResponse<models::CancelAllEquityOrdersResponse>>;
async fn cancel_equity_order(
&self,
params: CancelEquityOrderParams,
) -> anyhow::Result<RestApiResponse<models::CancelEquityOrderResponse>>;
async fn current_open_orders(
&self,
params: CurrentOpenOrdersParams,
) -> anyhow::Result<RestApiResponse<Vec<models::CurrentOpenOrdersResponseInner>>>;
async fn equity_order_detail(
&self,
params: EquityOrderDetailParams,
) -> anyhow::Result<RestApiResponse<models::EquityOrderDetailResponse>>;
async fn equity_order_history(
&self,
params: EquityOrderHistoryParams,
) -> anyhow::Result<RestApiResponse<models::EquityOrderHistoryResponse>>;
async fn equity_trade_history(
&self,
params: EquityTradeHistoryParams,
) -> anyhow::Result<RestApiResponse<models::EquityTradeHistoryResponse>>;
async fn place_equity_order(
&self,
params: PlaceEquityOrderParams,
) -> anyhow::Result<RestApiResponse<models::PlaceEquityOrderResponse>>;
}
#[derive(Debug, Clone)]
pub struct TradeApiClient {
configuration: ConfigurationRestApi,
}
impl TradeApiClient {
pub fn new(configuration: ConfigurationRestApi) -> Self {
Self { configuration }
}
}
#[allow(non_camel_case_types)]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum EquityOrderHistoryOrderTypeEnum {
#[serde(rename = "MARKET")]
Market,
#[serde(rename = "LIMIT")]
Limit,
}
impl EquityOrderHistoryOrderTypeEnum {
#[must_use]
pub fn as_str(&self) -> &'static str {
match self {
Self::Market => "MARKET",
Self::Limit => "LIMIT",
}
}
}
impl std::str::FromStr for EquityOrderHistoryOrderTypeEnum {
type Err = Box<dyn std::error::Error + Send + Sync>;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"MARKET" => Ok(Self::Market),
"LIMIT" => Ok(Self::Limit),
other => Err(format!("invalid EquityOrderHistoryOrderTypeEnum: {}", other).into()),
}
}
}
#[allow(non_camel_case_types)]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum EquityOrderHistorySideEnum {
#[serde(rename = "BUY")]
Buy,
#[serde(rename = "SELL")]
Sell,
}
impl EquityOrderHistorySideEnum {
#[must_use]
pub fn as_str(&self) -> &'static str {
match self {
Self::Buy => "BUY",
Self::Sell => "SELL",
}
}
}
impl std::str::FromStr for EquityOrderHistorySideEnum {
type Err = Box<dyn std::error::Error + Send + Sync>;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"BUY" => Ok(Self::Buy),
"SELL" => Ok(Self::Sell),
other => Err(format!("invalid EquityOrderHistorySideEnum: {}", other).into()),
}
}
}
#[allow(non_camel_case_types)]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum EquityTradeHistorySideEnum {
#[serde(rename = "BUY")]
Buy,
#[serde(rename = "SELL")]
Sell,
}
impl EquityTradeHistorySideEnum {
#[must_use]
pub fn as_str(&self) -> &'static str {
match self {
Self::Buy => "BUY",
Self::Sell => "SELL",
}
}
}
impl std::str::FromStr for EquityTradeHistorySideEnum {
type Err = Box<dyn std::error::Error + Send + Sync>;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"BUY" => Ok(Self::Buy),
"SELL" => Ok(Self::Sell),
other => Err(format!("invalid EquityTradeHistorySideEnum: {}", other).into()),
}
}
}
#[allow(non_camel_case_types)]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum PlaceEquityOrderSideEnum {
#[serde(rename = "BUY")]
Buy,
#[serde(rename = "SELL")]
Sell,
}
impl PlaceEquityOrderSideEnum {
#[must_use]
pub fn as_str(&self) -> &'static str {
match self {
Self::Buy => "BUY",
Self::Sell => "SELL",
}
}
}
impl std::str::FromStr for PlaceEquityOrderSideEnum {
type Err = Box<dyn std::error::Error + Send + Sync>;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"BUY" => Ok(Self::Buy),
"SELL" => Ok(Self::Sell),
other => Err(format!("invalid PlaceEquityOrderSideEnum: {}", other).into()),
}
}
}
#[allow(non_camel_case_types)]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum PlaceEquityOrderOrderTypeEnum {
#[serde(rename = "MARKET")]
Market,
#[serde(rename = "LIMIT")]
Limit,
}
impl PlaceEquityOrderOrderTypeEnum {
#[must_use]
pub fn as_str(&self) -> &'static str {
match self {
Self::Market => "MARKET",
Self::Limit => "LIMIT",
}
}
}
impl std::str::FromStr for PlaceEquityOrderOrderTypeEnum {
type Err = Box<dyn std::error::Error + Send + Sync>;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"MARKET" => Ok(Self::Market),
"LIMIT" => Ok(Self::Limit),
other => Err(format!("invalid PlaceEquityOrderOrderTypeEnum: {}", other).into()),
}
}
}
#[allow(non_camel_case_types)]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum PlaceEquityOrderTimeInForceEnum {
#[serde(rename = "DAY")]
Day,
#[serde(rename = "GTC")]
Gtc,
}
impl PlaceEquityOrderTimeInForceEnum {
#[must_use]
pub fn as_str(&self) -> &'static str {
match self {
Self::Day => "DAY",
Self::Gtc => "GTC",
}
}
}
impl std::str::FromStr for PlaceEquityOrderTimeInForceEnum {
type Err = Box<dyn std::error::Error + Send + Sync>;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"DAY" => Ok(Self::Day),
"GTC" => Ok(Self::Gtc),
other => Err(format!("invalid PlaceEquityOrderTimeInForceEnum: {}", other).into()),
}
}
}
#[allow(non_camel_case_types)]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum PlaceEquityOrderTradingSessionEnum {
#[serde(rename = "RTH")]
Rth,
#[serde(rename = "EXTENDED")]
Extended,
#[serde(rename = "24H")]
TradingSession24H,
}
impl PlaceEquityOrderTradingSessionEnum {
#[must_use]
pub fn as_str(&self) -> &'static str {
match self {
Self::Rth => "RTH",
Self::Extended => "EXTENDED",
Self::TradingSession24H => "24H",
}
}
}
impl std::str::FromStr for PlaceEquityOrderTradingSessionEnum {
type Err = Box<dyn std::error::Error + Send + Sync>;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"RTH" => Ok(Self::Rth),
"EXTENDED" => Ok(Self::Extended),
"24H" => Ok(Self::TradingSession24H),
other => Err(format!("invalid PlaceEquityOrderTradingSessionEnum: {}", other).into()),
}
}
}
#[allow(non_camel_case_types)]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum PlaceEquityOrderWalletTypeEnum {
#[serde(rename = "CARD")]
Card,
#[serde(rename = "MAIN")]
Main,
}
impl PlaceEquityOrderWalletTypeEnum {
#[must_use]
pub fn as_str(&self) -> &'static str {
match self {
Self::Card => "CARD",
Self::Main => "MAIN",
}
}
}
impl std::str::FromStr for PlaceEquityOrderWalletTypeEnum {
type Err = Box<dyn std::error::Error + Send + Sync>;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"CARD" => Ok(Self::Card),
"MAIN" => Ok(Self::Main),
other => Err(format!("invalid PlaceEquityOrderWalletTypeEnum: {}", other).into()),
}
}
}
/// Request parameters for the [`cancel_all_equity_orders`] operation.
///
/// This struct holds all of the inputs you can pass when calling
/// [`cancel_all_equity_orders`](#method.cancel_all_equity_orders).
#[derive(Clone, Debug, Builder, Deserialize, Default)]
#[builder(pattern = "owned", build_fn(error = "ParamBuildError"))]
pub struct CancelAllEquityOrdersParams {
/// The value cannot be greater than `60000`.
///
/// This field is **optional.
#[builder(setter(into), default)]
#[serde(rename = "recvWindow", default)]
pub recv_window: Option<i64>,
}
impl CancelAllEquityOrdersParams {
/// Create a builder for [`cancel_all_equity_orders`].
///
#[must_use]
pub fn builder() -> CancelAllEquityOrdersParamsBuilder {
CancelAllEquityOrdersParamsBuilder::default()
}
}
/// Request parameters for the [`cancel_equity_order`] operation.
///
/// This struct holds all of the inputs you can pass when calling
/// [`cancel_equity_order`](#method.cancel_equity_order).
#[derive(Clone, Debug, Builder, Deserialize)]
#[builder(pattern = "owned", build_fn(error = "ParamBuildError"))]
pub struct CancelEquityOrderParams {
/// Equity order id returned by `/order/place` or a query endpoint.
///
/// This field is **required.
#[builder(setter(into))]
#[serde(rename = "orderId")]
pub order_id: String,
/// The value cannot be greater than `60000`.
///
/// This field is **optional.
#[builder(setter(into), default)]
#[serde(rename = "recvWindow", default)]
pub recv_window: Option<i64>,
}
impl CancelEquityOrderParams {
/// Create a builder for [`cancel_equity_order`].
///
/// Required parameters:
///
/// * `order_id` — Equity order id returned by `/order/place` or a query endpoint.
///
#[must_use]
pub fn builder(order_id: String) -> CancelEquityOrderParamsBuilder {
CancelEquityOrderParamsBuilder::default().order_id(order_id)
}
}
/// Request parameters for the [`current_open_orders`] operation.
///
/// This struct holds all of the inputs you can pass when calling
/// [`current_open_orders`](#method.current_open_orders).
#[derive(Clone, Debug, Builder, Deserialize, Default)]
#[builder(pattern = "owned", build_fn(error = "ParamBuildError"))]
pub struct CurrentOpenOrdersParams {
/// The value cannot be greater than `60000`.
///
/// This field is **optional.
#[builder(setter(into), default)]
#[serde(rename = "recvWindow", default)]
pub recv_window: Option<i64>,
}
impl CurrentOpenOrdersParams {
/// Create a builder for [`current_open_orders`].
///
#[must_use]
pub fn builder() -> CurrentOpenOrdersParamsBuilder {
CurrentOpenOrdersParamsBuilder::default()
}
}
/// Request parameters for the [`equity_order_detail`] operation.
///
/// This struct holds all of the inputs you can pass when calling
/// [`equity_order_detail`](#method.equity_order_detail).
#[derive(Clone, Debug, Builder, Deserialize, Default)]
#[builder(pattern = "owned", build_fn(error = "ParamBuildError"))]
pub struct EquityOrderDetailParams {
/// Equity order id. Either `orderId` or `clientOrderId` must be provided.
///
/// This field is **optional.
#[builder(setter(into), default)]
#[serde(rename = "orderId", default)]
pub order_id: Option<String>,
/// Client-supplied order id. Either `orderId` or `clientOrderId` must be provided.
///
/// This field is **optional.
#[builder(setter(into), default)]
#[serde(rename = "clientOrderId", default)]
pub client_order_id: Option<String>,
/// The value cannot be greater than `60000`.
///
/// This field is **optional.
#[builder(setter(into), default)]
#[serde(rename = "recvWindow", default)]
pub recv_window: Option<i64>,
}
impl EquityOrderDetailParams {
/// Create a builder for [`equity_order_detail`].
///
#[must_use]
pub fn builder() -> EquityOrderDetailParamsBuilder {
EquityOrderDetailParamsBuilder::default()
}
}
/// Request parameters for the [`equity_order_history`] operation.
///
/// This struct holds all of the inputs you can pass when calling
/// [`equity_order_history`](#method.equity_order_history).
#[derive(Clone, Debug, Builder, Deserialize)]
#[builder(pattern = "owned", build_fn(error = "ParamBuildError"))]
pub struct EquityOrderHistoryParams {
/// Start time (ms epoch).
///
/// This field is **required.
#[builder(setter(into))]
#[serde(rename = "startTime")]
pub start_time: i64,
/// End time (ms epoch).
///
/// This field is **required.
#[builder(setter(into))]
#[serde(rename = "endTime")]
pub end_time: i64,
/// US-equity ticker filter, e.g. `NVDA`.
///
/// This field is **optional.
#[builder(setter(into), default)]
#[serde(rename = "symbol", default)]
pub symbol: Option<String>,
/// Order type filter: `MARKET` / `LIMIT`.
///
/// This field is **optional.
#[builder(setter(into), default)]
#[serde(rename = "orderType", default)]
pub order_type: Option<EquityOrderHistoryOrderTypeEnum>,
/// Side filter: `BUY` / `SELL`.
///
/// This field is **optional.
#[builder(setter(into), default)]
#[serde(rename = "side", default)]
pub side: Option<EquityOrderHistorySideEnum>,
/// Comma-separated status filter. Allowed values: `FILLED`, `PARTIALLY_FILLED`, `CANCELED`, `EXPIRED`, `REJECTED`.
///
/// This field is **optional.
#[builder(setter(into), default)]
#[serde(rename = "orderStatus", default)]
pub order_status: Option<String>,
/// Page number, 1-based. Default `1`.
///
/// This field is **optional.
#[builder(setter(into), default)]
#[serde(rename = "current", default)]
pub current: Option<i32>,
/// Page size. Default `20`, max `100`.
///
/// This field is **optional.
#[builder(setter(into), default)]
#[serde(rename = "size", default)]
pub size: Option<i32>,
/// The value cannot be greater than `60000`.
///
/// This field is **optional.
#[builder(setter(into), default)]
#[serde(rename = "recvWindow", default)]
pub recv_window: Option<i64>,
}
impl EquityOrderHistoryParams {
/// Create a builder for [`equity_order_history`].
///
/// Required parameters:
///
/// * `start_time` — Start time (ms epoch).
/// * `end_time` — End time (ms epoch).
///
#[must_use]
pub fn builder(start_time: i64, end_time: i64) -> EquityOrderHistoryParamsBuilder {
EquityOrderHistoryParamsBuilder::default()
.start_time(start_time)
.end_time(end_time)
}
}
/// Request parameters for the [`equity_trade_history`] operation.
///
/// This struct holds all of the inputs you can pass when calling
/// [`equity_trade_history`](#method.equity_trade_history).
#[derive(Clone, Debug, Builder, Deserialize)]
#[builder(pattern = "owned", build_fn(error = "ParamBuildError"))]
pub struct EquityTradeHistoryParams {
/// Start time (ms epoch).
///
/// This field is **required.
#[builder(setter(into))]
#[serde(rename = "startTime")]
pub start_time: i64,
/// End time (ms epoch).
///
/// This field is **required.
#[builder(setter(into))]
#[serde(rename = "endTime")]
pub end_time: i64,
/// US-equity ticker filter, e.g. `NVDA`.
///
/// This field is **optional.
#[builder(setter(into), default)]
#[serde(rename = "symbol", default)]
pub symbol: Option<String>,
/// Side filter: `BUY` / `SELL`.
///
/// This field is **optional.
#[builder(setter(into), default)]
#[serde(rename = "side", default)]
pub side: Option<EquityTradeHistorySideEnum>,
/// Narrow the result to executions of a single order.
///
/// This field is **optional.
#[builder(setter(into), default)]
#[serde(rename = "orderId", default)]
pub order_id: Option<String>,
/// Page number, 1-based. Default `1`.
///
/// This field is **optional.
#[builder(setter(into), default)]
#[serde(rename = "current", default)]
pub current: Option<i32>,
/// Page size. Default `20`, max `100`.
///
/// This field is **optional.
#[builder(setter(into), default)]
#[serde(rename = "size", default)]
pub size: Option<i32>,
/// The value cannot be greater than `60000`.
///
/// This field is **optional.
#[builder(setter(into), default)]
#[serde(rename = "recvWindow", default)]
pub recv_window: Option<i64>,
}
impl EquityTradeHistoryParams {
/// Create a builder for [`equity_trade_history`].
///
/// Required parameters:
///
/// * `start_time` — Start time (ms epoch).
/// * `end_time` — End time (ms epoch).
///
#[must_use]
pub fn builder(start_time: i64, end_time: i64) -> EquityTradeHistoryParamsBuilder {
EquityTradeHistoryParamsBuilder::default()
.start_time(start_time)
.end_time(end_time)
}
}
/// Request parameters for the [`place_equity_order`] operation.
///
/// This struct holds all of the inputs you can pass when calling
/// [`place_equity_order`](#method.place_equity_order).
#[derive(Clone, Debug, Builder, Deserialize)]
#[builder(pattern = "owned", build_fn(error = "ParamBuildError"))]
pub struct PlaceEquityOrderParams {
/// US stock ticker, e.g. `AAPL`, `TSLA`. Must be a symbol with tokenization enabled — check via `/market/tokenized-assets`.
///
/// This field is **required.
#[builder(setter(into))]
#[serde(rename = "symbol")]
pub symbol: String,
/// `BUY` / `SELL`.
///
/// This field is **required.
#[builder(setter(into))]
#[serde(rename = "side")]
pub side: PlaceEquityOrderSideEnum,
/// `MARKET` / `LIMIT`.
///
/// This field is **required.
#[builder(setter(into))]
#[serde(rename = "orderType")]
pub order_type: PlaceEquityOrderOrderTypeEnum,
/// Quote asset. Defaults to `USDC`; must be within the server's allowed set.
///
/// This field is **optional.
#[builder(setter(into), default)]
#[serde(rename = "quoteAsset", default)]
pub quote_asset: Option<String>,
/// **Required** for `LIMIT`; **forbidden** for `MARKET`. Maximum 2 decimal places.
///
/// This field is **optional.
#[builder(setter(into), default)]
#[serde(rename = "price", default)]
pub price: Option<String>,
/// **Required** for `LIMIT` (both sides) and `SELL MARKET`; **forbidden** for `BUY MARKET`.
///
/// This field is **optional.
#[builder(setter(into), default)]
#[serde(rename = "quantity", default)]
pub quantity: Option<String>,
/// **Required** for `BUY MARKET`; **forbidden** for `LIMIT` and `SELL MARKET`.
///
/// This field is **optional.
#[builder(setter(into), default)]
#[serde(rename = "notional", default)]
pub notional: Option<String>,
/// `DAY` (default) / `GTC`. `GTC` is only supported for `LIMIT` orders; a fractional-share `GTC` order must be paired with `tradingSession = EXTENDED` or `24H`.
///
/// This field is **optional.
#[builder(setter(into), default)]
#[serde(rename = "timeInForce", default)]
pub time_in_force: Option<PlaceEquityOrderTimeInForceEnum>,
/// `RTH` / `EXTENDED` / `24H`. **Required** for `LIMIT`; **forbidden** for `MARKET`.
///
/// This field is **optional.
#[builder(setter(into), default)]
#[serde(rename = "tradingSession", default)]
pub trading_session: Option<PlaceEquityOrderTradingSessionEnum>,
/// Payment wallet for `BUY` orders: `CARD` (default) / `MAIN`. `SELL` orders always settle to `CARD`.
///
/// This field is **optional.
#[builder(setter(into), default)]
#[serde(rename = "walletType", default)]
pub wallet_type: Option<PlaceEquityOrderWalletTypeEnum>,
/// Client-supplied order id. Format `^[a-zA-Z0-9-_]{32,36}$`. Auto-generated when omitted.
///
/// This field is **optional.
#[builder(setter(into), default)]
#[serde(rename = "clientOrderId", default)]
pub client_order_id: Option<String>,
/// Whether to tokenize the purchased stock asset upon settlement. Default `true`. Set to `false` to receive the underlying equity directly instead of a tokenized asset.
///
/// This field is **optional.
#[builder(setter(into), default)]
#[serde(rename = "tokenize", default)]
pub tokenize: Option<bool>,
/// The value cannot be greater than `60000`.
///
/// This field is **optional.
#[builder(setter(into), default)]
#[serde(rename = "recvWindow", default)]
pub recv_window: Option<i64>,
}
impl PlaceEquityOrderParams {
/// Create a builder for [`place_equity_order`].
///
/// Required parameters:
///
/// * `symbol` — US stock ticker, e.g. `AAPL`, `TSLA`. Must be a symbol with tokenization enabled — check via `/market/tokenized-assets`.
/// * `side` — `BUY` / `SELL`.
/// * `order_type` — `MARKET` / `LIMIT`.
///
#[must_use]
pub fn builder(
symbol: String,
side: PlaceEquityOrderSideEnum,
order_type: PlaceEquityOrderOrderTypeEnum,
) -> PlaceEquityOrderParamsBuilder {
PlaceEquityOrderParamsBuilder::default()
.symbol(symbol)
.side(side)
.order_type(order_type)
}
}
#[async_trait]
impl TradeApi for TradeApiClient {
async fn cancel_all_equity_orders(
&self,
params: CancelAllEquityOrdersParams,
) -> anyhow::Result<RestApiResponse<models::CancelAllEquityOrdersResponse>> {
let CancelAllEquityOrdersParams { recv_window } = params;
let mut query_params = BTreeMap::new();
let body_params = BTreeMap::new();
if let Some(rw) = recv_window {
query_params.insert("recvWindow".to_string(), json!(rw));
}
send_request::<models::CancelAllEquityOrdersResponse>(
&self.configuration,
"/sapi/v1/equity/order/cancel-all",
reqwest::Method::POST,
query_params,
body_params,
if HAS_TIME_UNIT {
self.configuration.time_unit
} else {
None
},
true,
)
.await
}
async fn cancel_equity_order(
&self,
params: CancelEquityOrderParams,
) -> anyhow::Result<RestApiResponse<models::CancelEquityOrderResponse>> {
let CancelEquityOrderParams {
order_id,
recv_window,
} = params;
let mut query_params = BTreeMap::new();
let body_params = BTreeMap::new();
query_params.insert("orderId".to_string(), json!(order_id));
if let Some(rw) = recv_window {
query_params.insert("recvWindow".to_string(), json!(rw));
}
send_request::<models::CancelEquityOrderResponse>(
&self.configuration,
"/sapi/v1/equity/order/cancel",
reqwest::Method::POST,
query_params,
body_params,
if HAS_TIME_UNIT {
self.configuration.time_unit
} else {
None
},
true,
)
.await
}
async fn current_open_orders(
&self,
params: CurrentOpenOrdersParams,
) -> anyhow::Result<RestApiResponse<Vec<models::CurrentOpenOrdersResponseInner>>> {
let CurrentOpenOrdersParams { recv_window } = params;
let mut query_params = BTreeMap::new();
let body_params = BTreeMap::new();
if let Some(rw) = recv_window {
query_params.insert("recvWindow".to_string(), json!(rw));
}
send_request::<Vec<models::CurrentOpenOrdersResponseInner>>(
&self.configuration,
"/sapi/v1/equity/order/open-orders",
reqwest::Method::GET,
query_params,
body_params,
if HAS_TIME_UNIT {
self.configuration.time_unit
} else {
None
},
true,
)
.await
}
async fn equity_order_detail(
&self,
params: EquityOrderDetailParams,
) -> anyhow::Result<RestApiResponse<models::EquityOrderDetailResponse>> {
let EquityOrderDetailParams {
order_id,
client_order_id,
recv_window,
} = params;
let mut query_params = BTreeMap::new();
let body_params = BTreeMap::new();
if let Some(rw) = order_id {
query_params.insert("orderId".to_string(), json!(rw));
}
if let Some(rw) = client_order_id {
query_params.insert("clientOrderId".to_string(), json!(rw));
}
if let Some(rw) = recv_window {
query_params.insert("recvWindow".to_string(), json!(rw));
}
send_request::<models::EquityOrderDetailResponse>(
&self.configuration,
"/sapi/v1/equity/order/detail",
reqwest::Method::GET,
query_params,
body_params,
if HAS_TIME_UNIT {
self.configuration.time_unit
} else {
None
},
true,
)
.await
}
async fn equity_order_history(
&self,
params: EquityOrderHistoryParams,
) -> anyhow::Result<RestApiResponse<models::EquityOrderHistoryResponse>> {
let EquityOrderHistoryParams {
start_time,
end_time,
symbol,
order_type,
side,
order_status,
current,
size,
recv_window,
} = params;
let mut query_params = BTreeMap::new();
let body_params = BTreeMap::new();
if let Some(rw) = symbol {
query_params.insert("symbol".to_string(), json!(rw));
}
if let Some(rw) = order_type {
query_params.insert("orderType".to_string(), json!(rw));
}
if let Some(rw) = side {
query_params.insert("side".to_string(), json!(rw));
}
if let Some(rw) = order_status {
query_params.insert("orderStatus".to_string(), json!(rw));
}
query_params.insert("startTime".to_string(), json!(start_time));
query_params.insert("endTime".to_string(), json!(end_time));
if let Some(rw) = current {
query_params.insert("current".to_string(), json!(rw));
}
if let Some(rw) = size {
query_params.insert("size".to_string(), json!(rw));
}
if let Some(rw) = recv_window {
query_params.insert("recvWindow".to_string(), json!(rw));
}
send_request::<models::EquityOrderHistoryResponse>(
&self.configuration,
"/sapi/v1/equity/order/history",
reqwest::Method::GET,
query_params,
body_params,
if HAS_TIME_UNIT {
self.configuration.time_unit
} else {
None
},
true,
)
.await
}
async fn equity_trade_history(
&self,
params: EquityTradeHistoryParams,
) -> anyhow::Result<RestApiResponse<models::EquityTradeHistoryResponse>> {
let EquityTradeHistoryParams {
start_time,
end_time,
symbol,
side,
order_id,
current,
size,
recv_window,
} = params;
let mut query_params = BTreeMap::new();
let body_params = BTreeMap::new();
if let Some(rw) = symbol {
query_params.insert("symbol".to_string(), json!(rw));
}
if let Some(rw) = side {
query_params.insert("side".to_string(), json!(rw));
}
if let Some(rw) = order_id {
query_params.insert("orderId".to_string(), json!(rw));
}
query_params.insert("startTime".to_string(), json!(start_time));
query_params.insert("endTime".to_string(), json!(end_time));
if let Some(rw) = current {
query_params.insert("current".to_string(), json!(rw));
}
if let Some(rw) = size {
query_params.insert("size".to_string(), json!(rw));
}
if let Some(rw) = recv_window {
query_params.insert("recvWindow".to_string(), json!(rw));
}
send_request::<models::EquityTradeHistoryResponse>(
&self.configuration,
"/sapi/v1/equity/trade/history",
reqwest::Method::GET,
query_params,
body_params,
if HAS_TIME_UNIT {
self.configuration.time_unit
} else {
None
},
true,
)
.await
}
async fn place_equity_order(
&self,
params: PlaceEquityOrderParams,
) -> anyhow::Result<RestApiResponse<models::PlaceEquityOrderResponse>> {
let PlaceEquityOrderParams {
symbol,
side,
order_type,
quote_asset,
price,
quantity,
notional,
time_in_force,
trading_session,
wallet_type,
client_order_id,
tokenize,
recv_window,
} = params;
let mut query_params = BTreeMap::new();
let body_params = BTreeMap::new();
query_params.insert("symbol".to_string(), json!(symbol));
if let Some(rw) = quote_asset {
query_params.insert("quoteAsset".to_string(), json!(rw));
}
query_params.insert("side".to_string(), json!(side));
query_params.insert("orderType".to_string(), json!(order_type));
if let Some(rw) = price {
query_params.insert("price".to_string(), json!(rw));
}
if let Some(rw) = quantity {
query_params.insert("quantity".to_string(), json!(rw));
}
if let Some(rw) = notional {
query_params.insert("notional".to_string(), json!(rw));
}
if let Some(rw) = time_in_force {
query_params.insert("timeInForce".to_string(), json!(rw));
}
if let Some(rw) = trading_session {
query_params.insert("tradingSession".to_string(), json!(rw));
}
if let Some(rw) = wallet_type {
query_params.insert("walletType".to_string(), json!(rw));
}
if let Some(rw) = client_order_id {
query_params.insert("clientOrderId".to_string(), json!(rw));
}
if let Some(rw) = tokenize {
query_params.insert("tokenize".to_string(), json!(rw));
}
if let Some(rw) = recv_window {
query_params.insert("recvWindow".to_string(), json!(rw));
}
send_request::<models::PlaceEquityOrderResponse>(
&self.configuration,
"/sapi/v1/equity/order/place",
reqwest::Method::POST,
query_params,
body_params,
if HAS_TIME_UNIT {
self.configuration.time_unit
} else {
None
},
true,
)
.await
}
}
#[cfg(all(test, feature = "stocks"))]
mod tests {
use super::*;
use crate::TOKIO_SHARED_RT;
use crate::{errors::ConnectorError, models::DataFuture, models::RestApiRateLimit};
use async_trait::async_trait;
use std::collections::HashMap;
struct DummyRestApiResponse<T> {
inner: Box<dyn FnOnce() -> DataFuture<Result<T, ConnectorError>> + Send + Sync>,
status: u16,
headers: HashMap<String, String>,
rate_limits: Option<Vec<RestApiRateLimit>>,
}
impl<T> From<DummyRestApiResponse<T>> for RestApiResponse<T> {
fn from(dummy: DummyRestApiResponse<T>) -> Self {
Self {
data_fn: dummy.inner,
status: dummy.status,
headers: dummy.headers,
rate_limits: dummy.rate_limits,
}
}
}
struct MockTradeApiClient {
force_error: bool,
}
#[async_trait]
impl TradeApi for MockTradeApiClient {
async fn cancel_all_equity_orders(
&self,
_params: CancelAllEquityOrdersParams,
) -> anyhow::Result<RestApiResponse<models::CancelAllEquityOrdersResponse>> {
if self.force_error {
return Err(ConnectorError::ConnectorClientError {
msg: "ResponseError".to_string(),
code: None,
}
.into());
}
let resp_json: Value = serde_json::from_str(r#"{"success":true}"#)
.unwrap_or_else(|_| serde_json::json!({}));
let dummy_response: models::CancelAllEquityOrdersResponse =
serde_json::from_value(resp_json.clone())
.expect("should parse into models::CancelAllEquityOrdersResponse");
let dummy = DummyRestApiResponse {
inner: Box::new(move || Box::pin(async move { Ok(dummy_response) })),
status: 200,
headers: HashMap::new(),
rate_limits: None,
};
Ok(dummy.into())
}
async fn cancel_equity_order(
&self,
_params: CancelEquityOrderParams,
) -> anyhow::Result<RestApiResponse<models::CancelEquityOrderResponse>> {
if self.force_error {
return Err(ConnectorError::ConnectorClientError {
msg: "ResponseError".to_string(),
code: None,
}
.into());
}
let resp_json: Value = serde_json::from_str(
r#"{"orderId":"c3c58f49-7b0d-4b9e-a2db-1a2f9a3b8c71","status":"S"}"#,
)
.unwrap_or_else(|_| serde_json::json!({}));
let dummy_response: models::CancelEquityOrderResponse =
serde_json::from_value(resp_json.clone())
.expect("should parse into models::CancelEquityOrderResponse");
let dummy = DummyRestApiResponse {
inner: Box::new(move || Box::pin(async move { Ok(dummy_response) })),
status: 200,
headers: HashMap::new(),
rate_limits: None,
};
Ok(dummy.into())
}
async fn current_open_orders(
&self,
_params: CurrentOpenOrdersParams,
) -> anyhow::Result<RestApiResponse<Vec<models::CurrentOpenOrdersResponseInner>>> {
if self.force_error {
return Err(ConnectorError::ConnectorClientError {
msg: "ResponseError".to_string(),
code: None,
}
.into());
}
let resp_json: Value = serde_json::from_str(r#"[{"orderId":"c3c58f49-7b0d-4b9e-a2db-1a2f9a3b8c71","symbol":"AAPL","quote":"USDC","side":"BUY","orderType":"LIMIT","limitPrice":"180.50","avgFilledPrice":"avgFilledPrice","qty":"1","notional":"notional","filledQty":"0","filledTotal":"filledTotal","fee":"0","session":"RTH","status":"NEW","createdAt":1735900000000,"updatedAt":1735900000000}]"#).unwrap_or_else(|_| serde_json::json!({}));
let dummy_response: Vec<models::CurrentOpenOrdersResponseInner> =
serde_json::from_value(resp_json.clone())
.expect("should parse into Vec<models::CurrentOpenOrdersResponseInner>");
let dummy = DummyRestApiResponse {
inner: Box::new(move || Box::pin(async move { Ok(dummy_response) })),
status: 200,
headers: HashMap::new(),
rate_limits: None,
};
Ok(dummy.into())
}
async fn equity_order_detail(
&self,
_params: EquityOrderDetailParams,
) -> anyhow::Result<RestApiResponse<models::EquityOrderDetailResponse>> {
if self.force_error {
return Err(ConnectorError::ConnectorClientError {
msg: "ResponseError".to_string(),
code: None,
}
.into());
}
let resp_json: Value = serde_json::from_str(r#"{"orderId":"c3c58f49-7b0d-4b9e-a2db-1a2f9a3b8c71","clientOrderId":"web_2c9c92b74f1e4a7c8f3b9e1a2d3c4b5a","symbol":"AAPL","quote":"USDC","side":"BUY","orderType":"LIMIT","limitPrice":"180.50","avgFilledPrice":"180.48","qty":"1","notional":"notional","filledQty":"1","filledTotal":"filledTotal","fee":"0.10","session":"RTH","status":"FILLED","createdAt":1735900000000,"updatedAt":1735900120000,"trades":[{"executionId":"exec-20260504-0001","executionAt":1735900115000,"price":"180.48","qty":"1"}]}"#).unwrap_or_else(|_| serde_json::json!({}));
let dummy_response: models::EquityOrderDetailResponse =
serde_json::from_value(resp_json.clone())
.expect("should parse into models::EquityOrderDetailResponse");
let dummy = DummyRestApiResponse {
inner: Box::new(move || Box::pin(async move { Ok(dummy_response) })),
status: 200,
headers: HashMap::new(),
rate_limits: None,
};
Ok(dummy.into())
}
async fn equity_order_history(
&self,
_params: EquityOrderHistoryParams,
) -> anyhow::Result<RestApiResponse<models::EquityOrderHistoryResponse>> {
if self.force_error {
return Err(ConnectorError::ConnectorClientError {
msg: "ResponseError".to_string(),
code: None,
}
.into());
}
let resp_json: Value = serde_json::from_str(r#"{"total":2,"page":1,"size":20,"rows":[{"orderId":"c3c58f49-7b0d-4b9e-a2db-1a2f9a3b8c71","symbol":"AAPL","quote":"USDC","side":"BUY","orderType":"LIMIT","limitPrice":"180.50","avgFilledPrice":"180.48","qty":"1","notional":"notional","filledQty":"1","filledTotal":"filledTotal","fee":"0.10","session":"RTH","status":"FILLED","createdAt":1735900000000,"updatedAt":1735900120000}]}"#).unwrap_or_else(|_| serde_json::json!({}));
let dummy_response: models::EquityOrderHistoryResponse =
serde_json::from_value(resp_json.clone())
.expect("should parse into models::EquityOrderHistoryResponse");
let dummy = DummyRestApiResponse {
inner: Box::new(move || Box::pin(async move { Ok(dummy_response) })),
status: 200,
headers: HashMap::new(),
rate_limits: None,
};
Ok(dummy.into())
}
async fn equity_trade_history(
&self,
_params: EquityTradeHistoryParams,
) -> anyhow::Result<RestApiResponse<models::EquityTradeHistoryResponse>> {
if self.force_error {
return Err(ConnectorError::ConnectorClientError {
msg: "ResponseError".to_string(),
code: None,
}
.into());
}
let resp_json: Value = serde_json::from_str(r#"{"total":1,"page":1,"size":20,"rows":[{"executionId":"exec-20260504-0001","orderId":"c3c58f49-7b0d-4b9e-a2db-1a2f9a3b8c71","symbol":"AAPL","quote":"USDC","side":"BUY","orderType":"LIMIT","price":"180.50","qty":"1","total":"180.50","executionAt":1735900115000,"updatedAt":1735900115200}]}"#).unwrap_or_else(|_| serde_json::json!({}));
let dummy_response: models::EquityTradeHistoryResponse =
serde_json::from_value(resp_json.clone())
.expect("should parse into models::EquityTradeHistoryResponse");
let dummy = DummyRestApiResponse {
inner: Box::new(move || Box::pin(async move { Ok(dummy_response) })),
status: 200,
headers: HashMap::new(),
rate_limits: None,
};
Ok(dummy.into())
}
async fn place_equity_order(
&self,
_params: PlaceEquityOrderParams,
) -> anyhow::Result<RestApiResponse<models::PlaceEquityOrderResponse>> {
if self.force_error {
return Err(ConnectorError::ConnectorClientError {
msg: "ResponseError".to_string(),
code: None,
}
.into());
}
let resp_json: Value = serde_json::from_str(r#"{"status":"S","orderId":"c3c58f49-7b0d-4b9e-a2db-1a2f9a3b8c71","clientOrderId":"web_2c9c92b74f1e4a7c8f3b9e1a2d3c4b5a"}"#).unwrap_or_else(|_| serde_json::json!({}));
let dummy_response: models::PlaceEquityOrderResponse =
serde_json::from_value(resp_json.clone())
.expect("should parse into models::PlaceEquityOrderResponse");
let dummy = DummyRestApiResponse {
inner: Box::new(move || Box::pin(async move { Ok(dummy_response) })),
status: 200,
headers: HashMap::new(),
rate_limits: None,
};
Ok(dummy.into())
}
}
#[test]
fn cancel_all_equity_orders_required_params_success() {
TOKIO_SHARED_RT.block_on(async {
let client = MockTradeApiClient { force_error: false };
let params = CancelAllEquityOrdersParams::builder().build().unwrap();
let resp_json: Value = serde_json::from_str(r#"{"success":true}"#)
.unwrap_or_else(|_| serde_json::json!({}));
let expected_response: models::CancelAllEquityOrdersResponse =
serde_json::from_value(resp_json.clone())
.expect("should parse into models::CancelAllEquityOrdersResponse");
let resp = client
.cancel_all_equity_orders(params)
.await
.expect("Expected a response");
let data_future = resp.data();
let actual_response = data_future.await.unwrap();
assert_eq!(actual_response, expected_response);
});
}
#[test]
fn cancel_all_equity_orders_optional_params_success() {
TOKIO_SHARED_RT.block_on(async {
let client = MockTradeApiClient { force_error: false };
let params = CancelAllEquityOrdersParams::builder()
.recv_window(5000)
.build()
.unwrap();
let resp_json: Value = serde_json::from_str(r#"{"success":true}"#)
.unwrap_or_else(|_| serde_json::json!({}));
let expected_response: models::CancelAllEquityOrdersResponse =
serde_json::from_value(resp_json.clone())
.expect("should parse into models::CancelAllEquityOrdersResponse");
let resp = client
.cancel_all_equity_orders(params)
.await
.expect("Expected a response");
let data_future = resp.data();
let actual_response = data_future.await.unwrap();
assert_eq!(actual_response, expected_response);
});
}
#[test]
fn cancel_all_equity_orders_response_error() {
TOKIO_SHARED_RT.block_on(async {
let client = MockTradeApiClient { force_error: true };
let params = CancelAllEquityOrdersParams::builder().build().unwrap();
match client.cancel_all_equity_orders(params).await {
Ok(_) => panic!("Expected an error"),
Err(err) => {
assert_eq!(err.to_string(), "Connector client error: ResponseError");
}
}
});
}
#[test]
fn cancel_equity_order_required_params_success() {
TOKIO_SHARED_RT.block_on(async {
let client = MockTradeApiClient { force_error: false };
let params = CancelEquityOrderParams::builder(
"c3c58f49-7b0d-4b9e-a2db-1a2f9a3b8c71".to_string(),
)
.build()
.unwrap();
let resp_json: Value = serde_json::from_str(
r#"{"orderId":"c3c58f49-7b0d-4b9e-a2db-1a2f9a3b8c71","status":"S"}"#,
)
.unwrap_or_else(|_| serde_json::json!({}));
let expected_response: models::CancelEquityOrderResponse =
serde_json::from_value(resp_json.clone())
.expect("should parse into models::CancelEquityOrderResponse");
let resp = client
.cancel_equity_order(params)
.await
.expect("Expected a response");
let data_future = resp.data();
let actual_response = data_future.await.unwrap();
assert_eq!(actual_response, expected_response);
});
}
#[test]
fn cancel_equity_order_optional_params_success() {
TOKIO_SHARED_RT.block_on(async {
let client = MockTradeApiClient { force_error: false };
let params = CancelEquityOrderParams::builder(
"c3c58f49-7b0d-4b9e-a2db-1a2f9a3b8c71".to_string(),
)
.recv_window(5000)
.build()
.unwrap();
let resp_json: Value = serde_json::from_str(
r#"{"orderId":"c3c58f49-7b0d-4b9e-a2db-1a2f9a3b8c71","status":"S"}"#,
)
.unwrap_or_else(|_| serde_json::json!({}));
let expected_response: models::CancelEquityOrderResponse =
serde_json::from_value(resp_json.clone())
.expect("should parse into models::CancelEquityOrderResponse");
let resp = client
.cancel_equity_order(params)
.await
.expect("Expected a response");
let data_future = resp.data();
let actual_response = data_future.await.unwrap();
assert_eq!(actual_response, expected_response);
});
}
#[test]
fn cancel_equity_order_response_error() {
TOKIO_SHARED_RT.block_on(async {
let client = MockTradeApiClient { force_error: true };
let params = CancelEquityOrderParams::builder(
"c3c58f49-7b0d-4b9e-a2db-1a2f9a3b8c71".to_string(),
)
.build()
.unwrap();
match client.cancel_equity_order(params).await {
Ok(_) => panic!("Expected an error"),
Err(err) => {
assert_eq!(err.to_string(), "Connector client error: ResponseError");
}
}
});
}
#[test]
fn current_open_orders_required_params_success() {
TOKIO_SHARED_RT.block_on(async {
let client = MockTradeApiClient { force_error: false };
let params = CurrentOpenOrdersParams::builder().build().unwrap();
let resp_json: Value = serde_json::from_str(r#"[{"orderId":"c3c58f49-7b0d-4b9e-a2db-1a2f9a3b8c71","symbol":"AAPL","quote":"USDC","side":"BUY","orderType":"LIMIT","limitPrice":"180.50","avgFilledPrice":"avgFilledPrice","qty":"1","notional":"notional","filledQty":"0","filledTotal":"filledTotal","fee":"0","session":"RTH","status":"NEW","createdAt":1735900000000,"updatedAt":1735900000000}]"#).unwrap_or_else(|_| serde_json::json!({}));
let expected_response : Vec<models::CurrentOpenOrdersResponseInner> = serde_json::from_value(resp_json.clone()).expect("should parse into Vec<models::CurrentOpenOrdersResponseInner>");
let resp = client.current_open_orders(params).await.expect("Expected a response");
let data_future = resp.data();
let actual_response = data_future.await.unwrap();
assert_eq!(actual_response, expected_response);
});
}
#[test]
fn current_open_orders_optional_params_success() {
TOKIO_SHARED_RT.block_on(async {
let client = MockTradeApiClient { force_error: false };
let params = CurrentOpenOrdersParams::builder().recv_window(5000).build().unwrap();
let resp_json: Value = serde_json::from_str(r#"[{"orderId":"c3c58f49-7b0d-4b9e-a2db-1a2f9a3b8c71","symbol":"AAPL","quote":"USDC","side":"BUY","orderType":"LIMIT","limitPrice":"180.50","avgFilledPrice":"avgFilledPrice","qty":"1","notional":"notional","filledQty":"0","filledTotal":"filledTotal","fee":"0","session":"RTH","status":"NEW","createdAt":1735900000000,"updatedAt":1735900000000}]"#).unwrap_or_else(|_| serde_json::json!({}));
let expected_response : Vec<models::CurrentOpenOrdersResponseInner> = serde_json::from_value(resp_json.clone()).expect("should parse into Vec<models::CurrentOpenOrdersResponseInner>");
let resp = client.current_open_orders(params).await.expect("Expected a response");
let data_future = resp.data();
let actual_response = data_future.await.unwrap();
assert_eq!(actual_response, expected_response);
});
}
#[test]
fn current_open_orders_response_error() {
TOKIO_SHARED_RT.block_on(async {
let client = MockTradeApiClient { force_error: true };
let params = CurrentOpenOrdersParams::builder().build().unwrap();
match client.current_open_orders(params).await {
Ok(_) => panic!("Expected an error"),
Err(err) => {
assert_eq!(err.to_string(), "Connector client error: ResponseError");
}
}
});
}
#[test]
fn equity_order_detail_required_params_success() {
TOKIO_SHARED_RT.block_on(async {
let client = MockTradeApiClient { force_error: false };
let params = EquityOrderDetailParams::builder().build().unwrap();
let resp_json: Value = serde_json::from_str(r#"{"orderId":"c3c58f49-7b0d-4b9e-a2db-1a2f9a3b8c71","clientOrderId":"web_2c9c92b74f1e4a7c8f3b9e1a2d3c4b5a","symbol":"AAPL","quote":"USDC","side":"BUY","orderType":"LIMIT","limitPrice":"180.50","avgFilledPrice":"180.48","qty":"1","notional":"notional","filledQty":"1","filledTotal":"filledTotal","fee":"0.10","session":"RTH","status":"FILLED","createdAt":1735900000000,"updatedAt":1735900120000,"trades":[{"executionId":"exec-20260504-0001","executionAt":1735900115000,"price":"180.48","qty":"1"}]}"#).unwrap_or_else(|_| serde_json::json!({}));
let expected_response : models::EquityOrderDetailResponse = serde_json::from_value(resp_json.clone()).expect("should parse into models::EquityOrderDetailResponse");
let resp = client.equity_order_detail(params).await.expect("Expected a response");
let data_future = resp.data();
let actual_response = data_future.await.unwrap();
assert_eq!(actual_response, expected_response);
});
}
#[test]
fn equity_order_detail_optional_params_success() {
TOKIO_SHARED_RT.block_on(async {
let client = MockTradeApiClient { force_error: false };
let params = EquityOrderDetailParams::builder().order_id("c3c58f49-7b0d-4b9e-a2db-1a2f9a3b8c71".to_string()).client_order_id("web_2c9c92b74f1e4a7c8f3b9e1a2d3c4b5a".to_string()).recv_window(5000).build().unwrap();
let resp_json: Value = serde_json::from_str(r#"{"orderId":"c3c58f49-7b0d-4b9e-a2db-1a2f9a3b8c71","clientOrderId":"web_2c9c92b74f1e4a7c8f3b9e1a2d3c4b5a","symbol":"AAPL","quote":"USDC","side":"BUY","orderType":"LIMIT","limitPrice":"180.50","avgFilledPrice":"180.48","qty":"1","notional":"notional","filledQty":"1","filledTotal":"filledTotal","fee":"0.10","session":"RTH","status":"FILLED","createdAt":1735900000000,"updatedAt":1735900120000,"trades":[{"executionId":"exec-20260504-0001","executionAt":1735900115000,"price":"180.48","qty":"1"}]}"#).unwrap_or_else(|_| serde_json::json!({}));
let expected_response : models::EquityOrderDetailResponse = serde_json::from_value(resp_json.clone()).expect("should parse into models::EquityOrderDetailResponse");
let resp = client.equity_order_detail(params).await.expect("Expected a response");
let data_future = resp.data();
let actual_response = data_future.await.unwrap();
assert_eq!(actual_response, expected_response);
});
}
#[test]
fn equity_order_detail_response_error() {
TOKIO_SHARED_RT.block_on(async {
let client = MockTradeApiClient { force_error: true };
let params = EquityOrderDetailParams::builder().build().unwrap();
match client.equity_order_detail(params).await {
Ok(_) => panic!("Expected an error"),
Err(err) => {
assert_eq!(err.to_string(), "Connector client error: ResponseError");
}
}
});
}
#[test]
fn equity_order_history_required_params_success() {
TOKIO_SHARED_RT.block_on(async {
let client = MockTradeApiClient { force_error: false };
let params = EquityOrderHistoryParams::builder(1735800000000,1735900000000,).build().unwrap();
let resp_json: Value = serde_json::from_str(r#"{"total":2,"page":1,"size":20,"rows":[{"orderId":"c3c58f49-7b0d-4b9e-a2db-1a2f9a3b8c71","symbol":"AAPL","quote":"USDC","side":"BUY","orderType":"LIMIT","limitPrice":"180.50","avgFilledPrice":"180.48","qty":"1","notional":"notional","filledQty":"1","filledTotal":"filledTotal","fee":"0.10","session":"RTH","status":"FILLED","createdAt":1735900000000,"updatedAt":1735900120000}]}"#).unwrap_or_else(|_| serde_json::json!({}));
let expected_response : models::EquityOrderHistoryResponse = serde_json::from_value(resp_json.clone()).expect("should parse into models::EquityOrderHistoryResponse");
let resp = client.equity_order_history(params).await.expect("Expected a response");
let data_future = resp.data();
let actual_response = data_future.await.unwrap();
assert_eq!(actual_response, expected_response);
});
}
#[test]
fn equity_order_history_optional_params_success() {
TOKIO_SHARED_RT.block_on(async {
let client = MockTradeApiClient { force_error: false };
let params = EquityOrderHistoryParams::builder(1735800000000,1735900000000,).symbol("NVDA".to_string()).order_type(EquityOrderHistoryOrderTypeEnum::Market).side(EquityOrderHistorySideEnum::Buy).order_status("FILLED,CANCELED".to_string()).current(1).size(20).recv_window(5000).build().unwrap();
let resp_json: Value = serde_json::from_str(r#"{"total":2,"page":1,"size":20,"rows":[{"orderId":"c3c58f49-7b0d-4b9e-a2db-1a2f9a3b8c71","symbol":"AAPL","quote":"USDC","side":"BUY","orderType":"LIMIT","limitPrice":"180.50","avgFilledPrice":"180.48","qty":"1","notional":"notional","filledQty":"1","filledTotal":"filledTotal","fee":"0.10","session":"RTH","status":"FILLED","createdAt":1735900000000,"updatedAt":1735900120000}]}"#).unwrap_or_else(|_| serde_json::json!({}));
let expected_response : models::EquityOrderHistoryResponse = serde_json::from_value(resp_json.clone()).expect("should parse into models::EquityOrderHistoryResponse");
let resp = client.equity_order_history(params).await.expect("Expected a response");
let data_future = resp.data();
let actual_response = data_future.await.unwrap();
assert_eq!(actual_response, expected_response);
});
}
#[test]
fn equity_order_history_response_error() {
TOKIO_SHARED_RT.block_on(async {
let client = MockTradeApiClient { force_error: true };
let params = EquityOrderHistoryParams::builder(1735800000000, 1735900000000)
.build()
.unwrap();
match client.equity_order_history(params).await {
Ok(_) => panic!("Expected an error"),
Err(err) => {
assert_eq!(err.to_string(), "Connector client error: ResponseError");
}
}
});
}
#[test]
fn equity_trade_history_required_params_success() {
TOKIO_SHARED_RT.block_on(async {
let client = MockTradeApiClient { force_error: false };
let params = EquityTradeHistoryParams::builder(1735800000000,1735900000000,).build().unwrap();
let resp_json: Value = serde_json::from_str(r#"{"total":1,"page":1,"size":20,"rows":[{"executionId":"exec-20260504-0001","orderId":"c3c58f49-7b0d-4b9e-a2db-1a2f9a3b8c71","symbol":"AAPL","quote":"USDC","side":"BUY","orderType":"LIMIT","price":"180.50","qty":"1","total":"180.50","executionAt":1735900115000,"updatedAt":1735900115200}]}"#).unwrap_or_else(|_| serde_json::json!({}));
let expected_response : models::EquityTradeHistoryResponse = serde_json::from_value(resp_json.clone()).expect("should parse into models::EquityTradeHistoryResponse");
let resp = client.equity_trade_history(params).await.expect("Expected a response");
let data_future = resp.data();
let actual_response = data_future.await.unwrap();
assert_eq!(actual_response, expected_response);
});
}
#[test]
fn equity_trade_history_optional_params_success() {
TOKIO_SHARED_RT.block_on(async {
let client = MockTradeApiClient { force_error: false };
let params = EquityTradeHistoryParams::builder(1735800000000,1735900000000,).symbol("NVDA".to_string()).side(EquityTradeHistorySideEnum::Buy).order_id("c3c58f49-7b0d-4b9e-a2db-1a2f9a3b8c71".to_string()).current(1).size(20).recv_window(5000).build().unwrap();
let resp_json: Value = serde_json::from_str(r#"{"total":1,"page":1,"size":20,"rows":[{"executionId":"exec-20260504-0001","orderId":"c3c58f49-7b0d-4b9e-a2db-1a2f9a3b8c71","symbol":"AAPL","quote":"USDC","side":"BUY","orderType":"LIMIT","price":"180.50","qty":"1","total":"180.50","executionAt":1735900115000,"updatedAt":1735900115200}]}"#).unwrap_or_else(|_| serde_json::json!({}));
let expected_response : models::EquityTradeHistoryResponse = serde_json::from_value(resp_json.clone()).expect("should parse into models::EquityTradeHistoryResponse");
let resp = client.equity_trade_history(params).await.expect("Expected a response");
let data_future = resp.data();
let actual_response = data_future.await.unwrap();
assert_eq!(actual_response, expected_response);
});
}
#[test]
fn equity_trade_history_response_error() {
TOKIO_SHARED_RT.block_on(async {
let client = MockTradeApiClient { force_error: true };
let params = EquityTradeHistoryParams::builder(1735800000000, 1735900000000)
.build()
.unwrap();
match client.equity_trade_history(params).await {
Ok(_) => panic!("Expected an error"),
Err(err) => {
assert_eq!(err.to_string(), "Connector client error: ResponseError");
}
}
});
}
#[test]
fn place_equity_order_required_params_success() {
TOKIO_SHARED_RT.block_on(async {
let client = MockTradeApiClient { force_error: false };
let params = PlaceEquityOrderParams::builder("AAPL".to_string(),PlaceEquityOrderSideEnum::Buy,PlaceEquityOrderOrderTypeEnum::Market,).build().unwrap();
let resp_json: Value = serde_json::from_str(r#"{"status":"S","orderId":"c3c58f49-7b0d-4b9e-a2db-1a2f9a3b8c71","clientOrderId":"web_2c9c92b74f1e4a7c8f3b9e1a2d3c4b5a"}"#).unwrap_or_else(|_| serde_json::json!({}));
let expected_response : models::PlaceEquityOrderResponse = serde_json::from_value(resp_json.clone()).expect("should parse into models::PlaceEquityOrderResponse");
let resp = client.place_equity_order(params).await.expect("Expected a response");
let data_future = resp.data();
let actual_response = data_future.await.unwrap();
assert_eq!(actual_response, expected_response);
});
}
#[test]
fn place_equity_order_optional_params_success() {
TOKIO_SHARED_RT.block_on(async {
let client = MockTradeApiClient { force_error: false };
let params = PlaceEquityOrderParams::builder("AAPL".to_string(),PlaceEquityOrderSideEnum::Buy,PlaceEquityOrderOrderTypeEnum::Market,).quote_asset("USDC".to_string()).price("180.50".to_string()).quantity("1".to_string()).notional("1000.00".to_string()).time_in_force(PlaceEquityOrderTimeInForceEnum::Day).trading_session(PlaceEquityOrderTradingSessionEnum::Rth).wallet_type(PlaceEquityOrderWalletTypeEnum::Card).client_order_id("web_2c9c92b74f1e4a7c8f3b9e1a2d3c4b5a".to_string()).tokenize(true).recv_window(5000).build().unwrap();
let resp_json: Value = serde_json::from_str(r#"{"status":"S","orderId":"c3c58f49-7b0d-4b9e-a2db-1a2f9a3b8c71","clientOrderId":"web_2c9c92b74f1e4a7c8f3b9e1a2d3c4b5a"}"#).unwrap_or_else(|_| serde_json::json!({}));
let expected_response : models::PlaceEquityOrderResponse = serde_json::from_value(resp_json.clone()).expect("should parse into models::PlaceEquityOrderResponse");
let resp = client.place_equity_order(params).await.expect("Expected a response");
let data_future = resp.data();
let actual_response = data_future.await.unwrap();
assert_eq!(actual_response, expected_response);
});
}
#[test]
fn place_equity_order_response_error() {
TOKIO_SHARED_RT.block_on(async {
let client = MockTradeApiClient { force_error: true };
let params = PlaceEquityOrderParams::builder(
"AAPL".to_string(),
PlaceEquityOrderSideEnum::Buy,
PlaceEquityOrderOrderTypeEnum::Market,
)
.build()
.unwrap();
match client.place_equity_order(params).await {
Ok(_) => panic!("Expected an error"),
Err(err) => {
assert_eq!(err.to_string(), "Connector client error: ResponseError");
}
}
});
}
}