ibkr_cp_api_client/rest/
endpoints.rs1use std::collections::HashMap;
2
3use chrono::NaiveDateTime;
4use rust_decimal::Decimal;
5use serde_json::{json, Value};
6
7use crate::client::IBClientPortal;
8use crate::models::account_ledger::AccountLedger;
9use crate::models::contract::SecurityDefinitions;
10use crate::models::contract_detail::ContractDetail;
11use crate::models::definitions::AssetClass;
12use crate::models::futures_contract::FuturesContracts;
13use crate::models::market_data_history::MarketDataHistory;
14use crate::models::order_ticket::OrderTicket;
15use crate::models::positions::Position;
16use crate::models::stock_contracts::StockContracts;
17use crate::models::tickle::AuthStatus;
18use crate::models::tickle::Tickle;
19pub async fn process_response<T: for<'a> serde::Deserialize<'a>>(
22 response: reqwest::Response,
23) -> Result<T, reqwest::Error> {
24 response.json().await
25}
26
27impl IBClientPortal {
28 fn get_url(&self, path: &str) -> String {
30 let base = if self.listen_ssl { "https" } else { "http" };
31 format!("{base}://localhost:{}/v1/api{path}", self.port)
32 }
33 pub async fn check_auth_status(&self) -> Result<AuthStatus, reqwest::Error> {
36 let response = self
37 .client
38 .post(self.get_url("/iserver/auth/status"))
39 .header(
40 reqwest::header::CONTENT_LENGTH,
41 reqwest::header::HeaderValue::from_static("0"),
42 )
43 .body("")
44 .send()
45 .await?;
46 response.json().await
47 }
48 pub async fn tickle(&self) -> Result<Tickle, reqwest::Error> {
51 let response = self
52 .client
53 .post(self.get_url("/tickle"))
54 .header(
55 reqwest::header::CONTENT_LENGTH,
56 reqwest::header::HeaderValue::from_static("0"),
57 )
58 .body("")
59 .send()
60 .await?;
61 response.json().await
62 }
63 pub async fn get_positions(&self, page: i32) -> Result<Vec<Position>, reqwest::Error> {
67 let path = format!("/portfolio/{}/positions/{}", self.account, page);
68 let response = self.client.get(self.get_url(&path)).body("").send().await?;
69 println!("{:#?}", response.status());
70 process_response(response).await
71 }
72 pub async fn get_session_id(&mut self) -> Result<(), reqwest::Error> {
74 let response = self.tickle().await?;
75 self.session_id = Some(response.session);
76 Ok(())
77 }
78 pub async fn get_security_definition_by_contract_id(
80 &self,
81 contract_ids: Vec<i64>,
82 ) -> Result<SecurityDefinitions, reqwest::Error> {
83 let path = "/trsrv/secdef";
84 let payload = json!({
85 "conids" : contract_ids,
86 });
87 let request = self.client.post(self.get_url(path));
88 let response = request.body(payload.to_string()).send().await?;
89 process_response(response).await
90 }
91 pub async fn get_futures_by_symbol(
93 &self,
94 symbols: Vec<&str>,
95 ) -> Result<FuturesContracts, reqwest::Error> {
96 let path = "/trsrv/futures";
97 let request = self
98 .client
99 .get(self.get_url(path))
100 .query(&[("symbols", symbols.join(","))]);
101 let response = request.send().await?;
102 process_response(response).await
103 }
104 pub async fn get_stocks_by_symbol(
106 &self,
107 symbols: Vec<&str>,
108 ) -> Result<StockContracts, reqwest::Error> {
109 let path = "/trsrv/stocks";
110 let request = self
111 .client
112 .get(self.get_url(path))
113 .query(&[("symbols", symbols.join(","))]);
114 let response = request.send().await?;
115 process_response(response).await
116 }
117 pub async fn search_for_security(
120 &self,
121 symbol_or_name: &str,
122 is_name: bool,
123 sec_type: AssetClass,
124 ) -> Result<Value, reqwest::Error> {
125 let path = "/iserver/secdef/search";
126 let body = json!( {
127 "symbol": symbol_or_name,
128 "name": is_name,
129 "secType": sec_type,
130 });
131 let request = self.client.post(self.get_url(path)).body(body.to_string());
132 let response = request.send().await?;
133 process_response(response).await
134 }
135 pub async fn get_options(
138 &self,
139 underlying_con_id: i64,
140 sectype: AssetClass,
141 month: Option<String>,
142 exchange: Option<String>,
143 strike: Option<Decimal>,
144 ) -> Result<Value, reqwest::Error> {
145 let path = "/iserver/secdef/info";
146 let mut query = vec![
147 ("conid", underlying_con_id.to_string()),
148 ("sectype", sectype.to_string()),
149 ];
150 if let Some(month) = month {
151 query.push(("month", month));
152 }
153 if let Some(exchange) = exchange {
154 query.push(("exchange", exchange));
155 }
156 if let Some(strike) = strike {
157 query.push(("strike", strike.to_string()));
158 }
159 let response = self
160 .client
161 .get(self.get_url(path))
162 .query(&query)
163 .send()
164 .await?;
165 response.json().await
166 }
167 pub async fn logout(&self) -> Result<Value, reqwest::Error> {
169 let response = self
170 .client
171 .post(self.get_url("/logout"))
172 .header(
173 reqwest::header::CONTENT_LENGTH,
174 reqwest::header::HeaderValue::from_static("0"),
175 )
176 .body("")
177 .send()
178 .await?;
179 response.json().await
180 }
181
182 pub async fn get_account_ledger(
183 &self,
184 ) -> Result<HashMap<String, AccountLedger>, reqwest::Error> {
185 let path = format!("/portfolio/{}/ledger", self.account);
186 let response = self.client.get(self.get_url(&path)).body("").send().await?;
187 process_response(response).await
188 }
189
190 pub async fn place_order(&self, orders: Vec<OrderTicket>) -> Result<Value, reqwest::Error> {
191 let path = format!("/iserver/account/{}/order", self.account);
192 let payload = json!({"orders":orders});
193 let request = self.client.post(self.get_url(&path));
194 let response = request.body(payload.to_string()).send().await?;
195 process_response(response).await
196 }
197
198 pub async fn get_contract_detail(&self, conid: i64) -> Result<ContractDetail, reqwest::Error> {
200 let path = format!("/iserver/contract/{}/info", conid);
201 let response = self.client.get(self.get_url(&path)).body("").send().await?;
202 response.json().await
203 }
204
205 pub async fn get_market_data_history(
211 &self,
212 conid: i64,
213 exchange: Option<&str>,
214 period: &str,
215 bar: &str,
216 outside_rth: bool,
217 start_time: Option<NaiveDateTime>,
218 ) -> Result<MarketDataHistory, reqwest::Error> {
219 let path = "/iserver/marketdata/history";
220 let start_time_str = match start_time {
221 Some(start_time) => start_time.format("%Y%m%d-%H:%M:%S").to_string(),
222 None => "".to_string(),
223 };
224
225 let request = self
226 .client
227 .get(self.get_url(path))
228 .query(&[("conid", conid)])
229 .query(&[("period", period)])
230 .query(&[("bar", bar)])
231 .query(&[("exchange", exchange.unwrap_or(""))])
232 .query(&[("outsideRth", outside_rth)])
233 .query(&[("startTime", start_time_str)]);
234 let response = request.send().await?;
235 response.json().await
236 }
237}