alpaca_base/
types.rs

1//! Core types for the Alpaca API.
2//!
3//! This module contains all the data structures used to interact with the Alpaca API.
4
5#![allow(missing_docs)]
6
7use chrono::{DateTime, Utc};
8use serde::{Deserialize, Serialize};
9use uuid::Uuid;
10
11/// Trading environment for Alpaca API.
12#[derive(Debug, Clone, PartialEq, Eq)]
13pub enum Environment {
14    /// Paper trading environment for testing.
15    Paper,
16    /// Live trading environment with real money.
17    Live,
18}
19
20impl Environment {
21    /// Returns the base URL for the trading API.
22    #[must_use]
23    pub fn base_url(&self) -> &'static str {
24        match self {
25            Environment::Paper => "https://paper-api.alpaca.markets",
26            Environment::Live => "https://api.alpaca.markets",
27        }
28    }
29
30    /// Returns the base URL for the market data API.
31    #[must_use]
32    pub fn data_url(&self) -> &'static str {
33        "https://data.alpaca.markets"
34    }
35
36    /// Returns the WebSocket URL for streaming data.
37    #[must_use]
38    pub fn websocket_url(&self) -> &'static str {
39        match self {
40            Environment::Paper => "wss://paper-api.alpaca.markets/stream",
41            Environment::Live => "wss://api.alpaca.markets/stream",
42        }
43    }
44}
45
46/// Account information from Alpaca API.
47#[derive(Debug, Serialize, Deserialize, Clone)]
48pub struct Account {
49    /// Unique account identifier.
50    pub id: Uuid,
51    /// Account number.
52    pub account_number: String,
53    /// Current account status.
54    pub status: AccountStatus,
55    /// Account currency (e.g., "USD").
56    pub currency: String,
57    /// Current buying power in dollars.
58    pub buying_power: String,
59    /// Regulation T buying power.
60    pub regt_buying_power: String,
61    /// Day trading buying power.
62    pub daytrading_buying_power: String,
63    /// Cash balance in dollars.
64    pub cash: String,
65    /// Total portfolio value in dollars.
66    pub portfolio_value: String,
67    /// Whether account is flagged as pattern day trader.
68    pub pattern_day_trader: bool,
69    /// Whether trading is blocked.
70    pub trading_blocked: bool,
71    /// Whether transfers are blocked.
72    pub transfers_blocked: bool,
73    /// Whether account is blocked.
74    pub account_blocked: bool,
75    /// Account creation timestamp.
76    pub created_at: DateTime<Utc>,
77    /// Whether trading is suspended by user.
78    pub trade_suspended_by_user: bool,
79    /// Margin multiplier.
80    pub multiplier: String,
81    /// Whether shorting is enabled.
82    pub shorting_enabled: bool,
83    /// Current equity value in dollars.
84    pub equity: String,
85    /// Previous day's equity value in dollars.
86    pub last_equity: String,
87    /// Long positions market value in dollars.
88    pub long_market_value: String,
89    /// Short positions market value in dollars.
90    pub short_market_value: String,
91    /// Initial margin requirement in dollars.
92    pub initial_margin: String,
93    /// Maintenance margin requirement in dollars.
94    pub maintenance_margin: String,
95    /// Previous day's maintenance margin in dollars.
96    pub last_maintenance_margin: String,
97    /// Special memorandum account value.
98    pub sma: String,
99    /// Number of day trades in the last 5 trading days.
100    pub daytrade_count: i32,
101}
102
103/// Account status.
104#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
105#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
106pub enum AccountStatus {
107    /// Account is in onboarding process.
108    Onboarding,
109    /// Account submission failed.
110    SubmissionFailed,
111    /// Account has been submitted for review.
112    Submitted,
113    /// Account information has been updated.
114    AccountUpdated,
115    /// Account is pending approval.
116    ApprovalPending,
117    /// Account is active and can trade.
118    Active,
119    /// Account application was rejected.
120    Rejected,
121    /// Account has been disabled.
122    Disabled,
123    /// Account has been closed.
124    AccountClosed,
125}
126
127/// Asset information.
128#[derive(Debug, Serialize, Deserialize, Clone)]
129pub struct Asset {
130    /// Unique asset identifier.
131    pub id: Uuid,
132    /// Asset class (equity or crypto).
133    pub class: AssetClass,
134    /// Exchange where the asset trades.
135    pub exchange: String,
136    /// Ticker symbol.
137    pub symbol: String,
138    /// Full asset name.
139    pub name: String,
140    /// Current asset status.
141    pub status: AssetStatus,
142    /// Whether the asset can be traded.
143    pub tradable: bool,
144    /// Whether the asset can be used as margin collateral.
145    pub marginable: bool,
146    /// Whether the asset can be shorted.
147    pub shortable: bool,
148    /// Whether the asset is easy to borrow for shorting.
149    pub easy_to_borrow: bool,
150    /// Whether fractional shares are supported.
151    pub fractionable: bool,
152}
153
154/// Asset class.
155#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
156#[serde(rename_all = "snake_case")]
157pub enum AssetClass {
158    /// US equity.
159    UsEquity,
160    /// Cryptocurrency.
161    Crypto,
162}
163
164/// Asset status.
165#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
166#[serde(rename_all = "snake_case")]
167pub enum AssetStatus {
168    /// Asset is active and tradable.
169    Active,
170    /// Asset is inactive.
171    Inactive,
172}
173
174/// Order information.
175#[derive(Debug, Serialize, Deserialize, Clone)]
176pub struct Order {
177    /// Unique order identifier.
178    pub id: Uuid,
179    /// Client-specified order ID.
180    pub client_order_id: String,
181    /// Order creation timestamp.
182    pub created_at: DateTime<Utc>,
183    /// Last update timestamp.
184    pub updated_at: DateTime<Utc>,
185    /// Submission timestamp.
186    pub submitted_at: Option<DateTime<Utc>>,
187    /// Fill timestamp.
188    pub filled_at: Option<DateTime<Utc>>,
189    /// Expiration timestamp.
190    pub expired_at: Option<DateTime<Utc>>,
191    /// Cancellation timestamp.
192    pub canceled_at: Option<DateTime<Utc>>,
193    /// Failure timestamp.
194    pub failed_at: Option<DateTime<Utc>>,
195    /// Replacement timestamp.
196    pub replaced_at: Option<DateTime<Utc>>,
197    /// ID of the order that replaced this one.
198    pub replaced_by: Option<Uuid>,
199    /// ID of the order this one replaces.
200    pub replaces: Option<Uuid>,
201    /// Asset identifier.
202    pub asset_id: Uuid,
203    /// Ticker symbol.
204    pub symbol: String,
205    /// Asset class.
206    pub asset_class: AssetClass,
207    /// Notional value in dollars.
208    pub notional: Option<String>,
209    /// Number of shares.
210    pub qty: Option<String>,
211    /// Number of shares filled.
212    pub filled_qty: String,
213    /// Average fill price in dollars.
214    pub filled_avg_price: Option<String>,
215    /// Order class (simple, bracket, etc.).
216    pub order_class: OrderClass,
217    /// Order type (market, limit, etc.).
218    pub order_type: OrderType,
219    /// Buy or sell.
220    pub side: OrderSide,
221    /// Time in force.
222    pub time_in_force: TimeInForce,
223    /// Limit price in dollars.
224    pub limit_price: Option<String>,
225    /// Stop price in dollars.
226    pub stop_price: Option<String>,
227    /// Current order status.
228    pub status: OrderStatus,
229    /// Whether extended hours trading is enabled.
230    pub extended_hours: bool,
231    /// Child orders for bracket/OCO/OTO orders.
232    pub legs: Option<Vec<Order>>,
233    /// Trailing stop percentage.
234    pub trail_percent: Option<String>,
235    /// Trailing stop price offset in dollars.
236    pub trail_price: Option<String>,
237    /// High water mark for trailing stop.
238    pub hwm: Option<String>,
239}
240
241/// Order class.
242#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
243#[serde(rename_all = "snake_case")]
244pub enum OrderClass {
245    /// Simple order.
246    Simple,
247    /// Bracket order with take-profit and stop-loss.
248    Bracket,
249    /// One-cancels-other order.
250    Oco,
251    /// One-triggers-other order.
252    Oto,
253}
254
255/// Order type.
256#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, Default)]
257#[serde(rename_all = "snake_case")]
258pub enum OrderType {
259    /// Market order.
260    #[default]
261    Market,
262    /// Limit order.
263    Limit,
264    /// Stop order.
265    Stop,
266    /// Stop-limit order.
267    StopLimit,
268    /// Trailing stop order.
269    TrailingStop,
270}
271
272/// Order side.
273#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, Default)]
274#[serde(rename_all = "snake_case")]
275pub enum OrderSide {
276    /// Buy order.
277    #[default]
278    Buy,
279    /// Sell order.
280    Sell,
281}
282
283/// Time in force for orders.
284///
285/// Specifies how long an order remains active before it is executed or expires.
286#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, Default)]
287#[serde(rename_all = "lowercase")]
288pub enum TimeInForce {
289    /// Day order - valid for the trading day.
290    #[default]
291    Day,
292    /// Good Till Canceled - remains active until filled or canceled.
293    Gtc,
294    /// Immediate Or Cancel - executes immediately, cancels unfilled portion.
295    Ioc,
296    /// Fill Or Kill - must be filled entirely immediately or canceled.
297    Fok,
298    /// Market On Open - executes at market open.
299    Opg,
300    /// Market On Close - executes at market close.
301    Cls,
302    /// Good Till Date - remains active until specified date.
303    Gtd,
304}
305
306/// Order status
307#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
308#[serde(rename_all = "snake_case")]
309pub enum OrderStatus {
310    New,
311    PartiallyFilled,
312    Filled,
313    DoneForDay,
314    Canceled,
315    Expired,
316    Replaced,
317    PendingCancel,
318    PendingReplace,
319    PendingReview,
320    Accepted,
321    PendingNew,
322    AcceptedForBidding,
323    Stopped,
324    Rejected,
325    Suspended,
326    Calculated,
327}
328
329/// Position information
330#[derive(Debug, Serialize, Deserialize, Clone)]
331pub struct Position {
332    pub asset_id: Uuid,
333    pub symbol: String,
334    pub exchange: String,
335    pub asset_class: AssetClass,
336    pub avg_entry_price: String,
337    pub qty: String,
338    pub side: PositionSide,
339    pub market_value: String,
340    pub cost_basis: String,
341    pub unrealized_pl: String,
342    pub unrealized_plpc: String,
343    pub unrealized_intraday_pl: String,
344    pub unrealized_intraday_plpc: String,
345    pub current_price: String,
346    pub lastday_price: String,
347    pub change_today: String,
348}
349
350/// Position side
351#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
352#[serde(rename_all = "snake_case")]
353pub enum PositionSide {
354    Long,
355    Short,
356}
357
358/// Market data bar
359#[derive(Debug, Serialize, Deserialize, Clone)]
360pub struct Bar {
361    pub timestamp: DateTime<Utc>,
362    pub open: f64,
363    pub high: f64,
364    pub low: f64,
365    pub close: f64,
366    pub volume: u64,
367    pub trade_count: Option<u64>,
368    pub vwap: Option<f64>,
369}
370
371/// Market data quote
372#[derive(Debug, Serialize, Deserialize, Clone)]
373pub struct Quote {
374    pub timestamp: DateTime<Utc>,
375    pub timeframe: String,
376    pub bid_price: f64,
377    pub bid_size: u32,
378    pub ask_price: f64,
379    pub ask_size: u32,
380    pub bid_exchange: String,
381    pub ask_exchange: String,
382}
383
384/// Market data trade
385#[derive(Debug, Serialize, Deserialize, Clone)]
386pub struct Trade {
387    pub timestamp: DateTime<Utc>,
388    pub price: f64,
389    pub size: u32,
390    pub exchange: String,
391    pub conditions: Vec<String>,
392    pub id: u64,
393}
394
395/// Timeframe for market data
396#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
397#[serde(rename_all = "snake_case")]
398pub enum Timeframe {
399    #[serde(rename = "1Min")]
400    OneMinute,
401    #[serde(rename = "5Min")]
402    FiveMinutes,
403    #[serde(rename = "15Min")]
404    FifteenMinutes,
405    #[serde(rename = "30Min")]
406    ThirtyMinutes,
407    #[serde(rename = "1Hour")]
408    OneHour,
409    #[serde(rename = "1Day")]
410    OneDay,
411    #[serde(rename = "1Week")]
412    OneWeek,
413    #[serde(rename = "1Month")]
414    OneMonth,
415}
416
417/// Watchlist information
418#[derive(Debug, Serialize, Deserialize, Clone)]
419pub struct Watchlist {
420    pub id: Uuid,
421    pub account_id: Uuid,
422    pub created_at: DateTime<Utc>,
423    pub updated_at: DateTime<Utc>,
424    pub name: String,
425    pub assets: Vec<Asset>,
426}
427
428/// Calendar information
429#[derive(Debug, Serialize, Deserialize, Clone)]
430pub struct Calendar {
431    pub date: String,
432    pub open: String,
433    pub close: String,
434    pub session_open: String,
435    pub session_close: String,
436}
437
438/// Clock information
439#[derive(Debug, Serialize, Deserialize, Clone)]
440pub struct Clock {
441    pub timestamp: DateTime<Utc>,
442    pub is_open: bool,
443    pub next_open: DateTime<Utc>,
444    pub next_close: DateTime<Utc>,
445}
446
447/// Portfolio history
448#[derive(Debug, Serialize, Deserialize, Clone)]
449pub struct PortfolioHistory {
450    pub timestamp: Vec<i64>,
451    pub equity: Vec<Option<f64>>,
452    pub profit_loss: Vec<Option<f64>>,
453    pub profit_loss_pct: Vec<Option<f64>>,
454    pub base_value: f64,
455    pub timeframe: String,
456}
457
458/// Account activity
459#[derive(Debug, Serialize, Deserialize, Clone)]
460pub struct AccountActivity {
461    pub id: String,
462    pub account_id: Uuid,
463    pub activity_type: ActivityType,
464    pub date: String,
465    pub net_amount: String,
466    pub symbol: Option<String>,
467    pub qty: Option<String>,
468    pub per_share_amount: Option<String>,
469}
470
471/// Activity type
472#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
473#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
474pub enum ActivityType {
475    Fill,
476    TransactionFee,
477    Misc,
478    AcatsIn,
479    AcatsOut,
480    Csd,
481    Csr,
482    Div,
483    Divcgl,
484    Divcgs,
485    Divfee,
486    Divft,
487    Divnra,
488    Divroc,
489    Divtw,
490    Divtxex,
491    Int,
492    Jnlc,
493    Jnls,
494    Ma,
495    Nc,
496    Opasn,
497    Opexp,
498    Opxrc,
499    Pta,
500    Ptc,
501    Reorg,
502    Sc,
503    Sso,
504    Tc,
505}
506
507/// News article
508#[derive(Debug, Serialize, Deserialize, Clone)]
509pub struct NewsArticle {
510    pub id: i64,
511    pub headline: String,
512    pub author: String,
513    pub created_at: DateTime<Utc>,
514    pub updated_at: DateTime<Utc>,
515    pub summary: String,
516    pub content: String,
517    pub url: String,
518    pub symbols: Vec<String>,
519}
520
521/// Crypto wallet
522#[derive(Debug, Serialize, Deserialize, Clone)]
523pub struct CryptoWallet {
524    pub id: Uuid,
525    pub name: String,
526    pub currency: String,
527    pub balance: String,
528    pub available_balance: String,
529    pub created_at: DateTime<Utc>,
530    pub updated_at: DateTime<Utc>,
531}
532
533/// Position intent for options orders.
534///
535/// Specifies the intent of an options order in relation to opening or closing positions.
536#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
537#[serde(rename_all = "snake_case")]
538pub enum PositionIntent {
539    /// Buy to open a new long position.
540    BuyToOpen,
541    /// Buy to close an existing short position.
542    BuyToClose,
543    /// Sell to open a new short position.
544    SellToOpen,
545    /// Sell to close an existing long position.
546    SellToClose,
547}
548
549/// Take profit configuration for bracket orders.
550#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
551pub struct TakeProfit {
552    /// The limit price for the take profit leg.
553    pub limit_price: String,
554}
555
556impl TakeProfit {
557    /// Creates a new take profit configuration.
558    #[must_use]
559    pub fn new(limit_price: impl Into<String>) -> Self {
560        Self {
561            limit_price: limit_price.into(),
562        }
563    }
564}
565
566/// Stop loss configuration for bracket orders.
567#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
568pub struct StopLoss {
569    /// The stop price that triggers the stop loss.
570    pub stop_price: String,
571    /// Optional limit price for a stop-limit order.
572    pub limit_price: Option<String>,
573}
574
575impl StopLoss {
576    /// Creates a new stop loss configuration with only a stop price (market order when triggered).
577    #[must_use]
578    pub fn new(stop_price: impl Into<String>) -> Self {
579        Self {
580            stop_price: stop_price.into(),
581            limit_price: None,
582        }
583    }
584
585    /// Creates a new stop loss configuration with both stop and limit prices (stop-limit order).
586    #[must_use]
587    pub fn with_limit(stop_price: impl Into<String>, limit_price: impl Into<String>) -> Self {
588        Self {
589            stop_price: stop_price.into(),
590            limit_price: Some(limit_price.into()),
591        }
592    }
593}
594
595/// Sort direction for order queries.
596#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
597#[serde(rename_all = "lowercase")]
598pub enum SortDirection {
599    /// Ascending order (oldest first).
600    Asc,
601    /// Descending order (newest first).
602    Desc,
603}
604
605/// Order query status filter.
606#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
607#[serde(rename_all = "lowercase")]
608pub enum OrderQueryStatus {
609    /// Only open orders.
610    Open,
611    /// Only closed orders.
612    Closed,
613    /// All orders.
614    All,
615}
616
617// ============================================================================
618// Options Trading Types
619// ============================================================================
620
621/// Option type (call or put).
622#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
623#[serde(rename_all = "lowercase")]
624pub enum OptionType {
625    /// Call option - right to buy.
626    Call,
627    /// Put option - right to sell.
628    Put,
629}
630
631/// Option style (American or European).
632#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
633#[serde(rename_all = "lowercase")]
634pub enum OptionStyle {
635    /// American style - can be exercised any time before expiration.
636    American,
637    /// European style - can only be exercised at expiration.
638    European,
639}
640
641/// Options trading approval level.
642#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
643#[serde(rename_all = "snake_case")]
644pub enum OptionsApprovalLevel {
645    /// Level 1: Covered calls and cash-secured puts.
646    #[serde(rename = "1")]
647    Level1,
648    /// Level 2: Long calls and puts, spreads.
649    #[serde(rename = "2")]
650    Level2,
651    /// Level 3: Naked calls and puts.
652    #[serde(rename = "3")]
653    Level3,
654    /// Options trading disabled.
655    Disabled,
656}
657
658/// Options approval status.
659#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
660#[serde(rename_all = "snake_case")]
661pub enum OptionsApprovalStatus {
662    /// Approval is pending.
663    Pending,
664    /// Options trading is approved.
665    Approved,
666    /// Options trading request was rejected.
667    Rejected,
668    /// Options trading is inactive.
669    Inactive,
670}
671
672/// Option contract information.
673#[derive(Debug, Serialize, Deserialize, Clone)]
674pub struct OptionContract {
675    /// Unique contract identifier.
676    pub id: Uuid,
677    /// OCC symbol for the contract.
678    pub symbol: String,
679    /// Human-readable contract name.
680    pub name: String,
681    /// Contract status.
682    pub status: AssetStatus,
683    /// Whether the contract is tradable.
684    pub tradable: bool,
685    /// Expiration date (YYYY-MM-DD).
686    pub expiration_date: String,
687    /// Strike price in dollars.
688    pub strike_price: String,
689    /// Option type (call or put).
690    #[serde(rename = "type")]
691    pub option_type: OptionType,
692    /// Option style (American or European).
693    pub style: OptionStyle,
694    /// Underlying asset symbol.
695    pub underlying_symbol: String,
696    /// Underlying asset ID.
697    pub underlying_asset_id: Uuid,
698    /// Root symbol for the option chain.
699    pub root_symbol: String,
700    /// Open interest (number of open contracts).
701    #[serde(default)]
702    pub open_interest: Option<String>,
703    /// Date when open interest was last updated.
704    #[serde(default)]
705    pub open_interest_date: Option<String>,
706    /// Contract size (typically 100 shares).
707    #[serde(default)]
708    pub size: Option<String>,
709    /// Close price from previous trading day.
710    #[serde(default)]
711    pub close_price: Option<String>,
712    /// Date of close price.
713    #[serde(default)]
714    pub close_price_date: Option<String>,
715}
716
717/// Option Greeks for pricing and risk analysis.
718#[derive(Debug, Serialize, Deserialize, Clone)]
719pub struct OptionGreeks {
720    /// Delta - rate of change of option price with respect to underlying price.
721    pub delta: Option<f64>,
722    /// Gamma - rate of change of delta with respect to underlying price.
723    pub gamma: Option<f64>,
724    /// Theta - rate of change of option price with respect to time (time decay).
725    pub theta: Option<f64>,
726    /// Vega - rate of change of option price with respect to volatility.
727    pub vega: Option<f64>,
728    /// Rho - rate of change of option price with respect to interest rate.
729    pub rho: Option<f64>,
730}
731
732/// Option quote data.
733#[derive(Debug, Serialize, Deserialize, Clone)]
734pub struct OptionQuote {
735    /// Quote timestamp.
736    #[serde(rename = "t")]
737    pub timestamp: DateTime<Utc>,
738    /// Bid price.
739    #[serde(rename = "bp")]
740    pub bid_price: f64,
741    /// Bid size.
742    #[serde(rename = "bs")]
743    pub bid_size: u64,
744    /// Ask price.
745    #[serde(rename = "ap")]
746    pub ask_price: f64,
747    /// Ask size.
748    #[serde(rename = "as")]
749    pub ask_size: u64,
750    /// Bid exchange.
751    #[serde(rename = "bx")]
752    pub bid_exchange: String,
753    /// Ask exchange.
754    #[serde(rename = "ax")]
755    pub ask_exchange: String,
756    /// Condition flags.
757    #[serde(rename = "c", default)]
758    pub conditions: Option<String>,
759}
760
761/// Option trade data.
762#[derive(Debug, Serialize, Deserialize, Clone)]
763pub struct OptionTrade {
764    /// Trade timestamp.
765    #[serde(rename = "t")]
766    pub timestamp: DateTime<Utc>,
767    /// Trade price.
768    #[serde(rename = "p")]
769    pub price: f64,
770    /// Trade size (number of contracts).
771    #[serde(rename = "s")]
772    pub size: u64,
773    /// Exchange where trade occurred.
774    #[serde(rename = "x")]
775    pub exchange: String,
776    /// Trade conditions.
777    #[serde(rename = "c", default)]
778    pub conditions: Option<String>,
779}
780
781/// Option bar (OHLCV) data.
782#[derive(Debug, Serialize, Deserialize, Clone)]
783pub struct OptionBar {
784    /// Bar timestamp.
785    #[serde(rename = "t")]
786    pub timestamp: DateTime<Utc>,
787    /// Open price.
788    #[serde(rename = "o")]
789    pub open: f64,
790    /// High price.
791    #[serde(rename = "h")]
792    pub high: f64,
793    /// Low price.
794    #[serde(rename = "l")]
795    pub low: f64,
796    /// Close price.
797    #[serde(rename = "c")]
798    pub close: f64,
799    /// Volume (number of contracts traded).
800    #[serde(rename = "v")]
801    pub volume: u64,
802    /// Number of trades.
803    #[serde(rename = "n", default)]
804    pub trade_count: Option<u64>,
805    /// Volume-weighted average price.
806    #[serde(rename = "vw", default)]
807    pub vwap: Option<f64>,
808}
809
810/// Option snapshot with latest quote, trade, and greeks.
811#[derive(Debug, Serialize, Deserialize, Clone)]
812pub struct OptionSnapshot {
813    /// Latest quote.
814    #[serde(rename = "latestQuote")]
815    pub latest_quote: Option<OptionQuote>,
816    /// Latest trade.
817    #[serde(rename = "latestTrade")]
818    pub latest_trade: Option<OptionTrade>,
819    /// Option Greeks.
820    pub greeks: Option<OptionGreeks>,
821    /// Implied volatility.
822    #[serde(rename = "impliedVolatility")]
823    pub implied_volatility: Option<f64>,
824}
825
826/// Options chain entry for a specific strike/expiration.
827#[derive(Debug, Serialize, Deserialize, Clone)]
828pub struct OptionChainEntry {
829    /// Option contract.
830    pub contract: OptionContract,
831    /// Snapshot data.
832    pub snapshot: Option<OptionSnapshot>,
833}
834
835/// Request to exercise an option.
836#[derive(Debug, Serialize, Deserialize, Clone)]
837pub struct OptionExerciseRequest {
838    /// Symbol of the option contract to exercise.
839    pub symbol: String,
840    /// Number of contracts to exercise.
841    #[serde(default)]
842    pub qty: Option<String>,
843}
844
845/// Options approval request for an account.
846#[derive(Debug, Serialize, Deserialize, Clone)]
847pub struct OptionsApprovalRequest {
848    /// Requested options trading level.
849    pub options_trading_level: OptionsApprovalLevel,
850}
851
852/// Options approval status response.
853#[derive(Debug, Serialize, Deserialize, Clone)]
854pub struct OptionsApproval {
855    /// Current options trading level.
856    pub options_trading_level: Option<OptionsApprovalLevel>,
857    /// Approval status.
858    pub status: OptionsApprovalStatus,
859    /// Reason for rejection (if applicable).
860    #[serde(default)]
861    pub reason: Option<String>,
862}
863
864/// Parameters for querying option contracts.
865#[derive(Debug, Serialize, Deserialize, Clone, Default)]
866pub struct OptionContractParams {
867    /// Filter by underlying symbol.
868    #[serde(skip_serializing_if = "Option::is_none")]
869    pub underlying_symbol: Option<String>,
870    /// Filter by expiration date (YYYY-MM-DD).
871    #[serde(skip_serializing_if = "Option::is_none")]
872    pub expiration_date: Option<String>,
873    /// Filter by expiration date greater than or equal to.
874    #[serde(skip_serializing_if = "Option::is_none")]
875    pub expiration_date_gte: Option<String>,
876    /// Filter by expiration date less than or equal to.
877    #[serde(skip_serializing_if = "Option::is_none")]
878    pub expiration_date_lte: Option<String>,
879    /// Filter by strike price greater than or equal to.
880    #[serde(skip_serializing_if = "Option::is_none")]
881    pub strike_price_gte: Option<String>,
882    /// Filter by strike price less than or equal to.
883    #[serde(skip_serializing_if = "Option::is_none")]
884    pub strike_price_lte: Option<String>,
885    /// Filter by option type (call or put).
886    #[serde(skip_serializing_if = "Option::is_none")]
887    #[serde(rename = "type")]
888    pub option_type: Option<OptionType>,
889    /// Filter by root symbol.
890    #[serde(skip_serializing_if = "Option::is_none")]
891    pub root_symbol: Option<String>,
892    /// Filter by style (american or european).
893    #[serde(skip_serializing_if = "Option::is_none")]
894    pub style: Option<OptionStyle>,
895    /// Maximum number of results.
896    #[serde(skip_serializing_if = "Option::is_none")]
897    pub limit: Option<u32>,
898    /// Pagination token.
899    #[serde(skip_serializing_if = "Option::is_none")]
900    pub page_token: Option<String>,
901}
902
903impl OptionContractParams {
904    /// Create new empty parameters.
905    #[must_use]
906    pub fn new() -> Self {
907        Self::default()
908    }
909
910    /// Filter by underlying symbol.
911    #[must_use]
912    pub fn underlying_symbol(mut self, symbol: &str) -> Self {
913        self.underlying_symbol = Some(symbol.to_string());
914        self
915    }
916
917    /// Filter by expiration date.
918    #[must_use]
919    pub fn expiration_date(mut self, date: &str) -> Self {
920        self.expiration_date = Some(date.to_string());
921        self
922    }
923
924    /// Filter by option type.
925    #[must_use]
926    pub fn option_type(mut self, option_type: OptionType) -> Self {
927        self.option_type = Some(option_type);
928        self
929    }
930
931    /// Filter by strike price range.
932    #[must_use]
933    pub fn strike_price_range(mut self, min: &str, max: &str) -> Self {
934        self.strike_price_gte = Some(min.to_string());
935        self.strike_price_lte = Some(max.to_string());
936        self
937    }
938
939    /// Set maximum number of results.
940    #[must_use]
941    pub fn limit(mut self, limit: u32) -> Self {
942        self.limit = Some(limit);
943        self
944    }
945}
946
947/// Parameters for querying option bars.
948#[derive(Debug, Serialize, Deserialize, Clone, Default)]
949pub struct OptionBarsParams {
950    /// Option symbols to query.
951    #[serde(skip_serializing_if = "Option::is_none")]
952    pub symbols: Option<String>,
953    /// Timeframe for bars (e.g., "1Min", "1Hour", "1Day").
954    #[serde(skip_serializing_if = "Option::is_none")]
955    pub timeframe: Option<String>,
956    /// Start time (RFC3339 format).
957    #[serde(skip_serializing_if = "Option::is_none")]
958    pub start: Option<String>,
959    /// End time (RFC3339 format).
960    #[serde(skip_serializing_if = "Option::is_none")]
961    pub end: Option<String>,
962    /// Maximum number of results.
963    #[serde(skip_serializing_if = "Option::is_none")]
964    pub limit: Option<u32>,
965    /// Pagination token.
966    #[serde(skip_serializing_if = "Option::is_none")]
967    pub page_token: Option<String>,
968}
969
970impl OptionBarsParams {
971    /// Create new parameters with symbols.
972    #[must_use]
973    pub fn new(symbols: &str) -> Self {
974        Self {
975            symbols: Some(symbols.to_string()),
976            ..Default::default()
977        }
978    }
979
980    /// Set timeframe.
981    #[must_use]
982    pub fn timeframe(mut self, timeframe: &str) -> Self {
983        self.timeframe = Some(timeframe.to_string());
984        self
985    }
986
987    /// Set time range.
988    #[must_use]
989    pub fn time_range(mut self, start: &str, end: &str) -> Self {
990        self.start = Some(start.to_string());
991        self.end = Some(end.to_string());
992        self
993    }
994
995    /// Set maximum number of results.
996    #[must_use]
997    pub fn limit(mut self, limit: u32) -> Self {
998        self.limit = Some(limit);
999        self
1000    }
1001}
1002
1003// ============================================================================
1004// Enhanced Stock Market Data Types
1005// ============================================================================
1006
1007/// Data feed source.
1008#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
1009#[serde(rename_all = "lowercase")]
1010pub enum DataFeed {
1011    /// IEX exchange data.
1012    Iex,
1013    /// SIP (Securities Information Processor) data.
1014    Sip,
1015    /// OTC (Over-The-Counter) data.
1016    Otc,
1017}
1018
1019/// Stock snapshot with latest market data.
1020#[derive(Debug, Serialize, Deserialize, Clone)]
1021pub struct StockSnapshot {
1022    /// Latest trade.
1023    #[serde(rename = "latestTrade")]
1024    pub latest_trade: Option<Trade>,
1025    /// Latest quote.
1026    #[serde(rename = "latestQuote")]
1027    pub latest_quote: Option<Quote>,
1028    /// Current minute bar.
1029    #[serde(rename = "minuteBar")]
1030    pub minute_bar: Option<Bar>,
1031    /// Current daily bar.
1032    #[serde(rename = "dailyBar")]
1033    pub daily_bar: Option<Bar>,
1034    /// Previous daily bar.
1035    #[serde(rename = "prevDailyBar")]
1036    pub prev_daily_bar: Option<Bar>,
1037}
1038
1039/// Corporate action type.
1040#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
1041#[serde(rename_all = "snake_case")]
1042pub enum CorporateActionType {
1043    /// Cash dividend.
1044    Dividend,
1045    /// Stock split.
1046    Split,
1047    /// Reverse stock split.
1048    ReverseSplit,
1049    /// Spinoff.
1050    Spinoff,
1051    /// Merger.
1052    Merger,
1053    /// Rights issue.
1054    Rights,
1055    /// Stock distribution.
1056    StockDividend,
1057    /// Redemption.
1058    Redemption,
1059    /// Name change.
1060    NameChange,
1061    /// Symbol change.
1062    SymbolChange,
1063    /// Worthless security.
1064    Worthless,
1065}
1066
1067/// Corporate action announcement.
1068#[derive(Debug, Serialize, Deserialize, Clone)]
1069pub struct CorporateAction {
1070    /// Unique identifier.
1071    pub id: String,
1072    /// Corporate action type.
1073    #[serde(rename = "ca_type")]
1074    pub action_type: CorporateActionType,
1075    /// Sub-type of the action.
1076    #[serde(rename = "ca_sub_type")]
1077    pub sub_type: Option<String>,
1078    /// Initiating symbol.
1079    pub initiating_symbol: Option<String>,
1080    /// Initiating original CUSIP.
1081    pub initiating_original_cusip: Option<String>,
1082    /// Target symbol.
1083    pub target_symbol: Option<String>,
1084    /// Target original CUSIP.
1085    pub target_original_cusip: Option<String>,
1086    /// Declaration date.
1087    pub declaration_date: Option<String>,
1088    /// Ex-date.
1089    pub ex_date: Option<String>,
1090    /// Record date.
1091    pub record_date: Option<String>,
1092    /// Payable date.
1093    pub payable_date: Option<String>,
1094    /// Cash amount per share.
1095    pub cash: Option<String>,
1096    /// Old rate (for splits).
1097    pub old_rate: Option<String>,
1098    /// New rate (for splits).
1099    pub new_rate: Option<String>,
1100}
1101
1102/// Limit Up Limit Down (LULD) data.
1103#[derive(Debug, Serialize, Deserialize, Clone)]
1104pub struct Luld {
1105    /// LULD indicator.
1106    #[serde(rename = "i")]
1107    pub indicator: String,
1108    /// Limit up price.
1109    #[serde(rename = "u")]
1110    pub limit_up_price: f64,
1111    /// Limit down price.
1112    #[serde(rename = "d")]
1113    pub limit_down_price: f64,
1114    /// Timestamp.
1115    #[serde(rename = "t")]
1116    pub timestamp: DateTime<Utc>,
1117}
1118
1119/// Trading status update.
1120#[derive(Debug, Serialize, Deserialize, Clone)]
1121pub struct TradingStatus {
1122    /// Status code.
1123    #[serde(rename = "sc")]
1124    pub status_code: String,
1125    /// Status message.
1126    #[serde(rename = "sm")]
1127    pub status_message: String,
1128    /// Reason code.
1129    #[serde(rename = "rc")]
1130    pub reason_code: String,
1131    /// Reason message.
1132    #[serde(rename = "rm")]
1133    pub reason_message: String,
1134    /// Timestamp.
1135    #[serde(rename = "t")]
1136    pub timestamp: DateTime<Utc>,
1137}
1138
1139/// Auction data.
1140#[derive(Debug, Serialize, Deserialize, Clone)]
1141pub struct Auction {
1142    /// Auction type (open, close).
1143    #[serde(rename = "at")]
1144    pub auction_type: String,
1145    /// Auction price.
1146    #[serde(rename = "ap")]
1147    pub price: Option<f64>,
1148    /// Auction size.
1149    #[serde(rename = "as")]
1150    pub size: Option<u64>,
1151    /// Timestamp.
1152    #[serde(rename = "t")]
1153    pub timestamp: DateTime<Utc>,
1154}
1155
1156/// Parameters for multi-symbol bars request.
1157#[derive(Debug, Serialize, Deserialize, Clone, Default)]
1158pub struct MultiBarsParams {
1159    /// Comma-separated list of symbols.
1160    #[serde(skip_serializing_if = "Option::is_none")]
1161    pub symbols: Option<String>,
1162    /// Timeframe (e.g., "1Min", "1Hour", "1Day").
1163    #[serde(skip_serializing_if = "Option::is_none")]
1164    pub timeframe: Option<String>,
1165    /// Start time (RFC3339).
1166    #[serde(skip_serializing_if = "Option::is_none")]
1167    pub start: Option<String>,
1168    /// End time (RFC3339).
1169    #[serde(skip_serializing_if = "Option::is_none")]
1170    pub end: Option<String>,
1171    /// Maximum number of bars per symbol.
1172    #[serde(skip_serializing_if = "Option::is_none")]
1173    pub limit: Option<u32>,
1174    /// Data feed source.
1175    #[serde(skip_serializing_if = "Option::is_none")]
1176    pub feed: Option<DataFeed>,
1177    /// Pagination token.
1178    #[serde(skip_serializing_if = "Option::is_none")]
1179    pub page_token: Option<String>,
1180}
1181
1182impl MultiBarsParams {
1183    /// Create new parameters with symbols.
1184    #[must_use]
1185    pub fn new(symbols: &str) -> Self {
1186        Self {
1187            symbols: Some(symbols.to_string()),
1188            ..Default::default()
1189        }
1190    }
1191
1192    /// Set timeframe.
1193    #[must_use]
1194    pub fn timeframe(mut self, timeframe: &str) -> Self {
1195        self.timeframe = Some(timeframe.to_string());
1196        self
1197    }
1198
1199    /// Set time range.
1200    #[must_use]
1201    pub fn time_range(mut self, start: &str, end: &str) -> Self {
1202        self.start = Some(start.to_string());
1203        self.end = Some(end.to_string());
1204        self
1205    }
1206
1207    /// Set data feed.
1208    #[must_use]
1209    pub fn feed(mut self, feed: DataFeed) -> Self {
1210        self.feed = Some(feed);
1211        self
1212    }
1213
1214    /// Set limit.
1215    #[must_use]
1216    pub fn limit(mut self, limit: u32) -> Self {
1217        self.limit = Some(limit);
1218        self
1219    }
1220}
1221
1222/// Parameters for multi-symbol quotes request.
1223#[derive(Debug, Serialize, Deserialize, Clone, Default)]
1224pub struct MultiQuotesParams {
1225    /// Comma-separated list of symbols.
1226    #[serde(skip_serializing_if = "Option::is_none")]
1227    pub symbols: Option<String>,
1228    /// Start time (RFC3339).
1229    #[serde(skip_serializing_if = "Option::is_none")]
1230    pub start: Option<String>,
1231    /// End time (RFC3339).
1232    #[serde(skip_serializing_if = "Option::is_none")]
1233    pub end: Option<String>,
1234    /// Maximum number of quotes per symbol.
1235    #[serde(skip_serializing_if = "Option::is_none")]
1236    pub limit: Option<u32>,
1237    /// Data feed source.
1238    #[serde(skip_serializing_if = "Option::is_none")]
1239    pub feed: Option<DataFeed>,
1240    /// Pagination token.
1241    #[serde(skip_serializing_if = "Option::is_none")]
1242    pub page_token: Option<String>,
1243}
1244
1245impl MultiQuotesParams {
1246    /// Create new parameters with symbols.
1247    #[must_use]
1248    pub fn new(symbols: &str) -> Self {
1249        Self {
1250            symbols: Some(symbols.to_string()),
1251            ..Default::default()
1252        }
1253    }
1254
1255    /// Set time range.
1256    #[must_use]
1257    pub fn time_range(mut self, start: &str, end: &str) -> Self {
1258        self.start = Some(start.to_string());
1259        self.end = Some(end.to_string());
1260        self
1261    }
1262
1263    /// Set data feed.
1264    #[must_use]
1265    pub fn feed(mut self, feed: DataFeed) -> Self {
1266        self.feed = Some(feed);
1267        self
1268    }
1269
1270    /// Set limit.
1271    #[must_use]
1272    pub fn limit(mut self, limit: u32) -> Self {
1273        self.limit = Some(limit);
1274        self
1275    }
1276}
1277
1278/// Parameters for multi-symbol trades request.
1279#[derive(Debug, Serialize, Deserialize, Clone, Default)]
1280pub struct MultiTradesParams {
1281    /// Comma-separated list of symbols.
1282    #[serde(skip_serializing_if = "Option::is_none")]
1283    pub symbols: Option<String>,
1284    /// Start time (RFC3339).
1285    #[serde(skip_serializing_if = "Option::is_none")]
1286    pub start: Option<String>,
1287    /// End time (RFC3339).
1288    #[serde(skip_serializing_if = "Option::is_none")]
1289    pub end: Option<String>,
1290    /// Maximum number of trades per symbol.
1291    #[serde(skip_serializing_if = "Option::is_none")]
1292    pub limit: Option<u32>,
1293    /// Data feed source.
1294    #[serde(skip_serializing_if = "Option::is_none")]
1295    pub feed: Option<DataFeed>,
1296    /// Pagination token.
1297    #[serde(skip_serializing_if = "Option::is_none")]
1298    pub page_token: Option<String>,
1299}
1300
1301impl MultiTradesParams {
1302    /// Create new parameters with symbols.
1303    #[must_use]
1304    pub fn new(symbols: &str) -> Self {
1305        Self {
1306            symbols: Some(symbols.to_string()),
1307            ..Default::default()
1308        }
1309    }
1310
1311    /// Set time range.
1312    #[must_use]
1313    pub fn time_range(mut self, start: &str, end: &str) -> Self {
1314        self.start = Some(start.to_string());
1315        self.end = Some(end.to_string());
1316        self
1317    }
1318
1319    /// Set data feed.
1320    #[must_use]
1321    pub fn feed(mut self, feed: DataFeed) -> Self {
1322        self.feed = Some(feed);
1323        self
1324    }
1325
1326    /// Set limit.
1327    #[must_use]
1328    pub fn limit(mut self, limit: u32) -> Self {
1329        self.limit = Some(limit);
1330        self
1331    }
1332}
1333
1334/// Parameters for corporate actions request.
1335#[derive(Debug, Serialize, Deserialize, Clone, Default)]
1336pub struct CorporateActionsParams {
1337    /// Filter by symbols.
1338    #[serde(skip_serializing_if = "Option::is_none")]
1339    pub symbols: Option<String>,
1340    /// Filter by action types.
1341    #[serde(skip_serializing_if = "Option::is_none")]
1342    pub types: Option<String>,
1343    /// Start date (YYYY-MM-DD).
1344    #[serde(skip_serializing_if = "Option::is_none")]
1345    pub start: Option<String>,
1346    /// End date (YYYY-MM-DD).
1347    #[serde(skip_serializing_if = "Option::is_none")]
1348    pub end: Option<String>,
1349    /// Maximum number of results.
1350    #[serde(skip_serializing_if = "Option::is_none")]
1351    pub limit: Option<u32>,
1352    /// Pagination token.
1353    #[serde(skip_serializing_if = "Option::is_none")]
1354    pub page_token: Option<String>,
1355}
1356
1357impl CorporateActionsParams {
1358    /// Create new empty parameters.
1359    #[must_use]
1360    pub fn new() -> Self {
1361        Self::default()
1362    }
1363
1364    /// Filter by symbols.
1365    #[must_use]
1366    pub fn symbols(mut self, symbols: &str) -> Self {
1367        self.symbols = Some(symbols.to_string());
1368        self
1369    }
1370
1371    /// Filter by action types.
1372    #[must_use]
1373    pub fn types(mut self, types: &str) -> Self {
1374        self.types = Some(types.to_string());
1375        self
1376    }
1377
1378    /// Set date range.
1379    #[must_use]
1380    pub fn date_range(mut self, start: &str, end: &str) -> Self {
1381        self.start = Some(start.to_string());
1382        self.end = Some(end.to_string());
1383        self
1384    }
1385
1386    /// Set limit.
1387    #[must_use]
1388    pub fn limit(mut self, limit: u32) -> Self {
1389        self.limit = Some(limit);
1390        self
1391    }
1392}
1393
1394// ============================================================================
1395// Broker API Types - Account Management
1396// ============================================================================
1397
1398/// Broker account status.
1399#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
1400#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
1401pub enum BrokerAccountStatus {
1402    /// Account is being onboarded.
1403    Onboarding,
1404    /// Submission failed.
1405    SubmissionFailed,
1406    /// Submitted for review.
1407    Submitted,
1408    /// Account action required.
1409    ActionRequired,
1410    /// Account is active.
1411    Active,
1412    /// Account is rejected.
1413    Rejected,
1414    /// Account is approved.
1415    Approved,
1416    /// Account is disabled.
1417    Disabled,
1418    /// Account is closed.
1419    AccountClosed,
1420}
1421
1422/// Agreement type for broker accounts.
1423#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
1424#[serde(rename_all = "snake_case")]
1425pub enum AgreementType {
1426    /// Margin agreement.
1427    MarginAgreement,
1428    /// Account agreement.
1429    AccountAgreement,
1430    /// Customer agreement.
1431    CustomerAgreement,
1432    /// Crypto agreement.
1433    CryptoAgreement,
1434    /// Options agreement.
1435    OptionsAgreement,
1436}
1437
1438/// Funding source for broker accounts.
1439#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
1440#[serde(rename_all = "snake_case")]
1441pub enum FundingSource {
1442    /// Employment income.
1443    EmploymentIncome,
1444    /// Investments.
1445    Investments,
1446    /// Inheritance.
1447    Inheritance,
1448    /// Business income.
1449    BusinessIncome,
1450    /// Savings.
1451    Savings,
1452    /// Family.
1453    Family,
1454}
1455
1456/// Tax ID type.
1457#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
1458#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
1459pub enum TaxIdType {
1460    /// USA Social Security Number.
1461    UsaSsn,
1462    /// Argentina CUIT.
1463    ArgArCuit,
1464    /// Australia Tax File Number.
1465    AusTfn,
1466    /// Australia Business Number.
1467    AusAbn,
1468    /// Brazil CPF.
1469    BraCpf,
1470    /// Canada SIN.
1471    CanSin,
1472    /// Chile RUT.
1473    ChlRut,
1474    /// Colombia NIT.
1475    ColNit,
1476    /// Germany Tax ID.
1477    DeuTaxId,
1478    /// Spain NIE.
1479    EspNie,
1480    /// France SPI.
1481    FraSpi,
1482    /// UK National Insurance Number.
1483    GbrNino,
1484    /// UK Unique Taxpayer Reference.
1485    GbrUtr,
1486    /// Hong Kong HKID.
1487    HkgHkid,
1488    /// Hungary Tax Number.
1489    HunTin,
1490    /// India PAN.
1491    IndPan,
1492    /// Israel ID.
1493    IsrId,
1494    /// Italy Fiscal Code.
1495    ItaCf,
1496    /// Japan My Number.
1497    JpnMyNumber,
1498    /// South Korea RRN.
1499    KorRrn,
1500    /// Mexico RFC.
1501    MexRfc,
1502    /// Netherlands BSN.
1503    NldBsn,
1504    /// New Zealand IRD.
1505    NzlIrd,
1506    /// Poland PESEL.
1507    PolPesel,
1508    /// Sweden Personal Number.
1509    SwePn,
1510    /// Singapore NRIC.
1511    SgpNric,
1512    /// Taiwan ID.
1513    TwnId,
1514    /// Not applicable.
1515    NotApplicable,
1516}
1517
1518/// Contact information for broker account.
1519#[derive(Debug, Serialize, Deserialize, Clone, Default)]
1520pub struct Contact {
1521    /// Email address.
1522    pub email_address: String,
1523    /// Phone number.
1524    #[serde(skip_serializing_if = "Option::is_none")]
1525    pub phone_number: Option<String>,
1526    /// Street address lines.
1527    pub street_address: Vec<String>,
1528    /// City.
1529    pub city: String,
1530    /// State or province.
1531    #[serde(skip_serializing_if = "Option::is_none")]
1532    pub state: Option<String>,
1533    /// Postal code.
1534    pub postal_code: String,
1535    /// Country (ISO 3166-1 alpha-3).
1536    pub country: String,
1537}
1538
1539impl Contact {
1540    /// Create new contact information.
1541    #[must_use]
1542    pub fn new(email: &str, city: &str, postal_code: &str, country: &str) -> Self {
1543        Self {
1544            email_address: email.to_string(),
1545            phone_number: None,
1546            street_address: Vec::new(),
1547            city: city.to_string(),
1548            state: None,
1549            postal_code: postal_code.to_string(),
1550            country: country.to_string(),
1551        }
1552    }
1553
1554    /// Set phone number.
1555    #[must_use]
1556    pub fn phone(mut self, phone: &str) -> Self {
1557        self.phone_number = Some(phone.to_string());
1558        self
1559    }
1560
1561    /// Add street address line.
1562    #[must_use]
1563    pub fn street(mut self, street: &str) -> Self {
1564        self.street_address.push(street.to_string());
1565        self
1566    }
1567
1568    /// Set state.
1569    #[must_use]
1570    pub fn state(mut self, state: &str) -> Self {
1571        self.state = Some(state.to_string());
1572        self
1573    }
1574}
1575
1576/// Identity information for broker account.
1577#[derive(Debug, Serialize, Deserialize, Clone, Default)]
1578pub struct Identity {
1579    /// Given (first) name.
1580    pub given_name: String,
1581    /// Family (last) name.
1582    pub family_name: String,
1583    /// Middle name.
1584    #[serde(skip_serializing_if = "Option::is_none")]
1585    pub middle_name: Option<String>,
1586    /// Date of birth (YYYY-MM-DD).
1587    pub date_of_birth: String,
1588    /// Tax ID.
1589    #[serde(skip_serializing_if = "Option::is_none")]
1590    pub tax_id: Option<String>,
1591    /// Tax ID type.
1592    #[serde(skip_serializing_if = "Option::is_none")]
1593    pub tax_id_type: Option<TaxIdType>,
1594    /// Country of citizenship (ISO 3166-1 alpha-3).
1595    #[serde(skip_serializing_if = "Option::is_none")]
1596    pub country_of_citizenship: Option<String>,
1597    /// Country of birth (ISO 3166-1 alpha-3).
1598    #[serde(skip_serializing_if = "Option::is_none")]
1599    pub country_of_birth: Option<String>,
1600    /// Country of tax residence (ISO 3166-1 alpha-3).
1601    #[serde(skip_serializing_if = "Option::is_none")]
1602    pub country_of_tax_residence: Option<String>,
1603    /// Funding source.
1604    #[serde(skip_serializing_if = "Option::is_none")]
1605    pub funding_source: Option<Vec<FundingSource>>,
1606    /// Annual income minimum in USD.
1607    #[serde(skip_serializing_if = "Option::is_none")]
1608    pub annual_income_min: Option<String>,
1609    /// Annual income maximum in USD.
1610    #[serde(skip_serializing_if = "Option::is_none")]
1611    pub annual_income_max: Option<String>,
1612    /// Liquid net worth minimum in USD.
1613    #[serde(skip_serializing_if = "Option::is_none")]
1614    pub liquid_net_worth_min: Option<String>,
1615    /// Liquid net worth maximum in USD.
1616    #[serde(skip_serializing_if = "Option::is_none")]
1617    pub liquid_net_worth_max: Option<String>,
1618    /// Total net worth minimum in USD.
1619    #[serde(skip_serializing_if = "Option::is_none")]
1620    pub total_net_worth_min: Option<String>,
1621    /// Total net worth maximum in USD.
1622    #[serde(skip_serializing_if = "Option::is_none")]
1623    pub total_net_worth_max: Option<String>,
1624}
1625
1626impl Identity {
1627    /// Create new identity information.
1628    #[must_use]
1629    pub fn new(given_name: &str, family_name: &str, date_of_birth: &str) -> Self {
1630        Self {
1631            given_name: given_name.to_string(),
1632            family_name: family_name.to_string(),
1633            date_of_birth: date_of_birth.to_string(),
1634            ..Default::default()
1635        }
1636    }
1637
1638    /// Set tax ID.
1639    #[must_use]
1640    pub fn tax_id(mut self, tax_id: &str, tax_id_type: TaxIdType) -> Self {
1641        self.tax_id = Some(tax_id.to_string());
1642        self.tax_id_type = Some(tax_id_type);
1643        self
1644    }
1645
1646    /// Set country of citizenship.
1647    #[must_use]
1648    pub fn citizenship(mut self, country: &str) -> Self {
1649        self.country_of_citizenship = Some(country.to_string());
1650        self
1651    }
1652
1653    /// Set funding sources.
1654    #[must_use]
1655    pub fn funding_sources(mut self, sources: Vec<FundingSource>) -> Self {
1656        self.funding_source = Some(sources);
1657        self
1658    }
1659}
1660
1661/// Disclosures for broker account.
1662#[derive(Debug, Serialize, Deserialize, Clone, Default)]
1663pub struct Disclosures {
1664    /// Is the account holder a control person.
1665    pub is_control_person: bool,
1666    /// Is affiliated with exchange or FINRA.
1667    pub is_affiliated_exchange_or_finra: bool,
1668    /// Is politically exposed.
1669    pub is_politically_exposed: bool,
1670    /// Has immediate family exposed.
1671    pub immediate_family_exposed: bool,
1672    /// Employment status.
1673    #[serde(skip_serializing_if = "Option::is_none")]
1674    pub employment_status: Option<String>,
1675    /// Employer name.
1676    #[serde(skip_serializing_if = "Option::is_none")]
1677    pub employer_name: Option<String>,
1678    /// Employer address.
1679    #[serde(skip_serializing_if = "Option::is_none")]
1680    pub employer_address: Option<String>,
1681    /// Employment position.
1682    #[serde(skip_serializing_if = "Option::is_none")]
1683    pub employment_position: Option<String>,
1684}
1685
1686impl Disclosures {
1687    /// Create new disclosures with all false.
1688    #[must_use]
1689    pub fn new() -> Self {
1690        Self::default()
1691    }
1692
1693    /// Set control person status.
1694    #[must_use]
1695    pub fn control_person(mut self, is_control: bool) -> Self {
1696        self.is_control_person = is_control;
1697        self
1698    }
1699
1700    /// Set employment information.
1701    #[must_use]
1702    pub fn employment(mut self, status: &str, employer: &str, position: &str) -> Self {
1703        self.employment_status = Some(status.to_string());
1704        self.employer_name = Some(employer.to_string());
1705        self.employment_position = Some(position.to_string());
1706        self
1707    }
1708}
1709
1710/// Agreement for broker account.
1711#[derive(Debug, Serialize, Deserialize, Clone)]
1712pub struct Agreement {
1713    /// Agreement type.
1714    pub agreement: AgreementType,
1715    /// When the agreement was signed (RFC3339).
1716    pub signed_at: String,
1717    /// IP address from which agreement was signed.
1718    pub ip_address: String,
1719    /// Revision of the agreement.
1720    #[serde(skip_serializing_if = "Option::is_none")]
1721    pub revision: Option<String>,
1722}
1723
1724impl Agreement {
1725    /// Create new agreement.
1726    #[must_use]
1727    pub fn new(agreement_type: AgreementType, signed_at: &str, ip_address: &str) -> Self {
1728        Self {
1729            agreement: agreement_type,
1730            signed_at: signed_at.to_string(),
1731            ip_address: ip_address.to_string(),
1732            revision: None,
1733        }
1734    }
1735}
1736
1737/// Trusted contact for broker account.
1738#[derive(Debug, Serialize, Deserialize, Clone, Default)]
1739pub struct TrustedContact {
1740    /// Given name.
1741    pub given_name: String,
1742    /// Family name.
1743    pub family_name: String,
1744    /// Email address.
1745    #[serde(skip_serializing_if = "Option::is_none")]
1746    pub email_address: Option<String>,
1747    /// Phone number.
1748    #[serde(skip_serializing_if = "Option::is_none")]
1749    pub phone_number: Option<String>,
1750    /// Street address.
1751    #[serde(skip_serializing_if = "Option::is_none")]
1752    pub street_address: Option<Vec<String>>,
1753    /// City.
1754    #[serde(skip_serializing_if = "Option::is_none")]
1755    pub city: Option<String>,
1756    /// State.
1757    #[serde(skip_serializing_if = "Option::is_none")]
1758    pub state: Option<String>,
1759    /// Postal code.
1760    #[serde(skip_serializing_if = "Option::is_none")]
1761    pub postal_code: Option<String>,
1762    /// Country.
1763    #[serde(skip_serializing_if = "Option::is_none")]
1764    pub country: Option<String>,
1765}
1766
1767impl TrustedContact {
1768    /// Create new trusted contact.
1769    #[must_use]
1770    pub fn new(given_name: &str, family_name: &str) -> Self {
1771        Self {
1772            given_name: given_name.to_string(),
1773            family_name: family_name.to_string(),
1774            ..Default::default()
1775        }
1776    }
1777
1778    /// Set email.
1779    #[must_use]
1780    pub fn email(mut self, email: &str) -> Self {
1781        self.email_address = Some(email.to_string());
1782        self
1783    }
1784
1785    /// Set phone.
1786    #[must_use]
1787    pub fn phone(mut self, phone: &str) -> Self {
1788        self.phone_number = Some(phone.to_string());
1789        self
1790    }
1791}
1792
1793/// Document type for KYC.
1794#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
1795#[serde(rename_all = "snake_case")]
1796pub enum DocumentType {
1797    /// Identity verification.
1798    IdentityVerification,
1799    /// Address verification.
1800    AddressVerification,
1801    /// Date of birth verification.
1802    DateOfBirthVerification,
1803    /// Tax ID verification.
1804    TaxIdVerification,
1805    /// Account approval letter.
1806    AccountApprovalLetter,
1807    /// W8BEN form.
1808    W8ben,
1809}
1810
1811/// Document for broker account.
1812#[derive(Debug, Serialize, Deserialize, Clone)]
1813pub struct Document {
1814    /// Document type.
1815    pub document_type: DocumentType,
1816    /// Document sub-type.
1817    #[serde(skip_serializing_if = "Option::is_none")]
1818    pub document_sub_type: Option<String>,
1819    /// Content (base64 encoded).
1820    pub content: String,
1821    /// MIME type.
1822    pub mime_type: String,
1823}
1824
1825/// Broker account.
1826#[derive(Debug, Serialize, Deserialize, Clone)]
1827pub struct BrokerAccount {
1828    /// Account ID.
1829    pub id: String,
1830    /// Account number.
1831    pub account_number: String,
1832    /// Account status.
1833    pub status: BrokerAccountStatus,
1834    /// Crypto status.
1835    #[serde(skip_serializing_if = "Option::is_none")]
1836    pub crypto_status: Option<BrokerAccountStatus>,
1837    /// Currency.
1838    pub currency: String,
1839    /// Created at timestamp.
1840    pub created_at: DateTime<Utc>,
1841    /// Contact information.
1842    #[serde(skip_serializing_if = "Option::is_none")]
1843    pub contact: Option<Contact>,
1844    /// Identity information.
1845    #[serde(skip_serializing_if = "Option::is_none")]
1846    pub identity: Option<Identity>,
1847    /// Disclosures.
1848    #[serde(skip_serializing_if = "Option::is_none")]
1849    pub disclosures: Option<Disclosures>,
1850    /// Agreements.
1851    #[serde(skip_serializing_if = "Option::is_none")]
1852    pub agreements: Option<Vec<Agreement>>,
1853    /// Trusted contact.
1854    #[serde(skip_serializing_if = "Option::is_none")]
1855    pub trusted_contact: Option<TrustedContact>,
1856}
1857
1858/// Request to create a broker account.
1859#[derive(Debug, Serialize, Deserialize, Clone)]
1860pub struct CreateBrokerAccountRequest {
1861    /// Contact information.
1862    pub contact: Contact,
1863    /// Identity information.
1864    pub identity: Identity,
1865    /// Disclosures.
1866    pub disclosures: Disclosures,
1867    /// Agreements.
1868    pub agreements: Vec<Agreement>,
1869    /// Documents.
1870    #[serde(skip_serializing_if = "Option::is_none")]
1871    pub documents: Option<Vec<Document>>,
1872    /// Trusted contact.
1873    #[serde(skip_serializing_if = "Option::is_none")]
1874    pub trusted_contact: Option<TrustedContact>,
1875    /// Enabled assets (us_equity, crypto).
1876    #[serde(skip_serializing_if = "Option::is_none")]
1877    pub enabled_assets: Option<Vec<String>>,
1878}
1879
1880impl CreateBrokerAccountRequest {
1881    /// Create new broker account request.
1882    #[must_use]
1883    pub fn new(
1884        contact: Contact,
1885        identity: Identity,
1886        disclosures: Disclosures,
1887        agreements: Vec<Agreement>,
1888    ) -> Self {
1889        Self {
1890            contact,
1891            identity,
1892            disclosures,
1893            agreements,
1894            documents: None,
1895            trusted_contact: None,
1896            enabled_assets: None,
1897        }
1898    }
1899
1900    /// Add documents.
1901    #[must_use]
1902    pub fn documents(mut self, documents: Vec<Document>) -> Self {
1903        self.documents = Some(documents);
1904        self
1905    }
1906
1907    /// Set trusted contact.
1908    #[must_use]
1909    pub fn trusted_contact(mut self, contact: TrustedContact) -> Self {
1910        self.trusted_contact = Some(contact);
1911        self
1912    }
1913
1914    /// Set enabled assets.
1915    #[must_use]
1916    pub fn enabled_assets(mut self, assets: Vec<String>) -> Self {
1917        self.enabled_assets = Some(assets);
1918        self
1919    }
1920}
1921
1922/// Request to update a broker account.
1923#[derive(Debug, Serialize, Deserialize, Clone, Default)]
1924pub struct UpdateBrokerAccountRequest {
1925    /// Contact information.
1926    #[serde(skip_serializing_if = "Option::is_none")]
1927    pub contact: Option<Contact>,
1928    /// Identity information.
1929    #[serde(skip_serializing_if = "Option::is_none")]
1930    pub identity: Option<Identity>,
1931    /// Disclosures.
1932    #[serde(skip_serializing_if = "Option::is_none")]
1933    pub disclosures: Option<Disclosures>,
1934    /// Trusted contact.
1935    #[serde(skip_serializing_if = "Option::is_none")]
1936    pub trusted_contact: Option<TrustedContact>,
1937}
1938
1939/// CIP (Customer Identification Program) info.
1940#[derive(Debug, Serialize, Deserialize, Clone)]
1941pub struct CipInfo {
1942    /// Provider name.
1943    pub provider_name: Vec<String>,
1944    /// CIP ID.
1945    #[serde(skip_serializing_if = "Option::is_none")]
1946    pub id: Option<String>,
1947    /// CIP result.
1948    #[serde(skip_serializing_if = "Option::is_none")]
1949    pub result: Option<String>,
1950    /// CIP status.
1951    #[serde(skip_serializing_if = "Option::is_none")]
1952    pub status: Option<String>,
1953    /// Created at.
1954    #[serde(skip_serializing_if = "Option::is_none")]
1955    pub created_at: Option<DateTime<Utc>>,
1956    /// Updated at.
1957    #[serde(skip_serializing_if = "Option::is_none")]
1958    pub updated_at: Option<DateTime<Utc>>,
1959}
1960
1961/// Parameters for listing broker accounts.
1962#[derive(Debug, Serialize, Deserialize, Clone, Default)]
1963pub struct ListBrokerAccountsParams {
1964    /// Filter by query string.
1965    #[serde(skip_serializing_if = "Option::is_none")]
1966    pub query: Option<String>,
1967    /// Created after timestamp.
1968    #[serde(skip_serializing_if = "Option::is_none")]
1969    pub created_after: Option<String>,
1970    /// Created before timestamp.
1971    #[serde(skip_serializing_if = "Option::is_none")]
1972    pub created_before: Option<String>,
1973    /// Filter by status.
1974    #[serde(skip_serializing_if = "Option::is_none")]
1975    pub status: Option<BrokerAccountStatus>,
1976    /// Sort order (asc or desc).
1977    #[serde(skip_serializing_if = "Option::is_none")]
1978    pub sort: Option<String>,
1979    /// Entities to include.
1980    #[serde(skip_serializing_if = "Option::is_none")]
1981    pub entities: Option<String>,
1982}
1983
1984impl ListBrokerAccountsParams {
1985    /// Create new empty parameters.
1986    #[must_use]
1987    pub fn new() -> Self {
1988        Self::default()
1989    }
1990
1991    /// Filter by query.
1992    #[must_use]
1993    pub fn query(mut self, query: &str) -> Self {
1994        self.query = Some(query.to_string());
1995        self
1996    }
1997
1998    /// Filter by status.
1999    #[must_use]
2000    pub fn status(mut self, status: BrokerAccountStatus) -> Self {
2001        self.status = Some(status);
2002        self
2003    }
2004
2005    /// Set sort order.
2006    #[must_use]
2007    pub fn sort_desc(mut self) -> Self {
2008        self.sort = Some("desc".to_string());
2009        self
2010    }
2011}
2012
2013// ============================================================================
2014// Broker API Types - Funding & Transfers
2015// ============================================================================
2016
2017/// ACH relationship status.
2018#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
2019#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
2020pub enum AchRelationshipStatus {
2021    /// Queued for processing.
2022    Queued,
2023    /// Approved.
2024    Approved,
2025    /// Pending verification.
2026    Pending,
2027    /// Cancel requested.
2028    CancelRequested,
2029    /// Canceled.
2030    Canceled,
2031}
2032
2033/// Bank account type.
2034#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
2035#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
2036pub enum BankAccountType {
2037    /// Checking account.
2038    Checking,
2039    /// Savings account.
2040    Savings,
2041}
2042
2043/// Transfer type.
2044#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
2045#[serde(rename_all = "lowercase")]
2046pub enum TransferType {
2047    /// ACH transfer.
2048    Ach,
2049    /// Wire transfer.
2050    Wire,
2051}
2052
2053/// Transfer direction.
2054#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
2055#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
2056pub enum TransferDirection {
2057    /// Incoming transfer (deposit).
2058    Incoming,
2059    /// Outgoing transfer (withdrawal).
2060    Outgoing,
2061}
2062
2063/// Transfer status.
2064#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
2065#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
2066pub enum TransferStatus {
2067    /// Queued for processing.
2068    Queued,
2069    /// Pending.
2070    Pending,
2071    /// Sent to clearing.
2072    SentToClearing,
2073    /// Approved.
2074    Approved,
2075    /// Complete.
2076    Complete,
2077    /// Returned.
2078    Returned,
2079    /// Canceled.
2080    Canceled,
2081}
2082
2083/// Journal entry type.
2084#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
2085#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
2086pub enum JournalEntryType {
2087    /// Cash journal.
2088    Jnlc,
2089    /// Security journal.
2090    Jnls,
2091}
2092
2093/// Journal status.
2094#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
2095#[serde(rename_all = "lowercase")]
2096pub enum JournalStatus {
2097    /// Pending.
2098    Pending,
2099    /// Executed.
2100    Executed,
2101    /// Canceled.
2102    Canceled,
2103    /// Rejected.
2104    Rejected,
2105}
2106
2107/// ACH relationship.
2108#[derive(Debug, Serialize, Deserialize, Clone)]
2109pub struct AchRelationship {
2110    /// Relationship ID.
2111    pub id: String,
2112    /// Account ID.
2113    pub account_id: String,
2114    /// Status.
2115    pub status: AchRelationshipStatus,
2116    /// Account owner name.
2117    pub account_owner_name: String,
2118    /// Bank account type.
2119    pub bank_account_type: BankAccountType,
2120    /// Bank account number (masked).
2121    pub bank_account_number: String,
2122    /// Bank routing number.
2123    pub bank_routing_number: String,
2124    /// Nickname.
2125    #[serde(skip_serializing_if = "Option::is_none")]
2126    pub nickname: Option<String>,
2127    /// Created at timestamp.
2128    pub created_at: DateTime<Utc>,
2129    /// Updated at timestamp.
2130    #[serde(skip_serializing_if = "Option::is_none")]
2131    pub updated_at: Option<DateTime<Utc>>,
2132}
2133
2134/// Request to create an ACH relationship.
2135#[derive(Debug, Serialize, Deserialize, Clone)]
2136pub struct CreateAchRelationshipRequest {
2137    /// Account owner name.
2138    pub account_owner_name: String,
2139    /// Bank account type.
2140    pub bank_account_type: BankAccountType,
2141    /// Bank account number.
2142    pub bank_account_number: String,
2143    /// Bank routing number.
2144    pub bank_routing_number: String,
2145    /// Nickname.
2146    #[serde(skip_serializing_if = "Option::is_none")]
2147    pub nickname: Option<String>,
2148    /// Processor token (for Plaid integration).
2149    #[serde(skip_serializing_if = "Option::is_none")]
2150    pub processor_token: Option<String>,
2151}
2152
2153impl CreateAchRelationshipRequest {
2154    /// Create new ACH relationship request.
2155    #[must_use]
2156    pub fn new(
2157        account_owner_name: &str,
2158        bank_account_type: BankAccountType,
2159        bank_account_number: &str,
2160        bank_routing_number: &str,
2161    ) -> Self {
2162        Self {
2163            account_owner_name: account_owner_name.to_string(),
2164            bank_account_type,
2165            bank_account_number: bank_account_number.to_string(),
2166            bank_routing_number: bank_routing_number.to_string(),
2167            nickname: None,
2168            processor_token: None,
2169        }
2170    }
2171
2172    /// Set nickname.
2173    #[must_use]
2174    pub fn nickname(mut self, nickname: &str) -> Self {
2175        self.nickname = Some(nickname.to_string());
2176        self
2177    }
2178
2179    /// Set processor token.
2180    #[must_use]
2181    pub fn processor_token(mut self, token: &str) -> Self {
2182        self.processor_token = Some(token.to_string());
2183        self
2184    }
2185}
2186
2187/// Transfer.
2188#[derive(Debug, Serialize, Deserialize, Clone)]
2189pub struct Transfer {
2190    /// Transfer ID.
2191    pub id: String,
2192    /// Relationship ID.
2193    #[serde(skip_serializing_if = "Option::is_none")]
2194    pub relationship_id: Option<String>,
2195    /// Account ID.
2196    pub account_id: String,
2197    /// Transfer type.
2198    #[serde(rename = "type")]
2199    pub transfer_type: TransferType,
2200    /// Status.
2201    pub status: TransferStatus,
2202    /// Amount in USD.
2203    pub amount: String,
2204    /// Direction.
2205    pub direction: TransferDirection,
2206    /// Created at timestamp.
2207    pub created_at: DateTime<Utc>,
2208    /// Updated at timestamp.
2209    #[serde(skip_serializing_if = "Option::is_none")]
2210    pub updated_at: Option<DateTime<Utc>>,
2211    /// Expires at timestamp.
2212    #[serde(skip_serializing_if = "Option::is_none")]
2213    pub expires_at: Option<DateTime<Utc>>,
2214    /// Reason for status.
2215    #[serde(skip_serializing_if = "Option::is_none")]
2216    pub reason: Option<String>,
2217}
2218
2219/// Request to create a transfer.
2220#[derive(Debug, Serialize, Deserialize, Clone)]
2221pub struct CreateTransferRequest {
2222    /// Transfer type.
2223    pub transfer_type: TransferType,
2224    /// Relationship ID (for ACH).
2225    #[serde(skip_serializing_if = "Option::is_none")]
2226    pub relationship_id: Option<String>,
2227    /// Amount in USD.
2228    pub amount: String,
2229    /// Direction.
2230    pub direction: TransferDirection,
2231    /// Additional info.
2232    #[serde(skip_serializing_if = "Option::is_none")]
2233    pub additional_information: Option<String>,
2234}
2235
2236impl CreateTransferRequest {
2237    /// Create new ACH transfer request.
2238    #[must_use]
2239    pub fn ach(relationship_id: &str, amount: &str, direction: TransferDirection) -> Self {
2240        Self {
2241            transfer_type: TransferType::Ach,
2242            relationship_id: Some(relationship_id.to_string()),
2243            amount: amount.to_string(),
2244            direction,
2245            additional_information: None,
2246        }
2247    }
2248
2249    /// Create new wire transfer request.
2250    #[must_use]
2251    pub fn wire(amount: &str, direction: TransferDirection) -> Self {
2252        Self {
2253            transfer_type: TransferType::Wire,
2254            relationship_id: None,
2255            amount: amount.to_string(),
2256            direction,
2257            additional_information: None,
2258        }
2259    }
2260}
2261
2262/// Wire bank details.
2263#[derive(Debug, Serialize, Deserialize, Clone)]
2264pub struct WireBank {
2265    /// Bank ID.
2266    pub id: String,
2267    /// Account ID.
2268    pub account_id: String,
2269    /// Bank name.
2270    pub name: String,
2271    /// Bank code.
2272    #[serde(skip_serializing_if = "Option::is_none")]
2273    pub bank_code: Option<String>,
2274    /// Bank code type.
2275    #[serde(skip_serializing_if = "Option::is_none")]
2276    pub bank_code_type: Option<String>,
2277    /// Country.
2278    #[serde(skip_serializing_if = "Option::is_none")]
2279    pub country: Option<String>,
2280    /// State/Province.
2281    #[serde(skip_serializing_if = "Option::is_none")]
2282    pub state_province: Option<String>,
2283    /// Postal code.
2284    #[serde(skip_serializing_if = "Option::is_none")]
2285    pub postal_code: Option<String>,
2286    /// City.
2287    #[serde(skip_serializing_if = "Option::is_none")]
2288    pub city: Option<String>,
2289    /// Street address.
2290    #[serde(skip_serializing_if = "Option::is_none")]
2291    pub street_address: Option<String>,
2292    /// Account number.
2293    #[serde(skip_serializing_if = "Option::is_none")]
2294    pub account_number: Option<String>,
2295    /// Created at timestamp.
2296    pub created_at: DateTime<Utc>,
2297}
2298
2299/// Request to create a wire bank.
2300#[derive(Debug, Serialize, Deserialize, Clone)]
2301pub struct CreateWireBankRequest {
2302    /// Bank name.
2303    pub name: String,
2304    /// Bank code.
2305    #[serde(skip_serializing_if = "Option::is_none")]
2306    pub bank_code: Option<String>,
2307    /// Bank code type (ABA, BIC, etc.).
2308    #[serde(skip_serializing_if = "Option::is_none")]
2309    pub bank_code_type: Option<String>,
2310    /// Country.
2311    #[serde(skip_serializing_if = "Option::is_none")]
2312    pub country: Option<String>,
2313    /// City.
2314    #[serde(skip_serializing_if = "Option::is_none")]
2315    pub city: Option<String>,
2316    /// Account number.
2317    #[serde(skip_serializing_if = "Option::is_none")]
2318    pub account_number: Option<String>,
2319}
2320
2321/// Journal entry.
2322#[derive(Debug, Serialize, Deserialize, Clone)]
2323pub struct Journal {
2324    /// Journal ID.
2325    pub id: String,
2326    /// From account ID.
2327    pub from_account: String,
2328    /// To account ID.
2329    pub to_account: String,
2330    /// Entry type.
2331    pub entry_type: JournalEntryType,
2332    /// Status.
2333    pub status: JournalStatus,
2334    /// Net amount.
2335    #[serde(skip_serializing_if = "Option::is_none")]
2336    pub net_amount: Option<String>,
2337    /// Symbol (for security journals).
2338    #[serde(skip_serializing_if = "Option::is_none")]
2339    pub symbol: Option<String>,
2340    /// Quantity (for security journals).
2341    #[serde(skip_serializing_if = "Option::is_none")]
2342    pub qty: Option<String>,
2343    /// Description.
2344    #[serde(skip_serializing_if = "Option::is_none")]
2345    pub description: Option<String>,
2346    /// Settle date.
2347    #[serde(skip_serializing_if = "Option::is_none")]
2348    pub settle_date: Option<String>,
2349    /// System date.
2350    #[serde(skip_serializing_if = "Option::is_none")]
2351    pub system_date: Option<String>,
2352}
2353
2354/// Request to create a journal entry.
2355#[derive(Debug, Serialize, Deserialize, Clone)]
2356pub struct CreateJournalRequest {
2357    /// From account ID.
2358    pub from_account: String,
2359    /// To account ID.
2360    pub to_account: String,
2361    /// Entry type.
2362    pub entry_type: JournalEntryType,
2363    /// Amount (for cash journals).
2364    #[serde(skip_serializing_if = "Option::is_none")]
2365    pub amount: Option<String>,
2366    /// Symbol (for security journals).
2367    #[serde(skip_serializing_if = "Option::is_none")]
2368    pub symbol: Option<String>,
2369    /// Quantity (for security journals).
2370    #[serde(skip_serializing_if = "Option::is_none")]
2371    pub qty: Option<String>,
2372    /// Description.
2373    #[serde(skip_serializing_if = "Option::is_none")]
2374    pub description: Option<String>,
2375}
2376
2377impl CreateJournalRequest {
2378    /// Create cash journal request.
2379    #[must_use]
2380    pub fn cash(from_account: &str, to_account: &str, amount: &str) -> Self {
2381        Self {
2382            from_account: from_account.to_string(),
2383            to_account: to_account.to_string(),
2384            entry_type: JournalEntryType::Jnlc,
2385            amount: Some(amount.to_string()),
2386            symbol: None,
2387            qty: None,
2388            description: None,
2389        }
2390    }
2391
2392    /// Create security journal request.
2393    #[must_use]
2394    pub fn security(from_account: &str, to_account: &str, symbol: &str, qty: &str) -> Self {
2395        Self {
2396            from_account: from_account.to_string(),
2397            to_account: to_account.to_string(),
2398            entry_type: JournalEntryType::Jnls,
2399            amount: None,
2400            symbol: Some(symbol.to_string()),
2401            qty: Some(qty.to_string()),
2402            description: None,
2403        }
2404    }
2405
2406    /// Set description.
2407    #[must_use]
2408    pub fn description(mut self, description: &str) -> Self {
2409        self.description = Some(description.to_string());
2410        self
2411    }
2412}
2413
2414/// Batch journal entry.
2415#[derive(Debug, Serialize, Deserialize, Clone)]
2416pub struct BatchJournalEntry {
2417    /// To account ID.
2418    pub to_account: String,
2419    /// Amount.
2420    pub amount: String,
2421}
2422
2423/// Request to create batch journal entries.
2424#[derive(Debug, Serialize, Deserialize, Clone)]
2425pub struct CreateBatchJournalRequest {
2426    /// From account ID.
2427    pub from_account: String,
2428    /// Entry type.
2429    pub entry_type: JournalEntryType,
2430    /// Entries.
2431    pub entries: Vec<BatchJournalEntry>,
2432    /// Description.
2433    #[serde(skip_serializing_if = "Option::is_none")]
2434    pub description: Option<String>,
2435}
2436
2437/// Parameters for listing transfers.
2438#[derive(Debug, Serialize, Deserialize, Clone, Default)]
2439pub struct ListTransfersParams {
2440    /// Filter by direction.
2441    #[serde(skip_serializing_if = "Option::is_none")]
2442    pub direction: Option<TransferDirection>,
2443    /// Maximum number of results.
2444    #[serde(skip_serializing_if = "Option::is_none")]
2445    pub limit: Option<u32>,
2446    /// Offset for pagination.
2447    #[serde(skip_serializing_if = "Option::is_none")]
2448    pub offset: Option<u32>,
2449}
2450
2451/// Parameters for listing journals.
2452#[derive(Debug, Serialize, Deserialize, Clone, Default)]
2453pub struct ListJournalsParams {
2454    /// After timestamp.
2455    #[serde(skip_serializing_if = "Option::is_none")]
2456    pub after: Option<String>,
2457    /// Before timestamp.
2458    #[serde(skip_serializing_if = "Option::is_none")]
2459    pub before: Option<String>,
2460    /// Filter by status.
2461    #[serde(skip_serializing_if = "Option::is_none")]
2462    pub status: Option<JournalStatus>,
2463    /// Filter by entry type.
2464    #[serde(skip_serializing_if = "Option::is_none")]
2465    pub entry_type: Option<JournalEntryType>,
2466    /// Filter by to account.
2467    #[serde(skip_serializing_if = "Option::is_none")]
2468    pub to_account: Option<String>,
2469    /// Filter by from account.
2470    #[serde(skip_serializing_if = "Option::is_none")]
2471    pub from_account: Option<String>,
2472}
2473
2474// ============================================================================
2475// Enhanced Crypto Trading Types
2476// ============================================================================
2477
2478/// Crypto blockchain chain.
2479#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
2480#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
2481pub enum CryptoChain {
2482    /// Bitcoin.
2483    Btc,
2484    /// Ethereum.
2485    Eth,
2486    /// Solana.
2487    Sol,
2488    /// Avalanche.
2489    Avax,
2490    /// Polygon.
2491    Matic,
2492    /// Arbitrum.
2493    Arb,
2494    /// Base.
2495    Base,
2496}
2497
2498/// Crypto transfer status.
2499#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
2500#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
2501pub enum CryptoTransferStatus {
2502    /// Pending approval.
2503    Pending,
2504    /// Approved.
2505    Approved,
2506    /// Pending send to blockchain.
2507    PendingSend,
2508    /// Sent to blockchain.
2509    Sent,
2510    /// Complete.
2511    Complete,
2512    /// Rejected.
2513    Rejected,
2514    /// Failed.
2515    Failed,
2516}
2517
2518/// Crypto transfer direction.
2519#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
2520#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
2521pub enum CryptoTransferDirection {
2522    /// Incoming (deposit).
2523    Incoming,
2524    /// Outgoing (withdrawal).
2525    Outgoing,
2526}
2527
2528/// Crypto wallet status.
2529#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
2530#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
2531pub enum CryptoWalletStatus {
2532    /// Active.
2533    Active,
2534    /// Inactive.
2535    Inactive,
2536    /// Pending.
2537    Pending,
2538}
2539
2540/// Broker crypto wallet for Broker API.
2541#[derive(Debug, Serialize, Deserialize, Clone)]
2542pub struct BrokerCryptoWallet {
2543    /// Wallet ID.
2544    pub id: String,
2545    /// Account ID.
2546    pub account_id: String,
2547    /// Asset symbol (e.g., BTC, ETH).
2548    pub asset: String,
2549    /// Wallet address.
2550    pub address: String,
2551    /// Blockchain chain.
2552    pub chain: CryptoChain,
2553    /// Wallet status.
2554    pub status: CryptoWalletStatus,
2555    /// Created at timestamp.
2556    pub created_at: DateTime<Utc>,
2557}
2558
2559/// Request to create a crypto wallet.
2560#[derive(Debug, Serialize, Deserialize, Clone)]
2561pub struct CreateCryptoWalletRequest {
2562    /// Asset symbol (e.g., BTC, ETH).
2563    pub asset: String,
2564}
2565
2566impl CreateCryptoWalletRequest {
2567    /// Create new wallet request.
2568    #[must_use]
2569    pub fn new(asset: &str) -> Self {
2570        Self {
2571            asset: asset.to_string(),
2572        }
2573    }
2574}
2575
2576/// Crypto transfer.
2577#[derive(Debug, Serialize, Deserialize, Clone)]
2578pub struct CryptoTransfer {
2579    /// Transfer ID.
2580    pub id: String,
2581    /// Wallet ID.
2582    pub wallet_id: String,
2583    /// Account ID.
2584    pub account_id: String,
2585    /// Asset symbol.
2586    pub asset: String,
2587    /// Amount.
2588    pub amount: String,
2589    /// Direction.
2590    pub direction: CryptoTransferDirection,
2591    /// Status.
2592    pub status: CryptoTransferStatus,
2593    /// Fee.
2594    #[serde(skip_serializing_if = "Option::is_none")]
2595    pub fee: Option<String>,
2596    /// Transaction hash.
2597    #[serde(skip_serializing_if = "Option::is_none")]
2598    pub tx_hash: Option<String>,
2599    /// Created at timestamp.
2600    pub created_at: DateTime<Utc>,
2601    /// Updated at timestamp.
2602    #[serde(skip_serializing_if = "Option::is_none")]
2603    pub updated_at: Option<DateTime<Utc>>,
2604}
2605
2606/// Request to create a crypto transfer.
2607#[derive(Debug, Serialize, Deserialize, Clone)]
2608pub struct CreateCryptoTransferRequest {
2609    /// Amount to transfer.
2610    pub amount: String,
2611    /// Destination address (for withdrawals).
2612    #[serde(skip_serializing_if = "Option::is_none")]
2613    pub address: Option<String>,
2614}
2615
2616impl CreateCryptoTransferRequest {
2617    /// Create withdrawal request.
2618    #[must_use]
2619    pub fn withdrawal(amount: &str, address: &str) -> Self {
2620        Self {
2621            amount: amount.to_string(),
2622            address: Some(address.to_string()),
2623        }
2624    }
2625}
2626
2627/// Whitelisted crypto address.
2628#[derive(Debug, Serialize, Deserialize, Clone)]
2629pub struct CryptoWhitelistAddress {
2630    /// Whitelist ID.
2631    pub id: String,
2632    /// Account ID.
2633    pub account_id: String,
2634    /// Asset symbol.
2635    pub asset: String,
2636    /// Whitelisted address.
2637    pub address: String,
2638    /// Chain.
2639    pub chain: CryptoChain,
2640    /// Label/nickname.
2641    #[serde(skip_serializing_if = "Option::is_none")]
2642    pub label: Option<String>,
2643    /// Created at timestamp.
2644    pub created_at: DateTime<Utc>,
2645}
2646
2647/// Request to add a whitelisted address.
2648#[derive(Debug, Serialize, Deserialize, Clone)]
2649pub struct CreateCryptoWhitelistRequest {
2650    /// Asset symbol.
2651    pub asset: String,
2652    /// Address to whitelist.
2653    pub address: String,
2654    /// Label/nickname.
2655    #[serde(skip_serializing_if = "Option::is_none")]
2656    pub label: Option<String>,
2657}
2658
2659impl CreateCryptoWhitelistRequest {
2660    /// Create new whitelist request.
2661    #[must_use]
2662    pub fn new(asset: &str, address: &str) -> Self {
2663        Self {
2664            asset: asset.to_string(),
2665            address: address.to_string(),
2666            label: None,
2667        }
2668    }
2669
2670    /// Set label.
2671    #[must_use]
2672    pub fn label(mut self, label: &str) -> Self {
2673        self.label = Some(label.to_string());
2674        self
2675    }
2676}
2677
2678/// Crypto snapshot with current price data.
2679#[derive(Debug, Serialize, Deserialize, Clone)]
2680pub struct CryptoSnapshot {
2681    /// Latest trade.
2682    #[serde(rename = "latestTrade")]
2683    pub latest_trade: Option<CryptoTrade>,
2684    /// Latest quote.
2685    #[serde(rename = "latestQuote")]
2686    pub latest_quote: Option<CryptoQuote>,
2687    /// Minute bar.
2688    #[serde(rename = "minuteBar")]
2689    pub minute_bar: Option<CryptoBar>,
2690    /// Daily bar.
2691    #[serde(rename = "dailyBar")]
2692    pub daily_bar: Option<CryptoBar>,
2693    /// Previous daily bar.
2694    #[serde(rename = "prevDailyBar")]
2695    pub prev_daily_bar: Option<CryptoBar>,
2696}
2697
2698/// Crypto trade data.
2699#[derive(Debug, Serialize, Deserialize, Clone)]
2700pub struct CryptoTrade {
2701    /// Timestamp.
2702    #[serde(rename = "t")]
2703    pub timestamp: DateTime<Utc>,
2704    /// Price.
2705    #[serde(rename = "p")]
2706    pub price: f64,
2707    /// Size.
2708    #[serde(rename = "s")]
2709    pub size: f64,
2710    /// Taker side.
2711    #[serde(rename = "tks")]
2712    pub taker_side: String,
2713    /// Trade ID.
2714    #[serde(rename = "i")]
2715    pub id: u64,
2716}
2717
2718/// Crypto quote data.
2719#[derive(Debug, Serialize, Deserialize, Clone)]
2720pub struct CryptoQuote {
2721    /// Timestamp.
2722    #[serde(rename = "t")]
2723    pub timestamp: DateTime<Utc>,
2724    /// Bid price.
2725    #[serde(rename = "bp")]
2726    pub bid_price: f64,
2727    /// Bid size.
2728    #[serde(rename = "bs")]
2729    pub bid_size: f64,
2730    /// Ask price.
2731    #[serde(rename = "ap")]
2732    pub ask_price: f64,
2733    /// Ask size.
2734    #[serde(rename = "as")]
2735    pub ask_size: f64,
2736}
2737
2738/// Crypto bar data.
2739#[derive(Debug, Serialize, Deserialize, Clone)]
2740pub struct CryptoBar {
2741    /// Timestamp.
2742    #[serde(rename = "t")]
2743    pub timestamp: DateTime<Utc>,
2744    /// Open price.
2745    #[serde(rename = "o")]
2746    pub open: f64,
2747    /// High price.
2748    #[serde(rename = "h")]
2749    pub high: f64,
2750    /// Low price.
2751    #[serde(rename = "l")]
2752    pub low: f64,
2753    /// Close price.
2754    #[serde(rename = "c")]
2755    pub close: f64,
2756    /// Volume.
2757    #[serde(rename = "v")]
2758    pub volume: f64,
2759    /// Number of trades.
2760    #[serde(rename = "n", skip_serializing_if = "Option::is_none")]
2761    pub trade_count: Option<u64>,
2762    /// Volume-weighted average price.
2763    #[serde(rename = "vw", skip_serializing_if = "Option::is_none")]
2764    pub vwap: Option<f64>,
2765}
2766
2767/// Crypto orderbook entry.
2768#[derive(Debug, Serialize, Deserialize, Clone)]
2769pub struct CryptoOrderbookEntry {
2770    /// Price.
2771    #[serde(rename = "p")]
2772    pub price: f64,
2773    /// Size.
2774    #[serde(rename = "s")]
2775    pub size: f64,
2776}
2777
2778/// Crypto orderbook.
2779#[derive(Debug, Serialize, Deserialize, Clone)]
2780pub struct CryptoOrderbook {
2781    /// Timestamp.
2782    #[serde(rename = "t")]
2783    pub timestamp: DateTime<Utc>,
2784    /// Bid entries.
2785    #[serde(rename = "b")]
2786    pub bids: Vec<CryptoOrderbookEntry>,
2787    /// Ask entries.
2788    #[serde(rename = "a")]
2789    pub asks: Vec<CryptoOrderbookEntry>,
2790}
2791
2792/// Parameters for crypto bars request.
2793#[derive(Debug, Serialize, Deserialize, Clone, Default)]
2794pub struct CryptoBarsParams {
2795    /// Comma-separated list of symbols.
2796    #[serde(skip_serializing_if = "Option::is_none")]
2797    pub symbols: Option<String>,
2798    /// Timeframe (e.g., "1Min", "1Hour", "1Day").
2799    #[serde(skip_serializing_if = "Option::is_none")]
2800    pub timeframe: Option<String>,
2801    /// Start time (RFC3339).
2802    #[serde(skip_serializing_if = "Option::is_none")]
2803    pub start: Option<String>,
2804    /// End time (RFC3339).
2805    #[serde(skip_serializing_if = "Option::is_none")]
2806    pub end: Option<String>,
2807    /// Maximum number of bars.
2808    #[serde(skip_serializing_if = "Option::is_none")]
2809    pub limit: Option<u32>,
2810}
2811
2812impl CryptoBarsParams {
2813    /// Create new parameters with symbols.
2814    #[must_use]
2815    pub fn new(symbols: &str) -> Self {
2816        Self {
2817            symbols: Some(symbols.to_string()),
2818            ..Default::default()
2819        }
2820    }
2821
2822    /// Set timeframe.
2823    #[must_use]
2824    pub fn timeframe(mut self, timeframe: &str) -> Self {
2825        self.timeframe = Some(timeframe.to_string());
2826        self
2827    }
2828
2829    /// Set time range.
2830    #[must_use]
2831    pub fn time_range(mut self, start: &str, end: &str) -> Self {
2832        self.start = Some(start.to_string());
2833        self.end = Some(end.to_string());
2834        self
2835    }
2836
2837    /// Set limit.
2838    #[must_use]
2839    pub fn limit(mut self, limit: u32) -> Self {
2840        self.limit = Some(limit);
2841        self
2842    }
2843}
2844
2845// ============================================================================
2846// News API Types
2847// ============================================================================
2848
2849/// News content type.
2850#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
2851#[serde(rename_all = "lowercase")]
2852pub enum NewsContentType {
2853    /// Article.
2854    Article,
2855    /// Video.
2856    Video,
2857    /// Audio.
2858    Audio,
2859}
2860
2861/// News image size.
2862#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
2863#[serde(rename_all = "lowercase")]
2864pub enum NewsImageSize {
2865    /// Thumbnail size.
2866    Thumb,
2867    /// Small size.
2868    Small,
2869    /// Large size.
2870    Large,
2871}
2872
2873/// News image.
2874#[derive(Debug, Serialize, Deserialize, Clone)]
2875pub struct NewsImage {
2876    /// Image size.
2877    pub size: NewsImageSize,
2878    /// Image URL.
2879    pub url: String,
2880}
2881
2882/// News source.
2883#[derive(Debug, Serialize, Deserialize, Clone)]
2884pub struct NewsSource {
2885    /// Source name.
2886    pub name: String,
2887    /// Source URL.
2888    #[serde(skip_serializing_if = "Option::is_none")]
2889    pub url: Option<String>,
2890    /// Favicon URL.
2891    #[serde(skip_serializing_if = "Option::is_none")]
2892    pub favicon_url: Option<String>,
2893}
2894
2895/// Enhanced news article with images and additional fields.
2896#[derive(Debug, Serialize, Deserialize, Clone)]
2897pub struct EnhancedNewsArticle {
2898    /// Article ID.
2899    pub id: u64,
2900    /// Headline.
2901    pub headline: String,
2902    /// Author.
2903    #[serde(skip_serializing_if = "Option::is_none")]
2904    pub author: Option<String>,
2905    /// Created at timestamp.
2906    pub created_at: DateTime<Utc>,
2907    /// Updated at timestamp.
2908    #[serde(skip_serializing_if = "Option::is_none")]
2909    pub updated_at: Option<DateTime<Utc>>,
2910    /// Summary.
2911    #[serde(skip_serializing_if = "Option::is_none")]
2912    pub summary: Option<String>,
2913    /// Full content.
2914    #[serde(skip_serializing_if = "Option::is_none")]
2915    pub content: Option<String>,
2916    /// Article URL.
2917    #[serde(skip_serializing_if = "Option::is_none")]
2918    pub url: Option<String>,
2919    /// Images.
2920    #[serde(default)]
2921    pub images: Vec<NewsImage>,
2922    /// Related symbols.
2923    #[serde(default)]
2924    pub symbols: Vec<String>,
2925    /// Source.
2926    #[serde(skip_serializing_if = "Option::is_none")]
2927    pub source: Option<String>,
2928}
2929
2930/// Parameters for news request.
2931#[derive(Debug, Serialize, Deserialize, Clone, Default)]
2932pub struct NewsParams {
2933    /// Filter by symbols (comma-separated).
2934    #[serde(skip_serializing_if = "Option::is_none")]
2935    pub symbols: Option<String>,
2936    /// Start time (RFC3339).
2937    #[serde(skip_serializing_if = "Option::is_none")]
2938    pub start: Option<String>,
2939    /// End time (RFC3339).
2940    #[serde(skip_serializing_if = "Option::is_none")]
2941    pub end: Option<String>,
2942    /// Sort order (asc or desc).
2943    #[serde(skip_serializing_if = "Option::is_none")]
2944    pub sort: Option<String>,
2945    /// Include content in response.
2946    #[serde(skip_serializing_if = "Option::is_none")]
2947    pub include_content: Option<bool>,
2948    /// Exclude articles without content.
2949    #[serde(skip_serializing_if = "Option::is_none")]
2950    pub exclude_contentless: Option<bool>,
2951    /// Maximum number of articles.
2952    #[serde(skip_serializing_if = "Option::is_none")]
2953    pub limit: Option<u32>,
2954    /// Page token for pagination.
2955    #[serde(skip_serializing_if = "Option::is_none")]
2956    pub page_token: Option<String>,
2957}
2958
2959impl NewsParams {
2960    /// Create new empty parameters.
2961    #[must_use]
2962    pub fn new() -> Self {
2963        Self::default()
2964    }
2965
2966    /// Filter by symbols.
2967    #[must_use]
2968    pub fn symbols(mut self, symbols: &str) -> Self {
2969        self.symbols = Some(symbols.to_string());
2970        self
2971    }
2972
2973    /// Set time range.
2974    #[must_use]
2975    pub fn time_range(mut self, start: &str, end: &str) -> Self {
2976        self.start = Some(start.to_string());
2977        self.end = Some(end.to_string());
2978        self
2979    }
2980
2981    /// Sort descending (newest first).
2982    #[must_use]
2983    pub fn sort_desc(mut self) -> Self {
2984        self.sort = Some("desc".to_string());
2985        self
2986    }
2987
2988    /// Sort ascending (oldest first).
2989    #[must_use]
2990    pub fn sort_asc(mut self) -> Self {
2991        self.sort = Some("asc".to_string());
2992        self
2993    }
2994
2995    /// Include full content.
2996    #[must_use]
2997    pub fn with_content(mut self) -> Self {
2998        self.include_content = Some(true);
2999        self
3000    }
3001
3002    /// Exclude articles without content.
3003    #[must_use]
3004    pub fn exclude_empty(mut self) -> Self {
3005        self.exclude_contentless = Some(true);
3006        self
3007    }
3008
3009    /// Set limit.
3010    #[must_use]
3011    pub fn limit(mut self, limit: u32) -> Self {
3012        self.limit = Some(limit);
3013        self
3014    }
3015
3016    /// Set page token.
3017    #[must_use]
3018    pub fn page_token(mut self, token: &str) -> Self {
3019        self.page_token = Some(token.to_string());
3020        self
3021    }
3022}
3023
3024// ============================================================================
3025// OAuth 2.0 Types
3026// ============================================================================
3027
3028/// OAuth 2.0 scope.
3029#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
3030#[serde(rename_all = "snake_case")]
3031pub enum OAuthScope {
3032    /// Read account information.
3033    #[serde(rename = "account:write")]
3034    AccountWrite,
3035    /// Trading access.
3036    Trading,
3037    /// Market data access.
3038    Data,
3039}
3040
3041impl std::fmt::Display for OAuthScope {
3042    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
3043        match self {
3044            OAuthScope::AccountWrite => write!(f, "account:write"),
3045            OAuthScope::Trading => write!(f, "trading"),
3046            OAuthScope::Data => write!(f, "data"),
3047        }
3048    }
3049}
3050
3051/// OAuth 2.0 configuration.
3052#[derive(Debug, Clone)]
3053pub struct OAuthConfig {
3054    /// Client ID.
3055    pub client_id: String,
3056    /// Client secret.
3057    pub client_secret: String,
3058    /// Redirect URI.
3059    pub redirect_uri: String,
3060    /// Requested scopes.
3061    pub scopes: Vec<OAuthScope>,
3062}
3063
3064impl OAuthConfig {
3065    /// Create new OAuth configuration.
3066    #[must_use]
3067    pub fn new(client_id: &str, client_secret: &str, redirect_uri: &str) -> Self {
3068        Self {
3069            client_id: client_id.to_string(),
3070            client_secret: client_secret.to_string(),
3071            redirect_uri: redirect_uri.to_string(),
3072            scopes: vec![],
3073        }
3074    }
3075
3076    /// Add a scope.
3077    #[must_use]
3078    pub fn scope(mut self, scope: OAuthScope) -> Self {
3079        self.scopes.push(scope);
3080        self
3081    }
3082
3083    /// Add multiple scopes.
3084    #[must_use]
3085    pub fn scopes(mut self, scopes: Vec<OAuthScope>) -> Self {
3086        self.scopes.extend(scopes);
3087        self
3088    }
3089
3090    /// Generate authorization URL.
3091    #[must_use]
3092    pub fn authorization_url(&self, state: &str) -> String {
3093        let scopes_str: String = self
3094            .scopes
3095            .iter()
3096            .map(|s| s.to_string())
3097            .collect::<Vec<_>>()
3098            .join(" ");
3099
3100        format!(
3101            "https://app.alpaca.markets/oauth/authorize?response_type=code&client_id={}&redirect_uri={}&state={}&scope={}",
3102            urlencoding::encode(&self.client_id),
3103            urlencoding::encode(&self.redirect_uri),
3104            urlencoding::encode(state),
3105            urlencoding::encode(&scopes_str)
3106        )
3107    }
3108}
3109
3110/// Request to exchange authorization code for token.
3111#[derive(Debug, Serialize, Deserialize, Clone)]
3112pub struct OAuthTokenRequest {
3113    /// Grant type.
3114    pub grant_type: String,
3115    /// Authorization code.
3116    #[serde(skip_serializing_if = "Option::is_none")]
3117    pub code: Option<String>,
3118    /// Client ID.
3119    pub client_id: String,
3120    /// Client secret.
3121    pub client_secret: String,
3122    /// Redirect URI.
3123    #[serde(skip_serializing_if = "Option::is_none")]
3124    pub redirect_uri: Option<String>,
3125    /// Refresh token (for refresh grant).
3126    #[serde(skip_serializing_if = "Option::is_none")]
3127    pub refresh_token: Option<String>,
3128}
3129
3130impl OAuthTokenRequest {
3131    /// Create authorization code exchange request.
3132    #[must_use]
3133    pub fn authorization_code(config: &OAuthConfig, code: &str) -> Self {
3134        Self {
3135            grant_type: "authorization_code".to_string(),
3136            code: Some(code.to_string()),
3137            client_id: config.client_id.clone(),
3138            client_secret: config.client_secret.clone(),
3139            redirect_uri: Some(config.redirect_uri.clone()),
3140            refresh_token: None,
3141        }
3142    }
3143
3144    /// Create refresh token request.
3145    #[must_use]
3146    pub fn refresh(config: &OAuthConfig, refresh_token: &str) -> Self {
3147        Self {
3148            grant_type: "refresh_token".to_string(),
3149            code: None,
3150            client_id: config.client_id.clone(),
3151            client_secret: config.client_secret.clone(),
3152            redirect_uri: None,
3153            refresh_token: Some(refresh_token.to_string()),
3154        }
3155    }
3156}
3157
3158/// Request to revoke a token.
3159#[derive(Debug, Serialize, Deserialize, Clone)]
3160pub struct OAuthRevokeRequest {
3161    /// Token to revoke.
3162    pub token: String,
3163    /// Client ID.
3164    pub client_id: String,
3165    /// Client secret.
3166    pub client_secret: String,
3167}
3168
3169impl OAuthRevokeRequest {
3170    /// Create revoke request.
3171    #[must_use]
3172    pub fn new(config: &OAuthConfig, token: &str) -> Self {
3173        Self {
3174            token: token.to_string(),
3175            client_id: config.client_id.clone(),
3176            client_secret: config.client_secret.clone(),
3177        }
3178    }
3179}
3180
3181// ============================================================================
3182// Broker API Events (SSE) Types
3183// ============================================================================
3184
3185/// Account status event type.
3186#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
3187#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
3188pub enum AccountStatusEventType {
3189    /// Account opened.
3190    AccountOpened,
3191    /// Account updated.
3192    AccountUpdated,
3193    /// Account approval pending.
3194    AccountApprovalPending,
3195    /// Account approved.
3196    AccountApproved,
3197    /// Account rejected.
3198    AccountRejected,
3199    /// Account disabled.
3200    AccountDisabled,
3201    /// Account enabled.
3202    AccountEnabled,
3203}
3204
3205/// Account status event from SSE stream.
3206#[derive(Debug, Serialize, Deserialize, Clone)]
3207pub struct AccountStatusEvent {
3208    /// Event ID.
3209    pub id: String,
3210    /// Account ID.
3211    pub account_id: String,
3212    /// Event type.
3213    pub event_type: AccountStatusEventType,
3214    /// Event timestamp.
3215    pub at: DateTime<Utc>,
3216    /// Status message.
3217    #[serde(skip_serializing_if = "Option::is_none")]
3218    pub status_message: Option<String>,
3219}
3220
3221/// Transfer status event type.
3222#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
3223#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
3224pub enum TransferStatusEventType {
3225    /// Transfer queued.
3226    TransferQueued,
3227    /// Transfer pending.
3228    TransferPending,
3229    /// Transfer approved.
3230    TransferApproved,
3231    /// Transfer complete.
3232    TransferComplete,
3233    /// Transfer returned.
3234    TransferReturned,
3235    /// Transfer canceled.
3236    TransferCanceled,
3237}
3238
3239/// Transfer status event from SSE stream.
3240#[derive(Debug, Serialize, Deserialize, Clone)]
3241pub struct TransferStatusEvent {
3242    /// Event ID.
3243    pub id: String,
3244    /// Account ID.
3245    pub account_id: String,
3246    /// Transfer ID.
3247    pub transfer_id: String,
3248    /// Event type.
3249    pub event_type: TransferStatusEventType,
3250    /// Event timestamp.
3251    pub at: DateTime<Utc>,
3252    /// Amount.
3253    #[serde(skip_serializing_if = "Option::is_none")]
3254    pub amount: Option<String>,
3255}
3256
3257/// Trade event from SSE stream.
3258#[derive(Debug, Serialize, Deserialize, Clone)]
3259pub struct BrokerTradeEvent {
3260    /// Event ID.
3261    pub id: String,
3262    /// Account ID.
3263    pub account_id: String,
3264    /// Order ID.
3265    pub order_id: String,
3266    /// Symbol.
3267    pub symbol: String,
3268    /// Side.
3269    pub side: OrderSide,
3270    /// Quantity.
3271    pub qty: String,
3272    /// Price.
3273    pub price: String,
3274    /// Event timestamp.
3275    pub at: DateTime<Utc>,
3276}
3277
3278/// Journal status event type.
3279#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
3280#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
3281pub enum JournalStatusEventType {
3282    /// Journal pending.
3283    JournalPending,
3284    /// Journal executed.
3285    JournalExecuted,
3286    /// Journal canceled.
3287    JournalCanceled,
3288    /// Journal rejected.
3289    JournalRejected,
3290}
3291
3292/// Journal status event from SSE stream.
3293#[derive(Debug, Serialize, Deserialize, Clone)]
3294pub struct JournalStatusEvent {
3295    /// Event ID.
3296    pub id: String,
3297    /// Journal ID.
3298    pub journal_id: String,
3299    /// Event type.
3300    pub event_type: JournalStatusEventType,
3301    /// Event timestamp.
3302    pub at: DateTime<Utc>,
3303    /// From account.
3304    #[serde(skip_serializing_if = "Option::is_none")]
3305    pub from_account: Option<String>,
3306    /// To account.
3307    #[serde(skip_serializing_if = "Option::is_none")]
3308    pub to_account: Option<String>,
3309}
3310
3311/// Non-trade activity event type.
3312#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
3313#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
3314pub enum NonTradeActivityType {
3315    /// Dividend.
3316    Div,
3317    /// Dividend tax.
3318    Divtax,
3319    /// Interest.
3320    Int,
3321    /// Journal entry.
3322    Jnl,
3323    /// Merger/acquisition.
3324    Ma,
3325    /// Fee.
3326    Fee,
3327    /// Deposit.
3328    Csd,
3329    /// Withdrawal.
3330    Csw,
3331}
3332
3333/// Non-trade activity event from SSE stream.
3334#[derive(Debug, Serialize, Deserialize, Clone)]
3335pub struct NonTradeActivityEvent {
3336    /// Event ID.
3337    pub id: String,
3338    /// Account ID.
3339    pub account_id: String,
3340    /// Activity type.
3341    pub activity_type: NonTradeActivityType,
3342    /// Event timestamp.
3343    pub at: DateTime<Utc>,
3344    /// Symbol (if applicable).
3345    #[serde(skip_serializing_if = "Option::is_none")]
3346    pub symbol: Option<String>,
3347    /// Amount.
3348    #[serde(skip_serializing_if = "Option::is_none")]
3349    pub amount: Option<String>,
3350    /// Description.
3351    #[serde(skip_serializing_if = "Option::is_none")]
3352    pub description: Option<String>,
3353}
3354
3355/// SSE event wrapper for all broker events.
3356#[derive(Debug, Serialize, Deserialize, Clone)]
3357#[serde(tag = "event_type")]
3358pub enum BrokerSseEvent {
3359    /// Account status event.
3360    #[serde(rename = "account_status")]
3361    AccountStatus(AccountStatusEvent),
3362    /// Transfer status event.
3363    #[serde(rename = "transfer_status")]
3364    TransferStatus(TransferStatusEvent),
3365    /// Trade event.
3366    #[serde(rename = "trade")]
3367    Trade(BrokerTradeEvent),
3368    /// Journal status event.
3369    #[serde(rename = "journal_status")]
3370    JournalStatus(JournalStatusEvent),
3371    /// Non-trade activity event.
3372    #[serde(rename = "nta")]
3373    NonTradeActivity(NonTradeActivityEvent),
3374}
3375
3376/// Parameters for SSE event stream.
3377#[derive(Debug, Serialize, Deserialize, Clone, Default)]
3378pub struct SseEventParams {
3379    /// Filter by account ID.
3380    #[serde(skip_serializing_if = "Option::is_none")]
3381    pub account_id: Option<String>,
3382    /// Start timestamp.
3383    #[serde(skip_serializing_if = "Option::is_none")]
3384    pub since: Option<String>,
3385    /// Until timestamp.
3386    #[serde(skip_serializing_if = "Option::is_none")]
3387    pub until: Option<String>,
3388}
3389
3390impl SseEventParams {
3391    /// Create new empty parameters.
3392    #[must_use]
3393    pub fn new() -> Self {
3394        Self::default()
3395    }
3396
3397    /// Filter by account ID.
3398    #[must_use]
3399    pub fn account_id(mut self, account_id: &str) -> Self {
3400        self.account_id = Some(account_id.to_string());
3401        self
3402    }
3403
3404    /// Set since timestamp.
3405    #[must_use]
3406    pub fn since(mut self, since: &str) -> Self {
3407        self.since = Some(since.to_string());
3408        self
3409    }
3410
3411    /// Set until timestamp.
3412    #[must_use]
3413    pub fn until(mut self, until: &str) -> Self {
3414        self.until = Some(until.to_string());
3415        self
3416    }
3417}
3418
3419// ============================================================================
3420// Enhanced Assets API Types
3421// ============================================================================
3422
3423/// Asset attribute.
3424#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
3425#[serde(rename_all = "snake_case")]
3426pub enum AssetAttribute {
3427    /// PTP no exception.
3428    PtpNoException,
3429    /// PTP with exception.
3430    PtpWithException,
3431    /// IPO asset.
3432    Ipo,
3433    /// Options enabled.
3434    OptionsEnabled,
3435    /// Fractional extended hours enabled.
3436    FractionalEhEnabled,
3437}
3438
3439/// Asset exchange.
3440#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
3441#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
3442pub enum AssetExchange {
3443    /// NYSE American.
3444    Amex,
3445    /// NYSE Arca.
3446    Arca,
3447    /// BATS Exchange.
3448    Bats,
3449    /// NYSE.
3450    Nyse,
3451    /// NASDAQ.
3452    Nasdaq,
3453    /// NASDAQ Global Market.
3454    NasdaqGm,
3455    /// NASDAQ Global Select.
3456    NasdaqGs,
3457    /// NASDAQ Capital Market.
3458    NasdaqCm,
3459    /// NYSE MKT.
3460    Nysearca,
3461    /// OTC Bulletin Board.
3462    Otc,
3463    /// Crypto exchange.
3464    Crypto,
3465    /// Options exchange.
3466    #[serde(rename = "OPRA")]
3467    Opra,
3468}
3469
3470/// Enhanced asset with all fields.
3471#[derive(Debug, Serialize, Deserialize, Clone)]
3472pub struct EnhancedAsset {
3473    /// Asset ID.
3474    pub id: Uuid,
3475    /// Asset class.
3476    pub class: String,
3477    /// Exchange.
3478    pub exchange: AssetExchange,
3479    /// Symbol.
3480    pub symbol: String,
3481    /// Name.
3482    #[serde(skip_serializing_if = "Option::is_none")]
3483    pub name: Option<String>,
3484    /// Status.
3485    pub status: AssetStatus,
3486    /// Tradable.
3487    pub tradable: bool,
3488    /// Marginable.
3489    pub marginable: bool,
3490    /// Shortable.
3491    pub shortable: bool,
3492    /// Easy to borrow.
3493    pub easy_to_borrow: bool,
3494    /// Fractionable.
3495    pub fractionable: bool,
3496    /// Maintenance margin requirement.
3497    #[serde(skip_serializing_if = "Option::is_none")]
3498    pub maintenance_margin_requirement: Option<f64>,
3499    /// Minimum order size.
3500    #[serde(skip_serializing_if = "Option::is_none")]
3501    pub min_order_size: Option<String>,
3502    /// Minimum trade increment.
3503    #[serde(skip_serializing_if = "Option::is_none")]
3504    pub min_trade_increment: Option<String>,
3505    /// Price increment.
3506    #[serde(skip_serializing_if = "Option::is_none")]
3507    pub price_increment: Option<String>,
3508    /// Asset attributes.
3509    #[serde(default)]
3510    pub attributes: Vec<AssetAttribute>,
3511}
3512
3513/// Parameters for listing assets.
3514#[derive(Debug, Serialize, Deserialize, Clone, Default)]
3515pub struct ListAssetsParams {
3516    /// Filter by status.
3517    #[serde(skip_serializing_if = "Option::is_none")]
3518    pub status: Option<AssetStatus>,
3519    /// Filter by asset class.
3520    #[serde(skip_serializing_if = "Option::is_none")]
3521    pub asset_class: Option<String>,
3522    /// Filter by exchange.
3523    #[serde(skip_serializing_if = "Option::is_none")]
3524    pub exchange: Option<String>,
3525    /// Filter by attributes.
3526    #[serde(skip_serializing_if = "Option::is_none")]
3527    pub attributes: Option<String>,
3528}
3529
3530impl ListAssetsParams {
3531    /// Create new empty parameters.
3532    #[must_use]
3533    pub fn new() -> Self {
3534        Self::default()
3535    }
3536
3537    /// Filter by status.
3538    #[must_use]
3539    pub fn status(mut self, status: AssetStatus) -> Self {
3540        self.status = Some(status);
3541        self
3542    }
3543
3544    /// Filter by asset class.
3545    #[must_use]
3546    pub fn asset_class(mut self, asset_class: &str) -> Self {
3547        self.asset_class = Some(asset_class.to_string());
3548        self
3549    }
3550
3551    /// Filter by exchange.
3552    #[must_use]
3553    pub fn exchange(mut self, exchange: &str) -> Self {
3554        self.exchange = Some(exchange.to_string());
3555        self
3556    }
3557
3558    /// Filter by attributes.
3559    #[must_use]
3560    pub fn attributes(mut self, attributes: &str) -> Self {
3561        self.attributes = Some(attributes.to_string());
3562        self
3563    }
3564}
3565
3566/// Option contract asset.
3567#[derive(Debug, Serialize, Deserialize, Clone)]
3568pub struct OptionContractAsset {
3569    /// Contract ID.
3570    pub id: Uuid,
3571    /// Symbol.
3572    pub symbol: String,
3573    /// Name.
3574    #[serde(skip_serializing_if = "Option::is_none")]
3575    pub name: Option<String>,
3576    /// Status.
3577    pub status: AssetStatus,
3578    /// Tradable.
3579    pub tradable: bool,
3580    /// Expiration date.
3581    pub expiration_date: String,
3582    /// Strike price.
3583    pub strike_price: String,
3584    /// Option type (call/put).
3585    pub option_type: OptionType,
3586    /// Option style (american/european).
3587    #[serde(skip_serializing_if = "Option::is_none")]
3588    pub option_style: Option<OptionStyle>,
3589    /// Underlying symbol.
3590    pub underlying_symbol: String,
3591    /// Underlying asset ID.
3592    #[serde(skip_serializing_if = "Option::is_none")]
3593    pub underlying_asset_id: Option<Uuid>,
3594    /// Root symbol.
3595    #[serde(skip_serializing_if = "Option::is_none")]
3596    pub root_symbol: Option<String>,
3597}
3598
3599/// Corporate action announcement.
3600#[derive(Debug, Serialize, Deserialize, Clone)]
3601pub struct CorporateActionAnnouncement {
3602    /// Announcement ID.
3603    pub id: String,
3604    /// Corporate action type.
3605    pub ca_type: String,
3606    /// Corporate action sub-type.
3607    #[serde(skip_serializing_if = "Option::is_none")]
3608    pub ca_sub_type: Option<String>,
3609    /// Initiating symbol.
3610    pub initiating_symbol: String,
3611    /// Initiating original CUSIP.
3612    #[serde(skip_serializing_if = "Option::is_none")]
3613    pub initiating_original_cusip: Option<String>,
3614    /// Target symbol.
3615    #[serde(skip_serializing_if = "Option::is_none")]
3616    pub target_symbol: Option<String>,
3617    /// Target original CUSIP.
3618    #[serde(skip_serializing_if = "Option::is_none")]
3619    pub target_original_cusip: Option<String>,
3620    /// Declaration date.
3621    #[serde(skip_serializing_if = "Option::is_none")]
3622    pub declaration_date: Option<String>,
3623    /// Ex date.
3624    #[serde(skip_serializing_if = "Option::is_none")]
3625    pub ex_date: Option<String>,
3626    /// Record date.
3627    #[serde(skip_serializing_if = "Option::is_none")]
3628    pub record_date: Option<String>,
3629    /// Payable date.
3630    #[serde(skip_serializing_if = "Option::is_none")]
3631    pub payable_date: Option<String>,
3632    /// Cash amount.
3633    #[serde(skip_serializing_if = "Option::is_none")]
3634    pub cash: Option<String>,
3635    /// Old rate.
3636    #[serde(skip_serializing_if = "Option::is_none")]
3637    pub old_rate: Option<String>,
3638    /// New rate.
3639    #[serde(skip_serializing_if = "Option::is_none")]
3640    pub new_rate: Option<String>,
3641}
3642
3643/// Parameters for listing corporate action announcements.
3644#[derive(Debug, Serialize, Deserialize, Clone, Default)]
3645pub struct ListAnnouncementsParams {
3646    /// Filter by corporate action types (comma-separated).
3647    #[serde(skip_serializing_if = "Option::is_none")]
3648    pub ca_types: Option<String>,
3649    /// Start date.
3650    #[serde(skip_serializing_if = "Option::is_none")]
3651    pub since: Option<String>,
3652    /// End date.
3653    #[serde(skip_serializing_if = "Option::is_none")]
3654    pub until: Option<String>,
3655    /// Filter by symbol.
3656    #[serde(skip_serializing_if = "Option::is_none")]
3657    pub symbol: Option<String>,
3658    /// Filter by CUSIP.
3659    #[serde(skip_serializing_if = "Option::is_none")]
3660    pub cusip: Option<String>,
3661    /// Date type (declaration, ex, record, payable).
3662    #[serde(skip_serializing_if = "Option::is_none")]
3663    pub date_type: Option<String>,
3664}
3665
3666impl ListAnnouncementsParams {
3667    /// Create new empty parameters.
3668    #[must_use]
3669    pub fn new() -> Self {
3670        Self::default()
3671    }
3672
3673    /// Filter by corporate action types.
3674    #[must_use]
3675    pub fn ca_types(mut self, ca_types: &str) -> Self {
3676        self.ca_types = Some(ca_types.to_string());
3677        self
3678    }
3679
3680    /// Set date range.
3681    #[must_use]
3682    pub fn date_range(mut self, since: &str, until: &str) -> Self {
3683        self.since = Some(since.to_string());
3684        self.until = Some(until.to_string());
3685        self
3686    }
3687
3688    /// Filter by symbol.
3689    #[must_use]
3690    pub fn symbol(mut self, symbol: &str) -> Self {
3691        self.symbol = Some(symbol.to_string());
3692        self
3693    }
3694
3695    /// Filter by CUSIP.
3696    #[must_use]
3697    pub fn cusip(mut self, cusip: &str) -> Self {
3698        self.cusip = Some(cusip.to_string());
3699        self
3700    }
3701}
3702
3703// ============================================================================
3704// Enhanced Account Activities Types
3705// ============================================================================
3706
3707/// Trade activity with detailed fields.
3708#[derive(Debug, Serialize, Deserialize, Clone)]
3709pub struct TradeActivity {
3710    /// Activity ID.
3711    pub id: String,
3712    /// Activity type.
3713    pub activity_type: ActivityType,
3714    /// Transaction time.
3715    pub transaction_time: DateTime<Utc>,
3716    /// Symbol.
3717    pub symbol: String,
3718    /// Order ID.
3719    pub order_id: Uuid,
3720    /// Side.
3721    pub side: OrderSide,
3722    /// Quantity.
3723    pub qty: String,
3724    /// Price.
3725    pub price: String,
3726    /// Cumulative quantity.
3727    #[serde(skip_serializing_if = "Option::is_none")]
3728    pub cum_qty: Option<String>,
3729    /// Leaves quantity.
3730    #[serde(skip_serializing_if = "Option::is_none")]
3731    pub leaves_qty: Option<String>,
3732}
3733
3734/// Non-trade activity with detailed fields.
3735#[derive(Debug, Serialize, Deserialize, Clone)]
3736pub struct NonTradeActivity {
3737    /// Activity ID.
3738    pub id: String,
3739    /// Activity type.
3740    pub activity_type: ActivityType,
3741    /// Date.
3742    pub date: String,
3743    /// Net amount.
3744    pub net_amount: String,
3745    /// Symbol (if applicable).
3746    #[serde(skip_serializing_if = "Option::is_none")]
3747    pub symbol: Option<String>,
3748    /// Quantity (if applicable).
3749    #[serde(skip_serializing_if = "Option::is_none")]
3750    pub qty: Option<String>,
3751    /// Per share amount.
3752    #[serde(skip_serializing_if = "Option::is_none")]
3753    pub per_share_amount: Option<String>,
3754    /// Description.
3755    #[serde(skip_serializing_if = "Option::is_none")]
3756    pub description: Option<String>,
3757}
3758
3759/// Parameters for listing account activities.
3760#[derive(Debug, Serialize, Deserialize, Clone, Default)]
3761pub struct ListActivitiesParams {
3762    /// Filter by activity types (comma-separated).
3763    #[serde(skip_serializing_if = "Option::is_none")]
3764    pub activity_types: Option<String>,
3765    /// Filter by date.
3766    #[serde(skip_serializing_if = "Option::is_none")]
3767    pub date: Option<String>,
3768    /// Filter until date.
3769    #[serde(skip_serializing_if = "Option::is_none")]
3770    pub until: Option<String>,
3771    /// Filter after date.
3772    #[serde(skip_serializing_if = "Option::is_none")]
3773    pub after: Option<String>,
3774    /// Sort direction.
3775    #[serde(skip_serializing_if = "Option::is_none")]
3776    pub direction: Option<SortDirection>,
3777    /// Page size.
3778    #[serde(skip_serializing_if = "Option::is_none")]
3779    pub page_size: Option<u32>,
3780    /// Page token.
3781    #[serde(skip_serializing_if = "Option::is_none")]
3782    pub page_token: Option<String>,
3783}
3784
3785impl ListActivitiesParams {
3786    /// Create new empty parameters.
3787    #[must_use]
3788    pub fn new() -> Self {
3789        Self::default()
3790    }
3791
3792    /// Filter by activity types.
3793    #[must_use]
3794    pub fn activity_types(mut self, types: &str) -> Self {
3795        self.activity_types = Some(types.to_string());
3796        self
3797    }
3798
3799    /// Filter by date.
3800    #[must_use]
3801    pub fn date(mut self, date: &str) -> Self {
3802        self.date = Some(date.to_string());
3803        self
3804    }
3805
3806    /// Filter until date.
3807    #[must_use]
3808    pub fn until(mut self, until: &str) -> Self {
3809        self.until = Some(until.to_string());
3810        self
3811    }
3812
3813    /// Filter after date.
3814    #[must_use]
3815    pub fn after(mut self, after: &str) -> Self {
3816        self.after = Some(after.to_string());
3817        self
3818    }
3819
3820    /// Set sort direction.
3821    #[must_use]
3822    pub fn direction(mut self, direction: SortDirection) -> Self {
3823        self.direction = Some(direction);
3824        self
3825    }
3826
3827    /// Set page size.
3828    #[must_use]
3829    pub fn page_size(mut self, size: u32) -> Self {
3830        self.page_size = Some(size);
3831        self
3832    }
3833
3834    /// Set page token.
3835    #[must_use]
3836    pub fn page_token(mut self, token: &str) -> Self {
3837        self.page_token = Some(token.to_string());
3838        self
3839    }
3840}
3841
3842// ============================================================================
3843// Portfolio Management Types
3844// ============================================================================
3845
3846/// Portfolio history timeframe.
3847#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
3848pub enum PortfolioTimeframe {
3849    /// 1 minute.
3850    #[serde(rename = "1Min")]
3851    OneMin,
3852    /// 5 minutes.
3853    #[serde(rename = "5Min")]
3854    FiveMin,
3855    /// 15 minutes.
3856    #[serde(rename = "15Min")]
3857    FifteenMin,
3858    /// 1 hour.
3859    #[serde(rename = "1H")]
3860    OneHour,
3861    /// 1 day.
3862    #[serde(rename = "1D")]
3863    OneDay,
3864}
3865
3866/// Portfolio history period.
3867#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
3868pub enum PortfolioPeriod {
3869    /// 1 day.
3870    #[serde(rename = "1D")]
3871    OneDay,
3872    /// 1 week.
3873    #[serde(rename = "1W")]
3874    OneWeek,
3875    /// 1 month.
3876    #[serde(rename = "1M")]
3877    OneMonth,
3878    /// 3 months.
3879    #[serde(rename = "3M")]
3880    ThreeMonths,
3881    /// 1 year.
3882    #[serde(rename = "1A")]
3883    OneYear,
3884    /// All time.
3885    #[serde(rename = "all")]
3886    All,
3887}
3888
3889/// Parameters for portfolio history.
3890#[derive(Debug, Serialize, Deserialize, Clone, Default)]
3891pub struct PortfolioHistoryParams {
3892    /// Period.
3893    #[serde(skip_serializing_if = "Option::is_none")]
3894    pub period: Option<String>,
3895    /// Timeframe.
3896    #[serde(skip_serializing_if = "Option::is_none")]
3897    pub timeframe: Option<String>,
3898    /// End date.
3899    #[serde(skip_serializing_if = "Option::is_none")]
3900    pub date_end: Option<String>,
3901    /// Include extended hours.
3902    #[serde(skip_serializing_if = "Option::is_none")]
3903    pub extended_hours: Option<bool>,
3904    /// Intraday reporting.
3905    #[serde(skip_serializing_if = "Option::is_none")]
3906    pub intraday_reporting: Option<String>,
3907    /// PnL reset.
3908    #[serde(skip_serializing_if = "Option::is_none")]
3909    pub pnl_reset: Option<String>,
3910}
3911
3912impl PortfolioHistoryParams {
3913    /// Create new empty parameters.
3914    #[must_use]
3915    pub fn new() -> Self {
3916        Self::default()
3917    }
3918
3919    /// Set period.
3920    #[must_use]
3921    pub fn period(mut self, period: PortfolioPeriod) -> Self {
3922        self.period = Some(match period {
3923            PortfolioPeriod::OneDay => "1D".to_string(),
3924            PortfolioPeriod::OneWeek => "1W".to_string(),
3925            PortfolioPeriod::OneMonth => "1M".to_string(),
3926            PortfolioPeriod::ThreeMonths => "3M".to_string(),
3927            PortfolioPeriod::OneYear => "1A".to_string(),
3928            PortfolioPeriod::All => "all".to_string(),
3929        });
3930        self
3931    }
3932
3933    /// Set timeframe.
3934    #[must_use]
3935    pub fn timeframe(mut self, timeframe: PortfolioTimeframe) -> Self {
3936        self.timeframe = Some(match timeframe {
3937            PortfolioTimeframe::OneMin => "1Min".to_string(),
3938            PortfolioTimeframe::FiveMin => "5Min".to_string(),
3939            PortfolioTimeframe::FifteenMin => "15Min".to_string(),
3940            PortfolioTimeframe::OneHour => "1H".to_string(),
3941            PortfolioTimeframe::OneDay => "1D".to_string(),
3942        });
3943        self
3944    }
3945
3946    /// Set end date.
3947    #[must_use]
3948    pub fn date_end(mut self, date_end: &str) -> Self {
3949        self.date_end = Some(date_end.to_string());
3950        self
3951    }
3952
3953    /// Include extended hours.
3954    #[must_use]
3955    pub fn extended_hours(mut self, extended_hours: bool) -> Self {
3956        self.extended_hours = Some(extended_hours);
3957        self
3958    }
3959}
3960
3961/// Target allocation for rebalancing.
3962#[derive(Debug, Serialize, Deserialize, Clone)]
3963pub struct TargetAllocation {
3964    /// Symbol.
3965    pub symbol: String,
3966    /// Target percentage (0-100).
3967    #[serde(skip_serializing_if = "Option::is_none")]
3968    pub percent: Option<f64>,
3969    /// Target notional value.
3970    #[serde(skip_serializing_if = "Option::is_none")]
3971    pub notional: Option<String>,
3972}
3973
3974impl TargetAllocation {
3975    /// Create allocation by percentage.
3976    #[must_use]
3977    pub fn percent(symbol: &str, percent: f64) -> Self {
3978        Self {
3979            symbol: symbol.to_string(),
3980            percent: Some(percent),
3981            notional: None,
3982        }
3983    }
3984
3985    /// Create allocation by notional value.
3986    #[must_use]
3987    pub fn notional(symbol: &str, notional: &str) -> Self {
3988        Self {
3989            symbol: symbol.to_string(),
3990            percent: None,
3991            notional: Some(notional.to_string()),
3992        }
3993    }
3994}
3995
3996/// Rebalance status.
3997#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
3998#[serde(rename_all = "lowercase")]
3999pub enum RebalanceStatus {
4000    /// Pending.
4001    Pending,
4002    /// In progress.
4003    InProgress,
4004    /// Completed.
4005    Completed,
4006    /// Failed.
4007    Failed,
4008    /// Canceled.
4009    Canceled,
4010}
4011
4012/// Rebalance portfolio request.
4013#[derive(Debug, Serialize, Deserialize, Clone)]
4014pub struct RebalancePortfolioRequest {
4015    /// Portfolio name.
4016    pub name: String,
4017    /// Description.
4018    #[serde(skip_serializing_if = "Option::is_none")]
4019    pub description: Option<String>,
4020    /// Target allocations.
4021    pub weights: Vec<TargetAllocation>,
4022}
4023
4024impl RebalancePortfolioRequest {
4025    /// Create new rebalance portfolio request.
4026    #[must_use]
4027    pub fn new(name: &str, weights: Vec<TargetAllocation>) -> Self {
4028        Self {
4029            name: name.to_string(),
4030            description: None,
4031            weights,
4032        }
4033    }
4034
4035    /// Set description.
4036    #[must_use]
4037    pub fn description(mut self, description: &str) -> Self {
4038        self.description = Some(description.to_string());
4039        self
4040    }
4041}
4042
4043/// Rebalance portfolio.
4044#[derive(Debug, Serialize, Deserialize, Clone)]
4045pub struct RebalancePortfolio {
4046    /// Portfolio ID.
4047    pub id: Uuid,
4048    /// Portfolio name.
4049    pub name: String,
4050    /// Description.
4051    #[serde(skip_serializing_if = "Option::is_none")]
4052    pub description: Option<String>,
4053    /// Target allocations.
4054    pub weights: Vec<TargetAllocation>,
4055    /// Created at.
4056    pub created_at: DateTime<Utc>,
4057    /// Updated at.
4058    pub updated_at: DateTime<Utc>,
4059}
4060
4061/// Rebalance run request.
4062#[derive(Debug, Serialize, Deserialize, Clone)]
4063pub struct RebalanceRunRequest {
4064    /// Account ID.
4065    pub account_id: String,
4066    /// Portfolio ID.
4067    pub portfolio_id: String,
4068    /// Type of rebalance.
4069    #[serde(rename = "type")]
4070    #[serde(skip_serializing_if = "Option::is_none")]
4071    pub run_type: Option<String>,
4072}
4073
4074impl RebalanceRunRequest {
4075    /// Create new rebalance run request.
4076    #[must_use]
4077    pub fn new(account_id: &str, portfolio_id: &str) -> Self {
4078        Self {
4079            account_id: account_id.to_string(),
4080            portfolio_id: portfolio_id.to_string(),
4081            run_type: None,
4082        }
4083    }
4084}
4085
4086/// Rebalance run result.
4087#[derive(Debug, Serialize, Deserialize, Clone)]
4088pub struct RebalanceRun {
4089    /// Run ID.
4090    pub id: Uuid,
4091    /// Account ID.
4092    pub account_id: String,
4093    /// Portfolio ID.
4094    pub portfolio_id: String,
4095    /// Status.
4096    pub status: RebalanceStatus,
4097    /// Created at.
4098    pub created_at: DateTime<Utc>,
4099    /// Completed at.
4100    #[serde(skip_serializing_if = "Option::is_none")]
4101    pub completed_at: Option<DateTime<Utc>>,
4102}
4103
4104/// Close position request.
4105#[derive(Debug, Serialize, Deserialize, Clone, Default)]
4106pub struct ClosePositionParams {
4107    /// Quantity to close.
4108    #[serde(skip_serializing_if = "Option::is_none")]
4109    pub qty: Option<String>,
4110    /// Percentage to close (0-100).
4111    #[serde(skip_serializing_if = "Option::is_none")]
4112    pub percentage: Option<String>,
4113}
4114
4115impl ClosePositionParams {
4116    /// Create new empty parameters.
4117    #[must_use]
4118    pub fn new() -> Self {
4119        Self::default()
4120    }
4121
4122    /// Close specific quantity.
4123    #[must_use]
4124    pub fn qty(mut self, qty: &str) -> Self {
4125        self.qty = Some(qty.to_string());
4126        self
4127    }
4128
4129    /// Close percentage.
4130    #[must_use]
4131    pub fn percentage(mut self, percentage: &str) -> Self {
4132        self.percentage = Some(percentage.to_string());
4133        self
4134    }
4135}
4136
4137// ============================================================================
4138// Rate Limiting Types
4139// ============================================================================
4140
4141/// Rate limit configuration.
4142#[derive(Debug, Clone)]
4143pub struct RateLimitConfig {
4144    /// Maximum requests per minute.
4145    pub requests_per_minute: u32,
4146    /// Burst limit.
4147    pub burst_limit: u32,
4148    /// Whether to retry on rate limit.
4149    pub retry_on_rate_limit: bool,
4150    /// Maximum retry attempts.
4151    pub max_retries: u32,
4152    /// Base delay for exponential backoff in milliseconds.
4153    pub base_delay_ms: u64,
4154}
4155
4156impl Default for RateLimitConfig {
4157    fn default() -> Self {
4158        Self {
4159            requests_per_minute: 200,
4160            burst_limit: 50,
4161            retry_on_rate_limit: true,
4162            max_retries: 3,
4163            base_delay_ms: 1000,
4164        }
4165    }
4166}
4167
4168impl RateLimitConfig {
4169    /// Create new rate limit configuration.
4170    #[must_use]
4171    pub fn new() -> Self {
4172        Self::default()
4173    }
4174
4175    /// Set requests per minute.
4176    #[must_use]
4177    pub fn requests_per_minute(mut self, rpm: u32) -> Self {
4178        self.requests_per_minute = rpm;
4179        self
4180    }
4181
4182    /// Set burst limit.
4183    #[must_use]
4184    pub fn burst_limit(mut self, limit: u32) -> Self {
4185        self.burst_limit = limit;
4186        self
4187    }
4188
4189    /// Enable or disable retry on rate limit.
4190    #[must_use]
4191    pub fn retry_on_rate_limit(mut self, retry: bool) -> Self {
4192        self.retry_on_rate_limit = retry;
4193        self
4194    }
4195
4196    /// Set maximum retries.
4197    #[must_use]
4198    pub fn max_retries(mut self, retries: u32) -> Self {
4199        self.max_retries = retries;
4200        self
4201    }
4202
4203    /// Set base delay for exponential backoff in milliseconds.
4204    #[must_use]
4205    pub fn base_delay_ms(mut self, delay: u64) -> Self {
4206        self.base_delay_ms = delay;
4207        self
4208    }
4209}
4210
4211/// Rate limit information from response headers.
4212#[derive(Debug, Clone)]
4213pub struct RateLimitStatus {
4214    /// Remaining requests in current window.
4215    pub remaining: u32,
4216    /// Total limit for current window.
4217    pub limit: u32,
4218    /// Reset timestamp in seconds since epoch.
4219    pub reset_at: u64,
4220}
4221
4222impl RateLimitStatus {
4223    /// Create new rate limit status.
4224    #[must_use]
4225    pub fn new(remaining: u32, limit: u32, reset_at: u64) -> Self {
4226        Self {
4227            remaining,
4228            limit,
4229            reset_at,
4230        }
4231    }
4232
4233    /// Check if rate limited.
4234    #[must_use]
4235    pub fn is_rate_limited(&self) -> bool {
4236        self.remaining == 0
4237    }
4238
4239    /// Get seconds until reset.
4240    #[must_use]
4241    pub fn seconds_until_reset(&self) -> u64 {
4242        let now = std::time::SystemTime::now()
4243            .duration_since(std::time::UNIX_EPOCH)
4244            .unwrap_or_default()
4245            .as_secs();
4246        self.reset_at.saturating_sub(now)
4247    }
4248}
4249
4250/// Request priority for queue management.
4251#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default)]
4252pub enum RequestPriority {
4253    /// Low priority.
4254    Low = 0,
4255    /// Normal priority.
4256    #[default]
4257    Normal = 1,
4258    /// High priority.
4259    High = 2,
4260    /// Critical priority (e.g., order cancellation).
4261    Critical = 3,
4262}
4263
4264// ============================================================================
4265// Margin and Short Selling Types
4266// ============================================================================
4267
4268/// Margin information for an account.
4269#[derive(Debug, Serialize, Deserialize, Clone)]
4270pub struct MarginInfo {
4271    /// Total buying power.
4272    pub buying_power: String,
4273    /// Regulation T buying power.
4274    pub regt_buying_power: String,
4275    /// Day trading buying power.
4276    pub daytrading_buying_power: String,
4277    /// Non-marginable buying power.
4278    pub non_marginable_buying_power: String,
4279    /// Initial margin requirement.
4280    pub initial_margin: String,
4281    /// Maintenance margin requirement.
4282    pub maintenance_margin: String,
4283    /// Last maintenance margin.
4284    pub last_maintenance_margin: String,
4285    /// Special Memorandum Account.
4286    #[serde(skip_serializing_if = "Option::is_none")]
4287    pub sma: Option<String>,
4288}
4289
4290/// Short position information.
4291#[derive(Debug, Serialize, Deserialize, Clone)]
4292pub struct ShortPosition {
4293    /// Symbol.
4294    pub symbol: String,
4295    /// Quantity (negative for short).
4296    pub qty: String,
4297    /// Average entry price.
4298    pub avg_entry_price: String,
4299    /// Current market value.
4300    pub market_value: String,
4301    /// Cost basis.
4302    pub cost_basis: String,
4303    /// Unrealized P&L.
4304    pub unrealized_pl: String,
4305    /// Unrealized P&L percentage.
4306    pub unrealized_plpc: String,
4307    /// Current price.
4308    pub current_price: String,
4309}
4310
4311/// Borrow rate information for short selling.
4312#[derive(Debug, Serialize, Deserialize, Clone)]
4313pub struct BorrowRate {
4314    /// Symbol.
4315    pub symbol: String,
4316    /// Annual borrow rate as percentage.
4317    pub rate: f64,
4318    /// Available quantity to borrow.
4319    pub available_qty: String,
4320    /// Whether easy to borrow.
4321    pub easy_to_borrow: bool,
4322}
4323
4324/// Margin requirement for a position.
4325#[derive(Debug, Serialize, Deserialize, Clone)]
4326pub struct MarginRequirement {
4327    /// Initial margin requirement percentage.
4328    pub initial: f64,
4329    /// Maintenance margin requirement percentage.
4330    pub maintenance: f64,
4331}
4332
4333impl MarginRequirement {
4334    /// Create new margin requirement.
4335    #[must_use]
4336    pub fn new(initial: f64, maintenance: f64) -> Self {
4337        Self {
4338            initial,
4339            maintenance,
4340        }
4341    }
4342
4343    /// Standard margin requirement (50% initial, 25% maintenance).
4344    #[must_use]
4345    pub fn standard() -> Self {
4346        Self::new(0.50, 0.25)
4347    }
4348
4349    /// Calculate initial margin for a position value.
4350    #[must_use]
4351    pub fn calculate_initial_margin(&self, position_value: f64) -> f64 {
4352        position_value * self.initial
4353    }
4354
4355    /// Calculate maintenance margin for a position value.
4356    #[must_use]
4357    pub fn calculate_maintenance_margin(&self, position_value: f64) -> f64 {
4358        position_value * self.maintenance
4359    }
4360}
4361
4362/// Locate request for short selling.
4363#[derive(Debug, Serialize, Deserialize, Clone)]
4364pub struct LocateRequest {
4365    /// Symbol to locate.
4366    pub symbol: String,
4367    /// Quantity to locate.
4368    pub qty: String,
4369}
4370
4371impl LocateRequest {
4372    /// Create new locate request.
4373    #[must_use]
4374    pub fn new(symbol: &str, qty: &str) -> Self {
4375        Self {
4376            symbol: symbol.to_string(),
4377            qty: qty.to_string(),
4378        }
4379    }
4380}
4381
4382/// Locate response for short selling.
4383#[derive(Debug, Serialize, Deserialize, Clone)]
4384pub struct LocateResponse {
4385    /// Symbol.
4386    pub symbol: String,
4387    /// Quantity available.
4388    pub available_qty: String,
4389    /// Borrow rate.
4390    pub rate: f64,
4391    /// Locate ID.
4392    #[serde(skip_serializing_if = "Option::is_none")]
4393    pub locate_id: Option<String>,
4394}
4395
4396/// Pattern day trader status.
4397#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
4398#[serde(rename_all = "lowercase")]
4399pub enum PdtStatus {
4400    /// Not a pattern day trader.
4401    No,
4402    /// Is a pattern day trader.
4403    Yes,
4404    /// PDT flag reset pending.
4405    Pending,
4406}
4407
4408/// Buying power calculation helper.
4409#[derive(Debug, Clone)]
4410pub struct BuyingPowerCalculator {
4411    /// Cash balance.
4412    pub cash: f64,
4413    /// Portfolio value.
4414    pub portfolio_value: f64,
4415    /// Margin multiplier (1.0 for cash, 2.0 for margin, 4.0 for day trading).
4416    pub margin_multiplier: f64,
4417}
4418
4419impl BuyingPowerCalculator {
4420    /// Create new calculator.
4421    #[must_use]
4422    pub fn new(cash: f64, portfolio_value: f64, margin_multiplier: f64) -> Self {
4423        Self {
4424            cash,
4425            portfolio_value,
4426            margin_multiplier,
4427        }
4428    }
4429
4430    /// Calculate buying power.
4431    #[must_use]
4432    pub fn buying_power(&self) -> f64 {
4433        self.cash * self.margin_multiplier
4434    }
4435
4436    /// Calculate maximum position size for a given price.
4437    #[must_use]
4438    pub fn max_shares(&self, price: f64) -> u64 {
4439        if price <= 0.0 {
4440            return 0;
4441        }
4442        (self.buying_power() / price).floor() as u64
4443    }
4444}
4445
4446// ============================================================================
4447// Fractional Trading Types
4448// ============================================================================
4449
4450/// Fractional quantity with precision.
4451#[derive(Debug, Clone, PartialEq)]
4452pub struct FractionalQty {
4453    /// The quantity value.
4454    value: f64,
4455}
4456
4457impl FractionalQty {
4458    /// Minimum fractional quantity.
4459    pub const MIN: f64 = 0.000001;
4460
4461    /// Create new fractional quantity.
4462    ///
4463    /// # Arguments
4464    /// * `value` - The quantity value
4465    ///
4466    /// # Returns
4467    /// Some(FractionalQty) if valid, None if invalid
4468    #[must_use]
4469    pub fn new(value: f64) -> Option<Self> {
4470        if value >= Self::MIN {
4471            Some(Self { value })
4472        } else {
4473            None
4474        }
4475    }
4476
4477    /// Get the value.
4478    #[must_use]
4479    pub fn value(&self) -> f64 {
4480        self.value
4481    }
4482
4483    /// Check if this is a whole number.
4484    #[must_use]
4485    pub fn is_whole(&self) -> bool {
4486        (self.value - self.value.round()).abs() < Self::MIN
4487    }
4488
4489    /// Round to whole shares.
4490    #[must_use]
4491    pub fn to_whole(&self) -> u64 {
4492        self.value.floor() as u64
4493    }
4494}
4495
4496impl std::fmt::Display for FractionalQty {
4497    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
4498        write!(f, "{:.6}", self.value)
4499    }
4500}
4501
4502/// Notional order amount (dollar-based).
4503#[derive(Debug, Serialize, Deserialize, Clone)]
4504pub struct NotionalAmount {
4505    /// The dollar amount.
4506    pub amount: String,
4507}
4508
4509impl NotionalAmount {
4510    /// Minimum notional amount ($1).
4511    pub const MIN: f64 = 1.0;
4512
4513    /// Create new notional amount.
4514    #[must_use]
4515    pub fn new(amount: &str) -> Self {
4516        Self {
4517            amount: amount.to_string(),
4518        }
4519    }
4520
4521    /// Create from f64.
4522    #[must_use]
4523    pub fn from_f64(amount: f64) -> Option<Self> {
4524        if amount >= Self::MIN {
4525            Some(Self {
4526                amount: format!("{:.2}", amount),
4527            })
4528        } else {
4529            None
4530        }
4531    }
4532
4533    /// Parse to f64.
4534    #[must_use]
4535    pub fn to_f64(&self) -> Option<f64> {
4536        self.amount.parse().ok()
4537    }
4538
4539    /// Validate the notional amount.
4540    #[must_use]
4541    pub fn is_valid(&self) -> bool {
4542        self.to_f64().is_some_and(|v| v >= Self::MIN)
4543    }
4544}
4545
4546/// Fractional trading validation.
4547#[derive(Debug, Clone)]
4548pub struct FractionalValidator {
4549    /// Minimum order size.
4550    pub min_order_size: f64,
4551    /// Minimum trade increment.
4552    pub min_trade_increment: f64,
4553}
4554
4555impl Default for FractionalValidator {
4556    fn default() -> Self {
4557        Self {
4558            min_order_size: 0.000001,
4559            min_trade_increment: 0.000001,
4560        }
4561    }
4562}
4563
4564impl FractionalValidator {
4565    /// Create new validator.
4566    #[must_use]
4567    pub fn new(min_order_size: f64, min_trade_increment: f64) -> Self {
4568        Self {
4569            min_order_size,
4570            min_trade_increment,
4571        }
4572    }
4573
4574    /// Validate a fractional quantity.
4575    #[must_use]
4576    pub fn validate_qty(&self, qty: f64) -> bool {
4577        qty >= self.min_order_size
4578    }
4579
4580    /// Validate a notional amount.
4581    #[must_use]
4582    pub fn validate_notional(&self, amount: f64) -> bool {
4583        amount >= NotionalAmount::MIN
4584    }
4585
4586    /// Round quantity to valid increment.
4587    #[must_use]
4588    pub fn round_qty(&self, qty: f64) -> f64 {
4589        let increments = (qty / self.min_trade_increment).floor();
4590        increments * self.min_trade_increment
4591    }
4592}
4593
4594/// Fractional order type restrictions.
4595#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
4596pub enum FractionalOrderRestriction {
4597    /// Only market orders allowed.
4598    #[default]
4599    MarketOnly,
4600    /// Market and limit orders allowed.
4601    MarketAndLimit,
4602    /// All order types allowed.
4603    All,
4604}
4605
4606// ============================================================================
4607// Paper Trading Types
4608// ============================================================================
4609
4610/// Paper trading configuration.
4611#[derive(Debug, Clone)]
4612pub struct PaperTradingConfig {
4613    /// Initial cash balance.
4614    pub initial_cash: f64,
4615    /// Whether to reset on creation.
4616    pub reset_on_create: bool,
4617}
4618
4619impl Default for PaperTradingConfig {
4620    fn default() -> Self {
4621        Self {
4622            initial_cash: 100_000.0,
4623            reset_on_create: false,
4624        }
4625    }
4626}
4627
4628impl PaperTradingConfig {
4629    /// Create new paper trading config.
4630    #[must_use]
4631    pub fn new() -> Self {
4632        Self::default()
4633    }
4634
4635    /// Set initial cash balance.
4636    #[must_use]
4637    pub fn initial_cash(mut self, cash: f64) -> Self {
4638        self.initial_cash = cash;
4639        self
4640    }
4641
4642    /// Enable reset on creation.
4643    #[must_use]
4644    pub fn reset_on_create(mut self, reset: bool) -> Self {
4645        self.reset_on_create = reset;
4646        self
4647    }
4648}
4649
4650/// Trading environment.
4651#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
4652pub enum TradingEnvironment {
4653    /// Live trading environment.
4654    Live,
4655    /// Paper trading environment.
4656    #[default]
4657    Paper,
4658}
4659
4660impl TradingEnvironment {
4661    /// Check if this is paper trading.
4662    #[must_use]
4663    pub fn is_paper(&self) -> bool {
4664        matches!(self, Self::Paper)
4665    }
4666
4667    /// Check if this is live trading.
4668    #[must_use]
4669    pub fn is_live(&self) -> bool {
4670        matches!(self, Self::Live)
4671    }
4672
4673    /// Get the base URL for this environment.
4674    #[must_use]
4675    pub fn base_url(&self) -> &'static str {
4676        match self {
4677            Self::Live => "https://api.alpaca.markets",
4678            Self::Paper => "https://paper-api.alpaca.markets",
4679        }
4680    }
4681
4682    /// Get the data URL for this environment.
4683    #[must_use]
4684    pub fn data_url(&self) -> &'static str {
4685        "https://data.alpaca.markets"
4686    }
4687
4688    /// Detect environment from API key prefix.
4689    #[must_use]
4690    pub fn from_api_key(api_key: &str) -> Self {
4691        if api_key.starts_with("PK") {
4692            Self::Paper
4693        } else {
4694            Self::Live
4695        }
4696    }
4697}
4698
4699/// Paper trading account reset request.
4700#[derive(Debug, Serialize, Deserialize, Clone, Default)]
4701pub struct ResetAccountRequest {
4702    /// New initial cash balance (optional).
4703    #[serde(skip_serializing_if = "Option::is_none")]
4704    pub cash: Option<String>,
4705}
4706
4707impl ResetAccountRequest {
4708    /// Create new reset request.
4709    #[must_use]
4710    pub fn new() -> Self {
4711        Self::default()
4712    }
4713
4714    /// Set initial cash.
4715    #[must_use]
4716    pub fn cash(mut self, cash: &str) -> Self {
4717        self.cash = Some(cash.to_string());
4718        self
4719    }
4720}
4721
4722/// Environment safety guard.
4723#[derive(Debug, Clone)]
4724pub struct EnvironmentGuard {
4725    /// Current environment.
4726    environment: TradingEnvironment,
4727    /// Whether live trading is allowed.
4728    allow_live: bool,
4729}
4730
4731impl EnvironmentGuard {
4732    /// Create new guard for paper trading only.
4733    #[must_use]
4734    pub fn paper_only() -> Self {
4735        Self {
4736            environment: TradingEnvironment::Paper,
4737            allow_live: false,
4738        }
4739    }
4740
4741    /// Create new guard allowing live trading.
4742    #[must_use]
4743    pub fn allow_live(environment: TradingEnvironment) -> Self {
4744        Self {
4745            environment,
4746            allow_live: true,
4747        }
4748    }
4749
4750    /// Check if current operation is allowed.
4751    #[must_use]
4752    pub fn is_allowed(&self) -> bool {
4753        self.allow_live || self.environment.is_paper()
4754    }
4755
4756    /// Get current environment.
4757    #[must_use]
4758    pub fn environment(&self) -> TradingEnvironment {
4759        self.environment
4760    }
4761}
4762
4763// ============================================================================
4764// Calendar and Clock Types
4765// ============================================================================
4766
4767/// Market session type.
4768#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
4769#[serde(rename_all = "snake_case")]
4770pub enum MarketSession {
4771    /// Pre-market session (4:00 AM - 9:30 AM ET).
4772    PreMarket,
4773    /// Regular market session (9:30 AM - 4:00 PM ET).
4774    Regular,
4775    /// After-hours session (4:00 PM - 8:00 PM ET).
4776    AfterHours,
4777    /// Market is closed.
4778    Closed,
4779}
4780
4781impl MarketSession {
4782    /// Check if trading is allowed in this session.
4783    #[must_use]
4784    pub fn is_trading_allowed(&self) -> bool {
4785        !matches!(self, Self::Closed)
4786    }
4787
4788    /// Check if this is the regular session.
4789    #[must_use]
4790    pub fn is_regular(&self) -> bool {
4791        matches!(self, Self::Regular)
4792    }
4793}
4794
4795/// Enhanced calendar day information.
4796#[derive(Debug, Serialize, Deserialize, Clone)]
4797pub struct CalendarDay {
4798    /// Trading date.
4799    pub date: String,
4800    /// Market open time.
4801    pub open: String,
4802    /// Market close time.
4803    pub close: String,
4804    /// Settlement date.
4805    #[serde(skip_serializing_if = "Option::is_none")]
4806    pub settlement_date: Option<String>,
4807    /// Pre-market session open time.
4808    #[serde(skip_serializing_if = "Option::is_none")]
4809    pub session_open: Option<String>,
4810    /// After-hours session close time.
4811    #[serde(skip_serializing_if = "Option::is_none")]
4812    pub session_close: Option<String>,
4813}
4814
4815/// Enhanced market clock information.
4816#[derive(Debug, Serialize, Deserialize, Clone)]
4817pub struct MarketClock {
4818    /// Current timestamp.
4819    pub timestamp: String,
4820    /// Whether the market is open.
4821    pub is_open: bool,
4822    /// Next market open time.
4823    pub next_open: String,
4824    /// Next market close time.
4825    pub next_close: String,
4826}
4827
4828impl MarketClock {
4829    /// Get the current market session.
4830    #[must_use]
4831    pub fn current_session(&self) -> MarketSession {
4832        if self.is_open {
4833            MarketSession::Regular
4834        } else {
4835            MarketSession::Closed
4836        }
4837    }
4838}
4839
4840/// Calendar query parameters.
4841#[derive(Debug, Serialize, Clone, Default)]
4842pub struct CalendarParams {
4843    /// Start date (YYYY-MM-DD).
4844    #[serde(skip_serializing_if = "Option::is_none")]
4845    pub start: Option<String>,
4846    /// End date (YYYY-MM-DD).
4847    #[serde(skip_serializing_if = "Option::is_none")]
4848    pub end: Option<String>,
4849}
4850
4851impl CalendarParams {
4852    /// Create new calendar params.
4853    #[must_use]
4854    pub fn new() -> Self {
4855        Self::default()
4856    }
4857
4858    /// Set start date.
4859    #[must_use]
4860    pub fn start(mut self, date: &str) -> Self {
4861        self.start = Some(date.to_string());
4862        self
4863    }
4864
4865    /// Set end date.
4866    #[must_use]
4867    pub fn end(mut self, date: &str) -> Self {
4868        self.end = Some(date.to_string());
4869        self
4870    }
4871}
4872
4873/// Trading day utilities.
4874#[derive(Debug, Clone)]
4875pub struct TradingDay {
4876    /// The date string (YYYY-MM-DD).
4877    pub date: String,
4878    /// Whether this is a trading day.
4879    pub is_trading_day: bool,
4880}
4881
4882impl TradingDay {
4883    /// Create a new trading day.
4884    #[must_use]
4885    pub fn new(date: &str, is_trading_day: bool) -> Self {
4886        Self {
4887            date: date.to_string(),
4888            is_trading_day,
4889        }
4890    }
4891
4892    /// Create a trading day (market open).
4893    #[must_use]
4894    pub fn trading(date: &str) -> Self {
4895        Self::new(date, true)
4896    }
4897
4898    /// Create a non-trading day (market closed).
4899    #[must_use]
4900    pub fn non_trading(date: &str) -> Self {
4901        Self::new(date, false)
4902    }
4903}
4904
4905// ============================================================================
4906// FIX Protocol Types
4907// ============================================================================
4908
4909/// FIX protocol version.
4910#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
4911pub enum FixVersion {
4912    /// FIX 4.2.
4913    #[serde(rename = "FIX.4.2")]
4914    #[default]
4915    Fix42,
4916    /// FIX 4.4.
4917    #[serde(rename = "FIX.4.4")]
4918    Fix44,
4919}
4920
4921impl std::fmt::Display for FixVersion {
4922    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
4923        match self {
4924            Self::Fix42 => write!(f, "FIX.4.2"),
4925            Self::Fix44 => write!(f, "FIX.4.4"),
4926        }
4927    }
4928}
4929
4930/// FIX session configuration.
4931#[derive(Debug, Clone)]
4932pub struct FixSessionConfig {
4933    /// FIX protocol version.
4934    pub version: FixVersion,
4935    /// Sender CompID.
4936    pub sender_comp_id: String,
4937    /// Target CompID.
4938    pub target_comp_id: String,
4939    /// Host address.
4940    pub host: String,
4941    /// Port number.
4942    pub port: u16,
4943    /// Heartbeat interval in seconds.
4944    pub heartbeat_interval: u32,
4945    /// Enable message logging.
4946    pub enable_logging: bool,
4947}
4948
4949impl FixSessionConfig {
4950    /// Create new FIX session config.
4951    #[must_use]
4952    pub fn new(sender_comp_id: &str, target_comp_id: &str, host: &str, port: u16) -> Self {
4953        Self {
4954            version: FixVersion::default(),
4955            sender_comp_id: sender_comp_id.to_string(),
4956            target_comp_id: target_comp_id.to_string(),
4957            host: host.to_string(),
4958            port,
4959            heartbeat_interval: 30,
4960            enable_logging: true,
4961        }
4962    }
4963
4964    /// Set FIX version.
4965    #[must_use]
4966    pub fn version(mut self, version: FixVersion) -> Self {
4967        self.version = version;
4968        self
4969    }
4970
4971    /// Set heartbeat interval in seconds.
4972    #[must_use]
4973    pub fn heartbeat_interval(mut self, seconds: u32) -> Self {
4974        self.heartbeat_interval = seconds;
4975        self
4976    }
4977
4978    /// Enable or disable logging.
4979    #[must_use]
4980    pub fn enable_logging(mut self, enable: bool) -> Self {
4981        self.enable_logging = enable;
4982        self
4983    }
4984}
4985
4986/// FIX message type.
4987#[derive(Debug, Clone, Copy, PartialEq, Eq)]
4988pub enum FixMsgType {
4989    /// Heartbeat (0).
4990    Heartbeat,
4991    /// Logon (A).
4992    Logon,
4993    /// Logout (5).
4994    Logout,
4995    /// New Order Single (D).
4996    NewOrderSingle,
4997    /// Order Cancel Request (F).
4998    OrderCancelRequest,
4999    /// Order Cancel/Replace Request (G).
5000    OrderCancelReplaceRequest,
5001    /// Execution Report (8).
5002    ExecutionReport,
5003    /// Order Cancel Reject (9).
5004    OrderCancelReject,
5005    /// Market Data Request (V).
5006    MarketDataRequest,
5007    /// Market Data Snapshot (W).
5008    MarketDataSnapshot,
5009}
5010
5011impl FixMsgType {
5012    /// Get the FIX message type tag value.
5013    #[must_use]
5014    pub fn tag_value(&self) -> &'static str {
5015        match self {
5016            Self::Heartbeat => "0",
5017            Self::Logon => "A",
5018            Self::Logout => "5",
5019            Self::NewOrderSingle => "D",
5020            Self::OrderCancelRequest => "F",
5021            Self::OrderCancelReplaceRequest => "G",
5022            Self::ExecutionReport => "8",
5023            Self::OrderCancelReject => "9",
5024            Self::MarketDataRequest => "V",
5025            Self::MarketDataSnapshot => "W",
5026        }
5027    }
5028}
5029
5030/// FIX session state.
5031#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
5032pub enum FixSessionState {
5033    /// Disconnected.
5034    #[default]
5035    Disconnected,
5036    /// Connecting.
5037    Connecting,
5038    /// Logged on.
5039    LoggedOn,
5040    /// Logging out.
5041    LoggingOut,
5042}
5043
5044/// FIX sequence numbers.
5045#[derive(Debug, Clone, Default)]
5046pub struct FixSequenceNumbers {
5047    /// Outgoing message sequence number.
5048    pub outgoing: u64,
5049    /// Incoming message sequence number.
5050    pub incoming: u64,
5051}
5052
5053impl FixSequenceNumbers {
5054    /// Create new sequence numbers.
5055    #[must_use]
5056    pub fn new() -> Self {
5057        Self::default()
5058    }
5059
5060    /// Increment outgoing sequence number.
5061    pub fn next_outgoing(&mut self) -> u64 {
5062        self.outgoing += 1;
5063        self.outgoing
5064    }
5065
5066    /// Increment incoming sequence number.
5067    pub fn next_incoming(&mut self) -> u64 {
5068        self.incoming += 1;
5069        self.incoming
5070    }
5071
5072    /// Reset sequence numbers.
5073    pub fn reset(&mut self) {
5074        self.outgoing = 0;
5075        self.incoming = 0;
5076    }
5077}
5078
5079// ============================================================================
5080// Statements and Confirmations Types
5081// ============================================================================
5082
5083/// Statement document type.
5084#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
5085#[serde(rename_all = "snake_case")]
5086pub enum StatementType {
5087    /// Monthly account statement.
5088    AccountStatement,
5089    /// Trade confirmation.
5090    TradeConfirmation,
5091    /// Tax document.
5092    TaxDocument,
5093}
5094
5095/// Tax form type.
5096#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
5097pub enum TaxFormType {
5098    /// Form 1099-B (proceeds from broker transactions).
5099    #[serde(rename = "1099-B")]
5100    Form1099B,
5101    /// Form 1099-DIV (dividends and distributions).
5102    #[serde(rename = "1099-DIV")]
5103    Form1099Div,
5104    /// Form 1099-INT (interest income).
5105    #[serde(rename = "1099-INT")]
5106    Form1099Int,
5107}
5108
5109/// Account document/statement.
5110#[derive(Debug, Serialize, Deserialize, Clone)]
5111pub struct AccountDocument {
5112    /// Document ID.
5113    pub id: String,
5114    /// Account ID.
5115    #[serde(skip_serializing_if = "Option::is_none")]
5116    pub account_id: Option<String>,
5117    /// Document type.
5118    pub document_type: StatementType,
5119    /// Document date.
5120    pub date: String,
5121    /// Download URL.
5122    #[serde(skip_serializing_if = "Option::is_none")]
5123    pub url: Option<String>,
5124}
5125
5126/// Trade confirmation document.
5127#[derive(Debug, Serialize, Deserialize, Clone)]
5128pub struct TradeConfirmation {
5129    /// Confirmation ID.
5130    pub id: String,
5131    /// Trade date.
5132    pub trade_date: String,
5133    /// Settlement date.
5134    pub settlement_date: String,
5135    /// Symbol.
5136    pub symbol: String,
5137    /// Side (buy/sell).
5138    pub side: String,
5139    /// Quantity.
5140    pub qty: String,
5141    /// Price.
5142    pub price: String,
5143    /// Total amount.
5144    pub amount: String,
5145}
5146
5147/// Tax document.
5148#[derive(Debug, Serialize, Deserialize, Clone)]
5149pub struct TaxDocument {
5150    /// Document ID.
5151    pub id: String,
5152    /// Tax year.
5153    pub tax_year: u16,
5154    /// Form type.
5155    pub form_type: TaxFormType,
5156    /// Download URL.
5157    #[serde(skip_serializing_if = "Option::is_none")]
5158    pub url: Option<String>,
5159}
5160
5161/// Document query parameters.
5162#[derive(Debug, Serialize, Clone, Default)]
5163pub struct DocumentParams {
5164    /// Start date (YYYY-MM-DD).
5165    #[serde(skip_serializing_if = "Option::is_none")]
5166    pub start: Option<String>,
5167    /// End date (YYYY-MM-DD).
5168    #[serde(skip_serializing_if = "Option::is_none")]
5169    pub end: Option<String>,
5170    /// Document type filter.
5171    #[serde(skip_serializing_if = "Option::is_none")]
5172    pub document_type: Option<StatementType>,
5173}
5174
5175impl DocumentParams {
5176    /// Create new document params.
5177    #[must_use]
5178    pub fn new() -> Self {
5179        Self::default()
5180    }
5181
5182    /// Set start date.
5183    #[must_use]
5184    pub fn start(mut self, date: &str) -> Self {
5185        self.start = Some(date.to_string());
5186        self
5187    }
5188
5189    /// Set end date.
5190    #[must_use]
5191    pub fn end(mut self, date: &str) -> Self {
5192        self.end = Some(date.to_string());
5193        self
5194    }
5195
5196    /// Set document type filter.
5197    #[must_use]
5198    pub fn document_type(mut self, doc_type: StatementType) -> Self {
5199        self.document_type = Some(doc_type);
5200        self
5201    }
5202}
5203
5204// ============================================================================
5205// Local Currency Trading Types
5206// ============================================================================
5207
5208/// Supported currencies for Local Currency Trading.
5209#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
5210#[serde(rename_all = "UPPERCASE")]
5211#[derive(Default)]
5212pub enum Currency {
5213    /// US Dollar.
5214    #[default]
5215    Usd,
5216    /// Euro.
5217    Eur,
5218    /// British Pound.
5219    Gbp,
5220    /// Canadian Dollar.
5221    Cad,
5222    /// Australian Dollar.
5223    Aud,
5224    /// Japanese Yen.
5225    Jpy,
5226    /// Swiss Franc.
5227    Chf,
5228}
5229
5230impl std::fmt::Display for Currency {
5231    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
5232        match self {
5233            Self::Usd => write!(f, "USD"),
5234            Self::Eur => write!(f, "EUR"),
5235            Self::Gbp => write!(f, "GBP"),
5236            Self::Cad => write!(f, "CAD"),
5237            Self::Aud => write!(f, "AUD"),
5238            Self::Jpy => write!(f, "JPY"),
5239            Self::Chf => write!(f, "CHF"),
5240        }
5241    }
5242}
5243
5244/// Exchange rate between two currencies.
5245#[derive(Debug, Serialize, Deserialize, Clone)]
5246pub struct ExchangeRate {
5247    /// Base currency.
5248    pub base: Currency,
5249    /// Quote currency.
5250    pub quote: Currency,
5251    /// Exchange rate.
5252    pub rate: f64,
5253    /// Timestamp.
5254    pub timestamp: String,
5255}
5256
5257impl ExchangeRate {
5258    /// Create new exchange rate.
5259    #[must_use]
5260    pub fn new(base: Currency, quote: Currency, rate: f64) -> Self {
5261        Self {
5262            base,
5263            quote,
5264            rate,
5265            timestamp: String::new(),
5266        }
5267    }
5268
5269    /// Convert amount from base to quote currency.
5270    #[must_use]
5271    pub fn convert(&self, amount: f64) -> f64 {
5272        amount * self.rate
5273    }
5274
5275    /// Get inverse rate.
5276    #[must_use]
5277    pub fn inverse(&self) -> f64 {
5278        if self.rate != 0.0 {
5279            1.0 / self.rate
5280        } else {
5281            0.0
5282        }
5283    }
5284}
5285
5286/// Currency pair for exchange rate queries.
5287#[derive(Debug, Clone)]
5288pub struct CurrencyPair {
5289    /// Base currency.
5290    pub base: Currency,
5291    /// Quote currency.
5292    pub quote: Currency,
5293}
5294
5295impl CurrencyPair {
5296    /// Create new currency pair.
5297    #[must_use]
5298    pub fn new(base: Currency, quote: Currency) -> Self {
5299        Self { base, quote }
5300    }
5301
5302    /// Get pair string (e.g., "EUR/USD").
5303    #[must_use]
5304    pub fn as_string(&self) -> String {
5305        format!("{}/{}", self.base, self.quote)
5306    }
5307}
5308
5309/// Local currency position values.
5310#[derive(Debug, Serialize, Deserialize, Clone)]
5311pub struct LctPosition {
5312    /// Symbol.
5313    pub symbol: String,
5314    /// Quantity.
5315    pub qty: String,
5316    /// Market value in local currency.
5317    pub market_value_local: String,
5318    /// Cost basis in local currency.
5319    pub cost_basis_local: String,
5320    /// Unrealized P&L in local currency.
5321    pub unrealized_pl_local: String,
5322    /// Local currency.
5323    pub currency: Currency,
5324}
5325
5326// ============================================================================
5327// IRA Account Types
5328// ============================================================================
5329
5330/// IRA account type.
5331#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
5332#[serde(rename_all = "snake_case")]
5333pub enum IraAccountType {
5334    /// Traditional IRA.
5335    Traditional,
5336    /// Roth IRA.
5337    Roth,
5338    /// SEP IRA (Simplified Employee Pension).
5339    Sep,
5340    /// SIMPLE IRA (Savings Incentive Match Plan for Employees).
5341    Simple,
5342}
5343
5344impl std::fmt::Display for IraAccountType {
5345    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
5346        match self {
5347            Self::Traditional => write!(f, "Traditional"),
5348            Self::Roth => write!(f, "Roth"),
5349            Self::Sep => write!(f, "SEP"),
5350            Self::Simple => write!(f, "SIMPLE"),
5351        }
5352    }
5353}
5354
5355/// IRA contribution.
5356#[derive(Debug, Serialize, Deserialize, Clone)]
5357pub struct IraContribution {
5358    /// Contribution ID.
5359    pub id: String,
5360    /// Account ID.
5361    pub account_id: String,
5362    /// Contribution amount.
5363    pub amount: String,
5364    /// Tax year.
5365    pub tax_year: u16,
5366    /// Contribution date.
5367    pub date: String,
5368    /// Contribution type (regular, rollover, etc.).
5369    #[serde(skip_serializing_if = "Option::is_none")]
5370    pub contribution_type: Option<String>,
5371}
5372
5373/// IRA distribution.
5374#[derive(Debug, Serialize, Deserialize, Clone)]
5375pub struct IraDistribution {
5376    /// Distribution ID.
5377    pub id: String,
5378    /// Account ID.
5379    pub account_id: String,
5380    /// Distribution amount.
5381    pub amount: String,
5382    /// Distribution date.
5383    pub date: String,
5384    /// Distribution reason.
5385    #[serde(skip_serializing_if = "Option::is_none")]
5386    pub reason: Option<String>,
5387    /// Federal withholding amount.
5388    #[serde(skip_serializing_if = "Option::is_none")]
5389    pub federal_withholding: Option<String>,
5390    /// State withholding amount.
5391    #[serde(skip_serializing_if = "Option::is_none")]
5392    pub state_withholding: Option<String>,
5393}
5394
5395/// IRA beneficiary.
5396#[derive(Debug, Serialize, Deserialize, Clone)]
5397pub struct IraBeneficiary {
5398    /// Beneficiary ID.
5399    pub id: String,
5400    /// Account ID.
5401    pub account_id: String,
5402    /// Beneficiary name.
5403    pub name: String,
5404    /// Beneficiary type (primary, contingent).
5405    pub beneficiary_type: String,
5406    /// Percentage share.
5407    pub percentage: f64,
5408    /// Relationship to account holder.
5409    #[serde(skip_serializing_if = "Option::is_none")]
5410    pub relationship: Option<String>,
5411}
5412
5413/// IRA contribution request.
5414#[derive(Debug, Serialize, Clone)]
5415pub struct CreateIraContributionRequest {
5416    /// Contribution amount.
5417    pub amount: String,
5418    /// Tax year.
5419    pub tax_year: u16,
5420}
5421
5422impl CreateIraContributionRequest {
5423    /// Create new contribution request.
5424    #[must_use]
5425    pub fn new(amount: &str, tax_year: u16) -> Self {
5426        Self {
5427            amount: amount.to_string(),
5428            tax_year,
5429        }
5430    }
5431}
5432
5433/// IRA distribution request.
5434#[derive(Debug, Serialize, Clone)]
5435pub struct CreateIraDistributionRequest {
5436    /// Distribution amount.
5437    pub amount: String,
5438    /// Distribution reason.
5439    #[serde(skip_serializing_if = "Option::is_none")]
5440    pub reason: Option<String>,
5441}
5442
5443impl CreateIraDistributionRequest {
5444    /// Create new distribution request.
5445    #[must_use]
5446    pub fn new(amount: &str) -> Self {
5447        Self {
5448            amount: amount.to_string(),
5449            reason: None,
5450        }
5451    }
5452
5453    /// Set distribution reason.
5454    #[must_use]
5455    pub fn reason(mut self, reason: &str) -> Self {
5456        self.reason = Some(reason.to_string());
5457        self
5458    }
5459}
5460
5461#[cfg(test)]
5462mod tests {
5463    use super::*;
5464
5465    #[test]
5466    fn test_take_profit_new() {
5467        let tp = TakeProfit::new("150.00");
5468        assert_eq!(tp.limit_price, "150.00");
5469    }
5470
5471    #[test]
5472    fn test_stop_loss_new() {
5473        let sl = StopLoss::new("95.00");
5474        assert_eq!(sl.stop_price, "95.00");
5475        assert!(sl.limit_price.is_none());
5476    }
5477
5478    #[test]
5479    fn test_stop_loss_with_limit() {
5480        let sl = StopLoss::with_limit("95.00", "94.50");
5481        assert_eq!(sl.stop_price, "95.00");
5482        assert_eq!(sl.limit_price, Some("94.50".to_string()));
5483    }
5484
5485    #[test]
5486    fn test_time_in_force_serialization() {
5487        let tif = TimeInForce::Gtc;
5488        let json = serde_json::to_string(&tif).unwrap();
5489        assert_eq!(json, "\"gtc\"");
5490
5491        let tif = TimeInForce::Gtd;
5492        let json = serde_json::to_string(&tif).unwrap();
5493        assert_eq!(json, "\"gtd\"");
5494    }
5495
5496    #[test]
5497    fn test_time_in_force_deserialization() {
5498        let tif: TimeInForce = serde_json::from_str("\"day\"").unwrap();
5499        assert_eq!(tif, TimeInForce::Day);
5500
5501        let tif: TimeInForce = serde_json::from_str("\"gtc\"").unwrap();
5502        assert_eq!(tif, TimeInForce::Gtc);
5503
5504        let tif: TimeInForce = serde_json::from_str("\"ioc\"").unwrap();
5505        assert_eq!(tif, TimeInForce::Ioc);
5506    }
5507
5508    #[test]
5509    fn test_order_class_serialization() {
5510        let oc = OrderClass::Bracket;
5511        let json = serde_json::to_string(&oc).unwrap();
5512        assert_eq!(json, "\"bracket\"");
5513
5514        let oc = OrderClass::Oco;
5515        let json = serde_json::to_string(&oc).unwrap();
5516        assert_eq!(json, "\"oco\"");
5517
5518        let oc = OrderClass::Oto;
5519        let json = serde_json::to_string(&oc).unwrap();
5520        assert_eq!(json, "\"oto\"");
5521    }
5522
5523    #[test]
5524    fn test_position_intent_serialization() {
5525        let pi = PositionIntent::BuyToOpen;
5526        let json = serde_json::to_string(&pi).unwrap();
5527        assert_eq!(json, "\"buy_to_open\"");
5528
5529        let pi = PositionIntent::SellToClose;
5530        let json = serde_json::to_string(&pi).unwrap();
5531        assert_eq!(json, "\"sell_to_close\"");
5532    }
5533
5534    #[test]
5535    fn test_order_query_status_serialization() {
5536        let status = OrderQueryStatus::Open;
5537        let json = serde_json::to_string(&status).unwrap();
5538        assert_eq!(json, "\"open\"");
5539
5540        let status = OrderQueryStatus::All;
5541        let json = serde_json::to_string(&status).unwrap();
5542        assert_eq!(json, "\"all\"");
5543    }
5544
5545    #[test]
5546    fn test_sort_direction_serialization() {
5547        let dir = SortDirection::Asc;
5548        let json = serde_json::to_string(&dir).unwrap();
5549        assert_eq!(json, "\"asc\"");
5550
5551        let dir = SortDirection::Desc;
5552        let json = serde_json::to_string(&dir).unwrap();
5553        assert_eq!(json, "\"desc\"");
5554    }
5555
5556    #[test]
5557    fn test_order_side_serialization() {
5558        let side = OrderSide::Buy;
5559        let json = serde_json::to_string(&side).unwrap();
5560        assert_eq!(json, "\"buy\"");
5561
5562        let side = OrderSide::Sell;
5563        let json = serde_json::to_string(&side).unwrap();
5564        assert_eq!(json, "\"sell\"");
5565    }
5566
5567    #[test]
5568    fn test_order_type_serialization() {
5569        let ot = OrderType::Market;
5570        let json = serde_json::to_string(&ot).unwrap();
5571        assert_eq!(json, "\"market\"");
5572
5573        let ot = OrderType::StopLimit;
5574        let json = serde_json::to_string(&ot).unwrap();
5575        assert_eq!(json, "\"stop_limit\"");
5576
5577        let ot = OrderType::TrailingStop;
5578        let json = serde_json::to_string(&ot).unwrap();
5579        assert_eq!(json, "\"trailing_stop\"");
5580    }
5581
5582    #[test]
5583    fn test_option_type_serialization() {
5584        let ot = OptionType::Call;
5585        let json = serde_json::to_string(&ot).unwrap();
5586        assert_eq!(json, "\"call\"");
5587
5588        let ot = OptionType::Put;
5589        let json = serde_json::to_string(&ot).unwrap();
5590        assert_eq!(json, "\"put\"");
5591    }
5592
5593    #[test]
5594    fn test_option_style_serialization() {
5595        let style = OptionStyle::American;
5596        let json = serde_json::to_string(&style).unwrap();
5597        assert_eq!(json, "\"american\"");
5598
5599        let style = OptionStyle::European;
5600        let json = serde_json::to_string(&style).unwrap();
5601        assert_eq!(json, "\"european\"");
5602    }
5603
5604    #[test]
5605    fn test_options_approval_level_serialization() {
5606        let level = OptionsApprovalLevel::Level1;
5607        let json = serde_json::to_string(&level).unwrap();
5608        assert_eq!(json, "\"1\"");
5609
5610        let level = OptionsApprovalLevel::Level3;
5611        let json = serde_json::to_string(&level).unwrap();
5612        assert_eq!(json, "\"3\"");
5613    }
5614
5615    #[test]
5616    fn test_option_contract_params_builder() {
5617        let params = OptionContractParams::new()
5618            .underlying_symbol("AAPL")
5619            .expiration_date("2024-03-15")
5620            .option_type(OptionType::Call)
5621            .limit(10);
5622
5623        assert_eq!(params.underlying_symbol, Some("AAPL".to_string()));
5624        assert_eq!(params.expiration_date, Some("2024-03-15".to_string()));
5625        assert_eq!(params.option_type, Some(OptionType::Call));
5626        assert_eq!(params.limit, Some(10));
5627    }
5628
5629    #[test]
5630    fn test_option_bars_params_builder() {
5631        let params = OptionBarsParams::new("AAPL240315C00150000")
5632            .timeframe("1Day")
5633            .time_range("2024-01-01", "2024-03-01")
5634            .limit(100);
5635
5636        assert_eq!(params.symbols, Some("AAPL240315C00150000".to_string()));
5637        assert_eq!(params.timeframe, Some("1Day".to_string()));
5638        assert_eq!(params.start, Some("2024-01-01".to_string()));
5639        assert_eq!(params.end, Some("2024-03-01".to_string()));
5640        assert_eq!(params.limit, Some(100));
5641    }
5642
5643    #[test]
5644    fn test_data_feed_serialization() {
5645        let feed = DataFeed::Sip;
5646        let json = serde_json::to_string(&feed).unwrap();
5647        assert_eq!(json, "\"sip\"");
5648
5649        let feed = DataFeed::Iex;
5650        let json = serde_json::to_string(&feed).unwrap();
5651        assert_eq!(json, "\"iex\"");
5652    }
5653
5654    #[test]
5655    fn test_corporate_action_type_serialization() {
5656        let action = CorporateActionType::Dividend;
5657        let json = serde_json::to_string(&action).unwrap();
5658        assert_eq!(json, "\"dividend\"");
5659
5660        let action = CorporateActionType::Split;
5661        let json = serde_json::to_string(&action).unwrap();
5662        assert_eq!(json, "\"split\"");
5663    }
5664
5665    #[test]
5666    fn test_multi_bars_params_builder() {
5667        let params = MultiBarsParams::new("AAPL,MSFT,GOOGL")
5668            .timeframe("1Day")
5669            .time_range("2024-01-01", "2024-03-01")
5670            .feed(DataFeed::Sip)
5671            .limit(100);
5672
5673        assert_eq!(params.symbols, Some("AAPL,MSFT,GOOGL".to_string()));
5674        assert_eq!(params.timeframe, Some("1Day".to_string()));
5675        assert_eq!(params.feed, Some(DataFeed::Sip));
5676        assert_eq!(params.limit, Some(100));
5677    }
5678
5679    #[test]
5680    fn test_corporate_actions_params_builder() {
5681        let params = CorporateActionsParams::new()
5682            .symbols("AAPL,MSFT")
5683            .types("dividend,split")
5684            .date_range("2024-01-01", "2024-12-31")
5685            .limit(50);
5686
5687        assert_eq!(params.symbols, Some("AAPL,MSFT".to_string()));
5688        assert_eq!(params.types, Some("dividend,split".to_string()));
5689        assert_eq!(params.start, Some("2024-01-01".to_string()));
5690        assert_eq!(params.limit, Some(50));
5691    }
5692
5693    #[test]
5694    fn test_broker_account_status_serialization() {
5695        let status = BrokerAccountStatus::Active;
5696        let json = serde_json::to_string(&status).unwrap();
5697        assert_eq!(json, "\"ACTIVE\"");
5698
5699        let status = BrokerAccountStatus::Onboarding;
5700        let json = serde_json::to_string(&status).unwrap();
5701        assert_eq!(json, "\"ONBOARDING\"");
5702    }
5703
5704    #[test]
5705    fn test_agreement_type_serialization() {
5706        let agreement = AgreementType::CustomerAgreement;
5707        let json = serde_json::to_string(&agreement).unwrap();
5708        assert_eq!(json, "\"customer_agreement\"");
5709    }
5710
5711    #[test]
5712    fn test_contact_builder() {
5713        let contact = Contact::new("test@example.com", "New York", "10001", "USA")
5714            .phone("+1234567890")
5715            .street("123 Main St")
5716            .state("NY");
5717
5718        assert_eq!(contact.email_address, "test@example.com");
5719        assert_eq!(contact.city, "New York");
5720        assert_eq!(contact.phone_number, Some("+1234567890".to_string()));
5721        assert_eq!(contact.state, Some("NY".to_string()));
5722    }
5723
5724    #[test]
5725    fn test_identity_builder() {
5726        let identity = Identity::new("John", "Doe", "1990-01-15")
5727            .tax_id("123-45-6789", TaxIdType::UsaSsn)
5728            .citizenship("USA");
5729
5730        assert_eq!(identity.given_name, "John");
5731        assert_eq!(identity.family_name, "Doe");
5732        assert_eq!(identity.tax_id, Some("123-45-6789".to_string()));
5733        assert_eq!(identity.tax_id_type, Some(TaxIdType::UsaSsn));
5734    }
5735
5736    #[test]
5737    fn test_disclosures_builder() {
5738        let disclosures = Disclosures::new().control_person(false).employment(
5739            "employed",
5740            "Acme Corp",
5741            "Engineer",
5742        );
5743
5744        assert!(!disclosures.is_control_person);
5745        assert_eq!(disclosures.employer_name, Some("Acme Corp".to_string()));
5746    }
5747
5748    #[test]
5749    fn test_transfer_status_serialization() {
5750        let status = TransferStatus::Complete;
5751        let json = serde_json::to_string(&status).unwrap();
5752        assert_eq!(json, "\"COMPLETE\"");
5753    }
5754
5755    #[test]
5756    fn test_create_ach_relationship_request() {
5757        let request = CreateAchRelationshipRequest::new(
5758            "John Doe",
5759            BankAccountType::Checking,
5760            "123456789",
5761            "021000021",
5762        )
5763        .nickname("Primary Account");
5764
5765        assert_eq!(request.account_owner_name, "John Doe");
5766        assert_eq!(request.bank_account_type, BankAccountType::Checking);
5767        assert_eq!(request.nickname, Some("Primary Account".to_string()));
5768    }
5769
5770    #[test]
5771    fn test_create_transfer_request_ach() {
5772        let request = CreateTransferRequest::ach("rel-123", "1000.00", TransferDirection::Incoming);
5773
5774        assert_eq!(request.transfer_type, TransferType::Ach);
5775        assert_eq!(request.relationship_id, Some("rel-123".to_string()));
5776        assert_eq!(request.amount, "1000.00");
5777        assert_eq!(request.direction, TransferDirection::Incoming);
5778    }
5779
5780    #[test]
5781    fn test_create_journal_request_cash() {
5782        let request =
5783            CreateJournalRequest::cash("acc-from", "acc-to", "500.00").description("Test transfer");
5784
5785        assert_eq!(request.entry_type, JournalEntryType::Jnlc);
5786        assert_eq!(request.amount, Some("500.00".to_string()));
5787        assert_eq!(request.description, Some("Test transfer".to_string()));
5788    }
5789
5790    #[test]
5791    fn test_crypto_chain_serialization() {
5792        let chain = CryptoChain::Eth;
5793        let json = serde_json::to_string(&chain).unwrap();
5794        assert_eq!(json, "\"ETH\"");
5795    }
5796
5797    #[test]
5798    fn test_crypto_transfer_status_serialization() {
5799        let status = CryptoTransferStatus::Complete;
5800        let json = serde_json::to_string(&status).unwrap();
5801        assert_eq!(json, "\"COMPLETE\"");
5802    }
5803
5804    #[test]
5805    fn test_create_crypto_whitelist_request() {
5806        let request =
5807            CreateCryptoWhitelistRequest::new("BTC", "bc1qxy2kgdygjrsqtzq2n0yrf2493p83kkfjhx0wlh")
5808                .label("My Hardware Wallet");
5809
5810        assert_eq!(request.asset, "BTC");
5811        assert_eq!(request.label, Some("My Hardware Wallet".to_string()));
5812    }
5813
5814    #[test]
5815    fn test_crypto_bars_params_builder() {
5816        let params = CryptoBarsParams::new("BTC/USD,ETH/USD")
5817            .timeframe("1Hour")
5818            .limit(100);
5819
5820        assert_eq!(params.symbols, Some("BTC/USD,ETH/USD".to_string()));
5821        assert_eq!(params.timeframe, Some("1Hour".to_string()));
5822        assert_eq!(params.limit, Some(100));
5823    }
5824
5825    #[test]
5826    fn test_news_content_type_serialization() {
5827        let content_type = NewsContentType::Article;
5828        let json = serde_json::to_string(&content_type).unwrap();
5829        assert_eq!(json, "\"article\"");
5830    }
5831
5832    #[test]
5833    fn test_news_params_builder() {
5834        let params = NewsParams::new()
5835            .symbols("AAPL,MSFT")
5836            .sort_desc()
5837            .with_content()
5838            .limit(50);
5839
5840        assert_eq!(params.symbols, Some("AAPL,MSFT".to_string()));
5841        assert_eq!(params.sort, Some("desc".to_string()));
5842        assert_eq!(params.include_content, Some(true));
5843        assert_eq!(params.limit, Some(50));
5844    }
5845
5846    #[test]
5847    fn test_oauth_scope_display() {
5848        assert_eq!(OAuthScope::AccountWrite.to_string(), "account:write");
5849        assert_eq!(OAuthScope::Trading.to_string(), "trading");
5850        assert_eq!(OAuthScope::Data.to_string(), "data");
5851    }
5852
5853    #[test]
5854    fn test_oauth_config_builder() {
5855        let config = OAuthConfig::new("client123", "secret456", "https://example.com/callback")
5856            .scope(OAuthScope::Trading)
5857            .scope(OAuthScope::Data);
5858
5859        assert_eq!(config.client_id, "client123");
5860        assert_eq!(config.scopes.len(), 2);
5861    }
5862
5863    #[test]
5864    fn test_oauth_token_authorization_header() {
5865        let token = crate::OAuthToken {
5866            access_token: "abc123".to_string(),
5867            refresh_token: Some("refresh456".to_string()),
5868            token_type: "Bearer".to_string(),
5869            expires_in: Some(3600),
5870            scope: Some("trading data".to_string()),
5871        };
5872
5873        assert_eq!(token.auth_header(), "Bearer abc123");
5874        assert!(token.has_refresh_token());
5875    }
5876
5877    #[test]
5878    fn test_account_status_event_type_serialization() {
5879        let event_type = AccountStatusEventType::AccountApproved;
5880        let json = serde_json::to_string(&event_type).unwrap();
5881        assert_eq!(json, "\"ACCOUNT_APPROVED\"");
5882    }
5883
5884    #[test]
5885    fn test_sse_event_params_builder() {
5886        let params = SseEventParams::new()
5887            .account_id("acc-123")
5888            .since("2024-01-01T00:00:00Z");
5889
5890        assert_eq!(params.account_id, Some("acc-123".to_string()));
5891        assert_eq!(params.since, Some("2024-01-01T00:00:00Z".to_string()));
5892    }
5893
5894    #[test]
5895    fn test_asset_status_serialization() {
5896        let status = AssetStatus::Active;
5897        let json = serde_json::to_string(&status).unwrap();
5898        assert_eq!(json, "\"active\"");
5899    }
5900
5901    #[test]
5902    fn test_list_assets_params_builder() {
5903        let params = ListAssetsParams::new()
5904            .status(AssetStatus::Active)
5905            .asset_class("us_equity")
5906            .exchange("NYSE");
5907
5908        assert_eq!(params.status, Some(AssetStatus::Active));
5909        assert_eq!(params.asset_class, Some("us_equity".to_string()));
5910        assert_eq!(params.exchange, Some("NYSE".to_string()));
5911    }
5912
5913    #[test]
5914    fn test_activity_type_serialization() {
5915        let activity = ActivityType::Fill;
5916        let json = serde_json::to_string(&activity).unwrap();
5917        assert_eq!(json, "\"FILL\"");
5918
5919        let div = ActivityType::Div;
5920        let json = serde_json::to_string(&div).unwrap();
5921        assert_eq!(json, "\"DIV\"");
5922    }
5923
5924    #[test]
5925    fn test_list_activities_params_builder() {
5926        let params = ListActivitiesParams::new()
5927            .activity_types("FILL,DIV")
5928            .direction(SortDirection::Desc)
5929            .page_size(100);
5930
5931        assert_eq!(params.activity_types, Some("FILL,DIV".to_string()));
5932        assert_eq!(params.direction, Some(SortDirection::Desc));
5933        assert_eq!(params.page_size, Some(100));
5934    }
5935
5936    #[test]
5937    fn test_portfolio_history_params_builder() {
5938        let params = PortfolioHistoryParams::new()
5939            .period(PortfolioPeriod::OneMonth)
5940            .timeframe(PortfolioTimeframe::OneDay)
5941            .extended_hours(true);
5942
5943        assert_eq!(params.period, Some("1M".to_string()));
5944        assert_eq!(params.timeframe, Some("1D".to_string()));
5945        assert_eq!(params.extended_hours, Some(true));
5946    }
5947
5948    #[test]
5949    fn test_target_allocation_percent() {
5950        let alloc = TargetAllocation::percent("AAPL", 25.0);
5951        assert_eq!(alloc.symbol, "AAPL");
5952        assert_eq!(alloc.percent, Some(25.0));
5953        assert!(alloc.notional.is_none());
5954    }
5955
5956    #[test]
5957    fn test_rate_limit_config_builder() {
5958        let config = RateLimitConfig::new()
5959            .requests_per_minute(100)
5960            .burst_limit(25)
5961            .max_retries(5);
5962
5963        assert_eq!(config.requests_per_minute, 100);
5964        assert_eq!(config.burst_limit, 25);
5965        assert_eq!(config.max_retries, 5);
5966    }
5967
5968    #[test]
5969    fn test_rate_limit_status() {
5970        let status = RateLimitStatus::new(50, 200, 1704067200);
5971        assert!(!status.is_rate_limited());
5972
5973        let limited = RateLimitStatus::new(0, 200, 1704067200);
5974        assert!(limited.is_rate_limited());
5975    }
5976
5977    #[test]
5978    fn test_margin_requirement_calculations() {
5979        let req = MarginRequirement::standard();
5980        assert!((req.initial - 0.50).abs() < f64::EPSILON);
5981        assert!((req.maintenance - 0.25).abs() < f64::EPSILON);
5982
5983        let initial = req.calculate_initial_margin(10000.0);
5984        assert!((initial - 5000.0).abs() < f64::EPSILON);
5985
5986        let maintenance = req.calculate_maintenance_margin(10000.0);
5987        assert!((maintenance - 2500.0).abs() < f64::EPSILON);
5988    }
5989
5990    #[test]
5991    fn test_buying_power_calculator() {
5992        let calc = BuyingPowerCalculator::new(10000.0, 50000.0, 2.0);
5993        assert!((calc.buying_power() - 20000.0).abs() < f64::EPSILON);
5994        assert_eq!(calc.max_shares(100.0), 200);
5995        assert_eq!(calc.max_shares(0.0), 0);
5996    }
5997
5998    #[test]
5999    fn test_fractional_qty() {
6000        let qty = FractionalQty::new(1.5).unwrap();
6001        assert!((qty.value() - 1.5).abs() < f64::EPSILON);
6002        assert!(!qty.is_whole());
6003        assert_eq!(qty.to_whole(), 1);
6004
6005        let whole = FractionalQty::new(5.0).unwrap();
6006        assert!(whole.is_whole());
6007
6008        assert!(FractionalQty::new(0.0).is_none());
6009    }
6010
6011    #[test]
6012    fn test_notional_amount() {
6013        let amount = NotionalAmount::from_f64(100.50).unwrap();
6014        assert_eq!(amount.amount, "100.50");
6015        assert!(amount.is_valid());
6016
6017        assert!(NotionalAmount::from_f64(0.5).is_none());
6018    }
6019
6020    #[test]
6021    fn test_trading_environment() {
6022        let paper = TradingEnvironment::Paper;
6023        assert!(paper.is_paper());
6024        assert!(!paper.is_live());
6025        assert_eq!(paper.base_url(), "https://paper-api.alpaca.markets");
6026
6027        let live = TradingEnvironment::Live;
6028        assert!(live.is_live());
6029        assert!(!live.is_paper());
6030
6031        assert_eq!(
6032            TradingEnvironment::from_api_key("PKABC123"),
6033            TradingEnvironment::Paper
6034        );
6035        assert_eq!(
6036            TradingEnvironment::from_api_key("AKABC123"),
6037            TradingEnvironment::Live
6038        );
6039    }
6040
6041    #[test]
6042    fn test_environment_guard() {
6043        let guard = EnvironmentGuard::paper_only();
6044        assert!(guard.is_allowed());
6045        assert!(guard.environment().is_paper());
6046
6047        let live_guard = EnvironmentGuard::allow_live(TradingEnvironment::Live);
6048        assert!(live_guard.is_allowed());
6049    }
6050
6051    #[test]
6052    fn test_market_session() {
6053        let regular = MarketSession::Regular;
6054        assert!(regular.is_trading_allowed());
6055        assert!(regular.is_regular());
6056
6057        let closed = MarketSession::Closed;
6058        assert!(!closed.is_trading_allowed());
6059        assert!(!closed.is_regular());
6060    }
6061
6062    #[test]
6063    fn test_calendar_params_builder() {
6064        let params = CalendarParams::new().start("2024-01-01").end("2024-12-31");
6065        assert_eq!(params.start, Some("2024-01-01".to_string()));
6066        assert_eq!(params.end, Some("2024-12-31".to_string()));
6067    }
6068
6069    #[test]
6070    fn test_fix_session_config() {
6071        let config = FixSessionConfig::new("SENDER", "TARGET", "localhost", 9876)
6072            .version(FixVersion::Fix44)
6073            .heartbeat_interval(60);
6074        assert_eq!(config.sender_comp_id, "SENDER");
6075        assert_eq!(config.target_comp_id, "TARGET");
6076        assert_eq!(config.version, FixVersion::Fix44);
6077        assert_eq!(config.heartbeat_interval, 60);
6078    }
6079
6080    #[test]
6081    fn test_fix_sequence_numbers() {
6082        let mut seq = FixSequenceNumbers::new();
6083        assert_eq!(seq.next_outgoing(), 1);
6084        assert_eq!(seq.next_outgoing(), 2);
6085        assert_eq!(seq.next_incoming(), 1);
6086        seq.reset();
6087        assert_eq!(seq.outgoing, 0);
6088        assert_eq!(seq.incoming, 0);
6089    }
6090
6091    #[test]
6092    fn test_document_params_builder() {
6093        let params = DocumentParams::new()
6094            .start("2024-01-01")
6095            .end("2024-12-31")
6096            .document_type(StatementType::AccountStatement);
6097        assert_eq!(params.start, Some("2024-01-01".to_string()));
6098        assert_eq!(params.end, Some("2024-12-31".to_string()));
6099        assert_eq!(params.document_type, Some(StatementType::AccountStatement));
6100    }
6101
6102    #[test]
6103    fn test_exchange_rate_conversion() {
6104        let rate = ExchangeRate::new(Currency::Eur, Currency::Usd, 1.10);
6105        assert!((rate.convert(100.0) - 110.0).abs() < 0.0001);
6106        assert!((rate.inverse() - 0.909_090_909_090_909_1).abs() < 0.0001);
6107    }
6108
6109    #[test]
6110    fn test_currency_pair() {
6111        let pair = CurrencyPair::new(Currency::Eur, Currency::Usd);
6112        assert_eq!(pair.as_string(), "EUR/USD");
6113    }
6114
6115    #[test]
6116    fn test_ira_account_type_display() {
6117        assert_eq!(IraAccountType::Traditional.to_string(), "Traditional");
6118        assert_eq!(IraAccountType::Roth.to_string(), "Roth");
6119        assert_eq!(IraAccountType::Sep.to_string(), "SEP");
6120        assert_eq!(IraAccountType::Simple.to_string(), "SIMPLE");
6121    }
6122
6123    #[test]
6124    fn test_create_ira_contribution_request() {
6125        let req = CreateIraContributionRequest::new("5000.00", 2024);
6126        assert_eq!(req.amount, "5000.00");
6127        assert_eq!(req.tax_year, 2024);
6128    }
6129}