1use std::collections::HashMap;
2use std::marker::PhantomData;
3use std::str::FromStr;
4use std::sync::Arc;
5use std::time::Duration;
6
7use async_trait::async_trait;
8use derive_setters::Setters;
9use serde::{Deserialize, Serialize};
10use serde_json::{from_value, to_value, Value};
11use thiserror::Error;
12use tokio::spawn;
13use tokio::sync::Mutex;
14use tokio::task::JoinHandle;
15use tokio::time::sleep;
16use tracing::{debug, error};
17use url::Url;
18
19use crate::{BasicMessageStream, BasicXtbConnection, BasicXtbStreamConnection, DataMessageFilter, MessageStream, ResponsePromise, XtbConnection, BasicXtbConnectionError, XtbStreamConnection, BasicXtbStreamConnectionError};
20use crate::message_processing::ProcessedMessage;
21use crate::schema::{COMMAND_GET_ALL_SYMBOLS, COMMAND_GET_CALENDAR, COMMAND_GET_CHART_LAST_REQUEST, COMMAND_GET_CHART_RANGE_REQUEST, COMMAND_GET_COMMISSION_DEF, COMMAND_GET_CURRENT_USER_DATA, COMMAND_GET_IBS_HISTORY, COMMAND_GET_MARGIN_LEVEL, COMMAND_GET_MARGIN_TRADE, COMMAND_GET_NEWS, COMMAND_GET_PROFIT_CALCULATION, COMMAND_GET_SERVER_TIME, COMMAND_GET_STEP_RULES, COMMAND_GET_SYMBOL, COMMAND_GET_TICK_PRICES, COMMAND_GET_TRADE_RECORDS, COMMAND_GET_TRADES, COMMAND_GET_TRADES_HISTORY, COMMAND_GET_TRADING_HOURS, COMMAND_GET_VERSION, COMMAND_LOGIN, COMMAND_PING, COMMAND_TRADE_TRANSACTION, COMMAND_TRADE_TRANSACTION_STATUS, ErrorResponse, GetAllSymbolsRequest, GetAllSymbolsResponse, GetCalendarRequest, GetCalendarResponse, GetChartLastRequestRequest, GetChartLastRequestResponse, GetChartRangeRequestRequest, GetChartRangeRequestResponse, GetCommissionDefRequest, GetCommissionDefResponse, GetCurrentUserDataRequest, GetCurrentUserDataResponse, GetIbsHistoryRequest, GetIbsHistoryResponse, GetMarginLevelRequest, GetMarginLevelResponse, GetMarginTradeRequest, GetMarginTradeResponse, GetNewsRequest, GetNewsResponse, GetProfitCalculationRequest, GetProfitCalculationResponse, GetServerTimeRequest, GetServerTimeResponse, GetStepRulesRequest, GetStepRulesResponse, GetSymbolRequest, GetSymbolResponse, GetTickPricesRequest, GetTickPricesResponse, GetTradeRecordsRequest, GetTradeRecordsResponse, GetTradesHistoryRequest, GetTradesHistoryResponse, GetTradesRequest, GetTradesResponse, GetTradingHoursRequest, GetTradingHoursResponse, GetVersionRequest, GetVersionResponse, LoginRequest, PingRequest, STREAM_BALANCE, STREAM_CANDLES, STREAM_BALANCE_SUBSCRIBE, STREAM_CANDLES_SUBSCRIBE, STREAM_KEEP_ALIVE_SUBSCRIBE, STREAM_NEWS_SUBSCRIBE, STREAM_PROFITS_SUBSCRIBE, STREAM_TICK_PRICES_SUBSCRIBE, STREAM_TRADE_STATUS_SUBSCRIBE, STREAM_TRADES_SUBSCRIBE, STREAM_KEEP_ALIVE, STREAM_NEWS, STREAM_PING, STREAM_PROFITS, STREAM_BALANCE_UNSUBSCRIBE, STREAM_CANDLES_UNSUBSCRIBE, STREAM_KEEP_ALIVE_UNSUBSCRIBE, STREAM_NEWS_UNSUBSCRIBE, STREAM_PROFITS_UNSUBSCRIBE, STREAM_TICK_PRICES_UNSUBSCRIBE, STREAM_TRADE_STATUS_UNSUBSCRIBE, STREAM_TRADES_UNSUBSCRIBE, STREAM_TICK_PRICES, STREAM_TRADE_STATUS, STREAM_TRADES, StreamDataMessage, StreamGetBalanceData, StreamGetBalanceSubscribe, StreamGetBalanceUnsubscribe, StreamGetCandlesData, StreamGetCandlesSubscribe, StreamGetCandlesUnsubscribe, StreamGetKeepAliveData, StreamGetKeepAliveSubscribe, StreamGetKeepAliveUnsubscribe, StreamGetNewsData, StreamGetNewsSubscribe, StreamGetNewsUnsubscribe, StreamGetProfitData, StreamGetProfitSubscribe, StreamGetProfitUnsubscribe, StreamGetTickPricesData, StreamGetTickPricesSubscribe, StreamGetTickPricesUnsubscribe, StreamGetTradesData, StreamGetTradesSubscribe, StreamGetTradeStatusData, StreamGetTradeStatusSubscribe, StreamGetTradeStatusUnsubscribe, StreamGetTradesUnsubscribe, StreamPingSubscribe, TradeTransactionRequest, TradeTransactionResponse, TradeTransactionStatusRequest, TradeTransactionStatusResponse};
22
23
24#[derive(Default, Setters)]
42#[setters(into, prefix = "with_", strip_option)]
43pub struct XtbClientBuilder {
44 api_url: Option<String>,
46 stream_api_url: Option<String>,
48 app_id: Option<String>,
50 app_name: Option<String>,
52 ping_period: Option<u64>,
54}
55
56
57const DEFAULT_PING_INTERVAL_S: u64 = 30;
58
59const DEFAULT_XTB_REAL: &'static str = "wss://ws.xtb.com/real";
60const DEFAULT_XTB_REAL_STREAM: &'static str = "wss://ws.xtb.com/realStream";
61const DEFAULT_XTB_DEMO: &'static str = "wss://ws.xtb.com/demo";
62const DEFAULT_XTB_DEMO_STREAM: &'static str = "wss://ws.xtb.com/demoStream";
63
64
65impl XtbClientBuilder {
66 pub fn new(api_url: &str, stream_api_url: &str) -> Self {
70 Self {
71 api_url: Some(api_url.to_string()),
72 stream_api_url: Some(stream_api_url.to_string()),
73 app_id: None,
74 app_name: None,
75 ping_period: None,
76 }
77 }
78
79 pub fn new_bare() -> Self {
83 return Self {
84 api_url: None,
85 stream_api_url: None,
86 app_id: None,
87 app_name: None,
88 ping_period: None,
89 }
90 }
91
92 pub fn new_real() -> Self {
94 Self::new(DEFAULT_XTB_REAL, DEFAULT_XTB_REAL_STREAM)
95 }
96
97
98 pub fn new_demo() -> Self {
100 Self::new(DEFAULT_XTB_DEMO, DEFAULT_XTB_DEMO_STREAM)
101 }
102
103 pub async fn build(self, user_id: &str, password: &str) -> Result<XtbClient, XtbClientBuilderError> {
119 let api_url = Self::make_url(self.api_url)?;
120 let stream_api_url = Self::make_url(self.stream_api_url)?;
121
122 let mut connection = BasicXtbConnection::new(api_url).await.map_err(|err| XtbClientBuilderError::CannotMakeConnection(err))?;
124 let mut login_request = LoginRequest::default().with_user_id(user_id).with_password(password);
125
126 if let Some(app_id) = self.app_id {
127 login_request = login_request.with_app_id(app_id);
128 }
129 if let Some(app_name) = self.app_name {
130 login_request = login_request.with_app_name(app_name);
131 }
132
133 let login_request_value = to_value(login_request).map_err(|err| XtbClientBuilderError::UnexpectedError(format!("{:?}", err)))?;
134
135 let response = connection
136 .send_command(COMMAND_LOGIN, Some(login_request_value)).await
137 .map_err(|err| XtbClientBuilderError::UnexpectedError(format!("{:?}", err)))?.await
138 .map_err(|err| XtbClientBuilderError::UnexpectedError(format!("{:?}", err)))?;
139
140 let stream_session_id = match response {
141 ProcessedMessage::ErrorResponse(msg) => return Err(XtbClientBuilderError::LoginFailed { user_id: user_id.to_string(), extra_info: format!("{:?}", msg) }),
142 ProcessedMessage::Response(response) => response.stream_session_id.unwrap(),
143 };
144
145 let stream_connection = BasicXtbStreamConnection::new(stream_api_url, stream_session_id).await.map_err(|err| XtbClientBuilderError::CannotMakeStreamConnection(err))?;
146
147 Ok(XtbClient::new(connection, stream_connection, self.ping_period.unwrap_or(DEFAULT_PING_INTERVAL_S)))
148 }
149
150 fn make_url(source: Option<String>) -> Result<Url, XtbClientBuilderError> {
158 let source_str = source.ok_or_else(|| XtbClientBuilderError::RequiredFieldMissing("api_url".to_owned()))?;
159 Url::from_str(&source_str).map_err(|err| XtbClientBuilderError::InvalidUrl(source_str, err))
160 }
161}
162
163
164#[derive(Debug, Error)]
165pub enum XtbClientBuilderError {
166 #[error("Required configuration field is missing: {0}")]
167 RequiredFieldMissing(String),
168 #[error("Url is invalid or malformed: {0} ({1})")]
169 InvalidUrl(String, url::ParseError),
170 #[error("Cannot connect to server")]
171 CannotMakeConnection(BasicXtbConnectionError),
172 #[error("Cannot connect to stream server")]
173 CannotMakeStreamConnection(BasicXtbStreamConnectionError),
174 #[error("Login failed for user: {user_id} ({extra_info:?})")]
175 LoginFailed { user_id: String, extra_info: String },
176 #[error("Something gets horribly wrong: {0}")]
177 UnexpectedError(String),
178}
179
180
181#[async_trait]
183pub trait RequestResponseApi {
184 type Error;
186
187 async fn get_all_symbols(&mut self, request: GetAllSymbolsRequest) -> Result<GetAllSymbolsResponse, Self::Error>;
189
190 async fn get_calendar(&mut self, request: GetCalendarRequest) -> Result<GetCalendarResponse, Self::Error>;
192
193 async fn get_chart_last_request(&mut self, request: GetChartLastRequestRequest) -> Result<GetChartLastRequestResponse, Self::Error>;
219
220 async fn get_chart_range_request(&mut self, request: GetChartRangeRequestRequest) -> Result<GetChartRangeRequestResponse, Self::Error>;
238
239 async fn get_commission_def(&mut self, request: GetCommissionDefRequest) -> Result<GetCommissionDefResponse, Self::Error>;
242
243 async fn get_current_user_data(&mut self, request: GetCurrentUserDataRequest) -> Result<GetCurrentUserDataResponse, Self::Error>;
245
246 async fn get_ibs_history(&mut self, request: GetIbsHistoryRequest) -> Result<GetIbsHistoryResponse, Self::Error>;
248
249 async fn get_margin_level(&mut self, request: GetMarginLevelRequest) -> Result<GetMarginLevelResponse, Self::Error>;
253
254 async fn get_margin_trade(&mut self, request: GetMarginTradeRequest) -> Result<GetMarginTradeResponse, Self::Error>;
257
258 async fn get_news(&mut self, request: GetNewsRequest) -> Result<GetNewsResponse, Self::Error>;
262
263 async fn get_profit_calculation(&mut self, request: GetProfitCalculationRequest) -> Result<GetProfitCalculationResponse, Self::Error>;
267
268 async fn get_server_time(&mut self, request: GetServerTimeRequest) -> Result<GetServerTimeResponse, Self::Error>;
270
271 async fn get_step_rules(&mut self, request: GetStepRulesRequest) -> Result<GetStepRulesResponse, Self::Error>;
273
274 async fn get_symbol(&mut self, request: GetSymbolRequest) -> Result<GetSymbolResponse, Self::Error>;
276
277 async fn get_tick_prices(&mut self, request: GetTickPricesRequest) -> Result<GetTickPricesResponse, Self::Error>;
283
284 async fn get_trade_records(&mut self, request: GetTradeRecordsRequest) -> Result<GetTradeRecordsResponse, Self::Error>;
286
287 async fn get_trades(&mut self, request: GetTradesRequest) -> Result<GetTradesResponse, Self::Error>;
290
291 async fn get_trades_history(&mut self, request: GetTradesHistoryRequest) -> Result<GetTradesHistoryResponse, Self::Error>;
295
296 async fn get_trading_hours(&mut self, request: GetTradingHoursRequest) -> Result<GetTradingHoursResponse, Self::Error>;
298
299 async fn get_version(&mut self, request: GetVersionRequest) -> Result<GetVersionResponse, Self::Error>;
301
302 async fn trade_transaction(&mut self, request: TradeTransactionRequest) -> Result<TradeTransactionResponse, Self::Error>;
314
315 async fn trade_transaction_status(&mut self, request: TradeTransactionStatusRequest) -> Result<TradeTransactionStatusResponse, Self::Error>;
321}
322
323
324#[async_trait]
326pub trait StreamApi {
327 type Error;
329
330 type Stream<T: Send + Sync + for<'de> Deserialize<'de>>;
331
332 async fn subscribe_balance(&mut self, arguments: StreamGetBalanceSubscribe) -> Result<Self::Stream<StreamGetBalanceData>, Self::Error>;
339
340 async fn subscribe_candles(&mut self, arguments: StreamGetCandlesSubscribe) -> Result<Self::Stream<StreamGetCandlesData>, Self::Error>;
343
344 async fn subscribe_keep_alive(&mut self, arguments: StreamGetKeepAliveSubscribe) -> Result<Self::Stream<StreamGetKeepAliveData>, Self::Error>;
347
348 async fn subscribe_news(&mut self, arguments: StreamGetNewsSubscribe) -> Result<Self::Stream<StreamGetNewsData>, Self::Error>;
350
351 async fn subscribe_profits(&mut self, arguments: StreamGetProfitSubscribe) -> Result<Self::Stream<StreamGetProfitData>, Self::Error>;
353
354 async fn subscribe_tick_prices(&mut self, arguments: StreamGetTickPricesSubscribe) -> Result<Self::Stream<StreamGetTickPricesData>, Self::Error>;
360
361 async fn subscribe_trades(&mut self, arguments: StreamGetTradesSubscribe) -> Result<Self::Stream<StreamGetTradesData>, Self::Error>;
365
366 async fn subscribe_trade_status(&mut self, arguments: StreamGetTradeStatusSubscribe) -> Result<Self::Stream<StreamGetTradeStatusData>, Self::Error>;
370}
371
372
373pub struct XtbClient {
379 connection: Arc<Mutex<BasicXtbConnection>>,
381 stream_manager: StreamManager,
383 ping_join_handle: JoinHandle<()>,
385 stream_ping_join_handle: JoinHandle<()>,
387}
388
389
390impl XtbClient {
391 pub fn builder() -> XtbClientBuilder {
396 XtbClientBuilder::default()
397 }
398
399 pub fn new(connection: BasicXtbConnection, stream_connection: BasicXtbStreamConnection, ping_period: u64) -> Self {
407 let connection = Arc::new(Mutex::new(connection));
408
409 let ping_join_handle = spawn_ping(connection.clone(), ping_period);
410
411 let stream_manager = StreamManager::new(stream_connection);
412 let stream_ping_join_handle = spawn_stream_ping(stream_manager.clone(), ping_period);
413
414 Self {
415 connection,
416 stream_manager,
417 ping_join_handle,
418 stream_ping_join_handle,
419 }
420 }
421
422 async fn send_and_wait_or_default<REQ, RESP>(&mut self, command: &str, request: REQ) -> Result<RESP, XtbClientError>
426 where
427 REQ: Serialize,
428 RESP: for<'de> Deserialize<'de> + Default {
429 self.send_and_wait(command, request).await.map(|val| val.unwrap_or_default())
430 }
431
432 async fn send_and_wait<REQ, RESP>(&mut self, command: &str, request: REQ) -> Result<Option<RESP>, XtbClientError>
434 where
435 REQ: Serialize,
436 RESP: for<'de> Deserialize<'de>
437 {
438 let promise = self.send(command, request).await?;
439 let response = promise.await.map_err(|err| {
440 error!("Unexpected error: {:?}", err);
441 XtbClientError::UnexpectedError
442 })?;
443 match response {
444 ProcessedMessage::Response(response) => {
445 match response.return_data {
446 Some(data) => from_value(data).map_err(|err| XtbClientError::DeserializationFailed(err)).map(|v| Some(v)),
447 None => Ok(None)
448 }
449 }
450 ProcessedMessage::ErrorResponse(err) => Err(XtbClientError::CommandFailed(err)),
451 }
452 }
453
454 async fn send<A>(&mut self, command: &str, request: A) -> Result<ResponsePromise, XtbClientError>
456 where
457 A: Serialize
458 {
459 let mut conn = self.connection.lock().await;
460 let payload = Self::convert_data_to_value(request)?;
461 conn.send_command(command, Some(payload)).await.map_err(|err| {
462 match err {
463 BasicXtbConnectionError::SerializationError(err) => XtbClientError::SerializationFailed(err),
464 BasicXtbConnectionError::CannotSendRequest(err) => XtbClientError::CannotSendCommand(err),
465 _ => XtbClientError::UnexpectedError,
466 }
467 })
468 }
469
470 fn convert_data_to_value<T: Serialize>(data: T) -> Result<Value, XtbClientError> {
477 to_value(data).map_err(|err| XtbClientError::SerializationFailed(err))
478 }
479
480 async fn send_simple_stream_command<T, SA, UA>(
495 &mut self,
496 subscribe_command: &str,
497 subscribe_arguments: SA,
498 unsubscribe_command: &str,
499 unsubscribe_arguments: UA,
500 data_command: &str,
501 ) -> Result<DataStream<T>, XtbClientError>
502 where
503 T: for<'de> Deserialize<'de> + Send + Sync,
504 SA: Serialize,
505 UA: Serialize,
506 {
507 let unsubscribe_arguments = Self::convert_data_to_value(unsubscribe_arguments)?;
508 let filter = DataMessageFilter::Command(data_command.to_owned());
509 let subscribe_arguments = Self::convert_data_to_value(subscribe_arguments)?;
510 self.stream_manager.subscribe(subscribe_command, Some(subscribe_arguments), unsubscribe_command, Some(unsubscribe_arguments), data_command, filter).await
511 }
512
513 async fn send_symbol_scoped_stream_command<T, SA, UA>(
529 &mut self,
530 subscribe_command: &str,
531 subscribe_arguments: SA,
532 unsubscribe_command: &str,
533 unsubscribe_arguments: UA,
534 data_command: &str,
535 symbol: &str,
536 ) -> Result<DataStream<T>, XtbClientError>
537 where
538 T: for<'de> Deserialize<'de> + Send + Sync,
539 SA: Serialize,
540 UA: Serialize,
541 {
542 let unsubscribe_arguments = Self::convert_data_to_value(unsubscribe_arguments)?;
543 let subscribe_arguments = Self::convert_data_to_value(subscribe_arguments)?;
544 let subscription_key = format!("{}.{}", data_command, symbol);
545
546 let filter = DataMessageFilter::All(vec![
547 DataMessageFilter::Command(data_command.to_owned()),
548 DataMessageFilter::FieldValue { name: "symbol".to_owned(), value: Value::String(symbol.to_owned()) },
549 ]);
550 self.stream_manager.subscribe(subscribe_command, Some(subscribe_arguments), unsubscribe_command, Some(unsubscribe_arguments), &subscription_key, filter).await
551 }
552}
553
554
555impl Drop for XtbClient {
556 fn drop(&mut self) {
557 self.ping_join_handle.abort();
558 self.stream_ping_join_handle.abort();
559 }
560}
561
562
563#[async_trait]
564impl RequestResponseApi for XtbClient {
565 type Error = XtbClientError;
566
567 async fn get_all_symbols(&mut self, request: GetAllSymbolsRequest) -> Result<GetAllSymbolsResponse, Self::Error> {
568 self.send_and_wait_or_default(COMMAND_GET_ALL_SYMBOLS, request).await
569 }
570
571 async fn get_calendar(&mut self, request: GetCalendarRequest) -> Result<GetCalendarResponse, Self::Error> {
572 self.send_and_wait_or_default(COMMAND_GET_CALENDAR, request).await
573 }
574
575 async fn get_chart_last_request(&mut self, request: GetChartLastRequestRequest) -> Result<GetChartLastRequestResponse, Self::Error> {
576 self.send_and_wait_or_default(COMMAND_GET_CHART_LAST_REQUEST, request).await
577 }
578
579 async fn get_chart_range_request(&mut self, request: GetChartRangeRequestRequest) -> Result<GetChartRangeRequestResponse, Self::Error> {
580 self.send_and_wait_or_default(COMMAND_GET_CHART_RANGE_REQUEST, request).await
581 }
582
583 async fn get_commission_def(&mut self, request: GetCommissionDefRequest) -> Result<GetCommissionDefResponse, Self::Error> {
584 self.send_and_wait_or_default(COMMAND_GET_COMMISSION_DEF, request).await
585 }
586
587 async fn get_current_user_data(&mut self, request: GetCurrentUserDataRequest) -> Result<GetCurrentUserDataResponse, Self::Error> {
588 self.send_and_wait_or_default(COMMAND_GET_CURRENT_USER_DATA, request).await
589 }
590
591 async fn get_ibs_history(&mut self, request: GetIbsHistoryRequest) -> Result<GetIbsHistoryResponse, Self::Error> {
592 self.send_and_wait_or_default(COMMAND_GET_IBS_HISTORY, request).await
593 }
594
595 async fn get_margin_level(&mut self, request: GetMarginLevelRequest) -> Result<GetMarginLevelResponse, Self::Error> {
596 self.send_and_wait_or_default(COMMAND_GET_MARGIN_LEVEL, request).await
597 }
598
599 async fn get_margin_trade(&mut self, request: GetMarginTradeRequest) -> Result<GetMarginTradeResponse, Self::Error> {
600 self.send_and_wait_or_default(COMMAND_GET_MARGIN_TRADE, request).await
601 }
602
603 async fn get_news(&mut self, request: GetNewsRequest) -> Result<GetNewsResponse, Self::Error> {
604 self.send_and_wait_or_default(COMMAND_GET_NEWS, request).await
605 }
606
607 async fn get_profit_calculation(&mut self, request: GetProfitCalculationRequest) -> Result<GetProfitCalculationResponse, Self::Error> {
608 self.send_and_wait_or_default(COMMAND_GET_PROFIT_CALCULATION, request).await
609 }
610
611 async fn get_server_time(&mut self, request: GetServerTimeRequest) -> Result<GetServerTimeResponse, Self::Error> {
612 self.send_and_wait_or_default(COMMAND_GET_SERVER_TIME, request).await
613 }
614
615 async fn get_step_rules(&mut self, request: GetStepRulesRequest) -> Result<GetStepRulesResponse, Self::Error> {
616 self.send_and_wait_or_default(COMMAND_GET_STEP_RULES, request).await
617 }
618
619 async fn get_symbol(&mut self, request: GetSymbolRequest) -> Result<GetSymbolResponse, Self::Error> {
620 self.send_and_wait_or_default(COMMAND_GET_SYMBOL, request).await
621 }
622
623 async fn get_tick_prices(&mut self, request: GetTickPricesRequest) -> Result<GetTickPricesResponse, Self::Error> {
624 self.send_and_wait_or_default(COMMAND_GET_TICK_PRICES, request).await
625 }
626
627 async fn get_trade_records(&mut self, request: GetTradeRecordsRequest) -> Result<GetTradeRecordsResponse, Self::Error> {
628 self.send_and_wait_or_default(COMMAND_GET_TRADE_RECORDS, request).await
629 }
630
631 async fn get_trades(&mut self, request: GetTradesRequest) -> Result<GetTradesResponse, Self::Error> {
632 self.send_and_wait_or_default(COMMAND_GET_TRADES, request).await
633 }
634
635 async fn get_trades_history(&mut self, request: GetTradesHistoryRequest) -> Result<GetTradesHistoryResponse, Self::Error> {
636 self.send_and_wait_or_default(COMMAND_GET_TRADES_HISTORY, request).await
637 }
638
639 async fn get_trading_hours(&mut self, request: GetTradingHoursRequest) -> Result<GetTradingHoursResponse, Self::Error> {
640 self.send_and_wait_or_default(COMMAND_GET_TRADING_HOURS, request).await
641 }
642
643 async fn get_version(&mut self, request: GetVersionRequest) -> Result<GetVersionResponse, Self::Error> {
644 self.send_and_wait_or_default(COMMAND_GET_VERSION, request).await
645 }
646
647 async fn trade_transaction(&mut self, request: TradeTransactionRequest) -> Result<TradeTransactionResponse, Self::Error> {
648 self.send_and_wait_or_default(COMMAND_TRADE_TRANSACTION, request).await
649 }
650
651 async fn trade_transaction_status(&mut self, request: TradeTransactionStatusRequest) -> Result<TradeTransactionStatusResponse, Self::Error> {
652 self.send_and_wait_or_default(COMMAND_TRADE_TRANSACTION_STATUS, request).await
653 }
654}
655
656
657#[async_trait]
658impl StreamApi for XtbClient {
659 type Error = XtbClientError;
660
661 type Stream<T: Send + Sync + for<'de> Deserialize<'de>> = DataStream<T>;
662
663 async fn subscribe_balance(&mut self, arguments: StreamGetBalanceSubscribe) -> Result<Self::Stream<StreamGetBalanceData>, Self::Error> {
664 let stop_arguments = Self::convert_data_to_value(StreamGetBalanceUnsubscribe::default())?;
665 self.send_simple_stream_command(STREAM_BALANCE_SUBSCRIBE, arguments, STREAM_BALANCE_UNSUBSCRIBE, stop_arguments, STREAM_BALANCE).await
666 }
667
668 async fn subscribe_candles(&mut self, arguments: StreamGetCandlesSubscribe) -> Result<Self::Stream<StreamGetCandlesData>, Self::Error> {
669 let stop_arguments = Self::convert_data_to_value(StreamGetCandlesUnsubscribe::default().with_symbol(&arguments.symbol))?;
670 let symbol = arguments.symbol.clone();
671 self.send_symbol_scoped_stream_command(STREAM_CANDLES_SUBSCRIBE, arguments, STREAM_CANDLES_UNSUBSCRIBE, stop_arguments, STREAM_CANDLES, &symbol).await
672 }
673
674 async fn subscribe_keep_alive(&mut self, arguments: StreamGetKeepAliveSubscribe) -> Result<Self::Stream<StreamGetKeepAliveData>, Self::Error> {
675 let stop_arguments = Self::convert_data_to_value(StreamGetKeepAliveUnsubscribe::default())?;
676 self.send_simple_stream_command(STREAM_KEEP_ALIVE_SUBSCRIBE, arguments, STREAM_KEEP_ALIVE_UNSUBSCRIBE, stop_arguments, STREAM_KEEP_ALIVE).await
677 }
678
679 async fn subscribe_news(&mut self, arguments: StreamGetNewsSubscribe) -> Result<Self::Stream<StreamGetNewsData>, Self::Error> {
680 let stop_arguments = Self::convert_data_to_value(StreamGetNewsUnsubscribe::default())?;
681 self.send_simple_stream_command(STREAM_NEWS_SUBSCRIBE, arguments, STREAM_NEWS_UNSUBSCRIBE, stop_arguments, STREAM_NEWS).await
682 }
683
684 async fn subscribe_profits(&mut self, arguments: StreamGetProfitSubscribe) -> Result<Self::Stream<StreamGetProfitData>, Self::Error> {
685 let stop_arguments = Self::convert_data_to_value(StreamGetProfitUnsubscribe::default())?;
686 self.send_simple_stream_command(STREAM_PROFITS_SUBSCRIBE, arguments, STREAM_PROFITS_UNSUBSCRIBE, stop_arguments, STREAM_PROFITS).await
687 }
688
689 async fn subscribe_tick_prices(&mut self, arguments: StreamGetTickPricesSubscribe) -> Result<Self::Stream<StreamGetTickPricesData>, Self::Error> {
690 let stop_arguments = Self::convert_data_to_value(StreamGetTickPricesUnsubscribe::default().with_symbol(&arguments.symbol))?;
691 let symbol = arguments.symbol.clone();
692 self.send_symbol_scoped_stream_command(STREAM_TICK_PRICES_SUBSCRIBE, arguments, STREAM_TICK_PRICES_UNSUBSCRIBE, stop_arguments, STREAM_TICK_PRICES, &symbol).await
693 }
694
695 async fn subscribe_trades(&mut self, arguments: StreamGetTradesSubscribe) -> Result<Self::Stream<StreamGetTradesData>, Self::Error> {
696 let stop_arguments = Self::convert_data_to_value(StreamGetTradesUnsubscribe::default())?;
697 self.send_simple_stream_command(STREAM_TRADES_SUBSCRIBE, arguments, STREAM_TRADES_UNSUBSCRIBE, stop_arguments, STREAM_TRADES).await
698 }
699
700 async fn subscribe_trade_status(&mut self, arguments: StreamGetTradeStatusSubscribe) -> Result<Self::Stream<StreamGetTradeStatusData>, Self::Error> {
701 let stop_arguments = Self::convert_data_to_value(StreamGetTradeStatusUnsubscribe::default())?;
702 self.send_simple_stream_command(STREAM_TRADE_STATUS_SUBSCRIBE, arguments, STREAM_TRADE_STATUS_UNSUBSCRIBE, stop_arguments, STREAM_TRADE_STATUS).await
703 }
704}
705
706
707#[derive(Debug, Error)]
708pub enum XtbClientError {
709 #[error("Cannot serialize arguments")]
710 SerializationFailed(serde_json::Error),
711 #[error("Cannot send command to server")]
712 CannotSendCommand(tokio_tungstenite::tungstenite::Error),
713 #[error("Cannot send stream command")]
714 CannotSendStreamCommand(BasicXtbStreamConnectionError),
715 #[error("Unexpected error.")]
716 UnexpectedError,
717 #[error("Cannot deserialize data")]
718 DeserializationFailed(serde_json::Error),
719 #[error("Command failed and an error response was returned")]
720 CommandFailed(ErrorResponse),
721}
722
723
724#[derive(Debug)]
726struct StreamManagerState {
727 connection: BasicXtbStreamConnection,
729 subscriptions: HashMap<String, usize>,
731}
732
733
734impl StreamManagerState {
735 pub fn new(connection: BasicXtbStreamConnection) -> Self {
737 Self {
738 connection,
739 subscriptions: HashMap::new(),
740 }
741 }
742}
743
744
745#[derive(Clone, Debug)]
748struct StreamManager {
749 state: Arc<Mutex<StreamManagerState>>,
751}
752
753
754impl StreamManager {
755 pub fn new(connection: BasicXtbStreamConnection) -> Self {
757 let state = Arc::new(Mutex::new(StreamManagerState::new(connection)));
758 Self {
759 state
760 }
761 }
762
763 pub async fn subscribe<T: for<'de> Deserialize<'de> + Send + Sync>(
779 &mut self,
780 subscribe_command: &str,
781 subscribe_arguments: Option<Value>,
782 unsubscribe_command: &str,
783 unsubscribe_arguments: Option<Value>,
784 subscription_key: &str,
785 filter: DataMessageFilter,
786 ) -> Result<DataStream<T>, XtbClientError> {
787 let mut state = self.state.lock().await;
788 let stream = state.connection.make_message_stream(filter).await;
789 state.connection.subscribe(subscribe_command, subscribe_arguments).await.map_err(|err| XtbClientError::CannotSendStreamCommand(err))?;
790 *state.subscriptions.entry(subscription_key.to_owned()).or_default() += 1;
791 Ok(DataStream::new(stream, self.clone(), subscription_key.to_owned(), unsubscribe_command.to_owned(), unsubscribe_arguments))
792 }
793
794 pub async fn unsubscribe(&mut self, subscription_key: &str, command: &str, arguments: Option<Value>) -> Result<(), XtbClientError> {
807 let mut state = self.state.lock().await;
808 let entry = state.subscriptions.entry(subscription_key.to_owned()).or_default();
809 if *entry > 0 {
810 *entry -= 1;
811 }
812 if *entry == 0 {
813 state.connection.unsubscribe(command, arguments).await.map_err(|err| XtbClientError::CannotSendStreamCommand(err))?;
814 }
815 Ok(())
816 }
817}
818
819
820pub struct DataStream<T>
824 where
825 T: for<'de> Deserialize<'de> + Send + Sync
826{
827 message_stream: BasicMessageStream,
829 stream_manager: StreamManager,
831 subscription_key: String,
833 unsubscribe_command: String,
835 unsubscribe_arguments: Option<Value>,
837 type_: PhantomData<T>,
839}
840
841impl<T> DataStream<T>
842 where
843 T: for<'de> Deserialize<'de> + Send + Sync
844{
845 fn new(message_stream: BasicMessageStream, stream_manager: StreamManager, subscription_key: String, unsubscribe_command: String, unsubscribe_arguments: Option<Value>) -> Self {
847 Self {
848 message_stream,
849 stream_manager,
850 subscription_key,
851 unsubscribe_command,
852 unsubscribe_arguments,
853 type_: PhantomData::<T>,
854 }
855 }
856
857 pub async fn next(&mut self) -> Result<Option<T>, DataStreamError> {
865 let message = self.message_stream.next().await;
866 match message {
867 Some(msg) => Self::process_message(msg).map(|r| Some(r)),
868 None => Ok(None),
869 }
870 }
871
872 fn process_message(msg: StreamDataMessage) -> Result<T, DataStreamError> {
874 from_value(msg.data).map_err(|err| DataStreamError::CannotDeserializeValue(err))
875 }
876}
877
878impl<T> Drop for DataStream<T>
879 where
880 T: for<'de> Deserialize<'de> + Send + Sync
881{
882 fn drop(&mut self) {
883 let mut manager = self.stream_manager.clone();
884 let unsubscribe_command = self.unsubscribe_command.clone();
885 let unsubscribe_arguments = self.unsubscribe_arguments.take();
886 let subscription_key = self.subscription_key.clone();
887 spawn(async move {
888 let result = manager.unsubscribe(&subscription_key, &unsubscribe_command, unsubscribe_arguments.clone()).await;
889 match result {
890 Err(err) => error!("Cannot unsubscribe command '{unsubscribe_command}' ({unsubscribe_arguments:?}). The subscription key was: '{subscription_key}'. The error was: {err:?}"),
891 _ => (),
892 };
893 });
894 }
895}
896
897#[derive(Debug, Error)]
898pub enum DataStreamError {
899 #[error("Cannot deserialize value: {0}")]
900 CannotDeserializeValue(serde_json::Error)
901}
902
903
904fn spawn_ping(conn: Arc<Mutex<BasicXtbConnection>>, ping_secs: u64) -> JoinHandle<()> {
919 let ping_value = to_value(PingRequest::default()).expect("Cannot serialize ping message");
920 spawn(async move {
921 let mut idx = 1u64;
922 loop {
923 let response_promise = {
924 let mut conn = conn.lock().await;
925 debug!("Sending ping #{} to connection", idx);
926 match conn.send_command(COMMAND_PING, Some(ping_value.clone())).await {
927 Ok(resp) => Some(resp),
928 Err(err) => {
929 error!("Cannot send ping #{}: {:?}", idx, err);
930 None
931 }
932 }
933 };
934 if let Some(response_promise) = response_promise {
935 match response_promise.await {
936 Ok(_) => (),
937 Err(err) => error!("Cannot await the ping response #{}: {:?}", idx, err)
938 }
939 }
940 idx += 1;
941 sleep(Duration::from_secs(ping_secs)).await;
942 }
943 })
944}
945
946
947fn spawn_stream_ping(stream_manager: StreamManager, ping_secs: u64) -> JoinHandle<()> {
962 let ping_value = to_value(StreamPingSubscribe::default()).expect("Cannot serialize the stream ping message");
963 spawn(async move {
964 let mut idx = 1u64;
965 loop {
966 {
967 debug!("Sending ping #{} to stream connection", idx);
968 let mut inner_state = stream_manager.state.lock().await;
969 match inner_state.connection.subscribe(STREAM_PING, Some(ping_value.clone())).await {
970 Ok(_) => (),
971 Err(err) => error!("Cannot send ping #{}: {:?}", idx, err)
972 }
973 }
974 idx += 1;
975 sleep(Duration::from_secs(ping_secs)).await;
976 }
977 })
978}