#![forbid(unsafe_code)]
#![deny(
clippy::unwrap_used,
clippy::expect_used,
clippy::cast_possible_truncation,
clippy::cast_sign_loss,
// A public item linking to a private one renders as dead, non-hyperlinked code on docs.rs — the
// reader is pointed at something they cannot open. Denied rather than warned because these
// accumulate silently: `cargo doc` is not part of the normal build or the pre-commit gate, so a
// warning here is one nobody sees. Fix by inlining the fact or linking a public item instead.
rustdoc::private_intra_doc_links
)]
#![warn(
unused,
clippy::cognitive_complexity,
unused_crate_dependencies,
unused_extern_crates,
clippy::unused_self,
clippy::useless_let_if_seq,
missing_debug_implementations,
rust_2018_idioms
)]
#![allow(clippy::type_complexity, clippy::too_many_arguments, type_alias_bounds)]
#![allow(clippy::redundant_field_names)]
#[cfg(test)]
use tracing_subscriber as _;
#[cfg(test)]
use serial_test as _;
#[cfg(test)]
use wiremock as _;
#[cfg(test)]
use time as _;
#[cfg(test)]
use temp_env as _;
#[cfg(test)]
use http as _;
#[cfg(test)]
use {hex as _, sha2 as _, tempfile as _};
#[cfg(test)]
use tokio_tungstenite as _;
use crate::{
error::DataError,
event::MarketEvent,
exchange::{Connector, PingInterval},
instrument::InstrumentData,
subscriber::{Subscribed, Subscriber},
subscription::{Subscription, SubscriptionKind},
transformer::ExchangeTransformer,
};
use futures::{SinkExt, Stream, StreamExt};
use rustrade_instrument::exchange::ExchangeId;
use rustrade_integration::{
Transformer,
error::SocketError,
protocol::{
StreamParser,
websocket::{WebSocket, WsError, WsMessage, WsSink, WsStream},
},
};
use rustrade_integration::stream::ExchangeStream;
use std::{collections::VecDeque, future::Future};
use tokio::sync::mpsc;
use tracing::{debug, error, warn};
pub mod error;
pub mod event;
pub mod exchange;
pub mod streams;
pub mod subscriber;
pub mod subscription;
pub mod instrument;
pub mod books;
pub mod transformer;
pub type ExchangeWsStream<Parser, Transformer> = ExchangeStream<Parser, WsStream, Transformer>;
pub trait Identifier<T> {
fn id(&self) -> T;
}
pub trait MarketStream<Exchange, Instrument, Kind>
where
Self: Stream<Item = Result<MarketEvent<Instrument::Key, Kind::Event>, DataError>>
+ Send
+ Sized
+ Unpin,
Exchange: Connector,
Instrument: InstrumentData,
Kind: SubscriptionKind,
{
fn init<SnapFetcher>(
subscriber: &Exchange::Subscriber,
subscriptions: &[Subscription<Exchange, Instrument, Kind>],
) -> impl Future<Output = Result<Self, DataError>> + Send
where
SnapFetcher: SnapshotFetcher<Exchange, Kind>,
Subscription<Exchange, Instrument, Kind>:
Identifier<Exchange::Channel> + Identifier<Exchange::Market>;
}
pub trait SnapshotFetcher<Exchange, Kind> {
fn fetch_snapshots<Instrument>(
subscriptions: &[Subscription<Exchange, Instrument, Kind>],
) -> impl Future<Output = Result<Vec<MarketEvent<Instrument::Key, Kind::Event>>, SocketError>> + Send
where
Exchange: Connector,
Instrument: InstrumentData,
Kind: SubscriptionKind,
Kind::Event: Send,
Subscription<Exchange, Instrument, Kind>: Identifier<Exchange::Market>;
}
impl<Exchange, Instrument, Kind, Transformer, Parser> MarketStream<Exchange, Instrument, Kind>
for ExchangeWsStream<Parser, Transformer>
where
Exchange: Connector + Send + Sync,
Exchange::Subscriber: Subscriber<Transport = WebSocket>,
Instrument: InstrumentData,
Kind: SubscriptionKind + Send + Sync,
Transformer: ExchangeTransformer<Exchange, Instrument::Key, Kind> + Send,
Kind::Event: Send + Sync,
Parser: StreamParser<Transformer::Input, Message = WsMessage, Error = WsError> + Send,
{
async fn init<SnapFetcher>(
subscriber: &Exchange::Subscriber,
subscriptions: &[Subscription<Exchange, Instrument, Kind>],
) -> Result<Self, DataError>
where
SnapFetcher: SnapshotFetcher<Exchange, Kind>,
Subscription<Exchange, Instrument, Kind>:
Identifier<Exchange::Channel> + Identifier<Exchange::Market>,
{
let Subscribed {
transport: websocket,
map: instrument_map,
buffered_websocket_events,
} = subscriber.subscribe(subscriptions).await?;
let initial_snapshots = SnapFetcher::fetch_snapshots(subscriptions).await?;
let (ws_sink, ws_stream) = websocket.split();
let (ws_sink_tx, ws_sink_rx) = mpsc::unbounded_channel();
tokio::spawn(distribute_messages_to_exchange(
Exchange::ID,
ws_sink,
ws_sink_rx,
));
if let Some(ping_interval) = Exchange::ping_interval() {
tokio::spawn(schedule_pings_to_exchange(
Exchange::ID,
ws_sink_tx.clone(),
ping_interval,
));
}
let mut transformer =
Transformer::init(instrument_map, &initial_snapshots, ws_sink_tx).await?;
let mut processed = process_buffered_events::<Parser, Transformer>(
&mut transformer,
buffered_websocket_events,
);
processed.extend(initial_snapshots.into_iter().map(Ok));
Ok(ExchangeWsStream::new(ws_stream, transformer, processed))
}
}
#[derive(Debug)]
pub struct NoInitialSnapshots;
impl<Exchange, Kind> SnapshotFetcher<Exchange, Kind> for NoInitialSnapshots {
fn fetch_snapshots<Instrument>(
_: &[Subscription<Exchange, Instrument, Kind>],
) -> impl Future<Output = Result<Vec<MarketEvent<Instrument::Key, Kind::Event>>, SocketError>> + Send
where
Exchange: Connector,
Instrument: InstrumentData,
Kind: SubscriptionKind,
Kind::Event: Send,
Subscription<Exchange, Instrument, Kind>: Identifier<Exchange::Market>,
{
std::future::ready(Ok(vec![]))
}
}
pub fn process_buffered_events<Parser, StreamTransformer>(
transformer: &mut StreamTransformer,
events: Vec<Parser::Message>,
) -> VecDeque<Result<StreamTransformer::Output, StreamTransformer::Error>>
where
Parser: StreamParser<StreamTransformer::Input>,
StreamTransformer: Transformer,
{
events
.into_iter()
.filter_map(|event| {
Parser::parse(Ok(event))?
.inspect_err(|error| {
warn!(
?error,
"failed to parse message buffered during Subscription validation"
)
})
.ok()
})
.flat_map(|parsed| transformer.transform(parsed))
.collect()
}
pub async fn distribute_messages_to_exchange(
exchange: ExchangeId,
mut ws_sink: WsSink,
mut ws_sink_rx: mpsc::UnboundedReceiver<WsMessage>,
) {
while let Some(message) = ws_sink_rx.recv().await {
if let Err(error) = ws_sink.send(message).await {
if rustrade_integration::protocol::websocket::is_websocket_disconnected(&error) {
break;
}
error!(
%exchange,
%error,
"failed to send output message to the exchange via WsSink"
);
}
}
}
pub async fn schedule_pings_to_exchange(
exchange: ExchangeId,
ws_sink_tx: mpsc::UnboundedSender<WsMessage>,
PingInterval { mut interval, ping }: PingInterval,
) {
loop {
interval.tick().await;
let payload = ping();
debug!(%exchange, %payload, "sending custom application-level ping to exchange");
if ws_sink_tx.send(payload).is_err() {
break;
}
}
}