#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use std::fmt;
use std::str::FromStr;
use crate::{
error::RithmicError,
rti::{
request_account_rms_updates, request_bracket_order, request_cancel_all_orders,
request_cancel_order, request_easy_to_borrow_list, request_exit_position,
request_modify_order, request_new_order, request_oco_order,
},
};
pub use crate::rti::request_time_bar_replay::BarType as TimeBarType;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[non_exhaustive]
pub enum OrderSide {
#[default]
Buy,
Sell,
}
impl OrderSide {
pub fn as_str_name(&self) -> &'static str {
match self {
Self::Buy => "BUY",
Self::Sell => "SELL",
}
}
}
impl fmt::Display for OrderSide {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str_name())
}
}
#[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))]
#[non_exhaustive]
pub enum OrderType {
Market,
#[default]
Limit,
StopMarket,
StopLimit,
MarketIfTouched,
LimitIfTouched,
}
impl OrderType {
pub fn as_str_name(&self) -> &'static str {
match self {
Self::Market => "MARKET",
Self::Limit => "LIMIT",
Self::StopMarket => "STOP_MARKET",
Self::StopLimit => "STOP_LIMIT",
Self::MarketIfTouched => "MARKET_IF_TOUCHED",
Self::LimitIfTouched => "LIMIT_IF_TOUCHED",
}
}
}
impl fmt::Display for OrderType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str_name())
}
}
#[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),
"MARKETIFTOUCHED" | "MIT" | "MARKET_IF_TOUCHED" | "MARKET-IF-TOUCHED" => {
Ok(Self::MarketIfTouched)
}
"LIMITIFTOUCHED" | "LIT" | "LIMIT_IF_TOUCHED" | "LIMIT-IF-TOUCHED" => {
Ok(Self::LimitIfTouched)
}
_ => 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,
OrderType::MarketIfTouched => Self::MarketIfTouched,
OrderType::LimitIfTouched => Self::LimitIfTouched,
}
}
}
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,
OrderType::MarketIfTouched => Self::MarketIfTouched,
OrderType::LimitIfTouched => Self::LimitIfTouched,
}
}
}
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,
OrderType::MarketIfTouched => Self::MarketIfTouched,
OrderType::LimitIfTouched => Self::LimitIfTouched,
}
}
}
impl TryFrom<OrderType> for request_oco_order::PriceType {
type Error = RithmicError;
fn try_from(order_type: OrderType) -> Result<Self, Self::Error> {
match order_type {
OrderType::Market => Ok(Self::Market),
OrderType::Limit => Ok(Self::Limit),
OrderType::StopMarket => Ok(Self::StopMarket),
OrderType::StopLimit => Ok(Self::StopLimit),
OrderType::MarketIfTouched | OrderType::LimitIfTouched => {
Err(RithmicError::InvalidArgument(format!(
"price_type {} is not available on an OCO leg",
order_type.as_str_name()
)))
}
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[non_exhaustive]
pub enum TimeInForce {
#[default]
Day,
Gtc,
Ioc,
Fok,
}
impl TimeInForce {
pub fn as_str_name(&self) -> &'static str {
match self {
Self::Day => "DAY",
Self::Gtc => "GTC",
Self::Ioc => "IOC",
Self::Fok => "FOK",
}
}
}
impl fmt::Display for TimeInForce {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str_name())
}
}
#[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,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[non_exhaustive]
pub enum ManualOrAutoEntry {
Manual,
#[default]
Auto,
}
impl ManualOrAutoEntry {
pub fn as_str_name(&self) -> &'static str {
match self {
Self::Manual => "MANUAL",
Self::Auto => "AUTO",
}
}
}
impl fmt::Display for ManualOrAutoEntry {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str_name())
}
}
impl From<ManualOrAutoEntry> for request_new_order::OrderPlacement {
fn from(entry: ManualOrAutoEntry) -> Self {
match entry {
ManualOrAutoEntry::Manual => Self::Manual,
ManualOrAutoEntry::Auto => Self::Auto,
}
}
}
impl From<ManualOrAutoEntry> for request_bracket_order::OrderPlacement {
fn from(entry: ManualOrAutoEntry) -> Self {
match entry {
ManualOrAutoEntry::Manual => Self::Manual,
ManualOrAutoEntry::Auto => Self::Auto,
}
}
}
impl From<ManualOrAutoEntry> for request_oco_order::OrderPlacement {
fn from(entry: ManualOrAutoEntry) -> Self {
match entry {
ManualOrAutoEntry::Manual => Self::Manual,
ManualOrAutoEntry::Auto => Self::Auto,
}
}
}
impl From<ManualOrAutoEntry> for request_modify_order::OrderPlacement {
fn from(entry: ManualOrAutoEntry) -> Self {
match entry {
ManualOrAutoEntry::Manual => Self::Manual,
ManualOrAutoEntry::Auto => Self::Auto,
}
}
}
impl From<ManualOrAutoEntry> for request_cancel_order::OrderPlacement {
fn from(entry: ManualOrAutoEntry) -> Self {
match entry {
ManualOrAutoEntry::Manual => Self::Manual,
ManualOrAutoEntry::Auto => Self::Auto,
}
}
}
impl From<ManualOrAutoEntry> for request_cancel_all_orders::OrderPlacement {
fn from(entry: ManualOrAutoEntry) -> Self {
match entry {
ManualOrAutoEntry::Manual => Self::Manual,
ManualOrAutoEntry::Auto => Self::Auto,
}
}
}
impl From<ManualOrAutoEntry> for request_exit_position::OrderPlacement {
fn from(entry: ManualOrAutoEntry) -> Self {
match entry {
ManualOrAutoEntry::Manual => Self::Manual,
ManualOrAutoEntry::Auto => Self::Auto,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[non_exhaustive]
pub enum BracketType {
StopOnly,
TargetOnly,
TargetAndStop,
StopOnlyStatic,
TargetOnlyStatic,
TargetAndStopStatic,
}
impl BracketType {
pub fn as_str_name(&self) -> &'static str {
match self {
Self::StopOnly => "STOP_ONLY",
Self::TargetOnly => "TARGET_ONLY",
Self::TargetAndStop => "TARGET_AND_STOP",
Self::StopOnlyStatic => "STOP_ONLY_STATIC",
Self::TargetOnlyStatic => "TARGET_ONLY_STATIC",
Self::TargetAndStopStatic => "TARGET_AND_STOP_STATIC",
}
}
}
impl fmt::Display for BracketType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str_name())
}
}
impl From<BracketType> for request_bracket_order::BracketType {
fn from(bracket_type: BracketType) -> Self {
match bracket_type {
BracketType::StopOnly => Self::StopOnly,
BracketType::TargetOnly => Self::TargetOnly,
BracketType::TargetAndStop => Self::TargetAndStop,
BracketType::StopOnlyStatic => Self::StopOnlyStatic,
BracketType::TargetOnlyStatic => Self::TargetOnlyStatic,
BracketType::TargetAndStopStatic => Self::TargetAndStopStatic,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[non_exhaustive]
pub enum BracketOperationType {
Afocca,
Focca,
Cca,
Fca,
Oca,
}
impl BracketOperationType {
pub fn as_str_name(&self) -> &'static str {
match self {
Self::Afocca => "AFOCCA",
Self::Focca => "FOCCA",
Self::Cca => "CCA",
Self::Fca => "FCA",
Self::Oca => "OCA",
}
}
}
impl fmt::Display for BracketOperationType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str_name())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[non_exhaustive]
pub enum FillHistoryRange {
#[non_exhaustive]
Ssboe {
start: i32,
finish: i32,
},
#[non_exhaustive]
TradeDate {
start: i32,
finish: i32,
},
}
impl FillHistoryRange {
pub fn ssboe(start: i32, finish: i32) -> Self {
Self::Ssboe { start, finish }
}
pub fn trade_date(start: i32, finish: i32) -> Self {
Self::TradeDate { start, finish }
}
pub fn index_format(&self) -> &'static str {
match self {
Self::Ssboe { .. } => "ssboe",
Self::TradeDate { .. } => "trade_date",
}
}
pub fn start(&self) -> i32 {
match self {
Self::Ssboe { start, .. } | Self::TradeDate { start, .. } => *start,
}
}
pub fn finish(&self) -> i32 {
match self {
Self::Ssboe { finish, .. } | Self::TradeDate { finish, .. } => *finish,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[non_exhaustive]
pub enum EasyToBorrowRequest {
Subscribe,
Unsubscribe,
}
impl EasyToBorrowRequest {
pub fn as_str_name(&self) -> &'static str {
match self {
Self::Subscribe => "SUBSCRIBE",
Self::Unsubscribe => "UNSUBSCRIBE",
}
}
}
impl fmt::Display for EasyToBorrowRequest {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str_name())
}
}
impl From<EasyToBorrowRequest> for request_easy_to_borrow_list::Request {
fn from(request: EasyToBorrowRequest) -> Self {
match request {
EasyToBorrowRequest::Subscribe => Self::Subscribe,
EasyToBorrowRequest::Unsubscribe => Self::Unsubscribe,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[non_exhaustive]
pub enum RmsUpdateBits {
AutoLiqThresholdCurrentValue,
}
impl RmsUpdateBits {
pub fn as_str_name(&self) -> &'static str {
match self {
Self::AutoLiqThresholdCurrentValue => "AUTO_LIQ_THRESHOLD_CURRENT_VALUE",
}
}
}
impl fmt::Display for RmsUpdateBits {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str_name())
}
}
impl From<RmsUpdateBits> for request_account_rms_updates::UpdateBits {
fn from(bits: RmsUpdateBits) -> Self {
match bits {
RmsUpdateBits::AutoLiqThresholdCurrentValue => Self::AutoLiqThresholdCurrentValue,
}
}
}
#[derive(Debug, Clone, Default, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[non_exhaustive]
#[must_use = "a request does nothing until passed to the history handle"]
pub struct VolumeProfileMinuteBarsRequest {
pub symbol: String,
pub exchange: String,
pub bar_type_period: i32,
pub start_time_sec: i32,
pub end_time_sec: i32,
pub user_max_count: Option<i32>,
pub resume_bars: Option<bool>,
}
impl VolumeProfileMinuteBarsRequest {
pub fn new() -> Self {
Self::default()
}
pub fn symbol(mut self, symbol: impl Into<String>) -> Self {
self.symbol = symbol.into();
self
}
pub fn exchange(mut self, exchange: impl Into<String>) -> Self {
self.exchange = exchange.into();
self
}
pub fn bar_type_period(mut self, bar_type_period: i32) -> Self {
self.bar_type_period = bar_type_period;
self
}
pub fn start_time_sec(mut self, start_time_sec: i32) -> Self {
self.start_time_sec = start_time_sec;
self
}
pub fn end_time_sec(mut self, end_time_sec: i32) -> Self {
self.end_time_sec = end_time_sec;
self
}
pub fn user_max_count(mut self, user_max_count: i32) -> Self {
self.user_max_count = Some(user_max_count);
self
}
pub fn resume_bars(mut self, resume_bars: bool) -> Self {
self.resume_bars = Some(resume_bars);
self
}
pub fn validate(&self) -> Result<(), RithmicError> {
validate_replay_window(
"volume-profile",
&self.symbol,
&self.exchange,
self.start_time_sec,
self.end_time_sec,
)?;
if self.bar_type_period < 1 {
return Err(RithmicError::InvalidArgument(
"bar_type_period must be at least 1".to_string(),
));
}
Ok(())
}
pub fn build(self) -> Result<Self, RithmicError> {
self.validate()?;
Ok(self)
}
}
fn validate_replay_window(
kind: &str,
symbol: &str,
exchange: &str,
start_time_sec: i32,
end_time_sec: i32,
) -> Result<(), RithmicError> {
if symbol.is_empty() {
return Err(RithmicError::InvalidArgument(format!(
"a {kind} request requires a symbol"
)));
}
if exchange.is_empty() {
return Err(RithmicError::InvalidArgument(format!(
"a {kind} request requires an exchange"
)));
}
if start_time_sec < 1 || end_time_sec < 1 {
return Err(RithmicError::InvalidArgument(
"start_time_sec and end_time_sec are both required, as positive Unix timestamps"
.to_string(),
));
}
if end_time_sec < start_time_sec {
return Err(RithmicError::InvalidArgument(
"end_time_sec must not precede start_time_sec".to_string(),
));
}
Ok(())
}
#[derive(Debug, Clone, Default, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[non_exhaustive]
#[must_use = "a request does nothing until passed to the history handle"]
pub struct TickBarReplayRequest {
pub symbol: String,
pub exchange: String,
pub bar_type_specifier: String,
pub start_time_sec: i32,
pub end_time_sec: i32,
pub user_max_count: Option<i32>,
pub resume_bars: Option<bool>,
}
impl TickBarReplayRequest {
pub fn new() -> Self {
Self::default()
}
pub fn symbol(mut self, symbol: impl Into<String>) -> Self {
self.symbol = symbol.into();
self
}
pub fn exchange(mut self, exchange: impl Into<String>) -> Self {
self.exchange = exchange.into();
self
}
pub fn bar_length(mut self, bar_length: u32) -> Self {
self.bar_type_specifier = bar_length.to_string();
self
}
pub fn bar_type_specifier(mut self, bar_type_specifier: impl Into<String>) -> Self {
self.bar_type_specifier = bar_type_specifier.into();
self
}
pub fn start_time_sec(mut self, start_time_sec: i32) -> Self {
self.start_time_sec = start_time_sec;
self
}
pub fn end_time_sec(mut self, end_time_sec: i32) -> Self {
self.end_time_sec = end_time_sec;
self
}
pub fn user_max_count(mut self, user_max_count: i32) -> Self {
self.user_max_count = Some(user_max_count);
self
}
pub fn resume_bars(mut self, resume_bars: bool) -> Self {
self.resume_bars = Some(resume_bars);
self
}
pub fn validate(&self) -> Result<(), RithmicError> {
validate_replay_window(
"tick bar replay",
&self.symbol,
&self.exchange,
self.start_time_sec,
self.end_time_sec,
)?;
match self.bar_type_specifier.parse::<u32>() {
Ok(length) if length >= 1 => Ok(()),
_ => Err(RithmicError::InvalidArgument(
"bar_length must be at least 1".to_string(),
)),
}
}
pub fn build(self) -> Result<Self, RithmicError> {
self.validate()?;
Ok(self)
}
}
#[derive(Debug, Clone, Default, PartialEq)]
#[non_exhaustive]
#[must_use = "a request does nothing until passed to the history handle"]
pub struct TimeBarReplayRequest {
pub symbol: String,
pub exchange: String,
pub bar_type: Option<TimeBarType>,
pub bar_type_period: i32,
pub start_time_sec: i32,
pub end_time_sec: i32,
pub user_max_count: Option<i32>,
pub resume_bars: Option<bool>,
}
impl TimeBarReplayRequest {
pub fn new() -> Self {
Self::default()
}
pub fn symbol(mut self, symbol: impl Into<String>) -> Self {
self.symbol = symbol.into();
self
}
pub fn exchange(mut self, exchange: impl Into<String>) -> Self {
self.exchange = exchange.into();
self
}
pub fn bar_type(mut self, bar_type: TimeBarType) -> Self {
self.bar_type = Some(bar_type);
self
}
pub fn bar_type_period(mut self, bar_type_period: i32) -> Self {
self.bar_type_period = bar_type_period;
self
}
pub fn start_time_sec(mut self, start_time_sec: i32) -> Self {
self.start_time_sec = start_time_sec;
self
}
pub fn end_time_sec(mut self, end_time_sec: i32) -> Self {
self.end_time_sec = end_time_sec;
self
}
pub fn user_max_count(mut self, user_max_count: i32) -> Self {
self.user_max_count = Some(user_max_count);
self
}
pub fn resume_bars(mut self, resume_bars: bool) -> Self {
self.resume_bars = Some(resume_bars);
self
}
pub fn validate(&self) -> Result<(), RithmicError> {
validate_replay_window(
"time bar replay",
&self.symbol,
&self.exchange,
self.start_time_sec,
self.end_time_sec,
)?;
if self.bar_type.is_none() {
return Err(RithmicError::InvalidArgument(
"a time bar replay request requires a bar_type".to_string(),
));
}
if self.bar_type_period < 1 {
return Err(RithmicError::InvalidArgument(
"bar_type_period must be at least 1".to_string(),
));
}
Ok(())
}
pub fn build(self) -> Result<Self, RithmicError> {
self.validate()?;
Ok(self)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[non_exhaustive]
pub enum OrderCondition {
EqualTo,
NotEqualTo,
GreaterThan,
#[default]
GreaterThanEqualTo,
LesserThan,
LesserThanEqualTo,
}
impl OrderCondition {
pub fn as_str_name(&self) -> &'static str {
match self {
Self::EqualTo => "EQUAL_TO",
Self::NotEqualTo => "NOT_EQUAL_TO",
Self::GreaterThan => "GREATER_THAN",
Self::GreaterThanEqualTo => "GREATER_THAN_EQUAL_TO",
Self::LesserThan => "LESSER_THAN",
Self::LesserThanEqualTo => "LESSER_THAN_EQUAL_TO",
}
}
}
impl fmt::Display for OrderCondition {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str_name())
}
}
impl From<OrderCondition> for request_new_order::Condition {
fn from(condition: OrderCondition) -> Self {
match condition {
OrderCondition::EqualTo => Self::EqualTo,
OrderCondition::NotEqualTo => Self::NotEqualTo,
OrderCondition::GreaterThan => Self::GreaterThan,
OrderCondition::GreaterThanEqualTo => Self::GreaterThanEqualTo,
OrderCondition::LesserThan => Self::LesserThan,
OrderCondition::LesserThanEqualTo => Self::LesserThanEqualTo,
}
}
}
impl From<OrderCondition> for request_bracket_order::Condition {
fn from(condition: OrderCondition) -> Self {
match condition {
OrderCondition::EqualTo => Self::EqualTo,
OrderCondition::NotEqualTo => Self::NotEqualTo,
OrderCondition::GreaterThan => Self::GreaterThan,
OrderCondition::GreaterThanEqualTo => Self::GreaterThanEqualTo,
OrderCondition::LesserThan => Self::LesserThan,
OrderCondition::LesserThanEqualTo => Self::LesserThanEqualTo,
}
}
}
impl From<OrderCondition> for request_modify_order::Condition {
fn from(condition: OrderCondition) -> Self {
match condition {
OrderCondition::EqualTo => Self::EqualTo,
OrderCondition::NotEqualTo => Self::NotEqualTo,
OrderCondition::GreaterThan => Self::GreaterThan,
OrderCondition::GreaterThanEqualTo => Self::GreaterThanEqualTo,
OrderCondition::LesserThan => Self::LesserThan,
OrderCondition::LesserThanEqualTo => Self::LesserThanEqualTo,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[non_exhaustive]
pub enum OrderPriceField {
BidPrice,
OfferPrice,
#[default]
TradePrice,
LeanPrice,
}
impl OrderPriceField {
pub fn as_str_name(&self) -> &'static str {
match self {
Self::BidPrice => "BID_PRICE",
Self::OfferPrice => "OFFER_PRICE",
Self::TradePrice => "TRADE_PRICE",
Self::LeanPrice => "LEAN_PRICE",
}
}
}
impl fmt::Display for OrderPriceField {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str_name())
}
}
impl From<OrderPriceField> for request_new_order::PriceField {
fn from(price_field: OrderPriceField) -> Self {
match price_field {
OrderPriceField::BidPrice => Self::BidPrice,
OrderPriceField::OfferPrice => Self::OfferPrice,
OrderPriceField::TradePrice => Self::TradePrice,
OrderPriceField::LeanPrice => Self::LeanPrice,
}
}
}
impl From<OrderPriceField> for request_bracket_order::PriceField {
fn from(price_field: OrderPriceField) -> Self {
match price_field {
OrderPriceField::BidPrice => Self::BidPrice,
OrderPriceField::OfferPrice => Self::OfferPrice,
OrderPriceField::TradePrice => Self::TradePrice,
OrderPriceField::LeanPrice => Self::LeanPrice,
}
}
}
impl From<OrderPriceField> for request_modify_order::PriceField {
fn from(price_field: OrderPriceField) -> Self {
match price_field {
OrderPriceField::BidPrice => Self::BidPrice,
OrderPriceField::OfferPrice => Self::OfferPrice,
OrderPriceField::TradePrice => Self::TradePrice,
OrderPriceField::LeanPrice => Self::LeanPrice,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn the_oco_price_type_rejects_the_if_touched_pair() {
for order_type in [
OrderType::Market,
OrderType::Limit,
OrderType::StopMarket,
OrderType::StopLimit,
] {
let converted = request_oco_order::PriceType::try_from(order_type).unwrap();
assert_eq!(converted.as_str_name(), order_type.as_str_name());
}
for order_type in [OrderType::MarketIfTouched, OrderType::LimitIfTouched] {
let err = request_oco_order::PriceType::try_from(order_type)
.unwrap_err()
.to_string();
assert!(err.contains("is not available on an OCO leg"), "{err}");
assert!(err.contains(order_type.as_str_name()), "{err}");
}
}
#[test]
fn volume_profile_request_rejects_a_missing_or_reversed_window() {
let request = VolumeProfileMinuteBarsRequest::new()
.symbol("ESH6")
.exchange("CME")
.bar_type_period(5);
let err = request
.clone()
.start_time_sec(-10)
.end_time_sec(1_750_003_600)
.build()
.unwrap_err()
.to_string();
assert!(err.contains("both required"), "{err}");
let err = request
.clone()
.start_time_sec(1_750_003_600)
.end_time_sec(1_750_000_000)
.build()
.unwrap_err()
.to_string();
assert!(err.contains("must not precede"), "{err}");
assert!(
request
.start_time_sec(1_750_000_000)
.end_time_sec(1_750_000_000)
.build()
.is_ok()
);
}
#[test]
fn order_type_round_trips_through_its_string_forms() {
for order_type in [
OrderType::Market,
OrderType::Limit,
OrderType::StopMarket,
OrderType::StopLimit,
OrderType::MarketIfTouched,
OrderType::LimitIfTouched,
] {
assert_eq!(order_type.to_string(), order_type.as_str_name());
assert_eq!(
order_type.to_string().parse::<OrderType>().unwrap(),
order_type
);
}
assert_eq!(
"mit".parse::<OrderType>().unwrap(),
OrderType::MarketIfTouched
);
assert_eq!(
"lit".parse::<OrderType>().unwrap(),
OrderType::LimitIfTouched
);
assert_eq!(
"market-if-touched".parse::<OrderType>().unwrap(),
OrderType::MarketIfTouched
);
assert_eq!(
"limit-if-touched".parse::<OrderType>().unwrap(),
OrderType::LimitIfTouched
);
}
fn tick_replay() -> TickBarReplayRequest {
TickBarReplayRequest::new()
.symbol("ESU6")
.exchange("CME")
.bar_length(1)
.start_time_sec(1_750_000_000)
.end_time_sec(1_750_003_600)
}
fn time_replay() -> TimeBarReplayRequest {
TimeBarReplayRequest::new()
.symbol("ESU6")
.exchange("CME")
.bar_type(TimeBarType::MinuteBar)
.bar_type_period(5)
.start_time_sec(1_750_000_000)
.end_time_sec(1_750_003_600)
}
#[test]
fn a_tick_replay_request_needs_an_instrument_and_an_ordered_window() {
assert!(tick_replay().validate().is_ok());
let err = TickBarReplayRequest {
symbol: String::new(),
..tick_replay()
}
.validate()
.unwrap_err()
.to_string();
assert!(
err.contains("tick bar replay request requires a symbol"),
"{err}"
);
let err = TickBarReplayRequest {
exchange: String::new(),
..tick_replay()
}
.validate()
.unwrap_err()
.to_string();
assert!(
err.contains("tick bar replay request requires an exchange"),
"{err}"
);
let err = tick_replay()
.end_time_sec(1_749_999_999)
.build()
.unwrap_err()
.to_string();
assert!(err.contains("must not precede"), "{err}");
}
#[test]
fn a_tick_replay_request_needs_a_bar_length_of_at_least_one() {
for specifier in ["0", "", "lots"] {
let err = tick_replay()
.bar_type_specifier(specifier)
.build()
.unwrap_err()
.to_string();
assert!(
err.contains("bar_length must be at least 1"),
"{specifier}: {err}"
);
}
assert_eq!(tick_replay().bar_length(5).bar_type_specifier, "5");
}
#[test]
fn a_time_replay_request_needs_a_bar_type_and_period() {
assert!(time_replay().validate().is_ok());
let err = TimeBarReplayRequest {
bar_type: None,
..time_replay()
}
.validate()
.unwrap_err()
.to_string();
assert!(err.contains("requires a bar_type"), "{err}");
let err = time_replay()
.bar_type_period(0)
.build()
.unwrap_err()
.to_string();
assert!(err.contains("bar_type_period must be at least 1"), "{err}");
}
}