use super::order::{PriceEffect, Symbol};
use crate::accounts::AccountNumber;
use crate::types::instrument::InstrumentType;
use pretty_simple_display::{DebugPretty, DisplaySimple};
use rust_decimal::Decimal;
use serde::{Deserialize, Serialize};
use std::fmt::Display;
#[derive(Debug, Serialize, Deserialize, Clone, Copy)]
pub enum QuantityDirection {
Long,
Short,
Zero,
}
impl Display for QuantityDirection {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
QuantityDirection::Long => write!(f, "Long"),
QuantityDirection::Short => write!(f, "Short"),
QuantityDirection::Zero => write!(f, "Zero"),
}
}
}
#[derive(DebugPretty, DisplaySimple, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct FullPosition {
pub account_number: AccountNumber,
pub symbol: Symbol,
pub instrument_type: InstrumentType,
pub underlying_symbol: Symbol,
#[serde(with = "rust_decimal::serde::arbitrary_precision")]
pub quantity: Decimal,
pub quantity_direction: QuantityDirection,
#[serde(with = "rust_decimal::serde::arbitrary_precision")]
pub close_price: Decimal,
#[serde(with = "rust_decimal::serde::arbitrary_precision")]
pub average_open_price: Decimal,
#[serde(with = "rust_decimal::serde::arbitrary_precision")]
pub average_yearly_market_close_price: Decimal,
#[serde(with = "rust_decimal::serde::arbitrary_precision")]
pub average_daily_market_close_price: Decimal,
#[serde(with = "rust_decimal::serde::float")]
pub multiplier: Decimal,
pub cost_effect: PriceEffect,
pub is_suppressed: bool,
pub is_frozen: bool,
#[serde(with = "rust_decimal::serde::arbitrary_precision")]
pub restricted_quantity: Decimal,
#[serde(with = "rust_decimal::serde::arbitrary_precision")]
pub realized_day_gain: Decimal,
pub realized_day_gain_effect: String,
pub realized_day_gain_date: String,
#[serde(with = "rust_decimal::serde::arbitrary_precision")]
pub realized_today: Decimal,
pub realized_today_effect: String,
pub realized_today_date: String,
pub created_at: String,
pub updated_at: String,
}
#[derive(DebugPretty, DisplaySimple, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub struct BriefPosition {
pub account_number: AccountNumber,
pub symbol: Symbol,
pub instrument_type: InstrumentType,
pub underlying_symbol: Symbol,
#[serde(with = "rust_decimal::serde::arbitrary_precision")]
pub quantity: Decimal,
pub quantity_direction: QuantityDirection,
#[serde(with = "rust_decimal::serde::arbitrary_precision")]
pub close_price: Decimal,
#[serde(with = "rust_decimal::serde::arbitrary_precision")]
pub average_open_price: Decimal,
#[serde(with = "rust_decimal::serde::float")]
pub multiplier: Decimal,
pub cost_effect: PriceEffect,
pub is_suppressed: bool,
pub is_frozen: bool,
#[serde(with = "rust_decimal::serde::float")]
pub restricted_quantity: Decimal,
#[serde(with = "rust_decimal::serde::arbitrary_precision")]
pub realized_day_gain: Decimal,
#[serde(with = "rust_decimal::serde::arbitrary_precision")]
pub realized_today: Decimal,
pub created_at: String,
pub updated_at: String,
}
#[cfg(test)]
mod tests {
use super::*;
use rust_decimal::Decimal;
use std::str::FromStr;
#[test]
fn test_quantity_direction_display() {
assert_eq!(format!("{}", QuantityDirection::Long), "Long");
assert_eq!(format!("{}", QuantityDirection::Short), "Short");
assert_eq!(format!("{}", QuantityDirection::Zero), "Zero");
}
#[test]
fn test_quantity_direction_serialization() {
let long = QuantityDirection::Long;
let serialized = serde_json::to_string(&long).unwrap();
assert_eq!(serialized, "\"Long\"");
let short = QuantityDirection::Short;
let serialized = serde_json::to_string(&short).unwrap();
assert_eq!(serialized, "\"Short\"");
let zero = QuantityDirection::Zero;
let serialized = serde_json::to_string(&zero).unwrap();
assert_eq!(serialized, "\"Zero\"");
}
#[test]
fn test_quantity_direction_deserialization() {
let long: QuantityDirection = serde_json::from_str("\"Long\"").unwrap();
matches!(long, QuantityDirection::Long);
let short: QuantityDirection = serde_json::from_str("\"Short\"").unwrap();
matches!(short, QuantityDirection::Short);
let zero: QuantityDirection = serde_json::from_str("\"Zero\"").unwrap();
matches!(zero, QuantityDirection::Zero);
}
#[test]
fn test_quantity_direction_clone_and_copy() {
let original = QuantityDirection::Long;
let cloned = original;
let copied = original;
matches!(cloned, QuantityDirection::Long);
matches!(copied, QuantityDirection::Long);
}
#[test]
fn test_quantity_direction_debug() {
let long = QuantityDirection::Long;
let debug_str = format!("{:?}", long);
assert_eq!(debug_str, "Long");
}
#[test]
fn test_full_position_debug() {
let json = r#"{
"account-number": "TEST123",
"symbol": "AAPL",
"instrument-type": "Equity",
"underlying-symbol": "AAPL",
"quantity": "100",
"quantity-direction": "Long",
"close-price": "150.50",
"average-open-price": "145.00",
"average-yearly-market-close-price": "140.00",
"average-daily-market-close-price": "149.00",
"multiplier": 1.0,
"cost-effect": "Debit",
"is-suppressed": false,
"is-frozen": false,
"restricted-quantity": "0",
"realized-day-gain": "550.00",
"realized-day-gain-effect": "Credit",
"realized-day-gain-date": "2024-01-01",
"realized-today": "550.00",
"realized-today-effect": "Credit",
"realized-today-date": "2024-01-01",
"created-at": "2024-01-01T10:00:00Z",
"updated-at": "2024-01-01T16:00:00Z"
}"#;
let position: Result<FullPosition, _> = serde_json::from_str(json);
assert!(position.is_ok());
let position = position.unwrap();
assert_eq!(position.account_number.0, "TEST123");
assert_eq!(position.symbol.0, "AAPL");
assert_eq!(position.quantity, Decimal::from_str("100").unwrap());
matches!(position.quantity_direction, QuantityDirection::Long);
matches!(position.instrument_type, InstrumentType::Equity);
}
#[test]
fn test_brief_position_debug() {
let json = r#"{
"account-number": "BRIEF123",
"symbol": "MSFT",
"instrument-type": "Equity",
"underlying-symbol": "MSFT",
"quantity": "50",
"quantity-direction": "Short",
"close-price": "300.00",
"average-open-price": "295.00",
"multiplier": 1.0,
"cost-effect": "Credit",
"is-suppressed": true,
"is-frozen": false,
"restricted-quantity": 10.0,
"realized-day-gain": "-250.00",
"realized-today": "-250.00",
"created-at": "2024-01-01T09:00:00Z",
"updated-at": "2024-01-01T15:30:00Z"
}"#;
let position: Result<BriefPosition, _> = serde_json::from_str(json);
assert!(position.is_ok());
let position = position.unwrap();
assert_eq!(position.account_number.0, "BRIEF123");
assert_eq!(position.symbol.0, "MSFT");
assert_eq!(position.quantity, Decimal::from_str("50").unwrap());
matches!(position.quantity_direction, QuantityDirection::Short);
assert!(position.is_suppressed);
assert!(!position.is_frozen);
}
#[test]
fn test_position_with_zero_quantity() {
let json = r#"{
"account-number": "ZERO123",
"symbol": "TSLA",
"instrument-type": "Equity",
"underlying-symbol": "TSLA",
"quantity": "0",
"quantity-direction": "Zero",
"close-price": "200.00",
"average-open-price": "200.00",
"multiplier": 1.0,
"cost-effect": "None",
"is-suppressed": false,
"is-frozen": false,
"restricted-quantity": 0.0,
"realized-day-gain": "0.00",
"realized-today": "0.00",
"created-at": "2024-01-01T12:00:00Z",
"updated-at": "2024-01-01T12:00:00Z"
}"#;
let position: Result<BriefPosition, _> = serde_json::from_str(json);
assert!(position.is_ok());
let position = position.unwrap();
matches!(position.quantity_direction, QuantityDirection::Zero);
assert_eq!(position.quantity, Decimal::ZERO);
matches!(position.cost_effect, PriceEffect::None);
}
}