use crate::error::RithmicError;
use crate::rti::messages::RithmicMessage;
#[derive(Debug, Clone)]
#[non_exhaustive]
#[allow(missing_docs)]
pub struct RithmicResponse {
pub request_id: String,
pub message: RithmicMessage,
pub is_update: bool,
pub has_more: bool,
pub multi_response: bool,
pub error: Option<RithmicError>,
pub source: String,
}
impl RithmicResponse {
pub fn rp_code(&self) -> Option<&[String]> {
super::rp_code::response_rp_code_slice(&self.message)
}
pub fn rp_code_num(&self) -> Option<&str> {
self.rp_code().and_then(|c| c.first().map(String::as_str))
}
pub fn rp_code_text(&self) -> Option<&str> {
self.rp_code().and_then(|c| c.get(1).map(String::as_str))
}
pub fn is_market_data(&self) -> bool {
matches!(
self.message,
RithmicMessage::BestBidOffer(_)
| RithmicMessage::LastTrade(_)
| RithmicMessage::DepthByOrder(_)
| RithmicMessage::DepthByOrderEndEvent(_)
| RithmicMessage::OrderBook(_)
)
}
pub fn is_order_update(&self) -> bool {
matches!(
self.message,
RithmicMessage::RithmicOrderNotification(_)
| RithmicMessage::ExchangeOrderNotification(_)
| RithmicMessage::BracketUpdates(_)
)
}
pub fn is_pnl_update(&self) -> bool {
matches!(
self.message,
RithmicMessage::AccountPnLPositionUpdate(_)
| RithmicMessage::InstrumentPnLPositionUpdate(_)
)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::rti::{
AccountPnLPositionUpdate, BestBidOffer, BracketUpdates, DepthByOrder, DepthByOrderEndEvent,
ExchangeOrderNotification, ForcedLogout, InstrumentPnLPositionUpdate, LastTrade, OrderBook,
RithmicOrderNotification, messages::RithmicMessage,
};
fn make_response(message: RithmicMessage) -> RithmicResponse {
RithmicResponse {
request_id: String::new(),
message,
is_update: false,
has_more: false,
multi_response: false,
error: None,
source: "test".to_string(),
}
}
#[test]
fn is_market_data_true_for_market_data_types() {
let bbo = make_response(RithmicMessage::BestBidOffer(BestBidOffer::default()));
let trade = make_response(RithmicMessage::LastTrade(LastTrade::default()));
let depth = make_response(RithmicMessage::DepthByOrder(DepthByOrder::default()));
let depth_end = make_response(RithmicMessage::DepthByOrderEndEvent(
DepthByOrderEndEvent::default(),
));
let orderbook = make_response(RithmicMessage::OrderBook(OrderBook::default()));
assert!(bbo.is_market_data());
assert!(trade.is_market_data());
assert!(depth.is_market_data());
assert!(depth_end.is_market_data());
assert!(orderbook.is_market_data());
}
#[test]
fn is_market_data_false_for_order_notifications() {
let response = make_response(RithmicMessage::RithmicOrderNotification(
RithmicOrderNotification::default(),
));
assert!(!response.is_market_data());
}
#[test]
fn is_order_update_true_for_order_notification_types() {
let rithmic_notif = make_response(RithmicMessage::RithmicOrderNotification(
RithmicOrderNotification::default(),
));
let exchange_notif = make_response(RithmicMessage::ExchangeOrderNotification(
ExchangeOrderNotification::default(),
));
let bracket = make_response(RithmicMessage::BracketUpdates(BracketUpdates::default()));
assert!(rithmic_notif.is_order_update());
assert!(exchange_notif.is_order_update());
assert!(bracket.is_order_update());
}
#[test]
fn is_order_update_false_for_market_data() {
let response = make_response(RithmicMessage::BestBidOffer(BestBidOffer::default()));
assert!(!response.is_order_update());
}
#[test]
fn is_pnl_update_true_for_pnl_types() {
let account_pnl = make_response(RithmicMessage::AccountPnLPositionUpdate(
AccountPnLPositionUpdate::default(),
));
let instrument_pnl = make_response(RithmicMessage::InstrumentPnLPositionUpdate(
InstrumentPnLPositionUpdate::default(),
));
assert!(account_pnl.is_pnl_update());
assert!(instrument_pnl.is_pnl_update());
}
#[test]
fn is_pnl_update_false_for_order_updates() {
let response = make_response(RithmicMessage::RithmicOrderNotification(
RithmicOrderNotification::default(),
));
assert!(!response.is_pnl_update());
}
#[test]
fn categories_are_mutually_exclusive() {
let market_data = make_response(RithmicMessage::BestBidOffer(BestBidOffer::default()));
assert!(market_data.is_market_data());
assert!(!market_data.is_order_update());
assert!(!market_data.is_pnl_update());
let order = make_response(RithmicMessage::RithmicOrderNotification(
RithmicOrderNotification::default(),
));
assert!(order.is_order_update());
assert!(!order.is_market_data());
assert!(!order.is_pnl_update());
let pnl = make_response(RithmicMessage::AccountPnLPositionUpdate(
AccountPnLPositionUpdate::default(),
));
assert!(pnl.is_pnl_update());
assert!(!pnl.is_market_data());
assert!(!pnl.is_order_update());
let conn_err = make_response(RithmicMessage::ConnectionError);
assert!(!conn_err.is_market_data());
assert!(!conn_err.is_order_update());
assert!(!conn_err.is_pnl_update());
}
#[test]
fn error_field_accepts_typed_rithmic_error() {
let mut response = make_response(RithmicMessage::ConnectionError);
response.error = Some(RithmicError::ConnectionClosed);
match &response.error {
Some(err) => {
assert!(err.is_connection_issue());
assert_eq!(err, &RithmicError::ConnectionClosed);
}
None => panic!("expected Some(RithmicError)"),
}
}
#[test]
fn error_field_forced_logout_is_connection_issue() {
let mut response = make_response(RithmicMessage::ForcedLogout(ForcedLogout::default()));
response.error = Some(RithmicError::ForcedLogout("server shutdown".into()));
let err = response.error.as_ref().expect("error should be set");
assert!(err.is_connection_issue());
}
#[test]
fn error_field_protocol_error_is_not_connection_issue() {
let mut response = make_response(RithmicMessage::ConnectionError);
response.error = Some(RithmicError::ProtocolError("decode failed".into()));
let err = response.error.as_ref().expect("error should be set");
assert!(!err.is_connection_issue());
}
}