From 45df44bc6dfaa01f59e388b17d6f4b23aa049b82 Mon Sep 17 00:00:00 2001 From: Wil Boayue Date: Sat, 12 Oct 2024 20:40:54 -0700 Subject: [PATCH] Implements account summary (#123) --- examples/account_summary.rs | 18 ++++ examples/pnl_single.rs | 7 +- examples/positions.rs | 6 +- src/accounts.rs | 137 ++++++++++++++++++++++++++++-- src/accounts/decoders.rs | 30 ++++--- src/accounts/decoders/tests.rs | 36 ++++---- src/accounts/encoders.rs | 22 +++-- src/accounts/encoders/tests.rs | 16 ++++ src/accounts/tests.rs | 22 ++++- src/client.rs | 36 ++++++-- src/lib.rs | 9 +- src/market_data/realtime.rs | 4 +- src/market_data/realtime/tests.rs | 2 +- src/messages.rs | 7 +- src/testdata.rs | 5 ++ src/testdata/incoming_messages.rs | 1 + src/testdata/outgoing_messages.rs | 1 + src/transport.rs | 28 ++---- src/transport/recorder/tests.rs | 2 +- 19 files changed, 305 insertions(+), 84 deletions(-) create mode 100644 examples/account_summary.rs create mode 100644 src/testdata.rs create mode 100644 src/testdata/incoming_messages.rs create mode 100644 src/testdata/outgoing_messages.rs diff --git a/examples/account_summary.rs b/examples/account_summary.rs new file mode 100644 index 00000000..a82c094f --- /dev/null +++ b/examples/account_summary.rs @@ -0,0 +1,18 @@ +use ibapi::accounts::{AccountSummaryTags, AccountUpdate}; +use ibapi::Client; + +fn main() { + let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed"); + + let group = "All"; + + let subscription = client + .account_summary(group, AccountSummaryTags::ALL) + .expect("error requesting account summary"); + for update in &subscription { + match update { + AccountUpdate::Summary(summary) => println!("{summary:?}"), + AccountUpdate::End => subscription.cancel().expect("cancel failed"), + } + } +} diff --git a/examples/pnl_single.rs b/examples/pnl_single.rs index 003dd33b..5b583fa8 100644 --- a/examples/pnl_single.rs +++ b/examples/pnl_single.rs @@ -4,7 +4,7 @@ use ibapi::Client; fn main() { env_logger::init(); - let matches = Command::new("pnl") + let matches = Command::new("pnl_single") .about("Gets realtime profit and loss updates of single contract") .arg(arg!(--connection_url ).default_value("127.0.0.1:4002")) .arg(arg!(--account ).required(true)) @@ -22,14 +22,15 @@ fn main() { // Get next item non-blocking if let Some(pnl) = subscription.try_next() { - println!("non-blocking PnL: {:?}", pnl); + println!("non-blocking PnL single: {:?}", pnl); } // Consume items blocking for next while let Some(pnl) = subscription.next() { - println!("PnL: {:?}", pnl); + println!("PnL single: {:?}", pnl); // After processing items subscription could be cancelled. subscription.cancel(); + break; } } diff --git a/examples/positions.rs b/examples/positions.rs index 341a4ce8..0f66065d 100644 --- a/examples/positions.rs +++ b/examples/positions.rs @@ -7,12 +7,16 @@ fn main() { while let Some(position_update) = positions.next() { match position_update { PositionUpdate::Position(position) => { - println!("{:4} {:4} @ {}", position.position, position.contract.symbol, position.average_cost) + println!( + "{:4} {:4} {} @ {}", + position.position, position.contract.symbol, position.contract.contract_id, position.average_cost + ) } PositionUpdate::PositionEnd => { println!("PositionEnd"); // all positions received. could continue listening for new additions or cancel. positions.cancel(); + break; } } } diff --git a/src/accounts.rs b/src/accounts.rs index 92b137bc..f6a1aa8b 100644 --- a/src/accounts.rs +++ b/src/accounts.rs @@ -19,6 +19,113 @@ use crate::{server_versions, Client, Error}; mod decoders; mod encoders; +#[derive(Debug, Default)] +/// Account information as it appears in the TWS’ Account Summary Window +pub struct AccountSummary { + /// The account identifier. + pub account: String, + /// The account’s attribute. + pub tag: String, + /// The account’s attribute’s value. + pub value: String, + /// The currency in which the value is expressed. + pub currency: String, +} + +pub struct AccountSummaryTags {} + +impl AccountSummaryTags { + pub const ACCOUNT_TYPE: &str = "AccountType"; + pub const NET_LIQUIDATION: &str = "NetLiquidation"; + pub const TOTAL_CASH_VALUE: &str = "TotalCashValue"; + pub const SETTLED_CASH: &str = "SettledCash"; + pub const ACCRUED_CASH: &str = "AccruedCash"; + pub const BUYING_POWER: &str = "BuyingPower"; + pub const EQUITY_WITH_LOAN_VALUE: &str = "EquityWithLoanValue"; + pub const PREVIOUS_DAY_EQUITY_WITH_LOAN_VALUE: &str = "PreviousDayEquityWithLoanValue"; + pub const GROSS_POSITION_VALUE: &str = "GrossPositionValue"; + pub const REQ_T_EQUITY: &str = "ReqTEquity"; + pub const REQ_T_MARGIN: &str = "ReqTMargin"; + pub const SMA: &str = "SMA"; + pub const INIT_MARGIN_REQ: &str = "InitMarginReq"; + pub const MAINT_MARGIN_REQ: &str = "MaintMarginReq"; + pub const AVAILABLE_FUNDS: &str = "AvailableFunds"; + pub const EXCESS_LIQUIDITY: &str = "ExcessLiquidity"; + pub const CUSHION: &str = "Cushion"; + pub const FULL_INIT_MARGIN_REQ: &str = "FullInitMarginReq"; + pub const FULL_MAINT_MARGIN_REQ: &str = "FullMaintMarginReq"; + pub const FULL_AVAILABLE_FUNDS: &str = "FullAvailableFunds"; + pub const FULL_EXCESS_LIQUIDITY: &str = "FullExcessLiquidity"; + pub const LOOK_AHEAD_NEXT_CHANGE: &str = "LookAheadNextChange"; + pub const LOOK_AHEAD_INIT_MARGIN_REQ: &str = "LookAheadInitMarginReq"; + pub const LOOK_AHEAD_MAINT_MARGIN_REQ: &str = "LookAheadMaintMarginReq"; + pub const LOOK_AHEAD_AVAILABLE_FUNDS: &str = "LookAheadAvailableFunds"; + pub const LOOK_AHEAD_EXCESS_LIQUIDITY: &str = "LookAheadExcessLiquidity"; + pub const HIGHEST_SEVERITY: &str = "HighestSeverity"; + pub const DAY_TRADES_REMAINING: &str = "DayTradesRemaining"; + pub const LEVERAGE: &str = "Leverage"; + + pub const ALL: &[&str] = &[ + Self::ACCOUNT_TYPE, + Self::NET_LIQUIDATION, + Self::TOTAL_CASH_VALUE, + Self::SETTLED_CASH, + Self::ACCRUED_CASH, + Self::BUYING_POWER, + Self::EQUITY_WITH_LOAN_VALUE, + Self::PREVIOUS_DAY_EQUITY_WITH_LOAN_VALUE, + Self::GROSS_POSITION_VALUE, + Self::REQ_T_EQUITY, + Self::REQ_T_MARGIN, + Self::SMA, + Self::INIT_MARGIN_REQ, + Self::MAINT_MARGIN_REQ, + Self::AVAILABLE_FUNDS, + Self::EXCESS_LIQUIDITY, + Self::CUSHION, + Self::FULL_INIT_MARGIN_REQ, + Self::FULL_MAINT_MARGIN_REQ, + Self::FULL_AVAILABLE_FUNDS, + Self::FULL_EXCESS_LIQUIDITY, + Self::LOOK_AHEAD_NEXT_CHANGE, + Self::LOOK_AHEAD_INIT_MARGIN_REQ, + Self::LOOK_AHEAD_MAINT_MARGIN_REQ, + Self::LOOK_AHEAD_AVAILABLE_FUNDS, + Self::LOOK_AHEAD_EXCESS_LIQUIDITY, + Self::HIGHEST_SEVERITY, + Self::DAY_TRADES_REMAINING, + Self::LEVERAGE, + ]; +} + +#[derive(Debug)] +pub enum AccountUpdate { + Summary(AccountSummary), + End, +} + +impl From for AccountUpdate { + fn from(val: AccountSummary) -> Self { + AccountUpdate::Summary(val) + } +} + +impl Subscribable for AccountUpdate { + const RESPONSE_MESSAGE_IDS: &[IncomingMessages] = &[IncomingMessages::AccountSummary, IncomingMessages::AccountSummaryEnd]; + + fn decode(server_version: i32, message: &mut ResponseMessage) -> Result { + match message.message_type() { + IncomingMessages::AccountSummary => Ok(AccountUpdate::Summary(decoders::decode_account_summary(server_version, message)?)), + IncomingMessages::AccountSummaryEnd => Ok(AccountUpdate::End), + message => Err(Error::Simple(format!("unexpected message: {message:?}"))), + } + } + + fn cancel_message(_server_version: i32, _request_id: Option) -> Result { + encoders::encode_cancel_positions() + } +} + // Realtime PnL update for account. #[derive(Debug, Default)] pub struct PnL { @@ -51,9 +158,9 @@ pub struct PnLSingle { /// DailyPnL for the position pub daily_pnl: f64, /// UnrealizedPnL total unrealized PnL for the position (since inception) updating in real time. - pub unrealized_pnl: Option, + pub unrealized_pnl: f64, /// Realized PnL for the position - pub realized_pnl: Option, + pub realized_pnl: f64, /// Current market value of the position pub value: f64, } @@ -176,7 +283,7 @@ pub(crate) fn positions(client: &Client) -> Result, Ok(Subscription { client, request_id: None, - responses, + subscription: responses, phantom: PhantomData, }) } @@ -198,7 +305,7 @@ pub(crate) fn positions_multi<'a>( Ok(Subscription { client, request_id: Some(request_id), - responses, + subscription: responses, phantom: PhantomData, }) } @@ -234,7 +341,7 @@ pub(crate) fn pnl<'a>(client: &'a Client, account: &str, model_code: Option<&str Ok(Subscription { client, request_id: Some(request_id), - responses, + subscription: responses, phantom: PhantomData, }) } @@ -252,7 +359,7 @@ pub(crate) fn pnl_single<'a>( contract_id: i32, model_code: Option<&str>, ) -> Result, Error> { - client.check_server_version(server_versions::PNL, "It does not support PnL requests.")?; + client.check_server_version(server_versions::REALIZED_PNL, "It does not support PnL requests.")?; let request_id = client.next_request_id(); @@ -262,7 +369,23 @@ pub(crate) fn pnl_single<'a>( Ok(Subscription { client, request_id: Some(request_id), - responses, + subscription: responses, + phantom: PhantomData, + }) +} + +pub fn account_summary<'a>(client: &'a Client, group: &str, tags: &[&str]) -> Result, Error> { + client.check_server_version(server_versions::ACCOUNT_SUMMARY, "It does not support account summary requests.")?; + + let request_id = client.next_request_id(); + + let request = encoders::encode_request_account_summary(request_id, group, tags)?; + let responses = client.send_request(request_id, request)?; + + Ok(Subscription { + client, + request_id: Some(request_id), + subscription: responses, phantom: PhantomData, }) } diff --git a/src/accounts/decoders.rs b/src/accounts/decoders.rs index e901942a..add4da19 100644 --- a/src/accounts/decoders.rs +++ b/src/accounts/decoders.rs @@ -2,7 +2,7 @@ use crate::contracts::SecurityType; use crate::messages::ResponseMessage; use crate::{server_versions, Error}; -use super::{FamilyCode, PnL, PnLSingle, Position, PositionMulti}; +use super::{AccountSummary, FamilyCode, PnL, PnLSingle, Position, PositionMulti}; pub(crate) fn decode_position(message: &mut ResponseMessage) -> Result { message.skip(); // message type @@ -112,21 +112,14 @@ pub(crate) fn decode_pnl(server_version: i32, message: &mut ResponseMessage) -> }) } -pub(crate) fn decode_pnl_single(server_version: i32, message: &mut ResponseMessage) -> Result { +pub(crate) fn decode_pnl_single(_server_version: i32, message: &mut ResponseMessage) -> Result { + message.skip(); // message type message.skip(); // request id let position = message.next_double()?; let daily_pnl = message.next_double()?; - let unrealized_pnl = if server_version >= server_versions::UNREALIZED_PNL { - Some(message.next_double()?) - } else { - None - }; - let realized_pnl = if server_version >= server_versions::REALIZED_PNL { - Some(message.next_double()?) - } else { - None - }; + let unrealized_pnl = message.next_double()?; + let realized_pnl = message.next_double()?; let value = message.next_double()?; Ok(PnLSingle { @@ -138,5 +131,18 @@ pub(crate) fn decode_pnl_single(server_version: i32, message: &mut ResponseMessa }) } +pub(crate) fn decode_account_summary(_server_version: i32, message: &mut ResponseMessage) -> Result { + message.skip(); // message type + message.skip(); // version + message.skip(); // request id + + Ok(AccountSummary { + account: message.next_string()?, + tag: message.next_string()?, + value: message.next_string()?, + currency: message.next_string()?, + }) +} + #[cfg(test)] mod tests; diff --git a/src/accounts/decoders/tests.rs b/src/accounts/decoders/tests.rs index 10bb2804..9b640832 100644 --- a/src/accounts/decoders/tests.rs +++ b/src/accounts/decoders/tests.rs @@ -1,4 +1,4 @@ -use crate::server_versions; +use crate::{accounts::AccountSummaryTags, server_versions}; #[test] fn test_decode_positions() { @@ -97,27 +97,25 @@ fn test_decode_pnl() { #[test] fn test_decode_pnl_single() { - let mut message = super::ResponseMessage::from("94\09000\00.1\00.2\00.3\0"); - - let pnl = super::decode_pnl_single(server_versions::REALIZED_PNL, &mut message).expect("error decoding pnl"); + let mut message = super::ResponseMessage::from("95\09000\0100.0\00.1\00.2\00.3\00.4\0"); - assert_eq!(pnl.daily_pnl, 0.10, "pnl.daily_pnl"); - assert_eq!(pnl.unrealized_pnl, Some(0.20), "pnl.unrealized_pnl"); - assert_eq!(pnl.realized_pnl, Some(0.30), "pnl.realized_pnl"); + let pnl_single = super::decode_pnl_single(server_versions::REALIZED_PNL, &mut message).expect("error decoding pnl single"); - let mut message = super::ResponseMessage::from("94\09000\00.1\00.2\00.3\0"); - - let pnl = super::decode_pnl_single(server_versions::UNREALIZED_PNL, &mut message).expect("error decoding pnl"); - - assert_eq!(pnl.daily_pnl, 0.10, "pnl.daily_pnl"); - assert_eq!(pnl.unrealized_pnl, Some(0.20), "pnl.unrealized_pnl"); - assert_eq!(pnl.realized_pnl, None, "pnl.realized_pnl"); + assert_eq!(pnl_single.position, 100., "pnl_single.position"); + assert_eq!(pnl_single.daily_pnl, 0.10, "pnl_single.daily_pnl"); + assert_eq!(pnl_single.unrealized_pnl, 0.20, "pnl_single.unrealized_pnl"); + assert_eq!(pnl_single.realized_pnl, 0.30, "pnl_single.realized_pnl"); + assert_eq!(pnl_single.value, 0.40, "pnl_single.value"); +} - let mut message = super::ResponseMessage::from("94\09000\00.1\00.2\00.3\0"); +#[test] +fn test_decode_account_summary() { + let mut message = super::ResponseMessage::from("94\01\09000\0DU1234567\0AccountType\0FA\0"); - let pnl = super::decode_pnl_single(server_versions::PNL, &mut message).expect("error decoding pnl"); + let account_summary = super::decode_account_summary(server_versions::REALIZED_PNL, &mut message).expect("error decoding pnl"); - assert_eq!(pnl.daily_pnl, 0.10, "pnl.daily_pnl"); - assert_eq!(pnl.unrealized_pnl, None, "pnl.unrealized_pnl"); - assert_eq!(pnl.realized_pnl, None, "pnl.realized_pnl"); + assert_eq!(account_summary.account, "DU1234567", "account_summary.account"); + assert_eq!(account_summary.tag, AccountSummaryTags::ACCOUNT_TYPE, "account_summary.tag"); + assert_eq!(account_summary.value, "FA", "account_summary.value"); + assert_eq!(account_summary.currency, "", "account_summary.currency"); } diff --git a/src/accounts/encoders.rs b/src/accounts/encoders.rs index 735c0f11..d796afc8 100644 --- a/src/accounts/encoders.rs +++ b/src/accounts/encoders.rs @@ -61,13 +61,7 @@ pub(crate) fn encode_request_pnl_single(request_id: i32, account: &str, contract message.push_field(&OutgoingMessages::RequestPnLSingle); message.push_field(&request_id); message.push_field(&account); - - if let Some(model_code) = model_code { - message.push_field(&model_code); - } else { - message.push_field(&""); - } - + message.push_field(&model_code); message.push_field(&contract_id); Ok(message) @@ -77,6 +71,20 @@ pub(crate) fn encode_cancel_pnl_single(request_id: i32) -> Result Result { + const VERSION: i32 = 1; + + let mut message = RequestMessage::new(); + + message.push_field(&OutgoingMessages::RequestAccountSummary); + message.push_field(&VERSION); + message.push_field(&request_id); + message.push_field(&group); + message.push_field(&tags.join(",")); + + Ok(message) +} + fn encode_simple(message_type: OutgoingMessages, version: i32) -> Result { let mut message = RequestMessage::new(); diff --git a/src/accounts/encoders/tests.rs b/src/accounts/encoders/tests.rs index 9d39b635..d090f3fb 100644 --- a/src/accounts/encoders/tests.rs +++ b/src/accounts/encoders/tests.rs @@ -83,3 +83,19 @@ fn test_encode_request_pnl_single() { assert_eq!(request[3], "", "message.model_code"); assert_eq!(request[4], contract_id.to_field(), "message.contract_id"); } + +#[test] +fn test_encode_request_account_summary() { + let version = 1; + let request_id = 3000; + let group = "All"; + let tags: &[&str] = &["AccountType", "TotalCashValue"]; + + let request = super::encode_request_account_summary(request_id, group, tags).expect("encode request pnl failed"); + + assert_eq!(request[0], OutgoingMessages::RequestAccountSummary.to_field(), "message.type"); + assert_eq!(request[1], version.to_field(), "message.version"); + assert_eq!(request[2], request_id.to_field(), "message.request_id"); + assert_eq!(request[3], group.to_field(), "message.group"); + assert_eq!(request[4], tags.join(","), "message.tags"); +} diff --git a/src/accounts/tests.rs b/src/accounts/tests.rs index c509bf1c..f7b704d1 100644 --- a/src/accounts/tests.rs +++ b/src/accounts/tests.rs @@ -1,6 +1,6 @@ use std::sync::{Arc, Mutex, RwLock}; -use crate::{server_versions, stubs::MessageBusStub, Client}; +use crate::{accounts::AccountSummaryTags, server_versions, stubs::MessageBusStub, Client}; #[test] fn test_pnl() { @@ -91,3 +91,23 @@ fn test_positions_multi() { assert_eq!(request_messages[2].encode_simple(), "74|1|9001||TARGET2024|"); assert_eq!(request_messages[3].encode_simple(), "75|1|9001|"); } + +#[test] +fn test_account_summary() { + let message_bus = Arc::new(Mutex::new(MessageBusStub { + request_messages: RwLock::new(vec![]), + response_messages: vec![], + })); + + let client = Client::stubbed(message_bus, server_versions::SIZE_RULES); + + let group = "All"; + let tags = &[AccountSummaryTags::ACCOUNT_TYPE]; + + let _ = client.account_summary(group, tags).expect("request account summary failed"); + + let request_messages = client.message_bus.lock().unwrap().request_messages(); + + assert_eq!(request_messages[0].encode_simple(), "62|1|9000|All|AccountType|"); + assert_eq!(request_messages[1].encode_simple(), "64|1|"); +} diff --git a/src/client.rs b/src/client.rs index 2458013a..f3ca5c9f 100644 --- a/src/client.rs +++ b/src/client.rs @@ -11,7 +11,7 @@ use time::macros::format_description; use time::OffsetDateTime; use time_tz::{timezones, OffsetResult, PrimitiveDateTimeExt, Tz}; -use crate::accounts::{FamilyCode, PnL, PnLSingle, PositionUpdate, PositionUpdateMulti}; +use crate::accounts::{AccountUpdate, FamilyCode, PnL, PnLSingle, PositionUpdate, PositionUpdateMulti}; use crate::contracts::Contract; use crate::errors::Error; use crate::market_data::historical; @@ -305,6 +305,31 @@ impl Client { accounts::pnl_single(self, account, contract_id, model_code) } + /// Requests a specific account’s summary. Subscribes to the account summary as presented in the TWS’ Account Summary tab. Data received is specified by using a specific tags value. + /// + /// # Arguments + /// * `group` - Set to “All” to return account summary data for all accounts, or set to a specific Advisor Account Group name that has already been created in TWS Global Configuration. + /// * `tags` - List of the desired tags. + /// + /// # Examples + /// + /// ```no_run + /// use ibapi::Client; + /// use ibapi::accounts::AccountSummaryTags; + /// + /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed"); + /// + /// let group = "All"; + /// + /// let subscription = client.account_summary(group, AccountSummaryTags::ALL).expect("error requesting pnl"); + /// for summary in &subscription { + /// println!("{summary:?}") + /// } + /// ``` + pub fn account_summary<'a>(&'a self, group: &str, tags: &[&str]) -> Result, Error> { + accounts::account_summary(self, group, tags) + } + // === Contracts === /// Requests contract information. @@ -1039,7 +1064,7 @@ impl Debug for Client { pub struct Subscription<'a, T: Subscribable> { pub(crate) client: &'a Client, pub(crate) request_id: Option, - pub(crate) responses: InternalSubscription, + pub(crate) subscription: InternalSubscription, pub(crate) phantom: PhantomData, } @@ -1048,7 +1073,7 @@ impl<'a, T: Subscribable> Subscription<'a, T> { /// Blocks until the item become available. pub fn next(&self) -> Option { loop { - if let Some(mut message) = self.responses.next() { + if let Some(mut message) = self.subscription.next() { if T::RESPONSE_MESSAGE_IDS.contains(&message.message_type()) { match T::decode(self.client.server_version(), &mut message) { Ok(val) => return Some(val), @@ -1081,7 +1106,7 @@ impl<'a, T: Subscribable> Subscription<'a, T> { /// //} /// ``` pub fn try_next(&self) -> Option { - if let Some(mut message) = self.responses.try_next() { + if let Some(mut message) = self.subscription.try_next() { if message.message_type() == IncomingMessages::Error { error!("{}", message.peek_string(4)); return None; @@ -1111,7 +1136,7 @@ impl<'a, T: Subscribable> Subscription<'a, T> { /// //} /// ``` pub fn next_timeout(&self, timeout: Duration) -> Option { - if let Some(mut message) = self.responses.next_timeout(timeout) { + if let Some(mut message) = self.subscription.next_timeout(timeout) { if message.message_type() == IncomingMessages::Error { error!("{}", message.peek_string(4)); return None; @@ -1133,6 +1158,7 @@ impl<'a, T: Subscribable> Subscription<'a, T> { pub fn cancel(&self) -> Result<(), Error> { if let Ok(message) = T::cancel_message(self.client.server_version(), self.request_id) { self.client.send_message(message)?; + self.subscription.cancel()?; } Ok(()) } diff --git a/src/lib.rs b/src/lib.rs index 37783cab..ff56311e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -122,15 +122,18 @@ pub mod orders; mod server_versions; -#[cfg(test)] -pub(crate) mod stubs; - #[doc(inline)] pub use errors::Error; #[doc(inline)] pub use client::Client; +#[cfg(test)] +pub(crate) mod stubs; + +#[cfg(test)] +pub(crate) mod testdata; + // ToField pub(crate) trait ToField { diff --git a/src/market_data/realtime.rs b/src/market_data/realtime.rs index a5feab09..9387712e 100644 --- a/src/market_data/realtime.rs +++ b/src/market_data/realtime.rs @@ -202,7 +202,7 @@ pub(crate) fn realtime_bars<'a>( Ok(Subscription { client, request_id: Some(request_id), - responses, + subscription: responses, phantom: PhantomData, }) } @@ -305,7 +305,7 @@ pub(crate) fn tick_by_tick_midpoint<'a>( Ok(Subscription { client, request_id: Some(request_id), - responses, + subscription: responses, phantom: PhantomData, }) } diff --git a/src/market_data/realtime/tests.rs b/src/market_data/realtime/tests.rs index 06949b7e..2431b60e 100644 --- a/src/market_data/realtime/tests.rs +++ b/src/market_data/realtime/tests.rs @@ -28,7 +28,7 @@ fn realtime_bars() { assert!(bars.is_ok(), "failed to request realtime bars: {}", bars.err().unwrap()); // Verify Responses - let mut bars = bars.unwrap(); + let bars = bars.unwrap(); if let Some(bar) = bars.next() { let timestamp = OffsetDateTime::from_unix_timestamp(1678323335).unwrap(); diff --git a/src/messages.rs b/src/messages.rs index 8e2a8063..0693c5a3 100644 --- a/src/messages.rs +++ b/src/messages.rs @@ -211,7 +211,12 @@ pub fn request_id_index(kind: IncomingMessages) -> Option { | IncomingMessages::HistoricalTickLast | IncomingMessages::PnL | IncomingMessages::PnLSingle => Some(1), - IncomingMessages::ContractDataEnd | IncomingMessages::RealTimeBars | IncomingMessages::Error | IncomingMessages::ExecutionDataEnd => Some(2), + IncomingMessages::ContractDataEnd + | IncomingMessages::RealTimeBars + | IncomingMessages::Error + | IncomingMessages::ExecutionDataEnd + | IncomingMessages::AccountSummary + | IncomingMessages::AccountSummaryEnd => Some(2), _ => { error!("could not determine request id index for {kind:?}"); None diff --git a/src/testdata.rs b/src/testdata.rs new file mode 100644 index 00000000..9281cd28 --- /dev/null +++ b/src/testdata.rs @@ -0,0 +1,5 @@ +#[cfg(test)] +pub(crate) mod incoming_messages; + +#[cfg(test)] +pub(crate) mod outgoing_messages; diff --git a/src/testdata/incoming_messages.rs b/src/testdata/incoming_messages.rs new file mode 100644 index 00000000..e9bec906 --- /dev/null +++ b/src/testdata/incoming_messages.rs @@ -0,0 +1 @@ +const POSITION_RESPONSE: &str = "61\03\0DU1234567\076792991\0TSLA\0STK\0\00.0\0\0\0NASDAQ\0USD\0TSLA\0NMS\0500\0196.77\0"; diff --git a/src/testdata/outgoing_messages.rs b/src/testdata/outgoing_messages.rs new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/src/testdata/outgoing_messages.rs @@ -0,0 +1 @@ + diff --git a/src/transport.rs b/src/transport.rs index 4180b607..713eb6c4 100644 --- a/src/transport.rs +++ b/src/transport.rs @@ -595,34 +595,20 @@ impl InternalSubscription { } } + pub(crate) fn cancel(&self) -> Result<(), Error> { + Ok(()) + } + fn receive(receiver: &Receiver) -> Option { - match receiver.recv() { - Ok(message) => Some(message), - Err(err) => { - error!("error receiving message: {err}"); - None - } - } + receiver.recv().ok() } fn try_receive(receiver: &Receiver) -> Option { - match receiver.try_recv() { - Ok(message) => Some(message), - Err(err) => { - error!("error receiving message: {err}"); - None - } - } + receiver.try_recv().ok() } fn timeout_receive(receiver: &Receiver, timeout: Duration) -> Option { - match receiver.recv_timeout(timeout) { - Ok(message) => Some(message), - Err(err) => { - error!("error receiving message: {err}"); - None - } - } + receiver.recv_timeout(timeout).ok() } } diff --git a/src/transport/recorder/tests.rs b/src/transport/recorder/tests.rs index 556a3c27..038826b5 100644 --- a/src/transport/recorder/tests.rs +++ b/src/transport/recorder/tests.rs @@ -21,7 +21,7 @@ fn recorder_is_disabled() { env::set_var(&key, &""); - let recorder = MessageRecorder::new(); + let _recorder = MessageRecorder::new(); // assert_eq!(false, recorder.enabled); // assert_eq!("", &recorder.recording_dir);