use std::fmt::Display;
use std::io::Write;
use std::str::{self, FromStr};
use byteorder::{BigEndian, WriteBytesExt};
use serde::{Deserialize, Serialize};
use time::OffsetDateTime;
use crate::{Error, ToField};
pub(crate) mod shared_channel_configuration;
#[cfg(test)]
mod tests;
pub(crate) const PROTOBUF_MSG_ID: i32 = 200;
pub(crate) const MESSAGE_ID_LEN: usize = 4;
#[derive(Debug, Default, PartialEq, Eq, Hash, Copy, Clone)]
pub enum IncomingMessages {
Shutdown = -2,
#[default]
NotValid = -1,
TickPrice = 1,
TickSize = 2,
OrderStatus = 3,
Error = 4,
OpenOrder = 5,
AccountValue = 6,
PortfolioValue = 7,
AccountUpdateTime = 8,
NextValidId = 9,
ContractData = 10,
ExecutionData = 11,
MarketDepth = 12,
MarketDepthL2 = 13,
NewsBulletins = 14,
ManagedAccounts = 15,
ReceiveFA = 16,
HistoricalData = 17,
BondContractData = 18,
ScannerParameters = 19,
ScannerData = 20,
TickOptionComputation = 21,
TickGeneric = 45,
TickString = 46,
TickEFP = 47, CurrentTime = 49,
RealTimeBars = 50,
FundamentalData = 51,
ContractDataEnd = 52,
OpenOrderEnd = 53,
AccountDownloadEnd = 54,
ExecutionDataEnd = 55,
DeltaNeutralValidation = 56,
TickSnapshotEnd = 57,
MarketDataType = 58,
CommissionsReport = 59,
Position = 61,
PositionEnd = 62,
AccountSummary = 63,
AccountSummaryEnd = 64,
VerifyMessageApi = 65,
VerifyCompleted = 66,
DisplayGroupList = 67,
DisplayGroupUpdated = 68,
VerifyAndAuthMessageApi = 69,
VerifyAndAuthCompleted = 70,
PositionMulti = 71,
PositionMultiEnd = 72,
AccountUpdateMulti = 73,
AccountUpdateMultiEnd = 74,
SecurityDefinitionOptionParameter = 75,
SecurityDefinitionOptionParameterEnd = 76,
SoftDollarTier = 77,
FamilyCodes = 78,
SymbolSamples = 79,
MktDepthExchanges = 80,
TickReqParams = 81,
SmartComponents = 82,
NewsArticle = 83,
TickNews = 84,
NewsProviders = 85,
HistoricalNews = 86,
HistoricalNewsEnd = 87,
HeadTimestamp = 88,
HistogramData = 89,
HistoricalDataUpdate = 90,
RerouteMktDataReq = 91,
RerouteMktDepthReq = 92,
MarketRule = 93,
PnL = 94,
PnLSingle = 95,
HistoricalTick = 96,
HistoricalTickBidAsk = 97,
HistoricalTickLast = 98,
TickByTick = 99,
OrderBound = 100,
CompletedOrder = 101,
CompletedOrdersEnd = 102,
ReplaceFAEnd = 103,
WshMetaData = 104,
WshEventData = 105,
HistoricalSchedule = 106,
UserInfo = 107,
HistoricalDataEnd = 108,
CurrentTimeInMillis = 109,
ConfigResponse = 110,
UpdateConfigResponse = 111,
}
impl From<i32> for IncomingMessages {
fn from(value: i32) -> IncomingMessages {
match value {
-2 => IncomingMessages::Shutdown,
1 => IncomingMessages::TickPrice,
2 => IncomingMessages::TickSize,
3 => IncomingMessages::OrderStatus,
4 => IncomingMessages::Error,
5 => IncomingMessages::OpenOrder,
6 => IncomingMessages::AccountValue,
7 => IncomingMessages::PortfolioValue,
8 => IncomingMessages::AccountUpdateTime,
9 => IncomingMessages::NextValidId,
10 => IncomingMessages::ContractData,
11 => IncomingMessages::ExecutionData,
12 => IncomingMessages::MarketDepth,
13 => IncomingMessages::MarketDepthL2,
14 => IncomingMessages::NewsBulletins,
15 => IncomingMessages::ManagedAccounts,
16 => IncomingMessages::ReceiveFA,
17 => IncomingMessages::HistoricalData,
18 => IncomingMessages::BondContractData,
19 => IncomingMessages::ScannerParameters,
20 => IncomingMessages::ScannerData,
21 => IncomingMessages::TickOptionComputation,
45 => IncomingMessages::TickGeneric,
46 => IncomingMessages::TickString,
47 => IncomingMessages::TickEFP, 49 => IncomingMessages::CurrentTime,
50 => IncomingMessages::RealTimeBars,
51 => IncomingMessages::FundamentalData,
52 => IncomingMessages::ContractDataEnd,
53 => IncomingMessages::OpenOrderEnd,
54 => IncomingMessages::AccountDownloadEnd,
55 => IncomingMessages::ExecutionDataEnd,
56 => IncomingMessages::DeltaNeutralValidation,
57 => IncomingMessages::TickSnapshotEnd,
58 => IncomingMessages::MarketDataType,
59 => IncomingMessages::CommissionsReport,
61 => IncomingMessages::Position,
62 => IncomingMessages::PositionEnd,
63 => IncomingMessages::AccountSummary,
64 => IncomingMessages::AccountSummaryEnd,
65 => IncomingMessages::VerifyMessageApi,
66 => IncomingMessages::VerifyCompleted,
67 => IncomingMessages::DisplayGroupList,
68 => IncomingMessages::DisplayGroupUpdated,
69 => IncomingMessages::VerifyAndAuthMessageApi,
70 => IncomingMessages::VerifyAndAuthCompleted,
71 => IncomingMessages::PositionMulti,
72 => IncomingMessages::PositionMultiEnd,
73 => IncomingMessages::AccountUpdateMulti,
74 => IncomingMessages::AccountUpdateMultiEnd,
75 => IncomingMessages::SecurityDefinitionOptionParameter,
76 => IncomingMessages::SecurityDefinitionOptionParameterEnd,
77 => IncomingMessages::SoftDollarTier,
78 => IncomingMessages::FamilyCodes,
79 => IncomingMessages::SymbolSamples,
80 => IncomingMessages::MktDepthExchanges,
81 => IncomingMessages::TickReqParams,
82 => IncomingMessages::SmartComponents,
83 => IncomingMessages::NewsArticle,
84 => IncomingMessages::TickNews,
85 => IncomingMessages::NewsProviders,
86 => IncomingMessages::HistoricalNews,
87 => IncomingMessages::HistoricalNewsEnd,
88 => IncomingMessages::HeadTimestamp,
89 => IncomingMessages::HistogramData,
90 => IncomingMessages::HistoricalDataUpdate,
91 => IncomingMessages::RerouteMktDataReq,
92 => IncomingMessages::RerouteMktDepthReq,
93 => IncomingMessages::MarketRule,
94 => IncomingMessages::PnL,
95 => IncomingMessages::PnLSingle,
96 => IncomingMessages::HistoricalTick,
97 => IncomingMessages::HistoricalTickBidAsk,
98 => IncomingMessages::HistoricalTickLast,
99 => IncomingMessages::TickByTick,
100 => IncomingMessages::OrderBound,
101 => IncomingMessages::CompletedOrder,
102 => IncomingMessages::CompletedOrdersEnd,
103 => IncomingMessages::ReplaceFAEnd,
104 => IncomingMessages::WshMetaData,
105 => IncomingMessages::WshEventData,
106 => IncomingMessages::HistoricalSchedule,
107 => IncomingMessages::UserInfo,
108 => IncomingMessages::HistoricalDataEnd,
109 => IncomingMessages::CurrentTimeInMillis,
110 => IncomingMessages::ConfigResponse,
111 => IncomingMessages::UpdateConfigResponse,
_ => IncomingMessages::NotValid,
}
}
}
impl FromStr for IncomingMessages {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.parse::<i32>() {
Ok(n) => Ok(IncomingMessages::from(n)),
Err(_) => Err(Error::parse_field(s, "invalid incoming message type")),
}
}
}
pub(crate) fn routes_by_request_id(kind: IncomingMessages) -> bool {
text_request_id_field(kind).is_some()
}
pub(crate) fn text_request_id_field(kind: IncomingMessages) -> Option<usize> {
match kind {
IncomingMessages::AccountSummary
| IncomingMessages::AccountSummaryEnd
| IncomingMessages::AccountUpdateMulti
| IncomingMessages::AccountUpdateMultiEnd
| IncomingMessages::ContractDataEnd
| IncomingMessages::DisplayGroupList
| IncomingMessages::DisplayGroupUpdated
| IncomingMessages::ExecutionDataEnd
| IncomingMessages::MarketDataType
| IncomingMessages::MarketDepth
| IncomingMessages::MarketDepthL2
| IncomingMessages::PositionMulti
| IncomingMessages::PositionMultiEnd
| IncomingMessages::RealTimeBars
| IncomingMessages::ScannerData
| IncomingMessages::TickGeneric
| IncomingMessages::TickPrice
| IncomingMessages::TickSize
| IncomingMessages::TickSnapshotEnd
| IncomingMessages::TickString => Some(2),
IncomingMessages::ConfigResponse
| IncomingMessages::UpdateConfigResponse
| IncomingMessages::BondContractData
| IncomingMessages::ContractData
| IncomingMessages::ExecutionData
| IncomingMessages::HeadTimestamp
| IncomingMessages::HistogramData
| IncomingMessages::HistoricalData
| IncomingMessages::HistoricalDataEnd
| IncomingMessages::HistoricalDataUpdate
| IncomingMessages::HistoricalNews
| IncomingMessages::HistoricalNewsEnd
| IncomingMessages::HistoricalSchedule
| IncomingMessages::HistoricalTick
| IncomingMessages::HistoricalTickBidAsk
| IncomingMessages::HistoricalTickLast
| IncomingMessages::NewsArticle
| IncomingMessages::OpenOrder
| IncomingMessages::PnL
| IncomingMessages::PnLSingle
| IncomingMessages::ReplaceFAEnd
| IncomingMessages::SecurityDefinitionOptionParameter
| IncomingMessages::SecurityDefinitionOptionParameterEnd
| IncomingMessages::SmartComponents
| IncomingMessages::SoftDollarTier
| IncomingMessages::SymbolSamples
| IncomingMessages::TickByTick
| IncomingMessages::TickNews
| IncomingMessages::TickOptionComputation
| IncomingMessages::TickReqParams
| IncomingMessages::UserInfo
| IncomingMessages::WshEventData
| IncomingMessages::WshMetaData => Some(1),
_ => None,
}
}
#[allow(dead_code)]
#[derive(Debug, Copy, Clone, Eq, Hash, PartialEq)]
pub enum OutgoingMessages {
RequestMarketData = 1,
CancelMarketData = 2,
PlaceOrder = 3,
CancelOrder = 4,
RequestOpenOrders = 5,
RequestAccountData = 6,
RequestExecutions = 7,
RequestIds = 8,
RequestContractData = 9,
RequestMarketDepth = 10,
CancelMarketDepth = 11,
RequestNewsBulletins = 12,
CancelNewsBulletin = 13,
ChangeServerLog = 14,
RequestAutoOpenOrders = 15,
RequestAllOpenOrders = 16,
RequestManagedAccounts = 17,
RequestFA = 18,
ReplaceFA = 19,
RequestHistoricalData = 20,
ExerciseOptions = 21,
RequestScannerSubscription = 22,
CancelScannerSubscription = 23,
RequestScannerParameters = 24,
CancelHistoricalData = 25,
RequestCurrentTime = 49,
RequestRealTimeBars = 50,
CancelRealTimeBars = 51,
RequestFundamentalData = 52,
CancelFundamentalData = 53,
ReqCalcImpliedVolat = 54,
ReqCalcOptionPrice = 55,
CancelImpliedVolatility = 56,
CancelOptionPrice = 57,
RequestGlobalCancel = 58,
RequestMarketDataType = 59,
RequestPositions = 61,
RequestAccountSummary = 62,
CancelAccountSummary = 63,
CancelPositions = 64,
VerifyRequest = 65,
VerifyMessage = 66,
QueryDisplayGroups = 67,
SubscribeToGroupEvents = 68,
UpdateDisplayGroup = 69,
UnsubscribeFromGroupEvents = 70,
StartApi = 71,
VerifyAndAuthRequest = 72,
VerifyAndAuthMessage = 73,
RequestPositionsMulti = 74,
CancelPositionsMulti = 75,
RequestAccountUpdatesMulti = 76,
CancelAccountUpdatesMulti = 77,
RequestSecurityDefinitionOptionalParameters = 78,
RequestSoftDollarTiers = 79,
RequestFamilyCodes = 80,
RequestMatchingSymbols = 81,
RequestMktDepthExchanges = 82,
RequestSmartComponents = 83,
RequestNewsArticle = 84,
RequestNewsProviders = 85,
RequestHistoricalNews = 86,
RequestHeadTimestamp = 87,
RequestHistogramData = 88,
CancelHistogramData = 89,
CancelHeadTimestamp = 90,
RequestMarketRule = 91,
RequestPnL = 92,
CancelPnL = 93,
RequestPnLSingle = 94,
CancelPnLSingle = 95,
RequestHistoricalTicks = 96,
RequestTickByTickData = 97,
CancelTickByTickData = 98,
RequestCompletedOrders = 99,
RequestWshMetaData = 100,
CancelWshMetaData = 101,
RequestWshEventData = 102,
CancelWshEventData = 103,
RequestUserInfo = 104,
RequestCurrentTimeInMillis = 105,
CancelContractData = 106,
CancelHistoricalTicks = 107,
ReqConfig = 108,
UpdateConfig = 109,
}
impl ToField for OutgoingMessages {
fn to_field(&self) -> String {
(*self as i32).to_string()
}
}
impl std::fmt::Display for OutgoingMessages {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", *self as i32)
}
}
impl TryFrom<i32> for OutgoingMessages {
type Error = Error;
fn try_from(id: i32) -> Result<Self, Self::Error> {
match id {
1 => Ok(OutgoingMessages::RequestMarketData),
2 => Ok(OutgoingMessages::CancelMarketData),
3 => Ok(OutgoingMessages::PlaceOrder),
4 => Ok(OutgoingMessages::CancelOrder),
5 => Ok(OutgoingMessages::RequestOpenOrders),
6 => Ok(OutgoingMessages::RequestAccountData),
7 => Ok(OutgoingMessages::RequestExecutions),
8 => Ok(OutgoingMessages::RequestIds),
9 => Ok(OutgoingMessages::RequestContractData),
10 => Ok(OutgoingMessages::RequestMarketDepth),
11 => Ok(OutgoingMessages::CancelMarketDepth),
12 => Ok(OutgoingMessages::RequestNewsBulletins),
13 => Ok(OutgoingMessages::CancelNewsBulletin),
14 => Ok(OutgoingMessages::ChangeServerLog),
15 => Ok(OutgoingMessages::RequestAutoOpenOrders),
16 => Ok(OutgoingMessages::RequestAllOpenOrders),
17 => Ok(OutgoingMessages::RequestManagedAccounts),
18 => Ok(OutgoingMessages::RequestFA),
19 => Ok(OutgoingMessages::ReplaceFA),
20 => Ok(OutgoingMessages::RequestHistoricalData),
21 => Ok(OutgoingMessages::ExerciseOptions),
22 => Ok(OutgoingMessages::RequestScannerSubscription),
23 => Ok(OutgoingMessages::CancelScannerSubscription),
24 => Ok(OutgoingMessages::RequestScannerParameters),
25 => Ok(OutgoingMessages::CancelHistoricalData),
49 => Ok(OutgoingMessages::RequestCurrentTime),
50 => Ok(OutgoingMessages::RequestRealTimeBars),
51 => Ok(OutgoingMessages::CancelRealTimeBars),
52 => Ok(OutgoingMessages::RequestFundamentalData),
53 => Ok(OutgoingMessages::CancelFundamentalData),
54 => Ok(OutgoingMessages::ReqCalcImpliedVolat),
55 => Ok(OutgoingMessages::ReqCalcOptionPrice),
56 => Ok(OutgoingMessages::CancelImpliedVolatility),
57 => Ok(OutgoingMessages::CancelOptionPrice),
58 => Ok(OutgoingMessages::RequestGlobalCancel),
59 => Ok(OutgoingMessages::RequestMarketDataType),
61 => Ok(OutgoingMessages::RequestPositions),
62 => Ok(OutgoingMessages::RequestAccountSummary),
63 => Ok(OutgoingMessages::CancelAccountSummary),
64 => Ok(OutgoingMessages::CancelPositions),
65 => Ok(OutgoingMessages::VerifyRequest),
66 => Ok(OutgoingMessages::VerifyMessage),
67 => Ok(OutgoingMessages::QueryDisplayGroups),
68 => Ok(OutgoingMessages::SubscribeToGroupEvents),
69 => Ok(OutgoingMessages::UpdateDisplayGroup),
70 => Ok(OutgoingMessages::UnsubscribeFromGroupEvents),
71 => Ok(OutgoingMessages::StartApi),
72 => Ok(OutgoingMessages::VerifyAndAuthRequest),
73 => Ok(OutgoingMessages::VerifyAndAuthMessage),
74 => Ok(OutgoingMessages::RequestPositionsMulti),
75 => Ok(OutgoingMessages::CancelPositionsMulti),
76 => Ok(OutgoingMessages::RequestAccountUpdatesMulti),
77 => Ok(OutgoingMessages::CancelAccountUpdatesMulti),
78 => Ok(OutgoingMessages::RequestSecurityDefinitionOptionalParameters),
79 => Ok(OutgoingMessages::RequestSoftDollarTiers),
80 => Ok(OutgoingMessages::RequestFamilyCodes),
81 => Ok(OutgoingMessages::RequestMatchingSymbols),
82 => Ok(OutgoingMessages::RequestMktDepthExchanges),
83 => Ok(OutgoingMessages::RequestSmartComponents),
84 => Ok(OutgoingMessages::RequestNewsArticle),
85 => Ok(OutgoingMessages::RequestNewsProviders),
86 => Ok(OutgoingMessages::RequestHistoricalNews),
87 => Ok(OutgoingMessages::RequestHeadTimestamp),
88 => Ok(OutgoingMessages::RequestHistogramData),
89 => Ok(OutgoingMessages::CancelHistogramData),
90 => Ok(OutgoingMessages::CancelHeadTimestamp),
91 => Ok(OutgoingMessages::RequestMarketRule),
92 => Ok(OutgoingMessages::RequestPnL),
93 => Ok(OutgoingMessages::CancelPnL),
94 => Ok(OutgoingMessages::RequestPnLSingle),
95 => Ok(OutgoingMessages::CancelPnLSingle),
96 => Ok(OutgoingMessages::RequestHistoricalTicks),
97 => Ok(OutgoingMessages::RequestTickByTickData),
98 => Ok(OutgoingMessages::CancelTickByTickData),
99 => Ok(OutgoingMessages::RequestCompletedOrders),
100 => Ok(OutgoingMessages::RequestWshMetaData),
101 => Ok(OutgoingMessages::CancelWshMetaData),
102 => Ok(OutgoingMessages::RequestWshEventData),
103 => Ok(OutgoingMessages::CancelWshEventData),
104 => Ok(OutgoingMessages::RequestUserInfo),
105 => Ok(OutgoingMessages::RequestCurrentTimeInMillis),
106 => Ok(OutgoingMessages::CancelContractData),
107 => Ok(OutgoingMessages::CancelHistoricalTicks),
108 => Ok(OutgoingMessages::ReqConfig),
109 => Ok(OutgoingMessages::UpdateConfig),
n => Err(Error::parse_field(n.to_string(), "unknown outgoing message type")),
}
}
}
impl FromStr for OutgoingMessages {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let id = s.parse::<i32>().map_err(|_| Error::parse_field(s, "invalid outgoing message type"))?;
OutgoingMessages::try_from(id)
}
}
impl OutgoingMessages {
pub(crate) fn is_cancel(self) -> bool {
match self {
OutgoingMessages::CancelMarketData
| OutgoingMessages::CancelOrder
| OutgoingMessages::CancelMarketDepth
| OutgoingMessages::CancelNewsBulletin
| OutgoingMessages::CancelScannerSubscription
| OutgoingMessages::CancelHistoricalData
| OutgoingMessages::CancelRealTimeBars
| OutgoingMessages::CancelFundamentalData
| OutgoingMessages::CancelImpliedVolatility
| OutgoingMessages::CancelOptionPrice
| OutgoingMessages::CancelAccountSummary
| OutgoingMessages::CancelPositions
| OutgoingMessages::CancelPositionsMulti
| OutgoingMessages::CancelAccountUpdatesMulti
| OutgoingMessages::CancelHistogramData
| OutgoingMessages::CancelHeadTimestamp
| OutgoingMessages::CancelPnL
| OutgoingMessages::CancelPnLSingle
| OutgoingMessages::CancelTickByTickData
| OutgoingMessages::CancelWshMetaData
| OutgoingMessages::CancelWshEventData
| OutgoingMessages::CancelContractData
| OutgoingMessages::CancelHistoricalTicks
| OutgoingMessages::RequestGlobalCancel
| OutgoingMessages::UnsubscribeFromGroupEvents => true,
OutgoingMessages::RequestMarketData
| OutgoingMessages::PlaceOrder
| OutgoingMessages::RequestOpenOrders
| OutgoingMessages::RequestAccountData
| OutgoingMessages::RequestExecutions
| OutgoingMessages::RequestIds
| OutgoingMessages::RequestContractData
| OutgoingMessages::RequestMarketDepth
| OutgoingMessages::RequestNewsBulletins
| OutgoingMessages::ChangeServerLog
| OutgoingMessages::RequestAutoOpenOrders
| OutgoingMessages::RequestAllOpenOrders
| OutgoingMessages::RequestManagedAccounts
| OutgoingMessages::RequestFA
| OutgoingMessages::ReplaceFA
| OutgoingMessages::RequestHistoricalData
| OutgoingMessages::ExerciseOptions
| OutgoingMessages::RequestScannerSubscription
| OutgoingMessages::RequestScannerParameters
| OutgoingMessages::RequestCurrentTime
| OutgoingMessages::RequestRealTimeBars
| OutgoingMessages::RequestFundamentalData
| OutgoingMessages::ReqCalcImpliedVolat
| OutgoingMessages::ReqCalcOptionPrice
| OutgoingMessages::RequestMarketDataType
| OutgoingMessages::RequestPositions
| OutgoingMessages::RequestAccountSummary
| OutgoingMessages::VerifyRequest
| OutgoingMessages::VerifyMessage
| OutgoingMessages::QueryDisplayGroups
| OutgoingMessages::SubscribeToGroupEvents
| OutgoingMessages::UpdateDisplayGroup
| OutgoingMessages::StartApi
| OutgoingMessages::VerifyAndAuthRequest
| OutgoingMessages::VerifyAndAuthMessage
| OutgoingMessages::RequestPositionsMulti
| OutgoingMessages::RequestAccountUpdatesMulti
| OutgoingMessages::RequestSecurityDefinitionOptionalParameters
| OutgoingMessages::RequestSoftDollarTiers
| OutgoingMessages::RequestFamilyCodes
| OutgoingMessages::RequestMatchingSymbols
| OutgoingMessages::RequestMktDepthExchanges
| OutgoingMessages::RequestSmartComponents
| OutgoingMessages::RequestNewsArticle
| OutgoingMessages::RequestNewsProviders
| OutgoingMessages::RequestHistoricalNews
| OutgoingMessages::RequestHeadTimestamp
| OutgoingMessages::RequestHistogramData
| OutgoingMessages::RequestMarketRule
| OutgoingMessages::RequestPnL
| OutgoingMessages::RequestPnLSingle
| OutgoingMessages::RequestHistoricalTicks
| OutgoingMessages::RequestTickByTickData
| OutgoingMessages::RequestCompletedOrders
| OutgoingMessages::RequestWshMetaData
| OutgoingMessages::RequestWshEventData
| OutgoingMessages::RequestUserInfo
| OutgoingMessages::RequestCurrentTimeInMillis
| OutgoingMessages::ReqConfig
| OutgoingMessages::UpdateConfig => false,
}
}
}
pub(crate) fn encode_length(message: &str) -> Vec<u8> {
encode_raw_length(message.as_bytes())
}
pub(crate) fn encode_protobuf_message(msg_id: i32, proto_bytes: &[u8]) -> Vec<u8> {
let mut buf = Vec::with_capacity(MESSAGE_ID_LEN + proto_bytes.len());
buf.write_i32::<BigEndian>(msg_id + PROTOBUF_MSG_ID).unwrap();
buf.extend_from_slice(proto_bytes);
buf
}
pub(crate) fn split_message_id(frame: &[u8]) -> Option<(i32, &[u8])> {
let (header, payload) = frame.split_first_chunk::<MESSAGE_ID_LEN>()?;
Some((i32::from_be_bytes(*header), payload))
}
pub(crate) fn outgoing_message_type(frame: &[u8]) -> Option<OutgoingMessages> {
let (id, _) = split_message_id(frame)?;
let id = if id > PROTOBUF_MSG_ID { id - PROTOBUF_MSG_ID } else { id };
OutgoingMessages::try_from(id).ok()
}
pub(crate) fn is_cancel_frame(frame: &[u8]) -> bool {
outgoing_message_type(frame).is_some_and(OutgoingMessages::is_cancel)
}
pub(crate) fn encode_raw_length(data: &[u8]) -> Vec<u8> {
let mut packet = Vec::with_capacity(data.len() + 4);
packet.write_u32::<BigEndian>(data.len() as u32).unwrap();
packet.write_all(data).unwrap();
packet
}
#[cfg(test)]
#[derive(Default, Debug, Clone)]
pub(crate) struct RequestMessage {
pub(crate) fields: Vec<String>,
}
#[cfg(all(test, feature = "sync"))]
impl RequestMessage {
pub(crate) fn encode(&self) -> String {
let mut data = self.fields.join("\0");
data.push('\0');
data
}
pub(crate) fn encode_simple(&self) -> String {
let mut data = self.fields.join("|");
data.push('|');
data
}
pub(crate) fn from(fields: &str) -> RequestMessage {
RequestMessage {
fields: fields.split_terminator('\x00').map(|x| x.to_string()).collect(),
}
}
pub(crate) fn from_simple(fields: &str) -> RequestMessage {
RequestMessage {
fields: fields.split_terminator('|').map(|x| x.to_string()).collect(),
}
}
}
#[cfg(test)]
impl std::ops::Index<usize> for RequestMessage {
type Output = String;
fn index(&self, i: usize) -> &Self::Output {
&self.fields[i]
}
}
#[derive(Clone, Copy, PartialEq, Eq, ::prost::Message)]
struct ProtoIdEnvelope {
#[prost(int32, optional, tag = "1")]
pub id: Option<i32>,
}
#[derive(Clone, PartialEq, ::prost::Message)]
struct ExecutionDetailsMinimal {
#[prost(message, optional, tag = "3")]
pub execution: Option<ExecutionMinimal>,
}
#[derive(Clone, PartialEq, ::prost::Message)]
struct ExecutionMinimal {
#[prost(int32, optional, tag = "1")]
pub order_id: Option<i32>,
#[prost(string, optional, tag = "2")]
pub exec_id: Option<String>,
}
#[derive(Clone, Default, Debug)]
pub(crate) struct ResponseMessage {
pub i: usize,
pub fields: Vec<String>,
pub raw_bytes: Option<Vec<u8>>,
kind: IncomingMessages,
message_id: Option<i32>,
}
impl ResponseMessage {
fn new(message_id: Option<i32>, fields: Vec<String>, raw_bytes: Option<Vec<u8>>) -> Self {
Self {
i: 0,
fields,
raw_bytes,
kind: message_id.map_or(IncomingMessages::NotValid, IncomingMessages::from),
message_id,
}
}
pub fn from_protobuf(message_type: i32, raw_bytes: Vec<u8>) -> Self {
Self::new(Some(message_type), Vec::new(), Some(raw_bytes))
}
pub(crate) fn message_id(&self) -> Option<i32> {
self.message_id
}
pub fn raw_bytes(&self) -> Option<&[u8]> {
self.raw_bytes.as_deref()
}
pub(crate) fn require_proto(&self) -> Result<&[u8], crate::Error> {
self.raw_bytes().ok_or_else(|| crate::Error::unexpected_wire_format(self))
}
pub(crate) fn expect_type(&self, expected: IncomingMessages) -> Result<&Self, Error> {
if self.message_type() == expected {
Ok(self)
} else {
Err(Error::UnexpectedResponse(format!("expected {expected:?}, got {self:?}")))
}
}
#[cfg_attr(not(feature = "sync"), allow(dead_code))] pub fn is_shutdown(&self) -> bool {
self.message_type() == IncomingMessages::Shutdown
}
pub fn message_type(&self) -> IncomingMessages {
self.kind
}
pub fn request_id(&self) -> Option<i32> {
let kind = self.message_type();
if !routes_by_request_id(kind) {
return None;
}
if let Some(raw) = self.raw_bytes() {
let env: ProtoIdEnvelope = prost::Message::decode(raw).ok()?;
env.id
} else {
self.peek_int(text_request_id_field(kind)?).ok()
}
}
pub fn order_id(&self) -> Option<i32> {
let raw = self.raw_bytes()?;
match self.message_type() {
IncomingMessages::OpenOrder | IncomingMessages::OrderStatus | IncomingMessages::ExecutionDataEnd => {
prost::Message::decode(raw).ok().and_then(|e: ProtoIdEnvelope| e.id)
}
IncomingMessages::ExecutionData => {
let p: ExecutionDetailsMinimal = prost::Message::decode(raw).ok()?;
p.execution.and_then(|e| e.order_id)
}
_ => None,
}
}
pub fn execution_id(&self) -> Option<String> {
let raw = self.raw_bytes()?;
match self.message_type() {
IncomingMessages::ExecutionData => {
let p: ExecutionDetailsMinimal = prost::Message::decode(raw).ok()?;
p.execution.and_then(|e| e.exec_id)
}
IncomingMessages::CommissionsReport => {
let p: crate::proto::CommissionAndFeesReport = prost::Message::decode(raw).ok()?;
p.exec_id
}
_ => None,
}
}
pub fn peek_int(&self, i: usize) -> Result<i32, Error> {
if self.raw_bytes().is_some() {
return Err(Error::unexpected_wire_format(self));
}
if i >= self.fields.len() {
return Err(Error::eof_at(i, "int"));
}
let field = &self.fields[i];
match field.parse() {
Ok(val) => Ok(val),
Err(err) => Err(Error::Parse(i, field.into(), err.to_string())),
}
}
pub fn next_int(&mut self) -> Result<i32, Error> {
if self.i >= self.fields.len() {
return Err(Error::eof_at(self.i, "int"));
}
let field = &self.fields[self.i];
self.i += 1;
match field.parse() {
Ok(val) => Ok(val),
Err(err) => Err(Error::Parse(self.i, field.into(), err.to_string())),
}
}
pub fn next_string(&mut self) -> Result<String, Error> {
if self.i >= self.fields.len() {
return Err(Error::eof_at(self.i, "string"));
}
let field = &self.fields[self.i];
self.i += 1;
Ok(String::from(field))
}
#[cfg(test)]
pub fn next_double(&mut self) -> Result<f64, Error> {
if self.i >= self.fields.len() {
return Err(Error::eof_at(self.i, "double"));
}
let field = &self.fields[self.i];
self.i += 1;
if field.is_empty() || field == "0" || field == "0.0" {
return Ok(0.0);
}
match field.parse() {
Ok(val) => Ok(val),
Err(err) => Err(Error::Parse(self.i, field.into(), err.to_string())),
}
}
pub fn from(fields: &str) -> ResponseMessage {
ResponseMessage::from_text_fields(fields.split_terminator('\x00').map(|x| x.to_string()).collect())
}
pub(crate) fn from_text_fields(fields: Vec<String>) -> ResponseMessage {
let message_id = fields.first().and_then(|id| i32::from_str(id).ok());
Self::new(message_id, fields, None)
}
#[cfg(test)]
pub fn from_simple(fields: &str) -> ResponseMessage {
ResponseMessage::from_text_fields(fields.split_terminator('|').map(|x| x.to_string()).collect())
}
pub fn encode(&self) -> String {
let mut data = self.fields.join("\0");
data.push('\0');
data
}
#[cfg(test)]
pub fn encode_simple(&self) -> String {
let mut data = self.fields.join("|");
data.push('|');
data
}
}
#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct Notice {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub request_id: Option<i32>,
pub code: i32,
pub message: String,
pub error_time: Option<OffsetDateTime>,
#[serde(default, skip_serializing_if = "String::is_empty")]
pub advanced_order_reject_json: String,
}
pub const ORDER_CANCELLED_CODE: i32 = 202;
pub const ORDER_MESSAGE_CODE: i32 = 399;
pub(crate) const MARKET_DEPTH_RESET_CODE: i32 = 317;
pub(crate) const HMDS_QUERY_MESSAGE_CODE: i32 = 165;
pub const WARNING_CODE_RANGE: std::ops::RangeInclusive<i32> = 2100..=2199;
pub(crate) fn is_warning_message(code: i32, message: &str) -> bool {
code == 0
|| WARNING_CODE_RANGE.contains(&code)
|| (code == ORDER_MESSAGE_CODE && message.lines().any(|line| line.trim_start().starts_with("Warning:")))
}
pub(crate) fn classify(code: i32, message: &str) -> NoticeCategory {
if code == ORDER_CANCELLED_CODE {
NoticeCategory::Cancellation
} else if DATA_ADVISORY_CODES.contains(&code) {
NoticeCategory::DataAdvisory
} else if is_warning_message(code, message) {
NoticeCategory::Warning
} else if SYSTEM_MESSAGE_CODES.contains(&code) {
NoticeCategory::SystemMessage
} else if REQUEST_ERROR_CODES.contains(&code) {
NoticeCategory::RequestError
} else if ORDER_REJECTION_CODE_RANGE.contains(&code) {
NoticeCategory::OrderRejection
} else {
NoticeCategory::Error
}
}
pub(crate) fn is_informational_code(code: i32, message: &str) -> bool {
match classify(code, message) {
NoticeCategory::Cancellation | NoticeCategory::DataAdvisory | NoticeCategory::Warning | NoticeCategory::SystemMessage => true,
NoticeCategory::RequestError | NoticeCategory::OrderRejection | NoticeCategory::Error => false,
}
}
pub(crate) const CONNECTIVITY_LOST_CODE: i32 = 1100;
pub(crate) const CONNECTIVITY_RESTORED_DATA_LOST_CODE: i32 = 1101;
pub(crate) const CONNECTIVITY_RESTORED_DATA_MAINTAINED_CODE: i32 = 1102;
pub(crate) const SOCKET_PORT_RESET_CODE: i32 = 1300;
pub const SYSTEM_MESSAGE_CODES: [i32; 4] = [
CONNECTIVITY_LOST_CODE,
CONNECTIVITY_RESTORED_DATA_LOST_CODE,
CONNECTIVITY_RESTORED_DATA_MAINTAINED_CODE,
SOCKET_PORT_RESET_CODE,
];
pub const DATA_ADVISORY_CODES: &[i32] = &[MARKET_DEPTH_RESET_CODE, 2188, 10089, 10090, 10091, 10167];
pub const REQUEST_ERROR_CODES: &[i32] = &[300, 301, 302, 309, 310, 316, 319, 326, 327, 330, 331, 354, 357, 365, 366, 385, 386];
pub(crate) const FARM_OK_CODES: [i32; 3] = [2104, 2106, 2158];
pub(crate) const FARM_BROKEN_CODES: [i32; 3] = [2103, 2105, 2157];
pub(crate) const FARM_INACTIVE_CODES: [i32; 2] = [2107, 2108];
pub(crate) const FARM_CONNECTING_CODES: [i32; 1] = [2119];
pub const ORDER_REJECTION_CODE_RANGE: std::ops::RangeInclusive<i32> = 200..=399;
pub const HANDSHAKE_UNKNOWN_FRAME_CODE: i32 = -3;
pub const HANDSHAKE_DECODE_FAILURE_CODE: i32 = -4;
pub const UNKNOWN_MESSAGE_TYPE_CODE: i32 = -5;
pub(crate) fn unknown_message_type_notice(message: &ResponseMessage) -> Notice {
log::warn!("unroutable frame: message id maps to no known type — the stream may be desynchronized: {message:?}");
let id = match message.message_id() {
Some(id) => format!("message id {id}"),
None => "no message id".to_string(),
};
Notice::synthesized(
UNKNOWN_MESSAGE_TYPE_CODE,
format!("received a frame with {id}, which maps to no known type; the stream may be desynchronized"),
)
}
pub const SUBSCRIPTION_LAG_CODE: i32 = -6;
#[cfg(feature = "async")]
pub(crate) fn subscription_lag_notice(skipped: u64) -> Notice {
let message = format!("subscription fell behind; {skipped} frames dropped (consumer lagged broadcast channel)");
log::warn!("{message}");
Notice::synthesized(SUBSCRIPTION_LAG_CODE, message)
}
#[cfg(feature = "async")]
pub(crate) fn order_lag_notice(skipped: u64) -> Notice {
let message = format!("order stream fell behind; {skipped} frames dropped — order state unknown, resync with open_orders() / executions()");
log::error!("{message}");
Notice::synthesized(SUBSCRIPTION_LAG_CODE, message)
}
pub const NOTICE_STREAM_LAG_CODE: i32 = -7;
#[cfg(feature = "async")]
pub(crate) fn notice_stream_lag_notice(skipped: u64) -> Notice {
let message = format!("notice stream fell behind; {skipped} notices dropped (consumer lagged the notice fan-out)");
log::warn!("{message}");
Notice::synthesized(NOTICE_STREAM_LAG_CODE, message)
}
pub const TRANSPORT_RECONNECT_CODE: i32 = -8;
pub(crate) fn transport_reconnect_notice() -> Notice {
Notice::synthesized(
TRANSPORT_RECONNECT_CODE,
"transport reconnected to TWS/Gateway; this is a new connection whose server-side state starts empty, so every request subscription from the previous connection is gone".to_string(),
)
}
#[non_exhaustive]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum NoticeCategory {
Cancellation,
Warning,
SystemMessage,
OrderRejection,
RequestError,
DataAdvisory,
Error,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ConnectivityStatus {
Ok,
Broken,
Inactive,
Connecting,
}
impl ConnectivityStatus {
pub fn from_code(code: i32) -> Option<ConnectivityStatus> {
if FARM_OK_CODES.contains(&code) {
Some(ConnectivityStatus::Ok)
} else if FARM_BROKEN_CODES.contains(&code) {
Some(ConnectivityStatus::Broken)
} else if FARM_INACTIVE_CODES.contains(&code) {
Some(ConnectivityStatus::Inactive)
} else if FARM_CONNECTING_CODES.contains(&code) {
Some(ConnectivityStatus::Connecting)
} else {
None
}
}
}
impl From<&ResponseMessage> for Notice {
fn from(message: &ResponseMessage) -> Notice {
let payload = message
.raw_bytes()
.and_then(crate::transport::routing::decode_error_envelope)
.unwrap_or_default();
Notice::from(payload)
}
}
impl Notice {
pub(crate) fn synthesized(code: i32, message: String) -> Notice {
Notice {
request_id: None,
code,
message,
error_time: None,
advanced_order_reject_json: String::new(),
}
}
pub fn is_cancellation(&self) -> bool {
self.category() == NoticeCategory::Cancellation
}
pub fn is_warning(&self) -> bool {
self.category() == NoticeCategory::Warning
}
pub fn is_system_message(&self) -> bool {
self.category() == NoticeCategory::SystemMessage
}
pub fn is_data_advisory(&self) -> bool {
self.category() == NoticeCategory::DataAdvisory
}
pub fn is_informational(&self) -> bool {
is_informational_code(self.code, &self.message)
}
pub fn is_error(&self) -> bool {
!self.is_informational()
}
pub fn is_order_rejection(&self) -> bool {
self.category() == NoticeCategory::OrderRejection
}
pub fn is_request_error(&self) -> bool {
self.category() == NoticeCategory::RequestError
}
pub fn is_handshake_synthetic(&self) -> bool {
self.code == HANDSHAKE_UNKNOWN_FRAME_CODE || self.code == HANDSHAKE_DECODE_FAILURE_CODE
}
pub fn is_client_synthesized(&self) -> bool {
self.code < 0
}
pub fn category(&self) -> NoticeCategory {
classify(self.code, &self.message)
}
pub fn connectivity_status(&self) -> Option<ConnectivityStatus> {
ConnectivityStatus::from_code(self.code)
}
fn log_level(&self) -> log::Level {
use log::Level;
match self.category() {
NoticeCategory::Cancellation => Level::Info,
NoticeCategory::Warning
if matches!(
self.connectivity_status(),
Some(ConnectivityStatus::Ok | ConnectivityStatus::Inactive | ConnectivityStatus::Connecting)
) =>
{
Level::Info
}
NoticeCategory::Warning | NoticeCategory::DataAdvisory => Level::Warn,
NoticeCategory::SystemMessage => match self.code {
CONNECTIVITY_RESTORED_DATA_MAINTAINED_CODE => Level::Info,
CONNECTIVITY_RESTORED_DATA_LOST_CODE => Level::Warn,
_ => Level::Error,
},
NoticeCategory::RequestError | NoticeCategory::OrderRejection | NoticeCategory::Error => Level::Error,
}
}
pub(crate) fn log(&self) {
log::log!(self.log_level(), "{self}");
}
}
impl Display for Notice {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "[{}] {}", self.code, self.message)
}
}
impl From<crate::transport::routing::DecodedError> for Notice {
fn from(payload: crate::transport::routing::DecodedError) -> Notice {
let error_time = payload
.error_time
.and_then(|millis| OffsetDateTime::from_unix_timestamp_nanos(millis as i128 * 1_000_000).ok());
Notice {
request_id: (payload.request_id != crate::transport::routing::UNSPECIFIED_REQUEST_ID).then_some(payload.request_id),
code: payload.error_code,
message: payload.error_message,
error_time,
advanced_order_reject_json: payload.advanced_order_reject_json,
}
}
}