use log::error;
use time::OffsetDateTime;
use crate::client::transport::ResponseIterator;
use crate::contracts::Contract;
use crate::messages::IncomingMessages;
use crate::orders::TagValue;
use crate::server_versions;
use crate::ToField;
use crate::{Client, Error};
mod decoders;
mod encoders;
#[cfg(test)]
mod tests;
#[derive(Clone, Debug, Copy)]
pub enum BarSize {
Sec5,
}
#[derive(Debug)]
pub struct BidAsk {
pub time: OffsetDateTime,
pub bid_price: f64,
pub ask_price: f64,
pub bid_size: i64,
pub ask_size: i64,
pub bid_ask_attribute: BidAskAttribute,
}
#[derive(Debug)]
pub struct BidAskAttribute {
pub bid_past_low: bool,
pub ask_past_high: bool,
}
#[derive(Debug)]
pub struct MidPoint {
pub time: OffsetDateTime,
pub mid_point: f64,
}
#[derive(Clone, Debug)]
pub struct Bar {
pub date: OffsetDateTime,
pub open: f64,
pub high: f64,
pub low: f64,
pub close: f64,
pub volume: f64,
pub wap: f64,
pub count: i32,
}
#[derive(Debug)]
pub struct Trade {
pub tick_type: String,
pub time: OffsetDateTime,
pub price: f64,
pub size: i64,
pub trade_attribute: TradeAttribute,
pub exchange: String,
pub special_conditions: String,
}
#[derive(Debug)]
pub struct TradeAttribute {
pub past_limit: bool,
pub unreported: bool,
}
#[derive(Clone, Debug, Copy)]
pub enum WhatToShow {
Trades,
MidPoint,
Bid,
Ask,
}
impl ToString for WhatToShow {
fn to_string(&self) -> String {
match self {
Self::Trades => "TRADES".to_string(),
Self::MidPoint => "MIDPOINT".to_string(),
Self::Bid => "BID".to_string(),
Self::Ask => "ASK".to_string(),
}
}
}
impl ToField for WhatToShow {
fn to_field(&self) -> String {
self.to_string()
}
}
pub(crate) fn realtime_bars<'a>(
client: &'a Client,
contract: &Contract,
bar_size: &BarSize,
what_to_show: &WhatToShow,
use_rth: bool,
options: Vec<TagValue>,
) -> Result<RealTimeBarIterator<'a>, Error> {
client.check_server_version(server_versions::REAL_TIME_BARS, "It does not support real time bars.")?;
if !contract.trading_class.is_empty() || contract.contract_id > 0 {
client.check_server_version(
server_versions::TRADING_CLASS,
"It does not support ConId nor TradingClass parameters in reqRealTimeBars.",
)?;
}
let request_id = client.next_request_id();
let packet = encoders::encode_request_realtime_bars(client.server_version(), request_id, contract, bar_size, what_to_show, use_rth, options)?;
let responses = client.send_durable_request(request_id, packet)?;
Ok(RealTimeBarIterator::new(client, request_id, responses))
}
pub(crate) fn tick_by_tick_all_last<'a>(
client: &'a Client,
contract: &Contract,
number_of_ticks: i32,
ignore_size: bool,
) -> Result<impl Iterator<Item = Trade> + 'a, Error> {
validate_tick_by_tick_request(client, contract, number_of_ticks, ignore_size)?;
let server_version = client.server_version();
let request_id = client.next_request_id();
let message = encoders::tick_by_tick(server_version, request_id, contract, "AllLast", number_of_ticks, ignore_size)?;
let responses = client.send_durable_request(request_id, message)?;
Ok(TradeIterator {
client,
request_id,
responses,
})
}
fn validate_tick_by_tick_request(client: &Client, _contract: &Contract, number_of_ticks: i32, ignore_size: bool) -> Result<(), Error> {
client.check_server_version(server_versions::TICK_BY_TICK, "It does not support tick-by-tick requests.")?;
if number_of_ticks != 0 || ignore_size {
client.check_server_version(
server_versions::TICK_BY_TICK_IGNORE_SIZE,
"It does not support ignoreSize and numberOfTicks parameters in tick-by-tick requests.",
)?;
}
Ok(())
}
pub(crate) fn tick_by_tick_last<'a>(
client: &'a Client,
contract: &Contract,
number_of_ticks: i32,
ignore_size: bool,
) -> Result<TradeIterator<'a>, Error> {
validate_tick_by_tick_request(client, contract, number_of_ticks, ignore_size)?;
let server_version = client.server_version();
let request_id = client.next_request_id();
let message = encoders::tick_by_tick(server_version, request_id, contract, "Last", number_of_ticks, ignore_size)?;
let responses = client.send_durable_request(request_id, message)?;
Ok(TradeIterator {
client,
request_id,
responses,
})
}
pub(crate) fn tick_by_tick_bid_ask<'a>(
client: &'a Client,
contract: &Contract,
number_of_ticks: i32,
ignore_size: bool,
) -> Result<BidAskIterator<'a>, Error> {
validate_tick_by_tick_request(client, contract, number_of_ticks, ignore_size)?;
let server_version = client.server_version();
let request_id = client.next_request_id();
let message = encoders::tick_by_tick(server_version, request_id, contract, "BidAsk", number_of_ticks, ignore_size)?;
let responses = client.send_durable_request(request_id, message)?;
Ok(BidAskIterator {
client,
request_id,
responses,
})
}
pub(crate) fn tick_by_tick_midpoint<'a>(
client: &'a Client,
contract: &Contract,
number_of_ticks: i32,
ignore_size: bool,
) -> Result<MidPointIterator<'a>, Error> {
validate_tick_by_tick_request(client, contract, number_of_ticks, ignore_size)?;
let server_version = client.server_version();
let request_id = client.next_request_id();
let message = encoders::tick_by_tick(server_version, request_id, contract, "MidPoint", number_of_ticks, ignore_size)?;
let responses = client.send_durable_request(request_id, message)?;
Ok(MidPointIterator {
client,
request_id,
responses,
})
}
pub(crate) struct RealTimeBarIterator<'a> {
client: &'a Client,
request_id: i32,
responses: ResponseIterator,
}
impl<'a> RealTimeBarIterator<'a> {
fn new(client: &'a Client, request_id: i32, responses: ResponseIterator) -> RealTimeBarIterator<'a> {
RealTimeBarIterator {
client,
request_id,
responses,
}
}
fn cancel_realtime_bars(&mut self) {
let message = encoders::cancel_realtime_bars(self.request_id).unwrap();
self.client.send_message(message).unwrap();
}
}
impl<'a> Iterator for RealTimeBarIterator<'a> {
type Item = Bar;
fn next(&mut self) -> Option<Self::Item> {
if let Some(mut message) = self.responses.next() {
match message.message_type() {
IncomingMessages::RealTimeBars => {
let decoded = decoders::decode_realtime_bar(&mut message);
if let Ok(bar) = decoded {
return Some(bar);
}
error!("unexpected message: {:?}", decoded.err());
None
}
_ => {
error!("unexpected message: {message:?}");
None
}
}
} else {
None
}
}
}
impl<'a> Drop for RealTimeBarIterator<'a> {
fn drop(&mut self) {
self.cancel_realtime_bars()
}
}
pub(crate) struct TradeIterator<'a> {
client: &'a Client,
request_id: i32,
responses: ResponseIterator,
}
impl<'a> Drop for TradeIterator<'a> {
fn drop(&mut self) {
cancel_tick_by_tick(self.client, self.request_id);
}
}
impl<'a> Iterator for TradeIterator<'a> {
type Item = Trade;
fn next(&mut self) -> Option<Self::Item> {
loop {
match self.responses.next() {
Some(mut message) => match message.message_type() {
IncomingMessages::TickByTick => match decoders::trade_tick(&mut message) {
Ok(tick) => return Some(tick),
Err(e) => error!("unexpected message {message:?}: {e:?}"),
},
_ => error!("unexpected message {message:?}"),
},
None => return None,
}
}
}
}
pub(crate) struct BidAskIterator<'a> {
client: &'a Client,
request_id: i32,
responses: ResponseIterator,
}
fn cancel_tick_by_tick(client: &Client, request_id: i32) {
if client.server_version() >= server_versions::TICK_BY_TICK {
let message = encoders::cancel_tick_by_tick(request_id).unwrap();
client.send_message(message).unwrap();
}
}
impl<'a> Drop for BidAskIterator<'a> {
fn drop(&mut self) {
cancel_tick_by_tick(self.client, self.request_id);
}
}
impl<'a> Iterator for BidAskIterator<'a> {
type Item = BidAsk;
fn next(&mut self) -> Option<Self::Item> {
loop {
match self.responses.next() {
Some(mut message) => match message.message_type() {
IncomingMessages::TickByTick => match decoders::bid_ask_tick(&mut message) {
Ok(tick) => return Some(tick),
Err(e) => error!("unexpected message {message:?}: {e:?}"),
},
_ => error!("unexpected message {message:?}"),
},
None => return None,
}
}
}
}
pub(crate) struct MidPointIterator<'a> {
client: &'a Client,
request_id: i32,
responses: ResponseIterator,
}
impl<'a> Drop for MidPointIterator<'a> {
fn drop(&mut self) {
cancel_tick_by_tick(self.client, self.request_id);
}
}
impl<'a> Iterator for MidPointIterator<'a> {
type Item = MidPoint;
fn next(&mut self) -> Option<Self::Item> {
loop {
match self.responses.next() {
Some(mut message) => match message.message_type() {
IncomingMessages::TickByTick => match decoders::mid_point_tick(&mut message) {
Ok(tick) => return Some(tick),
Err(e) => error!("unexpected message {message:?}: {e:?}"),
},
_ => error!("unexpected message {message:?}"),
},
None => return None,
}
}
}
}