use std::sync::Arc;
use std::time::Duration;
use quickfix_tokio::fix44::messages::market_data_request::{
MarketDataRequest, NoMDEntryTypes, NoRelatedSym,
};
use quickfix_tokio::fix44::messages::market_data_snapshot_full_refresh::{
MarketDataSnapshotFullRefresh, NoMDEntries,
};
use quickfix_tokio::fix44::{classify, fields, AnyMessage};
use quickfix_tokio::{
Application, ApplicationError, Engine, MemoryStoreFactory, Message, NullLogFactory, SessionId,
Settings, dec,
};
use tokio::sync::mpsc;
struct Publisher {
tx: mpsc::UnboundedSender<(SessionId, MarketDataSnapshotFullRefresh)>,
}
#[async_trait::async_trait]
impl Application for Publisher {
async fn from_app(
&self,
msg: &Message,
session_id: &SessionId,
) -> Result<(), ApplicationError> {
if let AnyMessage::MarketDataRequest(req) = classify(msg.clone()) {
let symbol = req
.no_related_sym()
.ok()
.and_then(|syms| syms.into_iter().next())
.and_then(|s| s.symbol().ok())
.unwrap_or_default();
let wants: Vec<char> = req
.no_md_entry_types()
.unwrap_or_default()
.iter()
.filter_map(|e| e.md_entry_type().ok())
.collect();
println!(
"publisher: request {} for {symbol}, entry types {:?}",
req.md_req_id().unwrap_or_default(),
wants
);
let mut snap = MarketDataSnapshotFullRefresh::new();
if let Ok(id) = req.md_req_id() {
snap.set_md_req_id(id);
}
snap.set_symbol(&symbol);
let book = [
(fields::MDEntryType::BID, dec!(100.25), dec!(500)),
(fields::MDEntryType::OFFER, dec!(100.75), dec!(300)),
];
let entries = book.iter().filter(|(t, _, _)| wants.contains(t)).map(|&(t, px, sz)| {
let mut e = NoMDEntries::new();
e.set_md_entry_type(t); e.set_md_entry_px(px);
e.set_md_entry_size(sz);
e
});
snap.set_no_md_entries(entries);
let _ = self.tx.send((session_id.clone(), snap));
Ok(())
} else {
Err(ApplicationError::UnsupportedMessageType)
}
}
}
struct Subscriber {
done: mpsc::UnboundedSender<MarketDataSnapshotFullRefresh>,
}
#[async_trait::async_trait]
impl Application for Subscriber {
async fn from_app(
&self,
msg: &Message,
_session_id: &SessionId,
) -> Result<(), ApplicationError> {
if let AnyMessage::MarketDataSnapshotFullRefresh(snap) = classify(msg.clone()) {
let _ = self.done.send(snap);
}
Ok(())
}
}
fn free_port() -> u16 {
std::net::TcpListener::bind("127.0.0.1:0").unwrap().local_addr().unwrap().port()
}
#[tokio::main]
async fn main() -> quickfix_tokio::Result<()> {
let port = free_port();
let spec = concat!(env!("CARGO_MANIFEST_DIR"), "/spec/FIX44.xml");
let (snap_tx, mut snap_rx) = mpsc::unbounded_channel();
let pub_engine = Engine::start(
&Settings::parse(&format!(
"[SESSION]\nConnectionType=acceptor\nBeginString=FIX.4.4\n\
SenderCompID=PUB\nTargetCompID=SUB\nSocketAcceptPort={port}\n\
HeartBtInt=30\nDataDictionary={spec}\n"
))?,
Arc::new(Publisher { tx: snap_tx }),
Arc::new(MemoryStoreFactory::new()),
Arc::new(NullLogFactory),
)
.await?;
let pub_sessions = pub_engine.session("FIX.4.4", "PUB", "SUB").unwrap();
tokio::spawn(async move {
while let Some((_, snap)) = snap_rx.recv().await {
let _ = pub_sessions.send(snap.into()).await;
}
});
let (done_tx, mut done_rx) = mpsc::unbounded_channel();
let sub_engine = Engine::start(
&Settings::parse(&format!(
"[SESSION]\nConnectionType=initiator\nBeginString=FIX.4.4\n\
SenderCompID=SUB\nTargetCompID=PUB\nSocketConnectHost=127.0.0.1\n\
SocketConnectPort={port}\nHeartBtInt=30\nReconnectInterval=1\n\
DataDictionary={spec}\n"
))?,
Arc::new(Subscriber { done: done_tx }),
Arc::new(MemoryStoreFactory::new()),
Arc::new(NullLogFactory),
)
.await?;
let sub = sub_engine.session("FIX.4.4", "SUB", "PUB").unwrap();
while !sub.is_logged_on().await {
tokio::time::sleep(Duration::from_millis(50)).await;
}
let mut req = MarketDataRequest::new(
"MDREQ-1",
fields::SubscriptionRequestType::SNAPSHOT,
0, );
let mut sym = NoRelatedSym::new();
sym.set_symbol("TSLA");
req.set_no_related_sym([sym]);
let entry_types = [fields::MDEntryType::BID, fields::MDEntryType::OFFER].map(|t| {
let mut e = NoMDEntryTypes::new();
e.set_md_entry_type(t);
e
});
req.set_no_md_entry_types(entry_types);
println!("subscriber: sending MarketDataRequest for TSLA (bid + offer)");
sub.send(req.into()).await?;
let snap = done_rx.recv().await.expect("snapshot");
println!("subscriber: snapshot for {}", snap.symbol().unwrap_or_default());
for entry in snap.no_md_entries().unwrap() {
let side = match entry.md_entry_type().unwrap() {
fields::MDEntryType::BID => "bid",
fields::MDEntryType::OFFER => "offer",
_ => "?",
};
println!(
" {side:>5} {} x {}",
entry.md_entry_px().unwrap(),
entry.md_entry_size().unwrap()
);
}
sub_engine.stop().await;
pub_engine.stop().await;
Ok(())
}