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;