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)]
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)]
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)]
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)]
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)]
883pub struct CalendarParams {
884    pub start: Option<String>,
885    pub end: Option<String>,
886}
887
888#[derive(Debug, Serialize, Deserialize)]
889pub struct NewsParams {
890    pub symbols: Option<String>,
891    pub start: Option<DateTime<Utc>>,
892    pub end: Option<DateTime<Utc>>,
893    pub sort: Option<String>,
894    pub include_content: Option<bool>,
895    pub exclude_contentless: Option<bool>,
896    pub page_token: Option<String>,
897    pub limit: Option<u32>,
898}
899
900#[derive(Debug, Serialize, Deserialize)]
901pub struct NewsResponse {
902    pub news: Vec<NewsArticle>,
903    pub next_page_token: Option<String>,
904}
905
906#[derive(Debug, Serialize, Deserialize)]
907pub struct CryptoBarsParams {
908    pub start: Option<DateTime<Utc>>,
909    pub end: Option<DateTime<Utc>>,
910    pub timeframe: Option<String>,
911    pub page_token: Option<String>,
912    pub limit: Option<u32>,
913    pub sort: Option<String>,
914}
915
916#[derive(Debug, Serialize, Deserialize)]
917pub struct CryptoBarsResponse {
918    pub bars: Vec<Bar>,
919    pub symbol: String,
920    pub next_page_token: Option<String>,
921}
922
923#[derive(Debug, Serialize, Deserialize)]
924pub struct CryptoQuotesParams {
925    pub start: Option<DateTime<Utc>>,
926    pub end: Option<DateTime<Utc>>,
927    pub page_token: Option<String>,
928    pub limit: Option<u32>,
929    pub sort: Option<String>,
930}
931
932#[derive(Debug, Serialize, Deserialize)]
933pub struct CryptoQuotesResponse {
934    pub quotes: Vec<Quote>,
935    pub symbol: String,
936    pub next_page_token: Option<String>,
937}
938
939#[derive(Debug, Serialize, Deserialize)]
940pub struct CryptoTradesParams {
941    pub start: Option<DateTime<Utc>>,
942    pub end: Option<DateTime<Utc>>,
943    pub page_token: Option<String>,
944    pub limit: Option<u32>,
945    pub sort: Option<String>,
946}
947
948#[derive(Debug, Serialize, Deserialize)]
949pub struct CryptoTradesResponse {
950    pub trades: Vec<Trade>,
951    pub symbol: String,
952    pub next_page_token: Option<String>,
953}
954
955// ============================================================================
956// Options Trading Endpoints
957// ============================================================================
958
959/// Response for listing option contracts.
960#[derive(Debug, Serialize, Deserialize)]
961pub struct OptionContractsResponse {
962    /// List of option contracts.
963    pub option_contracts: Vec<OptionContract>,
964    /// Token for next page of results.
965    pub next_page_token: Option<String>,
966}
967
968/// Response for option bars.
969#[derive(Debug, Serialize, Deserialize)]
970pub struct OptionBarsResponse {
971    /// Map of symbol to bars.
972    pub bars: std::collections::HashMap<String, Vec<OptionBar>>,
973    /// Token for next page of results.
974    pub next_page_token: Option<String>,
975}
976
977/// Response for option snapshots.
978#[derive(Debug, Serialize, Deserialize)]
979pub struct OptionSnapshotsResponse {
980    /// Map of symbol to snapshot.
981    pub snapshots: std::collections::HashMap<String, OptionSnapshot>,
982}
983
984impl AlpacaHttpClient {
985    // ========================================================================
986    // Options Contract Endpoints
987    // ========================================================================
988
989    /// List option contracts with optional filters.
990    ///
991    /// # Arguments
992    /// * `params` - Query parameters for filtering contracts
993    ///
994    /// # Returns
995    /// List of option contracts matching the filters
996    pub async fn get_option_contracts(
997        &self,
998        params: &OptionContractParams,
999    ) -> Result<OptionContractsResponse> {
1000        self.get_with_params("/v2/options/contracts", params).await
1001    }
1002
1003    /// Get a specific option contract by symbol or ID.
1004    ///
1005    /// # Arguments
1006    /// * `symbol_or_id` - OCC symbol or contract ID
1007    ///
1008    /// # Returns
1009    /// The option contract details
1010    pub async fn get_option_contract(&self, symbol_or_id: &str) -> Result<OptionContract> {
1011        self.get(&format!("/v2/options/contracts/{}", symbol_or_id))
1012            .await
1013    }
1014
1015    /// Exercise an option contract.
1016    ///
1017    /// # Arguments
1018    /// * `request` - Exercise request with symbol and quantity
1019    ///
1020    /// # Returns
1021    /// The resulting order from exercising the option
1022    pub async fn exercise_option(&self, request: &OptionExerciseRequest) -> Result<Order> {
1023        self.post("/v2/options/exercise", request).await
1024    }
1025
1026    // ========================================================================
1027    // Options Market Data Endpoints
1028    // ========================================================================
1029
1030    /// Get historical bars for option contracts.
1031    ///
1032    /// # Arguments
1033    /// * `params` - Query parameters including symbols, timeframe, and date range
1034    ///
1035    /// # Returns
1036    /// Historical bar data for the requested options
1037    pub async fn get_option_bars(&self, params: &OptionBarsParams) -> Result<OptionBarsResponse> {
1038        self.get_with_params("/v1beta1/options/bars", params).await
1039    }
1040
1041    /// Get snapshots for option contracts.
1042    ///
1043    /// # Arguments
1044    /// * `symbols` - Comma-separated list of option symbols
1045    ///
1046    /// # Returns
1047    /// Current snapshots with latest quote, trade, and greeks
1048    pub async fn get_option_snapshots(&self, symbols: &str) -> Result<OptionSnapshotsResponse> {
1049        #[derive(Serialize)]
1050        struct Params<'a> {
1051            symbols: &'a str,
1052        }
1053        self.get_with_params("/v1beta1/options/snapshots", &Params { symbols })
1054            .await
1055    }
1056
1057    /// Get the options chain for an underlying symbol.
1058    ///
1059    /// # Arguments
1060    /// * `underlying_symbol` - The underlying asset symbol (e.g., "AAPL")
1061    ///
1062    /// # Returns
1063    /// Options chain with all available contracts and snapshots
1064    pub async fn get_option_chain(
1065        &self,
1066        underlying_symbol: &str,
1067    ) -> Result<OptionSnapshotsResponse> {
1068        #[derive(Serialize)]
1069        struct Params<'a> {
1070            underlying_symbol: &'a str,
1071        }
1072        self.get_with_params("/v1beta1/options/snapshots", &Params { underlying_symbol })
1073            .await
1074    }
1075}
1076
1077// ============================================================================
1078// Enhanced Stock Market Data Endpoints
1079// ============================================================================
1080
1081/// Response for multi-symbol bars.
1082#[derive(Debug, Serialize, Deserialize)]
1083pub struct MultiBarsResponse {
1084    /// Map of symbol to bars.
1085    pub bars: std::collections::HashMap<String, Vec<Bar>>,
1086    /// Token for next page of results.
1087    pub next_page_token: Option<String>,
1088}
1089
1090/// Response for multi-symbol quotes.
1091#[derive(Debug, Serialize, Deserialize)]
1092pub struct MultiQuotesResponse {
1093    /// Map of symbol to quotes.
1094    pub quotes: std::collections::HashMap<String, Vec<Quote>>,
1095    /// Token for next page of results.
1096    pub next_page_token: Option<String>,
1097}
1098
1099/// Response for multi-symbol trades.
1100#[derive(Debug, Serialize, Deserialize)]
1101pub struct MultiTradesResponse {
1102    /// Map of symbol to trades.
1103    pub trades: std::collections::HashMap<String, Vec<Trade>>,
1104    /// Token for next page of results.
1105    pub next_page_token: Option<String>,
1106}
1107
1108/// Response for stock snapshots.
1109#[derive(Debug, Serialize, Deserialize)]
1110pub struct StockSnapshotsResponse {
1111    /// Map of symbol to snapshot.
1112    #[serde(flatten)]
1113    pub snapshots: std::collections::HashMap<String, StockSnapshot>,
1114}
1115
1116/// Response for corporate actions.
1117#[derive(Debug, Serialize, Deserialize)]
1118pub struct CorporateActionsResponse {
1119    /// List of corporate actions.
1120    pub corporate_actions: Vec<CorporateAction>,
1121    /// Token for next page of results.
1122    pub next_page_token: Option<String>,
1123}
1124
1125/// Response for latest bars.
1126#[derive(Debug, Serialize, Deserialize)]
1127pub struct LatestBarsResponse {
1128    /// Map of symbol to latest bar.
1129    pub bars: std::collections::HashMap<String, Bar>,
1130}
1131
1132/// Response for latest quotes.
1133#[derive(Debug, Serialize, Deserialize)]
1134pub struct LatestQuotesResponse {
1135    /// Map of symbol to latest quote.
1136    pub quotes: std::collections::HashMap<String, Quote>,
1137}
1138
1139/// Response for latest trades.
1140#[derive(Debug, Serialize, Deserialize)]
1141pub struct LatestTradesResponse {
1142    /// Map of symbol to latest trade.
1143    pub trades: std::collections::HashMap<String, Trade>,
1144}
1145
1146impl AlpacaHttpClient {
1147    // ========================================================================
1148    // Multi-Symbol Market Data Endpoints
1149    // ========================================================================
1150
1151    /// Get historical bars for multiple symbols.
1152    ///
1153    /// # Arguments
1154    /// * `params` - Query parameters including symbols, timeframe, and date range
1155    ///
1156    /// # Returns
1157    /// Historical bar data for all requested symbols
1158    pub async fn get_stock_bars(&self, params: &MultiBarsParams) -> Result<MultiBarsResponse> {
1159        self.get_with_params("/v2/stocks/bars", params).await
1160    }
1161
1162    /// Get historical quotes for multiple symbols.
1163    ///
1164    /// # Arguments
1165    /// * `params` - Query parameters including symbols and date range
1166    ///
1167    /// # Returns
1168    /// Historical quote data for all requested symbols
1169    pub async fn get_stock_quotes(
1170        &self,
1171        params: &MultiQuotesParams,
1172    ) -> Result<MultiQuotesResponse> {
1173        self.get_with_params("/v2/stocks/quotes", params).await
1174    }
1175
1176    /// Get historical trades for multiple symbols.
1177    ///
1178    /// # Arguments
1179    /// * `params` - Query parameters including symbols and date range
1180    ///
1181    /// # Returns
1182    /// Historical trade data for all requested symbols
1183    pub async fn get_stock_trades(
1184        &self,
1185        params: &MultiTradesParams,
1186    ) -> Result<MultiTradesResponse> {
1187        self.get_with_params("/v2/stocks/trades", params).await
1188    }
1189
1190    /// Get snapshots for multiple symbols.
1191    ///
1192    /// # Arguments
1193    /// * `symbols` - Comma-separated list of symbols
1194    ///
1195    /// # Returns
1196    /// Current snapshots with latest trade, quote, and bars
1197    pub async fn get_stock_snapshots(&self, symbols: &str) -> Result<StockSnapshotsResponse> {
1198        #[derive(Serialize)]
1199        struct Params<'a> {
1200            symbols: &'a str,
1201        }
1202        self.get_with_params("/v2/stocks/snapshots", &Params { symbols })
1203            .await
1204    }
1205
1206    // ========================================================================
1207    // Latest Market Data Endpoints
1208    // ========================================================================
1209
1210    /// Get latest bars for multiple symbols.
1211    ///
1212    /// # Arguments
1213    /// * `symbols` - Comma-separated list of symbols
1214    ///
1215    /// # Returns
1216    /// Latest bar for each symbol
1217    pub async fn get_latest_bars(&self, symbols: &str) -> Result<LatestBarsResponse> {
1218        #[derive(Serialize)]
1219        struct Params<'a> {
1220            symbols: &'a str,
1221        }
1222        self.get_with_params("/v2/stocks/bars/latest", &Params { symbols })
1223            .await
1224    }
1225
1226    /// Get latest quotes for multiple symbols.
1227    ///
1228    /// # Arguments
1229    /// * `symbols` - Comma-separated list of symbols
1230    ///
1231    /// # Returns
1232    /// Latest quote for each symbol
1233    pub async fn get_latest_quotes(&self, symbols: &str) -> Result<LatestQuotesResponse> {
1234        #[derive(Serialize)]
1235        struct Params<'a> {
1236            symbols: &'a str,
1237        }
1238        self.get_with_params("/v2/stocks/quotes/latest", &Params { symbols })
1239            .await
1240    }
1241
1242    /// Get latest trades for multiple symbols.
1243    ///
1244    /// # Arguments
1245    /// * `symbols` - Comma-separated list of symbols
1246    ///
1247    /// # Returns
1248    /// Latest trade for each symbol
1249    pub async fn get_latest_trades(&self, symbols: &str) -> Result<LatestTradesResponse> {
1250        #[derive(Serialize)]
1251        struct Params<'a> {
1252            symbols: &'a str,
1253        }
1254        self.get_with_params("/v2/stocks/trades/latest", &Params { symbols })
1255            .await
1256    }
1257
1258    // ========================================================================
1259    // Corporate Actions Endpoints
1260    // ========================================================================
1261
1262    /// Get corporate action announcements.
1263    ///
1264    /// # Arguments
1265    /// * `params` - Query parameters for filtering corporate actions
1266    ///
1267    /// # Returns
1268    /// List of corporate action announcements
1269    pub async fn get_corporate_actions(
1270        &self,
1271        params: &CorporateActionsParams,
1272    ) -> Result<CorporateActionsResponse> {
1273        self.get_with_params("/v1beta1/corporate-actions/announcements", params)
1274            .await
1275    }
1276}
1277
1278// ============================================================================
1279// Broker API - Account Management Endpoints
1280// ============================================================================
1281
1282impl AlpacaHttpClient {
1283    // ========================================================================
1284    // Broker Account Endpoints
1285    // ========================================================================
1286
1287    /// Create a new broker account.
1288    ///
1289    /// # Arguments
1290    /// * `request` - Account creation request with KYC data
1291    ///
1292    /// # Returns
1293    /// The created broker account
1294    pub async fn create_broker_account(
1295        &self,
1296        request: &CreateBrokerAccountRequest,
1297    ) -> Result<BrokerAccount> {
1298        self.post("/v1/accounts", request).await
1299    }
1300
1301    /// List all broker accounts.
1302    ///
1303    /// # Arguments
1304    /// * `params` - Optional query parameters for filtering
1305    ///
1306    /// # Returns
1307    /// List of broker accounts
1308    pub async fn list_broker_accounts(
1309        &self,
1310        params: &ListBrokerAccountsParams,
1311    ) -> Result<Vec<BrokerAccount>> {
1312        self.get_with_params("/v1/accounts", params).await
1313    }
1314
1315    /// Get a broker account by ID.
1316    ///
1317    /// # Arguments
1318    /// * `account_id` - The account ID
1319    ///
1320    /// # Returns
1321    /// The broker account
1322    pub async fn get_broker_account(&self, account_id: &str) -> Result<BrokerAccount> {
1323        self.get(&format!("/v1/accounts/{}", account_id)).await
1324    }
1325
1326    /// Update a broker account.
1327    ///
1328    /// # Arguments
1329    /// * `account_id` - The account ID
1330    /// * `request` - Update request with fields to change
1331    ///
1332    /// # Returns
1333    /// The updated broker account
1334    pub async fn update_broker_account(
1335        &self,
1336        account_id: &str,
1337        request: &UpdateBrokerAccountRequest,
1338    ) -> Result<BrokerAccount> {
1339        self.patch(&format!("/v1/accounts/{}", account_id), request)
1340            .await
1341    }
1342
1343    /// Close a broker account.
1344    ///
1345    /// # Arguments
1346    /// * `account_id` - The account ID to close
1347    pub async fn close_broker_account(&self, account_id: &str) -> Result<()> {
1348        self.delete(&format!("/v1/accounts/{}", account_id)).await
1349    }
1350
1351    /// Get trading account details for a broker account.
1352    ///
1353    /// # Arguments
1354    /// * `account_id` - The broker account ID
1355    ///
1356    /// # Returns
1357    /// Trading account details
1358    pub async fn get_broker_trading_account(&self, account_id: &str) -> Result<Account> {
1359        self.get(&format!("/v1/accounts/{}/trading", account_id))
1360            .await
1361    }
1362
1363    // ========================================================================
1364    // CIP (Customer Identification Program) Endpoints
1365    // ========================================================================
1366
1367    /// Submit CIP data for an account.
1368    ///
1369    /// # Arguments
1370    /// * `account_id` - The account ID
1371    /// * `cip_info` - CIP information to submit
1372    ///
1373    /// # Returns
1374    /// The submitted CIP info
1375    pub async fn submit_cip(&self, account_id: &str, cip_info: &CipInfo) -> Result<CipInfo> {
1376        self.post(&format!("/v1/accounts/{}/cip", account_id), cip_info)
1377            .await
1378    }
1379
1380    /// Get CIP status for an account.
1381    ///
1382    /// # Arguments
1383    /// * `account_id` - The account ID
1384    ///
1385    /// # Returns
1386    /// CIP information
1387    pub async fn get_cip(&self, account_id: &str) -> Result<CipInfo> {
1388        self.get(&format!("/v1/accounts/{}/cip", account_id)).await
1389    }
1390
1391    // ========================================================================
1392    // Document Endpoints
1393    // ========================================================================
1394
1395    /// Upload a document for an account.
1396    ///
1397    /// # Arguments
1398    /// * `account_id` - The account ID
1399    /// * `document` - Document to upload
1400    ///
1401    /// # Returns
1402    /// Upload confirmation
1403    pub async fn upload_document(
1404        &self,
1405        account_id: &str,
1406        document: &Document,
1407    ) -> Result<DocumentUploadResponse> {
1408        self.post(
1409            &format!("/v1/accounts/{}/documents/upload", account_id),
1410            document,
1411        )
1412        .await
1413    }
1414
1415    /// List documents for an account.
1416    ///
1417    /// # Arguments
1418    /// * `account_id` - The account ID
1419    ///
1420    /// # Returns
1421    /// List of documents
1422    pub async fn list_documents(&self, account_id: &str) -> Result<Vec<DocumentInfo>> {
1423        self.get(&format!("/v1/accounts/{}/documents", account_id))
1424            .await
1425    }
1426
1427    /// Get a specific document.
1428    ///
1429    /// # Arguments
1430    /// * `account_id` - The account ID
1431    /// * `document_id` - The document ID
1432    ///
1433    /// # Returns
1434    /// Document information
1435    pub async fn get_document(&self, account_id: &str, document_id: &str) -> Result<DocumentInfo> {
1436        self.get(&format!(
1437            "/v1/accounts/{}/documents/{}",
1438            account_id, document_id
1439        ))
1440        .await
1441    }
1442
1443    /// Delete a document.
1444    ///
1445    /// # Arguments
1446    /// * `account_id` - The account ID
1447    /// * `document_id` - The document ID
1448    pub async fn delete_document(&self, account_id: &str, document_id: &str) -> Result<()> {
1449        self.delete(&format!(
1450            "/v1/accounts/{}/documents/{}",
1451            account_id, document_id
1452        ))
1453        .await
1454    }
1455}
1456
1457/// Response for document upload.
1458#[derive(Debug, Serialize, Deserialize)]
1459pub struct DocumentUploadResponse {
1460    /// Document ID.
1461    pub id: String,
1462    /// Document type.
1463    pub document_type: DocumentType,
1464    /// Upload status.
1465    pub status: String,
1466}
1467
1468/// Document information.
1469#[derive(Debug, Serialize, Deserialize)]
1470pub struct DocumentInfo {
1471    /// Document ID.
1472    pub id: String,
1473    /// Document type.
1474    pub document_type: DocumentType,
1475    /// Document sub-type.
1476    #[serde(skip_serializing_if = "Option::is_none")]
1477    pub document_sub_type: Option<String>,
1478    /// Created at timestamp.
1479    pub created_at: DateTime<Utc>,
1480}
1481
1482// ============================================================================
1483// Broker API - Funding & Transfers Endpoints
1484// ============================================================================
1485
1486impl AlpacaHttpClient {
1487    // ========================================================================
1488    // ACH Relationship Endpoints
1489    // ========================================================================
1490
1491    /// Create an ACH relationship for an account.
1492    ///
1493    /// # Arguments
1494    /// * `account_id` - The account ID
1495    /// * `request` - ACH relationship creation request
1496    ///
1497    /// # Returns
1498    /// The created ACH relationship
1499    pub async fn create_ach_relationship(
1500        &self,
1501        account_id: &str,
1502        request: &CreateAchRelationshipRequest,
1503    ) -> Result<AchRelationship> {
1504        self.post(
1505            &format!("/v1/accounts/{}/ach_relationships", account_id),
1506            request,
1507        )
1508        .await
1509    }
1510
1511    /// List ACH relationships for an account.
1512    ///
1513    /// # Arguments
1514    /// * `account_id` - The account ID
1515    ///
1516    /// # Returns
1517    /// List of ACH relationships
1518    pub async fn list_ach_relationships(&self, account_id: &str) -> Result<Vec<AchRelationship>> {
1519        self.get(&format!("/v1/accounts/{}/ach_relationships", account_id))
1520            .await
1521    }
1522
1523    /// Delete an ACH relationship.
1524    ///
1525    /// # Arguments
1526    /// * `account_id` - The account ID
1527    /// * `relationship_id` - The relationship ID to delete
1528    pub async fn delete_ach_relationship(
1529        &self,
1530        account_id: &str,
1531        relationship_id: &str,
1532    ) -> Result<()> {
1533        self.delete(&format!(
1534            "/v1/accounts/{}/ach_relationships/{}",
1535            account_id, relationship_id
1536        ))
1537        .await
1538    }
1539
1540    // ========================================================================
1541    // Transfer Endpoints
1542    // ========================================================================
1543
1544    /// Create a transfer for an account.
1545    ///
1546    /// # Arguments
1547    /// * `account_id` - The account ID
1548    /// * `request` - Transfer creation request
1549    ///
1550    /// # Returns
1551    /// The created transfer
1552    pub async fn create_transfer(
1553        &self,
1554        account_id: &str,
1555        request: &CreateTransferRequest,
1556    ) -> Result<Transfer> {
1557        self.post(&format!("/v1/accounts/{}/transfers", account_id), request)
1558            .await
1559    }
1560
1561    /// List transfers for an account.
1562    ///
1563    /// # Arguments
1564    /// * `account_id` - The account ID
1565    /// * `params` - Optional query parameters
1566    ///
1567    /// # Returns
1568    /// List of transfers
1569    pub async fn list_transfers(
1570        &self,
1571        account_id: &str,
1572        params: &ListTransfersParams,
1573    ) -> Result<Vec<Transfer>> {
1574        self.get_with_params(&format!("/v1/accounts/{}/transfers", account_id), params)
1575            .await
1576    }
1577
1578    /// Get a specific transfer.
1579    ///
1580    /// # Arguments
1581    /// * `account_id` - The account ID
1582    /// * `transfer_id` - The transfer ID
1583    ///
1584    /// # Returns
1585    /// The transfer
1586    pub async fn get_transfer(&self, account_id: &str, transfer_id: &str) -> Result<Transfer> {
1587        self.get(&format!(
1588            "/v1/accounts/{}/transfers/{}",
1589            account_id, transfer_id
1590        ))
1591        .await
1592    }
1593
1594    /// Cancel a transfer.
1595    ///
1596    /// # Arguments
1597    /// * `account_id` - The account ID
1598    /// * `transfer_id` - The transfer ID to cancel
1599    pub async fn cancel_transfer(&self, account_id: &str, transfer_id: &str) -> Result<()> {
1600        self.delete(&format!(
1601            "/v1/accounts/{}/transfers/{}",
1602            account_id, transfer_id
1603        ))
1604        .await
1605    }
1606
1607    // ========================================================================
1608    // Wire Bank Endpoints
1609    // ========================================================================
1610
1611    /// List recipient banks for wire transfers.
1612    ///
1613    /// # Arguments
1614    /// * `account_id` - The account ID
1615    ///
1616    /// # Returns
1617    /// List of wire banks
1618    pub async fn list_wire_banks(&self, account_id: &str) -> Result<Vec<WireBank>> {
1619        self.get(&format!("/v1/accounts/{}/recipient_banks", account_id))
1620            .await
1621    }
1622
1623    /// Create a recipient bank for wire transfers.
1624    ///
1625    /// # Arguments
1626    /// * `account_id` - The account ID
1627    /// * `request` - Wire bank creation request
1628    ///
1629    /// # Returns
1630    /// The created wire bank
1631    pub async fn create_wire_bank(
1632        &self,
1633        account_id: &str,
1634        request: &CreateWireBankRequest,
1635    ) -> Result<WireBank> {
1636        self.post(
1637            &format!("/v1/accounts/{}/recipient_banks", account_id),
1638            request,
1639        )
1640        .await
1641    }
1642
1643    /// Delete a recipient bank.
1644    ///
1645    /// # Arguments
1646    /// * `account_id` - The account ID
1647    /// * `bank_id` - The bank ID to delete
1648    pub async fn delete_wire_bank(&self, account_id: &str, bank_id: &str) -> Result<()> {
1649        self.delete(&format!(
1650            "/v1/accounts/{}/recipient_banks/{}",
1651            account_id, bank_id
1652        ))
1653        .await
1654    }
1655
1656    // ========================================================================
1657    // Journal Endpoints
1658    // ========================================================================
1659
1660    /// Create a journal entry.
1661    ///
1662    /// # Arguments
1663    /// * `request` - Journal creation request
1664    ///
1665    /// # Returns
1666    /// The created journal
1667    pub async fn create_journal(&self, request: &CreateJournalRequest) -> Result<Journal> {
1668        self.post("/v1/journals", request).await
1669    }
1670
1671    /// List journal entries.
1672    ///
1673    /// # Arguments
1674    /// * `params` - Optional query parameters
1675    ///
1676    /// # Returns
1677    /// List of journals
1678    pub async fn list_journals(&self, params: &ListJournalsParams) -> Result<Vec<Journal>> {
1679        self.get_with_params("/v1/journals", params).await
1680    }
1681
1682    /// Create batch journal entries.
1683    ///
1684    /// # Arguments
1685    /// * `request` - Batch journal creation request
1686    ///
1687    /// # Returns
1688    /// List of created journals
1689    pub async fn create_batch_journals(
1690        &self,
1691        request: &CreateBatchJournalRequest,
1692    ) -> Result<Vec<Journal>> {
1693        self.post("/v1/journals/batch", request).await
1694    }
1695
1696    /// Delete a journal entry.
1697    ///
1698    /// # Arguments
1699    /// * `journal_id` - The journal ID to delete
1700    pub async fn delete_journal(&self, journal_id: &str) -> Result<()> {
1701        self.delete(&format!("/v1/journals/{}", journal_id)).await
1702    }
1703}
1704
1705// ============================================================================
1706// Enhanced Crypto Trading Endpoints
1707// ============================================================================
1708
1709/// Response for crypto snapshots (multi-symbol).
1710#[derive(Debug, Serialize, Deserialize)]
1711pub struct MultiCryptoSnapshotsResponse {
1712    /// Map of symbol to snapshot.
1713    pub snapshots: std::collections::HashMap<String, CryptoSnapshot>,
1714}
1715
1716/// Response for multi-symbol crypto bars.
1717#[derive(Debug, Serialize, Deserialize)]
1718pub struct MultiCryptoBarsResponse {
1719    /// Map of symbol to bars.
1720    pub bars: std::collections::HashMap<String, Vec<CryptoBar>>,
1721    /// Next page token.
1722    pub next_page_token: Option<String>,
1723}
1724
1725/// Response for latest crypto bars (multi-symbol).
1726#[derive(Debug, Serialize, Deserialize)]
1727pub struct LatestCryptoBarsResponse {
1728    /// Map of symbol to latest bar.
1729    pub bars: std::collections::HashMap<String, CryptoBar>,
1730}
1731
1732/// Response for latest crypto quotes (multi-symbol).
1733#[derive(Debug, Serialize, Deserialize)]
1734pub struct LatestCryptoQuotesResponse {
1735    /// Map of symbol to latest quote.
1736    pub quotes: std::collections::HashMap<String, CryptoQuote>,
1737}
1738
1739/// Response for latest crypto trades (multi-symbol).
1740#[derive(Debug, Serialize, Deserialize)]
1741pub struct LatestCryptoTradesResponse {
1742    /// Map of symbol to latest trade.
1743    pub trades: std::collections::HashMap<String, CryptoTrade>,
1744}
1745
1746/// Response for crypto orderbooks.
1747#[derive(Debug, Serialize, Deserialize)]
1748pub struct CryptoOrderbooksResponse {
1749    /// Map of symbol to orderbook.
1750    pub orderbooks: std::collections::HashMap<String, CryptoOrderbook>,
1751}
1752
1753impl AlpacaHttpClient {
1754    // ========================================================================
1755    // Crypto Wallet Endpoints (Broker API)
1756    // ========================================================================
1757
1758    /// List crypto wallets for an account.
1759    ///
1760    /// # Arguments
1761    /// * `account_id` - The account ID
1762    ///
1763    /// # Returns
1764    /// List of crypto wallets
1765    pub async fn list_crypto_wallets(&self, account_id: &str) -> Result<Vec<BrokerCryptoWallet>> {
1766        self.get(&format!("/v1/accounts/{}/wallets", account_id))
1767            .await
1768    }
1769
1770    /// Create a crypto wallet for an account.
1771    ///
1772    /// # Arguments
1773    /// * `account_id` - The account ID
1774    /// * `request` - Wallet creation request
1775    ///
1776    /// # Returns
1777    /// The created wallet
1778    pub async fn create_crypto_wallet(
1779        &self,
1780        account_id: &str,
1781        request: &CreateCryptoWalletRequest,
1782    ) -> Result<BrokerCryptoWallet> {
1783        self.post(&format!("/v1/accounts/{}/wallets", account_id), request)
1784            .await
1785    }
1786
1787    /// Get a crypto wallet by asset.
1788    ///
1789    /// # Arguments
1790    /// * `account_id` - The account ID
1791    /// * `asset` - The asset symbol (e.g., BTC, ETH)
1792    ///
1793    /// # Returns
1794    /// The crypto wallet
1795    pub async fn get_crypto_wallet(
1796        &self,
1797        account_id: &str,
1798        asset: &str,
1799    ) -> Result<BrokerCryptoWallet> {
1800        self.get(&format!("/v1/accounts/{}/wallets/{}", account_id, asset))
1801            .await
1802    }
1803
1804    /// List crypto wallet transfers.
1805    ///
1806    /// # Arguments
1807    /// * `account_id` - The account ID
1808    ///
1809    /// # Returns
1810    /// List of crypto transfers
1811    pub async fn list_crypto_transfers(&self, account_id: &str) -> Result<Vec<CryptoTransfer>> {
1812        self.get(&format!("/v1/accounts/{}/wallets/transfers", account_id))
1813            .await
1814    }
1815
1816    /// Create a crypto wallet transfer.
1817    ///
1818    /// # Arguments
1819    /// * `account_id` - The account ID
1820    /// * `asset` - The asset symbol
1821    /// * `request` - Transfer request
1822    ///
1823    /// # Returns
1824    /// The created transfer
1825    pub async fn create_crypto_transfer(
1826        &self,
1827        account_id: &str,
1828        asset: &str,
1829        request: &CreateCryptoTransferRequest,
1830    ) -> Result<CryptoTransfer> {
1831        self.post(
1832            &format!("/v1/accounts/{}/wallets/{}/transfers", account_id, asset),
1833            request,
1834        )
1835        .await
1836    }
1837
1838    /// List whitelisted crypto addresses.
1839    ///
1840    /// # Arguments
1841    /// * `account_id` - The account ID
1842    ///
1843    /// # Returns
1844    /// List of whitelisted addresses
1845    pub async fn list_crypto_whitelists(
1846        &self,
1847        account_id: &str,
1848    ) -> Result<Vec<CryptoWhitelistAddress>> {
1849        self.get(&format!("/v1/accounts/{}/wallets/whitelists", account_id))
1850            .await
1851    }
1852
1853    /// Add a whitelisted crypto address.
1854    ///
1855    /// # Arguments
1856    /// * `account_id` - The account ID
1857    /// * `request` - Whitelist request
1858    ///
1859    /// # Returns
1860    /// The created whitelist entry
1861    pub async fn create_crypto_whitelist(
1862        &self,
1863        account_id: &str,
1864        request: &CreateCryptoWhitelistRequest,
1865    ) -> Result<CryptoWhitelistAddress> {
1866        self.post(
1867            &format!("/v1/accounts/{}/wallets/whitelists", account_id),
1868            request,
1869        )
1870        .await
1871    }
1872
1873    // ========================================================================
1874    // Enhanced Crypto Market Data Endpoints
1875    // ========================================================================
1876
1877    /// Get multi-symbol crypto bars.
1878    ///
1879    /// # Arguments
1880    /// * `params` - Query parameters
1881    ///
1882    /// # Returns
1883    /// Crypto bar data for multiple symbols
1884    pub async fn get_multi_crypto_bars(
1885        &self,
1886        params: &CryptoBarsParams,
1887    ) -> Result<MultiCryptoBarsResponse> {
1888        self.get_with_params("/v1beta3/crypto/us/bars", params)
1889            .await
1890    }
1891
1892    /// Get latest crypto bars.
1893    ///
1894    /// # Arguments
1895    /// * `symbols` - Comma-separated list of symbols
1896    ///
1897    /// # Returns
1898    /// Latest bar for each symbol
1899    pub async fn get_latest_crypto_bars(&self, symbols: &str) -> Result<LatestCryptoBarsResponse> {
1900        #[derive(Serialize)]
1901        struct Params<'a> {
1902            symbols: &'a str,
1903        }
1904        self.get_with_params("/v1beta3/crypto/us/latest/bars", &Params { symbols })
1905            .await
1906    }
1907
1908    /// Get latest crypto quotes.
1909    ///
1910    /// # Arguments
1911    /// * `symbols` - Comma-separated list of symbols
1912    ///
1913    /// # Returns
1914    /// Latest quote for each symbol
1915    pub async fn get_latest_crypto_quotes(
1916        &self,
1917        symbols: &str,
1918    ) -> Result<LatestCryptoQuotesResponse> {
1919        #[derive(Serialize)]
1920        struct Params<'a> {
1921            symbols: &'a str,
1922        }
1923        self.get_with_params("/v1beta3/crypto/us/latest/quotes", &Params { symbols })
1924            .await
1925    }
1926
1927    /// Get latest crypto trades.
1928    ///
1929    /// # Arguments
1930    /// * `symbols` - Comma-separated list of symbols
1931    ///
1932    /// # Returns
1933    /// Latest trade for each symbol
1934    pub async fn get_latest_crypto_trades(
1935        &self,
1936        symbols: &str,
1937    ) -> Result<LatestCryptoTradesResponse> {
1938        #[derive(Serialize)]
1939        struct Params<'a> {
1940            symbols: &'a str,
1941        }
1942        self.get_with_params("/v1beta3/crypto/us/latest/trades", &Params { symbols })
1943            .await
1944    }
1945
1946    /// Get crypto snapshots for multiple symbols.
1947    ///
1948    /// # Arguments
1949    /// * `symbols` - Comma-separated list of symbols
1950    ///
1951    /// # Returns
1952    /// Snapshots for each symbol
1953    pub async fn get_multi_crypto_snapshots(
1954        &self,
1955        symbols: &str,
1956    ) -> Result<MultiCryptoSnapshotsResponse> {
1957        #[derive(Serialize)]
1958        struct Params<'a> {
1959            symbols: &'a str,
1960        }
1961        self.get_with_params("/v1beta3/crypto/us/snapshots", &Params { symbols })
1962            .await
1963    }
1964
1965    /// Get crypto orderbooks.
1966    ///
1967    /// # Arguments
1968    /// * `symbols` - Comma-separated list of symbols
1969    ///
1970    /// # Returns
1971    /// Orderbooks for each symbol
1972    pub async fn get_crypto_orderbooks(&self, symbols: &str) -> Result<CryptoOrderbooksResponse> {
1973        #[derive(Serialize)]
1974        struct Params<'a> {
1975            symbols: &'a str,
1976        }
1977        self.get_with_params("/v1beta3/crypto/us/latest/orderbooks", &Params { symbols })
1978            .await
1979    }
1980}
1981
1982// ============================================================================
1983// News API Endpoints
1984// ============================================================================
1985
1986/// Response for enhanced news request.
1987#[derive(Debug, Serialize, Deserialize)]
1988pub struct EnhancedNewsResponse {
1989    /// News articles.
1990    pub news: Vec<EnhancedNewsArticle>,
1991    /// Next page token.
1992    pub next_page_token: Option<String>,
1993}
1994
1995impl AlpacaHttpClient {
1996    /// Get enhanced news articles with images and full content.
1997    ///
1998    /// # Arguments
1999    /// * `params` - Query parameters for filtering news
2000    ///
2001    /// # Returns
2002    /// List of enhanced news articles with pagination
2003    pub async fn get_enhanced_news(
2004        &self,
2005        params: &alpaca_base::NewsParams,
2006    ) -> Result<EnhancedNewsResponse> {
2007        self.get_with_params("/v1beta1/news", params).await
2008    }
2009
2010    /// Get enhanced news for specific symbols.
2011    ///
2012    /// # Arguments
2013    /// * `symbols` - Comma-separated list of symbols
2014    /// * `limit` - Maximum number of articles
2015    ///
2016    /// # Returns
2017    /// List of enhanced news articles
2018    pub async fn get_enhanced_news_for_symbols(
2019        &self,
2020        symbols: &str,
2021        limit: u32,
2022    ) -> Result<EnhancedNewsResponse> {
2023        let params = alpaca_base::NewsParams::new().symbols(symbols).limit(limit);
2024        self.get_enhanced_news(&params).await
2025    }
2026
2027    /// Get latest enhanced news.
2028    ///
2029    /// # Arguments
2030    /// * `limit` - Maximum number of articles
2031    ///
2032    /// # Returns
2033    /// List of latest enhanced news articles
2034    pub async fn get_latest_enhanced_news(&self, limit: u32) -> Result<EnhancedNewsResponse> {
2035        let params = alpaca_base::NewsParams::new().sort_desc().limit(limit);
2036        self.get_enhanced_news(&params).await
2037    }
2038}
2039
2040// ============================================================================
2041// OAuth 2.0 Endpoints
2042// ============================================================================
2043
2044impl AlpacaHttpClient {
2045    /// Exchange authorization code for OAuth token.
2046    ///
2047    /// # Arguments
2048    /// * `request` - Token exchange request
2049    ///
2050    /// # Returns
2051    /// OAuth token
2052    pub async fn oauth_exchange_code(&self, request: &OAuthTokenRequest) -> Result<OAuthToken> {
2053        self.post("/oauth/token", request).await
2054    }
2055
2056    /// Refresh OAuth token.
2057    ///
2058    /// # Arguments
2059    /// * `request` - Token refresh request
2060    ///
2061    /// # Returns
2062    /// New OAuth token
2063    pub async fn oauth_refresh_token(&self, request: &OAuthTokenRequest) -> Result<OAuthToken> {
2064        self.post("/oauth/token", request).await
2065    }
2066
2067    /// Revoke OAuth token.
2068    ///
2069    /// # Arguments
2070    /// * `request` - Token revoke request
2071    pub async fn oauth_revoke_token(&self, request: &OAuthRevokeRequest) -> Result<()> {
2072        let _: serde_json::Value = self.post("/oauth/revoke", request).await?;
2073        Ok(())
2074    }
2075}
2076
2077// ============================================================================
2078// Broker API Events (SSE) Endpoints
2079// ============================================================================
2080
2081impl AlpacaHttpClient {
2082    /// Get account status events (SSE endpoint URL).
2083    ///
2084    /// Note: This returns the URL for SSE streaming. Use an SSE client to connect.
2085    ///
2086    /// # Arguments
2087    /// * `params` - Optional query parameters
2088    ///
2089    /// # Returns
2090    /// SSE endpoint URL
2091    #[must_use]
2092    pub fn get_account_status_events_url(&self, params: &SseEventParams) -> String {
2093        let base = "/v1/events/accounts/status";
2094        self.build_sse_url(base, params)
2095    }
2096
2097    /// Get transfer status events (SSE endpoint URL).
2098    ///
2099    /// Note: This returns the URL for SSE streaming. Use an SSE client to connect.
2100    ///
2101    /// # Arguments
2102    /// * `params` - Optional query parameters
2103    ///
2104    /// # Returns
2105    /// SSE endpoint URL
2106    #[must_use]
2107    pub fn get_transfer_status_events_url(&self, params: &SseEventParams) -> String {
2108        let base = "/v1/events/transfers/status";
2109        self.build_sse_url(base, params)
2110    }
2111
2112    /// Get trade events (SSE endpoint URL).
2113    ///
2114    /// Note: This returns the URL for SSE streaming. Use an SSE client to connect.
2115    ///
2116    /// # Arguments
2117    /// * `params` - Optional query parameters
2118    ///
2119    /// # Returns
2120    /// SSE endpoint URL
2121    #[must_use]
2122    pub fn get_trade_events_url(&self, params: &SseEventParams) -> String {
2123        let base = "/v1/events/trades";
2124        self.build_sse_url(base, params)
2125    }
2126
2127    /// Get journal status events (SSE endpoint URL).
2128    ///
2129    /// Note: This returns the URL for SSE streaming. Use an SSE client to connect.
2130    ///
2131    /// # Arguments
2132    /// * `params` - Optional query parameters
2133    ///
2134    /// # Returns
2135    /// SSE endpoint URL
2136    #[must_use]
2137    pub fn get_journal_status_events_url(&self, params: &SseEventParams) -> String {
2138        let base = "/v1/events/journals/status";
2139        self.build_sse_url(base, params)
2140    }
2141
2142    /// Get non-trade activity events (SSE endpoint URL).
2143    ///
2144    /// Note: This returns the URL for SSE streaming. Use an SSE client to connect.
2145    ///
2146    /// # Arguments
2147    /// * `params` - Optional query parameters
2148    ///
2149    /// # Returns
2150    /// SSE endpoint URL
2151    #[must_use]
2152    pub fn get_nta_events_url(&self, params: &SseEventParams) -> String {
2153        let base = "/v2beta1/events/nta";
2154        self.build_sse_url(base, params)
2155    }
2156
2157    /// Build SSE URL with query parameters.
2158    fn build_sse_url(&self, base: &str, params: &SseEventParams) -> String {
2159        let mut url = base.to_string();
2160        let mut query_parts = Vec::new();
2161
2162        if let Some(ref account_id) = params.account_id {
2163            query_parts.push(format!("account_id={}", account_id));
2164        }
2165        if let Some(ref since) = params.since {
2166            query_parts.push(format!("since={}", since));
2167        }
2168        if let Some(ref until) = params.until {
2169            query_parts.push(format!("until={}", until));
2170        }
2171
2172        if !query_parts.is_empty() {
2173            url.push('?');
2174            url.push_str(&query_parts.join("&"));
2175        }
2176
2177        url
2178    }
2179}
2180
2181// ============================================================================
2182// Enhanced Assets API Endpoints
2183// ============================================================================
2184
2185impl AlpacaHttpClient {
2186    /// List enhanced assets with filtering.
2187    ///
2188    /// # Arguments
2189    /// * `params` - Query parameters for filtering
2190    ///
2191    /// # Returns
2192    /// List of enhanced assets
2193    pub async fn list_enhanced_assets(
2194        &self,
2195        params: &ListAssetsParams,
2196    ) -> Result<Vec<EnhancedAsset>> {
2197        self.get_with_params("/v2/assets", params).await
2198    }
2199
2200    /// Get enhanced asset by symbol or ID.
2201    ///
2202    /// # Arguments
2203    /// * `symbol_or_id` - Symbol or asset ID
2204    ///
2205    /// # Returns
2206    /// Enhanced asset details
2207    pub async fn get_enhanced_asset(&self, symbol_or_id: &str) -> Result<EnhancedAsset> {
2208        self.get(&format!("/v2/assets/{}", symbol_or_id)).await
2209    }
2210
2211    /// Get options contracts for an underlying symbol.
2212    ///
2213    /// # Arguments
2214    /// * `symbol` - Underlying symbol
2215    ///
2216    /// # Returns
2217    /// List of option contract assets
2218    pub async fn get_asset_options(&self, symbol: &str) -> Result<Vec<OptionContractAsset>> {
2219        self.get(&format!("/v2/assets/{}/options", symbol)).await
2220    }
2221
2222    /// List corporate action announcements.
2223    ///
2224    /// # Arguments
2225    /// * `params` - Query parameters for filtering
2226    ///
2227    /// # Returns
2228    /// List of corporate action announcements
2229    pub async fn list_announcements(
2230        &self,
2231        params: &ListAnnouncementsParams,
2232    ) -> Result<Vec<CorporateActionAnnouncement>> {
2233        self.get_with_params("/v1beta1/corporate-actions/announcements", params)
2234            .await
2235    }
2236
2237    /// Get a specific corporate action announcement.
2238    ///
2239    /// # Arguments
2240    /// * `announcement_id` - Announcement ID
2241    ///
2242    /// # Returns
2243    /// Corporate action announcement
2244    pub async fn get_announcement(
2245        &self,
2246        announcement_id: &str,
2247    ) -> Result<CorporateActionAnnouncement> {
2248        self.get(&format!(
2249            "/v1beta1/corporate-actions/announcements/{}",
2250            announcement_id
2251        ))
2252        .await
2253    }
2254}
2255
2256// ============================================================================
2257// Enhanced Account Activities Endpoints
2258// ============================================================================
2259
2260impl AlpacaHttpClient {
2261    /// List account activities with filtering.
2262    ///
2263    /// # Arguments
2264    /// * `params` - Query parameters for filtering
2265    ///
2266    /// # Returns
2267    /// List of account activities
2268    pub async fn list_activities(
2269        &self,
2270        params: &ListActivitiesParams,
2271    ) -> Result<Vec<AccountActivity>> {
2272        self.get_with_params("/v2/account/activities", params).await
2273    }
2274
2275    /// List account activities by type.
2276    ///
2277    /// # Arguments
2278    /// * `activity_type` - Activity type to filter by
2279    /// * `params` - Additional query parameters
2280    ///
2281    /// # Returns
2282    /// List of account activities
2283    pub async fn list_activities_by_type(
2284        &self,
2285        activity_type: &str,
2286        params: &ListActivitiesParams,
2287    ) -> Result<Vec<AccountActivity>> {
2288        self.get_with_params(&format!("/v2/account/activities/{}", activity_type), params)
2289            .await
2290    }
2291
2292    /// List all broker accounts activities.
2293    ///
2294    /// # Arguments
2295    /// * `params` - Query parameters for filtering
2296    ///
2297    /// # Returns
2298    /// List of account activities across all accounts
2299    pub async fn list_broker_activities(
2300        &self,
2301        params: &ListActivitiesParams,
2302    ) -> Result<Vec<AccountActivity>> {
2303        self.get_with_params("/v1/accounts/activities", params)
2304            .await
2305    }
2306
2307    /// List activities for a specific broker account.
2308    ///
2309    /// # Arguments
2310    /// * `account_id` - Account ID
2311    /// * `params` - Query parameters for filtering
2312    ///
2313    /// # Returns
2314    /// List of account activities
2315    pub async fn list_broker_account_activities(
2316        &self,
2317        account_id: &str,
2318        params: &ListActivitiesParams,
2319    ) -> Result<Vec<AccountActivity>> {
2320        self.get_with_params(&format!("/v1/accounts/{}/activities", account_id), params)
2321            .await
2322    }
2323}
2324
2325// ============================================================================
2326// Portfolio Management Endpoints
2327// ============================================================================
2328
2329impl AlpacaHttpClient {
2330    /// Create rebalancing portfolio.
2331    ///
2332    /// # Arguments
2333    /// * `request` - Portfolio creation request
2334    ///
2335    /// # Returns
2336    /// Created portfolio
2337    pub async fn create_rebalance_portfolio(
2338        &self,
2339        request: &RebalancePortfolioRequest,
2340    ) -> Result<RebalancePortfolio> {
2341        self.post("/v1/rebalancing/portfolios", request).await
2342    }
2343
2344    /// List rebalancing portfolios.
2345    ///
2346    /// # Returns
2347    /// List of portfolios
2348    pub async fn list_rebalance_portfolios(&self) -> Result<Vec<RebalancePortfolio>> {
2349        self.get("/v1/rebalancing/portfolios").await
2350    }
2351
2352    /// Get rebalancing portfolio.
2353    ///
2354    /// # Arguments
2355    /// * `portfolio_id` - Portfolio ID
2356    ///
2357    /// # Returns
2358    /// Portfolio
2359    pub async fn get_rebalance_portfolio(&self, portfolio_id: &str) -> Result<RebalancePortfolio> {
2360        self.get(&format!("/v1/rebalancing/portfolios/{}", portfolio_id))
2361            .await
2362    }
2363
2364    /// Delete rebalancing portfolio.
2365    ///
2366    /// # Arguments
2367    /// * `portfolio_id` - Portfolio ID
2368    pub async fn delete_rebalance_portfolio(&self, portfolio_id: &str) -> Result<()> {
2369        let _: serde_json::Value = self
2370            .delete(&format!("/v1/rebalancing/portfolios/{}", portfolio_id))
2371            .await?;
2372        Ok(())
2373    }
2374
2375    /// Execute rebalance run.
2376    ///
2377    /// # Arguments
2378    /// * `request` - Rebalance run request
2379    ///
2380    /// # Returns
2381    /// Rebalance run
2382    pub async fn execute_rebalance(&self, request: &RebalanceRunRequest) -> Result<RebalanceRun> {
2383        self.post("/v1/rebalancing/runs", request).await
2384    }
2385
2386    /// List rebalance runs.
2387    ///
2388    /// # Returns
2389    /// List of rebalance runs
2390    pub async fn list_rebalance_runs(&self) -> Result<Vec<RebalanceRun>> {
2391        self.get("/v1/rebalancing/runs").await
2392    }
2393}
2394
2395// ============================================================================
2396// Margin and Short Selling Endpoints
2397// ============================================================================
2398
2399impl AlpacaHttpClient {
2400    /// Get locate availability for short selling.
2401    ///
2402    /// # Returns
2403    /// List of available locates
2404    pub async fn get_locates(&self) -> Result<Vec<LocateResponse>> {
2405        self.get("/v1/locate/stocks").await
2406    }
2407
2408    /// Request a locate for short selling.
2409    ///
2410    /// # Arguments
2411    /// * `request` - Locate request
2412    ///
2413    /// # Returns
2414    /// Locate response
2415    pub async fn request_locate(&self, request: &LocateRequest) -> Result<LocateResponse> {
2416        self.post("/v1/locate/stocks", request).await
2417    }
2418}
2419
2420// ============================================================================
2421// Paper Trading Endpoints
2422// ============================================================================
2423
2424impl AlpacaHttpClient {
2425    /// Reset paper trading account.
2426    ///
2427    /// # Returns
2428    /// The reset account
2429    pub async fn reset_paper_account(&self) -> Result<Account> {
2430        self.post("/v2/account/reset", &serde_json::json!({})).await
2431    }
2432}
2433
2434// ============================================================================
2435// Local Currency Trading Endpoints
2436// ============================================================================
2437
2438impl AlpacaHttpClient {
2439    /// Get exchange rates.
2440    ///
2441    /// # Returns
2442    /// List of exchange rates
2443    pub async fn get_exchange_rates(&self) -> Result<Vec<ExchangeRate>> {
2444        self.get("/v1/fx/rates").await
2445    }
2446
2447    /// Get specific exchange rate.
2448    ///
2449    /// # Arguments
2450    /// * `currency_pair` - Currency pair (e.g., "EUR/USD")
2451    ///
2452    /// # Returns
2453    /// Exchange rate
2454    pub async fn get_exchange_rate(&self, currency_pair: &str) -> Result<ExchangeRate> {
2455        self.get(&format!("/v1/fx/rates/{}", currency_pair)).await
2456    }
2457}
2458
2459// ============================================================================
2460// IRA Account Endpoints
2461// ============================================================================
2462
2463impl AlpacaHttpClient {
2464    /// List IRA contributions.
2465    ///
2466    /// # Arguments
2467    /// * `account_id` - Account ID
2468    ///
2469    /// # Returns
2470    /// List of IRA contributions
2471    pub async fn list_ira_contributions(&self, account_id: &str) -> Result<Vec<IraContribution>> {
2472        self.get(&format!("/v1/accounts/{}/ira/contributions", account_id))
2473            .await
2474    }
2475
2476    /// Create IRA contribution.
2477    ///
2478    /// # Arguments
2479    /// * `account_id` - Account ID
2480    /// * `request` - Contribution request
2481    ///
2482    /// # Returns
2483    /// Created contribution
2484    pub async fn create_ira_contribution(
2485        &self,
2486        account_id: &str,
2487        request: &CreateIraContributionRequest,
2488    ) -> Result<IraContribution> {
2489        self.post(
2490            &format!("/v1/accounts/{}/ira/contributions", account_id),
2491            request,
2492        )
2493        .await
2494    }
2495
2496    /// List IRA distributions.
2497    ///
2498    /// # Arguments
2499    /// * `account_id` - Account ID
2500    ///
2501    /// # Returns
2502    /// List of IRA distributions
2503    pub async fn list_ira_distributions(&self, account_id: &str) -> Result<Vec<IraDistribution>> {
2504        self.get(&format!("/v1/accounts/{}/ira/distributions", account_id))
2505            .await
2506    }
2507
2508    /// List IRA beneficiaries.
2509    ///
2510    /// # Arguments
2511    /// * `account_id` - Account ID
2512    ///
2513    /// # Returns
2514    /// List of IRA beneficiaries
2515    pub async fn list_ira_beneficiaries(&self, account_id: &str) -> Result<Vec<IraBeneficiary>> {
2516        self.get(&format!("/v1/accounts/{}/ira/beneficiaries", account_id))
2517            .await
2518    }
2519}
2520
2521#[cfg(test)]
2522mod tests {
2523    use super::*;
2524
2525    #[test]
2526    fn test_create_order_request_market() {
2527        let order = CreateOrderRequest::market("AAPL", OrderSide::Buy, "10");
2528        assert_eq!(order.symbol, "AAPL");
2529        assert_eq!(order.side, OrderSide::Buy);
2530        assert_eq!(order.qty, Some("10".to_string()));
2531        assert_eq!(order.order_type, OrderType::Market);
2532        assert_eq!(order.time_in_force, TimeInForce::Day);
2533    }
2534
2535    #[test]
2536    fn test_create_order_request_limit() {
2537        let order = CreateOrderRequest::limit("AAPL", OrderSide::Buy, "10", "150.00");
2538        assert_eq!(order.symbol, "AAPL");
2539        assert_eq!(order.limit_price, Some("150.00".to_string()));
2540        assert_eq!(order.order_type, OrderType::Limit);
2541    }
2542
2543    #[test]
2544    fn test_create_order_request_stop() {
2545        let order = CreateOrderRequest::stop("AAPL", OrderSide::Sell, "10", "145.00");
2546        assert_eq!(order.stop_price, Some("145.00".to_string()));
2547        assert_eq!(order.order_type, OrderType::Stop);
2548    }
2549
2550    #[test]
2551    fn test_create_order_request_stop_limit() {
2552        let order =
2553            CreateOrderRequest::stop_limit("AAPL", OrderSide::Sell, "10", "145.00", "144.50");
2554        assert_eq!(order.stop_price, Some("145.00".to_string()));
2555        assert_eq!(order.limit_price, Some("144.50".to_string()));
2556        assert_eq!(order.order_type, OrderType::StopLimit);
2557    }
2558
2559    #[test]
2560    fn test_create_order_request_trailing_stop_price() {
2561        let order = CreateOrderRequest::trailing_stop_price("AAPL", OrderSide::Sell, "10", "5.00");
2562        assert_eq!(order.trail_price, Some("5.00".to_string()));
2563        assert_eq!(order.order_type, OrderType::TrailingStop);
2564    }
2565
2566    #[test]
2567    fn test_create_order_request_trailing_stop_percent() {
2568        let order = CreateOrderRequest::trailing_stop_percent("AAPL", OrderSide::Sell, "10", "2.5");
2569        assert_eq!(order.trail_percent, Some("2.5".to_string()));
2570        assert_eq!(order.order_type, OrderType::TrailingStop);
2571    }
2572
2573    #[test]
2574    fn test_create_order_request_bracket() {
2575        let tp = TakeProfit::new("160.00");
2576        let sl = StopLoss::new("140.00");
2577        let order =
2578            CreateOrderRequest::bracket("AAPL", OrderSide::Buy, "10", OrderType::Market, tp, sl);
2579
2580        assert_eq!(order.order_class, Some(OrderClass::Bracket));
2581        assert!(order.take_profit.is_some());
2582        assert!(order.stop_loss.is_some());
2583        assert_eq!(order.take_profit.unwrap().limit_price, "160.00");
2584        assert_eq!(order.stop_loss.unwrap().stop_price, "140.00");
2585    }
2586
2587    #[test]
2588    fn test_create_order_request_oco() {
2589        let tp = TakeProfit::new("160.00");
2590        let sl = StopLoss::with_limit("140.00", "139.50");
2591        let order = CreateOrderRequest::oco("AAPL", OrderSide::Sell, "10", tp, sl);
2592
2593        assert_eq!(order.order_class, Some(OrderClass::Oco));
2594        assert!(order.take_profit.is_some());
2595        assert!(order.stop_loss.is_some());
2596    }
2597
2598    #[test]
2599    fn test_create_order_request_oto() {
2600        let sl = StopLoss::new("140.00");
2601        let order = CreateOrderRequest::oto("AAPL", OrderSide::Buy, "10", OrderType::Limit, sl);
2602
2603        assert_eq!(order.order_class, Some(OrderClass::Oto));
2604        assert!(order.stop_loss.is_some());
2605    }
2606
2607    #[test]
2608    fn test_create_order_request_builder_methods() {
2609        let order = CreateOrderRequest::market("AAPL", OrderSide::Buy, "10")
2610            .time_in_force(TimeInForce::Gtc)
2611            .extended_hours(true)
2612            .client_order_id("my-order-123");
2613
2614        assert_eq!(order.time_in_force, TimeInForce::Gtc);
2615        assert_eq!(order.extended_hours, Some(true));
2616        assert_eq!(order.client_order_id, Some("my-order-123".to_string()));
2617    }
2618
2619    #[test]
2620    fn test_create_order_request_position_intent() {
2621        let order = CreateOrderRequest::market("AAPL", OrderSide::Buy, "10")
2622            .position_intent(PositionIntent::BuyToOpen);
2623
2624        assert_eq!(order.position_intent, Some(PositionIntent::BuyToOpen));
2625    }
2626
2627    #[test]
2628    fn test_replace_order_request_builder() {
2629        let request = ReplaceOrderRequest::new()
2630            .qty("20")
2631            .limit_price("155.00")
2632            .time_in_force(TimeInForce::Gtc);
2633
2634        assert_eq!(request.qty, Some("20".to_string()));
2635        assert_eq!(request.limit_price, Some("155.00".to_string()));
2636        assert_eq!(request.time_in_force, Some(TimeInForce::Gtc));
2637    }
2638
2639    #[test]
2640    fn test_order_params_builder() {
2641        let params = OrderParams::new()
2642            .status(OrderQueryStatus::Open)
2643            .limit(100)
2644            .nested(true)
2645            .symbols("AAPL,GOOGL")
2646            .side(OrderSide::Buy)
2647            .direction(SortDirection::Desc);
2648
2649        assert_eq!(params.status, Some(OrderQueryStatus::Open));
2650        assert_eq!(params.limit, Some(100));
2651        assert_eq!(params.nested, Some(true));
2652        assert_eq!(params.symbols, Some("AAPL,GOOGL".to_string()));
2653        assert_eq!(params.side, Some(OrderSide::Buy));
2654        assert_eq!(params.direction, Some(SortDirection::Desc));
2655    }
2656
2657    #[test]
2658    fn test_create_order_request_serialization() {
2659        let order = CreateOrderRequest::limit("AAPL", OrderSide::Buy, "10", "150.00")
2660            .client_order_id("test-123");
2661
2662        let json = serde_json::to_string(&order).unwrap();
2663        assert!(json.contains("\"symbol\":\"AAPL\""));
2664        assert!(json.contains("\"side\":\"buy\""));
2665        assert!(json.contains("\"type\":\"limit\""));
2666        assert!(json.contains("\"limit_price\":\"150.00\""));
2667    }
2668
2669    #[test]
2670    fn test_bracket_order_serialization() {
2671        let tp = TakeProfit::new("160.00");
2672        let sl = StopLoss::with_limit("140.00", "139.50");
2673        let order =
2674            CreateOrderRequest::bracket("AAPL", OrderSide::Buy, "10", OrderType::Limit, tp, sl)
2675                .with_limit_price("150.00");
2676
2677        let json = serde_json::to_string(&order).unwrap();
2678        assert!(json.contains("\"order_class\":\"bracket\""));
2679        assert!(json.contains("\"take_profit\""));
2680        assert!(json.contains("\"stop_loss\""));
2681    }
2682}