1use serde::{Deserialize, Deserializer, Serialize};
4
5fn null_as_zero<'de, D: Deserializer<'de>>(d: D) -> Result<f64, D::Error> {
8 Ok(Option::<f64>::deserialize(d)?.unwrap_or(0.0))
9}
10
11#[derive(Debug, Deserialize)]
15pub struct ServerTimeResponse {
16 pub time: String,
18}
19
20#[derive(Debug, Deserialize)]
24#[serde(rename_all = "camelCase")]
25pub struct SymbolSearchResponse {
26 pub symbols: Vec<SymbolResult>,
28}
29
30#[derive(Debug, Deserialize)]
32#[serde(rename_all = "camelCase")]
33pub struct SymbolResult {
34 pub symbol: String,
36 pub symbol_id: u64,
38 pub description: String,
40 pub security_type: String,
42 pub listing_exchange: String,
44}
45
46#[derive(Debug, Deserialize)]
50#[serde(rename_all = "camelCase")]
51pub struct QuoteResponse {
52 pub quotes: Vec<Quote>,
54}
55
56#[derive(Debug, Deserialize)]
58#[serde(rename_all = "camelCase")]
59pub struct Quote {
60 pub symbol: String,
62 pub symbol_id: u64,
64 pub bid_price: Option<f64>,
66 pub ask_price: Option<f64>,
68 pub last_trade_price: Option<f64>,
70 pub volume: Option<u64>,
72 pub open_price: Option<f64>,
74 pub high_price: Option<f64>,
76 pub low_price: Option<f64>,
78}
79
80#[derive(Debug, Deserialize)]
84#[serde(rename_all = "camelCase")]
85pub struct OptionChainResponse {
86 pub option_chain: Vec<OptionExpiry>,
88}
89
90#[derive(Debug, Deserialize)]
92#[serde(rename_all = "camelCase")]
93pub struct OptionExpiry {
94 pub expiry_date: String,
96 pub description: String,
98 pub listing_exchange: String,
100 pub option_exercise_type: String,
102 pub chain_per_root: Vec<ChainPerRoot>,
104}
105
106#[derive(Debug, Deserialize)]
108#[serde(rename_all = "camelCase")]
109pub struct ChainPerRoot {
110 pub option_root: String,
112 pub multiplier: Option<u32>,
114 pub chain_per_strike_price: Vec<ChainPerStrike>,
116}
117
118#[derive(Debug, Deserialize)]
120#[serde(rename_all = "camelCase")]
121pub struct ChainPerStrike {
122 pub strike_price: f64,
124 pub call_symbol_id: u64,
126 pub put_symbol_id: u64,
128}
129
130#[derive(Debug, Serialize)]
134#[serde(rename_all = "camelCase")]
135pub struct OptionQuoteRequest {
136 pub option_ids: Vec<u64>,
138}
139
140#[derive(Debug, Deserialize)]
142#[serde(rename_all = "camelCase")]
143pub struct OptionQuoteResponse {
144 pub option_quotes: Vec<OptionQuote>,
146}
147
148#[derive(Debug, Deserialize)]
150#[serde(rename_all = "camelCase")]
151pub struct OptionQuote {
152 pub underlying: String,
154 pub underlying_id: u64,
156 pub symbol: String,
158 pub symbol_id: u64,
160 pub bid_price: Option<f64>,
162 pub ask_price: Option<f64>,
164 pub last_trade_price: Option<f64>,
166 pub volume: Option<u64>,
168 pub open_interest: Option<u64>,
170 pub volatility: Option<f64>,
172 pub delta: Option<f64>,
174 pub gamma: Option<f64>,
176 pub theta: Option<f64>,
178 pub vega: Option<f64>,
180 pub rho: Option<f64>,
182 pub strike_price: Option<f64>,
184 pub expiry_date: Option<String>,
186 pub option_type: Option<String>,
188 #[serde(rename = "VWAP")]
190 pub vwap: Option<f64>,
191 pub is_halted: Option<bool>,
193 pub bid_size: Option<u64>,
195 pub ask_size: Option<u64>,
197}
198
199#[derive(Debug, Deserialize)]
203#[serde(rename_all = "camelCase")]
204pub struct AccountsResponse {
205 pub accounts: Vec<Account>,
207}
208
209#[derive(Debug, Clone, Deserialize)]
211#[serde(rename_all = "camelCase")]
212pub struct Account {
213 #[serde(rename = "type")]
215 pub account_type: String,
216 pub number: String,
218 pub status: String,
220 #[serde(default)]
222 pub is_primary: bool,
223}
224
225#[derive(Debug, Deserialize)]
229#[serde(rename_all = "camelCase")]
230pub struct PositionsResponse {
231 pub positions: Vec<PositionItem>,
233}
234
235#[derive(Debug, Clone, Deserialize)]
237#[serde(rename_all = "camelCase")]
238pub struct PositionItem {
239 pub symbol: String,
241 pub symbol_id: u64,
243 #[serde(deserialize_with = "null_as_zero")]
246 pub open_quantity: f64,
247 pub current_market_value: Option<f64>,
249 pub current_price: Option<f64>,
251 #[serde(deserialize_with = "null_as_zero")]
253 pub average_entry_price: f64,
254 pub closed_pnl: Option<f64>,
256 pub open_pnl: Option<f64>,
258 #[serde(deserialize_with = "null_as_zero")]
260 pub total_cost: f64,
261}
262
263#[derive(Debug, Deserialize)]
267#[serde(rename_all = "camelCase")]
268pub struct ActivitiesResponse {
269 pub activities: Vec<ActivityItem>,
271}
272
273#[derive(Debug, Clone, Deserialize, Serialize)]
275#[serde(rename_all = "camelCase")]
276pub struct ActivityItem {
277 pub trade_date: String,
279 #[serde(default)]
281 pub transaction_date: Option<String>,
282 #[serde(default)]
284 pub settlement_date: Option<String>,
285 #[serde(default)]
287 pub description: Option<String>,
288 pub action: String,
290 pub symbol: String,
292 pub symbol_id: u64,
294 pub quantity: f64,
296 pub price: f64,
298 #[serde(default)]
300 pub gross_amount: f64,
301 #[serde(default)]
303 pub commission: f64,
304 pub net_amount: f64,
306 #[serde(default)]
308 pub currency: Option<String>,
309 #[serde(rename = "type")]
311 pub activity_type: String,
312}
313
314#[derive(Debug, Clone, Deserialize)]
320#[serde(rename_all = "camelCase")]
321pub struct AccountBalances {
322 pub per_currency_balances: Vec<PerCurrencyBalance>,
324 pub combined_balances: Vec<CombinedBalance>,
326 pub sod_per_currency_balances: Vec<PerCurrencyBalance>,
328 pub sod_combined_balances: Vec<CombinedBalance>,
330}
331
332#[derive(Debug, Clone, Deserialize)]
334#[serde(rename_all = "camelCase")]
335pub struct PerCurrencyBalance {
336 pub currency: String,
338 pub cash: f64,
340 pub market_value: f64,
342 pub total_equity: f64,
344 pub buying_power: f64,
346 pub maintenance_excess: f64,
348 pub is_real_time: bool,
350}
351
352#[derive(Debug, Clone, Deserialize)]
354#[serde(rename_all = "camelCase")]
355pub struct CombinedBalance {
356 pub currency: String,
358 pub cash: f64,
360 pub market_value: f64,
362 pub total_equity: f64,
364 pub buying_power: f64,
366 pub maintenance_excess: f64,
368 pub is_real_time: bool,
370}
371
372#[derive(Debug, Deserialize)]
376pub struct MarketsResponse {
377 pub markets: Vec<MarketInfo>,
379}
380
381#[derive(Debug, Clone, Deserialize, Serialize)]
383#[serde(rename_all = "camelCase")]
384pub struct MarketInfo {
385 pub name: String,
387 #[serde(default)]
389 pub currency: Option<String>,
390 #[serde(default)]
392 pub start_time: Option<String>,
393 #[serde(default)]
395 pub end_time: Option<String>,
396 #[serde(default)]
398 pub extended_start_time: Option<String>,
399 #[serde(default)]
401 pub extended_end_time: Option<String>,
402 #[serde(default)]
404 pub snapshot: Option<MarketSnapshot>,
405}
406
407#[derive(Debug, Clone, Deserialize, Serialize)]
409#[serde(rename_all = "camelCase")]
410pub struct MarketSnapshot {
411 pub is_open: bool,
413 #[serde(default)]
415 pub delay: u32,
416}
417
418#[derive(Debug, Deserialize)]
422pub struct SymbolDetailResponse {
423 pub symbols: Vec<SymbolDetail>,
425}
426
427#[derive(Debug, Clone, Deserialize)]
429#[serde(rename_all = "camelCase")]
430pub struct SymbolDetail {
431 pub symbol: String,
433 pub symbol_id: u64,
435 pub description: String,
437 pub security_type: String,
439 pub listing_exchange: String,
441 pub currency: String,
443 pub is_tradable: bool,
445 pub is_quotable: bool,
447 pub has_options: bool,
449 pub prev_day_close_price: Option<f64>,
451 pub high_price52: Option<f64>,
453 pub low_price52: Option<f64>,
455 pub average_vol3_months: Option<u64>,
457 pub average_vol20_days: Option<u64>,
459 pub outstanding_shares: Option<u64>,
461 pub eps: Option<f64>,
463 pub pe: Option<f64>,
465 pub dividend: Option<f64>,
467 #[serde(rename = "yield")]
469 pub dividend_yield: Option<f64>,
470 pub ex_date: Option<String>,
472 pub dividend_date: Option<String>,
474 pub market_cap: Option<f64>,
476 pub industry_sector: Option<String>,
478 pub industry_group: Option<String>,
480 pub industry_sub_group: Option<String>,
482 pub option_type: Option<String>,
484 pub option_expiry: Option<String>,
486 pub option_strike_price: Option<f64>,
488 pub option_exercise_type: Option<String>,
490}
491
492#[derive(Debug, Clone, Copy, Serialize)]
496pub enum OrderStateFilter {
497 All,
499 Open,
501 Closed,
503}
504
505impl std::fmt::Display for OrderStateFilter {
506 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
507 match self {
508 Self::All => write!(f, "All"),
509 Self::Open => write!(f, "Open"),
510 Self::Closed => write!(f, "Closed"),
511 }
512 }
513}
514
515#[derive(Debug, Deserialize)]
517#[serde(rename_all = "camelCase")]
518pub struct OrdersResponse {
519 pub orders: Vec<OrderItem>,
521}
522
523#[derive(Debug, Clone, Deserialize, Serialize)]
525#[serde(rename_all = "camelCase")]
526pub struct OrderItem {
527 pub id: u64,
529 pub symbol: String,
531 pub symbol_id: u64,
533 #[serde(default)]
535 pub total_quantity: f64,
536 #[serde(default)]
538 pub open_quantity: f64,
539 #[serde(default)]
541 pub filled_quantity: f64,
542 #[serde(default)]
544 pub canceled_quantity: f64,
545 pub side: String,
547 pub order_type: String,
549 #[serde(default)]
551 pub limit_price: Option<f64>,
552 #[serde(default)]
554 pub stop_price: Option<f64>,
555 #[serde(default)]
557 pub avg_exec_price: Option<f64>,
558 #[serde(default)]
560 pub last_exec_price: Option<f64>,
561 #[serde(default)]
563 pub commission_charged: f64,
564 pub state: String,
566 pub time_in_force: String,
568 pub creation_time: String,
570 pub update_time: String,
572 #[serde(default)]
574 pub notes: Option<String>,
575 #[serde(default)]
577 pub is_all_or_none: bool,
578 #[serde(default)]
580 pub is_anonymous: bool,
581 #[serde(default)]
583 pub order_group_id: Option<u64>,
584 #[serde(default)]
586 pub chain_id: Option<u64>,
587}
588
589#[derive(Debug, Deserialize)]
593#[serde(rename_all = "camelCase")]
594pub struct ExecutionsResponse {
595 pub executions: Vec<Execution>,
597}
598
599#[derive(Debug, Clone, Deserialize, Serialize)]
601#[serde(rename_all = "camelCase")]
602pub struct Execution {
603 pub symbol: String,
605 pub symbol_id: u64,
607 pub quantity: f64,
609 pub side: String,
611 pub price: f64,
613 pub id: u64,
615 pub order_id: u64,
617 pub order_chain_id: u64,
619 #[serde(default)]
621 pub exchange_exec_id: Option<String>,
622 pub timestamp: String,
624 #[serde(default)]
626 pub notes: Option<String>,
627 #[serde(default)]
629 pub venue: Option<String>,
630 #[serde(default, deserialize_with = "null_as_zero")]
632 pub total_cost: f64,
633 #[serde(default, deserialize_with = "null_as_zero")]
635 pub order_placement_commission: f64,
636 #[serde(default, deserialize_with = "null_as_zero")]
638 pub commission: f64,
639 #[serde(default, deserialize_with = "null_as_zero")]
641 pub execution_fee: f64,
642 #[serde(default, deserialize_with = "null_as_zero")]
644 pub sec_fee: f64,
645 #[serde(default, deserialize_with = "null_as_zero")]
647 pub canadian_execution_fee: f64,
648 #[serde(default)]
650 pub parent_id: u64,
651}
652
653#[derive(Debug, Deserialize)]
657#[serde(rename_all = "camelCase")]
658pub struct CandleResponse {
659 pub candles: Vec<Candle>,
661}
662
663#[derive(Debug, Deserialize)]
665pub struct Candle {
666 pub start: String,
668 pub end: String,
670 pub open: f64,
672 pub high: f64,
674 pub low: f64,
676 pub close: f64,
678 pub volume: u64,
680}
681
682#[cfg(test)]
683mod tests {
684 use super::*;
685
686 #[test]
687 fn markets_response_deserializes_from_questrade_json() {
688 let json = r#"{
690 "markets": [
691 {
692 "name": "NYSE",
693 "tradingVenues": ["NYSE"],
694 "defaultTradingVenue": "NYSE",
695 "primaryOrderRoutes": ["NYSE"],
696 "secondaryOrderRoutes": [],
697 "level1Feeds": ["NYSE"],
698 "level2Feeds": [],
699 "extendedStartTime": "2026-02-21T08:00:00.000000-05:00",
700 "startTime": "2026-02-21T09:30:00.000000-05:00",
701 "endTime": "2026-02-21T16:00:00.000000-05:00",
702 "extendedEndTime": "2026-02-21T20:00:00.000000-05:00",
703 "currency": "USD",
704 "snapshot": { "isOpen": true, "delay": 0 }
705 },
706 {
707 "name": "TSX",
708 "tradingVenues": ["TSX"],
709 "defaultTradingVenue": "TSX",
710 "primaryOrderRoutes": ["TSX"],
711 "secondaryOrderRoutes": [],
712 "level1Feeds": ["TSX"],
713 "level2Feeds": [],
714 "extendedStartTime": "2026-02-21T08:00:00.000000-05:00",
715 "startTime": "2026-02-21T09:30:00.000000-05:00",
716 "endTime": "2026-02-21T16:00:00.000000-05:00",
717 "extendedEndTime": "2026-02-21T17:00:00.000000-05:00",
718 "currency": "CAD",
719 "snapshot": null
720 }
721 ]
722 }"#;
723
724 let resp: MarketsResponse = serde_json::from_str(json).unwrap();
725 assert_eq!(resp.markets.len(), 2);
726
727 let nyse = &resp.markets[0];
728 assert_eq!(nyse.name, "NYSE");
729 assert_eq!(nyse.currency.as_deref(), Some("USD"));
730 assert_eq!(
731 nyse.start_time.as_deref(),
732 Some("2026-02-21T09:30:00.000000-05:00")
733 );
734 assert_eq!(
735 nyse.end_time.as_deref(),
736 Some("2026-02-21T16:00:00.000000-05:00")
737 );
738 let snap = nyse.snapshot.as_ref().unwrap();
739 assert!(snap.is_open);
740 assert_eq!(snap.delay, 0);
741
742 let tsx = &resp.markets[1];
744 assert_eq!(tsx.name, "TSX");
745 assert!(tsx.snapshot.is_none());
746 }
747
748 #[test]
749 fn account_balances_deserializes_from_questrade_json() {
750 let json = r#"{
751 "perCurrencyBalances": [
752 {
753 "currency": "CAD",
754 "cash": 10000.0,
755 "marketValue": 50000.0,
756 "totalEquity": 60000.0,
757 "buyingPower": 60000.0,
758 "maintenanceExcess": 60000.0,
759 "isRealTime": false
760 }
761 ],
762 "combinedBalances": [
763 {
764 "currency": "CAD",
765 "cash": 10000.0,
766 "marketValue": 50000.0,
767 "totalEquity": 60000.0,
768 "buyingPower": 60000.0,
769 "maintenanceExcess": 60000.0,
770 "isRealTime": false
771 }
772 ],
773 "sodPerCurrencyBalances": [
774 {
775 "currency": "CAD",
776 "cash": 9000.0,
777 "marketValue": 49000.0,
778 "totalEquity": 58000.0,
779 "buyingPower": 58000.0,
780 "maintenanceExcess": 58000.0,
781 "isRealTime": false
782 }
783 ],
784 "sodCombinedBalances": [
785 {
786 "currency": "CAD",
787 "cash": 9000.0,
788 "marketValue": 49000.0,
789 "totalEquity": 58000.0,
790 "buyingPower": 58000.0,
791 "maintenanceExcess": 58000.0,
792 "isRealTime": false
793 }
794 ]
795 }"#;
796
797 let balances: AccountBalances = serde_json::from_str(json).unwrap();
798 assert_eq!(balances.per_currency_balances.len(), 1);
799 let cad = &balances.per_currency_balances[0];
800 assert_eq!(cad.currency, "CAD");
801 assert_eq!(cad.cash, 10000.0);
802 assert_eq!(cad.market_value, 50000.0);
803 assert_eq!(cad.total_equity, 60000.0);
804 assert!(!cad.is_real_time);
805 assert_eq!(balances.combined_balances.len(), 1);
806 assert_eq!(balances.sod_per_currency_balances.len(), 1);
807 assert_eq!(balances.sod_combined_balances.len(), 1);
808 }
809
810 #[test]
811 fn symbol_detail_deserializes_from_questrade_json() {
812 let json = r#"{
813 "symbols": [
814 {
815 "symbol": "AAPL",
816 "symbolId": 8049,
817 "description": "Apple Inc.",
818 "securityType": "Stock",
819 "listingExchange": "NASDAQ",
820 "currency": "USD",
821 "isTradable": true,
822 "isQuotable": true,
823 "hasOptions": true,
824 "prevDayClosePrice": 182.50,
825 "highPrice52": 199.62,
826 "lowPrice52": 124.17,
827 "averageVol3Months": 52000000,
828 "averageVol20Days": 50000000,
829 "outstandingShares": 15700000000,
830 "eps": 6.14,
831 "pe": 29.74,
832 "dividend": 0.96,
833 "yield": 0.53,
834 "exDate": "2023-11-10T00:00:00.000000-05:00",
835 "dividendDate": "2023-11-16T00:00:00.000000-05:00",
836 "marketCap": 2866625000000.0,
837 "industrySector": "Technology",
838 "industryGroup": "Technology Hardware, Storage & Peripherals",
839 "industrySubGroup": "Other",
840 "optionType": null,
841 "optionExpiry": null,
842 "optionStrikePrice": null,
843 "optionExerciseType": null
844 }
845 ]
846 }"#;
847
848 let resp: SymbolDetailResponse = serde_json::from_str(json).unwrap();
849 assert_eq!(resp.symbols.len(), 1);
850 let s = &resp.symbols[0];
851 assert_eq!(s.symbol, "AAPL");
852 assert_eq!(s.symbol_id, 8049);
853 assert_eq!(s.description, "Apple Inc.");
854 assert_eq!(s.security_type, "Stock");
855 assert_eq!(s.listing_exchange, "NASDAQ");
856 assert_eq!(s.currency, "USD");
857 assert!(s.is_tradable);
858 assert!(s.is_quotable);
859 assert!(s.has_options);
860 assert_eq!(s.prev_day_close_price, Some(182.50));
861 assert_eq!(s.high_price52, Some(199.62));
862 assert_eq!(s.low_price52, Some(124.17));
863 assert_eq!(s.eps, Some(6.14));
864 assert_eq!(s.dividend_yield, Some(0.53));
865 assert_eq!(s.industry_sector.as_deref(), Some("Technology"));
866 assert!(s.option_type.is_none());
867 assert!(s.option_expiry.is_none());
868 }
869
870 #[test]
871 fn orders_response_deserializes_from_questrade_json() {
872 let json = r#"{
873 "orders": [
874 {
875 "id": 173577870,
876 "symbol": "AAPL",
877 "symbolId": 8049,
878 "totalQuantity": 100,
879 "openQuantity": 0,
880 "filledQuantity": 100,
881 "canceledQuantity": 0,
882 "side": "Buy",
883 "orderType": "Limit",
884 "limitPrice": 150.50,
885 "stopPrice": null,
886 "avgExecPrice": 150.25,
887 "lastExecPrice": 150.25,
888 "commissionCharged": 4.95,
889 "state": "Executed",
890 "timeInForce": "Day",
891 "creationTime": "2026-02-20T10:30:00.000000-05:00",
892 "updateTime": "2026-02-20T10:31:15.000000-05:00",
893 "notes": null,
894 "isAllOrNone": false,
895 "isAnonymous": false,
896 "orderGroupId": 0,
897 "chainId": 173577870
898 },
899 {
900 "id": 173600001,
901 "symbol": "MSFT",
902 "symbolId": 9291,
903 "totalQuantity": 50,
904 "openQuantity": 50,
905 "filledQuantity": 0,
906 "canceledQuantity": 0,
907 "side": "Buy",
908 "orderType": "Limit",
909 "limitPrice": 400.00,
910 "stopPrice": null,
911 "avgExecPrice": null,
912 "lastExecPrice": null,
913 "commissionCharged": 0,
914 "state": "Pending",
915 "timeInForce": "GoodTillCanceled",
916 "creationTime": "2026-02-21T09:45:00.000000-05:00",
917 "updateTime": "2026-02-21T09:45:00.000000-05:00",
918 "notes": "Staff note here",
919 "isAllOrNone": true,
920 "isAnonymous": false
921 }
922 ]
923 }"#;
924
925 let resp: OrdersResponse = serde_json::from_str(json).unwrap();
926 assert_eq!(resp.orders.len(), 2);
927
928 let o1 = &resp.orders[0];
930 assert_eq!(o1.id, 173577870);
931 assert_eq!(o1.symbol, "AAPL");
932 assert_eq!(o1.symbol_id, 8049);
933 assert_eq!(o1.total_quantity, 100.0);
934 assert_eq!(o1.open_quantity, 0.0);
935 assert_eq!(o1.filled_quantity, 100.0);
936 assert_eq!(o1.canceled_quantity, 0.0);
937 assert_eq!(o1.side, "Buy");
938 assert_eq!(o1.order_type, "Limit");
939 assert_eq!(o1.limit_price, Some(150.50));
940 assert!(o1.stop_price.is_none());
941 assert_eq!(o1.avg_exec_price, Some(150.25));
942 assert_eq!(o1.last_exec_price, Some(150.25));
943 assert_eq!(o1.commission_charged, 4.95);
944 assert_eq!(o1.state, "Executed");
945 assert_eq!(o1.time_in_force, "Day");
946 assert!(o1.notes.is_none());
947 assert!(!o1.is_all_or_none);
948 assert_eq!(o1.chain_id, Some(173577870));
949
950 let o2 = &resp.orders[1];
952 assert_eq!(o2.id, 173600001);
953 assert_eq!(o2.symbol, "MSFT");
954 assert_eq!(o2.state, "Pending");
955 assert_eq!(o2.time_in_force, "GoodTillCanceled");
956 assert!(o2.avg_exec_price.is_none());
957 assert!(o2.last_exec_price.is_none());
958 assert_eq!(o2.commission_charged, 0.0);
959 assert_eq!(o2.notes.as_deref(), Some("Staff note here"));
960 assert!(o2.is_all_or_none);
961 assert!(o2.order_group_id.is_none());
963 assert!(o2.chain_id.is_none());
964 }
965
966 #[test]
967 fn execution_deserializes_from_questrade_json() {
968 let json = r#"{
969 "executions": [
970 {
971 "symbol": "AAPL",
972 "symbolId": 8049,
973 "quantity": 10,
974 "side": "Buy",
975 "price": 536.87,
976 "id": 53817310,
977 "orderId": 177106005,
978 "orderChainId": 17710600,
979 "exchangeExecId": "XS1771060050147",
980 "timestamp": "2014-03-31T13:38:29.000000-04:00",
981 "notes": "",
982 "venue": "LAMP",
983 "totalCost": 5368.7,
984 "orderPlacementCommission": 0,
985 "commission": 4.95,
986 "executionFee": 0,
987 "secFee": 0,
988 "canadianExecutionFee": 0,
989 "parentId": 0
990 }
991 ]
992 }"#;
993
994 let resp: ExecutionsResponse = serde_json::from_str(json).unwrap();
995 assert_eq!(resp.executions.len(), 1);
996
997 let e = &resp.executions[0];
998 assert_eq!(e.symbol, "AAPL");
999 assert_eq!(e.symbol_id, 8049);
1000 assert_eq!(e.quantity, 10.0);
1001 assert_eq!(e.side, "Buy");
1002 assert_eq!(e.price, 536.87);
1003 assert_eq!(e.id, 53817310);
1004 assert_eq!(e.order_id, 177106005);
1005 assert_eq!(e.order_chain_id, 17710600);
1006 assert_eq!(e.exchange_exec_id.as_deref(), Some("XS1771060050147"));
1007 assert_eq!(e.timestamp, "2014-03-31T13:38:29.000000-04:00");
1008 assert_eq!(e.venue.as_deref(), Some("LAMP"));
1009 assert_eq!(e.total_cost, 5368.7);
1010 assert_eq!(e.commission, 4.95);
1011 assert_eq!(e.execution_fee, 0.0);
1012 assert_eq!(e.sec_fee, 0.0);
1013 assert_eq!(e.parent_id, 0);
1014 }
1015}