1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
use reqwest::{ Client, Method, RequestBuilder, Url };
use serde::Serialize;
use snafu::ResultExt;
use std::env;
use crate::{error, Result};
const LIVE_API: &'static str = "https://api.alpaca.markets";
const PAPER_API: &'static str = "https://paper-api.alpaca.markets";
#[derive(Debug, Serialize)]
struct Authenticate {
key_id: String,
secret_key: String
}
#[derive(Debug, Serialize)]
#[serde(content = "data", rename_all="snake_case", tag = "action")]
enum ActionMessage {
Authenticate(Authenticate),
}
pub struct Alpaca {
api_key: String,
api_secret: String,
host: String
}
impl Alpaca {
async fn build(live: bool, api_key_id: &str, api_secret_key: &str) -> Result<Alpaca> {
let host = if live { LIVE_API } else { PAPER_API };
let alpaca = Alpaca {
api_key: api_key_id.to_string(),
api_secret: api_secret_key.to_string(),
host: env::var("TEST_URL").unwrap_or(host.to_string())
};
let response = alpaca.request(Method::GET, "v2/clock")?
.send().await.context(error::RequestFailed)?;
if response.status().is_success() { return Ok(alpaca) }
let status = response.status().as_u16();
match status {
401 => error::InvalidCredentials.fail()?,
403 => error::InvalidCredentials.fail()?,
_ => error::CallFailed{ url: response.url().to_string(), status }.fail()?
}
}
pub(crate) fn stream(&self) -> (String, String) {
let mut ws_host = self.host.clone();
ws_host.replace_range(..4, "ws");
ws_host.push_str("/stream");
let authenticate = ActionMessage::Authenticate(Authenticate { key_id: self.api_key.clone(), secret_key: self.api_secret.clone() });
let message = serde_json::to_string(&authenticate).context(error::InternalJSON).unwrap();
(ws_host, message)
}
pub async fn live(api_key_id: &str, api_secret_key: &str) -> Result<Alpaca> { Alpaca::build(true, api_key_id, api_secret_key).await }
pub async fn paper(api_key_id: &str, api_secret_key: &str) -> Result<Alpaca> { Alpaca::build(false, api_key_id, api_secret_key).await }
pub(crate) fn request(&self, method: Method, path: &str) -> Result<RequestBuilder> {
let url = Url::parse(&self.host).context(error::InternalURL { url: &self.host})?
.join(path).context(error::InternalURL { url: path })?;
let client = Client::new();
Ok(client.request(method, url)
.header("APCA-API-KEY-ID", self.api_key.clone())
.header("APCA-API-SECRET-KEY", self.api_secret.clone()))
}
}