Skip to content

Commit 5c6321e

Browse files
authored
Merge pull request #34 from pbeets/feat/serde-order-types
feat: Add serde-compatible order types
2 parents 3e7c8cb + 2903fb7 commit 5c6321e

File tree

3 files changed

+277
-2
lines changed

3 files changed

+277
-2
lines changed

src/lib.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,9 @@ mod request_handler;
139139
/// connection health events.
140140
pub mod rti;
141141

142+
/// High-level trading types with serde support.
143+
pub mod types;
144+
142145
/// Utility types for working with Rithmic data.
143146
pub mod util;
144147

@@ -167,3 +170,9 @@ pub use api::{
167170

168171
// Re-export utility types for convenience
169172
pub use util::{InstrumentInfo, OrderStatus, rithmic_to_unix_nanos, rithmic_to_unix_nanos_precise};
173+
174+
// Re-export high-level trading types
175+
pub use types::{
176+
OrderSide, OrderType, ParseOrderSideError, ParseOrderTypeError, ParseTimeInForceError,
177+
TimeInForce,
178+
};

src/types.rs

Lines changed: 252 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,252 @@
1+
//! Order enums with serde support and protobuf conversions.
2+
3+
#[cfg(feature = "serde")]
4+
use serde::{Deserialize, Serialize};
5+
6+
use std::fmt;
7+
use std::str::FromStr;
8+
9+
use crate::rti::{
10+
request_bracket_order, request_modify_order, request_new_order, request_oco_order,
11+
};
12+
13+
/// Buy or sell.
14+
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
15+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
16+
pub enum OrderSide {
17+
#[default]
18+
Buy,
19+
Sell,
20+
}
21+
22+
impl fmt::Display for OrderSide {
23+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
24+
match self {
25+
Self::Buy => write!(f, "BUY"),
26+
Self::Sell => write!(f, "SELL"),
27+
}
28+
}
29+
}
30+
31+
#[derive(Debug, Clone, PartialEq, Eq)]
32+
pub struct ParseOrderSideError(String);
33+
34+
impl fmt::Display for ParseOrderSideError {
35+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
36+
write!(f, "invalid order side: '{}'", self.0)
37+
}
38+
}
39+
40+
impl std::error::Error for ParseOrderSideError {}
41+
42+
impl FromStr for OrderSide {
43+
type Err = ParseOrderSideError;
44+
45+
fn from_str(s: &str) -> Result<Self, Self::Err> {
46+
match s.to_uppercase().as_str() {
47+
"BUY" | "B" => Ok(Self::Buy),
48+
"SELL" | "S" => Ok(Self::Sell),
49+
_ => Err(ParseOrderSideError(s.to_string())),
50+
}
51+
}
52+
}
53+
54+
impl From<OrderSide> for request_new_order::TransactionType {
55+
fn from(side: OrderSide) -> Self {
56+
match side {
57+
OrderSide::Buy => Self::Buy,
58+
OrderSide::Sell => Self::Sell,
59+
}
60+
}
61+
}
62+
63+
impl From<OrderSide> for request_bracket_order::TransactionType {
64+
fn from(side: OrderSide) -> Self {
65+
match side {
66+
OrderSide::Buy => Self::Buy,
67+
OrderSide::Sell => Self::Sell,
68+
}
69+
}
70+
}
71+
72+
impl From<OrderSide> for request_oco_order::TransactionType {
73+
fn from(side: OrderSide) -> Self {
74+
match side {
75+
OrderSide::Buy => Self::Buy,
76+
OrderSide::Sell => Self::Sell,
77+
}
78+
}
79+
}
80+
81+
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
82+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
83+
pub enum OrderType {
84+
Market,
85+
#[default]
86+
Limit,
87+
StopMarket,
88+
StopLimit,
89+
}
90+
91+
impl fmt::Display for OrderType {
92+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
93+
match self {
94+
Self::Market => write!(f, "MARKET"),
95+
Self::Limit => write!(f, "LIMIT"),
96+
Self::StopMarket => write!(f, "STOP_MARKET"),
97+
Self::StopLimit => write!(f, "STOP_LIMIT"),
98+
}
99+
}
100+
}
101+
102+
#[derive(Debug, Clone, PartialEq, Eq)]
103+
pub struct ParseOrderTypeError(String);
104+
105+
impl fmt::Display for ParseOrderTypeError {
106+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
107+
write!(f, "invalid order type: '{}'", self.0)
108+
}
109+
}
110+
111+
impl std::error::Error for ParseOrderTypeError {}
112+
113+
impl FromStr for OrderType {
114+
type Err = ParseOrderTypeError;
115+
116+
fn from_str(s: &str) -> Result<Self, Self::Err> {
117+
match s.to_uppercase().as_str() {
118+
"MARKET" | "MKT" => Ok(Self::Market),
119+
"LIMIT" | "LMT" => Ok(Self::Limit),
120+
"STOPMARKET" | "STPMKT" | "STOP_MARKET" | "STOP-MARKET" => Ok(Self::StopMarket),
121+
"STOPLIMIT" | "STPLMT" | "STOP_LIMIT" | "STOP-LIMIT" => Ok(Self::StopLimit),
122+
_ => Err(ParseOrderTypeError(s.to_string())),
123+
}
124+
}
125+
}
126+
127+
impl From<OrderType> for request_new_order::PriceType {
128+
fn from(order_type: OrderType) -> Self {
129+
match order_type {
130+
OrderType::Market => Self::Market,
131+
OrderType::Limit => Self::Limit,
132+
OrderType::StopMarket => Self::StopMarket,
133+
OrderType::StopLimit => Self::StopLimit,
134+
}
135+
}
136+
}
137+
138+
impl From<OrderType> for request_modify_order::PriceType {
139+
fn from(order_type: OrderType) -> Self {
140+
match order_type {
141+
OrderType::Market => Self::Market,
142+
OrderType::Limit => Self::Limit,
143+
OrderType::StopMarket => Self::StopMarket,
144+
OrderType::StopLimit => Self::StopLimit,
145+
}
146+
}
147+
}
148+
149+
impl From<OrderType> for request_bracket_order::PriceType {
150+
fn from(order_type: OrderType) -> Self {
151+
match order_type {
152+
OrderType::Market => Self::Market,
153+
OrderType::Limit => Self::Limit,
154+
OrderType::StopMarket => Self::StopMarket,
155+
OrderType::StopLimit => Self::StopLimit,
156+
}
157+
}
158+
}
159+
160+
impl From<OrderType> for request_oco_order::PriceType {
161+
fn from(order_type: OrderType) -> Self {
162+
match order_type {
163+
OrderType::Market => Self::Market,
164+
OrderType::Limit => Self::Limit,
165+
OrderType::StopMarket => Self::StopMarket,
166+
OrderType::StopLimit => Self::StopLimit,
167+
}
168+
}
169+
}
170+
171+
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
172+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
173+
pub enum TimeInForce {
174+
#[default]
175+
Day,
176+
Gtc,
177+
Ioc,
178+
Fok,
179+
}
180+
181+
impl fmt::Display for TimeInForce {
182+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
183+
match self {
184+
Self::Day => write!(f, "DAY"),
185+
Self::Gtc => write!(f, "GTC"),
186+
Self::Ioc => write!(f, "IOC"),
187+
Self::Fok => write!(f, "FOK"),
188+
}
189+
}
190+
}
191+
192+
#[derive(Debug, Clone, PartialEq, Eq)]
193+
pub struct ParseTimeInForceError(String);
194+
195+
impl fmt::Display for ParseTimeInForceError {
196+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
197+
write!(f, "invalid time-in-force: '{}'", self.0)
198+
}
199+
}
200+
201+
impl std::error::Error for ParseTimeInForceError {}
202+
203+
impl FromStr for TimeInForce {
204+
type Err = ParseTimeInForceError;
205+
206+
fn from_str(s: &str) -> Result<Self, Self::Err> {
207+
match s.to_uppercase().as_str() {
208+
"DAY" => Ok(Self::Day),
209+
"GTC" | "GOODTILLCANCELLED" | "GOOD_TILL_CANCELLED" | "GOOD-TILL-CANCELLED" => {
210+
Ok(Self::Gtc)
211+
}
212+
"IOC" | "IMMEDIATEORCANCEL" | "IMMEDIATE_OR_CANCEL" | "IMMEDIATE-OR-CANCEL" => {
213+
Ok(Self::Ioc)
214+
}
215+
"FOK" | "FILLORKILL" | "FILL_OR_KILL" | "FILL-OR-KILL" => Ok(Self::Fok),
216+
_ => Err(ParseTimeInForceError(s.to_string())),
217+
}
218+
}
219+
}
220+
221+
impl From<TimeInForce> for request_new_order::Duration {
222+
fn from(tif: TimeInForce) -> Self {
223+
match tif {
224+
TimeInForce::Day => Self::Day,
225+
TimeInForce::Gtc => Self::Gtc,
226+
TimeInForce::Ioc => Self::Ioc,
227+
TimeInForce::Fok => Self::Fok,
228+
}
229+
}
230+
}
231+
232+
impl From<TimeInForce> for request_bracket_order::Duration {
233+
fn from(tif: TimeInForce) -> Self {
234+
match tif {
235+
TimeInForce::Day => Self::Day,
236+
TimeInForce::Gtc => Self::Gtc,
237+
TimeInForce::Ioc => Self::Ioc,
238+
TimeInForce::Fok => Self::Fok,
239+
}
240+
}
241+
}
242+
243+
impl From<TimeInForce> for request_oco_order::Duration {
244+
fn from(tif: TimeInForce) -> Self {
245+
match tif {
246+
TimeInForce::Day => Self::Day,
247+
TimeInForce::Gtc => Self::Gtc,
248+
TimeInForce::Ioc => Self::Ioc,
249+
TimeInForce::Fok => Self::Fok,
250+
}
251+
}
252+
}

