1use chrono::{DateTime, TimeZone, Utc};
47use futures::{stream, StreamExt};
48use itertools::Itertools;
49
50pub mod api;
51pub mod common;
52pub mod csv;
53pub mod errors;
54pub mod request;
55pub mod session;
56pub mod streamer;
57pub mod symbol;
58
59use crate::errors::*;
60pub use crate::{api::*, request::*, session::Session};
61
62const MAX_SYMBOL_SUMMARY_BATCH_SIZE: usize = 500;
63const PARALLEL_REQUESTS: usize = 10;
64
65pub async fn accounts(session: &Session) -> Result<Vec<accounts::Account>, ApiError> {
66 let url = "customers/me/accounts";
67 let response: api::Response<accounts::Response> =
68 deserialize_response(request(url, "", session).await?).await?;
69 Ok(response
70 .data
71 .items
72 .into_iter()
73 .map(|item| item.account)
74 .collect())
75}
76
77pub async fn watchlists(session: &Session) -> Result<Vec<watchlists::Item>, ApiError> {
78 let url = "watchlists";
79 let response: api::Response<watchlists::Response> =
80 deserialize_response(request(url, "", session).await?).await?;
81 Ok(response.data.items)
82}
83
84pub async fn public_watchlists(session: &Session) -> Result<Vec<watchlists::Item>, ApiError> {
85 let url = "public-watchlists";
86 let response: api::Response<watchlists::Response> =
87 deserialize_response(request(url, "", session).await?).await?;
88 Ok(response.data.items)
89}
90
91pub async fn balances(
92 account: &accounts::Account,
93 session: &Session,
94) -> Result<balances::Data, ApiError> {
95 let url = format!("accounts/{}/balances", account.account_number);
96 let response: api::Response<balances::Data> =
97 deserialize_response(request(&url, "", session).await?).await?;
98 Ok(response.data)
99}
100
101pub async fn positions(
102 account: &accounts::Account,
103 session: &Session,
104) -> Result<Vec<positions::Item>, ApiError> {
105 let url = format!("accounts/{}/positions", account.account_number);
106 let response: api::Response<positions::Response> =
107 deserialize_response(request(&url, "", session).await?).await?;
108 Ok(response.data.items)
109}
110
111pub async fn transactions<Tz: TimeZone>(
112 account: &accounts::Account,
113 start_date: DateTime<Tz>,
114 end_date: DateTime<Tz>,
115 prev_pagination: Option<Pagination>,
116 session: &Session,
117) -> Result<Option<(Vec<transactions::Item>, Option<Pagination>)>, ApiError> {
118 let page_offset = if let Some(api::Pagination {
119 page_offset,
120 total_pages,
121 ..
122 }) = prev_pagination
123 {
124 if page_offset + 1 >= total_pages {
125 return Ok(None);
126 }
127 page_offset + 1
128 } else {
129 0
130 };
131
132 let url = format!("accounts/{}/transactions", account.account_number);
133 let parameters = format!(
134 "start-date={}&end-date={}&page-offset={}",
135 start_date.with_timezone(&Utc),
136 end_date.with_timezone(&Utc),
137 page_offset
138 );
139 let response: api::Response<transactions::Response> =
140 deserialize_response(request(&url, ¶meters, session).await?).await?;
141
142 Ok(Some((response.data.items, response.pagination)))
143}
144
145pub async fn market_metrics(
146 symbols: &[String],
147 session: &Session,
148) -> Result<Vec<market_metrics::Item>, ApiError> {
149 let results = stream::iter(symbols.chunks(MAX_SYMBOL_SUMMARY_BATCH_SIZE).map(
150 |batch| async move {
151 let symbols = batch.iter().cloned().join(",");
152
153 let url_path = "market-metrics";
154 let params_string = &format!("symbols={}", symbols);
155 let response: Result<api::Response<market_metrics::Response>, ApiError> =
156 deserialize_response(request(url_path, params_string, session).await?).await;
157
158 response
159 },
160 ))
161 .buffered(PARALLEL_REQUESTS)
162 .collect::<Vec<_>>()
163 .await;
164
165 let mut json = vec![];
166 for result in results.into_iter() {
167 json.append(&mut result?.data.items);
168 }
169
170 Ok(json)
171}
172
173pub async fn option_chains(
174 symbol: &str,
175 session: &Session,
176) -> Result<Vec<option_chains::Item>, ApiError> {
177 let url = format!("option-chains/{}/nested", symbol);
178 let response: api::Response<option_chains::Response> =
179 deserialize_response(request(&url, "", session).await?).await?;
180 Ok(response.data.items)
181}