use super::Kalshi;
use crate::kalshi_error::*;
use std::fmt;
use uuid::Uuid;
use serde::{Deserialize, Deserializer, Serialize};
const PORTFOLIO_PATH: &str = "/portfolio";
impl Kalshi {
pub async fn get_balance(&self) -> Result<i64, KalshiError> {
let result: BalanceResponse = self
.signed_get(&format!("{}/balance", PORTFOLIO_PATH))
.await?;
Ok(result.balance)
}
#[allow(clippy::too_many_arguments)]
pub async fn get_orders(
&self,
ticker: Option<String>,
event_ticker: Option<String>,
min_ts: Option<i64>,
max_ts: Option<i64>,
status: Option<OrderStatus>,
limit: Option<i32>,
cursor: Option<String>,
) -> Result<(Option<String>, Vec<Order>), KalshiError> {
let mut params: Vec<(&str, String)> = Vec::with_capacity(7);
add_param!(params, "ticker", ticker);
add_param!(params, "limit", limit);
add_param!(params, "cursor", cursor);
add_param!(params, "min_ts", min_ts);
add_param!(params, "max_ts", max_ts);
add_param!(params, "event_ticker", event_ticker);
add_param!(params, "status", status.map(|s| s.to_string()));
let path = if params.is_empty() {
format!("{}/orders", PORTFOLIO_PATH)
} else {
let query_string = params
.iter()
.map(|(k, v)| format!("{}={}", k, v))
.collect::<Vec<_>>()
.join("&");
format!("{}/orders?{}", PORTFOLIO_PATH, query_string)
};
let result: MultipleOrderResponse = self.signed_get(&path).await?;
Ok((result.cursor, result.orders))
}
pub async fn get_single_order(&self, order_id: &String) -> Result<Order, KalshiError> {
let path = format!("{}/orders/{}", PORTFOLIO_PATH, order_id);
let result: SingleOrderResponse = self.signed_get(&path).await?;
Ok(result.order)
}
pub async fn cancel_order(&self, order_id: &str) -> Result<(Order, i32), KalshiError> {
let path = format!("{}/orders/{}", PORTFOLIO_PATH, order_id);
let result: DeleteOrderResponse = self.signed_delete(&path).await?;
Ok((result.order, result.reduced_by))
}
pub async fn decrease_order(
&self,
order_id: &str,
reduce_by: Option<i32>,
reduce_to: Option<i32>,
) -> Result<Order, KalshiError> {
match (reduce_by, reduce_to) {
(Some(_), Some(_)) => {
return Err(KalshiError::UserInputError(
"Can only provide reduce_by strict exclusive or reduce_to, can't provide both"
.to_string(),
));
}
(None, None) => {
return Err(KalshiError::UserInputError(
"Must provide either reduce_by exclusive or reduce_to, can't provide neither"
.to_string(),
));
}
_ => {}
}
let decrease_payload = DecreaseOrderPayload {
reduce_by,
reduce_to,
};
let path = format!("{}/orders/{}/decrease", PORTFOLIO_PATH, order_id);
let result: DecreaseOrderResponse = self.signed_post(&path, &decrease_payload).await?;
Ok(result.order)
}
pub async fn get_fills(
&self,
ticker: Option<String>,
order_id: Option<String>,
min_ts: Option<i64>,
max_ts: Option<i64>,
limit: Option<i32>,
cursor: Option<String>,
) -> Result<(Option<String>, Vec<Fill>), KalshiError> {
let mut params: Vec<(&str, String)> = Vec::with_capacity(7);
add_param!(params, "ticker", ticker);
add_param!(params, "limit", limit);
add_param!(params, "cursor", cursor);
add_param!(params, "min_ts", min_ts);
add_param!(params, "max_ts", max_ts);
add_param!(params, "order_id", order_id);
let path = if params.is_empty() {
format!("{}/fills", PORTFOLIO_PATH)
} else {
let query_string = params
.iter()
.map(|(k, v)| format!("{}={}", k, v))
.collect::<Vec<_>>()
.join("&");
format!("{}/fills?{}", PORTFOLIO_PATH, query_string)
};
let result: MultipleFillsResponse = self.signed_get(&path).await?;
Ok((result.cursor, result.fills))
}
pub async fn get_settlements(
&self,
limit: Option<i64>,
cursor: Option<String>,
ticker: Option<String>,
event_ticker: Option<String>,
min_ts: Option<i64>,
max_ts: Option<i64>,
) -> Result<(Option<String>, Vec<Settlement>), KalshiError> {
let mut params: Vec<(&str, String)> = Vec::with_capacity(6);
add_param!(params, "limit", limit);
add_param!(params, "cursor", cursor);
add_param!(params, "ticker", ticker);
add_param!(params, "event_ticker", event_ticker);
add_param!(params, "min_ts", min_ts);
add_param!(params, "max_ts", max_ts);
let path = if params.is_empty() {
format!("{}/settlements", PORTFOLIO_PATH)
} else {
let query_string = params
.iter()
.map(|(k, v)| format!("{}={}", k, v))
.collect::<Vec<_>>()
.join("&");
format!("{}/settlements?{}", PORTFOLIO_PATH, query_string)
};
let result: PortfolioSettlementResponse = self.signed_get(&path).await?;
Ok((result.cursor, result.settlements))
}
pub async fn get_positions(
&self,
limit: Option<i64>,
cursor: Option<String>,
settlement_status: Option<String>,
ticker: Option<String>,
event_ticker: Option<String>,
count_filter: Option<String>,
) -> Result<(Option<String>, Vec<EventPosition>, Vec<MarketPosition>), KalshiError> {
let mut params: Vec<(&str, String)> = Vec::with_capacity(7);
add_param!(params, "limit", limit);
add_param!(params, "cursor", cursor);
add_param!(params, "settlement_status", settlement_status);
add_param!(params, "ticker", ticker);
add_param!(params, "event_ticker", event_ticker);
add_param!(params, "count_filter", count_filter);
let path = if params.is_empty() {
format!("{}/positions", PORTFOLIO_PATH)
} else {
let query_string = params
.iter()
.map(|(k, v)| format!("{}={}", k, v))
.collect::<Vec<_>>()
.join("&");
format!("{}/positions?{}", PORTFOLIO_PATH, query_string)
};
let result: GetPositionsResponse = self.signed_get(&path).await?;
Ok((
result.cursor,
result.event_positions,
result.market_positions,
))
}
#[allow(clippy::too_many_arguments)]
pub async fn create_order(
&self,
action: Action,
client_order_id: Option<String>,
count: i32,
side: Side,
ticker: String,
input_type: OrderType,
buy_max_cost: Option<i64>,
expiration_ts: Option<i64>,
yes_price: Option<i64>,
no_price: Option<i64>,
sell_position_floor: Option<i32>,
yes_price_dollars: Option<String>,
no_price_dollars: Option<String>,
time_in_force: Option<TimeInForce>,
post_only: Option<bool>,
reduce_only: Option<bool>,
self_trade_prevention_type: Option<SelfTradePreventionType>,
order_group_id: Option<String>,
cancel_order_on_pause: Option<bool>,
) -> Result<Order, KalshiError> {
if let OrderType::Limit = input_type {
if yes_price.is_some() && yes_price_dollars.is_some() {
return Err(KalshiError::UserInputError(
"Cannot provide both yes_price and yes_price_dollars".to_string(),
));
}
if no_price.is_some() && no_price_dollars.is_some() {
return Err(KalshiError::UserInputError(
"Cannot provide both no_price and no_price_dollars".to_string(),
));
}
let has_price = yes_price.is_some()
|| no_price.is_some()
|| yes_price_dollars.is_some()
|| no_price_dollars.is_some();
if !has_price {
return Err(KalshiError::UserInputError(
"Must provide a price (yes_price, no_price, yes_price_dollars, or no_price_dollars)".to_string(),
));
}
let has_yes = yes_price.is_some() || yes_price_dollars.is_some();
let has_no = no_price.is_some() || no_price_dollars.is_some();
if has_yes && has_no {
return Err(KalshiError::UserInputError(
"Can only provide yes price or no price, not both".to_string(),
));
}
}
let unwrapped_id = match client_order_id {
Some(id) => id,
_ => String::from(Uuid::new_v4()),
};
let order_payload = CreateOrderPayload {
action,
client_order_id: unwrapped_id,
count,
side,
ticker,
r#type: input_type,
buy_max_cost,
expiration_ts,
yes_price,
no_price,
sell_position_floor,
yes_price_dollars,
no_price_dollars,
time_in_force,
post_only,
reduce_only,
self_trade_prevention_type,
order_group_id,
cancel_order_on_pause,
};
let path = format!("{}/orders", PORTFOLIO_PATH);
let result: SingleOrderResponse = self.signed_post(&path, &order_payload).await?;
Ok(result.order)
}
pub async fn batch_create_order(
&self,
batch: Vec<OrderCreationField>,
) -> Result<Vec<Result<Order, KalshiError>>, KalshiError> {
if batch.is_empty() {
return Ok(Vec::new());
}
if batch.len() > 20 {
return Err(KalshiError::UserInputError(
"Batch size exceeds 20; split the request".into(),
));
}
let orders: Vec<CreateOrderPayload> = batch
.into_iter()
.map(|field| field.into_payload())
.collect();
let path = format!("{}/orders/batched", PORTFOLIO_PATH);
let body = BatchCreateOrderPayload { orders };
let response: BatchCreateOrdersResponse = self.signed_post(&path, &body).await?;
let mut out = Vec::with_capacity(response.orders.len());
for item in response.orders {
match (item.order, item.error) {
(Some(order), None) => out.push(Ok(order)),
(_, Some(err)) => out.push(Err(KalshiError::UserInputError(
err.message.unwrap_or_else(|| "unknown error".into()),
))),
_ => out.push(Err(KalshiError::InternalError(
"malformed batch-create response".into(),
))),
}
}
Ok(out)
}
pub async fn batch_cancel_order(
&self,
ids: Vec<String>,
) -> Result<Vec<Result<(Order, i32), KalshiError>>, KalshiError> {
if ids.is_empty() {
return Ok(Vec::new());
}
if ids.len() > 20 {
return Err(KalshiError::UserInputError(
"Batch size exceeds 20; split the request".into(),
));
}
let path = format!("{}/orders/batched", PORTFOLIO_PATH);
let body = BatchCancelOrderPayload { ids };
let response: BatchCancelOrdersResponse =
self.signed_delete_with_body(&path, &body).await?;
let mut out = Vec::with_capacity(response.orders.len());
for item in response.orders {
match (item.order, item.reduced_by, item.error) {
(Some(order), Some(reduced_by), None) => out.push(Ok((order, reduced_by))),
(_, _, Some(err)) => out.push(Err(KalshiError::UserInputError(
err.message.unwrap_or_else(|| "unknown error".into()),
))),
_ => out.push(Err(KalshiError::InternalError(
"malformed batch-cancel response".into(),
))),
}
}
Ok(out)
}
pub async fn get_total_resting_order_value(&self) -> Result<i64, KalshiError> {
let path = "/portfolio/summary/total_resting_order_value";
let res: TotalRestingOrderValueResponse = self.signed_get(path).await?;
Ok(res.total_resting_order_value)
}
pub async fn get_order_groups(&self) -> Result<Vec<OrderGroup>, KalshiError> {
let path = "/portfolio/order_groups";
let res: OrderGroupsResponse = self.signed_get(path).await?;
Ok(res.order_groups)
}
pub async fn create_order_group(
&self,
contracts_limit: i32,
) -> Result<OrderGroup, KalshiError> {
let path = "/portfolio/order_groups/create";
let body = CreateOrderGroupRequest { contracts_limit };
self.signed_post(path, &body).await
}
pub async fn get_order_group(&self, order_group_id: &str) -> Result<OrderGroup, KalshiError> {
let path = format!("/portfolio/order_groups/{}", order_group_id);
let res: OrderGroupResponse = self.signed_get(&path).await?;
Ok(res.order_group)
}
pub async fn delete_order_group(&self, order_group_id: &str) -> Result<(), KalshiError> {
let path = format!("/portfolio/order_groups/{}", order_group_id);
let _res: DeleteOrderGroupResponse = self.signed_delete(&path).await?;
Ok(())
}
pub async fn reset_order_group(&self, order_group_id: &str) -> Result<OrderGroup, KalshiError> {
let path = format!("/portfolio/order_groups/{}/reset", order_group_id);
self.signed_put(&path, None::<&()>).await
}
pub async fn get_queue_positions(
&self,
order_ids: Vec<String>,
) -> Result<Vec<OrderQueuePosition>, KalshiError> {
let path = "/portfolio/orders/queue_positions";
let mut params = vec![];
for id in order_ids {
params.push(("order_ids".to_string(), id));
}
let url = format!("{}{}", self.base_url, path);
let final_url = reqwest::Url::parse_with_params(&url, ¶ms)?;
let res: QueuePositionsResponse = self.client.get(final_url).send().await?.json().await?;
Ok(res.queue_positions)
}
#[allow(clippy::too_many_arguments)]
pub async fn amend_order(
&self,
order_id: &str,
ticker: &str,
side: Side,
action: Action,
client_order_id: &str,
updated_client_order_id: &str,
yes_price: Option<i32>,
no_price: Option<i32>,
yes_price_dollars: Option<String>,
no_price_dollars: Option<String>,
count: Option<i32>,
) -> Result<AmendOrderResponse, KalshiError> {
let price_count = [
yes_price.is_some(),
no_price.is_some(),
yes_price_dollars.is_some(),
no_price_dollars.is_some(),
]
.iter()
.filter(|&&x| x)
.count();
if price_count > 1 {
return Err(KalshiError::UserInputError(
"At most one of yes_price, no_price, yes_price_dollars, or no_price_dollars can be provided".to_string(),
));
}
let path = format!("{}/orders/{}/amend", PORTFOLIO_PATH, order_id);
let body = AmendOrderRequest {
ticker: ticker.to_string(),
side,
action,
client_order_id: client_order_id.to_string(),
updated_client_order_id: updated_client_order_id.to_string(),
yes_price,
no_price,
yes_price_dollars,
no_price_dollars,
count,
};
self.signed_post(&path, &body).await
}
pub async fn get_order_queue_position(
&self,
order_id: &str,
) -> Result<OrderQueuePosition, KalshiError> {
let path = format!("/portfolio/orders/{}/queue_position", order_id);
self.signed_get(&path).await
}
}
#[derive(Debug, Serialize, Deserialize)]
struct BalanceResponse {
balance: i64,
}
#[derive(Debug, Deserialize, Serialize)]
struct SingleOrderResponse {
order: Order,
}
#[derive(Debug, Deserialize, Serialize)]
struct MultipleOrderResponse {
orders: Vec<Order>,
#[serde(deserialize_with = "empty_string_is_none")]
cursor: Option<String>,
}
fn empty_string_is_none<'de, D>(deserializer: D) -> Result<Option<String>, D::Error>
where
D: Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
if s.is_empty() {
Ok(None)
} else {
Ok(Some(s))
}
}
#[derive(Debug, Deserialize, Serialize)]
struct DeleteOrderResponse {
order: Order,
reduced_by: i32,
}
#[derive(Debug, Deserialize, Serialize)]
struct DecreaseOrderResponse {
order: Order,
}
#[derive(Debug, Deserialize, Serialize)]
struct DecreaseOrderPayload {
reduce_by: Option<i32>,
reduce_to: Option<i32>,
}
#[derive(Debug, Deserialize, Serialize)]
struct MultipleFillsResponse {
fills: Vec<Fill>,
cursor: Option<String>,
}
#[derive(Debug, Deserialize, Serialize)]
struct PortfolioSettlementResponse {
cursor: Option<String>,
settlements: Vec<Settlement>,
}
#[derive(Debug, Deserialize, Serialize)]
struct GetPositionsResponse {
cursor: Option<String>,
event_positions: Vec<EventPosition>,
market_positions: Vec<MarketPosition>,
}
#[derive(Debug, Deserialize, Serialize)]
struct CreateOrderPayload {
action: Action,
client_order_id: String,
count: i32,
side: Side,
ticker: String,
r#type: OrderType,
#[serde(skip_serializing_if = "Option::is_none")]
buy_max_cost: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
expiration_ts: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
yes_price: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
no_price: Option<i64>,
#[serde(skip_serializing_if = "Option::is_none")]
sell_position_floor: Option<i32>,
#[serde(skip_serializing_if = "Option::is_none")]
yes_price_dollars: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
no_price_dollars: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
time_in_force: Option<TimeInForce>,
#[serde(skip_serializing_if = "Option::is_none")]
post_only: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
reduce_only: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
self_trade_prevention_type: Option<SelfTradePreventionType>,
#[serde(skip_serializing_if = "Option::is_none")]
order_group_id: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
cancel_order_on_pause: Option<bool>,
}
#[derive(Debug, Deserialize, Serialize)]
pub struct Order {
pub order_id: String,
#[serde(default)]
pub user_id: Option<String>,
pub ticker: String,
pub status: OrderStatus,
#[serde(default)]
pub yes_price: Option<i32>,
#[serde(default)]
pub no_price: Option<i32>,
#[serde(default)]
pub count: Option<i32>,
#[serde(default)]
pub created_time: Option<String>,
#[serde(default)]
pub last_update_time: Option<String>,
#[serde(default)]
pub expiration_time: Option<String>,
#[serde(default)]
pub fill_count: Option<i32>,
#[serde(default)]
pub initial_count: Option<i32>,
#[serde(default)]
pub remaining_count: Option<i32>,
#[serde(default)]
pub queue_position: Option<i32>,
#[serde(default)]
pub taker_fill_count: Option<i32>,
#[serde(default)]
pub place_count: Option<i32>,
#[serde(default)]
pub decrease_count: Option<i32>,
#[serde(default)]
pub maker_fill_count: Option<i32>,
#[serde(default)]
pub fcc_cancel_count: Option<i32>,
#[serde(default)]
pub close_cancel_count: Option<i32>,
#[serde(default)]
pub taker_fees: Option<i32>,
#[serde(default)]
pub taker_fees_dollars: Option<String>,
#[serde(default)]
pub taker_fill_cost: Option<i32>,
#[serde(default)]
pub taker_fill_cost_dollars: Option<String>,
#[serde(default)]
pub maker_fees: Option<i32>,
#[serde(default)]
pub maker_fees_dollars: Option<String>,
#[serde(default)]
pub maker_fill_cost: Option<i32>,
#[serde(default)]
pub maker_fill_cost_dollars: Option<String>,
#[serde(default)]
pub yes_price_dollars: Option<String>,
#[serde(default)]
pub no_price_dollars: Option<String>,
pub action: Action,
pub side: Side,
#[serde(rename = "type")]
pub r#type: String,
pub client_order_id: String,
#[serde(default)]
pub order_group_id: Option<String>,
#[serde(default)]
pub self_trade_prevention_type: Option<String>,
}
#[derive(Debug, Deserialize, Serialize)]
pub struct Fill {
pub action: Action,
pub count: i32,
pub created_time: String,
pub is_taker: bool,
pub no_price: i64,
pub order_id: String,
pub side: Side,
pub ticker: String,
pub trade_id: String,
pub yes_price: i64,
}
#[derive(Debug, Deserialize, Serialize)]
pub struct Settlement {
pub market_result: String,
pub no_count: i64,
pub no_total_cost: i64,
pub revenue: i64,
pub settled_time: String,
pub ticker: String,
pub yes_count: i64,
pub yes_total_cost: i64,
}
#[derive(Debug, Deserialize, Serialize)]
pub struct EventPosition {
pub event_exposure: i64,
pub event_ticker: String,
pub fees_paid: i64,
pub realized_pnl: i64,
#[serde(default)]
pub resting_order_count: Option<i32>,
pub total_cost: i64,
}
#[derive(Debug, Deserialize, Serialize)]
pub struct MarketPosition {
pub fees_paid: i64,
pub market_exposure: i64,
pub position: i32,
pub realized_pnl: i64,
#[serde(default)]
pub resting_orders_count: Option<i32>,
pub ticker: String,
pub total_traded: i64,
}
#[derive(Debug, Deserialize, Serialize)]
pub struct OrderCreationField {
pub action: Action,
pub client_order_id: Option<String>,
pub count: i32,
pub side: Side,
pub ticker: String,
pub input_type: OrderType,
pub buy_max_cost: Option<i64>,
pub expiration_ts: Option<i64>,
pub yes_price: Option<i64>,
pub no_price: Option<i64>,
pub sell_position_floor: Option<i32>,
pub yes_price_dollars: Option<String>,
pub no_price_dollars: Option<String>,
pub time_in_force: Option<TimeInForce>,
pub post_only: Option<bool>,
pub reduce_only: Option<bool>,
pub self_trade_prevention_type: Option<SelfTradePreventionType>,
pub order_group_id: Option<String>,
pub cancel_order_on_pause: Option<bool>,
}
impl OrderCreationField {
fn into_payload(self) -> CreateOrderPayload {
CreateOrderPayload {
action: self.action,
client_order_id: self
.client_order_id
.unwrap_or_else(|| Uuid::new_v4().to_string()),
count: self.count,
side: self.side,
ticker: self.ticker,
r#type: self.input_type,
buy_max_cost: self.buy_max_cost,
expiration_ts: self.expiration_ts,
yes_price: self.yes_price,
no_price: self.no_price,
sell_position_floor: self.sell_position_floor,
yes_price_dollars: self.yes_price_dollars,
no_price_dollars: self.no_price_dollars,
time_in_force: self.time_in_force,
post_only: self.post_only,
reduce_only: self.reduce_only,
self_trade_prevention_type: self.self_trade_prevention_type,
order_group_id: self.order_group_id,
cancel_order_on_pause: self.cancel_order_on_pause,
}
}
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum Side {
Yes,
No,
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum Action {
Buy,
Sell,
}
impl fmt::Display for Action {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Action::Buy => write!(f, "buy"),
Action::Sell => write!(f, "sell"),
}
}
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum OrderStatus {
Resting,
Canceled,
Executed,
Pending,
}
impl fmt::Display for OrderStatus {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
OrderStatus::Resting => write!(f, "resting"),
OrderStatus::Canceled => write!(f, "cancelled"),
OrderStatus::Executed => write!(f, "executed"),
OrderStatus::Pending => write!(f, "pending"),
}
}
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum OrderType {
Market,
Limit,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "snake_case")]
pub enum TimeInForce {
FillOrKill,
GoodTillCanceled,
ImmediateOrCancel,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "snake_case")]
pub enum SelfTradePreventionType {
TakerAtCross,
Maker,
}
#[derive(Debug, Serialize, Deserialize)]
struct BatchCreateOrderPayload {
orders: Vec<CreateOrderPayload>,
}
#[derive(Debug, Serialize, Deserialize)]
struct BatchCancelOrderPayload {
ids: Vec<String>,
}
#[derive(Debug, Serialize, Deserialize)]
struct ApiError {
message: Option<String>,
}
#[derive(Debug, Serialize, Deserialize)]
struct BatchCreateOrderResponseItem {
order: Option<Order>,
error: Option<ApiError>,
}
#[derive(Debug, Serialize, Deserialize)]
struct BatchCancelOrderResponseItem {
order: Option<Order>,
reduced_by: Option<i32>,
error: Option<ApiError>,
}
#[derive(Debug, Serialize, Deserialize)]
struct BatchCreateOrdersResponse {
orders: Vec<BatchCreateOrderResponseItem>,
}
#[derive(Debug, Serialize, Deserialize)]
struct BatchCancelOrdersResponse {
orders: Vec<BatchCancelOrderResponseItem>,
}
#[derive(Debug, Deserialize)]
struct TotalRestingOrderValueResponse {
total_resting_order_value: i64,
}
#[derive(Debug, Serialize)]
struct CreateOrderGroupRequest {
contracts_limit: i32,
}
#[derive(Debug, Deserialize)]
struct OrderGroupsResponse {
order_groups: Vec<OrderGroup>,
}
#[derive(Debug, Deserialize)]
struct OrderGroupResponse {
order_group: OrderGroup,
}
#[derive(Debug, Deserialize)]
struct DeleteOrderGroupResponse {}
#[derive(Debug, Deserialize, Serialize)]
pub struct OrderGroup {
pub id: String,
pub contracts_limit: i32,
pub total_contracts: Option<i32>,
pub order_ids: Vec<String>,
pub created_time: String,
}
#[derive(Debug, Deserialize)]
struct QueuePositionsResponse {
queue_positions: Vec<OrderQueuePosition>,
}
#[derive(Debug, Deserialize, Serialize)]
pub struct OrderQueuePosition {
pub order_id: String,
pub queue_position: Option<i64>,
pub total_queue_depth: Option<i64>,
}
#[derive(Debug, Serialize)]
struct AmendOrderRequest {
ticker: String,
side: Side,
action: Action,
client_order_id: String,
updated_client_order_id: String,
#[serde(skip_serializing_if = "Option::is_none")]
yes_price: Option<i32>,
#[serde(skip_serializing_if = "Option::is_none")]
no_price: Option<i32>,
#[serde(skip_serializing_if = "Option::is_none")]
yes_price_dollars: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
no_price_dollars: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
count: Option<i32>,
}
#[derive(Debug, Deserialize, Serialize)]
pub struct AmendOrderResponse {
pub old_order: Order,
pub order: Order,
}
#[cfg(test)]
mod test {
use crate::portfolio::MultipleOrderResponse;
#[test]
fn test_serialize_multiple_order_response() -> serde_json::Result<()> {
let json = r#"{"orders":[],"cursor":""}"#;
let result = serde_json::from_str::<MultipleOrderResponse>(json)?;
assert!(result.orders.is_empty());
assert!(result.cursor.is_none());
Ok(())
}
}