use chrono::{DateTime, NaiveDate, NaiveDateTime, Utc};
use rust_decimal::Decimal;
use serde::{Deserialize, Deserializer, Serialize, de};
use std::collections::HashMap;
use uuid::Uuid;
use crate::trading::enums::{
AccountStatus, ActivityType, AssetClass, AssetExchange, AssetStatus, CorporateActionSubType,
CorporateActionType, DTBPCheck, ExerciseStyle, NonTradeActivityStatus, OrderClass, OrderSide,
OrderStatus, OrderType, PDTCheck, PositionIntent, PositionSide, TimeInForce, TradeActivityType,
TradeConfirmationEmail, TradeEvent,
};
use crate::types::ContractType;
use crate::types::serde_util::{self, empty_string_as_none};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Asset {
pub id: Uuid,
#[serde(rename = "class")]
pub asset_class: AssetClass,
pub exchange: AssetExchange,
pub symbol: String,
#[serde(default)]
pub name: Option<String>,
pub status: AssetStatus,
pub tradable: bool,
pub marginable: bool,
pub shortable: bool,
pub easy_to_borrow: bool,
pub fractionable: bool,
#[serde(default, with = "crate::types::option_decimal")]
pub min_order_size: Option<Decimal>,
#[serde(default, with = "crate::types::option_decimal")]
pub min_trade_increment: Option<Decimal>,
#[serde(default, with = "crate::types::option_decimal")]
pub price_increment: Option<Decimal>,
#[serde(default, with = "crate::types::option_decimal")]
pub maintenance_margin_requirement: Option<Decimal>,
#[serde(default)]
pub attributes: Option<Vec<String>>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct UsdPositionValues {
#[serde(with = "crate::types::decimal")]
pub avg_entry_price: Decimal,
#[serde(with = "crate::types::decimal")]
pub market_value: Decimal,
#[serde(with = "crate::types::decimal")]
pub cost_basis: Decimal,
#[serde(with = "crate::types::decimal")]
pub unrealized_pl: Decimal,
#[serde(with = "crate::types::decimal")]
pub unrealized_plpc: Decimal,
#[serde(with = "crate::types::decimal")]
pub unrealized_intraday_pl: Decimal,
#[serde(with = "crate::types::decimal")]
pub unrealized_intraday_plpc: Decimal,
#[serde(with = "crate::types::decimal")]
pub current_price: Decimal,
#[serde(with = "crate::types::decimal")]
pub lastday_price: Decimal,
#[serde(with = "crate::types::decimal")]
pub change_today: Decimal,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Position {
pub asset_id: Uuid,
pub symbol: String,
pub exchange: AssetExchange,
pub asset_class: AssetClass,
#[serde(default)]
pub asset_marginable: Option<bool>,
#[serde(with = "crate::types::decimal")]
pub avg_entry_price: Decimal,
#[serde(with = "crate::types::decimal")]
pub qty: Decimal,
pub side: PositionSide,
#[serde(default, with = "crate::types::option_decimal")]
pub market_value: Option<Decimal>,
#[serde(with = "crate::types::decimal")]
pub cost_basis: Decimal,
#[serde(default, with = "crate::types::option_decimal")]
pub unrealized_pl: Option<Decimal>,
#[serde(default, with = "crate::types::option_decimal")]
pub unrealized_plpc: Option<Decimal>,
#[serde(default, with = "crate::types::option_decimal")]
pub unrealized_intraday_pl: Option<Decimal>,
#[serde(default, with = "crate::types::option_decimal")]
pub unrealized_intraday_plpc: Option<Decimal>,
#[serde(default, with = "crate::types::option_decimal")]
pub current_price: Option<Decimal>,
#[serde(default, with = "crate::types::option_decimal")]
pub lastday_price: Option<Decimal>,
#[serde(default, with = "crate::types::option_decimal")]
pub change_today: Option<Decimal>,
#[serde(default, with = "crate::types::option_decimal")]
pub swap_rate: Option<Decimal>,
#[serde(default, with = "crate::types::option_decimal")]
pub avg_entry_swap_rate: Option<Decimal>,
#[serde(default)]
pub usd: Option<UsdPositionValues>,
#[serde(default, with = "crate::types::option_decimal")]
pub qty_available: Option<Decimal>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct AllAccountsPositions {
pub as_of: DateTime<Utc>,
pub positions: HashMap<String, Vec<Position>>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Order {
pub id: Uuid,
pub client_order_id: String,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
pub submitted_at: DateTime<Utc>,
#[serde(default)]
pub filled_at: Option<DateTime<Utc>>,
#[serde(default)]
pub expired_at: Option<DateTime<Utc>>,
#[serde(default)]
pub expires_at: Option<DateTime<Utc>>,
#[serde(default)]
pub canceled_at: Option<DateTime<Utc>>,
#[serde(default)]
pub failed_at: Option<DateTime<Utc>>,
#[serde(default)]
pub replaced_at: Option<DateTime<Utc>>,
#[serde(default, deserialize_with = "empty_string_as_none")]
pub replaced_by: Option<Uuid>,
#[serde(default, deserialize_with = "empty_string_as_none")]
pub replaces: Option<Uuid>,
#[serde(default, deserialize_with = "empty_string_as_none")]
pub asset_id: Option<Uuid>,
#[serde(default, deserialize_with = "empty_string_as_none")]
pub symbol: Option<String>,
#[serde(default, deserialize_with = "empty_string_as_none")]
pub asset_class: Option<AssetClass>,
#[serde(default, with = "crate::types::option_decimal")]
pub notional: Option<Decimal>,
#[serde(default, with = "crate::types::option_decimal")]
pub qty: Option<Decimal>,
#[serde(default, with = "crate::types::option_decimal")]
pub filled_qty: Option<Decimal>,
#[serde(default, with = "crate::types::option_decimal")]
pub filled_avg_price: Option<Decimal>,
#[serde(default = "order_class_default", deserialize_with = "order_class")]
pub order_class: OrderClass,
#[serde(
rename = "order_type",
default,
deserialize_with = "empty_string_as_none"
)]
pub order_type_deprecated: Option<OrderType>,
#[serde(rename = "type", default, deserialize_with = "empty_string_as_none")]
pub order_type: Option<OrderType>,
#[serde(default, deserialize_with = "empty_string_as_none")]
pub side: Option<OrderSide>,
pub time_in_force: TimeInForce,
#[serde(default, with = "crate::types::option_decimal")]
pub limit_price: Option<Decimal>,
#[serde(default, with = "crate::types::option_decimal")]
pub stop_price: Option<Decimal>,
pub status: OrderStatus,
pub extended_hours: bool,
#[serde(default)]
pub legs: Option<Vec<Order>>,
#[serde(default, with = "crate::types::option_decimal")]
pub trail_percent: Option<Decimal>,
#[serde(default, with = "crate::types::option_decimal")]
pub trail_price: Option<Decimal>,
#[serde(default, with = "crate::types::option_decimal")]
pub hwm: Option<Decimal>,
#[serde(default, deserialize_with = "empty_string_as_none")]
pub position_intent: Option<PositionIntent>,
#[serde(default, with = "crate::types::option_decimal")]
pub ratio_qty: Option<Decimal>,
}
fn order_class_default() -> OrderClass {
OrderClass::Simple
}
fn order_class<'de, D: Deserializer<'de>>(deserializer: D) -> Result<OrderClass, D::Error> {
let raw = Option::<String>::deserialize(deserializer)?;
Ok(match raw.as_deref().map(str::trim) {
None | Some("") => OrderClass::Simple,
Some(value) => OrderClass::from(value),
})
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct FailedClosePositionDetails {
#[serde(with = "serde_util::int")]
pub code: i64,
pub message: String,
#[serde(default, with = "crate::types::option_decimal")]
pub available: Option<Decimal>,
#[serde(default, with = "crate::types::option_decimal")]
pub existing_qty: Option<Decimal>,
#[serde(default, with = "crate::types::option_decimal")]
pub held_for_orders: Option<Decimal>,
#[serde(default)]
pub symbol: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(untagged)]
pub enum ClosePositionBody {
Order(Box<Order>),
Failed(FailedClosePositionDetails),
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct ClosePositionResponse {
#[serde(default, deserialize_with = "empty_string_as_none")]
pub order_id: Option<Uuid>,
#[serde(default, with = "serde_util::int::option")]
pub status: Option<i64>,
#[serde(default)]
pub symbol: Option<String>,
pub body: ClosePositionBody,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct PortfolioHistory {
pub timestamp: Vec<i64>,
pub equity: Vec<f64>,
pub profit_loss: Vec<f64>,
pub profit_loss_pct: Vec<Option<f64>>,
#[serde(default)]
pub base_value: Option<f64>,
pub timeframe: String,
#[serde(default)]
pub cashflow: HashMap<ActivityType, Vec<f64>>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Watchlist {
pub id: Uuid,
pub account_id: Uuid,
pub name: String,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
#[serde(default)]
pub assets: Option<Vec<Asset>>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Clock {
pub timestamp: DateTime<Utc>,
pub is_open: bool,
pub next_open: DateTime<Utc>,
pub next_close: DateTime<Utc>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
pub struct Calendar {
pub date: NaiveDate,
pub open: NaiveDateTime,
pub close: NaiveDateTime,
pub session_open: Option<NaiveDateTime>,
pub session_close: Option<NaiveDateTime>,
pub settlement_date: Option<NaiveDate>,
}
impl<'de> Deserialize<'de> for Calendar {
fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
#[derive(Deserialize)]
struct Raw {
date: NaiveDate,
open: String,
close: String,
#[serde(default)]
session_open: Option<String>,
#[serde(default)]
session_close: Option<String>,
#[serde(default)]
settlement_date: Option<NaiveDate>,
}
let raw = Raw::deserialize(deserializer)?;
let combine = |time: &str, format: &str, field: &str| {
NaiveDateTime::parse_from_str(&format!("{} {time}", raw.date), format)
.map_err(|e| de::Error::custom(format!("{field}: {e}")))
};
let session = |time: Option<&String>, field: &str| match time {
Some(time) => combine(time, "%Y-%m-%d %H%M", field).map(Some),
None => Ok(None),
};
Ok(Self {
open: combine(&raw.open, "%Y-%m-%d %H:%M", "open")?,
close: combine(&raw.close, "%Y-%m-%d %H:%M", "close")?,
session_open: session(raw.session_open.as_ref(), "session_open")?,
session_close: session(raw.session_close.as_ref(), "session_close")?,
settlement_date: raw.settlement_date,
date: raw.date,
})
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct NonTradeActivity {
pub id: String,
pub account_id: Uuid,
pub activity_type: ActivityType,
pub date: NaiveDate,
#[serde(with = "crate::types::decimal")]
pub net_amount: Decimal,
pub description: String,
#[serde(default, deserialize_with = "empty_string_as_none")]
pub status: Option<NonTradeActivityStatus>,
#[serde(default, deserialize_with = "empty_string_as_none")]
pub symbol: Option<String>,
#[serde(default, with = "crate::types::option_decimal")]
pub qty: Option<Decimal>,
#[serde(default, with = "crate::types::option_decimal")]
pub price: Option<Decimal>,
#[serde(default, with = "crate::types::option_decimal")]
pub per_share_amount: Option<Decimal>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct TradeActivity {
pub id: String,
pub account_id: Uuid,
pub activity_type: ActivityType,
pub transaction_time: DateTime<Utc>,
#[serde(rename = "type")]
pub trade_type: TradeActivityType,
#[serde(with = "crate::types::decimal")]
pub price: Decimal,
#[serde(with = "crate::types::decimal")]
pub qty: Decimal,
pub side: OrderSide,
pub symbol: String,
#[serde(with = "crate::types::decimal")]
pub leaves_qty: Decimal,
pub order_id: Uuid,
#[serde(with = "crate::types::decimal")]
pub cum_qty: Decimal,
pub order_status: OrderStatus,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(untagged)]
pub enum Activity {
Trade(TradeActivity),
NonTrade(NonTradeActivity),
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct TradeAccount {
pub id: Uuid,
pub account_number: String,
pub status: AccountStatus,
#[serde(default, deserialize_with = "empty_string_as_none")]
pub crypto_status: Option<AccountStatus>,
#[serde(default)]
pub currency: Option<String>,
#[serde(default, with = "crate::types::option_decimal")]
pub buying_power: Option<Decimal>,
#[serde(default, with = "crate::types::option_decimal")]
pub regt_buying_power: Option<Decimal>,
#[serde(default, with = "crate::types::option_decimal")]
pub daytrading_buying_power: Option<Decimal>,
#[serde(default, with = "crate::types::option_decimal")]
pub non_marginable_buying_power: Option<Decimal>,
#[serde(default, with = "crate::types::option_decimal")]
pub cash: Option<Decimal>,
#[serde(default, with = "crate::types::option_decimal")]
pub accrued_fees: Option<Decimal>,
#[serde(default, with = "crate::types::option_decimal")]
pub pending_transfer_out: Option<Decimal>,
#[serde(default, with = "crate::types::option_decimal")]
pub pending_transfer_in: Option<Decimal>,
#[serde(default, with = "crate::types::option_decimal")]
pub portfolio_value: Option<Decimal>,
#[serde(default)]
pub pattern_day_trader: Option<bool>,
#[serde(default)]
pub trading_blocked: Option<bool>,
#[serde(default)]
pub transfers_blocked: Option<bool>,
#[serde(default)]
pub account_blocked: Option<bool>,
#[serde(default)]
pub created_at: Option<DateTime<Utc>>,
#[serde(default)]
pub trade_suspended_by_user: Option<bool>,
#[serde(default, with = "crate::types::option_decimal")]
pub multiplier: Option<Decimal>,
#[serde(default)]
pub shorting_enabled: Option<bool>,
#[serde(default, with = "crate::types::option_decimal")]
pub equity: Option<Decimal>,
#[serde(default, with = "crate::types::option_decimal")]
pub last_equity: Option<Decimal>,
#[serde(default, with = "crate::types::option_decimal")]
pub long_market_value: Option<Decimal>,
#[serde(default, with = "crate::types::option_decimal")]
pub short_market_value: Option<Decimal>,
#[serde(default, with = "crate::types::option_decimal")]
pub initial_margin: Option<Decimal>,
#[serde(default, with = "crate::types::option_decimal")]
pub maintenance_margin: Option<Decimal>,
#[serde(default, with = "crate::types::option_decimal")]
pub last_maintenance_margin: Option<Decimal>,
#[serde(default, with = "crate::types::option_decimal")]
pub sma: Option<Decimal>,
#[serde(default, with = "serde_util::int::option")]
pub daytrade_count: Option<i64>,
#[serde(default, with = "crate::types::option_decimal")]
pub options_buying_power: Option<Decimal>,
#[serde(default, with = "serde_util::int::option")]
pub options_approved_level: Option<i64>,
#[serde(default, with = "serde_util::int::option")]
pub options_trading_level: Option<i64>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct AccountConfiguration {
#[serde(default, deserialize_with = "empty_string_as_none")]
pub dtbp_check: Option<DTBPCheck>,
pub fractional_trading: bool,
pub max_margin_multiplier: String,
pub no_shorting: bool,
#[serde(default, deserialize_with = "empty_string_as_none")]
pub pdt_check: Option<PDTCheck>,
pub suspend_trade: bool,
pub trade_confirm_email: TradeConfirmationEmail,
pub ptp_no_exception_entry: bool,
#[serde(default, with = "serde_util::int::option")]
pub max_options_trading_level: Option<i64>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct CorporateActionAnnouncement {
pub id: Uuid,
pub corporate_action_id: String,
pub ca_type: CorporateActionType,
pub ca_sub_type: CorporateActionSubType,
pub initiating_symbol: String,
pub initiating_original_cusip: String,
#[serde(default, deserialize_with = "empty_string_as_none")]
pub target_symbol: Option<String>,
#[serde(default, deserialize_with = "empty_string_as_none")]
pub target_original_cusip: Option<String>,
#[serde(default, deserialize_with = "empty_string_as_none")]
pub declaration_date: Option<NaiveDate>,
#[serde(default, deserialize_with = "empty_string_as_none")]
pub ex_date: Option<NaiveDate>,
#[serde(default, deserialize_with = "empty_string_as_none")]
pub record_date: Option<NaiveDate>,
#[serde(default, deserialize_with = "empty_string_as_none")]
pub payable_date: Option<NaiveDate>,
#[serde(with = "crate::types::decimal")]
pub cash: Decimal,
#[serde(with = "crate::types::decimal")]
pub old_rate: Decimal,
#[serde(with = "crate::types::decimal")]
pub new_rate: Decimal,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct TradeUpdate {
pub event: TradeEvent,
#[serde(default, deserialize_with = "empty_string_as_none")]
pub execution_id: Option<Uuid>,
pub order: Order,
pub timestamp: DateTime<Utc>,
#[serde(default, with = "crate::types::option_decimal")]
pub position_qty: Option<Decimal>,
#[serde(default, with = "crate::types::option_decimal")]
pub price: Option<Decimal>,
#[serde(default, with = "crate::types::option_decimal")]
pub qty: Option<Decimal>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct OptionContract {
pub id: String,
pub symbol: String,
pub name: String,
pub status: AssetStatus,
pub tradable: bool,
pub expiration_date: NaiveDate,
pub root_symbol: String,
pub underlying_symbol: String,
pub underlying_asset_id: Uuid,
#[serde(rename = "type")]
pub contract_type: ContractType,
pub style: ExerciseStyle,
#[serde(with = "crate::types::decimal")]
pub strike_price: Decimal,
pub size: String,
#[serde(default, with = "crate::types::option_decimal")]
pub open_interest: Option<Decimal>,
#[serde(default, deserialize_with = "empty_string_as_none")]
pub open_interest_date: Option<NaiveDate>,
#[serde(default, with = "crate::types::option_decimal")]
pub close_price: Option<Decimal>,
#[serde(default, deserialize_with = "empty_string_as_none")]
pub close_price_date: Option<NaiveDate>,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct OptionContractsResponse {
#[serde(default)]
pub option_contracts: Option<Vec<OptionContract>>,
#[serde(default)]
pub next_page_token: Option<String>,
}