Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,9 @@ mod request_handler;
/// connection health events.
pub mod rti;

/// High-level trading types with serde support.
pub mod types;

/// Utility types for working with Rithmic data.
pub mod util;

Expand Down Expand Up @@ -167,3 +170,9 @@ pub use api::{

// Re-export utility types for convenience
pub use util::{InstrumentInfo, OrderStatus, rithmic_to_unix_nanos, rithmic_to_unix_nanos_precise};

// Re-export high-level trading types
pub use types::{
OrderSide, OrderType, ParseOrderSideError, ParseOrderTypeError, ParseTimeInForceError,
TimeInForce,
};
252 changes: 252 additions & 0 deletions src/types.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,252 @@
//! Order enums with serde support and protobuf conversions.

#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};

use std::fmt;
use std::str::FromStr;

use crate::rti::{
request_bracket_order, request_modify_order, request_new_order, request_oco_order,
};

/// Buy or sell.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum OrderSide {
#[default]
Buy,
Sell,
}

impl fmt::Display for OrderSide {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Buy => write!(f, "BUY"),
Self::Sell => write!(f, "SELL"),
}
}
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ParseOrderSideError(String);

impl fmt::Display for ParseOrderSideError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "invalid order side: '{}'", self.0)
}
}

impl std::error::Error for ParseOrderSideError {}

impl FromStr for OrderSide {
type Err = ParseOrderSideError;

fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_uppercase().as_str() {
"BUY" | "B" => Ok(Self::Buy),
"SELL" | "S" => Ok(Self::Sell),
_ => Err(ParseOrderSideError(s.to_string())),
}
}
}

impl From<OrderSide> for request_new_order::TransactionType {
fn from(side: OrderSide) -> Self {
match side {
OrderSide::Buy => Self::Buy,
OrderSide::Sell => Self::Sell,
}
}
}

impl From<OrderSide> for request_bracket_order::TransactionType {
fn from(side: OrderSide) -> Self {
match side {
OrderSide::Buy => Self::Buy,
OrderSide::Sell => Self::Sell,
}
}
}

impl From<OrderSide> for request_oco_order::TransactionType {
fn from(side: OrderSide) -> Self {
match side {
OrderSide::Buy => Self::Buy,
OrderSide::Sell => Self::Sell,
}
}
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum OrderType {
Market,
#[default]
Limit,
StopMarket,
StopLimit,
}

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"),
Self::StopMarket => write!(f, "STOP_MARKET"),
Self::StopLimit => write!(f, "STOP_LIMIT"),
}
}
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ParseOrderTypeError(String);

impl fmt::Display for ParseOrderTypeError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "invalid order type: '{}'", self.0)
}
}

impl std::error::Error for ParseOrderTypeError {}

impl FromStr for OrderType {
type Err = ParseOrderTypeError;

fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_uppercase().as_str() {
"MARKET" | "MKT" => Ok(Self::Market),
"LIMIT" | "LMT" => Ok(Self::Limit),
"STOPMARKET" | "STPMKT" | "STOP_MARKET" | "STOP-MARKET" => Ok(Self::StopMarket),
"STOPLIMIT" | "STPLMT" | "STOP_LIMIT" | "STOP-LIMIT" => Ok(Self::StopLimit),
_ => Err(ParseOrderTypeError(s.to_string())),
}
}
}

impl From<OrderType> for request_new_order::PriceType {
fn from(order_type: OrderType) -> Self {
match order_type {
OrderType::Market => Self::Market,
OrderType::Limit => Self::Limit,
OrderType::StopMarket => Self::StopMarket,
OrderType::StopLimit => Self::StopLimit,
}
}
}

impl From<OrderType> for request_modify_order::PriceType {
fn from(order_type: OrderType) -> Self {
match order_type {
OrderType::Market => Self::Market,
OrderType::Limit => Self::Limit,
OrderType::StopMarket => Self::StopMarket,
OrderType::StopLimit => Self::StopLimit,
}
}
}

impl From<OrderType> for request_bracket_order::PriceType {
fn from(order_type: OrderType) -> Self {
match order_type {
OrderType::Market => Self::Market,
OrderType::Limit => Self::Limit,
OrderType::StopMarket => Self::StopMarket,
OrderType::StopLimit => Self::StopLimit,
}
}
}

impl From<OrderType> for request_oco_order::PriceType {
fn from(order_type: OrderType) -> Self {
match order_type {
OrderType::Market => Self::Market,
OrderType::Limit => Self::Limit,
OrderType::StopMarket => Self::StopMarket,
OrderType::StopLimit => Self::StopLimit,
}
}
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum TimeInForce {
#[default]
Day,
Gtc,
Ioc,
Fok,
}

impl fmt::Display for TimeInForce {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Day => write!(f, "DAY"),
Self::Gtc => write!(f, "GTC"),
Self::Ioc => write!(f, "IOC"),
Self::Fok => write!(f, "FOK"),
}
}
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ParseTimeInForceError(String);

