Skip to main content

ibapi/accounts/
sync.rs

1//! Synchronous implementation of account management functionality
2
3use time::OffsetDateTime;
4
5use crate::client::blocking::{ClientRequestBuilders, SharesChannel, Subscription};
6use crate::messages::OutgoingMessages;
7use crate::protocol::{check_version, Features};
8use crate::{client::sync::Client, Error};
9
10use super::common::{decoders, encoders};
11use super::types::{AccountGroup, AccountId, ContractId, ModelCode};
12use super::*;
13
14// Implement SharesChannel for PositionUpdate subscription
15impl SharesChannel for Subscription<PositionUpdate> {}
16
17impl Client {
18    /// TWS's current time. TWS is synchronized with the server (not local computer) using NTP and this function will receive the current time in TWS.
19    ///
20    /// # Examples
21    ///
22    /// ```no_run
23    /// use ibapi::client::blocking::Client;
24    ///
25    /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
26    /// let server_time = client.server_time().expect("error requesting server time");
27    /// println!("server time: {server_time:?}");
28    /// ```
29    pub fn server_time(&self) -> Result<OffsetDateTime, Error> {
30        crate::common::request_helpers::blocking::one_shot_with_retry(
31            self,
32            OutgoingMessages::RequestCurrentTime,
33            encoders::encode_request_server_time,
34            decoders::decode_server_time,
35            || Err(Error::UnexpectedEndOfStream),
36        )
37    }
38
39    /// Requests the current server time with millisecond precision.
40    pub fn server_time_millis(&self) -> Result<OffsetDateTime, Error> {
41        check_version(self.server_version, Features::CURRENT_TIME_IN_MILLIS)?;
42
43        crate::common::request_helpers::blocking::one_shot_with_retry(
44            self,
45            OutgoingMessages::RequestCurrentTimeInMillis,
46            encoders::encode_request_server_time_millis,
47            decoders::decode_server_time_millis,
48            || Err(Error::UnexpectedEndOfStream),
49        )
50    }
51
52    /// Subscribes to [PositionUpdate]s for all accessible accounts.
53    /// All positions sent initially, and then only updates as positions change.
54    ///
55    /// # Examples
56    ///
57    /// ```no_run
58    /// use ibapi::client::blocking::Client;
59    /// use ibapi::accounts::PositionUpdate;
60    ///
61    /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
62    /// let subscription = client.positions().expect("error requesting positions");
63    /// for position_response in subscription.iter_data() {
64    ///     match position_response? {
65    ///         PositionUpdate::Position(position) => println!("{position:?}"),
66    ///         PositionUpdate::PositionEnd => println!("initial set of positions received"),
67    ///     }
68    /// }
69    /// # Ok::<(), ibapi::Error>(())
70    /// ```
71    pub fn positions(&self) -> Result<Subscription<PositionUpdate>, Error> {
72        crate::common::request_helpers::blocking::shared_subscription(
73            self,
74            Features::POSITIONS,
75            OutgoingMessages::RequestPositions,
76            encoders::encode_request_positions,
77        )
78    }
79
80    /// Subscribes to [PositionUpdateMulti] updates for account and/or model.
81    /// Initially all positions are returned, and then updates are returned for any position changes in real time.
82    ///
83    /// # Arguments
84    /// * `account`    - If an account Id is provided, only the account's positions belonging to the specified model will be delivered.
85    /// * `model_code` - The code of the model's positions we are interested in.
86    ///
87    /// # Examples
88    ///
89    /// ```no_run
90    /// use ibapi::client::blocking::Client;
91    ///
92    /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
93    ///
94    /// use ibapi::accounts::types::AccountId;
95    ///
96    /// let account = AccountId("U1234567".to_string());
97    /// let subscription = client.positions_multi(Some(&account), None).expect("error requesting positions by model");
98    /// for position in subscription.iter() {
99    ///     println!("{position:?}")
100    /// }
101    /// ```
102    pub fn positions_multi(&self, account: Option<&AccountId>, model_code: Option<&ModelCode>) -> Result<Subscription<PositionUpdateMulti>, Error> {
103        check_version(self.server_version(), Features::MODELS_SUPPORT)?;
104
105        let builder = self.request();
106        let request = encoders::encode_request_positions_multi(builder.request_id(), account, model_code)?;
107
108        builder.send(request)
109    }
110
111    /// Creates subscription for real time daily PnL and unrealized PnL updates.
112    ///
113    /// # Arguments
114    /// * `account`    - account for which to receive PnL updates
115    /// * `model_code` - specify to request PnL updates for a specific model
116    ///
117    /// # Examples
118    ///
119    /// ```no_run
120    /// use ibapi::client::blocking::Client;
121    ///
122    /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
123    /// use ibapi::accounts::types::AccountId;
124    ///
125    /// let account = AccountId("account id".to_string());
126    /// let subscription = client.pnl(&account, None).expect("error requesting pnl");
127    /// for pnl in subscription.iter() {
128    ///     println!("{pnl:?}")
129    /// }
130    /// ```
131    pub fn pnl(&self, account: &AccountId, model_code: Option<&ModelCode>) -> Result<Subscription<PnL>, Error> {
132        crate::common::request_helpers::blocking::request_with_id(self, Features::PNL, |id| encoders::encode_request_pnl(id, account, model_code))
133    }
134
135    /// Requests real time updates for daily PnL of individual positions.
136    ///
137    /// # Arguments
138    /// * `account`     - Account in which position exists
139    /// * `contract_id` - Contract ID of contract to receive daily PnL updates for. Note: does not return response if invalid conId is entered.
140    /// * `model_code`  - Model in which position exists
141    ///
142    /// # Examples
143    ///
144    /// ```no_run
145    /// use ibapi::client::blocking::Client;
146    ///
147    /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
148    ///
149    /// use ibapi::accounts::types::{AccountId, ContractId};
150    ///
151    /// let account = AccountId("<account id>".to_string());
152    /// let contract_id = ContractId(1001);
153    ///
154    /// let subscription = client.pnl_single(&account, contract_id, None).expect("error requesting pnl");
155    /// for pnl in &subscription {
156    ///     println!("{pnl:?}")
157    /// }
158    /// ```
159    pub fn pnl_single(&self, account: &AccountId, contract_id: ContractId, model_code: Option<&ModelCode>) -> Result<Subscription<PnLSingle>, Error> {
160        crate::common::request_helpers::blocking::request_with_id(self, Features::REALIZED_PNL, |id| {
161            encoders::encode_request_pnl_single(id, account, contract_id, model_code)
162        })
163    }
164
165    /// 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.
166    ///
167    /// # Arguments
168    /// * `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.
169    /// * `tags`  - List of the desired tags.
170    ///
171    /// # Examples
172    ///
173    /// ```no_run
174    /// use ibapi::client::blocking::Client;
175    /// use ibapi::accounts::AccountSummaryTags;
176    /// use ibapi::accounts::types::AccountGroup;
177    ///
178    /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
179    ///
180    /// let group = AccountGroup("All".to_string());
181    ///
182    /// let subscription = client.account_summary(&group, &[AccountSummaryTags::ACCOUNT_TYPE]).expect("error requesting account summary");
183    /// for summary in &subscription {
184    ///     println!("{summary:?}")
185    /// }
186    /// ```
187    pub fn account_summary(&self, group: &AccountGroup, tags: &[&str]) -> Result<Subscription<AccountSummaryResult>, Error> {
188        crate::common::request_helpers::blocking::request_with_id(self, Features::ACCOUNT_SUMMARY, |id| {
189            encoders::encode_request_account_summary(id, group, tags)
190        })
191    }
192
193    /// Subscribes to a specific account's information and portfolio.
194    ///
195    /// All account values and positions will be returned initially, and then there will only be updates when there is a change in a position, or to an account value every 3 minutes if it has changed. Only one account can be subscribed at a time.
196    ///
197    /// # Arguments
198    /// * `account` - The account id (i.e. U1234567) for which the information is requested.
199    ///
200    /// # Examples
201    ///
202    /// ```no_run
203    /// use ibapi::client::blocking::Client;
204    /// use ibapi::accounts::AccountUpdate;
205    ///
206    /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
207    ///
208    /// use ibapi::accounts::types::AccountId;
209    ///
210    /// let account = AccountId("U1234567".to_string());
211    ///
212    /// let subscription = client.account_updates(&account).expect("error requesting account updates");
213    /// for update in subscription.iter_data() {
214    ///     let update = update?;
215    ///     println!("{update:?}");
216    ///
217    ///     // stop after full initial update
218    ///     if let AccountUpdate::End = update {
219    ///         subscription.cancel();
220    ///     }
221    /// }
222    /// # Ok::<(), ibapi::Error>(())
223    /// ```
224    pub fn account_updates(&self, account: &AccountId) -> Result<Subscription<AccountUpdate>, Error> {
225        crate::common::request_helpers::blocking::shared_request(self, OutgoingMessages::RequestAccountData, || {
226            encoders::encode_request_account_updates(true, account)
227        })
228    }
229
230    /// Requests account updates for account and/or model.
231    ///
232    /// All account values and positions will be returned initially, and then there will only be updates when there is a change in a position, or to an account value every 3 minutes if it has changed. Only one account can be subscribed at a time.
233    ///
234    /// # Arguments
235    /// * `account`        - Account values can be requested for a particular account.
236    /// * `model_code`     - Account values can also be requested for a model.
237    ///
238    /// # Examples
239    ///
240    /// ```no_run
241    /// use ibapi::client::blocking::Client;
242    /// use ibapi::accounts::AccountUpdateMulti;
243    ///
244    /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
245    ///
246    /// use ibapi::accounts::types::AccountId;
247    ///
248    /// let account = AccountId("U1234567".to_string());
249    ///
250    /// let subscription = client.account_updates_multi(Some(&account), None).expect("error requesting account updates multi");
251    /// for update in subscription.iter_data() {
252    ///     let update = update?;
253    ///     println!("{update:?}");
254    ///
255    ///     // stop after full initial update
256    ///     if let AccountUpdateMulti::End = update {
257    ///         subscription.cancel();
258    ///     }
259    /// }
260    /// # Ok::<(), ibapi::Error>(())
261    /// ```
262    pub fn account_updates_multi(
263        &self,
264        account: Option<&AccountId>,
265        model_code: Option<&ModelCode>,
266    ) -> Result<Subscription<AccountUpdateMulti>, Error> {
267        check_version(self.server_version(), Features::MODELS_SUPPORT)?;
268
269        let builder = self.request();
270        let request = encoders::encode_request_account_updates_multi(builder.request_id(), account, model_code)?;
271
272        builder.send(request)
273    }
274
275    /// Requests the accounts to which the logged user has access to.
276    ///
277    /// # Examples
278    ///
279    /// ```no_run
280    /// use ibapi::client::blocking::Client;
281    ///
282    /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
283    ///
284    /// let accounts = client.managed_accounts().expect("error requesting managed accounts");
285    /// println!("managed accounts: {accounts:?}")
286    /// ```
287    pub fn managed_accounts(&self) -> Result<Vec<String>, Error> {
288        crate::common::request_helpers::blocking::one_shot_with_retry(
289            self,
290            OutgoingMessages::RequestManagedAccounts,
291            encoders::encode_request_managed_accounts,
292            decoders::decode_managed_accounts,
293            || Ok(Vec::default()),
294        )
295    }
296
297    /// Get current [FamilyCode]s for all accessible accounts.
298    pub fn family_codes(&self) -> Result<Vec<FamilyCode>, Error> {
299        crate::common::request_helpers::blocking::one_shot_request(
300            self,
301            Features::FAMILY_CODES,
302            OutgoingMessages::RequestFamilyCodes,
303            encoders::encode_request_family_codes,
304            decoders::decode_family_codes,
305            Vec::default,
306        )
307    }
308
309    /// Request the configured soft dollar tiers available to the account.
310    ///
311    /// # Examples
312    ///
313    /// ```no_run
314    /// use ibapi::client::blocking::Client;
315    ///
316    /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
317    ///
318    /// let tiers = client.soft_dollar_tiers().expect("request failed");
319    /// for tier in &tiers {
320    ///     println!("{}: {}", tier.name, tier.display_name);
321    /// }
322    /// ```
323    pub fn soft_dollar_tiers(&self) -> Result<Vec<crate::orders::SoftDollarTier>, Error> {
324        check_version(self.server_version, Features::SOFT_DOLLAR_TIER)?;
325
326        crate::common::request_helpers::blocking::one_shot_request_with_retry(
327            self,
328            encoders::encode_request_soft_dollar_tiers,
329            decoders::decode_soft_dollar_tiers_message,
330            || Err(Error::UnexpectedEndOfStream),
331        )
332    }
333
334    /// Request white-branding identity information for the logged-in user.
335    ///
336    /// # Examples
337    ///
338    /// ```no_run
339    /// use ibapi::client::blocking::Client;
340    ///
341    /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
342    ///
343    /// let info = client.user_info().expect("request failed");
344    /// println!("white branding id: {}", info.white_branding_id);
345    /// ```
346    pub fn user_info(&self) -> Result<UserInfo, Error> {
347        check_version(self.server_version, Features::USER_INFO)?;
348
349        crate::common::request_helpers::blocking::one_shot_request_with_retry(
350            self,
351            encoders::encode_request_user_info,
352            decoders::decode_user_info_message,
353            || Err(Error::UnexpectedEndOfStream),
354        )
355    }
356
357    /// Request the current Financial Advisor configuration as an XML string.
358    ///
359    /// # Arguments
360    /// * `fa_data_type` - which FA dataset to fetch.
361    ///
362    /// # Examples
363    ///
364    /// ```no_run
365    /// use ibapi::client::blocking::Client;
366    /// use ibapi::accounts::FaDataType;
367    ///
368    /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
369    ///
370    /// let cfg = client.request_fa(FaDataType::Groups).expect("request failed");
371    /// println!("{}", cfg.xml);
372    /// ```
373    pub fn request_fa(&self, fa_data_type: FaDataType) -> Result<FaConfig, Error> {
374        crate::common::request_helpers::blocking::one_shot_with_retry(
375            self,
376            OutgoingMessages::RequestFA,
377            || encoders::encode_request_fa(fa_data_type as i32),
378            decoders::decode_receive_fa,
379            || Err(Error::UnexpectedEndOfStream),
380        )
381    }
382
383    /// Replace the Financial Advisor configuration on the server.
384    ///
385    /// # Arguments
386    /// * `fa_data_type` - which FA dataset to replace.
387    /// * `xml`          - the replacement configuration as an XML string.
388    ///
389    /// # Examples
390    ///
391    /// ```no_run
392    /// use ibapi::client::blocking::Client;
393    /// use ibapi::accounts::FaDataType;
394    ///
395    /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
396    ///
397    /// let result = client.replace_fa(FaDataType::Groups, "<xml/>").expect("request failed");
398    /// println!("{}", result.text);
399    /// ```
400    pub fn replace_fa(&self, fa_data_type: FaDataType, xml: &str) -> Result<ReplaceFaResult, Error> {
401        check_version(self.server_version, Features::REPLACE_FA_END)?;
402
403        crate::common::request_helpers::blocking::one_shot_request_with_retry(
404            self,
405            |request_id| encoders::encode_replace_fa(request_id, fa_data_type as i32, xml),
406            decoders::decode_replace_fa_end_message,
407            || Err(Error::UnexpectedEndOfStream),
408        )
409    }
410
411    /// Set the verbosity level for server-side TWS API diagnostics.
412    ///
413    /// # Examples
414    ///
415    /// ```no_run
416    /// use ibapi::client::blocking::Client;
417    /// use ibapi::accounts::ServerLogLevel;
418    ///
419    /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
420    ///
421    /// client.set_server_log_level(ServerLogLevel::Detail).expect("request failed");
422    /// ```
423    pub fn set_server_log_level(&self, log_level: ServerLogLevel) -> Result<(), Error> {
424        let message = encoders::encode_set_server_log_level(log_level as i32)?;
425        self.send_message(message)?;
426        Ok(())
427    }
428
429    /// Initiate a TWS extension verification handshake.
430    ///
431    /// Most users will not call this directly; it is part of the IB Linking flow.
432    ///
433    /// # Examples
434    ///
435    /// ```no_run
436    /// use ibapi::client::blocking::Client;
437    ///
438    /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
439    ///
440    /// let challenge = client.verify_request("MyApp", "1.0").expect("request failed");
441    /// println!("{}", challenge.api_data);
442    /// ```
443    pub fn verify_request(&self, api_name: &str, api_version: &str) -> Result<VerificationChallenge, Error> {
444        check_version(self.server_version, Features::LINKING)?;
445
446        crate::common::request_helpers::blocking::one_shot_with_retry(
447            self,
448            OutgoingMessages::VerifyRequest,
449            || encoders::encode_verify_request(api_name, api_version),
450            decoders::decode_verify_message_api,
451            || Err(Error::UnexpectedEndOfStream),
452        )
453    }
454
455    /// Continue a TWS extension verification handshake by sending the API response data.
456    ///
457    /// # Examples
458    ///
459    /// ```no_run
460    /// use ibapi::client::blocking::Client;
461    ///
462    /// let client = Client::connect("127.0.0.1:4002", 100).expect("connection failed");
463    ///
464    /// let result = client.verify_message("signed-challenge").expect("request failed");
465    /// if result.is_successful {
466    ///     println!("verified");
467    /// } else {
468    ///     eprintln!("{}", result.error_text);
469    /// }
470    /// ```
471    pub fn verify_message(&self, api_data: &str) -> Result<VerificationResult, Error> {
472        check_version(self.server_version, Features::LINKING)?;
473
474        crate::common::request_helpers::blocking::one_shot_with_retry(
475            self,
476            OutgoingMessages::VerifyMessage,
477            || encoders::encode_verify_message(api_data),
478            decoders::decode_verify_completed,
479            || Err(Error::UnexpectedEndOfStream),
480        )
481    }
482}
483
484#[cfg(test)]
485#[path = "sync_tests.rs"]
486mod tests;