src/util/order_status.rs

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
//! Order status types and utilities.
22
3+
#[cfg(feature = "serde")]
4+
use serde::{Deserialize, Serialize};
5+
36
use std::convert::Infallible;
47
use std::fmt;
58
use std::str::FromStr;
@@ -10,6 +13,7 @@ pub const CANCELLED: &str = "cancelled";
1013
pub const PENDING: &str = "pending";
1114
pub const REJECTED: &str = "rejected";
1215
pub const PARTIAL: &str = "partial";
16+
pub const EXPIRED: &str = "expired";
1317

1418
/// Order status with helpers for checking terminal/active states.
1519
///
@@ -25,21 +29,26 @@ pub const PARTIAL: &str = "partial";
2529
/// assert!(status.is_terminal());
2630
/// ```
2731
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
32+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
2833
pub enum OrderStatus {
2934
Open,
3035
Complete,
3136
Cancelled,
3237
Pending,
3338
Rejected,
3439
Partial,
40+
Expired,
3541
#[default]
3642
Unknown,
3743
}
3844

3945
impl OrderStatus {
40-
/// Returns true if this is a terminal status (Complete, Cancelled, Rejected).
46+
/// Returns true if this is a terminal status (Complete, Cancelled, Rejected, Expired).
4147
pub fn is_terminal(&self) -> bool {
42-
matches!(self, Self::Complete | Self::Cancelled | Self::Rejected)
48+
matches!(
49+
self,
50+
Self::Complete | Self::Cancelled | Self::Rejected | Self::Expired
51+
)
4352
}
4453

4554
/// Returns true if this is an active status (Open, Pending, Partial).
@@ -59,6 +68,7 @@ impl FromStr for OrderStatus {
5968
PENDING => Self::Pending,
6069
REJECTED => Self::Rejected,
6170
PARTIAL | "partially_filled" | "partially filled" => Self::Partial,
71+
EXPIRED => Self::Expired,
6272
_ => Self::Unknown,
6373
};
6474
Ok(status)
@@ -74,6 +84,7 @@ impl fmt::Display for OrderStatus {
7484
Self::Pending => PENDING,
7585
Self::Rejected => REJECTED,
7686
Self::Partial => PARTIAL,
87+
Self::Expired => EXPIRED,
7788
Self::Unknown => "unknown",
7889
};
7990
write!(f, "{}", s)
@@ -126,6 +137,7 @@ mod tests {
126137
assert!(OrderStatus::Complete.is_terminal());
127138
assert!(OrderStatus::Cancelled.is_terminal());
128139
assert!(OrderStatus::Rejected.is_terminal());
140+
assert!(OrderStatus::Expired.is_terminal());
129141
assert!(!OrderStatus::Open.is_terminal());
130142
assert!(!OrderStatus::Pending.is_terminal());
131143
assert!(!OrderStatus::Partial.is_terminal());
@@ -140,6 +152,7 @@ mod tests {
140152
assert!(!OrderStatus::Complete.is_active());
141153
assert!(!OrderStatus::Cancelled.is_active());
142154
assert!(!OrderStatus::Rejected.is_active());
155+
assert!(!OrderStatus::Expired.is_active());
143156
assert!(!OrderStatus::Unknown.is_active());
144157
}
145158

@@ -161,6 +174,7 @@ mod tests {
161174
OrderStatus::Pending,
162175
OrderStatus::Rejected,
163176
OrderStatus::Partial,
177+
OrderStatus::Expired,
164178
] {
165179
let s = status.to_string();
166180
let parsed: OrderStatus = s.parse().unwrap();

0 commit comments

Comments
 (0)