impl fmt::Display for ParseTimeInForceError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "invalid time-in-force: '{}'", self.0)
}
}

impl std::error::Error for ParseTimeInForceError {}

impl FromStr for TimeInForce {
type Err = ParseTimeInForceError;

fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_uppercase().as_str() {
"DAY" => Ok(Self::Day),
"GTC" | "GOODTILLCANCELLED" | "GOOD_TILL_CANCELLED" | "GOOD-TILL-CANCELLED" => {
Ok(Self::Gtc)
}
"IOC" | "IMMEDIATEORCANCEL" | "IMMEDIATE_OR_CANCEL" | "IMMEDIATE-OR-CANCEL" => {
Ok(Self::Ioc)
}
"FOK" | "FILLORKILL" | "FILL_OR_KILL" | "FILL-OR-KILL" => Ok(Self::Fok),
_ => Err(ParseTimeInForceError(s.to_string())),
}
}
}

impl From<TimeInForce> for request_new_order::Duration {
fn from(tif: TimeInForce) -> Self {
match tif {
TimeInForce::Day => Self::Day,
TimeInForce::Gtc => Self::Gtc,
TimeInForce::Ioc => Self::Ioc,
TimeInForce::Fok => Self::Fok,
}
}
}

impl From<TimeInForce> for request_bracket_order::Duration {
fn from(tif: TimeInForce) -> Self {
match tif {
TimeInForce::Day => Self::Day,
TimeInForce::Gtc => Self::Gtc,
TimeInForce::Ioc => Self::Ioc,
TimeInForce::Fok => Self::Fok,
}
}
}

impl From<TimeInForce> for request_oco_order::Duration {
fn from(tif: TimeInForce) -> Self {
match tif {
TimeInForce::Day => Self::Day,
TimeInForce::Gtc => Self::Gtc,
TimeInForce::Ioc => Self::Ioc,
TimeInForce::Fok => Self::Fok,
}
}
}
18 changes: 16 additions & 2 deletions src/util/order_status.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
//! Order status types and utilities.

#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};

use std::convert::Infallible;
use std::fmt;
use std::str::FromStr;
Expand All @@ -10,6 +13,7 @@ pub const CANCELLED: &str = "cancelled";
pub const PENDING: &str = "pending";
pub const REJECTED: &str = "rejected";
pub const PARTIAL: &str = "partial";
pub const EXPIRED: &str = "expired";

/// Order status with helpers for checking terminal/active states.
///
Expand All @@ -25,21 +29,26 @@ pub const PARTIAL: &str = "partial";
/// assert!(status.is_terminal());
/// ```
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum OrderStatus {
Open,
Complete,
Cancelled,
Pending,
Rejected,
Partial,
Expired,
#[default]
Unknown,
}

impl OrderStatus {
/// Returns true if this is a terminal status (Complete, Cancelled, Rejected).
/// Returns true if this is a terminal status (Complete, Cancelled, Rejected, Expired).
pub fn is_terminal(&self) -> bool {
matches!(self, Self::Complete | Self::Cancelled | Self::Rejected)
matches!(
self,
Self::Complete | Self::Cancelled | Self::Rejected | Self::Expired
)
}

/// Returns true if this is an active status (Open, Pending, Partial).
Expand All @@ -59,6 +68,7 @@ impl FromStr for OrderStatus {
PENDING => Self::Pending,
REJECTED => Self::Rejected,
PARTIAL | "partially_filled" | "partially filled" => Self::Partial,
EXPIRED => Self::Expired,
_ => Self::Unknown,
};
Ok(status)
Expand All @@ -74,6 +84,7 @@ impl fmt::Display for OrderStatus {
Self::Pending => PENDING,
Self::Rejected => REJECTED,
Self::Partial => PARTIAL,
Self::Expired => EXPIRED,
Self::Unknown => "unknown",
};
write!(f, "{}", s)
Expand Down Expand Up @@ -126,6 +137,7 @@ mod tests {
assert!(OrderStatus::Complete.is_terminal());
assert!(OrderStatus::Cancelled.is_terminal());
assert!(OrderStatus::Rejected.is_terminal());
assert!(OrderStatus::Expired.is_terminal());
assert!(!OrderStatus::Open.is_terminal());
assert!(!OrderStatus::Pending.is_terminal());
assert!(!OrderStatus::Partial.is_terminal());
Expand All @@ -140,6 +152,7 @@ mod tests {
assert!(!OrderStatus::Complete.is_active());
assert!(!OrderStatus::Cancelled.is_active());
assert!(!OrderStatus::Rejected.is_active());
assert!(!OrderStatus::Expired.is_active());
assert!(!OrderStatus::Unknown.is_active());
}

Expand All @@ -161,6 +174,7 @@ mod tests {
OrderStatus::Pending,
OrderStatus::Rejected,
OrderStatus::Partial,
OrderStatus::Expired,
] {
let s = status.to_string();
let parsed: OrderStatus = s.parse().unwrap();
Expand Down