Skip to main content

alpaca_data/options/
client.rs

1use std::fmt;
2use std::sync::Arc;
3
4use alpaca_http::RequestParts;
5use reqwest::Method;
6use serde::de::DeserializeOwned;
7
8use crate::{Error, client::ClientInner, pagination};
9
10use super::response::merge_snapshot_page;
11use super::{
12    BarsRequest, BarsResponse, ChainRequest, ChainResponse, ConditionCodesRequest,
13    ConditionCodesResponse, ExchangeCodesResponse, LatestQuotesRequest, LatestQuotesResponse,
14    LatestTradesRequest, LatestTradesResponse, SnapshotsRequest, SnapshotsResponse, TradesRequest,
15    TradesResponse,
16};
17
18const MAX_SNAPSHOT_SYMBOLS_PER_REQUEST: usize = 100;
19
20#[derive(Clone)]
21pub struct OptionsClient {
22    inner: Arc<ClientInner>,
23}
24
25impl OptionsClient {
26    pub(crate) fn new(inner: Arc<ClientInner>) -> Self {
27        Self { inner }
28    }
29
30    pub async fn bars(&self, request: BarsRequest) -> Result<BarsResponse, Error> {
31        request.validate()?;
32        self.get_json(
33            "options.bars",
34            "/v1beta1/options/bars",
35            request.into_query(),
36        )
37        .await
38    }
39
40    pub async fn bars_all(&self, request: BarsRequest) -> Result<BarsResponse, Error> {
41        let client = self.clone();
42        pagination::collect_all(request, move |request| {
43            let client = client.clone();
44            async move { client.bars(request).await }
45        })
46        .await
47    }
48
49    pub async fn trades(&self, request: TradesRequest) -> Result<TradesResponse, Error> {
50        request.validate()?;
51        self.get_json(
52            "options.trades",
53            "/v1beta1/options/trades",
54            request.into_query(),
55        )
56        .await
57    }
58
59    pub async fn trades_all(&self, request: TradesRequest) -> Result<TradesResponse, Error> {
60        let client = self.clone();
61        pagination::collect_all(request, move |request| {
62            let client = client.clone();
63            async move { client.trades(request).await }
64        })
65        .await
66    }
67
68    pub async fn latest_quotes(
69        &self,
70        request: LatestQuotesRequest,
71    ) -> Result<LatestQuotesResponse, Error> {
72        request.validate()?;
73        self.get_json(
74            "options.latest_quotes",
75            "/v1beta1/options/quotes/latest",
76            request.into_query(),
77        )
78        .await
79    }
80
81    pub async fn latest_trades(
82        &self,
83        request: LatestTradesRequest,
84    ) -> Result<LatestTradesResponse, Error> {
85        request.validate()?;
86        self.get_json(
87            "options.latest_trades",
88            "/v1beta1/options/trades/latest",
89            request.into_query(),
90        )
91        .await
92    }
93
94    pub async fn snapshots(&self, request: SnapshotsRequest) -> Result<SnapshotsResponse, Error> {
95        request.validate()?;
96        self.get_json(
97            "options.snapshots",
98            "/v1beta1/options/snapshots",
99            request.into_query(),
100        )
101        .await
102    }
103
104    pub async fn snapshots_all(
105        &self,
106        request: SnapshotsRequest,
107    ) -> Result<SnapshotsResponse, Error> {
108        request.validate_all()?;
109
110        let mut combined = SnapshotsResponse::default();
111        for batch in request.batches(MAX_SNAPSHOT_SYMBOLS_PER_REQUEST) {
112            let client = self.clone();
113            let next = pagination::collect_all(batch, move |request| {
114                let client = client.clone();
115                async move { client.snapshots(request).await }
116            })
117            .await?;
118
119            merge_snapshot_page(
120                "options.snapshots_all",
121                &mut combined.snapshots,
122                next.snapshots,
123            )?;
124        }
125
126        Ok(combined)
127    }
128
129    pub async fn chain(&self, request: ChainRequest) -> Result<ChainResponse, Error> {
130        request.validate()?;
131        let path = format!("/v1beta1/options/snapshots/{}", request.path_symbol());
132        self.get_json("options.chain", path, request.into_query())
133            .await
134    }
135
136    pub async fn chain_all(&self, request: ChainRequest) -> Result<ChainResponse, Error> {
137        let client = self.clone();
138        pagination::collect_all(request, move |request| {
139            let client = client.clone();
140            async move { client.chain(request).await }
141        })
142        .await
143    }
144
145    pub async fn condition_codes(
146        &self,
147        request: ConditionCodesRequest,
148    ) -> Result<ConditionCodesResponse, Error> {
149        let path = format!(
150            "/v1beta1/options/meta/conditions/{}",
151            request.ticktype.as_str()
152        );
153        self.get_json("options.condition_codes", path, Vec::new())
154            .await
155    }
156
157    pub async fn exchange_codes(&self) -> Result<ExchangeCodesResponse, Error> {
158        self.get_json(
159            "options.exchange_codes",
160            "/v1beta1/options/meta/exchanges",
161            Vec::new(),
162        )
163        .await
164    }
165
166    #[allow(dead_code)]
167    #[must_use]
168    pub(crate) fn inner(&self) -> &Arc<ClientInner> {
169        &self.inner
170    }
171
172    async fn get_json<Response>(
173        &self,
174        operation: &'static str,
175        path: impl Into<String>,
176        query: Vec<(String, String)>,
177    ) -> Result<Response, Error>
178    where
179        Response: DeserializeOwned,
180    {
181        let request = RequestParts::new(Method::GET, path.into())
182            .with_operation(operation)
183            .with_query(query);
184
185        self.inner
186            .send_json::<Response>(request)
187            .await
188            .map(|response| response.into_body())
189    }
190}
191
192impl fmt::Debug for OptionsClient {
193    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
194        f.debug_struct("OptionsClient").finish()
195    }
196}