1#[cfg(feature = "serde")]
4use serde::{Deserialize, Serialize};
5
6use std::fmt;
7use std::str::FromStr;
8
9use crate::{
10 error::RithmicError,
11 rti::{
12 request_account_rms_updates, request_bracket_order, request_cancel_all_orders,
13 request_cancel_order, request_easy_to_borrow_list, request_exit_position,
14 request_modify_order, request_new_order, request_oco_order,
15 },
16};
17
18pub use crate::rti::request_time_bar_replay::BarType as TimeBarType;
23
24#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
26#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
27#[non_exhaustive]
28pub enum OrderSide {
29 #[default]
31 Buy,
32 Sell,
34}
35
36impl OrderSide {
37 pub fn as_str_name(&self) -> &'static str {
39 match self {
40 Self::Buy => "BUY",
41 Self::Sell => "SELL",
42 }
43 }
44}
45
46impl fmt::Display for OrderSide {
47 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
48 f.write_str(self.as_str_name())
49 }
50}
51
52#[derive(Debug, Clone, PartialEq, Eq)]
54pub struct ParseOrderSideError(String);
55
56impl fmt::Display for ParseOrderSideError {
57 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
58 write!(f, "invalid order side: '{}'", self.0)
59 }
60}
61
62impl std::error::Error for ParseOrderSideError {}
63
64impl FromStr for OrderSide {
65 type Err = ParseOrderSideError;
66
67 fn from_str(s: &str) -> Result<Self, Self::Err> {
68 match s.to_uppercase().as_str() {
69 "BUY" | "B" => Ok(Self::Buy),
70 "SELL" | "S" => Ok(Self::Sell),
71 _ => Err(ParseOrderSideError(s.to_string())),
72 }
73 }
74}
75
76impl From<OrderSide> for request_new_order::TransactionType {
77 fn from(side: OrderSide) -> Self {
78 match side {
79 OrderSide::Buy => Self::Buy,
80 OrderSide::Sell => Self::Sell,
81 }
82 }
83}
84
85impl From<OrderSide> for request_bracket_order::TransactionType {
86 fn from(side: OrderSide) -> Self {
87 match side {
88 OrderSide::Buy => Self::Buy,
89 OrderSide::Sell => Self::Sell,
90 }
91 }
92}
93
94impl From<OrderSide> for request_oco_order::TransactionType {
95 fn from(side: OrderSide) -> Self {
96 match side {
97 OrderSide::Buy => Self::Buy,
98 OrderSide::Sell => Self::Sell,
99 }
100 }
101}
102
103#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
105#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
106#[non_exhaustive]
107pub enum OrderType {
108 Market,
110 #[default]
112 Limit,
113 StopMarket,
115 StopLimit,
117 MarketIfTouched,
119 LimitIfTouched,
121}
122
123impl OrderType {
124 pub fn as_str_name(&self) -> &'static str {
126 match self {
127 Self::Market => "MARKET",
128 Self::Limit => "LIMIT",
129 Self::StopMarket => "STOP_MARKET",
130 Self::StopLimit => "STOP_LIMIT",
131 Self::MarketIfTouched => "MARKET_IF_TOUCHED",
132 Self::LimitIfTouched => "LIMIT_IF_TOUCHED",
133 }
134 }
135}
136
137impl fmt::Display for OrderType {
138 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
139 f.write_str(self.as_str_name())
140 }
141}
142
143#[derive(Debug, Clone, PartialEq, Eq)]
145pub struct ParseOrderTypeError(String);
146
147impl fmt::Display for ParseOrderTypeError {
148 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
149 write!(f, "invalid order type: '{}'", self.0)
150 }
151}
152
153impl std::error::Error for ParseOrderTypeError {}
154
155impl FromStr for OrderType {
156 type Err = ParseOrderTypeError;
157
158 fn from_str(s: &str) -> Result<Self, Self::Err> {
159 match s.to_uppercase().as_str() {
160 "MARKET" | "MKT" => Ok(Self::Market),
161 "LIMIT" | "LMT" => Ok(Self::Limit),
162 "STOPMARKET" | "STPMKT" | "STOP_MARKET" | "STOP-MARKET" => Ok(Self::StopMarket),
163 "STOPLIMIT" | "STPLMT" | "STOP_LIMIT" | "STOP-LIMIT" => Ok(Self::StopLimit),
164 "MARKETIFTOUCHED" | "MIT" | "MARKET_IF_TOUCHED" | "MARKET-IF-TOUCHED" => {
165 Ok(Self::MarketIfTouched)
166 }
167 "LIMITIFTOUCHED" | "LIT" | "LIMIT_IF_TOUCHED" | "LIMIT-IF-TOUCHED" => {
168 Ok(Self::LimitIfTouched)
169 }
170 _ => Err(ParseOrderTypeError(s.to_string())),
171 }
172 }
173}
174
175impl From<OrderType> for request_new_order::PriceType {
176 fn from(order_type: OrderType) -> Self {
177 match order_type {
178 OrderType::Market => Self::Market,
179 OrderType::Limit => Self::Limit,
180 OrderType::StopMarket => Self::StopMarket,
181 OrderType::StopLimit => Self::StopLimit,
182 OrderType::MarketIfTouched => Self::MarketIfTouched,
183 OrderType::LimitIfTouched => Self::LimitIfTouched,
184 }
185 }
186}
187
188impl From<OrderType> for request_modify_order::PriceType {
189 fn from(order_type: OrderType) -> Self {
190 match order_type {
191 OrderType::Market => Self::Market,
192 OrderType::Limit => Self::Limit,
193 OrderType::StopMarket => Self::StopMarket,
194 OrderType::StopLimit => Self::StopLimit,
195 OrderType::MarketIfTouched => Self::MarketIfTouched,
196 OrderType::LimitIfTouched => Self::LimitIfTouched,
197 }
198 }
199}
200
201impl From<OrderType> for request_bracket_order::PriceType {
202 fn from(order_type: OrderType) -> Self {
203 match order_type {
204 OrderType::Market => Self::Market,
205 OrderType::Limit => Self::Limit,
206 OrderType::StopMarket => Self::StopMarket,
207 OrderType::StopLimit => Self::StopLimit,
208 OrderType::MarketIfTouched => Self::MarketIfTouched,
209 OrderType::LimitIfTouched => Self::LimitIfTouched,
210 }
211 }
212}
213
214impl TryFrom<OrderType> for request_oco_order::PriceType {
217 type Error = RithmicError;
218
219 fn try_from(order_type: OrderType) -> Result<Self, Self::Error> {
220 match order_type {
221 OrderType::Market => Ok(Self::Market),
222 OrderType::Limit => Ok(Self::Limit),
223 OrderType::StopMarket => Ok(Self::StopMarket),
224 OrderType::StopLimit => Ok(Self::StopLimit),
225 OrderType::MarketIfTouched | OrderType::LimitIfTouched => {
226 Err(RithmicError::InvalidArgument(format!(
227 "price_type {} is not available on an OCO leg",
228 order_type.as_str_name()
229 )))
230 }
231 }
232 }
233}
234
235#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
237#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
238#[non_exhaustive]
239pub enum TimeInForce {
240 #[default]
242 Day,
243 Gtc,
245 Ioc,
247 Fok,
249}
250
251impl TimeInForce {
252 pub fn as_str_name(&self) -> &'static str {
254 match self {
255 Self::Day => "DAY",
256 Self::Gtc => "GTC",
257 Self::Ioc => "IOC",
258 Self::Fok => "FOK",
259 }
260 }
261}
262
263impl fmt::Display for TimeInForce {
264 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
265 f.write_str(self.as_str_name())
266 }
267}
268
269#[derive(Debug, Clone, PartialEq, Eq)]
271pub struct ParseTimeInForceError(String);
272
273impl fmt::Display for ParseTimeInForceError {
274 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
275 write!(f, "invalid time-in-force: '{}'", self.0)
276 }
277}
278
279impl std::error::Error for ParseTimeInForceError {}
280
281impl FromStr for TimeInForce {
282 type Err = ParseTimeInForceError;
283
284 fn from_str(s: &str) -> Result<Self, Self::Err> {
285 match s.to_uppercase().as_str() {
286 "DAY" => Ok(Self::Day),
287 "GTC" | "GOODTILLCANCELLED" | "GOOD_TILL_CANCELLED" | "GOOD-TILL-CANCELLED" => {
288 Ok(Self::Gtc)
289 }
290 "IOC" | "IMMEDIATEORCANCEL" | "IMMEDIATE_OR_CANCEL" | "IMMEDIATE-OR-CANCEL" => {
291 Ok(Self::Ioc)
292 }
293 "FOK" | "FILLORKILL" | "FILL_OR_KILL" | "FILL-OR-KILL" => Ok(Self::Fok),
294 _ => Err(ParseTimeInForceError(s.to_string())),
295 }
296 }
297}
298
299impl From<TimeInForce> for request_new_order::Duration {
300 fn from(tif: TimeInForce) -> Self {
301 match tif {
302 TimeInForce::Day => Self::Day,
303 TimeInForce::Gtc => Self::Gtc,
304 TimeInForce::Ioc => Self::Ioc,
305 TimeInForce::Fok => Self::Fok,
306 }
307 }
308}
309
310impl From<TimeInForce> for request_bracket_order::Duration {
311 fn from(tif: TimeInForce) -> Self {
312 match tif {
313 TimeInForce::Day => Self::Day,
314 TimeInForce::Gtc => Self::Gtc,
315 TimeInForce::Ioc => Self::Ioc,
316 TimeInForce::Fok => Self::Fok,
317 }
318 }
319}
320
321impl From<TimeInForce> for request_oco_order::Duration {
322 fn from(tif: TimeInForce) -> Self {
323 match tif {
324 TimeInForce::Day => Self::Day,
325 TimeInForce::Gtc => Self::Gtc,
326 TimeInForce::Ioc => Self::Ioc,
327 TimeInForce::Fok => Self::Fok,
328 }
329 }
330}
331
332#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
334#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
335#[non_exhaustive]
336pub enum ManualOrAutoEntry {
337 Manual,
339 #[default]
341 Auto,
342}
343
344impl ManualOrAutoEntry {
345 pub fn as_str_name(&self) -> &'static str {
347 match self {
348 Self::Manual => "MANUAL",
349 Self::Auto => "AUTO",
350 }
351 }
352}
353
354impl fmt::Display for ManualOrAutoEntry {
355 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
356 f.write_str(self.as_str_name())
357 }
358}
359
360impl From<ManualOrAutoEntry> for request_new_order::OrderPlacement {
361 fn from(entry: ManualOrAutoEntry) -> Self {
362 match entry {
363 ManualOrAutoEntry::Manual => Self::Manual,
364 ManualOrAutoEntry::Auto => Self::Auto,
365 }
366 }
367}
368
369impl From<ManualOrAutoEntry> for request_bracket_order::OrderPlacement {
370 fn from(entry: ManualOrAutoEntry) -> Self {
371 match entry {
372 ManualOrAutoEntry::Manual => Self::Manual,
373 ManualOrAutoEntry::Auto => Self::Auto,
374 }
375 }
376}
377
378impl From<ManualOrAutoEntry> for request_oco_order::OrderPlacement {
379 fn from(entry: ManualOrAutoEntry) -> Self {
380 match entry {
381 ManualOrAutoEntry::Manual => Self::Manual,
382 ManualOrAutoEntry::Auto => Self::Auto,
383 }
384 }
385}
386
387impl From<ManualOrAutoEntry> for request_modify_order::OrderPlacement {
388 fn from(entry: ManualOrAutoEntry) -> Self {
389 match entry {
390 ManualOrAutoEntry::Manual => Self::Manual,
391 ManualOrAutoEntry::Auto => Self::Auto,
392 }
393 }
394}
395
396impl From<ManualOrAutoEntry> for request_cancel_order::OrderPlacement {
397 fn from(entry: ManualOrAutoEntry) -> Self {
398 match entry {
399 ManualOrAutoEntry::Manual => Self::Manual,
400 ManualOrAutoEntry::Auto => Self::Auto,
401 }
402 }
403}
404
405impl From<ManualOrAutoEntry> for request_cancel_all_orders::OrderPlacement {
406 fn from(entry: ManualOrAutoEntry) -> Self {
407 match entry {
408 ManualOrAutoEntry::Manual => Self::Manual,
409 ManualOrAutoEntry::Auto => Self::Auto,
410 }
411 }
412}
413
414impl From<ManualOrAutoEntry> for request_exit_position::OrderPlacement {
415 fn from(entry: ManualOrAutoEntry) -> Self {
416 match entry {
417 ManualOrAutoEntry::Manual => Self::Manual,
418 ManualOrAutoEntry::Auto => Self::Auto,
419 }
420 }
421}
422
423#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
429#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
430#[non_exhaustive]
431pub enum BracketType {
432 StopOnly,
434 TargetOnly,
436 TargetAndStop,
438 StopOnlyStatic,
440 TargetOnlyStatic,
442 TargetAndStopStatic,
444}
445
446impl BracketType {
447 pub fn as_str_name(&self) -> &'static str {
449 match self {
450 Self::StopOnly => "STOP_ONLY",
451 Self::TargetOnly => "TARGET_ONLY",
452 Self::TargetAndStop => "TARGET_AND_STOP",
453 Self::StopOnlyStatic => "STOP_ONLY_STATIC",
454 Self::TargetOnlyStatic => "TARGET_ONLY_STATIC",
455 Self::TargetAndStopStatic => "TARGET_AND_STOP_STATIC",
456 }
457 }
458}
459
460impl fmt::Display for BracketType {
461 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
462 f.write_str(self.as_str_name())
463 }
464}
465
466impl From<BracketType> for request_bracket_order::BracketType {
467 fn from(bracket_type: BracketType) -> Self {
468 match bracket_type {
469 BracketType::StopOnly => Self::StopOnly,
470 BracketType::TargetOnly => Self::TargetOnly,
471 BracketType::TargetAndStop => Self::TargetAndStop,
472 BracketType::StopOnlyStatic => Self::StopOnlyStatic,
473 BracketType::TargetOnlyStatic => Self::TargetOnlyStatic,
474 BracketType::TargetAndStopStatic => Self::TargetAndStopStatic,
475 }
476 }
477}
478
479#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
494#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
495#[non_exhaustive]
496pub enum BracketOperationType {
497 Afocca,
499 Focca,
501 Cca,
503 Fca,
505 Oca,
508}
509
510impl BracketOperationType {
511 pub fn as_str_name(&self) -> &'static str {
513 match self {
514 Self::Afocca => "AFOCCA",
515 Self::Focca => "FOCCA",
516 Self::Cca => "CCA",
517 Self::Fca => "FCA",
518 Self::Oca => "OCA",
519 }
520 }
521}
522
523impl fmt::Display for BracketOperationType {
524 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
525 f.write_str(self.as_str_name())
526 }
527}
528
529#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
532#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
533#[non_exhaustive]
534pub enum FillHistoryRange {
535 #[non_exhaustive]
537 Ssboe {
538 start: i32,
540 finish: i32,
542 },
543 #[non_exhaustive]
545 TradeDate {
546 start: i32,
548 finish: i32,
550 },
551}
552
553impl FillHistoryRange {
554 pub fn ssboe(start: i32, finish: i32) -> Self {
556 Self::Ssboe { start, finish }
557 }
558
559 pub fn trade_date(start: i32, finish: i32) -> Self {
561 Self::TradeDate { start, finish }
562 }
563
564 pub fn index_format(&self) -> &'static str {
566 match self {
567 Self::Ssboe { .. } => "ssboe",
568 Self::TradeDate { .. } => "trade_date",
569 }
570 }
571
572 pub fn start(&self) -> i32 {
574 match self {
575 Self::Ssboe { start, .. } | Self::TradeDate { start, .. } => *start,
576 }
577 }
578
579 pub fn finish(&self) -> i32 {
581 match self {
582 Self::Ssboe { finish, .. } | Self::TradeDate { finish, .. } => *finish,
583 }
584 }
585}
586
587#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
589#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
590#[non_exhaustive]
591pub enum EasyToBorrowRequest {
592 Subscribe,
594 Unsubscribe,
596}
597
598impl EasyToBorrowRequest {
599 pub fn as_str_name(&self) -> &'static str {
601 match self {
602 Self::Subscribe => "SUBSCRIBE",
603 Self::Unsubscribe => "UNSUBSCRIBE",
604 }
605 }
606}
607
608impl fmt::Display for EasyToBorrowRequest {
609 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
610 f.write_str(self.as_str_name())
611 }
612}
613
614impl From<EasyToBorrowRequest> for request_easy_to_borrow_list::Request {
615 fn from(request: EasyToBorrowRequest) -> Self {
616 match request {
617 EasyToBorrowRequest::Subscribe => Self::Subscribe,
618 EasyToBorrowRequest::Unsubscribe => Self::Unsubscribe,
619 }
620 }
621}
622
623#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
630#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
631#[non_exhaustive]
632pub enum RmsUpdateBits {
633 AutoLiqThresholdCurrentValue,
635}
636
637impl RmsUpdateBits {
638 pub fn as_str_name(&self) -> &'static str {
640 match self {
641 Self::AutoLiqThresholdCurrentValue => "AUTO_LIQ_THRESHOLD_CURRENT_VALUE",
642 }
643 }
644}
645
646impl fmt::Display for RmsUpdateBits {
647 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
648 f.write_str(self.as_str_name())
649 }
650}
651
652impl From<RmsUpdateBits> for request_account_rms_updates::UpdateBits {
653 fn from(bits: RmsUpdateBits) -> Self {
654 match bits {
655 RmsUpdateBits::AutoLiqThresholdCurrentValue => Self::AutoLiqThresholdCurrentValue,
656 }
657 }
658}
659
660#[derive(Debug, Clone, Default, PartialEq)]
681#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
682#[non_exhaustive]
683#[must_use = "a request does nothing until passed to the history handle"]
684pub struct VolumeProfileMinuteBarsRequest {
685 pub symbol: String,
687 pub exchange: String,
689 pub bar_type_period: i32,
691 pub start_time_sec: i32,
693 pub end_time_sec: i32,
695 pub user_max_count: Option<i32>,
698 pub resume_bars: Option<bool>,
700}
701
702impl VolumeProfileMinuteBarsRequest {
703 pub fn new() -> Self {
705 Self::default()
706 }
707
708 pub fn symbol(mut self, symbol: impl Into<String>) -> Self {
710 self.symbol = symbol.into();
711 self
712 }
713
714 pub fn exchange(mut self, exchange: impl Into<String>) -> Self {
716 self.exchange = exchange.into();
717 self
718 }
719
720 pub fn bar_type_period(mut self, bar_type_period: i32) -> Self {
722 self.bar_type_period = bar_type_period;
723 self
724 }
725
726 pub fn start_time_sec(mut self, start_time_sec: i32) -> Self {
728 self.start_time_sec = start_time_sec;
729 self
730 }
731
732 pub fn end_time_sec(mut self, end_time_sec: i32) -> Self {
734 self.end_time_sec = end_time_sec;
735 self
736 }
737
738 pub fn user_max_count(mut self, user_max_count: i32) -> Self {
740 self.user_max_count = Some(user_max_count);
741 self
742 }
743
744 pub fn resume_bars(mut self, resume_bars: bool) -> Self {
746 self.resume_bars = Some(resume_bars);
747 self
748 }
749
750 pub fn validate(&self) -> Result<(), RithmicError> {
753 validate_replay_window(
754 "volume-profile",
755 &self.symbol,
756 &self.exchange,
757 self.start_time_sec,
758 self.end_time_sec,
759 )?;
760
761 if self.bar_type_period < 1 {
762 return Err(RithmicError::InvalidArgument(
763 "bar_type_period must be at least 1".to_string(),
764 ));
765 }
766 Ok(())
767 }
768
769 pub fn build(self) -> Result<Self, RithmicError> {
772 self.validate()?;
773 Ok(self)
774 }
775}
776
777fn validate_replay_window(
780 kind: &str,
781 symbol: &str,
782 exchange: &str,
783 start_time_sec: i32,
784 end_time_sec: i32,
785) -> Result<(), RithmicError> {
786 if symbol.is_empty() {
787 return Err(RithmicError::InvalidArgument(format!(
788 "a {kind} request requires a symbol"
789 )));
790 }
791
792 if exchange.is_empty() {
793 return Err(RithmicError::InvalidArgument(format!(
794 "a {kind} request requires an exchange"
795 )));
796 }
797
798 if start_time_sec < 1 || end_time_sec < 1 {
799 return Err(RithmicError::InvalidArgument(
800 "start_time_sec and end_time_sec are both required, as positive Unix timestamps"
801 .to_string(),
802 ));
803 }
804
805 if end_time_sec < start_time_sec {
806 return Err(RithmicError::InvalidArgument(
807 "end_time_sec must not precede start_time_sec".to_string(),
808 ));
809 }
810 Ok(())
811}
812
813#[derive(Debug, Clone, Default, PartialEq)]
837#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
838#[non_exhaustive]
839#[must_use = "a request does nothing until passed to the history handle"]
840pub struct TickBarReplayRequest {
841 pub symbol: String,
843 pub exchange: String,
845 pub bar_type_specifier: String,
848 pub start_time_sec: i32,
850 pub end_time_sec: i32,
852 pub user_max_count: Option<i32>,
855 pub resume_bars: Option<bool>,
858}
859
860impl TickBarReplayRequest {
861 pub fn new() -> Self {
863 Self::default()
864 }
865
866 pub fn symbol(mut self, symbol: impl Into<String>) -> Self {
868 self.symbol = symbol.into();
869 self
870 }
871
872 pub fn exchange(mut self, exchange: impl Into<String>) -> Self {
874 self.exchange = exchange.into();
875 self
876 }
877
878 pub fn bar_length(mut self, bar_length: u32) -> Self {
880 self.bar_type_specifier = bar_length.to_string();
881 self
882 }
883
884 pub fn bar_type_specifier(mut self, bar_type_specifier: impl Into<String>) -> Self {
887 self.bar_type_specifier = bar_type_specifier.into();
888 self
889 }
890
891 pub fn start_time_sec(mut self, start_time_sec: i32) -> Self {
893 self.start_time_sec = start_time_sec;
894 self
895 }
896
897 pub fn end_time_sec(mut self, end_time_sec: i32) -> Self {
899 self.end_time_sec = end_time_sec;
900 self
901 }
902
903 pub fn user_max_count(mut self, user_max_count: i32) -> Self {
905 self.user_max_count = Some(user_max_count);
906 self
907 }
908
909 pub fn resume_bars(mut self, resume_bars: bool) -> Self {
911 self.resume_bars = Some(resume_bars);
912 self
913 }
914
915 pub fn validate(&self) -> Result<(), RithmicError> {
918 validate_replay_window(
919 "tick bar replay",
920 &self.symbol,
921 &self.exchange,
922 self.start_time_sec,
923 self.end_time_sec,
924 )?;
925
926 match self.bar_type_specifier.parse::<u32>() {
927 Ok(length) if length >= 1 => Ok(()),
928 _ => Err(RithmicError::InvalidArgument(
929 "bar_length must be at least 1".to_string(),
930 )),
931 }
932 }
933
934 pub fn build(self) -> Result<Self, RithmicError> {
937 self.validate()?;
938 Ok(self)
939 }
940}
941
942#[derive(Debug, Clone, Default, PartialEq)]
969#[non_exhaustive]
970#[must_use = "a request does nothing until passed to the history handle"]
971pub struct TimeBarReplayRequest {
972 pub symbol: String,
974 pub exchange: String,
976 pub bar_type: Option<TimeBarType>,
978 pub bar_type_period: i32,
980 pub start_time_sec: i32,
982 pub end_time_sec: i32,
984 pub user_max_count: Option<i32>,
987 pub resume_bars: Option<bool>,
990}
991
992impl TimeBarReplayRequest {
993 pub fn new() -> Self {
995 Self::default()
996 }
997
998 pub fn symbol(mut self, symbol: impl Into<String>) -> Self {
1000 self.symbol = symbol.into();
1001 self
1002 }
1003
1004 pub fn exchange(mut self, exchange: impl Into<String>) -> Self {
1006 self.exchange = exchange.into();
1007 self
1008 }
1009
1010 pub fn bar_type(mut self, bar_type: TimeBarType) -> Self {
1012 self.bar_type = Some(bar_type);
1013 self
1014 }
1015
1016 pub fn bar_type_period(mut self, bar_type_period: i32) -> Self {
1018 self.bar_type_period = bar_type_period;
1019 self
1020 }
1021
1022 pub fn start_time_sec(mut self, start_time_sec: i32) -> Self {
1024 self.start_time_sec = start_time_sec;
1025 self
1026 }
1027
1028 pub fn end_time_sec(mut self, end_time_sec: i32) -> Self {
1030 self.end_time_sec = end_time_sec;
1031 self
1032 }
1033
1034 pub fn user_max_count(mut self, user_max_count: i32) -> Self {
1036 self.user_max_count = Some(user_max_count);
1037 self
1038 }
1039
1040 pub fn resume_bars(mut self, resume_bars: bool) -> Self {
1042 self.resume_bars = Some(resume_bars);
1043 self
1044 }
1045
1046 pub fn validate(&self) -> Result<(), RithmicError> {
1049 validate_replay_window(
1050 "time bar replay",
1051 &self.symbol,
1052 &self.exchange,
1053 self.start_time_sec,
1054 self.end_time_sec,
1055 )?;
1056
1057 if self.bar_type.is_none() {
1058 return Err(RithmicError::InvalidArgument(
1059 "a time bar replay request requires a bar_type".to_string(),
1060 ));
1061 }
1062
1063 if self.bar_type_period < 1 {
1064 return Err(RithmicError::InvalidArgument(
1065 "bar_type_period must be at least 1".to_string(),
1066 ));
1067 }
1068 Ok(())
1069 }
1070
1071 pub fn build(self) -> Result<Self, RithmicError> {
1074 self.validate()?;
1075 Ok(self)
1076 }
1077}
1078
1079#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
1082#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1083#[non_exhaustive]
1084pub enum OrderCondition {
1085 EqualTo,
1087 NotEqualTo,
1089 GreaterThan,
1091 #[default]
1093 GreaterThanEqualTo,
1094 LesserThan,
1096 LesserThanEqualTo,
1098}
1099
1100impl OrderCondition {
1101 pub fn as_str_name(&self) -> &'static str {
1103 match self {
1104 Self::EqualTo => "EQUAL_TO",
1105 Self::NotEqualTo => "NOT_EQUAL_TO",
1106 Self::GreaterThan => "GREATER_THAN",
1107 Self::GreaterThanEqualTo => "GREATER_THAN_EQUAL_TO",
1108 Self::LesserThan => "LESSER_THAN",
1109 Self::LesserThanEqualTo => "LESSER_THAN_EQUAL_TO",
1110 }
1111 }
1112}
1113
1114impl fmt::Display for OrderCondition {
1115 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1116 f.write_str(self.as_str_name())
1117 }
1118}
1119
1120impl From<OrderCondition> for request_new_order::Condition {
1121 fn from(condition: OrderCondition) -> Self {
1122 match condition {
1123 OrderCondition::EqualTo => Self::EqualTo,
1124 OrderCondition::NotEqualTo => Self::NotEqualTo,
1125 OrderCondition::GreaterThan => Self::GreaterThan,
1126 OrderCondition::GreaterThanEqualTo => Self::GreaterThanEqualTo,
1127 OrderCondition::LesserThan => Self::LesserThan,
1128 OrderCondition::LesserThanEqualTo => Self::LesserThanEqualTo,
1129 }
1130 }
1131}
1132
1133impl From<OrderCondition> for request_bracket_order::Condition {
1134 fn from(condition: OrderCondition) -> Self {
1135 match condition {
1136 OrderCondition::EqualTo => Self::EqualTo,
1137 OrderCondition::NotEqualTo => Self::NotEqualTo,
1138 OrderCondition::GreaterThan => Self::GreaterThan,
1139 OrderCondition::GreaterThanEqualTo => Self::GreaterThanEqualTo,
1140 OrderCondition::LesserThan => Self::LesserThan,
1141 OrderCondition::LesserThanEqualTo => Self::LesserThanEqualTo,
1142 }
1143 }
1144}
1145
1146impl From<OrderCondition> for request_modify_order::Condition {
1147 fn from(condition: OrderCondition) -> Self {
1148 match condition {
1149 OrderCondition::EqualTo => Self::EqualTo,
1150 OrderCondition::NotEqualTo => Self::NotEqualTo,
1151 OrderCondition::GreaterThan => Self::GreaterThan,
1152 OrderCondition::GreaterThanEqualTo => Self::GreaterThanEqualTo,
1153 OrderCondition::LesserThan => Self::LesserThan,
1154 OrderCondition::LesserThanEqualTo => Self::LesserThanEqualTo,
1155 }
1156 }
1157}
1158
1159#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
1161#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
1162#[non_exhaustive]
1163pub enum OrderPriceField {
1164 BidPrice,
1166 OfferPrice,
1168 #[default]
1170 TradePrice,
1171 LeanPrice,
1173}
1174
1175impl OrderPriceField {
1176 pub fn as_str_name(&self) -> &'static str {
1178 match self {
1179 Self::BidPrice => "BID_PRICE",
1180 Self::OfferPrice => "OFFER_PRICE",
1181 Self::TradePrice => "TRADE_PRICE",
1182 Self::LeanPrice => "LEAN_PRICE",
1183 }
1184 }
1185}
1186
1187impl fmt::Display for OrderPriceField {
1188 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1189 f.write_str(self.as_str_name())
1190 }
1191}
1192
1193impl From<OrderPriceField> for request_new_order::PriceField {
1194 fn from(price_field: OrderPriceField) -> Self {
1195 match price_field {
1196 OrderPriceField::BidPrice => Self::BidPrice,
1197 OrderPriceField::OfferPrice => Self::OfferPrice,
1198 OrderPriceField::TradePrice => Self::TradePrice,
1199 OrderPriceField::LeanPrice => Self::LeanPrice,
1200 }
1201 }
1202}
1203
1204impl From<OrderPriceField> for request_bracket_order::PriceField {
1205 fn from(price_field: OrderPriceField) -> Self {
1206 match price_field {
1207 OrderPriceField::BidPrice => Self::BidPrice,
1208 OrderPriceField::OfferPrice => Self::OfferPrice,
1209 OrderPriceField::TradePrice => Self::TradePrice,
1210 OrderPriceField::LeanPrice => Self::LeanPrice,
1211 }
1212 }
1213}
1214
1215impl From<OrderPriceField> for request_modify_order::PriceField {
1216 fn from(price_field: OrderPriceField) -> Self {
1217 match price_field {
1218 OrderPriceField::BidPrice => Self::BidPrice,
1219 OrderPriceField::OfferPrice => Self::OfferPrice,
1220 OrderPriceField::TradePrice => Self::TradePrice,
1221 OrderPriceField::LeanPrice => Self::LeanPrice,
1222 }
1223 }
1224}
1225
1226#[cfg(test)]
1227mod tests {
1228 use super::*;
1229
1230 #[test]
1233 fn the_oco_price_type_rejects_the_if_touched_pair() {
1234 for order_type in [
1235 OrderType::Market,
1236 OrderType::Limit,
1237 OrderType::StopMarket,
1238 OrderType::StopLimit,
1239 ] {
1240 let converted = request_oco_order::PriceType::try_from(order_type).unwrap();
1241 assert_eq!(converted.as_str_name(), order_type.as_str_name());
1242 }
1243
1244 for order_type in [OrderType::MarketIfTouched, OrderType::LimitIfTouched] {
1245 let err = request_oco_order::PriceType::try_from(order_type)
1246 .unwrap_err()
1247 .to_string();
1248 assert!(err.contains("is not available on an OCO leg"), "{err}");
1249 assert!(err.contains(order_type.as_str_name()), "{err}");
1250 }
1251 }
1252
1253 #[test]
1254 fn volume_profile_request_rejects_a_missing_or_reversed_window() {
1255 let request = VolumeProfileMinuteBarsRequest::new()
1256 .symbol("ESH6")
1257 .exchange("CME")
1258 .bar_type_period(5);
1259
1260 let err = request
1261 .clone()
1262 .start_time_sec(-10)
1263 .end_time_sec(1_750_003_600)
1264 .build()
1265 .unwrap_err()
1266 .to_string();
1267 assert!(err.contains("both required"), "{err}");
1268
1269 let err = request
1270 .clone()
1271 .start_time_sec(1_750_003_600)
1272 .end_time_sec(1_750_000_000)
1273 .build()
1274 .unwrap_err()
1275 .to_string();
1276 assert!(err.contains("must not precede"), "{err}");
1277
1278 assert!(
1279 request
1280 .start_time_sec(1_750_000_000)
1281 .end_time_sec(1_750_000_000)
1282 .build()
1283 .is_ok()
1284 );
1285 }
1286
1287 #[test]
1288 fn order_type_round_trips_through_its_string_forms() {
1289 for order_type in [
1290 OrderType::Market,
1291 OrderType::Limit,
1292 OrderType::StopMarket,
1293 OrderType::StopLimit,
1294 OrderType::MarketIfTouched,
1295 OrderType::LimitIfTouched,
1296 ] {
1297 assert_eq!(order_type.to_string(), order_type.as_str_name());
1298 assert_eq!(
1299 order_type.to_string().parse::<OrderType>().unwrap(),
1300 order_type
1301 );
1302 }
1303
1304 assert_eq!(
1305 "mit".parse::<OrderType>().unwrap(),
1306 OrderType::MarketIfTouched
1307 );
1308 assert_eq!(
1309 "lit".parse::<OrderType>().unwrap(),
1310 OrderType::LimitIfTouched
1311 );
1312 assert_eq!(
1313 "market-if-touched".parse::<OrderType>().unwrap(),
1314 OrderType::MarketIfTouched
1315 );
1316 assert_eq!(
1317 "limit-if-touched".parse::<OrderType>().unwrap(),
1318 OrderType::LimitIfTouched
1319 );
1320 }
1321
1322 fn tick_replay() -> TickBarReplayRequest {
1323 TickBarReplayRequest::new()
1324 .symbol("ESU6")
1325 .exchange("CME")
1326 .bar_length(1)
1327 .start_time_sec(1_750_000_000)
1328 .end_time_sec(1_750_003_600)
1329 }
1330
1331 fn time_replay() -> TimeBarReplayRequest {
1332 TimeBarReplayRequest::new()
1333 .symbol("ESU6")
1334 .exchange("CME")
1335 .bar_type(TimeBarType::MinuteBar)
1336 .bar_type_period(5)
1337 .start_time_sec(1_750_000_000)
1338 .end_time_sec(1_750_003_600)
1339 }
1340
1341 #[test]
1342 fn a_tick_replay_request_needs_an_instrument_and_an_ordered_window() {
1343 assert!(tick_replay().validate().is_ok());
1344
1345 let err = TickBarReplayRequest {
1346 symbol: String::new(),
1347 ..tick_replay()
1348 }
1349 .validate()
1350 .unwrap_err()
1351 .to_string();
1352 assert!(
1353 err.contains("tick bar replay request requires a symbol"),
1354 "{err}"
1355 );
1356
1357 let err = TickBarReplayRequest {
1358 exchange: String::new(),
1359 ..tick_replay()
1360 }
1361 .validate()
1362 .unwrap_err()
1363 .to_string();
1364 assert!(
1365 err.contains("tick bar replay request requires an exchange"),
1366 "{err}"
1367 );
1368
1369 let err = tick_replay()
1370 .end_time_sec(1_749_999_999)
1371 .build()
1372 .unwrap_err()
1373 .to_string();
1374 assert!(err.contains("must not precede"), "{err}");
1375 }
1376
1377 #[test]
1380 fn a_tick_replay_request_needs_a_bar_length_of_at_least_one() {
1381 for specifier in ["0", "", "lots"] {
1382 let err = tick_replay()
1383 .bar_type_specifier(specifier)
1384 .build()
1385 .unwrap_err()
1386 .to_string();
1387 assert!(
1388 err.contains("bar_length must be at least 1"),
1389 "{specifier}: {err}"
1390 );
1391 }
1392
1393 assert_eq!(tick_replay().bar_length(5).bar_type_specifier, "5");
1394 }
1395
1396 #[test]
1397 fn a_time_replay_request_needs_a_bar_type_and_period() {
1398 assert!(time_replay().validate().is_ok());
1399
1400 let err = TimeBarReplayRequest {
1401 bar_type: None,
1402 ..time_replay()
1403 }
1404 .validate()
1405 .unwrap_err()
1406 .to_string();
1407 assert!(err.contains("requires a bar_type"), "{err}");
1408
1409 let err = time_replay()
1410 .bar_type_period(0)
1411 .build()
1412 .unwrap_err()
1413 .to_string();
1414 assert!(err.contains("bar_type_period must be at least 1"), "{err}");
1415 }
1416}