ig_client/application/services/
account_service.rs1use crate::application::services::AccountService;
2use crate::{
3 application::models::account::{
4 AccountActivity, AccountInfo, Positions, TransactionHistory, WorkingOrders,
5 },
6 config::Config,
7 error::AppError,
8 session::interface::IgSession,
9 transport::http_client::IgHttpClient,
10};
11use async_trait::async_trait;
12use reqwest::Method;
13use std::sync::Arc;
14use tracing::{debug, info};
15
16pub struct AccountServiceImpl<T: IgHttpClient> {
18 config: Arc<Config>,
19 client: Arc<T>,
20}
21
22impl<T: IgHttpClient> AccountServiceImpl<T> {
23 pub fn new(config: Arc<Config>, client: Arc<T>) -> Self {
25 Self { config, client }
26 }
27
28 pub fn get_config(&self) -> Arc<Config> {
33 self.config.clone()
34 }
35
36 pub fn set_config(&mut self, config: Arc<Config>) {
41 self.config = config;
42 }
43}
44
45#[async_trait]
46impl<T: IgHttpClient + 'static> AccountService for AccountServiceImpl<T> {
47 async fn get_accounts(&self, session: &IgSession) -> Result<AccountInfo, AppError> {
48 info!("Getting account information");
49
50 let result = self
51 .client
52 .request::<(), AccountInfo>(Method::GET, "accounts", session, None, "1")
53 .await?;
54
55 debug!(
56 "Account information obtained: {} accounts",
57 result.accounts.len()
58 );
59 Ok(result)
60 }
61
62 async fn get_positions(&self, session: &IgSession) -> Result<Positions, AppError> {
63 debug!("Getting open positions");
64
65 let result = self
66 .client
67 .request::<(), Positions>(Method::GET, "positions", session, None, "2")
68 .await?;
69
70 debug!("Positions obtained: {} positions", result.positions.len());
71 Ok(result)
72 }
73
74 async fn get_positions_w_filter(
97 &self,
98 filter: &str,
99 session: &IgSession,
100 ) -> Result<Positions, AppError> {
101 debug!("Getting open positions with filter: {}", filter);
102
103 let mut positions = self.get_positions(session).await?;
104
105 positions
106 .positions
107 .retain(|position| position.market.epic.contains(filter));
108
109 debug!(
110 "Positions obtained after filtering: {} positions",
111 positions.positions.len()
112 );
113 Ok(positions)
114 }
115
116 async fn get_working_orders(&self, session: &IgSession) -> Result<WorkingOrders, AppError> {
117 info!("Getting working orders");
118
119 let result = self
120 .client
121 .request::<(), WorkingOrders>(Method::GET, "workingorders", session, None, "2")
122 .await?;
123
124 debug!(
125 "Working orders obtained: {} orders",
126 result.working_orders.len()
127 );
128 Ok(result)
129 }
130
131 async fn get_activity(
132 &self,
133 session: &IgSession,
134 from: &str,
135 to: &str,
136 ) -> Result<AccountActivity, AppError> {
137 let path = format!("history/activity?from={from}&to={to}&pageSize=500");
138 info!("Getting account activity");
139
140 let result = self
141 .client
142 .request::<(), AccountActivity>(Method::GET, &path, session, None, "3")
143 .await?;
144
145 debug!(
146 "Account activity obtained: {} activities",
147 result.activities.len()
148 );
149 Ok(result)
150 }
151
152 async fn get_activity_with_details(
153 &self,
154 session: &IgSession,
155 from: &str,
156 to: &str,
157 ) -> Result<AccountActivity, AppError> {
158 let path = format!("history/activity?from={from}&to={to}&detailed=true&pageSize=500");
159 info!("Getting detailed account activity");
160
161 let result = self
162 .client
163 .request::<(), AccountActivity>(Method::GET, &path, session, None, "3")
164 .await?;
165
166 debug!(
167 "Detailed account activity obtained: {} activities",
168 result.activities.len()
169 );
170 Ok(result)
171 }
172
173 async fn get_transactions(
174 &self,
175 session: &IgSession,
176 from: &str,
177 to: &str,
178 ) -> Result<TransactionHistory, AppError> {
179 const PAGE_SIZE: u32 = 200;
180 let mut all_transactions = Vec::new();
181 let mut current_page = 1;
182 #[allow(unused_assignments)]
183 let mut last_metadata = None;
184
185 loop {
186 let path = format!(
187 "history/transactions?from={from}&to={to}&pageSize={PAGE_SIZE}&pageNumber={current_page}"
188 );
189 info!("Getting transaction history page {}", current_page);
190
191 let result = self
192 .client
193 .request::<(), TransactionHistory>(Method::GET, &path, session, None, "2")
194 .await?;
195
196 let total_pages = result.metadata.page_data.total_pages as u32;
197 last_metadata = Some(result.metadata);
198 all_transactions.extend(result.transactions);
199
200 if current_page >= total_pages {
201 break;
202 }
203 current_page += 1;
204 }
205
206 debug!(
207 "Total transaction history obtained: {} transactions",
208 all_transactions.len()
209 );
210
211 Ok(TransactionHistory {
212 transactions: all_transactions,
213 metadata: last_metadata
214 .ok_or_else(|| AppError::InvalidInput("Could not retrieve metadata".to_string()))?,
215 })
216 }
217}
218
219#[cfg(test)]
220mod tests {
221 use super::*;
222 use crate::config::Config;
223 use crate::transport::http_client::IgHttpClientImpl;
224 use crate::utils::rate_limiter::RateLimitType;
225 use std::sync::Arc;
226
227 #[test]
228 fn test_get_and_set_config() {
229 let config = Arc::new(Config::with_rate_limit_type(
230 RateLimitType::TradingAccount,
231 0.7,
232 ));
233 let client = Arc::new(IgHttpClientImpl::new(config.clone()));
234 let mut service = AccountServiceImpl::new(config.clone(), client.clone());
235
236 let cfg1 = service.get_config();
237 assert!(Arc::ptr_eq(&cfg1, &config));
238
239 let new_cfg = Arc::new(Config::default());
240 service.set_config(new_cfg.clone());
241 assert!(Arc::ptr_eq(&service.get_config(), &new_cfg));
242 }
243}