use serde::{Deserialize, Serialize};
use crate::{
client::{DataStream, ResponseContext, Subscription},
messages::{IncomingMessages, OutgoingMessages},
orders::TagValue,
server_versions, Client, Error,
};
mod decoders;
#[cfg(test)]
mod tests;
pub(super) fn scanner_parameters(client: &Client) -> Result<String, Error> {
let request = encoders::encode_scanner_parameters()?;
let subscription = client.send_shared_request(OutgoingMessages::RequestScannerParameters, request)?;
match subscription.next() {
Some(Ok(message)) => decoders::decode_scanner_parameters(message),
Some(Err(Error::ConnectionReset)) => scanner_parameters(client),
Some(Err(e)) => Err(e),
None => Err(Error::UnexpectedEndOfStream),
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct ScannerSubscription {
pub number_of_rows: i32,
pub instrument: Option<String>,
pub location_code: Option<String>,
pub scan_code: Option<String>,
pub above_price: Option<f64>,
pub below_price: Option<f64>,
pub above_volume: Option<i32>,
pub average_option_volume_above: Option<i32>,
pub market_cap_above: Option<f64>,
pub market_cap_below: Option<f64>,
pub moody_rating_above: Option<String>,
pub moody_rating_below: Option<String>,
pub sp_rating_above: Option<String>,
pub sp_rating_below: Option<String>,
pub maturity_date_above: Option<String>,
pub maturity_date_below: Option<String>,
pub coupon_rate_above: Option<f64>,
pub coupon_rate_below: Option<f64>,
pub exclude_convertible: bool,
pub scanner_setting_pairs: Option<String>,
pub stock_type_filter: Option<String>,
}
impl Default for ScannerSubscription {
fn default() -> Self {
ScannerSubscription {
number_of_rows: -1,
instrument: None,
location_code: None,
scan_code: None,
above_price: None,
below_price: None,
above_volume: None,
average_option_volume_above: None,
market_cap_above: None,
market_cap_below: None,
moody_rating_above: None,
moody_rating_below: None,
sp_rating_above: None,
sp_rating_below: None,
maturity_date_above: None,
maturity_date_below: None,
coupon_rate_above: None,
coupon_rate_below: None,
exclude_convertible: false,
scanner_setting_pairs: None,
stock_type_filter: None,
}
}
}
impl DataStream<Vec<ScannerData>> for Vec<ScannerData> {
fn decode(_client: &Client, message: &mut crate::messages::ResponseMessage) -> Result<Vec<ScannerData>, Error> {
match message.message_type() {
IncomingMessages::ScannerData => Ok(decoders::decode_scanner_data(message.clone())?),
_ => Err(Error::UnexpectedResponse(message.clone())),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq)]
pub struct ScannerData {
pub rank: i32,
pub contract_details: crate::contracts::ContractDetails,
pub leg: String,
}
pub(super) fn scanner_subscription<'a>(
client: &'a Client,
subscription: &ScannerSubscription,
filter: &Vec<TagValue>,
) -> Result<Subscription<'a, Vec<ScannerData>>, Error> {
if !filter.is_empty() {
client.check_server_version(
server_versions::SCANNER_GENERIC_OPTS,
"It does not support API scanner subscription generic filter options.",
)?
}
let request_id = client.next_request_id();
let request = encoders::encode_scanner_subscription(request_id, client.server_version, subscription, filter)?;
let subscription = client.send_request(request_id, request)?;
Ok(Subscription::new(client, subscription, ResponseContext::default()))
}
mod encoders {
use crate::messages::OutgoingMessages;
use crate::messages::RequestMessage;
use crate::orders::TagValue;
use crate::server_versions;
use crate::Error;
use super::ScannerSubscription;
pub(super) fn encode_scanner_parameters() -> Result<RequestMessage, Error> {
const VERSION: i32 = 1;
let mut message = RequestMessage::new();
message.push_field(&OutgoingMessages::RequestScannerParameters);
message.push_field(&VERSION);
Ok(message)
}
pub(super) fn encode_scanner_subscription(
request_id: i32,
server_version: i32,
subscription: &ScannerSubscription,
filter: &Vec<TagValue>,
) -> Result<RequestMessage, Error> {
const VERSION: i32 = 4;
let mut message = RequestMessage::new();
message.push_field(&OutgoingMessages::RequestScannerSubscription);
if server_version < server_versions::SCANNER_GENERIC_OPTS {
message.push_field(&VERSION);
}
message.push_field(&request_id);
message.push_field(&subscription.number_of_rows);
message.push_field(&subscription.instrument);
message.push_field(&subscription.location_code);
message.push_field(&subscription.scan_code);
message.push_field(&subscription.above_price);
message.push_field(&subscription.below_price);
message.push_field(&subscription.above_volume);
message.push_field(&subscription.market_cap_above);
message.push_field(&subscription.market_cap_below);
message.push_field(&subscription.moody_rating_above);
message.push_field(&subscription.moody_rating_below);
message.push_field(&subscription.sp_rating_above);
message.push_field(&subscription.sp_rating_below);
message.push_field(&subscription.maturity_date_above);
message.push_field(&subscription.maturity_date_below);
message.push_field(&subscription.coupon_rate_above);
message.push_field(&subscription.coupon_rate_below);
message.push_field(&subscription.exclude_convertible);
message.push_field(&subscription.average_option_volume_above);
message.push_field(&subscription.scanner_setting_pairs);
message.push_field(&subscription.stock_type_filter);
if server_version >= server_versions::SCANNER_GENERIC_OPTS {
message.push_field(filter);
}
if server_version >= server_versions::LINKING {
message.push_field(&""); }
Ok(message)
}
}