etrade/
lib.rs

1#[macro_use]
2extern crate log;
3
4#[macro_use]
5extern crate serde;
6
7use std::{
8  collections::{BTreeSet, HashMap},
9  sync::Arc,
10};
11
12use anyhow::Result;
13use async_trait::async_trait;
14use secstr::SecUtf8;
15use std::sync::Mutex;
16use strum::EnumString;
17
18pub mod accounts;
19pub mod alerts;
20pub mod options;
21pub mod orders;
22mod session;
23pub mod transactions;
24
25#[cfg(all(feature = "keychain", target_os = "linux"))]
26mod linux;
27#[cfg(all(feature = "keychain", target_os = "linux"))]
28pub use linux::KeychainStore;
29
30#[cfg(all(feature = "keychain", target_os = "macos"))]
31mod macos;
32#[cfg(all(feature = "keychain", target_os = "macos"))]
33pub use macos::KeychainStore;
34
35#[cfg(all(feature = "keychain", target_os = "windows"))]
36mod windows;
37#[cfg(all(feature = "keychain", target_os = "windows"))]
38pub use windows::KeychainStore;
39
40pub use accounts::Api as Accounts;
41pub use session::CallbackProvider;
42pub use session::Session;
43pub use session::OOB;
44
45// The sandbox url to use as base url for the etrade api
46const SANDBOX_URL: &str = "https://apisb.etrade.com";
47
48// The production url to use as base url for the etrade api
49const LIVE_URL: &str = "https://api.etrade.com";
50
51fn qs_params<'a, T: serde::Serialize + serde::Deserialize<'a>>(
52  params: &T,
53) -> Result<Option<BTreeSet<(String, String)>>> {
54  let qss = serde_urlencoded::to_string(params)?;
55  let qs: BTreeSet<(String, String)> = serde_urlencoded::from_str(&qss)?;
56  if qs.is_empty() {
57    Ok(None)
58  } else {
59    Ok(Some(qs))
60  }
61}
62
63fn empty_body() -> Option<()> {
64  None
65}
66
67#[derive(Debug, Clone, Copy, Deserialize, Serialize, EnumString, strum::Display)]
68pub enum Mode {
69  Sandbox,
70  Live,
71}
72
73#[derive(Debug, Clone, Deserialize, Serialize, Default)]
74#[serde(rename_all = "camelCase", default)]
75pub struct Messages {
76  #[serde(rename = "Message", skip_serializing_if = "Vec::is_empty")]
77  pub message: Vec<Message>,
78}
79
80impl Messages {
81  pub fn is_empty(&self) -> bool {
82    self.message.is_empty()
83  }
84}
85
86#[derive(Debug, Clone, Deserialize, Serialize, Default)]
87#[serde(rename_all = "camelCase", default)]
88pub struct Message {
89  pub description: String,
90  pub code: i32,
91  #[serde(rename = "type")]
92  pub tpe: MessageType,
93}
94
95#[derive(Debug, Clone, Copy, Deserialize, Serialize, EnumString)]
96#[strum(serialize_all = "lowercase")]
97pub enum MessageType {
98  #[serde(rename = "WARNING")]
99  Warning,
100  #[serde(rename = "INFO")]
101  Info,
102  #[serde(rename = "INFO_HOLD")]
103  InfoHold,
104  #[serde(rename = "ERROR")]
105  Error,
106}
107
108impl Default for MessageType {
109  fn default() -> Self {
110    MessageType::Info
111  }
112}
113
114#[derive(Debug, Deserialize, Serialize, Default, Clone)]
115#[serde(rename_all = "camelCase", default)]
116pub struct Product {
117  pub symbol: String,
118  #[serde(skip_serializing_if = "Option::is_none")]
119  pub security_type: Option<SecurityType>,
120  #[serde(skip_serializing_if = "Option::is_none")]
121  pub security_sub_type: Option<String>,
122  pub call_put: String,
123  pub expiry_year: i32,
124  pub expiry_month: i32,
125  pub expiry_day: i32,
126  pub strike_price: f64,
127  pub expiry_type: String,
128}
129
130#[derive(Debug, Clone, Copy, Deserialize, Serialize, EnumString)]
131pub enum SecurityType {
132  #[serde(rename = "EQ")]
133  Eq,
134  #[serde(rename = "OPTN")]
135  Optn,
136  #[serde(rename = "MF")]
137  Mf,
138  #[serde(rename = "MMF")]
139  Mmf,
140}
141
142#[derive(Debug, Clone, Copy, Deserialize, Serialize, EnumString)]
143#[strum(serialize_all = "lowercase")]
144pub enum MarketSession {
145  #[serde(rename = "REGULAR")]
146  Regular,
147  #[serde(rename = "EXTENDED")]
148  Extended,
149}
150
151#[derive(Debug, Clone, Copy, Deserialize, Serialize, EnumString)]
152#[strum(serialize_all = "lowercase")]
153pub enum OptionType {
154  #[serde(rename = "CALL")]
155  Call,
156  #[serde(rename = "PUT")]
157  Put,
158}
159
160#[derive(Debug, Clone, Copy, Deserialize, Serialize, EnumString)]
161#[strum(serialize_all = "lowercase")]
162pub enum SortOrder {
163  #[serde(rename = "ASC")]
164  Asc,
165  #[serde(rename = "DESC")]
166  Desc,
167}
168
169#[derive(Debug, Clone)]
170pub struct Credentials {
171  pub key: SecUtf8,
172  pub secret: SecUtf8,
173}
174
175impl Credentials {
176  pub fn new(key: SecUtf8, secret: SecUtf8) -> Credentials {
177    Credentials { key, secret }
178  }
179}
180
181impl Into<oauth::Credentials> for Credentials {
182  fn into(self) -> oauth::Credentials {
183    oauth::Credentials::new(self.key.into_unsecure(), self.secret.into_unsecure())
184  }
185}
186
187impl<T> From<oauth::Credentials<T>> for Credentials
188where
189  T: Into<SecUtf8>,
190{
191  fn from(input: oauth::Credentials<T>) -> Self {
192    Credentials {
193      key: input.identifier.into(),
194      secret: input.secret.into(),
195    }
196  }
197}
198
199#[async_trait]
200pub trait Store {
201  async fn put(
202    &self,
203    namespace: impl Into<String> + Send,
204    key: impl Into<String> + Send,
205    value: impl Into<SecUtf8> + Send,
206  ) -> Result<()>;
207  async fn del(&self, namespace: impl AsRef<str> + Send, key: impl AsRef<str> + Send) -> Result<()>;
208  async fn get(&self, namespace: impl AsRef<str> + Send, key: impl AsRef<str> + Send) -> Result<Option<SecUtf8>>;
209}
210
211#[derive(Debug)]
212pub struct Memstore {
213  data: Arc<Mutex<HashMap<String, HashMap<String, SecUtf8>>>>,
214}
215
216impl Memstore {
217  pub fn new() -> Self {
218    Memstore {
219      data: Arc::new(Mutex::new(HashMap::new())),
220    }
221  }
222}
223
224impl Default for Memstore {
225  fn default() -> Self {
226    Memstore::new()
227  }
228}
229
230#[async_trait]
231impl Store for Memstore {
232  async fn put(
233    &self,
234    namespace: impl Into<String> + Send,
235    key: impl Into<String> + Send,
236    value: impl Into<SecUtf8> + Send,
237  ) -> Result<()> {
238    let mut data = self.data.lock().unwrap();
239
240    let svc_state = data.entry(namespace.into()).or_insert_with(HashMap::new);
241    svc_state.insert(key.into(), value.into());
242    Ok(())
243  }
244
245  async fn del(&self, namespace: impl AsRef<str> + Send, key: impl AsRef<str> + Send) -> Result<()> {
246    let mut data = self.data.lock().unwrap();
247
248    if let Some(st) = data.get_mut(namespace.as_ref()) {
249      st.remove(key.as_ref());
250    }
251    Ok(())
252  }
253
254  async fn get(&self, namespace: impl AsRef<str> + Send, key: impl AsRef<str> + Send) -> Result<Option<SecUtf8>> {
255    let data = self.data.lock().unwrap();
256    Ok(data.get(namespace.as_ref()).and_then(|r| r.get(key.as_ref()).cloned()))
257  }
258}
259
260#[cfg(test)]
261pub mod tests {
262
263  use super::{Memstore, Store};
264  use anyhow::Result;
265  use secstr::SecUtf8;
266  pub(crate) fn init() {
267    std::env::set_var("RUST_LOG", "debug");
268    let _ = pretty_env_logger::try_init();
269  }
270  #[tokio::test]
271  async fn test_mem_store() {
272    verify_token_store(Memstore::new()).await;
273  }
274
275  pub async fn verify_token_store(token_store: impl Store) {
276    let expected: Result<SecUtf8> = Ok("hello".into());
277    token_store.put("my_svc", "api_key", "hello").await.unwrap();
278    assert_eq!(token_store.get("my_svc", "api_key").await.ok(), Some(expected.ok()));
279    assert!(token_store.del("my_svc", "api_key").await.is_ok());
280    assert!(token_store.get("my_svc", "api_key").await.unwrap().is_none());
281  }
282}