1use lightstreamer_rs::{
4 Client, ClientConfig, Credentials, FieldSchema, ItemGroup, ServerAddress, Snapshot,
5 Subscription, SubscriptionMode, Updates,
6};
7
8use crate::error::IgResult;
9
10const MARKET_FIELDS: [&str; 5] = [
14 "BID",
15 "OFFER",
16 "UPDATE_TIME",
17 "MARKET_STATE",
18 "MARKET_DELAY",
19];
20const MARKET_LADDER_FIELDS: [&str; 20] = [
21 "BIDPRICE1",
22 "BIDPRICE2",
23 "BIDPRICE3",
24 "BIDPRICE4",
25 "BIDPRICE5",
26 "ASKPRICE1",
27 "ASKPRICE2",
28 "ASKPRICE3",
29 "ASKPRICE4",
30 "ASKPRICE5",
31 "BIDSIZE1",
32 "BIDSIZE2",
33 "BIDSIZE3",
34 "BIDSIZE4",
35 "BIDSIZE5",
36 "ASKSIZE1",
37 "ASKSIZE2",
38 "ASKSIZE3",
39 "ASKSIZE4",
40 "ASKSIZE5",
41];
42const ACCOUNT_FIELDS: [&str; 5] = ["PNL", "DEPOSIT", "USED_MARGIN", "AVAILABLE_CASH", "FUNDS"];
43const TRADE_FIELDS: [&str; 3] = ["CONFIRMS", "OPU", "WOU"];
44
45pub struct IgStreamingClient {
49 client: Client,
50 account_id: String,
51}
52
53impl std::fmt::Debug for IgStreamingClient {
54 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
55 formatter
56 .debug_struct("IgStreamingClient")
57 .field("account_id", &"<redacted>")
58 .finish_non_exhaustive()
59 }
60}
61
62impl IgStreamingClient {
63 pub(crate) async fn connect(
64 endpoint: &str,
65 account_id: String,
66 cst: String,
67 x_security_token: String,
68 ) -> IgResult<Self> {
69 let password = format!("CST-{cst}|XST-{x_security_token}");
70 let config = ClientConfig::builder(ServerAddress::try_new(endpoint)?)
71 .with_credentials(Credentials::new(&account_id, password))
72 .build()?;
73 let (client, session_events) = Client::connect(config).await?;
74 drop(session_events);
75 Ok(Self { client, account_id })
76 }
77
78 pub async fn subscribe_markets<I, S>(&self, epics: I) -> IgResult<Updates>
80 where
81 I: IntoIterator<Item = S>,
82 S: AsRef<str>,
83 {
84 let items = epics
85 .into_iter()
86 .map(|epic| format!("MARKET:{}", epic.as_ref()));
87 self.subscribe(SubscriptionMode::Merge, items, MARKET_FIELDS, Snapshot::On)
88 .await
89 }
90
91 pub async fn subscribe_market_ladders<I, S>(&self, epics: I) -> IgResult<Updates>
97 where
98 I: IntoIterator<Item = S>,
99 S: AsRef<str>,
100 {
101 let items = epics
102 .into_iter()
103 .map(|epic| format!("MARKET:{}", epic.as_ref()));
104 self.subscribe(
105 SubscriptionMode::Merge,
106 items,
107 MARKET_LADDER_FIELDS,
108 Snapshot::On,
109 )
110 .await
111 }
112
113 pub async fn subscribe_account(&self) -> IgResult<Updates> {
115 self.subscribe(
116 SubscriptionMode::Merge,
117 [format!("ACCOUNT:{}", self.account_id)],
118 ACCOUNT_FIELDS,
119 Snapshot::On,
120 )
121 .await
122 }
123
124 pub async fn subscribe_trades(&self) -> IgResult<Updates> {
126 self.subscribe(
127 SubscriptionMode::Distinct,
128 [format!("TRADE:{}", self.account_id)],
129 TRADE_FIELDS,
130 Snapshot::On,
131 )
132 .await
133 }
134
135 pub async fn disconnect(self) -> IgResult<()> {
137 self.client.disconnect().await?;
138 Ok(())
139 }
140
141 async fn subscribe<I, S, F>(
142 &self,
143 mode: SubscriptionMode,
144 items: I,
145 fields: F,
146 snapshot: Snapshot,
147 ) -> IgResult<Updates>
148 where
149 I: IntoIterator<Item = S>,
150 S: Into<String>,
151 F: IntoIterator,
152 F::Item: Into<String>,
153 {
154 let subscription = Subscription::new(
155 mode,
156 ItemGroup::from_items(items)?,
157 FieldSchema::from_fields(fields)?,
158 )
159 .with_snapshot(snapshot);
160 Ok(self.client.subscribe(subscription).await?)
161 }
162}