use crate::accounts::AccountNumber;
use crate::types::instrument::InstrumentType;
use derive_builder::Builder;
use pretty_simple_display::{DebugPretty, DisplaySimple};
use rust_decimal::Decimal;
use serde::{Deserialize, Serialize};
use std::fmt;
#[derive(Debug, Serialize, Deserialize, Clone)]
pub enum PriceEffect {
Debit,
Credit,
None,
}
impl fmt::Display for PriceEffect {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
PriceEffect::Debit => write!(f, "Debit"),
PriceEffect::Credit => write!(f, "Credit"),
PriceEffect::None => write!(f, "None"),
}
}
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub enum Action {
#[serde(rename = "Buy to Open")]
BuyToOpen,
#[serde(rename = "Sell to Open")]
SellToOpen,
#[serde(rename = "Buy to Close")]
BuyToClose,
#[serde(rename = "Sell to Close")]
SellToClose,
Sell,
Buy,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub enum OrderType {
Limit,
Market,
#[serde(rename = "Marketable Limit")]
MarketableLimit,
Stop,
#[serde(rename = "Stop Limit")]
StopLimit,
#[serde(rename = "Notional Market")]
NotionalMarket,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub enum TimeInForce {
#[serde(rename = "Day")]
Day,
#[serde(rename = "GTC")]
Gtc,
#[serde(rename = "GTD")]
Gtd,
#[serde(rename = "Ext")]
Ext,
#[serde(rename = "GTC Ext")]
GTCExt,
#[serde(rename = "IOC")]
Ioc,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub enum OrderStatus {
Received,
Routed,
#[serde(rename = "In Flight")]
InFlight,
Live,
#[serde(rename = "Cancel Requested")]
CancelRequested,
#[serde(rename = "Replace Requested")]
ReplaceRequested,
Contingent,
Filled,
Cancelled,
Expired,
Rejected,
Removed,
#[serde(rename = "Partially Removed")]
PartiallyRemoved,
}
impl fmt::Display for OrderStatus {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
OrderStatus::Received => write!(f, "Received"),
OrderStatus::Routed => write!(f, "Routed"),
OrderStatus::InFlight => write!(f, "In Flight"),
OrderStatus::Live => write!(f, "Live"),
OrderStatus::CancelRequested => write!(f, "Cancel Requested"),
OrderStatus::ReplaceRequested => write!(f, "Replace Requested"),
OrderStatus::Contingent => write!(f, "Contingent"),
OrderStatus::Filled => write!(f, "Filled"),
OrderStatus::Cancelled => write!(f, "Cancelled"),
OrderStatus::Expired => write!(f, "Expired"),
OrderStatus::Rejected => write!(f, "Rejected"),
OrderStatus::Removed => write!(f, "Removed"),
OrderStatus::PartiallyRemoved => write!(f, "Partially Removed"),
}
}
}
#[derive(
DebugPretty, DisplaySimple, Serialize, Deserialize, Clone, PartialEq, Eq, PartialOrd, Ord, Hash,
)]
#[serde(transparent)]
pub struct Symbol(pub String);
impl<T: AsRef<str>> From<T> for Symbol {
fn from(value: T) -> Self {
Self(value.as_ref().to_owned())
}
}
pub trait AsSymbol {
fn as_symbol(&self) -> Symbol;
}
impl<T: AsRef<str>> AsSymbol for T {
fn as_symbol(&self) -> Symbol {
Symbol(self.as_ref().to_owned())
}
}
impl AsSymbol for Symbol {
fn as_symbol(&self) -> Symbol {
self.clone()
}
}
impl AsSymbol for &Symbol {
fn as_symbol(&self) -> Symbol {
(*self).clone()
}
}
#[derive(DebugPretty, DisplaySimple, Serialize, Deserialize, Clone)]
#[serde(transparent)]
pub struct OrderId(pub u64);
#[derive(DebugPretty, DisplaySimple, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct LiveOrderRecord {
pub id: OrderId,
pub account_number: AccountNumber,
pub time_in_force: TimeInForce,
pub order_type: OrderType,
pub size: u64,
pub underlying_symbol: Symbol,
#[serde(with = "rust_decimal::serde::arbitrary_precision")]
pub price: Decimal,
pub price_effect: PriceEffect,
pub status: OrderStatus,
pub cancellable: bool,
pub editable: bool,
pub edited: bool,
}
#[allow(dead_code)]
#[derive(DebugPretty, DisplaySimple, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct LiveOrderLeg {
pub instrument_type: InstrumentType,
pub symbol: Symbol,
pub quantity: u64,
pub remaining_quantity: u64,
pub action: Action,
pub fills: Vec<String>,
}
#[derive(Builder, Serialize)]
#[serde(rename_all = "kebab-case")]
#[builder(setter(into))]
pub struct Order {
time_in_force: TimeInForce,
order_type: OrderType,
#[serde(with = "rust_decimal::serde::arbitrary_precision")]
price: Decimal,
price_effect: PriceEffect,
legs: Vec<OrderLeg>,
}
#[derive(Builder, Serialize, Deserialize, Clone, Debug)]
#[serde(rename_all = "kebab-case")]
#[builder(setter(into))]
pub struct OrderLeg {
instrument_type: InstrumentType,
symbol: Symbol,
#[serde(with = "rust_decimal::serde::float")]
quantity: Decimal,
action: Action,
}
#[derive(DebugPretty, DisplaySimple, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct OrderPlacedResult {
pub order: LiveOrderRecord,
pub warnings: Vec<Warning>,
pub buying_power_effect: BuyingPowerEffect,
pub fee_calculation: FeeCalculation,
}
#[derive(DebugPretty, DisplaySimple, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct DryRunResult {
pub order: DryRunRecord,
pub warnings: Vec<Warning>,
pub buying_power_effect: BuyingPowerEffect,
pub fee_calculation: FeeCalculation,
}
#[derive(DebugPretty, DisplaySimple, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct DryRunRecord {
pub account_number: AccountNumber,
pub time_in_force: TimeInForce,
pub order_type: OrderType,
pub size: u64,
pub underlying_symbol: Symbol,
#[serde(with = "rust_decimal::serde::arbitrary_precision")]
pub price: Decimal,
pub price_effect: PriceEffect,
pub status: OrderStatus,
pub cancellable: bool,
pub editable: bool,
pub edited: bool,
pub legs: Vec<OrderLeg>,
}
#[derive(DebugPretty, DisplaySimple, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct BuyingPowerEffect {
#[serde(with = "rust_decimal::serde::arbitrary_precision")]
pub change_in_margin_requirement: Decimal,
pub change_in_margin_requirement_effect: PriceEffect,
#[serde(with = "rust_decimal::serde::arbitrary_precision")]
pub change_in_buying_power: Decimal,
pub change_in_buying_power_effect: PriceEffect,
#[serde(with = "rust_decimal::serde::arbitrary_precision")]
pub current_buying_power: Decimal,
pub current_buying_power_effect: PriceEffect,
#[serde(with = "rust_decimal::serde::arbitrary_precision")]
pub impact: Decimal,
pub effect: PriceEffect,
}
#[derive(DebugPretty, DisplaySimple, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct FeeCalculation {
#[serde(with = "rust_decimal::serde::arbitrary_precision")]
pub total_fees: Decimal,
pub total_fees_effect: PriceEffect,
}
#[derive(DebugPretty, DisplaySimple, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct Warning {}
#[cfg(test)]
mod tests {
use super::*;
use rust_decimal::Decimal;
use std::str::FromStr;
#[test]
fn test_price_effect_display() {
assert_eq!(format!("{}", PriceEffect::Debit), "Debit");
assert_eq!(format!("{}", PriceEffect::Credit), "Credit");
assert_eq!(format!("{}", PriceEffect::None), "None");
}
#[test]
fn test_order_status_display() {
assert_eq!(format!("{}", OrderStatus::Received), "Received");
assert_eq!(format!("{}", OrderStatus::Live), "Live");
assert_eq!(format!("{}", OrderStatus::Filled), "Filled");
assert_eq!(format!("{}", OrderStatus::Cancelled), "Cancelled");
assert_eq!(format!("{}", OrderStatus::InFlight), "In Flight");
assert_eq!(
format!("{}", OrderStatus::CancelRequested),
"Cancel Requested"
);
assert_eq!(
format!("{}", OrderStatus::ReplaceRequested),
"Replace Requested"
);
assert_eq!(
format!("{}", OrderStatus::PartiallyRemoved),
"Partially Removed"
);
}
#[test]
fn test_symbol_from_string() {
let symbol = Symbol::from("AAPL");
assert_eq!(symbol.0, "AAPL");
let symbol = Symbol::from(String::from("MSFT"));
assert_eq!(symbol.0, "MSFT");
}
#[test]
fn test_symbol_as_symbol_trait() {
let symbol_str = "TSLA";
let symbol = symbol_str.as_symbol();
assert_eq!(symbol.0, "TSLA");
let symbol_string = String::from("GOOGL");
let symbol = symbol_string.as_symbol();
assert_eq!(symbol.0, "GOOGL");
let symbol_obj = Symbol::from("NVDA");
let symbol = symbol_obj.as_symbol();
assert_eq!(symbol.0, "NVDA");
let symbol_ref = &Symbol::from("AMD");
let symbol = symbol_ref.as_symbol();
assert_eq!(symbol.0, "AMD");
}
#[test]
fn test_order_id() {
let order_id = OrderId(12345);
assert_eq!(order_id.0, 12345);
}
#[test]
fn test_order_builder() {
let order = OrderBuilder::default()
.time_in_force(TimeInForce::Day)
.order_type(OrderType::Limit)
.price(Decimal::from_str("150.50").unwrap())
.price_effect(PriceEffect::Debit)
.legs(vec![])
.build()
.unwrap();
let serialized = serde_json::to_string(&order).unwrap();
assert!(serialized.contains("Day"));
assert!(serialized.contains("Limit"));
assert!(serialized.contains("150.50"));
assert!(serialized.contains("Debit"));
}
#[test]
fn test_order_leg_builder() {
let order_leg = OrderLegBuilder::default()
.instrument_type(InstrumentType::Equity)
.symbol(Symbol::from("AAPL"))
.quantity(Decimal::from(100))
.action(Action::Buy)
.build()
.unwrap();
let serialized = serde_json::to_string(&order_leg).unwrap();
assert!(serialized.contains("Equity"));
assert!(serialized.contains("AAPL"));
assert!(serialized.contains("100"));
assert!(serialized.contains("Buy"));
}
#[test]
fn test_enum_serialization() {
let action = Action::BuyToOpen;
let serialized = serde_json::to_string(&action).unwrap();
assert_eq!(serialized, "\"Buy to Open\"");
let action = Action::SellToClose;
let serialized = serde_json::to_string(&action).unwrap();
assert_eq!(serialized, "\"Sell to Close\"");
let order_type = OrderType::MarketableLimit;
let serialized = serde_json::to_string(&order_type).unwrap();
assert_eq!(serialized, "\"Marketable Limit\"");
let order_type = OrderType::StopLimit;
let serialized = serde_json::to_string(&order_type).unwrap();
assert_eq!(serialized, "\"Stop Limit\"");
let tif = TimeInForce::Gtc;
let serialized = serde_json::to_string(&tif).unwrap();
assert_eq!(serialized, "\"GTC\"");
let tif = TimeInForce::GTCExt;
let serialized = serde_json::to_string(&tif).unwrap();
assert_eq!(serialized, "\"GTC Ext\"");
}
#[test]
fn test_enum_deserialization() {
let action: Action = serde_json::from_str("\"Buy to Open\"").unwrap();
matches!(action, Action::BuyToOpen);
let action: Action = serde_json::from_str("\"Sell to Close\"").unwrap();
matches!(action, Action::SellToClose);
let status: OrderStatus = serde_json::from_str("\"In Flight\"").unwrap();
matches!(status, OrderStatus::InFlight);
let status: OrderStatus = serde_json::from_str("\"Cancel Requested\"").unwrap();
matches!(status, OrderStatus::CancelRequested);
}
#[test]
fn test_symbol_clone_and_eq() {
let symbol1 = Symbol::from("AAPL");
let symbol2 = symbol1.clone();
assert_eq!(symbol1, symbol2);
let symbol3 = Symbol::from("MSFT");
assert_ne!(symbol1, symbol3);
}
#[test]
fn test_symbol_ordering() {
let symbol1 = Symbol::from("AAPL");
let symbol2 = Symbol::from("MSFT");
let symbol3 = Symbol::from("AAPL");
assert!(symbol1 < symbol2);
assert!(symbol1 <= symbol3);
assert!(symbol2 > symbol1);
assert_eq!(symbol1, symbol3);
}
#[test]
fn test_price_effect_clone() {
let effect1 = PriceEffect::Debit;
let effect2 = effect1.clone();
matches!(effect2, PriceEffect::Debit);
}
#[test]
fn test_all_enum_variants_exist() {
let _actions = [
Action::BuyToOpen,
Action::SellToOpen,
Action::BuyToClose,
Action::SellToClose,
Action::Sell,
Action::Buy,
];
let _order_types = [
OrderType::Limit,
OrderType::Market,
OrderType::MarketableLimit,
OrderType::Stop,
OrderType::StopLimit,
OrderType::NotionalMarket,
];
let _time_in_forces = [
TimeInForce::Day,
TimeInForce::Gtc,
TimeInForce::Gtd,
TimeInForce::Ext,
TimeInForce::GTCExt,
TimeInForce::Ioc,
];
let _statuses = [
OrderStatus::Received,
OrderStatus::Routed,
OrderStatus::InFlight,
OrderStatus::Live,
OrderStatus::CancelRequested,
OrderStatus::ReplaceRequested,
OrderStatus::Contingent,
OrderStatus::Filled,
OrderStatus::Cancelled,
OrderStatus::Expired,
OrderStatus::Rejected,
OrderStatus::Removed,
OrderStatus::PartiallyRemoved,
];
}
}