From 6ee39aa9b995c7c4adf0baa057823f31a29cecf3 Mon Sep 17 00:00:00 2001 From: Wil Boayue Date: Fri, 1 Nov 2024 12:43:34 -0700 Subject: [PATCH 1/7] checkpoint --- src/client.rs | 16 ++++++++++++++++ src/scanner.rs | 18 +++++++++++++++++- 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/src/client.rs b/src/client.rs index 51be0f48..ac0b9b33 100644 --- a/src/client.rs +++ b/src/client.rs @@ -1375,6 +1375,22 @@ impl Client { scanner::scanner_parameters(self) } + /// Starts a subscription to market scan results based on the provided parameters. + /// + /// # Examples + /// + /// ```no_run + /// use ibapi::Client; + /// + /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed"); + /// + /// let parameters = client.scanner_parameters().expect("request scanner parameters failed"); + /// println!("{:?}", parameters); + /// ``` + pub fn scanner_subscription(&self, subscription: scanner::ScannerSubscription, filter: &[&str]) -> Result { + scanner::scanner_subscription(self, subscription, filter) + } + // == Internal Use == #[cfg(test)] diff --git a/src/scanner.rs b/src/scanner.rs index dd47c471..8e09f071 100644 --- a/src/scanner.rs +++ b/src/scanner.rs @@ -1,4 +1,4 @@ -use crate::{messages::OutgoingMessages, Client, Error}; +use crate::{client::DataStream, messages::OutgoingMessages, Client, Error}; // Requests an XML list of scanner parameters valid in TWS. pub(super) fn scanner_parameters(client: &Client) -> Result { @@ -12,6 +12,22 @@ pub(super) fn scanner_parameters(client: &Client) -> Result { } } +pub struct ScannerSubscription {} + +pub enum Scanner { + Data(ScannerData), + End, +} + +pub struct ScannerData {} + +// impl DataStream for ScannerData { +// } + +pub(super) fn scanner_subscription(client: &Client, subscription: ScannerSubscription, filter: &[&str]) -> Result { + Err(Error::NotImplemented) +} + mod encoders { use crate::messages::OutgoingMessages; use crate::messages::RequestMessage; From 2cda654b2b802fac8d3c07101344ceb66573f560 Mon Sep 17 00:00:00 2001 From: Wil Boayue Date: Fri, 1 Nov 2024 12:58:08 -0700 Subject: [PATCH 2/7] checkpoint --- examples/scanner_subscription.rs | 46 +++++++++++++ src/client.rs | 2 +- src/scanner.rs | 107 ++++++++++++++++++++++++++++++- 3 files changed, 151 insertions(+), 4 deletions(-) create mode 100644 examples/scanner_subscription.rs diff --git a/examples/scanner_subscription.rs b/examples/scanner_subscription.rs new file mode 100644 index 00000000..b52b24f5 --- /dev/null +++ b/examples/scanner_subscription.rs @@ -0,0 +1,46 @@ +use ibapi::{scanner, Client}; + +// This example demonstrates setting up a market scanner. + +fn main() { + env_logger::init(); + + let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed"); + + let scanner_subscription = scanner::ScannerSubscription { + number_of_rows: 10, + instrument: None, + location_code: Some("STK.US.MAJOR".to_string()), + scan_code: Some("TOP_PERC_GAIN".to_string()), + above_price: 0.0, + below_price: 100.0, + above_volume: 1000000, + average_option_volume_above: 0, + market_cap_above: f64::MAX, + market_cap_below: f64::MAX, + 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: f64::MAX, + coupon_rate_below: f64::MAX, + exclude_convertible: false, + scanner_setting_pairs: None, + stock_type_filter: None, + }; + + let subscription = client.scanner_subscription(scanner_subscription, Vec::default()).expect("request scanner parameters failed"); + for scanner_data in subscription { + match scanner_data { + scanner::Scanner::Data(data) => { + println!("{:?}", data); + } + scanner::Scanner::End => { + println!("End of scanner data"); + break; + } + } + } +} diff --git a/src/client.rs b/src/client.rs index ac0b9b33..06243b68 100644 --- a/src/client.rs +++ b/src/client.rs @@ -1387,7 +1387,7 @@ impl Client { /// let parameters = client.scanner_parameters().expect("request scanner parameters failed"); /// println!("{:?}", parameters); /// ``` - pub fn scanner_subscription(&self, subscription: scanner::ScannerSubscription, filter: &[&str]) -> Result { + pub fn scanner_subscription(&self, subscription: scanner::ScannerSubscription, filter: Vec) -> Result, Error> { scanner::scanner_subscription(self, subscription, filter) } diff --git a/src/scanner.rs b/src/scanner.rs index 8e09f071..229fa65f 100644 --- a/src/scanner.rs +++ b/src/scanner.rs @@ -1,4 +1,6 @@ -use crate::{client::DataStream, messages::OutgoingMessages, Client, Error}; +use serde::{Deserialize, Serialize}; + +use crate::{client::{DataStream, Subscription}, messages::OutgoingMessages, orders::TagValue, Client, Error}; // Requests an XML list of scanner parameters valid in TWS. pub(super) fn scanner_parameters(client: &Client) -> Result { @@ -12,19 +14,118 @@ pub(super) fn scanner_parameters(client: &Client) -> Result { } } -pub struct ScannerSubscription {} +pub struct ScannerSubscription { + /// The number of rows to be returned for the query + pub number_of_rows: i32, + + /// The instrument's type for the scan. I.e. STK, FUT.HK, etc. + pub instrument: Option, + + /// The request's location (STK.US, STK.US.MAJOR, etc). + pub location_code: Option, + + /// Same as TWS Market Scanner's "parameters" field, for example: TOP_PERC_GAIN + pub scan_code: Option, + + /// Filters out Contracts which price is below this value + pub above_price: f64, + + /// Filters out contracts which price is above this value. + pub below_price: f64, + + /// Filters out Contracts which volume is above this value. + pub above_volume: i32, + + /// Filters out Contracts which option volume is above this value. + pub average_option_volume_above: i32, + + /// Filters out Contracts which market cap is above this value. + pub market_cap_above: f64, + + /// Filters out Contracts which market cap is below this value. + pub market_cap_below: f64, + + /// Filters out Contracts which Moody's rating is below this value. + pub moody_rating_above: Option, + + /// Filters out Contracts which Moody's rating is above this value. + pub moody_rating_below: Option, + + /// Filters out Contracts with a S&P rating below this value. + pub sp_rating_above: Option, + + /// Filters out Contracts with a S&P rating above this value. + pub sp_rating_below: Option, + + /// Filter out Contracts with a maturity date earlier than this value. + pub maturity_date_above: Option, + + /// Filter out Contracts with a maturity date older than this value. + pub maturity_date_below: Option, + + /// Filter out Contracts with a coupon rate lower than this value. + pub coupon_rate_above: f64, + + /// Filter out Contracts with a coupon rate higher than this value. + pub coupon_rate_below: f64, + + /// Filters out Convertible bonds + pub exclude_convertible: bool, + + /// For example, a pairing "Annual, true" used on the "top Option Implied Vol % Gainers" scan would return annualized volatilities. + pub scanner_setting_pairs: Option, + + /// CORP = Corporation, ADR = American Depositary Receipt, ETF = Exchange Traded Fund, REIT = Real Estate Investment Trust, CEF = Closed End Fund + pub stock_type_filter: Option, +} + +impl Default for ScannerSubscription { + fn default() -> Self { + ScannerSubscription { + number_of_rows: -1, + instrument: None, + location_code: None, + scan_code: None, + above_price: f64::MAX, + below_price: f64::MAX, + above_volume: i32::MAX, + average_option_volume_above: i32::MAX, + market_cap_above: f64::MAX, + market_cap_below: f64::MAX, + 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: f64::MAX, + coupon_rate_below: f64::MAX, + exclude_convertible: false, + scanner_setting_pairs: None, + stock_type_filter: None, + } + } +} +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub enum Scanner { Data(ScannerData), End, } +impl DataStream for Scanner { + fn decode(client: &Client, message: &mut crate::messages::ResponseMessage) -> Result { + Err(Error::NotImplemented) + } +} + +#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq)] pub struct ScannerData {} // impl DataStream for ScannerData { // } -pub(super) fn scanner_subscription(client: &Client, subscription: ScannerSubscription, filter: &[&str]) -> Result { +pub(super) fn scanner_subscription(client: &Client, subscription: ScannerSubscription, filter: Vec) -> Result, Error> { Err(Error::NotImplemented) } From 9e05f55d65d4e81771b2370a746a89f6156751f0 Mon Sep 17 00:00:00 2001 From: Wilfred Boayue Date: Fri, 1 Nov 2024 14:40:51 -0700 Subject: [PATCH 3/7] encode scanner subscription --- examples/scanner_subscription.rs | 28 ++------ src/client.rs | 2 +- src/lib.rs | 6 ++ src/scanner.rs | 117 ++++++++++++++++++++----------- 4 files changed, 89 insertions(+), 64 deletions(-) diff --git a/examples/scanner_subscription.rs b/examples/scanner_subscription.rs index b52b24f5..dbc8e776 100644 --- a/examples/scanner_subscription.rs +++ b/examples/scanner_subscription.rs @@ -9,31 +9,15 @@ fn main() { let scanner_subscription = scanner::ScannerSubscription { number_of_rows: 10, - instrument: None, + instrument: Some("STK".to_string()), location_code: Some("STK.US.MAJOR".to_string()), - scan_code: Some("TOP_PERC_GAIN".to_string()), - above_price: 0.0, - below_price: 100.0, - above_volume: 1000000, - average_option_volume_above: 0, - market_cap_above: f64::MAX, - market_cap_below: f64::MAX, - 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: f64::MAX, - coupon_rate_below: f64::MAX, - exclude_convertible: false, - scanner_setting_pairs: None, - stock_type_filter: None, + scan_code: Some("MOST_ACTIVE".to_string()), + ..Default::default() }; - let subscription = client.scanner_subscription(scanner_subscription, Vec::default()).expect("request scanner parameters failed"); - for scanner_data in subscription { - match scanner_data { + let subscription = client.scanner_subscription(&scanner_subscription, &Vec::default()).expect("request scanner parameters failed"); + for contract in subscription { + match contract { scanner::Scanner::Data(data) => { println!("{:?}", data); } diff --git a/src/client.rs b/src/client.rs index 06243b68..d9476926 100644 --- a/src/client.rs +++ b/src/client.rs @@ -1387,7 +1387,7 @@ impl Client { /// let parameters = client.scanner_parameters().expect("request scanner parameters failed"); /// println!("{:?}", parameters); /// ``` - pub fn scanner_subscription(&self, subscription: scanner::ScannerSubscription, filter: Vec) -> Result, Error> { + pub fn scanner_subscription(&self, subscription: &scanner::ScannerSubscription, filter: &Vec) -> Result, Error> { scanner::scanner_subscription(self, subscription, filter) } diff --git a/src/lib.rs b/src/lib.rs index 916f5b19..0f728f65 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -160,6 +160,12 @@ impl ToField for String { } } +impl ToField for Option { + fn to_field(&self) -> String { + encode_option_field(self) + } +} + impl ToField for &str { fn to_field(&self) -> String { <&str>::clone(self).to_string() diff --git a/src/scanner.rs b/src/scanner.rs index 229fa65f..bb33a2cb 100644 --- a/src/scanner.rs +++ b/src/scanner.rs @@ -1,6 +1,6 @@ use serde::{Deserialize, Serialize}; -use crate::{client::{DataStream, Subscription}, messages::OutgoingMessages, orders::TagValue, Client, Error}; +use crate::{client::{DataStream, ResponseContext, Subscription}, messages::OutgoingMessages, orders::TagValue, server_versions, Client, Error}; // Requests an XML list of scanner parameters valid in TWS. pub(super) fn scanner_parameters(client: &Client) -> Result { @@ -17,64 +17,44 @@ pub(super) fn scanner_parameters(client: &Client) -> Result { pub struct ScannerSubscription { /// The number of rows to be returned for the query pub number_of_rows: i32, - /// The instrument's type for the scan. I.e. STK, FUT.HK, etc. pub instrument: Option, - /// The request's location (STK.US, STK.US.MAJOR, etc). pub location_code: Option, - /// Same as TWS Market Scanner's "parameters" field, for example: TOP_PERC_GAIN pub scan_code: Option, - /// Filters out Contracts which price is below this value - pub above_price: f64, - + pub above_price: Option, /// Filters out contracts which price is above this value. - pub below_price: f64, - + pub below_price: Option, /// Filters out Contracts which volume is above this value. - pub above_volume: i32, - + pub above_volume: Option, /// Filters out Contracts which option volume is above this value. - pub average_option_volume_above: i32, - + pub average_option_volume_above: Option, /// Filters out Contracts which market cap is above this value. - pub market_cap_above: f64, - + pub market_cap_above: Option, /// Filters out Contracts which market cap is below this value. - pub market_cap_below: f64, - + pub market_cap_below: Option, /// Filters out Contracts which Moody's rating is below this value. pub moody_rating_above: Option, - /// Filters out Contracts which Moody's rating is above this value. pub moody_rating_below: Option, - /// Filters out Contracts with a S&P rating below this value. pub sp_rating_above: Option, - /// Filters out Contracts with a S&P rating above this value. pub sp_rating_below: Option, - /// Filter out Contracts with a maturity date earlier than this value. pub maturity_date_above: Option, - /// Filter out Contracts with a maturity date older than this value. pub maturity_date_below: Option, - /// Filter out Contracts with a coupon rate lower than this value. - pub coupon_rate_above: f64, - + pub coupon_rate_above: Option, /// Filter out Contracts with a coupon rate higher than this value. - pub coupon_rate_below: f64, - + pub coupon_rate_below: Option, /// Filters out Convertible bonds pub exclude_convertible: bool, - /// For example, a pairing "Annual, true" used on the "top Option Implied Vol % Gainers" scan would return annualized volatilities. pub scanner_setting_pairs: Option, - /// CORP = Corporation, ADR = American Depositary Receipt, ETF = Exchange Traded Fund, REIT = Real Estate Investment Trust, CEF = Closed End Fund pub stock_type_filter: Option, } @@ -86,20 +66,20 @@ impl Default for ScannerSubscription { instrument: None, location_code: None, scan_code: None, - above_price: f64::MAX, - below_price: f64::MAX, - above_volume: i32::MAX, - average_option_volume_above: i32::MAX, - market_cap_above: f64::MAX, - market_cap_below: f64::MAX, + 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: f64::MAX, - coupon_rate_below: f64::MAX, + coupon_rate_above: None, + coupon_rate_below: None, exclude_convertible: false, scanner_setting_pairs: None, stock_type_filter: None, @@ -122,18 +102,30 @@ impl DataStream for Scanner { #[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq)] pub struct ScannerData {} -// impl DataStream for ScannerData { -// } +pub(super) fn scanner_subscription<'a>(client: &'a Client, subscription: &ScannerSubscription, filter: &Vec) -> Result, 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)?; -pub(super) fn scanner_subscription(client: &Client, subscription: ScannerSubscription, filter: Vec) -> Result, Error> { - Err(Error::NotImplemented) + 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 { const VERSION: i32 = 1; @@ -144,6 +136,49 @@ mod encoders { Ok(message) } + + pub(super) fn encode_scanner_subscription(request_id: i32, server_version: i32, subscription: &ScannerSubscription, filter: &Vec) -> Result { + const VERSION: i32 = 4; + + let mut message = RequestMessage::new(); + + message.push_field(&OutgoingMessages::RequestScannerParameters); + 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(&""); // ignore subscription options + } + + Ok(message) + } } mod decoders { From 59971bb40e3b22ca1afdc78dfe9233817ac2f471 Mon Sep 17 00:00:00 2001 From: Wilfred Boayue Date: Fri, 1 Nov 2024 14:56:34 -0700 Subject: [PATCH 4/7] checkpoint --- src/scanner.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/scanner.rs b/src/scanner.rs index bb33a2cb..c52229a0 100644 --- a/src/scanner.rs +++ b/src/scanner.rs @@ -100,7 +100,13 @@ impl DataStream for Scanner { } #[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq)] -pub struct ScannerData {} +/// Provides the data resulting from the market scanner request. +pub struct ScannerData { + /// The ranking position of the contract in the scanner sort. + pub rank: i32, + /// The contract matching the scanner subscription/ + pub contract: crate::contracts::Contract, +} pub(super) fn scanner_subscription<'a>(client: &'a Client, subscription: &ScannerSubscription, filter: &Vec) -> Result, Error> { if !filter.is_empty() { From 2323b8d0041a441cb9ad56527b2dfb1b5c877948 Mon Sep 17 00:00:00 2001 From: Wil Boayue Date: Fri, 1 Nov 2024 14:57:19 -0700 Subject: [PATCH 5/7] request market scan --- src/messages.rs | 1 + src/scanner.rs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/messages.rs b/src/messages.rs index ae825b81..c5e16953 100644 --- a/src/messages.rs +++ b/src/messages.rs @@ -238,6 +238,7 @@ pub fn request_id_index(kind: IncomingMessages) -> Option { | IncomingMessages::AccountUpdateMultiEnd | IncomingMessages::MarketDepth | IncomingMessages::MarketDepthL2 + | IncomingMessages::ScannerData | IncomingMessages::TickSnapshotEnd | IncomingMessages::TickPrice | IncomingMessages::TickSize diff --git a/src/scanner.rs b/src/scanner.rs index bb33a2cb..ea984aee 100644 --- a/src/scanner.rs +++ b/src/scanner.rs @@ -142,7 +142,7 @@ mod encoders { let mut message = RequestMessage::new(); - message.push_field(&OutgoingMessages::RequestScannerParameters); + message.push_field(&OutgoingMessages::RequestScannerSubscription); if server_version < server_versions::SCANNER_GENERIC_OPTS { message.push_field(&VERSION); } From 667904953624089c3eebf3aa03f97a1fba230c9c Mon Sep 17 00:00:00 2001 From: Wilfred Boayue Date: Fri, 1 Nov 2024 15:29:34 -0700 Subject: [PATCH 6/7] decode scanner data --- examples/scanner_subscription.rs | 12 ++---- src/client.rs | 3 +- src/contracts.rs | 2 +- src/scanner.rs | 69 ++++++++++++++++++++++++++++---- 4 files changed, 68 insertions(+), 18 deletions(-) diff --git a/examples/scanner_subscription.rs b/examples/scanner_subscription.rs index dbc8e776..3479652a 100644 --- a/examples/scanner_subscription.rs +++ b/examples/scanner_subscription.rs @@ -16,15 +16,9 @@ fn main() { }; let subscription = client.scanner_subscription(&scanner_subscription, &Vec::default()).expect("request scanner parameters failed"); - for contract in subscription { - match contract { - scanner::Scanner::Data(data) => { - println!("{:?}", data); - } - scanner::Scanner::End => { - println!("End of scanner data"); - break; - } + for scan_results in subscription { + for scan_data in scan_results.iter() { + println!("{:?}", scan_data); } } } diff --git a/src/client.rs b/src/client.rs index d9476926..19376fc6 100644 --- a/src/client.rs +++ b/src/client.rs @@ -18,6 +18,7 @@ use crate::messages::{IncomingMessages, OutgoingMessages}; use crate::messages::{RequestMessage, ResponseMessage}; use crate::news::NewsArticle; use crate::orders::{CancelOrder, Executions, ExerciseOptions, Order, Orders, PlaceOrder}; +use crate::scanner::ScannerData; use crate::transport::{Connection, ConnectionMetadata, InternalSubscription, MessageBus, TcpMessageBus}; use crate::{accounts, contracts, market_data, news, orders, scanner}; @@ -1387,7 +1388,7 @@ impl Client { /// let parameters = client.scanner_parameters().expect("request scanner parameters failed"); /// println!("{:?}", parameters); /// ``` - pub fn scanner_subscription(&self, subscription: &scanner::ScannerSubscription, filter: &Vec) -> Result, Error> { + pub fn scanner_subscription(&self, subscription: &scanner::ScannerSubscription, filter: &Vec) -> Result>, Error> { scanner::scanner_subscription(self, subscription, filter) } diff --git a/src/contracts.rs b/src/contracts.rs index 0cc18075..9eae837a 100644 --- a/src/contracts.rs +++ b/src/contracts.rs @@ -292,7 +292,7 @@ pub struct DeltaNeutralContract { } /// ContractDetails provides extended contract details. -#[derive(Debug, Default)] +#[derive(Debug, Default, Clone, PartialEq, Serialize, Deserialize)] pub struct ContractDetails { /// A fully-defined Contract object. pub contract: Contract, diff --git a/src/scanner.rs b/src/scanner.rs index 3bae6894..6fd8ef06 100644 --- a/src/scanner.rs +++ b/src/scanner.rs @@ -1,6 +1,6 @@ use serde::{Deserialize, Serialize}; -use crate::{client::{DataStream, ResponseContext, Subscription}, messages::OutgoingMessages, orders::TagValue, server_versions, Client, Error}; +use crate::{client::{DataStream, ResponseContext, Subscription}, messages::{IncomingMessages, OutgoingMessages}, orders::TagValue, server_versions, Client, Error}; // Requests an XML list of scanner parameters valid in TWS. pub(super) fn scanner_parameters(client: &Client) -> Result { @@ -93,9 +93,14 @@ pub enum Scanner { End, } -impl DataStream for Scanner { - fn decode(client: &Client, message: &mut crate::messages::ResponseMessage) -> Result { - Err(Error::NotImplemented) +impl DataStream> for Vec { + fn decode(client: &Client, message: &mut crate::messages::ResponseMessage) -> Result, Error> { + match message.message_type() { + IncomingMessages::ScannerData => { + Ok(decoders::decode_scanner_data(message.clone())?) + } + _ => Err(Error::UnexpectedResponse(message.clone())), + } } } @@ -104,11 +109,13 @@ impl DataStream for Scanner { pub struct ScannerData { /// The ranking position of the contract in the scanner sort. pub rank: i32, - /// The contract matching the scanner subscription/ - pub contract: crate::contracts::Contract, + /// The contract matching the scanner subscription. + pub contract_details: crate::contracts::ContractDetails, + /// Describes the combo legs when the scanner is returning EFP. + pub leg: String } -pub(super) fn scanner_subscription<'a>(client: &'a Client, subscription: &ScannerSubscription, filter: &Vec) -> Result, Error> { +pub(super) fn scanner_subscription<'a>(client: &'a Client, subscription: &ScannerSubscription, filter: &Vec) -> Result>, Error> { if !filter.is_empty() { client.check_server_version( server_versions::SCANNER_GENERIC_OPTS, @@ -188,13 +195,61 @@ mod encoders { } mod decoders { + use crate::contracts::{SecurityType}; use crate::messages::ResponseMessage; use crate::Error; + use super::ScannerData; + pub(super) fn decode_scanner_parameters(mut message: ResponseMessage) -> Result { message.skip(); // skip message type message.skip(); // skip message version Ok(message.next_string()?) } + + pub(super) fn decode_scanner_data(mut message: ResponseMessage) -> Result, Error> { + message.skip(); // skip message type + let message_version = message.next_int()?; + message.skip(); // request id + + let number_of_elements = message.next_int()?; + let mut matches = Vec::with_capacity(number_of_elements as usize); + + for _ in 0..number_of_elements { + let mut scanner_data = ScannerData { + rank: message.next_int()?, + ..Default::default() + }; + + if message_version > 3 { + scanner_data.contract_details.contract.contract_id = message.next_int()?; + } + scanner_data.contract_details.contract.symbol = message.next_string()?; + scanner_data.contract_details.contract.security_type = SecurityType::from(&message.next_string()?); + scanner_data.contract_details.contract.last_trade_date_or_contract_month = message.next_string()?; + scanner_data.contract_details.contract.strike = message.next_double()?; + scanner_data.contract_details.contract.right = message.next_string()?; + scanner_data.contract_details.contract.exchange = message.next_string()?; + scanner_data.contract_details.contract.currency = message.next_string()?; + scanner_data.contract_details.contract.local_symbol = message.next_string()?; + scanner_data.contract_details.market_name = message.next_string()?; + scanner_data.contract_details.contract.trading_class = message.next_string()?; + + message.skip(); // distance + message.skip(); // benchmark + message.skip(); // projection + + scanner_data.leg = if message_version >= 2 { + message.next_string()? + } else { + "".to_string() + }; + + matches.push(scanner_data); + } + + Ok(matches) + } + } From 86c1799a59d2aaa0318b6c22683119817f41c7e3 Mon Sep 17 00:00:00 2001 From: Wil Boayue Date: Fri, 1 Nov 2024 15:43:18 -0700 Subject: [PATCH 7/7] cleanup example --- examples/scanner_subscription.rs | 10 +++++-- src/client.rs | 6 +++- src/scanner.rs | 49 ++++++++++++++++---------------- 3 files changed, 38 insertions(+), 27 deletions(-) diff --git a/examples/scanner_subscription.rs b/examples/scanner_subscription.rs index 3479652a..d93d1dce 100644 --- a/examples/scanner_subscription.rs +++ b/examples/scanner_subscription.rs @@ -15,10 +15,16 @@ fn main() { ..Default::default() }; - let subscription = client.scanner_subscription(&scanner_subscription, &Vec::default()).expect("request scanner parameters failed"); + let subscription = client + .scanner_subscription(&scanner_subscription, &Vec::default()) + .expect("request scanner parameters failed"); for scan_results in subscription { for scan_data in scan_results.iter() { - println!("{:?}", scan_data); + println!( + "rank: {}, contract_id: {}, symbol: {}", + scan_data.rank, scan_data.contract_details.contract.contract_id, scan_data.contract_details.contract.symbol + ); } + break; } } diff --git a/src/client.rs b/src/client.rs index 19376fc6..cf8e7efa 100644 --- a/src/client.rs +++ b/src/client.rs @@ -1388,7 +1388,11 @@ impl Client { /// let parameters = client.scanner_parameters().expect("request scanner parameters failed"); /// println!("{:?}", parameters); /// ``` - pub fn scanner_subscription(&self, subscription: &scanner::ScannerSubscription, filter: &Vec) -> Result>, Error> { + pub fn scanner_subscription( + &self, + subscription: &scanner::ScannerSubscription, + filter: &Vec, + ) -> Result>, Error> { scanner::scanner_subscription(self, subscription, filter) } diff --git a/src/scanner.rs b/src/scanner.rs index 6fd8ef06..e2a94fcc 100644 --- a/src/scanner.rs +++ b/src/scanner.rs @@ -1,6 +1,11 @@ use serde::{Deserialize, Serialize}; -use crate::{client::{DataStream, ResponseContext, Subscription}, messages::{IncomingMessages, OutgoingMessages}, orders::TagValue, server_versions, Client, Error}; +use crate::{ + client::{DataStream, ResponseContext, Subscription}, + messages::{IncomingMessages, OutgoingMessages}, + orders::TagValue, + server_versions, Client, Error, +}; // Requests an XML list of scanner parameters valid in TWS. pub(super) fn scanner_parameters(client: &Client) -> Result { @@ -87,18 +92,10 @@ impl Default for ScannerSubscription { } } -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] -pub enum Scanner { - Data(ScannerData), - End, -} - impl DataStream> for Vec { - fn decode(client: &Client, message: &mut crate::messages::ResponseMessage) -> Result, Error> { + fn decode(_client: &Client, message: &mut crate::messages::ResponseMessage) -> Result, Error> { match message.message_type() { - IncomingMessages::ScannerData => { - Ok(decoders::decode_scanner_data(message.clone())?) - } + IncomingMessages::ScannerData => Ok(decoders::decode_scanner_data(message.clone())?), _ => Err(Error::UnexpectedResponse(message.clone())), } } @@ -112,10 +109,14 @@ pub struct ScannerData { /// The contract matching the scanner subscription. pub contract_details: crate::contracts::ContractDetails, /// Describes the combo legs when the scanner is returning EFP. - pub leg: String + pub leg: String, } -pub(super) fn scanner_subscription<'a>(client: &'a Client, subscription: &ScannerSubscription, filter: &Vec) -> Result>, Error> { +pub(super) fn scanner_subscription<'a>( + client: &'a Client, + subscription: &ScannerSubscription, + filter: &Vec, +) -> Result>, Error> { if !filter.is_empty() { client.check_server_version( server_versions::SCANNER_GENERIC_OPTS, @@ -150,7 +151,12 @@ mod encoders { Ok(message) } - pub(super) fn encode_scanner_subscription(request_id: i32, server_version: i32, subscription: &ScannerSubscription, filter: &Vec) -> Result { + pub(super) fn encode_scanner_subscription( + request_id: i32, + server_version: i32, + subscription: &ScannerSubscription, + filter: &Vec, + ) -> Result { const VERSION: i32 = 4; let mut message = RequestMessage::new(); @@ -187,7 +193,7 @@ mod encoders { message.push_field(filter); } if server_version >= server_versions::LINKING { - message.push_field(&""); // ignore subscription options + message.push_field(&""); // ignore subscription options } Ok(message) @@ -195,7 +201,7 @@ mod encoders { } mod decoders { - use crate::contracts::{SecurityType}; + use crate::contracts::SecurityType; use crate::messages::ResponseMessage; use crate::Error; @@ -205,7 +211,7 @@ mod decoders { message.skip(); // skip message type message.skip(); // skip message version - Ok(message.next_string()?) + message.next_string() } pub(super) fn decode_scanner_data(mut message: ResponseMessage) -> Result, Error> { @@ -222,7 +228,7 @@ mod decoders { ..Default::default() }; - if message_version > 3 { + if message_version >= 3 { scanner_data.contract_details.contract.contract_id = message.next_int()?; } scanner_data.contract_details.contract.symbol = message.next_string()?; @@ -240,16 +246,11 @@ mod decoders { message.skip(); // benchmark message.skip(); // projection - scanner_data.leg = if message_version >= 2 { - message.next_string()? - } else { - "".to_string() - }; + scanner_data.leg = if message_version >= 2 { message.next_string()? } else { "".to_string() }; matches.push(scanner_data); } Ok(matches) } - }