use std::fmt;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[serde(rename_all = "lowercase")]
pub enum Side {
Buy,
Sell,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[serde(rename_all = "lowercase")]
pub enum OptionPositionEffect {
Open,
Close,
}
impl fmt::Display for OptionPositionEffect {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Open => write!(formatter, "open"),
Self::Close => write!(formatter, "close"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "clap", derive(clap::ValueEnum))]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[serde(rename_all = "lowercase")]
pub enum OrderType {
Market,
Limit,
}
impl fmt::Display for OrderType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Market => write!(f, "market"),
Self::Limit => write!(f, "limit"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[serde(rename_all = "lowercase")]
pub enum TimeInForce {
Gfd,
Gtc,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "clap", derive(clap::ValueEnum))]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[serde(rename_all = "lowercase")]
pub enum Trigger {
Immediate,
Stop,
}
impl fmt::Display for Trigger {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Immediate => write!(f, "immediate"),
Self::Stop => write!(f, "stop"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "clap", derive(clap::ValueEnum))]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[serde(rename_all = "snake_case")]
pub enum MarketHours {
#[cfg_attr(feature = "clap", value(name = "regular"))]
RegularHours,
#[cfg_attr(feature = "clap", value(name = "extended"))]
ExtendedHours,
#[cfg_attr(feature = "clap", value(name = "all-day"))]
AllDayHours,
}
impl fmt::Display for MarketHours {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::RegularHours => write!(f, "regular"),
Self::ExtendedHours => write!(f, "extended"),
Self::AllDayHours => write!(f, "all-day"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum OrderAmount {
Quantity(f64),
DollarAmount(f64),
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct CancelAllOutcome {
pub cancelled: Vec<String>,
pub failed: Vec<CancelAllFailure>,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct CancelAllFailure {
pub order_id: Option<String>,
pub error: String,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct StockOrder {
pub id: Option<String>,
pub account: Option<String>,
pub instrument: Option<String>,
pub symbol: Option<String>,
pub side: Option<String>,
pub quantity: Option<String>,
pub price: Option<String>,
pub average_price: Option<String>,
pub cumulative_quantity: Option<String>,
pub state: Option<String>,
pub created_at: Option<String>,
pub updated_at: Option<String>,
pub cancel: Option<String>,
#[serde(rename = "type")]
pub order_type: Option<String>,
pub time_in_force: Option<String>,
pub extended_hours: Option<bool>,
pub stop_price: Option<String>,
pub trigger: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OptionOrder {
pub id: Option<String>,
pub chain_id: Option<String>,
pub chain_symbol: Option<String>,
pub direction: Option<String>,
pub legs: Option<Vec<serde_json::Value>>,
pub premium: Option<String>,
pub price: Option<String>,
pub processed_premium: Option<String>,
pub quantity: Option<String>,
pub state: Option<String>,
pub time_in_force: Option<String>,
#[serde(rename = "type")]
pub order_type: Option<String>,
pub created_at: Option<String>,
pub updated_at: Option<String>,
pub cancel_url: Option<String>,
}
#[derive(Debug, Clone)]
pub struct StockOrderRequest {
pub symbol: String,
pub amount: OrderAmount,
pub side: Side,
pub order_type: OrderType,
pub limit_price: Option<f64>,
pub trigger: Trigger,
pub stop_price: Option<f64>,
pub time_in_force: TimeInForce,
pub market_hours: MarketHours,
}
#[derive(Debug, Clone)]
pub struct OptionOrderRequest {
pub symbol: String,
pub expiration_date: String,
pub strike_price: f64,
pub option_type: String,
pub side: Side,
pub quantity: f64,
pub limit_price: f64,
pub position_effect: OptionPositionEffect,
pub time_in_force: TimeInForce,
}
#[derive(Debug, Serialize)]
pub(crate) struct DollarBasedAmount {
pub amount: String,
pub currency_code: String,
}
#[derive(Debug, Serialize)]
pub(crate) struct StockOrderPayload {
pub account: String,
pub instrument: String,
pub symbol: String,
pub quantity: String,
pub side: Side,
#[serde(rename = "type")]
pub order_type: OrderType,
pub time_in_force: TimeInForce,
pub trigger: Trigger,
pub market_hours: MarketHours,
#[serde(skip_serializing_if = "Option::is_none")]
pub price: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub stop_price: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub dollar_based_amount: Option<DollarBasedAmount>,
}
#[derive(Debug, Serialize)]
pub(crate) struct OptionOrderPayload {
pub account: String,
pub direction: String,
pub time_in_force: TimeInForce,
pub legs: Vec<OptionLeg>,
#[serde(rename = "type")]
pub order_type: &'static str,
pub trigger: &'static str,
pub price: String,
pub quantity: String,
pub override_day_trade_checks: bool,
pub override_dtbp_checks: bool,
pub ref_id: String,
}
#[derive(Debug, Serialize)]
pub(crate) struct OptionLeg {
pub position_effect: OptionPositionEffect,
pub side: Side,
pub ratio_quantity: u32,
pub option: String,
}
#[cfg(test)]
mod tests {
use super::{CancelAllFailure, CancelAllOutcome};
#[test]
fn cancel_all_outcome_serializes_round_trip() {
let outcome = CancelAllOutcome {
cancelled: vec!["order-1".to_string(), "order-2".to_string()],
failed: vec![
CancelAllFailure {
order_id: Some("order-3".to_string()),
error: "already filled".to_string(),
},
CancelAllFailure {
order_id: None,
error: "open order was missing an ID".to_string(),
},
],
};
let json = serde_json::to_string(&outcome).unwrap();
let round_tripped: CancelAllOutcome = serde_json::from_str(&json).unwrap();
assert_eq!(round_tripped, outcome);
}
}