use tracing::{debug, error, info};
use tokio::{
sync::{broadcast, mpsc, oneshot},
task::JoinHandle,
};
use crate::{
ConnectStrategy,
api::receiver_api::RithmicResponse,
config::{LoginConfig, RithmicConfig},
error::RithmicError,
plants::{
await_all_responses, await_first_response,
core::{PlantActor, PlantCore, SelectResult},
},
rti::{
messages::RithmicMessage, request_login::SysInfraType, request_tick_bar_update,
request_time_bar_replay::BarType, request_time_bar_update,
},
types::{TickBarReplayRequest, TimeBarReplayRequest, VolumeProfileMinuteBarsRequest},
};
pub(crate) enum HistoryPlantCommand {
Close,
Abort,
GetSystemInfo {
response_sender: oneshot::Sender<Result<Vec<RithmicResponse>, RithmicError>>,
},
Login {
config: LoginConfig,
response_sender: oneshot::Sender<Result<Vec<RithmicResponse>, RithmicError>>,
},
SetLogin,
Logout {
response_sender: oneshot::Sender<Result<Vec<RithmicResponse>, RithmicError>>,
},
UpdateHeartbeat {
seconds: u64,
},
LoadTicks {
request: TickBarReplayRequest,
response_sender: oneshot::Sender<Result<Vec<RithmicResponse>, RithmicError>>,
},
LoadTimeBars {
request: TimeBarReplayRequest,
response_sender: oneshot::Sender<Result<Vec<RithmicResponse>, RithmicError>>,
},
LoadVolumeProfileMinuteBars {
request: VolumeProfileMinuteBarsRequest,
response_sender: oneshot::Sender<Result<Vec<RithmicResponse>, RithmicError>>,
},
ResumeBars {
request_key: String,
response_sender: oneshot::Sender<Result<Vec<RithmicResponse>, RithmicError>>,
},
SubscribeTimeBarUpdates {
symbol: String,
exchange: String,
bar_type: request_time_bar_update::BarType,
bar_type_period: i32,
request: request_time_bar_update::Request,
response_sender: oneshot::Sender<Result<Vec<RithmicResponse>, RithmicError>>,
},
SubscribeTickBarUpdates {
symbol: String,
exchange: String,
bar_type: request_tick_bar_update::BarType,
bar_sub_type: request_tick_bar_update::BarSubType,
bar_type_specifier: String,
request: request_tick_bar_update::Request,
response_sender: oneshot::Sender<Result<Vec<RithmicResponse>, RithmicError>>,
},
}
#[derive(Debug)]
pub struct RithmicHistoryPlant {
pub(crate) connection_handle: JoinHandle<()>,
sender: mpsc::Sender<HistoryPlantCommand>,
subscription_sender: broadcast::Sender<RithmicResponse>,
}
impl RithmicHistoryPlant {
pub async fn connect(
config: &RithmicConfig,
strategy: ConnectStrategy,
) -> Result<RithmicHistoryPlant, RithmicError> {
let (req_tx, req_rx) = mpsc::channel::<HistoryPlantCommand>(32);
let (sub_tx, _sub_rx) = broadcast::channel::<RithmicResponse>(20_000);
let mut history_plant = HistoryPlant::new(req_rx, sub_tx.clone(), config, strategy).await?;
let connection_handle = tokio::spawn(async move {
history_plant.run().await;
});
Ok(RithmicHistoryPlant {
connection_handle,
sender: req_tx,
subscription_sender: sub_tx,
})
}
}
impl RithmicHistoryPlant {
pub async fn await_shutdown(self) -> Result<(), tokio::task::JoinError> {
self.connection_handle.await
}
pub fn get_handle(&self) -> RithmicHistoryPlantHandle {
RithmicHistoryPlantHandle {
sender: self.sender.clone(),
subscription_receiver: self.subscription_sender.subscribe(),
subscription_sender: self.subscription_sender.clone(),
}
}
}
#[derive(Debug)]
struct HistoryPlant {
core: PlantCore,
request_receiver: mpsc::Receiver<HistoryPlantCommand>,
}
impl HistoryPlant {
async fn new(
request_receiver: mpsc::Receiver<HistoryPlantCommand>,
subscription_sender: broadcast::Sender<RithmicResponse>,
config: &RithmicConfig,
strategy: ConnectStrategy,
) -> Result<HistoryPlant, RithmicError> {
let core = PlantCore::new(subscription_sender, config, strategy, "history_plant").await?;
Ok(HistoryPlant {
core,
request_receiver,
})
}
}
impl PlantActor for HistoryPlant {
type Command = HistoryPlantCommand;
async fn run(&mut self) {
loop {
let result = self.core.next_event(&mut self.request_receiver).await;
let stop = match result {
SelectResult::HeartbeatFired => self.core.send_heartbeat().await,
SelectResult::PingFired => self.core.send_ping().await,
SelectResult::PingTimeout => self.core.handle_ping_timeout(),
SelectResult::Command(cmd) => {
if matches!(cmd, HistoryPlantCommand::Abort) {
self.core.handle_abort()
} else {
self.handle_command(cmd).await;
false
}
}
SelectResult::RithmicMessage(msg) => self.core.handle_rithmic_message(msg).await,
SelectResult::StreamClosed => self.core.handle_stream_closed(),
};
if stop {
break;
}
}
}
async fn handle_command(&mut self, command: HistoryPlantCommand) {
if self.core.close_requested
&& !matches!(
command,
HistoryPlantCommand::Close
| HistoryPlantCommand::SetLogin
| HistoryPlantCommand::UpdateHeartbeat { .. }
| HistoryPlantCommand::Abort
)
{
debug!("history_plant: dropping a command queued after close was requested");
return;
}
match command {
HistoryPlantCommand::Close => {
self.core.handle_close().await;
}
HistoryPlantCommand::GetSystemInfo { response_sender } => {
self.core.handle_get_system_info(response_sender).await;
}
HistoryPlantCommand::Login {
config,
response_sender,
} => {
self.core
.handle_login(config, SysInfraType::HistoryPlant, response_sender)
.await;
}
HistoryPlantCommand::SetLogin => {
self.core.handle_set_login();
}
HistoryPlantCommand::Logout { response_sender } => {
self.core.handle_logout(response_sender).await;
}
HistoryPlantCommand::UpdateHeartbeat { seconds } => {
self.core.handle_update_heartbeat(seconds);
}
HistoryPlantCommand::LoadTicks {
request,
response_sender,
} => {
let (tick_bar_replay_buf, id) = self
.core
.rithmic_sender_api
.request_tick_bar_replay(&request);
self.core
.register_and_send(tick_bar_replay_buf, id, response_sender)
.await;
}
HistoryPlantCommand::LoadTimeBars {
request,
response_sender,
} => {
let (time_bar_replay_buf, id) = self
.core
.rithmic_sender_api
.request_time_bar_replay(&request);
self.core
.register_and_send(time_bar_replay_buf, id, response_sender)
.await;
}
HistoryPlantCommand::LoadVolumeProfileMinuteBars {
request,
response_sender,
} => {
let (buf, id) = self
.core
.rithmic_sender_api
.request_volume_profile_minute_bars(&request);
self.core.register_and_send(buf, id, response_sender).await;
}
HistoryPlantCommand::ResumeBars {
request_key,
response_sender,
} => {
let (buf, id) = self
.core
.rithmic_sender_api
.request_resume_bars(&request_key);
self.core.register_and_send(buf, id, response_sender).await;
}
HistoryPlantCommand::SubscribeTimeBarUpdates {
symbol,
exchange,
bar_type,
bar_type_period,
request,
response_sender,
} => {
let (buf, id) = self.core.rithmic_sender_api.request_time_bar_update(
&symbol,
&exchange,
bar_type,
bar_type_period,
request,
);
self.core.register_and_send(buf, id, response_sender).await;
}
HistoryPlantCommand::SubscribeTickBarUpdates {
symbol,
exchange,
bar_type,
bar_sub_type,
bar_type_specifier,
request,
response_sender,
} => {
let (buf, id) = self.core.rithmic_sender_api.request_tick_bar_update(
&symbol,
&exchange,
bar_type,
bar_sub_type,
&bar_type_specifier,
request,
);
self.core.register_and_send(buf, id, response_sender).await;
}
HistoryPlantCommand::Abort => {
unreachable!("Abort is handled in run() before handle_command");
}
}
}
}
pub struct RithmicHistoryPlantHandle {
sender: mpsc::Sender<HistoryPlantCommand>,
subscription_sender: broadcast::Sender<RithmicResponse>,
pub subscription_receiver: broadcast::Receiver<RithmicResponse>,
}
impl std::fmt::Debug for RithmicHistoryPlantHandle {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("RithmicHistoryPlantHandle")
.field("sender", &self.sender)
.field("subscription_sender", &self.subscription_sender)
.finish_non_exhaustive()
}
}
impl RithmicHistoryPlantHandle {
pub async fn get_system_info(&self) -> Result<RithmicResponse, RithmicError> {
let (tx, rx) = oneshot::channel::<Result<Vec<RithmicResponse>, RithmicError>>();
let command = HistoryPlantCommand::GetSystemInfo {
response_sender: tx,
};
let _ = self.sender.send(command).await;
await_first_response(rx).await
}
pub async fn login(&self) -> Result<RithmicResponse, RithmicError> {
self.login_with_config(LoginConfig::default()).await
}
pub async fn login_with_config(
&self,
config: LoginConfig,
) -> Result<RithmicResponse, RithmicError> {
info!("history_plant: logging in");
let (tx, rx) = oneshot::channel::<Result<Vec<RithmicResponse>, RithmicError>>();
let mut config = config;
config.aggregated_quotes = None;
let command = HistoryPlantCommand::Login {
config,
response_sender: tx,
};
let _ = self.sender.send(command).await;
let response = await_first_response(rx).await?;
if let Some(err) = response.error.clone() {
error!("history_plant: login failed {:?}", err);
return Err(err);
}
let _ = self.sender.send(HistoryPlantCommand::SetLogin).await;
if let RithmicMessage::ResponseLogin(resp) = &response.message {
if let Some(hb) = resp.heartbeat_interval {
let secs = hb as u64;
self.update_heartbeat(secs).await;
}
if let Some(session_id) = &resp.unique_user_id {
info!("history_plant: session id: {}", session_id);
}
}
info!("history_plant: logged in");
Ok(response)
}
async fn update_heartbeat(&self, seconds: u64) {
let command = HistoryPlantCommand::UpdateHeartbeat { seconds };
let _ = self.sender.send(command).await;
}
pub async fn disconnect(&self) -> Result<RithmicResponse, RithmicError> {
let (tx, rx) = oneshot::channel::<Result<Vec<RithmicResponse>, RithmicError>>();
let command = HistoryPlantCommand::Logout {
response_sender: tx,
};
let _ = self.sender.send(command).await;
let outcome = rx.await.map_err(|_| RithmicError::ConnectionClosed);
let _ = self.sender.send(HistoryPlantCommand::Close).await;
let response = outcome??
.into_iter()
.next()
.ok_or(RithmicError::EmptyResponse)?;
Ok(response)
}
pub fn abort(&self) {
let _ = self.sender.try_send(HistoryPlantCommand::Abort);
}
pub async fn load_ticks(
&self,
symbol: String,
exchange: String,
start_time_sec: i32,
end_time_sec: i32,
) -> Result<Vec<RithmicResponse>, RithmicError> {
self.load_tick_bars(symbol, exchange, 1, start_time_sec, end_time_sec)
.await
}
pub async fn load_tick_bars(
&self,
symbol: String,
exchange: String,
bar_length: u32,
start_time_sec: i32,
end_time_sec: i32,
) -> Result<Vec<RithmicResponse>, RithmicError> {
self.tick_bar_replay(
TickBarReplayRequest::new()
.symbol(symbol)
.exchange(exchange)
.bar_length(bar_length)
.start_time_sec(start_time_sec)
.end_time_sec(end_time_sec),
)
.await
}
async fn tick_bar_replay(
&self,
request: TickBarReplayRequest,
) -> Result<Vec<RithmicResponse>, RithmicError> {
request.validate()?;
let (tx, rx) = oneshot::channel::<Result<Vec<RithmicResponse>, RithmicError>>();
let command = HistoryPlantCommand::LoadTicks {
request,
response_sender: tx,
};
let _ = self.sender.send(command).await;
await_all_responses(rx).await
}
pub async fn load_ticks_all(
&self,
symbol: String,
exchange: String,
start_time_sec: i32,
end_time_sec: i32,
) -> Result<Vec<RithmicResponse>, RithmicError> {
self.load_tick_bars_all(symbol, exchange, 1, start_time_sec, end_time_sec)
.await
}
pub async fn load_tick_bars_all(
&self,
symbol: String,
exchange: String,
bar_length: u32,
start_time_sec: i32,
end_time_sec: i32,
) -> Result<Vec<RithmicResponse>, RithmicError> {
self.tick_bar_replay(
TickBarReplayRequest::new()
.symbol(symbol)
.exchange(exchange)
.bar_length(bar_length)
.start_time_sec(start_time_sec)
.end_time_sec(end_time_sec)
.resume_bars(true),
)
.await
}
pub async fn load_time_bars_all(
&self,
symbol: String,
exchange: String,
bar_type: BarType,
bar_type_period: i32,
start_time_sec: i32,
end_time_sec: i32,
) -> Result<Vec<RithmicResponse>, RithmicError> {
self.time_bar_replay(
TimeBarReplayRequest::new()
.symbol(symbol)
.exchange(exchange)
.bar_type(bar_type)
.bar_type_period(bar_type_period)
.start_time_sec(start_time_sec)
.end_time_sec(end_time_sec)
.resume_bars(true),
)
.await
}
pub async fn load_time_bars(
&self,
symbol: String,
exchange: String,
bar_type: BarType,
bar_type_period: i32,
start_time_sec: i32,
end_time_sec: i32,
) -> Result<Vec<RithmicResponse>, RithmicError> {
self.time_bar_replay(
TimeBarReplayRequest::new()
.symbol(symbol)
.exchange(exchange)
.bar_type(bar_type)
.bar_type_period(bar_type_period)
.start_time_sec(start_time_sec)
.end_time_sec(end_time_sec),
)
.await
}
async fn time_bar_replay(
&self,
request: TimeBarReplayRequest,
) -> Result<Vec<RithmicResponse>, RithmicError> {
request.validate()?;
let (tx, rx) = oneshot::channel::<Result<Vec<RithmicResponse>, RithmicError>>();
let command = HistoryPlantCommand::LoadTimeBars {
request,
response_sender: tx,
};
let _ = self.sender.send(command).await;
await_all_responses(rx).await
}
pub async fn load_volume_profile_minute_bars(
&self,
request: VolumeProfileMinuteBarsRequest,
) -> Result<Vec<RithmicResponse>, RithmicError> {
let (tx, rx) = oneshot::channel::<Result<Vec<RithmicResponse>, RithmicError>>();
let command = HistoryPlantCommand::LoadVolumeProfileMinuteBars {
request,
response_sender: tx,
};
let _ = self.sender.send(command).await;
await_all_responses(rx).await
}
pub async fn resume_bars(
&self,
request_key: String,
) -> Result<Vec<RithmicResponse>, RithmicError> {
let (tx, rx) = oneshot::channel::<Result<Vec<RithmicResponse>, RithmicError>>();
let command = HistoryPlantCommand::ResumeBars {
request_key,
response_sender: tx,
};
let _ = self.sender.send(command).await;
await_all_responses(rx).await
}
pub async fn subscribe_time_bar_updates(
&self,
symbol: &str,
exchange: &str,
bar_type: request_time_bar_update::BarType,
bar_type_period: i32,
request: request_time_bar_update::Request,
) -> Result<RithmicResponse, RithmicError> {
let (tx, rx) = oneshot::channel::<Result<Vec<RithmicResponse>, RithmicError>>();
let command = HistoryPlantCommand::SubscribeTimeBarUpdates {
symbol: symbol.to_string(),
exchange: exchange.to_string(),
bar_type,
bar_type_period,
request,
response_sender: tx,
};
let _ = self.sender.send(command).await;
await_first_response(rx).await
}
pub async fn subscribe_tick_bar_updates(
&self,
symbol: &str,
exchange: &str,
bar_type: request_tick_bar_update::BarType,
bar_sub_type: request_tick_bar_update::BarSubType,
bar_type_specifier: &str,
request: request_tick_bar_update::Request,
) -> Result<RithmicResponse, RithmicError> {
let (tx, rx) = oneshot::channel::<Result<Vec<RithmicResponse>, RithmicError>>();
let command = HistoryPlantCommand::SubscribeTickBarUpdates {
symbol: symbol.to_string(),
exchange: exchange.to_string(),
bar_type,
bar_sub_type,
bar_type_specifier: bar_type_specifier.to_string(),
request,
response_sender: tx,
};
let _ = self.sender.send(command).await;
await_first_response(rx).await
}
}
impl Clone for RithmicHistoryPlantHandle {
fn clone(&self) -> Self {
RithmicHistoryPlantHandle {
sender: self.sender.clone(),
subscription_receiver: self.subscription_sender.subscribe(),
subscription_sender: self.subscription_sender.clone(),
}
}
}
#[cfg(test)]
mod tests;