ig_client/application/services/
order_service.rs1use crate::application::models::account::WorkingOrders;
2use crate::application::models::order::{
3 ClosePositionRequest, ClosePositionResponse, CreateOrderRequest, CreateOrderResponse,
4 OrderConfirmation, UpdatePositionRequest, UpdatePositionResponse,
5};
6use crate::application::models::working_order::{
7 CreateWorkingOrderRequest, CreateWorkingOrderResponse,
8};
9use crate::application::services::interfaces::order::OrderService;
10use crate::config::Config;
11use crate::error::AppError;
12use crate::session::interface::IgSession;
13use crate::transport::http_client::IgHttpClient;
14use async_trait::async_trait;
15use reqwest::Method;
16use std::sync::Arc;
17use tracing::{debug, info};
18
19pub struct OrderServiceImpl<T: IgHttpClient> {
21 config: Arc<Config>,
22 client: Arc<T>,
23}
24
25impl<T: IgHttpClient> OrderServiceImpl<T> {
26 pub fn new(config: Arc<Config>, client: Arc<T>) -> Self {
28 Self { config, client }
29 }
30
31 pub fn get_config(&self) -> Arc<Config> {
36 self.config.clone()
37 }
38
39 pub fn set_config(&mut self, config: Arc<Config>) {
44 self.config = config;
45 }
46}
47
48#[async_trait]
49impl<T: IgHttpClient + 'static> OrderService for OrderServiceImpl<T> {
50 async fn create_order(
51 &self,
52 session: &IgSession,
53 order: &CreateOrderRequest,
54 ) -> Result<CreateOrderResponse, AppError> {
55 info!("Creating order for: {}", order.epic);
56
57 let result = self
58 .client
59 .request::<CreateOrderRequest, CreateOrderResponse>(
60 Method::POST,
61 "positions/otc",
62 session,
63 Some(order),
64 "2",
65 )
66 .await?;
67
68 debug!("Order created with reference: {}", result.deal_reference);
69 Ok(result)
70 }
71
72 async fn get_order_confirmation(
73 &self,
74 session: &IgSession,
75 deal_reference: &str,
76 ) -> Result<OrderConfirmation, AppError> {
77 let path = format!("confirms/{deal_reference}");
78 info!("Getting confirmation for order: {}", deal_reference);
79
80 let result = self
81 .client
82 .request::<(), OrderConfirmation>(Method::GET, &path, session, None, "1")
83 .await?;
84
85 debug!("Confirmation obtained for order: {}", deal_reference);
86 Ok(result)
87 }
88
89 async fn update_position(
90 &self,
91 session: &IgSession,
92 deal_id: &str,
93 update: &UpdatePositionRequest,
94 ) -> Result<UpdatePositionResponse, AppError> {
95 let path = format!("positions/otc/{deal_id}");
96 info!("Updating position: {}", deal_id);
97
98 let result = self
99 .client
100 .request::<UpdatePositionRequest, UpdatePositionResponse>(
101 Method::PUT,
102 &path,
103 session,
104 Some(update),
105 "2",
106 )
107 .await?;
108
109 debug!(
110 "Position updated: {} with deal reference: {}",
111 deal_id, result.deal_reference
112 );
113 Ok(result)
114 }
115
116 async fn close_position(
117 &self,
118 session: &IgSession,
119 close_request: &ClosePositionRequest,
120 ) -> Result<ClosePositionResponse, AppError> {
121 use crate::constants::USER_AGENT;
122 use reqwest::Client;
123 use tracing::error;
124
125 info!("{}", serde_json::to_string(close_request)?);
126
127 let url = format!("{}/positions/otc", self.config.rest_api.base_url);
130
131 session.respect_rate_limit().await?;
133
134 let client = Client::new();
135 let response = client
136 .post(&url)
137 .header("Content-Type", "application/json; charset=utf-8")
138 .header("Accept", "application/json; charset=utf-8")
139 .header("User-Agent", USER_AGENT)
140 .header("Version", "1")
141 .header("X-IG-API-KEY", &self.config.credentials.api_key)
142 .header("CST", &session.cst)
143 .header("X-SECURITY-TOKEN", &session.token)
144 .header("_method", "DELETE") .json(close_request)
146 .send()
147 .await?;
148
149 let status = response.status();
150 let response_text = response.text().await?;
151
152 if status.is_success() {
153 let close_response: ClosePositionResponse = serde_json::from_str(&response_text)?;
154 debug!(
155 "Position closed with reference: {}",
156 close_response.deal_reference
157 );
158 Ok(close_response)
159 } else {
160 error!(
161 "Unexpected status code {} for request to {}: {}",
162 status, url, response_text
163 );
164 Err(AppError::Unexpected(status))
165 }
166 }
167
168 async fn get_working_orders(&self, session: &IgSession) -> Result<WorkingOrders, AppError> {
169 info!("Getting all working orders");
170
171 let result = self
172 .client
173 .request::<(), WorkingOrders>(Method::GET, "workingorders", session, None, "2")
174 .await?;
175
176 debug!("Retrieved {} working orders", result.working_orders.len());
177 Ok(result)
178 }
179
180 async fn create_working_order(
181 &self,
182 session: &IgSession,
183 order: &CreateWorkingOrderRequest,
184 ) -> Result<CreateWorkingOrderResponse, AppError> {
185 info!("Creating working order for: {}", order.epic);
186
187 let result = self
188 .client
189 .request::<CreateWorkingOrderRequest, CreateWorkingOrderResponse>(
190 Method::POST,
191 "workingorders/otc",
192 session,
193 Some(order),
194 "2",
195 )
196 .await?;
197
198 debug!(
199 "Working order created with reference: {}",
200 result.deal_reference
201 );
202 Ok(result)
203 }
204}
205
206#[cfg(test)]
207mod tests {
208 use super::*;
209 use crate::config::Config;
210 use crate::transport::http_client::IgHttpClientImpl;
211 use crate::utils::rate_limiter::RateLimitType;
212 use std::sync::Arc;
213
214 #[test]
215 fn test_get_and_set_config() {
216 let config = Arc::new(Config::with_rate_limit_type(
217 RateLimitType::NonTradingAccount,
218 0.7,
219 ));
220 let client = Arc::new(IgHttpClientImpl::new(config.clone()));
221 let mut service = OrderServiceImpl::new(config.clone(), client.clone());
222
223 let cfg1 = service.get_config();
224 assert!(Arc::ptr_eq(&cfg1, &config));
225
226 let new_cfg = Arc::new(Config::default());
227 service.set_config(new_cfg.clone());
228 assert!(Arc::ptr_eq(&service.get_config(), &new_cfg));
229 }
230}