alpaca_http/
endpoints.rs

1//! HTTP API endpoints for Alpaca trading platform.
2//!
3//! This module provides implementations for all Alpaca REST API endpoints
4//! including account management, order operations, position management,
5//! market data, and more.
6
7#![allow(missing_docs)]
8
9use crate::client::AlpacaHttpClient;
10use alpaca_base::{OAuthToken, Result, types::*};
11use chrono::{DateTime, NaiveDate, Utc};
12use serde::{Deserialize, Serialize};
13use uuid::Uuid;
14
15impl AlpacaHttpClient {
16    // Account endpoints
17
18    /// Get account information
19    pub async fn get_account(&self) -> Result<Account> {
20        self.get("/v2/account").await
21    }
22
23    /// Get account configurations
24    pub async fn get_account_configurations(&self) -> Result<AccountConfigurations> {
25        self.get("/v2/account/configurations").await
26    }
27
28    /// Update account configurations
29    pub async fn update_account_configurations(
30        &self,
31        config: &AccountConfigurations,
32    ) -> Result<AccountConfigurations> {
33        self.patch("/v2/account/configurations", config).await
34    }
35
36    /// Get account activities
37    pub async fn get_account_activities(
38        &self,
39        params: &ActivityParams,
40    ) -> Result<Vec<AccountActivity>> {
41        self.get_with_params("/v2/account/activities", params).await
42    }
43
44    /// Get portfolio history
45    pub async fn get_portfolio_history(
46        &self,
47        params: &PortfolioHistoryParams,
48    ) -> Result<PortfolioHistory> {
49        self.get_with_params("/v2/account/portfolio/history", params)
50            .await
51    }
52
53    // Asset endpoints
54
55    /// Get all assets
56    pub async fn get_assets(&self, params: &AssetParams) -> Result<Vec<Asset>> {
57        self.get_with_params("/v2/assets", params).await
58    }
59
60    /// Get asset by ID
61    pub async fn get_asset(&self, asset_id: &Uuid) -> Result<Asset> {
62        self.get(&format!("/v2/assets/{}", asset_id)).await
63    }
64
65    /// Get asset by symbol
66    pub async fn get_asset_by_symbol(&self, symbol: &str) -> Result<Asset> {
67        self.get(&format!("/v2/assets/{}", symbol)).await
68    }
69
70    // Order endpoints
71
72    /// Get all orders
73    pub async fn get_orders(&self, params: &OrderParams) -> Result<Vec<Order>> {
74        self.get_with_params("/v2/orders", params).await
75    }
76
77    /// Create a new order
78    pub async fn create_order(&self, order: &CreateOrderRequest) -> Result<Order> {
79        self.post("/v2/orders", order).await
80    }
81
82    /// Get order by ID
83    pub async fn get_order(&self, order_id: &Uuid) -> Result<Order> {
84        self.get(&format!("/v2/orders/{}", order_id)).await
85    }
86
87    /// Get order by client order ID
88    pub async fn get_order_by_client_id(&self, client_order_id: &str) -> Result<Order> {
89        self.get(&format!(
90            "/v2/orders:by_client_order_id?client_order_id={}",
91            client_order_id
92        ))
93        .await
94    }
95
96    /// Replace an order
97    pub async fn replace_order(
98        &self,
99        order_id: &Uuid,
100        order: &ReplaceOrderRequest,
101    ) -> Result<Order> {
102        self.patch(&format!("/v2/orders/{}", order_id), order).await
103    }
104
105    /// Cancel an order
106    pub async fn cancel_order(&self, order_id: &Uuid) -> Result<()> {
107        self.delete(&format!("/v2/orders/{}", order_id)).await
108    }
109
110    /// Cancel all orders
111    pub async fn cancel_all_orders(&self) -> Result<Vec<CancelOrderResponse>> {
112        self.delete("/v2/orders").await
113    }
114
115    // Position endpoints
116
117    /// Get all positions
118    pub async fn get_positions(&self) -> Result<Vec<Position>> {
119        self.get("/v2/positions").await
120    }
121
122    /// Get position by symbol
123    pub async fn get_position(&self, symbol: &str) -> Result<Position> {
124        self.get(&format!("/v2/positions/{}", symbol)).await
125    }
126
127    /// Close all positions
128    pub async fn close_all_positions(
129        &self,
130        cancel_orders: bool,
131    ) -> Result<Vec<ClosePositionResponse>> {
132        let url = format!("/v2/positions?cancel_orders={}", cancel_orders);
133        self.delete(&url).await
134    }
135
136    /// Close position by symbol
137    pub async fn close_position(
138        &self,
139        symbol: &str,
140        _params: &ClosePositionRequest,
141    ) -> Result<Order> {
142        self.delete(&format!("/v2/positions/{}", symbol)).await
143    }
144
145    // Watchlist endpoints
146
147    /// Get all watchlists
148    pub async fn get_watchlists(&self) -> Result<Vec<Watchlist>> {
149        self.get("/v2/watchlists").await
150    }
151
152    /// Create a new watchlist
153    pub async fn create_watchlist(&self, watchlist: &CreateWatchlistRequest) -> Result<Watchlist> {
154        self.post("/v2/watchlists", watchlist).await
155    }
156
157    /// Get watchlist by ID
158    pub async fn get_watchlist(&self, watchlist_id: &Uuid) -> Result<Watchlist> {
159        self.get(&format!("/v2/watchlists/{}", watchlist_id)).await
160    }
161
162    /// Update watchlist
163    pub async fn update_watchlist(
164        &self,
165        watchlist_id: &Uuid,
166        watchlist: &UpdateWatchlistRequest,
167    ) -> Result<Watchlist> {
168        self.put(&format!("/v2/watchlists/{}", watchlist_id), watchlist)
169            .await
170    }
171
172    /// Delete watchlist
173    pub async fn delete_watchlist(&self, watchlist_id: &Uuid) -> Result<()> {
174        self.delete(&format!("/v2/watchlists/{}", watchlist_id))
175            .await
176    }
177
178    /// Add asset to watchlist
179    pub async fn add_to_watchlist(&self, watchlist_id: &Uuid, symbol: &str) -> Result<Watchlist> {
180        let request = AddToWatchlistRequest {
181            symbol: symbol.to_string(),
182        };
183        self.post(&format!("/v2/watchlists/{}", watchlist_id), &request)
184            .await
185    }
186
187    /// Remove asset from watchlist
188    pub async fn remove_from_watchlist(&self, watchlist_id: &Uuid, symbol: &str) -> Result<()> {
189        self.delete(&format!("/v2/watchlists/{}/{}", watchlist_id, symbol))
190            .await
191    }
192
193    // Market data endpoints
194
195    /// Get bars for a symbol
196    pub async fn get_bars(&self, symbol: &str, params: &BarsParams) -> Result<BarsResponse> {
197        self.get_with_params(&format!("/v2/stocks/{}/bars", symbol), params)
198            .await
199    }
200
201    /// Get quotes for a symbol
202    pub async fn get_quotes(&self, symbol: &str, params: &QuotesParams) -> Result<QuotesResponse> {
203        self.get_with_params(&format!("/v2/stocks/{}/quotes", symbol), params)
204            .await
205    }
206
207    /// Get trades for a symbol
208    pub async fn get_trades(&self, symbol: &str, params: &TradesParams) -> Result<TradesResponse> {
209        self.get_with_params(&format!("/v2/stocks/{}/trades", symbol), params)
210            .await
211    }
212
213    /// Get latest bar for a symbol
214    pub async fn get_latest_bar(&self, symbol: &str) -> Result<LatestBarResponse> {
215        self.get(&format!("/v2/stocks/{}/bars/latest", symbol))
216            .await
217    }
218
219    /// Get latest quote for a symbol
220    pub async fn get_latest_quote(&self, symbol: &str) -> Result<LatestQuoteResponse> {
221        self.get(&format!("/v2/stocks/{}/quotes/latest", symbol))
222            .await
223    }
224
225    /// Get latest trade for a symbol
226    pub async fn get_latest_trade(&self, symbol: &str) -> Result<LatestTradeResponse> {
227        self.get(&format!("/v2/stocks/{}/trades/latest", symbol))
228            .await
229    }
230
231    // Calendar and clock endpoints
232
233    /// Get market calendar
234    pub async fn get_calendar(&self, params: &CalendarParams) -> Result<Vec<Calendar>> {
235        self.get_with_params("/v2/calendar", params).await
236    }
237
238    /// Get market clock
239    pub async fn get_clock(&self) -> Result<Clock> {
240        self.get("/v2/clock").await
241    }
242
243    // News endpoints
244
245    /// Get news articles
246    pub async fn get_news(&self, params: &NewsParams) -> Result<NewsResponse> {
247        self.get_with_params("/v1beta1/news", params).await
248    }
249
250    // Crypto endpoints
251
252    /// Get crypto bars
253    pub async fn get_crypto_bars(
254        &self,
255        symbol: &str,
256        params: &CryptoBarsParams,
257    ) -> Result<CryptoBarsResponse> {
258        self.get_with_params(&format!("/v1beta1/crypto/{}/bars", symbol), params)
259            .await
260    }
261
262    /// Get crypto quotes
263    pub async fn get_crypto_quotes(
264        &self,
265        symbol: &str,
266        params: &CryptoQuotesParams,
267    ) -> Result<CryptoQuotesResponse> {
268        self.get_with_params(&format!("/v1beta1/crypto/{}/quotes", symbol), params)
269            .await
270    }
271
272    /// Get crypto trades
273    pub async fn get_crypto_trades(
274        &self,
275        symbol: &str,
276        params: &CryptoTradesParams,
277    ) -> Result<CryptoTradesResponse> {
278        self.get_with_params(&format!("/v1beta1/crypto/{}/trades", symbol), params)
279            .await
280    }
281}
282
283// Request/Response types
284
285#[derive(Debug, Serialize, Deserialize)]
286pub struct AccountConfigurations {
287    pub dtbp_check: Option<String>,
288    pub trade_confirm_email: Option<String>,
289    pub suspend_trade: Option<bool>,
290    pub no_shorting: Option<bool>,
291    pub max_margin_multiplier: Option<String>,
292    pub pdt_check: Option<String>,
293    pub max_dte: Option<i32>,
294}
295
296#[derive(Debug, Serialize, Deserialize)]
297pub struct ActivityParams {
298    pub activity_type: Option<ActivityType>,
299    pub date: Option<String>,
300    pub until: Option<String>,
301    pub after: Option<String>,
302    pub direction: Option<String>,
303    pub page_size: Option<u32>,
304    pub page_token: Option<String>,
305}
306
307#[derive(Debug, Serialize, Deserialize)]
308pub struct PortfolioHistoryParams {
309    pub period: Option<String>,
310    pub timeframe: Option<String>,
311    pub date_end: Option<String>,
312    pub extended_hours: Option<bool>,
313}
314
315#[derive(Debug, Serialize, Deserialize, Default)]
316pub struct AssetParams {
317    pub status: Option<AssetStatus>,
318    pub asset_class: Option<AssetClass>,
319    pub exchange: Option<String>,
320    pub attributes: Option<String>,
321}
322
323/// Parameters for querying orders.
324#[derive(Debug, Default, Serialize, Deserialize)]
325pub struct OrderParams {
326    /// Filter by order status (open, closed, all).
327    pub status: Option<OrderQueryStatus>,
328    /// Maximum number of orders to return (default 50, max 500).
329    pub limit: Option<u32>,
330    /// Filter orders created after this timestamp.
331    pub after: Option<DateTime<Utc>>,
332    /// Filter orders created until this timestamp.
333    pub until: Option<DateTime<Utc>>,
334    /// Sort direction (asc or desc).
335    pub direction: Option<SortDirection>,
336    /// Include nested leg orders for multi-leg orders.
337    pub nested: Option<bool>,
338    /// Comma-separated list of symbols to filter by.
339    pub symbols: Option<String>,
340    /// Filter by order side (buy or sell).
341    pub side: Option<OrderSide>,
342}
343
344impl OrderParams {
345    /// Creates a new empty OrderParams.
346    #[must_use]
347    pub fn new() -> Self {
348        Self::default()
349    }
350
351    /// Sets the status filter.
352    #[must_use]
353    pub fn status(mut self, status: OrderQueryStatus) -> Self {
354        self.status = Some(status);
355        self
356    }
357
358    /// Sets the limit.
359    #[must_use]
360    pub fn limit(mut self, limit: u32) -> Self {
361        self.limit = Some(limit);
362        self
363    }
364
365    /// Sets the after timestamp filter.
366    #[must_use]
367    pub fn after(mut self, after: DateTime<Utc>) -> Self {
368        self.after = Some(after);
369        self
370    }
371
372    /// Sets the until timestamp filter.
373    #[must_use]
374    pub fn until(mut self, until: DateTime<Utc>) -> Self {
375        self.until = Some(until);
376        self
377    }
378
379    /// Sets the sort direction.
380    #[must_use]
381    pub fn direction(mut self, direction: SortDirection) -> Self {
382        self.direction = Some(direction);
383        self
384    }
385
386    /// Enables nested leg orders in response.
387    #[must_use]
388    pub fn nested(mut self, nested: bool) -> Self {
389        self.nested = Some(nested);
390        self
391    }
392
393    /// Sets the symbols filter.
394    #[must_use]
395    pub fn symbols(mut self, symbols: impl Into<String>) -> Self {
396        self.symbols = Some(symbols.into());
397        self
398    }
399
400    /// Sets the side filter.
401    #[must_use]
402    pub fn side(mut self, side: OrderSide) -> Self {
403        self.side = Some(side);
404        self
405    }
406}
407
408/// Request to create a new order.
409///
410/// Supports all order types including simple, bracket, OCO, and OTO orders.
411#[derive(Debug, Default, Serialize, Deserialize)]
412pub struct CreateOrderRequest {
413    /// The symbol to trade.
414    pub symbol: String,
415    /// The quantity to trade (mutually exclusive with notional).
416    pub qty: Option<String>,
417    /// The notional dollar amount to trade (mutually exclusive with qty).
418    pub notional: Option<String>,
419    /// The side of the order (buy or sell).
420    pub side: OrderSide,
421    /// The type of order.
422    #[serde(rename = "type")]
423    pub order_type: OrderType,
424    /// How long the order remains active.
425    pub time_in_force: TimeInForce,
426    /// Limit price for limit orders.
427    pub limit_price: Option<String>,
428    /// Stop price for stop orders.
429    pub stop_price: Option<String>,
430    /// Trail price for trailing stop orders (dollar amount).
431    pub trail_price: Option<String>,
432    /// Trail percent for trailing stop orders (percentage).
433    pub trail_percent: Option<String>,
434    /// Whether to allow trading during extended hours.
435    pub extended_hours: Option<bool>,
436    /// Client-specified order ID for idempotency.
437    pub client_order_id: Option<String>,
438    /// Order class (simple, bracket, oco, oto).
439    pub order_class: Option<OrderClass>,
440    /// Take profit configuration for bracket orders.
441    pub take_profit: Option<TakeProfit>,
442    /// Stop loss configuration for bracket orders.
443    pub stop_loss: Option<StopLoss>,
444    /// Position intent for options orders.
445    pub position_intent: Option<PositionIntent>,
446    /// Good-till-date expiration (for GTD orders).
447    #[serde(skip_serializing_if = "Option::is_none")]
448    pub gtd_date: Option<NaiveDate>,
449}
450
451impl CreateOrderRequest {
452    /// Creates a new market order request.
453    #[must_use]
454    pub fn market(symbol: impl Into<String>, side: OrderSide, qty: impl Into<String>) -> Self {
455        Self {
456            symbol: symbol.into(),
457            qty: Some(qty.into()),
458            side,
459            order_type: OrderType::Market,
460            time_in_force: TimeInForce::Day,
461            ..Default::default()
462        }
463    }
464
465    /// Creates a new limit order request.
466    #[must_use]
467    pub fn limit(
468        symbol: impl Into<String>,
469        side: OrderSide,
470        qty: impl Into<String>,
471        limit_price: impl Into<String>,
472    ) -> Self {
473        Self {
474            symbol: symbol.into(),
475            qty: Some(qty.into()),
476            side,
477            order_type: OrderType::Limit,
478            time_in_force: TimeInForce::Day,
479            limit_price: Some(limit_price.into()),
480            ..Default::default()
481        }
482    }
483
484    /// Creates a new stop order request.
485    #[must_use]
486    pub fn stop(
487        symbol: impl Into<String>,
488        side: OrderSide,
489        qty: impl Into<String>,
490        stop_price: impl Into<String>,
491    ) -> Self {
492        Self {
493            symbol: symbol.into(),
494            qty: Some(qty.into()),
495            side,
496            order_type: OrderType::Stop,
497            time_in_force: TimeInForce::Day,
498            stop_price: Some(stop_price.into()),
499            ..Default::default()
500        }
501    }
502
503    /// Creates a new stop-limit order request.
504    #[must_use]
505    pub fn stop_limit(
506        symbol: impl Into<String>,
507        side: OrderSide,
508        qty: impl Into<String>,
509        stop_price: impl Into<String>,
510        limit_price: impl Into<String>,
511    ) -> Self {
512        Self {
513            symbol: symbol.into(),
514            qty: Some(qty.into()),
515            side,
516            order_type: OrderType::StopLimit,
517            time_in_force: TimeInForce::Day,
518            stop_price: Some(stop_price.into()),
519            limit_price: Some(limit_price.into()),
520            ..Default::default()
521        }
522    }
523
524    /// Creates a new trailing stop order request with a trail price.
525    #[must_use]
526    pub fn trailing_stop_price(
527        symbol: impl Into<String>,
528        side: OrderSide,
529        qty: impl Into<String>,
530        trail_price: impl Into<String>,
531    ) -> Self {
532        Self {
533            symbol: symbol.into(),
534            qty: Some(qty.into()),
535            side,
536            order_type: OrderType::TrailingStop,
537            time_in_force: TimeInForce::Day,
538            trail_price: Some(trail_price.into()),
539            ..Default::default()
540        }
541    }
542
543    /// Creates a new trailing stop order request with a trail percent.
544    #[must_use]
545    pub fn trailing_stop_percent(
546        symbol: impl Into<String>,
547        side: OrderSide,
548        qty: impl Into<String>,
549        trail_percent: impl Into<String>,
550    ) -> Self {
551        Self {
552            symbol: symbol.into(),
553            qty: Some(qty.into()),
554            side,
555            order_type: OrderType::TrailingStop,
556            time_in_force: TimeInForce::Day,
557            trail_percent: Some(trail_percent.into()),
558            ..Default::default()
559        }
560    }
561
562    /// Creates a new bracket order request.
563    ///
564    /// A bracket order is a set of three orders: a primary order and two
565    /// conditional orders (take profit and stop loss) that are triggered
566    /// when the primary order fills.
567    #[must_use]
568    pub fn bracket(
569        symbol: impl Into<String>,
570        side: OrderSide,
571        qty: impl Into<String>,
572        order_type: OrderType,
573        take_profit: TakeProfit,
574        stop_loss: StopLoss,
575    ) -> Self {
576        Self {
577            symbol: symbol.into(),
578            qty: Some(qty.into()),
579            side,
580            order_type,
581            time_in_force: TimeInForce::Day,
582            order_class: Some(OrderClass::Bracket),
583            take_profit: Some(take_profit),
584            stop_loss: Some(stop_loss),
585            ..Default::default()
586        }
587    }
588
589    /// Creates a new OCO (One-Cancels-Other) order request.
590    ///
591    /// An OCO order is a set of two orders where if one is executed,
592    /// the other is automatically canceled.
593    #[must_use]
594    pub fn oco(
595        symbol: impl Into<String>,
596        side: OrderSide,
597        qty: impl Into<String>,
598        take_profit: TakeProfit,
599        stop_loss: StopLoss,
600    ) -> Self {
601        Self {
602            symbol: symbol.into(),
603            qty: Some(qty.into()),
604            side,
605            order_type: OrderType::Limit,
606            time_in_force: TimeInForce::Day,
607            order_class: Some(OrderClass::Oco),
608            take_profit: Some(take_profit),
609            stop_loss: Some(stop_loss),
610            ..Default::default()
611        }
612    }
613
614    /// Creates a new OTO (One-Triggers-Other) order request.
615    ///
616    /// An OTO order is a primary order that, when filled, triggers
617    /// a secondary order.
618    #[must_use]
619    pub fn oto(
620        symbol: impl Into<String>,
621        side: OrderSide,
622        qty: impl Into<String>,
623        order_type: OrderType,
624        stop_loss: StopLoss,
625    ) -> Self {
626        Self {
627            symbol: symbol.into(),
628            qty: Some(qty.into()),
629            side,
630            order_type,
631            time_in_force: TimeInForce::Day,
632            order_class: Some(OrderClass::Oto),
633            stop_loss: Some(stop_loss),
634            ..Default::default()
635        }
636    }
637
638    /// Sets the time in force for the order.
639    #[must_use]
640    pub fn time_in_force(mut self, tif: TimeInForce) -> Self {
641        self.time_in_force = tif;
642        self
643    }
644
645    /// Sets the limit price for the order.
646    #[must_use]
647    pub fn with_limit_price(mut self, price: impl Into<String>) -> Self {
648        self.limit_price = Some(price.into());
649        self
650    }
651
652    /// Enables extended hours trading.
653    #[must_use]
654    pub fn extended_hours(mut self, enabled: bool) -> Self {
655        self.extended_hours = Some(enabled);
656        self
657    }
658
659    /// Sets a client order ID for idempotency.
660    #[must_use]
661    pub fn client_order_id(mut self, id: impl Into<String>) -> Self {
662        self.client_order_id = Some(id.into());
663        self
664    }
665
666    /// Sets the position intent for options orders.
667    #[must_use]
668    pub fn position_intent(mut self, intent: PositionIntent) -> Self {
669        self.position_intent = Some(intent);
670        self
671    }
672
673    /// Sets the GTD (Good Till Date) expiration date.
674    #[must_use]
675    pub fn gtd_date(mut self, date: NaiveDate) -> Self {
676        self.time_in_force = TimeInForce::Gtd;
677        self.gtd_date = Some(date);
678        self
679    }
680}
681
682/// Request to replace (modify) an existing order.
683#[derive(Debug, Default, Serialize, Deserialize)]
684pub struct ReplaceOrderRequest {
685    /// New quantity for the order.
686    pub qty: Option<String>,
687    /// New time in force.
688    pub time_in_force: Option<TimeInForce>,
689    /// New limit price.
690    pub limit_price: Option<String>,
691    /// New stop price.
692    pub stop_price: Option<String>,
693    /// New trail value (price or percent depending on original order).
694    pub trail: Option<String>,
695    /// New client order ID.
696    pub client_order_id: Option<String>,
697}
698
699impl ReplaceOrderRequest {
700    /// Creates a new empty replace order request.
701    #[must_use]
702    pub fn new() -> Self {
703        Self::default()
704    }
705
706    /// Sets the new quantity.
707    #[must_use]
708    pub fn qty(mut self, qty: impl Into<String>) -> Self {
709        self.qty = Some(qty.into());
710        self
711    }
712
713    /// Sets the new time in force.
714    #[must_use]
715    pub fn time_in_force(mut self, tif: TimeInForce) -> Self {
716        self.time_in_force = Some(tif);
717        self
718    }
719
720    /// Sets the new limit price.
721    #[must_use]
722    pub fn limit_price(mut self, price: impl Into<String>) -> Self {
723        self.limit_price = Some(price.into());
724        self
725    }
726
727    /// Sets the new stop price.
728    #[must_use]
729    pub fn stop_price(mut self, price: impl Into<String>) -> Self {
730        self.stop_price = Some(price.into());
731        self
732    }
733
734    /// Sets the new trail value.
735    #[must_use]
736    pub fn trail(mut self, trail: impl Into<String>) -> Self {
737        self.trail = Some(trail.into());
738        self
739    }
740
741    /// Sets the new client order ID.
742    #[must_use]
743    pub fn client_order_id(mut self, id: impl Into<String>) -> Self {
744        self.client_order_id = Some(id.into());
745        self
746    }
747}
748
749#[derive(Debug, Serialize, Deserialize)]
750pub struct CancelOrderResponse {
751    pub id: Uuid,
752    pub status: i32,
753}
754
755#[derive(Debug, Serialize, Deserialize)]
756pub struct ClosePositionResponse {
757    pub symbol: String,
758    pub status: i32,
759}
760
761/// Request to close a position.
762#[derive(Debug, Serialize, Deserialize, Default)]
763pub struct ClosePositionRequest {
764    /// Quantity to close.
765    pub qty: Option<String>,
766    /// Percentage of position to close.
767    pub percentage: Option<String>,
768}
769
770impl ClosePositionRequest {
771    /// Create a new close position request (closes entire position).
772    #[must_use]
773    pub fn new() -> Self {
774        Self::default()
775    }
776
777    /// Set quantity to close.
778    #[must_use]
779    pub fn qty(mut self, qty: impl Into<String>) -> Self {
780        self.qty = Some(qty.into());
781        self
782    }
783
784    /// Set percentage to close.
785    #[must_use]
786    pub fn percentage(mut self, percentage: impl Into<String>) -> Self {
787        self.percentage = Some(percentage.into());
788        self
789    }
790}
791
792#[derive(Debug, Serialize, Deserialize)]
793pub struct CreateWatchlistRequest {
794    pub name: String,
795    pub symbols: Option<Vec<String>>,
796}
797
798#[derive(Debug, Serialize, Deserialize)]
799pub struct UpdateWatchlistRequest {
800    pub name: Option<String>,
801    pub symbols: Option<Vec<String>>,
802}
803
804#[derive(Debug, Serialize, Deserialize)]
805pub struct AddToWatchlistRequest {
806    pub symbol: String,
807}
808
809#[derive(Debug, Serialize, Deserialize, Default)]
810pub struct BarsParams {
811    pub start: Option<DateTime<Utc>>,
812    pub end: Option<DateTime<Utc>>,
813    pub timeframe: Option<String>,
814    pub page_token: Option<String>,
815    pub limit: Option<u32>,
816    pub asof: Option<String>,
817    pub feed: Option<String>,
818    pub sort: Option<String>,
819}
820
821#[derive(Debug, Serialize, Deserialize)]
822pub struct BarsResponse {
823    pub bars: Vec<Bar>,
824    pub symbol: String,
825    pub next_page_token: Option<String>,
826}
827
828#[derive(Debug, Serialize, Deserialize, Default)]
829pub struct QuotesParams {
830    pub start: Option<DateTime<Utc>>,
831    pub end: Option<DateTime<Utc>>,
832    pub page_token: Option<String>,
833    pub limit: Option<u32>,
834    pub asof: Option<String>,
835    pub feed: Option<String>,
836    pub sort: Option<String>,
837}
838
839#[derive(Debug, Serialize, Deserialize)]
840pub struct QuotesResponse {
841    pub quotes: Vec<Quote>,
842    pub symbol: String,
843    pub next_page_token: Option<String>,
844}
845
846#[derive(Debug, Serialize, Deserialize, Default)]
847pub struct TradesParams {
848    pub start: Option<DateTime<Utc>>,
849    pub end: Option<DateTime<Utc>>,
850    pub page_token: Option<String>,
851    pub limit: Option<u32>,
852    pub asof: Option<String>,
853    pub feed: Option<String>,
854    pub sort: Option<String>,
855}
856
857#[derive(Debug, Serialize, Deserialize)]
858pub struct TradesResponse {
859    pub trades: Vec<Trade>,
860    pub symbol: String,
861    pub next_page_token: Option<String>,
862}
863
864#[derive(Debug, Serialize, Deserialize)]
865pub struct LatestBarResponse {
866    pub bar: Bar,
867    pub symbol: String,
868}
869
870#[derive(Debug, Serialize, Deserialize)]
871pub struct LatestQuoteResponse {
872    pub quote: Quote,
873    pub symbol: String,
874}
875
876#[derive(Debug, Serialize, Deserialize)]
877pub struct LatestTradeResponse {
878    pub trade: Trade,
879    pub symbol: String,
880}
881
882#[derive(Debug, Serialize, Deserialize, Default)]
883pub struct CalendarParams {
884    pub start: Option<String>,
885    pub end: Option<String>,
886}
887
888impl CalendarParams {
889    /// Create new calendar params.
890    #[must_use]
891    pub fn new() -> Self {
892        Self::default()
893    }
894
895    /// Set start date.
896    #[must_use]
897    pub fn start(mut self, start: &str) -> Self {
898        self.start = Some(start.to_string());
899        self
900    }
901
902    /// Set end date.
903    #[must_use]
904    pub fn end(mut self, end: &str) -> Self {
905        self.end = Some(end.to_string());
906        self
907    }
908}
909
910#[derive(Debug, Serialize, Deserialize)]
911pub struct NewsParams {
912    pub symbols: Option<String>,
913    pub start: Option<DateTime<Utc>>,
914    pub end: Option<DateTime<Utc>>,
915    pub sort: Option<String>,
916    pub include_content: Option<bool>,
917    pub exclude_contentless: Option<bool>,
918    pub page_token: Option<String>,
919    pub limit: Option<u32>,
920}
921
922#[derive(Debug, Serialize, Deserialize)]
923pub struct NewsResponse {
924    pub news: Vec<NewsArticle>,
925    pub next_page_token: Option<String>,
926}
927
928#[derive(Debug, Serialize, Deserialize)]
929pub struct CryptoBarsParams {
930    pub start: Option<DateTime<Utc>>,
931    pub end: Option<DateTime<Utc>>,
932    pub timeframe: Option<String>,
933    pub page_token: Option<String>,
934    pub limit: Option<u32>,
935    pub sort: Option<String>,
936}
937
938#[derive(Debug, Serialize, Deserialize)]
939pub struct CryptoBarsResponse {
940    pub bars: Vec<Bar>,
941    pub symbol: String,
942    pub next_page_token: Option<String>,
943}
944
945#[derive(Debug, Serialize, Deserialize)]
946pub struct CryptoQuotesParams {
947    pub start: Option<DateTime<Utc>>,
948    pub end: Option<DateTime<Utc>>,
949    pub page_token: Option<String>,
950    pub limit: Option<u32>,
951    pub sort: Option<String>,
952}
953
954#[derive(Debug, Serialize, Deserialize)]
955pub struct CryptoQuotesResponse {
956    pub quotes: Vec<Quote>,
957    pub symbol: String,
958    pub next_page_token: Option<String>,
959}
960
961#[derive(Debug, Serialize, Deserialize)]
962pub struct CryptoTradesParams {
963    pub start: Option<DateTime<Utc>>,
964    pub end: Option<DateTime<Utc>>,
965    pub page_token: Option<String>,
966    pub limit: Option<u32>,
967    pub sort: Option<String>,
968}
969
970#[derive(Debug, Serialize, Deserialize)]
971pub struct CryptoTradesResponse {
972    pub trades: Vec<Trade>,
973    pub symbol: String,
974    pub next_page_token: Option<String>,
975}
976
977// ============================================================================
978// Options Trading Endpoints
979// ============================================================================
980
981/// Response for listing option contracts.
982#[derive(Debug, Serialize, Deserialize)]
983pub struct OptionContractsResponse {
984    /// List of option contracts.
985    pub option_contracts: Vec<OptionContract>,
986    /// Token for next page of results.
987    pub next_page_token: Option<String>,
988}
989
990/// Response for option bars.
991#[derive(Debug, Serialize, Deserialize)]
992pub struct OptionBarsResponse {
993    /// Map of symbol to bars.
994    pub bars: std::collections::HashMap<String, Vec<OptionBar>>,
995    /// Token for next page of results.
996    pub next_page_token: Option<String>,
997}
998
999/// Response for option snapshots.
1000#[derive(Debug, Serialize, Deserialize)]
1001pub struct OptionSnapshotsResponse {
1002    /// Map of symbol to snapshot.
1003    pub snapshots: std::collections::HashMap<String, OptionSnapshot>,
1004}
1005
1006impl AlpacaHttpClient {
1007    // ========================================================================
1008    // Options Contract Endpoints
1009    // ========================================================================
1010
1011    /// List option contracts with optional filters.
1012    ///
1013    /// # Arguments
1014    /// * `params` - Query parameters for filtering contracts
1015    ///
1016    /// # Returns
1017    /// List of option contracts matching the filters
1018    pub async fn get_option_contracts(
1019        &self,
1020        params: &OptionContractParams,
1021    ) -> Result<OptionContractsResponse> {
1022        self.get_with_params("/v2/options/contracts", params).await
1023    }
1024
1025    /// Get a specific option contract by symbol or ID.
1026    ///
1027    /// # Arguments
1028    /// * `symbol_or_id` - OCC symbol or contract ID
1029    ///
1030    /// # Returns
1031    /// The option contract details
1032    pub async fn get_option_contract(&self, symbol_or_id: &str) -> Result<OptionContract> {
1033        self.get(&format!("/v2/options/contracts/{}", symbol_or_id))
1034            .await
1035    }
1036
1037    /// Exercise an option contract.
1038    ///
1039    /// # Arguments
1040    /// * `request` - Exercise request with symbol and quantity
1041    ///
1042    /// # Returns
1043    /// The resulting order from exercising the option
1044    pub async fn exercise_option(&self, request: &OptionExerciseRequest) -> Result<Order> {
1045        self.post("/v2/options/exercise", request).await
1046    }
1047
1048    // ========================================================================
1049    // Options Market Data Endpoints
1050    // ========================================================================
1051
1052    /// Get historical bars for option contracts.
1053    ///
1054    /// # Arguments
1055    /// * `params` - Query parameters including symbols, timeframe, and date range
1056    ///
1057    /// # Returns
1058    /// Historical bar data for the requested options
1059    pub async fn get_option_bars(&self, params: &OptionBarsParams) -> Result<OptionBarsResponse> {
1060        self.get_with_params("/v1beta1/options/bars", params).await
1061    }
1062
1063    /// Get snapshots for option contracts.
1064    ///
1065    /// # Arguments
1066    /// * `symbols` - Comma-separated list of option symbols
1067    ///
1068    /// # Returns
1069    /// Current snapshots with latest quote, trade, and greeks
1070    pub async fn get_option_snapshots(&self, symbols: &str) -> Result<OptionSnapshotsResponse> {
1071        #[derive(Serialize)]
1072        struct Params<'a> {
1073            symbols: &'a str,
1074        }
1075        self.get_with_params("/v1beta1/options/snapshots", &Params { symbols })
1076            .await
1077    }
1078
1079    /// Get the options chain for an underlying symbol.
1080    ///
1081    /// # Arguments
1082    /// * `underlying_symbol` - The underlying asset symbol (e.g., "AAPL")
1083    ///
1084    /// # Returns
1085    /// Options chain with all available contracts and snapshots
1086    pub async fn get_option_chain(
1087        &self,
1088        underlying_symbol: &str,
1089    ) -> Result<OptionSnapshotsResponse> {
1090        #[derive(Serialize)]
1091        struct Params<'a> {
1092            underlying_symbol: &'a str,
1093        }
1094        self.get_with_params("/v1beta1/options/snapshots", &Params { underlying_symbol })
1095            .await
1096    }
1097}
1098
1099// ============================================================================
1100// Enhanced Stock Market Data Endpoints
1101// ============================================================================
1102
1103/// Response for multi-symbol bars.
1104#[derive(Debug, Serialize, Deserialize)]
1105pub struct MultiBarsResponse {
1106    /// Map of symbol to bars.
1107    pub bars: std::collections::HashMap<String, Vec<Bar>>,
1108    /// Token for next page of results.
1109    pub next_page_token: Option<String>,
1110}
1111
1112/// Response for multi-symbol quotes.
1113#[derive(Debug, Serialize, Deserialize)]
1114pub struct MultiQuotesResponse {
1115    /// Map of symbol to quotes.
1116    pub quotes: std::collections::HashMap<String, Vec<Quote>>,
1117    /// Token for next page of results.
1118    pub next_page_token: Option<String>,
1119}
1120
1121/// Response for multi-symbol trades.
1122#[derive(Debug, Serialize, Deserialize)]
1123pub struct MultiTradesResponse {
1124    /// Map of symbol to trades.
1125    pub trades: std::collections::HashMap<String, Vec<Trade>>,
1126    /// Token for next page of results.
1127    pub next_page_token: Option<String>,
1128}
1129
1130/// Response for stock snapshots.
1131#[derive(Debug, Serialize, Deserialize)]
1132pub struct StockSnapshotsResponse {
1133    /// Map of symbol to snapshot.
1134    #[serde(flatten)]
1135    pub snapshots: std::collections::HashMap<String, StockSnapshot>,
1136}
1137
1138/// Response for corporate actions.
1139#[derive(Debug, Serialize, Deserialize)]
1140pub struct CorporateActionsResponse {
1141    /// List of corporate actions.
1142    pub corporate_actions: Vec<CorporateAction>,
1143    /// Token for next page of results.
1144    pub next_page_token: Option<String>,
1145}
1146
1147/// Response for latest bars.
1148#[derive(Debug, Serialize, Deserialize)]
1149pub struct LatestBarsResponse {
1150    /// Map of symbol to latest bar.
1151    pub bars: std::collections::HashMap<String, Bar>,
1152}
1153
1154/// Response for latest quotes.
1155#[derive(Debug, Serialize, Deserialize)]
1156pub struct LatestQuotesResponse {
1157    /// Map of symbol to latest quote.
1158    pub quotes: std::collections::HashMap<String, Quote>,
1159}
1160
1161/// Response for latest trades.
1162#[derive(Debug, Serialize, Deserialize)]
1163pub struct LatestTradesResponse {
1164    /// Map of symbol to latest trade.
1165    pub trades: std::collections::HashMap<String, Trade>,
1166}
1167
1168impl AlpacaHttpClient {
1169    // ========================================================================
1170    // Multi-Symbol Market Data Endpoints
1171    // ========================================================================
1172
1173    /// Get historical bars for multiple symbols.
1174    ///
1175    /// # Arguments
1176    /// * `params` - Query parameters including symbols, timeframe, and date range
1177    ///
1178    /// # Returns
1179    /// Historical bar data for all requested symbols
1180    pub async fn get_stock_bars(&self, params: &MultiBarsParams) -> Result<MultiBarsResponse> {
1181        self.get_with_params("/v2/stocks/bars", params).await
1182    }
1183
1184    /// Get historical quotes for multiple symbols.
1185    ///
1186    /// # Arguments
1187    /// * `params` - Query parameters including symbols and date range
1188    ///
1189    /// # Returns
1190    /// Historical quote data for all requested symbols
1191    pub async fn get_stock_quotes(
1192        &self,
1193        params: &MultiQuotesParams,
1194    ) -> Result<MultiQuotesResponse> {
1195        self.get_with_params("/v2/stocks/quotes", params).await
1196    }
1197
1198    /// Get historical trades for multiple symbols.
1199    ///
1200    /// # Arguments
1201    /// * `params` - Query parameters including symbols and date range
1202    ///
1203    /// # Returns
1204    /// Historical trade data for all requested symbols
1205    pub async fn get_stock_trades(
1206        &self,
1207        params: &MultiTradesParams,
1208    ) -> Result<MultiTradesResponse> {
1209        self.get_with_params("/v2/stocks/trades", params).await
1210    }
1211
1212    /// Get snapshots for multiple symbols.
1213    ///
1214    /// # Arguments
1215    /// * `symbols` - Comma-separated list of symbols
1216    ///
1217    /// # Returns
1218    /// Current snapshots with latest trade, quote, and bars
1219    pub async fn get_stock_snapshots(&self, symbols: &str) -> Result<StockSnapshotsResponse> {
1220        #[derive(Serialize)]
1221        struct Params<'a> {
1222            symbols: &'a str,
1223        }
1224        self.get_with_params("/v2/stocks/snapshots", &Params { symbols })
1225            .await
1226    }
1227
1228    // ========================================================================
1229    // Latest Market Data Endpoints
1230    // ========================================================================
1231
1232    /// Get latest bars for multiple symbols.
1233    ///
1234    /// # Arguments
1235    /// * `symbols` - Comma-separated list of symbols
1236    ///
1237    /// # Returns
1238    /// Latest bar for each symbol
1239    pub async fn get_latest_bars(&self, symbols: &str) -> Result<LatestBarsResponse> {
1240        #[derive(Serialize)]
1241        struct Params<'a> {
1242            symbols: &'a str,
1243        }
1244        self.get_with_params("/v2/stocks/bars/latest", &Params { symbols })
1245            .await
1246    }
1247
1248    /// Get latest quotes for multiple symbols.
1249    ///
1250    /// # Arguments
1251    /// * `symbols` - Comma-separated list of symbols
1252    ///
1253    /// # Returns
1254    /// Latest quote for each symbol
1255    pub async fn get_latest_quotes(&self, symbols: &str) -> Result<LatestQuotesResponse> {
1256        #[derive(Serialize)]
1257        struct Params<'a> {
1258            symbols: &'a str,
1259        }
1260        self.get_with_params("/v2/stocks/quotes/latest", &Params { symbols })
1261            .await
1262    }
1263
1264    /// Get latest trades for multiple symbols.
1265    ///
1266    /// # Arguments
1267    /// * `symbols` - Comma-separated list of symbols
1268    ///
1269    /// # Returns
1270    /// Latest trade for each symbol
1271    pub async fn get_latest_trades(&self, symbols: &str) -> Result<LatestTradesResponse> {
1272        #[derive(Serialize)]
1273        struct Params<'a> {
1274            symbols: &'a str,
1275        }
1276        self.get_with_params("/v2/stocks/trades/latest", &Params { symbols })
1277            .await
1278    }
1279
1280    // ========================================================================
1281    // Corporate Actions Endpoints
1282    // ========================================================================
1283
1284    /// Get corporate action announcements.
1285    ///
1286    /// # Arguments
1287    /// * `params` - Query parameters for filtering corporate actions
1288    ///
1289    /// # Returns
1290    /// List of corporate action announcements
1291    pub async fn get_corporate_actions(
1292        &self,
1293        params: &CorporateActionsParams,
1294    ) -> Result<CorporateActionsResponse> {
1295        self.get_with_params("/v1beta1/corporate-actions/announcements", params)
1296            .await
1297    }
1298}
1299
1300// ============================================================================
1301// Broker API - Account Management Endpoints
1302// ============================================================================
1303
1304impl AlpacaHttpClient {
1305    // ========================================================================
1306    // Broker Account Endpoints
1307    // ========================================================================
1308
1309    /// Create a new broker account.
1310    ///
1311    /// # Arguments
1312    /// * `request` - Account creation request with KYC data
1313    ///
1314    /// # Returns
1315    /// The created broker account
1316    pub async fn create_broker_account(
1317        &self,
1318        request: &CreateBrokerAccountRequest,
1319    ) -> Result<BrokerAccount> {
1320        self.post("/v1/accounts", request).await
1321    }
1322
1323    /// List all broker accounts.
1324    ///
1325    /// # Arguments
1326    /// * `params` - Optional query parameters for filtering
1327    ///
1328    /// # Returns
1329    /// List of broker accounts
1330    pub async fn list_broker_accounts(
1331        &self,
1332        params: &ListBrokerAccountsParams,
1333    ) -> Result<Vec<BrokerAccount>> {
1334        self.get_with_params("/v1/accounts", params).await
1335    }
1336
1337    /// Get a broker account by ID.
1338    ///
1339    /// # Arguments
1340    /// * `account_id` - The account ID
1341    ///
1342    /// # Returns
1343    /// The broker account
1344    pub async fn get_broker_account(&self, account_id: &str) -> Result<BrokerAccount> {
1345        self.get(&format!("/v1/accounts/{}", account_id)).await
1346    }
1347
1348    /// Update a broker account.
1349    ///
1350    /// # Arguments
1351    /// * `account_id` - The account ID
1352    /// * `request` - Update request with fields to change
1353    ///
1354    /// # Returns
1355    /// The updated broker account
1356    pub async fn update_broker_account(
1357        &self,
1358        account_id: &str,
1359        request: &UpdateBrokerAccountRequest,
1360    ) -> Result<BrokerAccount> {
1361        self.patch(&format!("/v1/accounts/{}", account_id), request)
1362            .await
1363    }
1364
1365    /// Close a broker account.
1366    ///
1367    /// # Arguments
1368    /// * `account_id` - The account ID to close
1369    pub async fn close_broker_account(&self, account_id: &str) -> Result<()> {
1370        self.delete(&format!("/v1/accounts/{}", account_id)).await
1371    }
1372
1373    /// Get trading account details for a broker account.
1374    ///
1375    /// # Arguments
1376    /// * `account_id` - The broker account ID
1377    ///
1378    /// # Returns
1379    /// Trading account details
1380    pub async fn get_broker_trading_account(&self, account_id: &str) -> Result<Account> {
1381        self.get(&format!("/v1/accounts/{}/trading", account_id))
1382            .await
1383    }
1384
1385    // ========================================================================
1386    // CIP (Customer Identification Program) Endpoints
1387    // ========================================================================
1388
1389    /// Submit CIP data for an account.
1390    ///
1391    /// # Arguments
1392    /// * `account_id` - The account ID
1393    /// * `cip_info` - CIP information to submit
1394    ///
1395    /// # Returns
1396    /// The submitted CIP info
1397    pub async fn submit_cip(&self, account_id: &str, cip_info: &CipInfo) -> Result<CipInfo> {
1398        self.post(&format!("/v1/accounts/{}/cip", account_id), cip_info)
1399            .await
1400    }
1401
1402    /// Get CIP status for an account.
1403    ///
1404    /// # Arguments
1405    /// * `account_id` - The account ID
1406    ///
1407    /// # Returns
1408    /// CIP information
1409    pub async fn get_cip(&self, account_id: &str) -> Result<CipInfo> {
1410        self.get(&format!("/v1/accounts/{}/cip", account_id)).await
1411    }
1412
1413    // ========================================================================
1414    // Document Endpoints
1415    // ========================================================================
1416
1417    /// Upload a document for an account.
1418    ///
1419    /// # Arguments
1420    /// * `account_id` - The account ID
1421    /// * `document` - Document to upload
1422    ///
1423    /// # Returns
1424    /// Upload confirmation
1425    pub async fn upload_document(
1426        &self,
1427        account_id: &str,
1428        document: &Document,
1429    ) -> Result<DocumentUploadResponse> {
1430        self.post(
1431            &format!("/v1/accounts/{}/documents/upload", account_id),
1432            document,
1433        )
1434        .await
1435    }
1436
1437    /// List documents for an account.
1438    ///
1439    /// # Arguments
1440    /// * `account_id` - The account ID
1441    ///
1442    /// # Returns
1443    /// List of documents
1444    pub async fn list_documents(&self, account_id: &str) -> Result<Vec<DocumentInfo>> {
1445        self.get(&format!("/v1/accounts/{}/documents", account_id))
1446            .await
1447    }
1448
1449    /// Get a specific document.
1450    ///
1451    /// # Arguments
1452    /// * `account_id` - The account ID
1453    /// * `document_id` - The document ID
1454    ///
1455    /// # Returns
1456    /// Document information
1457    pub async fn get_document(&self, account_id: &str, document_id: &str) -> Result<DocumentInfo> {
1458        self.get(&format!(
1459            "/v1/accounts/{}/documents/{}",
1460            account_id, document_id
1461        ))
1462        .await
1463    }
1464
1465    /// Delete a document.
1466    ///
1467    /// # Arguments
1468    /// * `account_id` - The account ID
1469    /// * `document_id` - The document ID
1470    pub async fn delete_document(&self, account_id: &str, document_id: &str) -> Result<()> {
1471        self.delete(&format!(
1472            "/v1/accounts/{}/documents/{}",
1473            account_id, document_id
1474        ))
1475        .await
1476    }
1477}
1478
1479/// Response for document upload.
1480#[derive(Debug, Serialize, Deserialize)]
1481pub struct DocumentUploadResponse {
1482    /// Document ID.
1483    pub id: String,
1484    /// Document type.
1485    pub document_type: DocumentType,
1486    /// Upload status.
1487    pub status: String,
1488}
1489
1490/// Document information.
1491#[derive(Debug, Serialize, Deserialize)]
1492pub struct DocumentInfo {
1493    /// Document ID.
1494    pub id: String,
1495    /// Document type.
1496    pub document_type: DocumentType,
1497    /// Document sub-type.
1498    #[serde(skip_serializing_if = "Option::is_none")]
1499    pub document_sub_type: Option<String>,
1500    /// Created at timestamp.
1501    pub created_at: DateTime<Utc>,
1502}
1503
1504// ============================================================================
1505// Broker API - Funding & Transfers Endpoints
1506// ============================================================================
1507
1508impl AlpacaHttpClient {
1509    // ========================================================================
1510    // ACH Relationship Endpoints
1511    // ========================================================================
1512
1513    /// Create an ACH relationship for an account.
1514    ///
1515    /// # Arguments
1516    /// * `account_id` - The account ID
1517    /// * `request` - ACH relationship creation request
1518    ///
1519    /// # Returns
1520    /// The created ACH relationship
1521    pub async fn create_ach_relationship(
1522        &self,
1523        account_id: &str,
1524        request: &CreateAchRelationshipRequest,
1525    ) -> Result<AchRelationship> {
1526        self.post(
1527            &format!("/v1/accounts/{}/ach_relationships", account_id),
1528            request,
1529        )
1530        .await
1531    }
1532
1533    /// List ACH relationships for an account.
1534    ///
1535    /// # Arguments
1536    /// * `account_id` - The account ID
1537    ///
1538    /// # Returns
1539    /// List of ACH relationships
1540    pub async fn list_ach_relationships(&self, account_id: &str) -> Result<Vec<AchRelationship>> {
1541        self.get(&format!("/v1/accounts/{}/ach_relationships", account_id))
1542            .await
1543    }
1544
1545    /// Delete an ACH relationship.
1546    ///
1547    /// # Arguments
1548    /// * `account_id` - The account ID
1549    /// * `relationship_id` - The relationship ID to delete
1550    pub async fn delete_ach_relationship(
1551        &self,
1552        account_id: &str,
1553        relationship_id: &str,
1554    ) -> Result<()> {
1555        self.delete(&format!(
1556            "/v1/accounts/{}/ach_relationships/{}",
1557            account_id, relationship_id
1558        ))
1559        .await
1560    }
1561
1562    // ========================================================================
1563    // Transfer Endpoints
1564    // ========================================================================
1565
1566    /// Create a transfer for an account.
1567    ///
1568    /// # Arguments
1569    /// * `account_id` - The account ID
1570    /// * `request` - Transfer creation request
1571    ///
1572    /// # Returns
1573    /// The created transfer
1574    pub async fn create_transfer(
1575        &self,
1576        account_id: &str,
1577        request: &CreateTransferRequest,
1578    ) -> Result<Transfer> {
1579        self.post(&format!("/v1/accounts/{}/transfers", account_id), request)
1580            .await
1581    }
1582
1583    /// List transfers for an account.
1584    ///
1585    /// # Arguments
1586    /// * `account_id` - The account ID
1587    /// * `params` - Optional query parameters
1588    ///
1589    /// # Returns
1590    /// List of transfers
1591    pub async fn list_transfers(
1592        &self,
1593        account_id: &str,
1594        params: &ListTransfersParams,
1595    ) -> Result<Vec<Transfer>> {
1596        self.get_with_params(&format!("/v1/accounts/{}/transfers", account_id), params)
1597            .await
1598    }
1599
1600    /// Get a specific transfer.
1601    ///
1602    /// # Arguments
1603    /// * `account_id` - The account ID
1604    /// * `transfer_id` - The transfer ID
1605    ///
1606    /// # Returns
1607    /// The transfer
1608    pub async fn get_transfer(&self, account_id: &str, transfer_id: &str) -> Result<Transfer> {
1609        self.get(&format!(
1610            "/v1/accounts/{}/transfers/{}",
1611            account_id, transfer_id
1612        ))
1613        .await
1614    }
1615
1616    /// Cancel a transfer.
1617    ///
1618    /// # Arguments
1619    /// * `account_id` - The account ID
1620    /// * `transfer_id` - The transfer ID to cancel
1621    pub async fn cancel_transfer(&self, account_id: &str, transfer_id: &str) -> Result<()> {
1622        self.delete(&format!(
1623            "/v1/accounts/{}/transfers/{}",
1624            account_id, transfer_id
1625        ))
1626        .await
1627    }
1628
1629    // ========================================================================
1630    // Wire Bank Endpoints
1631    // ========================================================================
1632
1633    /// List recipient banks for wire transfers.
1634    ///
1635    /// # Arguments
1636    /// * `account_id` - The account ID
1637    ///
1638    /// # Returns
1639    /// List of wire banks
1640    pub async fn list_wire_banks(&self, account_id: &str) -> Result<Vec<WireBank>> {
1641        self.get(&format!("/v1/accounts/{}/recipient_banks", account_id))
1642            .await
1643    }
1644
1645    /// Create a recipient bank for wire transfers.
1646    ///
1647    /// # Arguments
1648    /// * `account_id` - The account ID
1649    /// * `request` - Wire bank creation request
1650    ///
1651    /// # Returns
1652    /// The created wire bank
1653    pub async fn create_wire_bank(
1654        &self,
1655        account_id: &str,
1656        request: &CreateWireBankRequest,
1657    ) -> Result<WireBank> {
1658        self.post(
1659            &format!("/v1/accounts/{}/recipient_banks", account_id),
1660            request,
1661        )
1662        .await
1663    }
1664
1665    /// Delete a recipient bank.
1666    ///
1667    /// # Arguments
1668    /// * `account_id` - The account ID
1669    /// * `bank_id` - The bank ID to delete
1670    pub async fn delete_wire_bank(&self, account_id: &str, bank_id: &str) -> Result<()> {
1671        self.delete(&format!(
1672            "/v1/accounts/{}/recipient_banks/{}",
1673            account_id, bank_id
1674        ))
1675        .await
1676    }
1677
1678    // ========================================================================
1679    // Journal Endpoints
1680    // ========================================================================
1681
1682    /// Create a journal entry.
1683    ///
1684    /// # Arguments
1685    /// * `request` - Journal creation request
1686    ///
1687    /// # Returns
1688    /// The created journal
1689    pub async fn create_journal(&self, request: &CreateJournalRequest) -> Result<Journal> {
1690        self.post("/v1/journals", request).await
1691    }
1692
1693    /// List journal entries.
1694    ///
1695    /// # Arguments
1696    /// * `params` - Optional query parameters
1697    ///
1698    /// # Returns
1699    /// List of journals
1700    pub async fn list_journals(&self, params: &ListJournalsParams) -> Result<Vec<Journal>> {
1701        self.get_with_params("/v1/journals", params).await
1702    }
1703
1704    /// Create batch journal entries.
1705    ///
1706    /// # Arguments
1707    /// * `request` - Batch journal creation request
1708    ///
1709    /// # Returns
1710    /// List of created journals
1711    pub async fn create_batch_journals(
1712        &self,
1713        request: &CreateBatchJournalRequest,
1714    ) -> Result<Vec<Journal>> {
1715        self.post("/v1/journals/batch", request).await
1716    }
1717
1718    /// Delete a journal entry.
1719    ///
1720    /// # Arguments
1721    /// * `journal_id` - The journal ID to delete
1722    pub async fn delete_journal(&self, journal_id: &str) -> Result<()> {
1723        self.delete(&format!("/v1/journals/{}", journal_id)).await
1724    }
1725}
1726
1727// ============================================================================
1728// Enhanced Crypto Trading Endpoints
1729// ============================================================================
1730
1731/// Response for crypto snapshots (multi-symbol).
1732#[derive(Debug, Serialize, Deserialize)]
1733pub struct MultiCryptoSnapshotsResponse {
1734    /// Map of symbol to snapshot.
1735    pub snapshots: std::collections::HashMap<String, CryptoSnapshot>,
1736}
1737
1738/// Response for multi-symbol crypto bars.
1739#[derive(Debug, Serialize, Deserialize)]
1740pub struct MultiCryptoBarsResponse {
1741    /// Map of symbol to bars.
1742    pub bars: std::collections::HashMap<String, Vec<CryptoBar>>,
1743    /// Next page token.
1744    pub next_page_token: Option<String>,
1745}
1746
1747/// Response for latest crypto bars (multi-symbol).
1748#[derive(Debug, Serialize, Deserialize)]
1749pub struct LatestCryptoBarsResponse {
1750    /// Map of symbol to latest bar.
1751    pub bars: std::collections::HashMap<String, CryptoBar>,
1752}
1753
1754/// Response for latest crypto quotes (multi-symbol).
1755#[derive(Debug, Serialize, Deserialize)]
1756pub struct LatestCryptoQuotesResponse {
1757    /// Map of symbol to latest quote.
1758    pub quotes: std::collections::HashMap<String, CryptoQuote>,
1759}
1760
1761/// Response for latest crypto trades (multi-symbol).
1762#[derive(Debug, Serialize, Deserialize)]
1763pub struct LatestCryptoTradesResponse {
1764    /// Map of symbol to latest trade.
1765    pub trades: std::collections::HashMap<String, CryptoTrade>,
1766}
1767
1768/// Response for crypto orderbooks.
1769#[derive(Debug, Serialize, Deserialize)]
1770pub struct CryptoOrderbooksResponse {
1771    /// Map of symbol to orderbook.
1772    pub orderbooks: std::collections::HashMap<String, CryptoOrderbook>,
1773}
1774
1775impl AlpacaHttpClient {
1776    // ========================================================================
1777    // Crypto Wallet Endpoints (Broker API)
1778    // ========================================================================
1779
1780    /// List crypto wallets for an account.
1781    ///
1782    /// # Arguments
1783    /// * `account_id` - The account ID
1784    ///
1785    /// # Returns
1786    /// List of crypto wallets
1787    pub async fn list_crypto_wallets(&self, account_id: &str) -> Result<Vec<BrokerCryptoWallet>> {
1788        self.get(&format!("/v1/accounts/{}/wallets", account_id))
1789            .await
1790    }
1791
1792    /// Create a crypto wallet for an account.
1793    ///
1794    /// # Arguments
1795    /// * `account_id` - The account ID
1796    /// * `request` - Wallet creation request
1797    ///
1798    /// # Returns
1799    /// The created wallet
1800    pub async fn create_crypto_wallet(
1801        &self,
1802        account_id: &str,
1803        request: &CreateCryptoWalletRequest,
1804    ) -> Result<BrokerCryptoWallet> {
1805        self.post(&format!("/v1/accounts/{}/wallets", account_id), request)
1806            .await
1807    }
1808
1809    /// Get a crypto wallet by asset.
1810    ///
1811    /// # Arguments
1812    /// * `account_id` - The account ID
1813    /// * `asset` - The asset symbol (e.g., BTC, ETH)
1814    ///
1815    /// # Returns
1816    /// The crypto wallet
1817    pub async fn get_crypto_wallet(
1818        &self,
1819        account_id: &str,
1820        asset: &str,
1821    ) -> Result<BrokerCryptoWallet> {
1822        self.get(&format!("/v1/accounts/{}/wallets/{}", account_id, asset))
1823            .await
1824    }
1825
1826    /// List crypto wallet transfers.
1827    ///
1828    /// # Arguments
1829    /// * `account_id` - The account ID
1830    ///
1831    /// # Returns
1832    /// List of crypto transfers
1833    pub async fn list_crypto_transfers(&self, account_id: &str) -> Result<Vec<CryptoTransfer>> {
1834        self.get(&format!("/v1/accounts/{}/wallets/transfers", account_id))
1835            .await
1836    }
1837
1838    /// Create a crypto wallet transfer.
1839    ///
1840    /// # Arguments
1841    /// * `account_id` - The account ID
1842    /// * `asset` - The asset symbol
1843    /// * `request` - Transfer request
1844    ///
1845    /// # Returns
1846    /// The created transfer
1847    pub async fn create_crypto_transfer(
1848        &self,
1849        account_id: &str,
1850        asset: &str,
1851        request: &CreateCryptoTransferRequest,
1852    ) -> Result<CryptoTransfer> {
1853        self.post(
1854            &format!("/v1/accounts/{}/wallets/{}/transfers", account_id, asset),
1855            request,
1856        )
1857        .await
1858    }
1859
1860    /// List whitelisted crypto addresses.
1861    ///
1862    /// # Arguments
1863    /// * `account_id` - The account ID
1864    ///
1865    /// # Returns
1866    /// List of whitelisted addresses
1867    pub async fn list_crypto_whitelists(
1868        &self,
1869        account_id: &str,
1870    ) -> Result<Vec<CryptoWhitelistAddress>> {
1871        self.get(&format!("/v1/accounts/{}/wallets/whitelists", account_id))
1872            .await
1873    }
1874
1875    /// Add a whitelisted crypto address.
1876    ///
1877    /// # Arguments
1878    /// * `account_id` - The account ID
1879    /// * `request` - Whitelist request
1880    ///
1881    /// # Returns
1882    /// The created whitelist entry
1883    pub async fn create_crypto_whitelist(
1884        &self,
1885        account_id: &str,
1886        request: &CreateCryptoWhitelistRequest,
1887    ) -> Result<CryptoWhitelistAddress> {
1888        self.post(
1889            &format!("/v1/accounts/{}/wallets/whitelists", account_id),
1890            request,
1891        )
1892        .await
1893    }
1894
1895    // ========================================================================
1896    // Enhanced Crypto Market Data Endpoints
1897    // ========================================================================
1898
1899    /// Get multi-symbol crypto bars.
1900    ///
1901    /// # Arguments
1902    /// * `params` - Query parameters
1903    ///
1904    /// # Returns
1905    /// Crypto bar data for multiple symbols
1906    pub async fn get_multi_crypto_bars(
1907        &self,
1908        params: &CryptoBarsParams,
1909    ) -> Result<MultiCryptoBarsResponse> {
1910        self.get_with_params("/v1beta3/crypto/us/bars", params)
1911            .await
1912    }
1913
1914    /// Get latest crypto bars.
1915    ///
1916    /// # Arguments
1917    /// * `symbols` - Comma-separated list of symbols
1918    ///
1919    /// # Returns
1920    /// Latest bar for each symbol
1921    pub async fn get_latest_crypto_bars(&self, symbols: &str) -> Result<LatestCryptoBarsResponse> {
1922        #[derive(Serialize)]
1923        struct Params<'a> {
1924            symbols: &'a str,
1925        }
1926        self.get_with_params("/v1beta3/crypto/us/latest/bars", &Params { symbols })
1927            .await
1928    }
1929
1930    /// Get latest crypto quotes.
1931    ///
1932    /// # Arguments
1933    /// * `symbols` - Comma-separated list of symbols
1934    ///
1935    /// # Returns
1936    /// Latest quote for each symbol
1937    pub async fn get_latest_crypto_quotes(
1938        &self,
1939        symbols: &str,
1940    ) -> Result<LatestCryptoQuotesResponse> {
1941        #[derive(Serialize)]
1942        struct Params<'a> {
1943            symbols: &'a str,
1944        }
1945        self.get_with_params("/v1beta3/crypto/us/latest/quotes", &Params { symbols })
1946            .await
1947    }
1948
1949    /// Get latest crypto trades.
1950    ///
1951    /// # Arguments
1952    /// * `symbols` - Comma-separated list of symbols
1953    ///
1954    /// # Returns
1955    /// Latest trade for each symbol
1956    pub async fn get_latest_crypto_trades(
1957        &self,
1958        symbols: &str,
1959    ) -> Result<LatestCryptoTradesResponse> {
1960        #[derive(Serialize)]
1961        struct Params<'a> {
1962            symbols: &'a str,
1963        }
1964        self.get_with_params("/v1beta3/crypto/us/latest/trades", &Params { symbols })
1965            .await
1966    }
1967
1968    /// Get crypto snapshots for multiple symbols.
1969    ///
1970    /// # Arguments
1971    /// * `symbols` - Comma-separated list of symbols
1972    ///
1973    /// # Returns
1974    /// Snapshots for each symbol
1975    pub async fn get_multi_crypto_snapshots(
1976        &self,
1977        symbols: &str,
1978    ) -> Result<MultiCryptoSnapshotsResponse> {
1979        #[derive(Serialize)]
1980        struct Params<'a> {
1981            symbols: &'a str,
1982        }
1983        self.get_with_params("/v1beta3/crypto/us/snapshots", &Params { symbols })
1984            .await
1985    }
1986
1987    /// Get crypto orderbooks.
1988    ///
1989    /// # Arguments
1990    /// * `symbols` - Comma-separated list of symbols
1991    ///
1992    /// # Returns
1993    /// Orderbooks for each symbol
1994    pub async fn get_crypto_orderbooks(&self, symbols: &str) -> Result<CryptoOrderbooksResponse> {
1995        #[derive(Serialize)]
1996        struct Params<'a> {
1997            symbols: &'a str,
1998        }
1999        self.get_with_params("/v1beta3/crypto/us/latest/orderbooks", &Params { symbols })
2000            .await
2001    }
2002}
2003
2004// ============================================================================
2005// News API Endpoints
2006// ============================================================================
2007
2008/// Response for enhanced news request.
2009#[derive(Debug, Serialize, Deserialize)]
2010pub struct EnhancedNewsResponse {
2011    /// News articles.
2012    pub news: Vec<EnhancedNewsArticle>,
2013    /// Next page token.
2014    pub next_page_token: Option<String>,
2015}
2016
2017impl AlpacaHttpClient {
2018    /// Get enhanced news articles with images and full content.
2019    ///
2020    /// # Arguments
2021    /// * `params` - Query parameters for filtering news
2022    ///
2023    /// # Returns
2024    /// List of enhanced news articles with pagination
2025    pub async fn get_enhanced_news(
2026        &self,
2027        params: &alpaca_base::NewsParams,
2028    ) -> Result<EnhancedNewsResponse> {
2029        self.get_with_params("/v1beta1/news", params).await
2030    }
2031
2032    /// Get enhanced news for specific symbols.
2033    ///
2034    /// # Arguments
2035    /// * `symbols` - Comma-separated list of symbols
2036    /// * `limit` - Maximum number of articles
2037    ///
2038    /// # Returns
2039    /// List of enhanced news articles
2040    pub async fn get_enhanced_news_for_symbols(
2041        &self,
2042        symbols: &str,
2043        limit: u32,
2044    ) -> Result<EnhancedNewsResponse> {
2045        let params = alpaca_base::NewsParams::new().symbols(symbols).limit(limit);
2046        self.get_enhanced_news(&params).await
2047    }
2048
2049    /// Get latest enhanced news.
2050    ///
2051    /// # Arguments
2052    /// * `limit` - Maximum number of articles
2053    ///
2054    /// # Returns
2055    /// List of latest enhanced news articles
2056    pub async fn get_latest_enhanced_news(&self, limit: u32) -> Result<EnhancedNewsResponse> {
2057        let params = alpaca_base::NewsParams::new().sort_desc().limit(limit);
2058        self.get_enhanced_news(&params).await
2059    }
2060}
2061
2062// ============================================================================
2063// OAuth 2.0 Endpoints
2064// ============================================================================
2065
2066impl AlpacaHttpClient {
2067    /// Exchange authorization code for OAuth token.
2068    ///
2069    /// # Arguments
2070    /// * `request` - Token exchange request
2071    ///
2072    /// # Returns
2073    /// OAuth token
2074    pub async fn oauth_exchange_code(&self, request: &OAuthTokenRequest) -> Result<OAuthToken> {
2075        self.post("/oauth/token", request).await
2076    }
2077
2078    /// Refresh OAuth token.
2079    ///
2080    /// # Arguments
2081    /// * `request` - Token refresh request
2082    ///
2083    /// # Returns
2084    /// New OAuth token
2085    pub async fn oauth_refresh_token(&self, request: &OAuthTokenRequest) -> Result<OAuthToken> {
2086        self.post("/oauth/token", request).await
2087    }
2088
2089    /// Revoke OAuth token.
2090    ///
2091    /// # Arguments
2092    /// * `request` - Token revoke request
2093    pub async fn oauth_revoke_token(&self, request: &OAuthRevokeRequest) -> Result<()> {
2094        let _: serde_json::Value = self.post("/oauth/revoke", request).await?;
2095        Ok(())
2096    }
2097}
2098
2099// ============================================================================
2100// Broker API Events (SSE) Endpoints
2101// ============================================================================
2102
2103impl AlpacaHttpClient {
2104    /// Get account status events (SSE endpoint URL).
2105    ///
2106    /// Note: This returns the URL for SSE streaming. Use an SSE client to connect.
2107    ///
2108    /// # Arguments
2109    /// * `params` - Optional query parameters
2110    ///
2111    /// # Returns
2112    /// SSE endpoint URL
2113    #[must_use]
2114    pub fn get_account_status_events_url(&self, params: &SseEventParams) -> String {
2115        let base = "/v1/events/accounts/status";
2116        self.build_sse_url(base, params)
2117    }
2118
2119    /// Get transfer status events (SSE endpoint URL).
2120    ///
2121    /// Note: This returns the URL for SSE streaming. Use an SSE client to connect.
2122    ///
2123    /// # Arguments
2124    /// * `params` - Optional query parameters
2125    ///
2126    /// # Returns
2127    /// SSE endpoint URL
2128    #[must_use]
2129    pub fn get_transfer_status_events_url(&self, params: &SseEventParams) -> String {
2130        let base = "/v1/events/transfers/status";
2131        self.build_sse_url(base, params)
2132    }
2133
2134    /// Get trade events (SSE endpoint URL).
2135    ///
2136    /// Note: This returns the URL for SSE streaming. Use an SSE client to connect.
2137    ///
2138    /// # Arguments
2139    /// * `params` - Optional query parameters
2140    ///
2141    /// # Returns
2142    /// SSE endpoint URL
2143    #[must_use]
2144    pub fn get_trade_events_url(&self, params: &SseEventParams) -> String {
2145        let base = "/v1/events/trades";
2146        self.build_sse_url(base, params)
2147    }
2148
2149    /// Get journal status events (SSE endpoint URL).
2150    ///
2151    /// Note: This returns the URL for SSE streaming. Use an SSE client to connect.
2152    ///
2153    /// # Arguments
2154    /// * `params` - Optional query parameters
2155    ///
2156    /// # Returns
2157    /// SSE endpoint URL
2158    #[must_use]
2159    pub fn get_journal_status_events_url(&self, params: &SseEventParams) -> String {
2160        let base = "/v1/events/journals/status";
2161        self.build_sse_url(base, params)
2162    }
2163
2164    /// Get non-trade activity events (SSE endpoint URL).
2165    ///
2166    /// Note: This returns the URL for SSE streaming. Use an SSE client to connect.
2167    ///
2168    /// # Arguments
2169    /// * `params` - Optional query parameters
2170    ///
2171    /// # Returns
2172    /// SSE endpoint URL
2173    #[must_use]
2174    pub fn get_nta_events_url(&self, params: &SseEventParams) -> String {
2175        let base = "/v2beta1/events/nta";
2176        self.build_sse_url(base, params)
2177    }
2178
2179    /// Build SSE URL with query parameters.
2180    fn build_sse_url(&self, base: &str, params: &SseEventParams) -> String {
2181        let mut url = base.to_string();
2182        let mut query_parts = Vec::new();
2183
2184        if let Some(ref account_id) = params.account_id {
2185            query_parts.push(format!("account_id={}", account_id));
2186        }
2187        if let Some(ref since) = params.since {
2188            query_parts.push(format!("since={}", since));
2189        }
2190        if let Some(ref until) = params.until {
2191            query_parts.push(format!("until={}", until));
2192        }
2193
2194        if !query_parts.is_empty() {
2195            url.push('?');
2196            url.push_str(&query_parts.join("&"));
2197        }
2198
2199        url
2200    }
2201}
2202
2203// ============================================================================
2204// Enhanced Assets API Endpoints
2205// ============================================================================
2206
2207impl AlpacaHttpClient {
2208    /// List enhanced assets with filtering.
2209    ///
2210    /// # Arguments
2211    /// * `params` - Query parameters for filtering
2212    ///
2213    /// # Returns
2214    /// List of enhanced assets
2215    pub async fn list_enhanced_assets(
2216        &self,
2217        params: &ListAssetsParams,
2218    ) -> Result<Vec<EnhancedAsset>> {
2219        self.get_with_params("/v2/assets", params).await
2220    }
2221
2222    /// Get enhanced asset by symbol or ID.
2223    ///
2224    /// # Arguments
2225    /// * `symbol_or_id` - Symbol or asset ID
2226    ///
2227    /// # Returns
2228    /// Enhanced asset details
2229    pub async fn get_enhanced_asset(&self, symbol_or_id: &str) -> Result<EnhancedAsset> {
2230        self.get(&format!("/v2/assets/{}", symbol_or_id)).await
2231    }
2232
2233    /// Get options contracts for an underlying symbol.
2234    ///
2235    /// # Arguments
2236    /// * `symbol` - Underlying symbol
2237    ///
2238    /// # Returns
2239    /// List of option contract assets
2240    pub async fn get_asset_options(&self, symbol: &str) -> Result<Vec<OptionContractAsset>> {
2241        self.get(&format!("/v2/assets/{}/options", symbol)).await
2242    }
2243
2244    /// List corporate action announcements.
2245    ///
2246    /// # Arguments
2247    /// * `params` - Query parameters for filtering
2248    ///
2249    /// # Returns
2250    /// List of corporate action announcements
2251    pub async fn list_announcements(
2252        &self,
2253        params: &ListAnnouncementsParams,
2254    ) -> Result<Vec<CorporateActionAnnouncement>> {
2255        self.get_with_params("/v1beta1/corporate-actions/announcements", params)
2256            .await
2257    }
2258
2259    /// Get a specific corporate action announcement.
2260    ///
2261    /// # Arguments
2262    /// * `announcement_id` - Announcement ID
2263    ///
2264    /// # Returns
2265    /// Corporate action announcement
2266    pub async fn get_announcement(
2267        &self,
2268        announcement_id: &str,
2269    ) -> Result<CorporateActionAnnouncement> {
2270        self.get(&format!(
2271            "/v1beta1/corporate-actions/announcements/{}",
2272            announcement_id
2273        ))
2274        .await
2275    }
2276}
2277
2278// ============================================================================
2279// Enhanced Account Activities Endpoints
2280// ============================================================================
2281
2282impl AlpacaHttpClient {
2283    /// List account activities with filtering.
2284    ///
2285    /// # Arguments
2286    /// * `params` - Query parameters for filtering
2287    ///
2288    /// # Returns
2289    /// List of account activities
2290    pub async fn list_activities(
2291        &self,
2292        params: &ListActivitiesParams,
2293    ) -> Result<Vec<AccountActivity>> {
2294        self.get_with_params("/v2/account/activities", params).await
2295    }
2296
2297    /// List account activities by type.
2298    ///
2299    /// # Arguments
2300    /// * `activity_type` - Activity type to filter by
2301    /// * `params` - Additional query parameters
2302    ///
2303    /// # Returns
2304    /// List of account activities
2305    pub async fn list_activities_by_type(
2306        &self,
2307        activity_type: &str,
2308        params: &ListActivitiesParams,
2309    ) -> Result<Vec<AccountActivity>> {
2310        self.get_with_params(&format!("/v2/account/activities/{}", activity_type), params)
2311            .await
2312    }
2313
2314    /// List all broker accounts activities.
2315    ///
2316    /// # Arguments
2317    /// * `params` - Query parameters for filtering
2318    ///
2319    /// # Returns
2320    /// List of account activities across all accounts
2321    pub async fn list_broker_activities(
2322        &self,
2323        params: &ListActivitiesParams,
2324    ) -> Result<Vec<AccountActivity>> {
2325        self.get_with_params("/v1/accounts/activities", params)
2326            .await
2327    }
2328
2329    /// List activities for a specific broker account.
2330    ///
2331    /// # Arguments
2332    /// * `account_id` - Account ID
2333    /// * `params` - Query parameters for filtering
2334    ///
2335    /// # Returns
2336    /// List of account activities
2337    pub async fn list_broker_account_activities(
2338        &self,
2339        account_id: &str,
2340        params: &ListActivitiesParams,
2341    ) -> Result<Vec<AccountActivity>> {
2342        self.get_with_params(&format!("/v1/accounts/{}/activities", account_id), params)
2343            .await
2344    }
2345}
2346
2347// ============================================================================
2348// Portfolio Management Endpoints
2349// ============================================================================
2350
2351impl AlpacaHttpClient {
2352    /// Create rebalancing portfolio.
2353    ///
2354    /// # Arguments
2355    /// * `request` - Portfolio creation request
2356    ///
2357    /// # Returns
2358    /// Created portfolio
2359    pub async fn create_rebalance_portfolio(
2360        &self,
2361        request: &RebalancePortfolioRequest,
2362    ) -> Result<RebalancePortfolio> {
2363        self.post("/v1/rebalancing/portfolios", request).await
2364    }
2365
2366    /// List rebalancing portfolios.
2367    ///
2368    /// # Returns
2369    /// List of portfolios
2370    pub async fn list_rebalance_portfolios(&self) -> Result<Vec<RebalancePortfolio>> {
2371        self.get("/v1/rebalancing/portfolios").await
2372    }
2373
2374    /// Get rebalancing portfolio.
2375    ///
2376    /// # Arguments
2377    /// * `portfolio_id` - Portfolio ID
2378    ///
2379    /// # Returns
2380    /// Portfolio
2381    pub async fn get_rebalance_portfolio(&self, portfolio_id: &str) -> Result<RebalancePortfolio> {
2382        self.get(&format!("/v1/rebalancing/portfolios/{}", portfolio_id))
2383            .await
2384    }
2385
2386    /// Delete rebalancing portfolio.
2387    ///
2388    /// # Arguments
2389    /// * `portfolio_id` - Portfolio ID
2390    pub async fn delete_rebalance_portfolio(&self, portfolio_id: &str) -> Result<()> {
2391        let _: serde_json::Value = self
2392            .delete(&format!("/v1/rebalancing/portfolios/{}", portfolio_id))
2393            .await?;
2394        Ok(())
2395    }
2396
2397    /// Execute rebalance run.
2398    ///
2399    /// # Arguments
2400    /// * `request` - Rebalance run request
2401    ///
2402    /// # Returns
2403    /// Rebalance run
2404    pub async fn execute_rebalance(&self, request: &RebalanceRunRequest) -> Result<RebalanceRun> {
2405        self.post("/v1/rebalancing/runs", request).await
2406    }
2407
2408    /// List rebalance runs.
2409    ///
2410    /// # Returns
2411    /// List of rebalance runs
2412    pub async fn list_rebalance_runs(&self) -> Result<Vec<RebalanceRun>> {
2413        self.get("/v1/rebalancing/runs").await
2414    }
2415}
2416
2417// ============================================================================
2418// Margin and Short Selling Endpoints
2419// ============================================================================
2420
2421impl AlpacaHttpClient {
2422    /// Get locate availability for short selling.
2423    ///
2424    /// # Returns
2425    /// List of available locates
2426    pub async fn get_locates(&self) -> Result<Vec<LocateResponse>> {
2427        self.get("/v1/locate/stocks").await
2428    }
2429
2430    /// Request a locate for short selling.
2431    ///
2432    /// # Arguments
2433    /// * `request` - Locate request
2434    ///
2435    /// # Returns
2436    /// Locate response
2437    pub async fn request_locate(&self, request: &LocateRequest) -> Result<LocateResponse> {
2438        self.post("/v1/locate/stocks", request).await
2439    }
2440}
2441
2442// ============================================================================
2443// Paper Trading Endpoints
2444// ============================================================================
2445
2446impl AlpacaHttpClient {
2447    /// Reset paper trading account.
2448    ///
2449    /// # Returns
2450    /// The reset account
2451    pub async fn reset_paper_account(&self) -> Result<Account> {
2452        self.post("/v2/account/reset", &serde_json::json!({})).await
2453    }
2454}
2455
2456// ============================================================================
2457// Local Currency Trading Endpoints
2458// ============================================================================
2459
2460impl AlpacaHttpClient {
2461    /// Get exchange rates.
2462    ///
2463    /// # Returns
2464    /// List of exchange rates
2465    pub async fn get_exchange_rates(&self) -> Result<Vec<ExchangeRate>> {
2466        self.get("/v1/fx/rates").await
2467    }
2468
2469    /// Get specific exchange rate.
2470    ///
2471    /// # Arguments
2472    /// * `currency_pair` - Currency pair (e.g., "EUR/USD")
2473    ///
2474    /// # Returns
2475    /// Exchange rate
2476    pub async fn get_exchange_rate(&self, currency_pair: &str) -> Result<ExchangeRate> {
2477        self.get(&format!("/v1/fx/rates/{}", currency_pair)).await
2478    }
2479}
2480
2481// ============================================================================
2482// IRA Account Endpoints
2483// ============================================================================
2484
2485impl AlpacaHttpClient {
2486    /// List IRA contributions.
2487    ///
2488    /// # Arguments
2489    /// * `account_id` - Account ID
2490    ///
2491    /// # Returns
2492    /// List of IRA contributions
2493    pub async fn list_ira_contributions(&self, account_id: &str) -> Result<Vec<IraContribution>> {
2494        self.get(&format!("/v1/accounts/{}/ira/contributions", account_id))
2495            .await
2496    }
2497
2498    /// Create IRA contribution.
2499    ///
2500    /// # Arguments
2501    /// * `account_id` - Account ID
2502    /// * `request` - Contribution request
2503    ///
2504    /// # Returns
2505    /// Created contribution
2506    pub async fn create_ira_contribution(
2507        &self,
2508        account_id: &str,
2509        request: &CreateIraContributionRequest,
2510    ) -> Result<IraContribution> {
2511        self.post(
2512            &format!("/v1/accounts/{}/ira/contributions", account_id),
2513            request,
2514        )
2515        .await
2516    }
2517
2518    /// List IRA distributions.
2519    ///
2520    /// # Arguments
2521    /// * `account_id` - Account ID
2522    ///
2523    /// # Returns
2524    /// List of IRA distributions
2525    pub async fn list_ira_distributions(&self, account_id: &str) -> Result<Vec<IraDistribution>> {
2526        self.get(&format!("/v1/accounts/{}/ira/distributions", account_id))
2527            .await
2528    }
2529
2530    /// List IRA beneficiaries.
2531    ///
2532    /// # Arguments
2533    /// * `account_id` - Account ID
2534    ///
2535    /// # Returns
2536    /// List of IRA beneficiaries
2537    pub async fn list_ira_beneficiaries(&self, account_id: &str) -> Result<Vec<IraBeneficiary>> {
2538        self.get(&format!("/v1/accounts/{}/ira/beneficiaries", account_id))
2539            .await
2540    }
2541}
2542
2543#[cfg(test)]
2544mod tests {
2545    use super::*;
2546
2547    #[test]
2548    fn test_create_order_request_market() {
2549        let order = CreateOrderRequest::market("AAPL", OrderSide::Buy, "10");
2550        assert_eq!(order.symbol, "AAPL");
2551        assert_eq!(order.side, OrderSide::Buy);
2552        assert_eq!(order.qty, Some("10".to_string()));
2553        assert_eq!(order.order_type, OrderType::Market);
2554        assert_eq!(order.time_in_force, TimeInForce::Day);
2555    }
2556
2557    #[test]
2558    fn test_create_order_request_limit() {
2559        let order = CreateOrderRequest::limit("AAPL", OrderSide::Buy, "10", "150.00");
2560        assert_eq!(order.symbol, "AAPL");
2561        assert_eq!(order.limit_price, Some("150.00".to_string()));
2562        assert_eq!(order.order_type, OrderType::Limit);
2563    }
2564
2565    #[test]
2566    fn test_create_order_request_stop() {
2567        let order = CreateOrderRequest::stop("AAPL", OrderSide::Sell, "10", "145.00");
2568        assert_eq!(order.stop_price, Some("145.00".to_string()));
2569        assert_eq!(order.order_type, OrderType::Stop);
2570    }
2571
2572    #[test]
2573    fn test_create_order_request_stop_limit() {
2574        let order =
2575            CreateOrderRequest::stop_limit("AAPL", OrderSide::Sell, "10", "145.00", "144.50");
2576        assert_eq!(order.stop_price, Some("145.00".to_string()));
2577        assert_eq!(order.limit_price, Some("144.50".to_string()));
2578        assert_eq!(order.order_type, OrderType::StopLimit);
2579    }
2580
2581    #[test]
2582    fn test_create_order_request_trailing_stop_price() {
2583        let order = CreateOrderRequest::trailing_stop_price("AAPL", OrderSide::Sell, "10", "5.00");
2584        assert_eq!(order.trail_price, Some("5.00".to_string()));
2585        assert_eq!(order.order_type, OrderType::TrailingStop);
2586    }
2587
2588    #[test]
2589    fn test_create_order_request_trailing_stop_percent() {
2590        let order = CreateOrderRequest::trailing_stop_percent("AAPL", OrderSide::Sell, "10", "2.5");
2591        assert_eq!(order.trail_percent, Some("2.5".to_string()));
2592        assert_eq!(order.order_type, OrderType::TrailingStop);
2593    }
2594
2595    #[test]
2596    fn test_create_order_request_bracket() {
2597        let tp = TakeProfit::new("160.00");
2598        let sl = StopLoss::new("140.00");
2599        let order =
2600            CreateOrderRequest::bracket("AAPL", OrderSide::Buy, "10", OrderType::Market, tp, sl);
2601
2602        assert_eq!(order.order_class, Some(OrderClass::Bracket));
2603        assert!(order.take_profit.is_some());
2604        assert!(order.stop_loss.is_some());
2605        assert_eq!(order.take_profit.unwrap().limit_price, "160.00");
2606        assert_eq!(order.stop_loss.unwrap().stop_price, "140.00");
2607    }
2608
2609    #[test]
2610    fn test_create_order_request_oco() {
2611        let tp = TakeProfit::new("160.00");
2612        let sl = StopLoss::with_limit("140.00", "139.50");
2613        let order = CreateOrderRequest::oco("AAPL", OrderSide::Sell, "10", tp, sl);
2614
2615        assert_eq!(order.order_class, Some(OrderClass::Oco));
2616        assert!(order.take_profit.is_some());
2617        assert!(order.stop_loss.is_some());
2618    }
2619
2620    #[test]
2621    fn test_create_order_request_oto() {
2622        let sl = StopLoss::new("140.00");
2623        let order = CreateOrderRequest::oto("AAPL", OrderSide::Buy, "10", OrderType::Limit, sl);
2624
2625        assert_eq!(order.order_class, Some(OrderClass::Oto));
2626        assert!(order.stop_loss.is_some());
2627    }
2628
2629    #[test]
2630    fn test_create_order_request_builder_methods() {
2631        let order = CreateOrderRequest::market("AAPL", OrderSide::Buy, "10")
2632            .time_in_force(TimeInForce::Gtc)
2633            .extended_hours(true)
2634            .client_order_id("my-order-123");
2635
2636        assert_eq!(order.time_in_force, TimeInForce::Gtc);
2637        assert_eq!(order.extended_hours, Some(true));
2638        assert_eq!(order.client_order_id, Some("my-order-123".to_string()));
2639    }
2640
2641    #[test]
2642    fn test_create_order_request_position_intent() {
2643        let order = CreateOrderRequest::market("AAPL", OrderSide::Buy, "10")
2644            .position_intent(PositionIntent::BuyToOpen);
2645
2646        assert_eq!(order.position_intent, Some(PositionIntent::BuyToOpen));
2647    }
2648
2649    #[test]
2650    fn test_replace_order_request_builder() {
2651        let request = ReplaceOrderRequest::new()
2652            .qty("20")
2653            .limit_price("155.00")
2654            .time_in_force(TimeInForce::Gtc);
2655
2656        assert_eq!(request.qty, Some("20".to_string()));
2657        assert_eq!(request.limit_price, Some("155.00".to_string()));
2658        assert_eq!(request.time_in_force, Some(TimeInForce::Gtc));
2659    }
2660
2661    #[test]
2662    fn test_order_params_builder() {
2663        let params = OrderParams::new()
2664            .status(OrderQueryStatus::Open)
2665            .limit(100)
2666            .nested(true)
2667            .symbols("AAPL,GOOGL")
2668            .side(OrderSide::Buy)
2669            .direction(SortDirection::Desc);
2670
2671        assert_eq!(params.status, Some(OrderQueryStatus::Open));
2672        assert_eq!(params.limit, Some(100));
2673        assert_eq!(params.nested, Some(true));
2674        assert_eq!(params.symbols, Some("AAPL,GOOGL".to_string()));
2675        assert_eq!(params.side, Some(OrderSide::Buy));
2676        assert_eq!(params.direction, Some(SortDirection::Desc));
2677    }
2678
2679    #[test]
2680    fn test_create_order_request_serialization() {
2681        let order = CreateOrderRequest::limit("AAPL", OrderSide::Buy, "10", "150.00")
2682            .client_order_id("test-123");
2683
2684        let json = serde_json::to_string(&order).unwrap();
2685        assert!(json.contains("\"symbol\":\"AAPL\""));
2686        assert!(json.contains("\"side\":\"buy\""));
2687        assert!(json.contains("\"type\":\"limit\""));
2688        assert!(json.contains("\"limit_price\":\"150.00\""));
2689    }
2690
2691    #[test]
2692    fn test_bracket_order_serialization() {
2693        let tp = TakeProfit::new("160.00");
2694        let sl = StopLoss::with_limit("140.00", "139.50");
2695        let order =
2696            CreateOrderRequest::bracket("AAPL", OrderSide::Buy, "10", OrderType::Limit, tp, sl)
2697                .with_limit_price("150.00");
2698
2699        let json = serde_json::to_string(&order).unwrap();
2700        assert!(json.contains("\"order_class\":\"bracket\""));
2701        assert!(json.contains("\"take_profit\""));
2702        assert!(json.contains("\"stop_loss\""));
2703    }
2704}