Skip to main content

alpaca_api_client/market_data/options/
trades.rs

1use crate::request;
2use serde::Deserialize;
3use std::collections::HashMap;
4
5#[derive(Deserialize, Debug)]
6pub struct OptionTrade {
7    pub t: String, // Timestamp
8    pub x: String, // Exchange
9    pub p: f64,    // Price
10    pub s: i32,    // Size
11    pub c: String, // Condition
12}
13
14pub type HistoricalOptionTrades = HashMap<String, Vec<OptionTrade>>;
15pub type LatestOptionTrades = HashMap<String, OptionTrade>;
16
17#[derive(Deserialize, Debug)]
18pub struct HistoricalOptionTradesResponse {
19    trades: HistoricalOptionTrades,
20    next_page_token: Option<String>,
21}
22
23#[derive(Deserialize, Debug)]
24pub struct LatestOptionTradesResponse {
25    trades: LatestOptionTrades,
26}
27
28pub struct HistoricalOptionTradesQuery<'a> {
29    url: &'a str,
30    symbols: Vec<&'a str>,
31    start: Option<&'a str>,
32    end: Option<&'a str>,
33    limit: Option<i32>,
34    sort_asc: bool,
35    sort_desc: bool,
36}
37
38pub struct LatestOptionTradesQuery<'a> {
39    url: &'a str,
40    symbols: Vec<&'a str>,
41    feed: Option<&'a str>,
42}
43
44impl<'a> HistoricalOptionTradesQuery<'a> {
45    pub fn new(symbols: Vec<&'a str>) -> Self {
46        Self {
47            url: "https://data.alpaca.markets/v1beta1/options/trades",
48            symbols,
49            start: None,
50            end: None,
51            limit: None,
52            sort_asc: false,
53            sort_desc: false,
54        }
55    }
56
57    pub fn start(mut self, start: &'a str) -> Self {
58        self.start = Some(start);
59        self
60    }
61
62    pub fn end(mut self, end: &'a str) -> Self {
63        self.end = Some(end);
64        self
65    }
66
67    pub fn limit(mut self, limit: i32) -> Self {
68        self.limit = Some(limit);
69        self
70    }
71
72    pub fn sort_asc(mut self) -> Self {
73        self.sort_asc = true;
74        self.sort_desc = false;
75        self
76    }
77
78    pub fn sort_desc(mut self) -> Self {
79        self.sort_desc = true;
80        self.sort_asc = false;
81        self
82    }
83
84    fn build(&self) -> String {
85        let mut query = format!("symbols={}", self.symbols.join(","));
86
87        if let Some(start) = self.start {
88            query.push_str(&format!("&start={}", start));
89        }
90
91        if let Some(end) = self.end {
92            query.push_str(&format!("&end={}", end));
93        }
94
95        if let Some(limit) = self.limit {
96            query.push_str(&format!("&limit={}", limit));
97        }
98
99        if self.sort_asc {
100            query.push_str("&sort=asc");
101        } else if self.sort_desc {
102            query.push_str("&sort=desc");
103        }
104
105        format!("{}?{}", self.url, query)
106    }
107
108    pub fn send(&self) -> Result<HistoricalOptionTrades, ureq::Error> {
109        let route = self.build();
110        let mut trades: HistoricalOptionTrades = HashMap::new();
111        let mut page_token = None;
112
113        let mut i = 0;
114        let data_limit = if let Some(limit) = self.limit {
115            limit
116        } else {
117            1000
118        };
119        loop {
120            if i >= data_limit {
121                break;
122            }
123
124            // If a token exists, append to address
125            let temp_address = match page_token {
126                Some(token) => format!("{}&page_token={}", &route, &token),
127                _ => route.clone(),
128            };
129            let response = request("GET", &temp_address).call()?;
130            let response: HistoricalOptionTradesResponse = response.into_body().read_json()?;
131
132            // Add trades to collection
133            for (symbol, trade) in response.trades {
134                i += trade.len() as i32;
135                trades.entry(symbol).or_insert(Vec::new()).extend(trade);
136            }
137
138            // If a token is in response, assign to page_token for next loop
139            match response.next_page_token {
140                Some(next_page_token) => page_token = Some(next_page_token.clone()),
141                _ => break,
142            }
143        }
144
145        Ok(trades)
146    }
147}
148
149impl<'a> LatestOptionTradesQuery<'a> {
150    pub fn new(symbols: Vec<&'a str>) -> Self {
151        Self {
152            url: "https://data.alpaca.markets/v1beta1/options/trades/latest",
153            symbols,
154            feed: None,
155        }
156    }
157
158    pub fn feed(mut self, feed: &'a str) -> Self {
159        self.feed = Some(feed);
160        self
161    }
162
163    fn build(self) -> String {
164        let mut query = format!("symbols={}", self.symbols.join(","));
165
166        if let Some(feed) = self.feed {
167            query.push_str(&format!("&feed={}", feed));
168        }
169
170        format!("{}?{}", self.url, query)
171    }
172
173    pub fn send(self) -> Result<LatestOptionTrades, ureq::Error> {
174        let route = self.build();
175        let response = request("GET", &route).call()?;
176        let response: LatestOptionTradesResponse = response.into_body().read_json()?;
177        Ok(response.trades)
178    }
179}
180
181#[cfg(test)]
182mod tests {
183    use super::*;
184
185    #[test]
186    fn test_historical_option_trade_query() {
187        let res = HistoricalOptionTradesQuery::new(vec!["AAPL261218C00200000"])
188            .limit(10)
189            .send()
190            .unwrap();
191        dbg!(&res);
192        assert!(res.contains_key("AAPL261218C00200000"));
193    }
194
195    #[test]
196    fn test_latest_option_trade_query() {
197        let res = LatestOptionTradesQuery::new(vec!["AAPL261218C00200000"])
198            .feed("indicative")
199            .send()
200            .unwrap();
201        dbg!(&res);
202        assert!(res.contains_key("AAPL261218C00200000"));
203    }
204}