1use std::collections::BTreeMap;
2
3use num_enum::{FromPrimitive, IntoPrimitive};
4use rust_decimal::Decimal;
5use serde::{Deserialize, Deserializer, Serialize, Serializer};
6use strum_macros::{Display, EnumString};
7use time::{Date, OffsetDateTime};
8
9use crate::{Market, serde_utils};
10
11#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, EnumString, Display)]
13#[allow(clippy::upper_case_acronyms)]
14pub enum OrderType {
15 Unknown,
17 #[strum(serialize = "LO")]
19 LO,
20 #[strum(serialize = "ELO")]
22 ELO,
23 #[strum(serialize = "MO")]
25 MO,
26 #[strum(serialize = "AO")]
28 AO,
29 #[strum(serialize = "ALO")]
31 ALO,
32 #[strum(serialize = "ODD")]
34 ODD,
35 #[strum(serialize = "LIT")]
37 LIT,
38 #[strum(serialize = "MIT")]
40 MIT,
41 #[strum(serialize = "TSLPAMT")]
43 TSLPAMT,
44 #[strum(serialize = "TSLPPCT")]
46 TSLPPCT,
47 #[strum(serialize = "TSMAMT")]
49 TSMAMT,
50 #[strum(serialize = "TSMPCT")]
52 TSMPCT,
53 #[strum(serialize = "SLO")]
55 SLO,
56}
57
58#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, EnumString, Display)]
60pub enum OrderStatus {
61 Unknown,
63 #[strum(serialize = "NotReported")]
65 NotReported,
66 #[strum(serialize = "ReplacedNotReported")]
68 ReplacedNotReported,
69 #[strum(serialize = "ProtectedNotReported")]
71 ProtectedNotReported,
72 #[strum(serialize = "VarietiesNotReported")]
74 VarietiesNotReported,
75 #[strum(serialize = "FilledStatus")]
77 Filled,
78 #[strum(serialize = "WaitToNew")]
80 WaitToNew,
81 #[strum(serialize = "NewStatus")]
83 New,
84 #[strum(serialize = "WaitToReplace")]
86 WaitToReplace,
87 #[strum(serialize = "PendingReplaceStatus")]
89 PendingReplace,
90 #[strum(serialize = "ReplacedStatus")]
92 Replaced,
93 #[strum(serialize = "PartialFilledStatus")]
95 PartialFilled,
96 #[strum(serialize = "WaitToCancel")]
98 WaitToCancel,
99 #[strum(serialize = "PendingCancelStatus")]
101 PendingCancel,
102 #[strum(serialize = "RejectedStatus")]
104 Rejected,
105 #[strum(serialize = "CanceledStatus")]
107 Canceled,
108 #[strum(serialize = "ExpiredStatus")]
110 Expired,
111 #[strum(serialize = "PartialWithdrawal")]
113 PartialWithdrawal,
114 #[strum(serialize = "DelayedNotReported")]
116 DelayedNotReported,
117}
118
119#[derive(Debug, Clone, Serialize, Deserialize)]
121pub struct Execution {
122 pub order_id: String,
124 pub trade_id: String,
126 pub symbol: String,
128 #[serde(
130 serialize_with = "time::serde::rfc3339::serialize",
131 deserialize_with = "serde_utils::timestamp::deserialize"
132 )]
133 pub trade_done_at: OffsetDateTime,
134 pub quantity: Decimal,
136 pub price: Decimal,
138 #[serde(default)]
141 pub side: OrderSide,
142}
143
144#[derive(Debug, Clone, Serialize, Deserialize)]
146pub struct AllExecutionsResponse {
147 pub has_more: bool,
149 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
151 pub trades: Vec<Execution>,
152}
153
154#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, EnumString, Display)]
156pub enum OrderSide {
157 Unknown,
159 #[strum(serialize = "Buy")]
161 Buy,
162 #[strum(serialize = "Sell")]
164 Sell,
165}
166
167#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, EnumString, Display)]
169pub enum TriggerPriceType {
170 Unknown,
172 #[strum(serialize = "LIT")]
174 LimitIfTouched,
175 #[strum(serialize = "MIT")]
177 MarketIfTouched,
178}
179
180#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, EnumString, Display)]
182pub enum OrderTag {
183 Unknown,
185 #[strum(serialize = "Normal")]
187 Normal,
188 #[strum(serialize = "Gtc")]
190 LongTerm,
191 #[strum(serialize = "Grey")]
193 Grey,
194}
195
196#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, EnumString, Display)]
198pub enum TimeInForceType {
199 Unknown,
201 #[strum(serialize = "Day")]
203 Day,
204 #[strum(serialize = "GTC")]
206 GoodTilCanceled,
207 #[strum(serialize = "GTD")]
209 GoodTilDate,
210}
211
212#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, EnumString, Display)]
214pub enum TriggerStatus {
215 Unknown,
217 #[strum(serialize = "DEACTIVE")]
219 Deactive,
220 #[strum(serialize = "ACTIVE")]
222 Active,
223 #[strum(serialize = "RELEASED")]
225 Released,
226}
227
228#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, EnumString, Display)]
230pub enum OutsideRTH {
231 Unknown,
233 #[strum(serialize = "RTH_ONLY")]
235 RTHOnly,
236 #[strum(serialize = "ANY_TIME")]
238 AnyTime,
239 #[strum(serialize = "OVERNIGHT")]
241 Overnight,
242 #[strum(serialize = "OPTION_PRE_MARKET")]
244 OptionPreMarket,
245}
246
247#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, EnumString, Display)]
249pub enum AttachedOrderType {
250 Unknown,
252 #[strum(serialize = "PROFIT_TAKER")]
254 ProfitTaker,
255 #[strum(serialize = "STOP_LOSS")]
257 StopLoss,
258 #[strum(serialize = "BRACKET")]
260 Bracket,
261}
262
263#[derive(Debug, Clone, Serialize, Deserialize)]
265pub struct AttachedOrderDetail {
266 pub order_id: String,
268 pub attached_type_display: AttachedOrderType,
270 #[serde(with = "serde_utils::decimal_opt_empty_is_none")]
272 pub trigger_price: Option<Decimal>,
273 pub quantity: Decimal,
275 pub executed_qty: Decimal,
277 pub status: OrderStatus,
279 #[serde(
281 serialize_with = "time::serde::rfc3339::serialize",
282 deserialize_with = "serde_utils::timestamp::deserialize"
283 )]
284 pub updated_at: OffsetDateTime,
285 pub withdrawn: bool,
287 #[serde(with = "serde_utils::date_opt")]
289 pub gtd: Option<Date>,
290 pub time_in_force: TimeInForceType,
292 pub counter_id: String,
294 #[serde(with = "serde_utils::trigger_status")]
296 pub trigger_status: Option<TriggerStatus>,
297 pub executed_amount: Decimal,
299 pub tag: OrderTag,
301 #[serde(
303 serialize_with = "time::serde::rfc3339::serialize",
304 deserialize_with = "serde_utils::timestamp::deserialize"
305 )]
306 pub submitted_at: OffsetDateTime,
307 #[serde(with = "serde_utils::decimal_opt_empty_is_none")]
309 pub executed_price: Option<Decimal>,
310 #[serde(with = "serde_utils::outside_rth")]
312 pub force_only_rth: Option<OutsideRTH>,
313 pub reviewed: bool,
315 pub activate_order_type: OrderType,
317 #[serde(with = "serde_utils::outside_rth")]
319 pub activate_rth: Option<OutsideRTH>,
320 #[serde(with = "serde_utils::decimal_opt_empty_is_none")]
322 pub submit_price: Option<Decimal>,
323 #[serde(
325 default,
326 deserialize_with = "serde_utils::int32_opt_null_or_0_is_none::deserialize",
327 serialize_with = "serde_utils::int32_opt_0_is_none::serialize"
328 )]
329 pub limit_depth_level: Option<i32>,
330}
331
332#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, EnumString, Display)]
334pub enum MultiLegStrategy {
335 Unknown,
337 #[strum(to_string = "CoveredCall", serialize = "0")]
339 CoveredCall,
340 #[strum(to_string = "CoveredPut", serialize = "1")]
342 CoveredPut,
343 #[strum(to_string = "VerticalCallSpread", serialize = "2")]
345 VerticalCallSpread,
346 #[strum(to_string = "VerticalPutSpread", serialize = "3")]
348 VerticalPutSpread,
349 #[strum(to_string = "Collar", serialize = "4")]
351 Collar,
352 #[strum(to_string = "Straddle", serialize = "5")]
354 Straddle,
355 #[strum(to_string = "Strangle", serialize = "6")]
357 Strangle,
358 #[strum(to_string = "CalendarCallSpread", serialize = "7")]
360 CalendarCallSpread,
361 #[strum(to_string = "CalendarPutSpread", serialize = "8")]
363 CalendarPutSpread,
364}
365
366#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, EnumString, Display)]
368pub enum MultiLegPosition {
369 Unknown,
371 #[strum(serialize = "LONG")]
373 Long,
374 #[strum(serialize = "SHORT")]
376 Short,
377}
378
379#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, EnumString, Display)]
381pub enum ContractDirection {
382 Unknown,
384 #[strum(serialize = "C")]
386 Call,
387 #[strum(serialize = "P")]
389 Put,
390}
391
392#[derive(Debug, Clone, Serialize, Deserialize)]
394pub struct MultiLegOrderLeg {
395 pub symbol: String,
397 pub side: OrderSide,
399 pub position: MultiLegPosition,
401 #[serde(with = "serde_utils::decimal_empty_is_0")]
403 pub ratio_quantity: Decimal,
404 #[serde(with = "serde_utils::decimal_opt_empty_is_none")]
406 pub strike_price: Option<Decimal>,
407 #[serde(default, with = "serde_utils::date_ymd_opt")]
409 pub expire_date: Option<Date>,
410 pub contract_direction: ContractDirection,
412}
413
414#[derive(Debug, Clone, Serialize, Deserialize)]
416pub struct MultiLegInfo {
417 pub strategy: MultiLegStrategy,
419 pub strategy_name: String,
421 pub multileg_id: String,
423 pub code: String,
425 #[serde(default)]
427 pub legs: Vec<MultiLegOrderLeg>,
428}
429
430#[derive(Debug, Clone, Serialize, Deserialize)]
432pub struct Order {
433 pub order_id: String,
435 pub status: OrderStatus,
437 pub stock_name: String,
439 pub quantity: Decimal,
441 pub executed_quantity: Decimal,
443 #[serde(with = "serde_utils::decimal_opt_empty_is_none")]
445 pub price: Option<Decimal>,
446 #[serde(with = "serde_utils::decimal_opt_0_is_none")]
448 pub executed_price: Option<Decimal>,
449 #[serde(
451 serialize_with = "time::serde::rfc3339::serialize",
452 deserialize_with = "serde_utils::timestamp::deserialize"
453 )]
454 pub submitted_at: OffsetDateTime,
455 pub side: OrderSide,
457 pub symbol: String,
459 pub order_type: OrderType,
461 #[serde(with = "serde_utils::decimal_opt_empty_is_none")]
463 pub last_done: Option<Decimal>,
464 #[serde(with = "serde_utils::decimal_opt_empty_is_none")]
466 pub trigger_price: Option<Decimal>,
467 pub msg: String,
469 pub tag: OrderTag,
471 pub time_in_force: TimeInForceType,
473 #[serde(with = "serde_utils::date_opt")]
475 pub expire_date: Option<Date>,
476 #[serde(
478 deserialize_with = "serde_utils::timestamp_opt::deserialize",
479 serialize_with = "serde_utils::rfc3339_opt::serialize"
480 )]
481 pub updated_at: Option<OffsetDateTime>,
482 #[serde(
484 deserialize_with = "serde_utils::timestamp_opt::deserialize",
485 serialize_with = "serde_utils::rfc3339_opt::serialize"
486 )]
487 pub trigger_at: Option<OffsetDateTime>,
488 #[serde(with = "serde_utils::decimal_opt_empty_is_none")]
490 pub trailing_amount: Option<Decimal>,
491 #[serde(with = "serde_utils::decimal_opt_empty_is_none")]
493 pub trailing_percent: Option<Decimal>,
494 #[serde(with = "serde_utils::decimal_opt_empty_is_none")]
496 pub limit_offset: Option<Decimal>,
497 #[serde(with = "serde_utils::trigger_status")]
499 pub trigger_status: Option<TriggerStatus>,
500 pub currency: String,
502 #[serde(with = "serde_utils::outside_rth")]
504 pub outside_rth: Option<OutsideRTH>,
505 #[serde(with = "serde_utils::int32_opt_0_is_none")]
507 pub limit_depth_level: Option<i32>,
508 #[serde(with = "serde_utils::int32_opt_0_is_none")]
510 pub trigger_count: Option<i32>,
511 #[serde(with = "serde_utils::decimal_opt_empty_is_none")]
513 pub monitor_price: Option<Decimal>,
514 pub remark: String,
516 #[serde(
519 default,
520 deserialize_with = "serde_utils::timestamp_opt_lenient::deserialize",
521 serialize_with = "serde_utils::rfc3339_opt::serialize"
522 )]
523 pub delayed_until: Option<OffsetDateTime>,
524 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
526 pub attached_orders: Vec<AttachedOrderDetail>,
527 #[serde(default)]
530 pub multi_leg: Option<MultiLegInfo>,
531}
532
533#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, EnumString, Display)]
535pub enum CommissionFreeStatus {
536 Unknown,
538 None,
540 Calculated,
542 Pending,
544 Ready,
546}
547
548#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, EnumString, Display)]
550pub enum DeductionStatus {
551 Unknown,
553 #[strum(serialize = "NONE")]
555 None,
556 #[strum(serialize = "NO_DATA")]
558 NoData,
559 #[strum(serialize = "PENDING")]
561 Pending,
562 #[strum(serialize = "DONE")]
564 Done,
565}
566
567#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, EnumString, Display)]
569pub enum ChargeCategoryCode {
570 Unknown,
572 #[strum(serialize = "BROKER_FEES")]
574 Broker,
575 #[strum(serialize = "THIRD_FEES")]
577 Third,
578}
579
580#[derive(Debug, Clone, Serialize, Deserialize)]
582pub struct OrderHistoryDetail {
583 #[serde(with = "serde_utils::decimal_empty_is_0")]
586 pub price: Decimal,
587 #[serde(with = "serde_utils::decimal_empty_is_0")]
590 pub quantity: Decimal,
591 pub status: OrderStatus,
593 pub msg: String,
595 #[serde(
597 serialize_with = "time::serde::rfc3339::serialize",
598 deserialize_with = "serde_utils::timestamp::deserialize"
599 )]
600 pub time: OffsetDateTime,
601}
602
603#[derive(Debug, Clone, Serialize, Deserialize)]
605pub struct OrderChargeFee {
606 pub code: String,
608 pub name: String,
610 #[serde(with = "serde_utils::decimal_empty_is_0")]
612 pub amount: Decimal,
613 pub currency: String,
615}
616
617#[derive(Debug, Clone, Serialize, Deserialize)]
619pub struct OrderChargeItem {
620 pub code: ChargeCategoryCode,
622 pub name: String,
624 pub fees: Vec<OrderChargeFee>,
626}
627
628#[derive(Debug, Clone, Serialize, Deserialize)]
630pub struct OrderChargeDetail {
631 pub total_amount: Decimal,
633 pub currency: String,
635 pub items: Vec<OrderChargeItem>,
637}
638
639#[derive(Debug, Clone, Serialize, Deserialize)]
641pub struct OrderDetail {
642 pub order_id: String,
644 pub status: OrderStatus,
646 pub stock_name: String,
648 pub quantity: Decimal,
650 pub executed_quantity: Decimal,
652 #[serde(with = "serde_utils::decimal_opt_empty_is_none")]
654 pub price: Option<Decimal>,
655 #[serde(with = "serde_utils::decimal_opt_0_is_none")]
657 pub executed_price: Option<Decimal>,
658 #[serde(
660 serialize_with = "time::serde::rfc3339::serialize",
661 deserialize_with = "serde_utils::timestamp::deserialize"
662 )]
663 pub submitted_at: OffsetDateTime,
664 pub side: OrderSide,
666 pub symbol: String,
668 pub order_type: OrderType,
670 #[serde(with = "serde_utils::decimal_opt_empty_is_none")]
672 pub last_done: Option<Decimal>,
673 #[serde(with = "serde_utils::decimal_opt_empty_is_none")]
675 pub trigger_price: Option<Decimal>,
676 pub msg: String,
678 pub tag: OrderTag,
680 pub time_in_force: TimeInForceType,
682 #[serde(with = "serde_utils::date_opt")]
684 pub expire_date: Option<Date>,
685 #[serde(
687 deserialize_with = "serde_utils::timestamp_opt::deserialize",
688 serialize_with = "serde_utils::rfc3339_opt::serialize"
689 )]
690 pub updated_at: Option<OffsetDateTime>,
691 #[serde(
693 deserialize_with = "serde_utils::timestamp_opt::deserialize",
694 serialize_with = "serde_utils::rfc3339_opt::serialize"
695 )]
696 pub trigger_at: Option<OffsetDateTime>,
697 #[serde(with = "serde_utils::decimal_opt_empty_is_none")]
699 pub trailing_amount: Option<Decimal>,
700 #[serde(with = "serde_utils::decimal_opt_empty_is_none")]
702 pub trailing_percent: Option<Decimal>,
703 #[serde(with = "serde_utils::decimal_opt_empty_is_none")]
705 pub limit_offset: Option<Decimal>,
706 #[serde(with = "serde_utils::trigger_status")]
708 pub trigger_status: Option<TriggerStatus>,
709 pub currency: String,
711 #[serde(with = "serde_utils::outside_rth")]
713 pub outside_rth: Option<OutsideRTH>,
714 #[serde(with = "serde_utils::int32_opt_0_is_none")]
716 pub limit_depth_level: Option<i32>,
717 #[serde(with = "serde_utils::int32_opt_0_is_none")]
719 pub trigger_count: Option<i32>,
720 #[serde(with = "serde_utils::decimal_opt_empty_is_none")]
722 pub monitor_price: Option<Decimal>,
723 pub remark: String,
725 #[serde(
728 default,
729 deserialize_with = "serde_utils::timestamp_opt_lenient::deserialize",
730 serialize_with = "serde_utils::rfc3339_opt::serialize"
731 )]
732 pub delayed_until: Option<OffsetDateTime>,
733 pub free_status: CommissionFreeStatus,
735 #[serde(with = "serde_utils::decimal_opt_empty_is_none")]
737 pub free_amount: Option<Decimal>,
738 #[serde(with = "serde_utils::symbol_opt")]
740 pub free_currency: Option<String>,
741 pub deductions_status: DeductionStatus,
743 #[serde(with = "serde_utils::decimal_opt_empty_is_none")]
745 pub deductions_amount: Option<Decimal>,
746 #[serde(with = "serde_utils::symbol_opt")]
748 pub deductions_currency: Option<String>,
749 pub platform_deducted_status: DeductionStatus,
751 #[serde(with = "serde_utils::decimal_opt_empty_is_none")]
753 pub platform_deducted_amount: Option<Decimal>,
754 #[serde(with = "serde_utils::symbol_opt")]
756 pub platform_deducted_currency: Option<String>,
757 pub history: Vec<OrderHistoryDetail>,
759 pub charge_detail: Option<OrderChargeDetail>,
761 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
763 pub attached_orders: Vec<AttachedOrderDetail>,
764 #[serde(default)]
767 pub multi_leg: Option<MultiLegInfo>,
768}
769
770#[derive(Debug, Clone, Serialize, Deserialize)]
772pub struct CashInfo {
773 pub withdraw_cash: Decimal,
775 pub available_cash: Decimal,
777 pub frozen_cash: Decimal,
779 pub settling_cash: Decimal,
781 pub currency: String,
783}
784
785#[derive(Debug, Clone, Serialize, Deserialize)]
787pub struct FrozenTransactionFee {
788 pub currency: String,
790 pub frozen_transaction_fee: Decimal,
792}
793
794#[derive(Debug, Clone, Serialize, Deserialize)]
796pub struct AccountBalance {
797 pub total_cash: Decimal,
799 pub max_finance_amount: Decimal,
801 pub remaining_finance_amount: Decimal,
803 #[serde(with = "serde_utils::risk_level")]
805 pub risk_level: i32,
806 pub margin_call: Decimal,
808 pub currency: String,
810 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
812 pub cash_infos: Vec<CashInfo>,
813 #[serde(with = "serde_utils::decimal_empty_is_0")]
815 pub net_assets: Decimal,
816 #[serde(with = "serde_utils::decimal_empty_is_0")]
818 pub init_margin: Decimal,
819 #[serde(with = "serde_utils::decimal_empty_is_0")]
821 pub maintenance_margin: Decimal,
822 #[serde(with = "serde_utils::decimal_empty_is_0")]
824 pub buy_power: Decimal,
825 pub frozen_transaction_fees: Vec<FrozenTransactionFee>,
827}
828
829#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, FromPrimitive, IntoPrimitive)]
831#[repr(i32)]
832pub enum BalanceType {
833 #[num_enum(default)]
835 Unknown = 0,
836 Cash = 1,
838 Stock = 2,
840 Fund = 3,
842}
843
844impl Serialize for BalanceType {
845 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
846 let value: i32 = (*self).into();
847 value.serialize(serializer)
848 }
849}
850
851impl<'de> Deserialize<'de> for BalanceType {
852 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
853 let value = i32::deserialize(deserializer)?;
854 Ok(BalanceType::from(value))
855 }
856}
857
858#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, FromPrimitive, Serialize)]
860#[repr(i32)]
861pub enum CashFlowDirection {
862 #[num_enum(default)]
864 Unknown,
865 Out = 1,
867 In = 2,
869}
870
871impl<'de> Deserialize<'de> for CashFlowDirection {
872 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
873 let value = i32::deserialize(deserializer)?;
874 Ok(CashFlowDirection::from(value))
875 }
876}
877
878#[derive(Debug, Clone, Serialize, Deserialize)]
880pub struct CashFlow {
881 pub transaction_flow_name: String,
883 pub direction: CashFlowDirection,
885 pub business_type: BalanceType,
887 pub balance: Decimal,
889 pub currency: String,
891 #[serde(
893 serialize_with = "time::serde::rfc3339::serialize",
894 deserialize_with = "serde_utils::timestamp::deserialize"
895 )]
896 pub business_time: OffsetDateTime,
897 #[serde(with = "serde_utils::symbol_opt")]
899 pub symbol: Option<String>,
900 pub description: String,
902}
903
904#[derive(Debug, Clone, Serialize, Deserialize)]
906pub struct FundPositionsResponse {
907 #[serde(rename = "list")]
909 pub channels: Vec<FundPositionChannel>,
910}
911
912#[derive(Debug, Clone, Serialize, Deserialize)]
914pub struct FundPositionChannel {
915 pub account_channel: String,
917
918 #[serde(
920 default,
921 deserialize_with = "crate::serde_utils::null_as_default",
922 rename = "fund_info"
923 )]
924 pub positions: Vec<FundPosition>,
925}
926
927#[derive(Debug, Clone, Serialize, Deserialize)]
929pub struct FundPosition {
930 #[serde(alias = "symbol")]
932 pub counter_id: String,
933 #[serde(with = "serde_utils::decimal_empty_is_0")]
935 pub current_net_asset_value: Decimal,
936 #[serde(
938 serialize_with = "time::serde::rfc3339::serialize",
939 deserialize_with = "serde_utils::timestamp::deserialize"
940 )]
941 pub net_asset_value_day: OffsetDateTime,
942 pub symbol_name: String,
944 pub currency: String,
946 #[serde(with = "serde_utils::decimal_empty_is_0")]
948 pub cost_net_asset_value: Decimal,
949 #[serde(with = "serde_utils::decimal_empty_is_0")]
951 pub holding_units: Decimal,
952}
953
954#[derive(Debug, Clone, Serialize, Deserialize)]
956pub struct StockPositionsResponse {
957 #[serde(rename = "list")]
959 pub channels: Vec<StockPositionChannel>,
960}
961
962#[derive(Debug, Clone, Serialize, Deserialize)]
964pub struct StockPositionChannel {
965 pub account_channel: String,
967
968 #[serde(
970 default,
971 deserialize_with = "crate::serde_utils::null_as_default",
972 rename = "stock_info"
973 )]
974 pub positions: Vec<StockPosition>,
975}
976
977#[derive(Debug, Clone, Serialize, Deserialize)]
979pub struct StockPosition {
980 pub symbol: String,
982 pub symbol_name: String,
984 pub quantity: Decimal,
986 pub available_quantity: Decimal,
988 pub currency: String,
990 #[serde(with = "serde_utils::decimal_empty_is_0")]
993 pub cost_price: Decimal,
994 pub market: Market,
996 #[serde(with = "serde_utils::decimal_opt_empty_is_none")]
998 pub init_quantity: Option<Decimal>,
999}
1000
1001#[derive(Debug, Clone, Serialize, Deserialize)]
1003pub struct MarginRatio {
1004 pub im_factor: Decimal,
1006 pub mm_factor: Decimal,
1008 pub fm_factor: Decimal,
1010}
1011
1012impl_serde_for_enum_string!(
1013 OrderType,
1014 OrderStatus,
1015 OrderSide,
1016 TriggerPriceType,
1017 OrderTag,
1018 TimeInForceType,
1019 TriggerStatus,
1020 OutsideRTH,
1021 CommissionFreeStatus,
1022 DeductionStatus,
1023 ChargeCategoryCode
1024);
1025impl_serde_for_enum_string!(AttachedOrderType);
1026impl_default_for_enum_string!(AttachedOrderType);
1027impl_serde_for_enum_string!(MultiLegStrategy, MultiLegPosition, ContractDirection);
1028impl_default_for_enum_string!(MultiLegStrategy, MultiLegPosition, ContractDirection);
1029impl_default_for_enum_string!(
1030 OrderType,
1031 OrderStatus,
1032 OrderSide,
1033 TriggerPriceType,
1034 OrderTag,
1035 TimeInForceType,
1036 TriggerStatus,
1037 OutsideRTH,
1038 CommissionFreeStatus,
1039 DeductionStatus,
1040 ChargeCategoryCode
1041);
1042
1043#[derive(Debug, Clone, Default)]
1055pub struct GetUSHistoryOrders {
1056 pub symbol: Option<String>,
1058 pub side: OrderSide,
1060 pub start_at: i64,
1062 pub end_at: i64,
1064 pub query_type: i32,
1066 pub page: i32,
1068 pub limit: i32,
1070}
1071
1072pub type QueryUSOrdersOptions = GetUSHistoryOrders;
1074
1075#[derive(Debug, Clone, Serialize)]
1077pub(crate) struct USQueryOrdersBody {
1078 pub account_channel: String,
1079 pub action: i32,
1080 pub start_at: f64,
1081 pub end_at: f64,
1082 pub symbols: Vec<String>,
1083 pub security_types: Vec<String>,
1084 pub query_type: i32,
1085 pub page: i32,
1086 pub limit: i32,
1087 pub query_version: f64,
1088}
1089
1090#[derive(Debug, Clone, Serialize, Deserialize, Default)]
1092pub struct QueryUSOrdersResponse {
1093 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1096 pub orders: Vec<serde_json::Value>,
1097 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1099 pub total_count: i32,
1100}
1101
1102#[derive(Debug, Clone, Serialize, Deserialize, Default)]
1104pub struct USOrderHistory {
1105 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1107 pub exec_type: i32,
1108 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1110 pub status: String,
1111 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1113 pub price: String,
1114 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1116 pub qty: String,
1117 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1119 pub time: String,
1120 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1122 pub msg: String,
1123 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1125 pub is_manually: bool,
1126 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1128 pub opp_party_id: String,
1129 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1131 pub trd_match_id: String,
1132 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1134 pub operator: String,
1135 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1137 pub op_entrust_way: String,
1138 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1140 pub cxl_rej_response_to: i32,
1141 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1143 pub withdrawal_reason: String,
1144 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1146 pub opp_name: String,
1147 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1149 pub exec_id: String,
1150}
1151
1152#[derive(Debug, Clone, Serialize, Deserialize, Default)]
1154pub struct USButtonControl {
1155 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1157 pub withdraw: i32,
1158 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1160 pub replace: i32,
1161 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1163 pub exceptionable: Vec<String>,
1164}
1165
1166#[derive(Debug, Clone, Serialize, Deserialize, Default)]
1168pub struct USChargeItem {
1169 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1171 pub code: i32,
1172 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1174 pub name: String,
1175 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1177 pub fees: Vec<String>,
1178}
1179
1180#[derive(Debug, Clone, Serialize, Deserialize, Default)]
1182pub struct USChargeDetail {
1183 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1185 pub currency: String,
1186 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1188 pub total_amount: String,
1189 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1191 pub items: Vec<USChargeItem>,
1192}
1193
1194#[derive(Debug, Clone, Serialize, Deserialize, Default)]
1196pub struct USAttachedOrder {
1197 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1199 pub attached_type_display: i32,
1200 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1202 pub executed_qty: String,
1203 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1205 pub quantity: String,
1206 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1208 pub status: String,
1209 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1211 pub trigger_price: String,
1212 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1214 pub order_id: String,
1215 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1217 pub gtd: String,
1218 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1220 pub time_in_force: i32,
1221 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1223 pub tag: i32,
1224 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1226 pub activate_order_type: String,
1227 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1229 pub activate_rth: i32,
1230 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1232 pub submit_price: String,
1233 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1235 pub symbol: String,
1236 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1238 pub withdrawn: bool,
1239}
1240
1241#[derive(Debug, Clone, Serialize, Deserialize, Default)]
1246pub struct USOrderDetail {
1247 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1249 pub id: String,
1250 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1252 pub aaid: String,
1253 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1255 pub account_channel: String,
1256 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1258 pub action: i32,
1259 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1261 pub symbol: String,
1262 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1264 pub underlying_symbol: String,
1265 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1267 pub security_type: String,
1268 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1270 pub name: String,
1271 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1273 pub currency: String,
1274 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1276 pub trade_currency: String,
1277 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1279 pub order_type: String,
1280 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1282 pub status: String,
1283 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1285 pub price: String,
1286 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1288 pub quantity: String,
1289 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1291 pub executed_qty: String,
1292 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1294 pub executed_price: String,
1295 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1297 pub executed_amount: String,
1298 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1300 pub operate_direction: String,
1301 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1303 pub time_in_force: i32,
1304 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1306 pub gtd: String,
1307 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1309 pub tag: i32,
1310 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1312 pub msg: String,
1313 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1315 pub force_only_rth: i32,
1316 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1318 pub submitted_at: String,
1319 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1321 pub done_at: String,
1322 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1324 pub trigger_price: String,
1325 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1327 pub trigger_at: String,
1328 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1330 pub trigger_status: i32,
1331 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1333 pub trigger_exchange: String,
1334 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1336 pub trigger_last_done: String,
1337 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1339 pub trigger_count: i32,
1340 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1342 pub tailing_amount: String,
1343 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1345 pub tailing_percent: String,
1346 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1348 pub limit_offset: String,
1349 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1351 pub limit_depth_level: i32,
1352 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1354 pub market_price: String,
1355 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1357 pub submitted_amount: String,
1358 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1360 pub estimated_fee: String,
1361 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1363 pub free_status: i32,
1364 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1366 pub free_amount: String,
1367 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1369 pub free_currency: String,
1370 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1372 pub deductions_status: i32,
1373 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1375 pub deductions_amount: String,
1376 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1378 pub deductions_currency: String,
1379 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1381 pub platform_deductions_status: i32,
1382 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1384 pub platform_deductions_amount: String,
1385 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1387 pub platform_deductions_currency: String,
1388 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1390 pub display_account: String,
1391 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1393 pub settlement_account: String,
1394 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1396 pub settlement_channel: String,
1397 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1399 pub customer_name: String,
1400 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1402 pub real_name: String,
1403 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1405 pub en_name: String,
1406 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1408 pub joint_real_name: String,
1409 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1411 pub joint_en_name: String,
1412 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1414 pub org_id: String,
1415 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1417 pub bcan: String,
1418 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1420 pub op_entrust_way: i32,
1421 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1423 pub op_entrust_way_name: String,
1424 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1426 pub remark: String,
1427 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1429 pub notice: String,
1430 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1432 pub short_sell_type: i32,
1433 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1435 pub ploy_type: String,
1436 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1438 pub ploy_id: String,
1439 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1441 pub ploy_status: String,
1442 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1444 pub trend: i32,
1445 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1447 pub withdrawal_reason: String,
1448 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1450 pub activate_order_type: String,
1451 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1453 pub activate_rth: i32,
1454 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1456 pub submit_price: String,
1457 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1459 pub contract_direction: String,
1460 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1462 pub strike_price: String,
1463 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1465 pub contract_size: String,
1466 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1468 pub monitor_price: String,
1469 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1471 pub button_control: USButtonControl,
1472 pub charge_detail: Option<USChargeDetail>,
1474 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1476 pub attached_orders: Vec<USAttachedOrder>,
1477 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1479 pub order_histories: Vec<USOrderHistory>,
1480}
1481
1482#[derive(Debug, Clone, Serialize, Deserialize, Default)]
1485pub struct USOrderDetailResponse {
1486 pub order: Option<USOrderDetail>,
1488 pub current_attached_order: Option<USOrderDetail>,
1490 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1492 pub current_millisecond: String,
1493}
1494
1495#[derive(Debug, Clone, Serialize, Deserialize, Default)]
1497pub struct USCashEntry {
1498 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1500 pub currency: String,
1501 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1503 pub frozen_buy_cash: String,
1504 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1506 pub outstanding: String,
1507 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1509 pub settled_cash: String,
1510 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1512 pub total_amount: String,
1513 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1515 pub total_cash: String,
1516}
1517
1518#[derive(Debug, Clone, Serialize, Deserialize, Default)]
1520pub struct USCryptoEntry {
1521 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1523 pub asset_type: String,
1524 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1527 pub symbol: String,
1528 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1530 pub quantity: String,
1531 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1533 pub currency: String,
1534 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1536 pub average_cost: String,
1537 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1539 pub market: String,
1540 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1542 pub trade_status: String,
1543 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1545 pub prev_close: String,
1546 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1548 pub last_done: String,
1549 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1551 pub market_price: String,
1552 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1554 pub pretrade_close: String,
1555 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1557 pub stock_invest_of_today: String,
1558 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1560 pub today_pl: String,
1561 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1563 pub pretrade_stock_invest_of_today: String,
1564 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1566 pub pretrade_today_pl: String,
1567 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1569 pub night_last_done: String,
1570 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1572 pub night_prev_close: String,
1573 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1575 pub position_side: String,
1576 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1578 pub open_position_time: String,
1579 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1581 pub name: String,
1582 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1584 pub industry_counter_id: String,
1585 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1587 pub industry_name: String,
1588}
1589
1590#[derive(Debug, Clone, Serialize, Deserialize, Default)]
1592pub struct USStockEntry {
1593 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1596 pub symbol: String,
1597 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1599 pub full_symbol: String,
1600 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1602 pub asset_type: String,
1603 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1605 pub quantity: String,
1606 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1608 pub currency: String,
1609 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1611 pub average_cost: String,
1612 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1614 pub market: String,
1615 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1617 pub trade_status: String,
1618 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1620 pub prev_close: String,
1621 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1623 pub last_done: String,
1624 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1626 pub market_price: String,
1627 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1629 pub pretrade_close: String,
1630 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1632 pub stock_invest_of_today: String,
1633 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1635 pub today_pl: String,
1636 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1638 pub pretrade_stock_invest_of_today: String,
1639 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1641 pub pretrade_today_pl: String,
1642 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1644 pub night_last_done: String,
1645 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1647 pub night_prev_close: String,
1648 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1650 pub position_side: String,
1651 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1653 pub open_position_time: String,
1654 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1656 pub name: String,
1657 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1659 pub industry_counter_id: String,
1660 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1662 pub industry_name: String,
1663}
1664
1665#[derive(Debug, Clone, Serialize, Deserialize, Default)]
1668pub struct USAssetOverview {
1669 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1671 pub account_type: String,
1672 #[serde(default, with = "crate::serde_utils::timestamp_opt")]
1674 pub asset_timestamp: Option<time::OffsetDateTime>,
1675 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1677 pub cash_buy_power: String,
1678 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1680 pub overnight_buy_power: String,
1681 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1683 pub currency: String,
1684 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1686 pub cash_list: Vec<USCashEntry>,
1687 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1689 pub stock_list: Vec<USStockEntry>,
1690 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1692 pub option_list: Vec<serde_json::Value>,
1693 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1695 pub crypto_list: Vec<USCryptoEntry>,
1696 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1698 pub multi_leg: serde_json::Value,
1699}
1700
1701#[derive(Debug, Clone, Serialize, Deserialize, Default)]
1703pub struct USRealizedPLMetric {
1704 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1706 pub amount: String,
1707 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1709 pub period: i32,
1710 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1712 pub rate: String,
1713}
1714
1715#[derive(Debug, Clone, Serialize, Deserialize, Default)]
1718pub struct USRealizedPLEntry {
1719 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1721 pub category: i32,
1722 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1724 pub currency: String,
1725 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1727 pub metrics: Vec<USRealizedPLMetric>,
1728}
1729
1730#[derive(Debug, Clone, Default)]
1733pub struct GetUSRealizedPLOptions {
1734 pub currency: String,
1736 pub category: String,
1738}
1739
1740#[derive(Debug, Clone, Serialize, Deserialize, Default)]
1744pub struct USRealizedPL {
1745 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1747 pub realized_pl_list: Vec<USRealizedPLEntry>,
1748}
1749
1750#[derive(Debug, Clone, Default)]
1752pub struct GetUSAvailableHoldingOptions {
1753 pub symbol: String,
1756 pub equity_type: String,
1759 pub position_side: String,
1761}
1762
1763#[derive(Debug, Clone, Serialize, Deserialize, Default)]
1765pub struct USAvailableHolding {
1766 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1769 pub available_qty: String,
1770}
1771
1772#[derive(Debug, Clone, Serialize, Deserialize, Default)]
1774pub struct USBuyLimitInfo {
1775 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1777 pub currency: String,
1778 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1780 pub cash_buy_power: String,
1781 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1783 pub overnight_buy_power: String,
1784 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1786 pub day_trade_buy_power: String,
1787 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1789 pub instant_buy_power: String,
1790 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1792 pub intraday_margin_buy_power: String,
1793 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1795 pub option_buy_power: String,
1796 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1798 pub crypto_buy_power: String,
1799 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1801 pub mme: String,
1802}
1803
1804#[derive(Debug, Clone, Serialize, Deserialize, Default)]
1806pub struct USCashDetail {
1807 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1809 pub currency: String,
1810 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1812 pub total_amount: String,
1813 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1815 pub available_cash: String,
1816 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1818 pub withdraw_cash: String,
1819 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1821 pub frozen_cash: String,
1822 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1824 pub settled_cash: String,
1825 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1827 pub outstanding: String,
1828 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1830 pub external_amount: String,
1831}
1832
1833#[derive(Debug, Clone, Serialize, Deserialize, Default)]
1835pub struct USMarginStatus {
1836 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1838 pub currency: String,
1839 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1841 pub nlv: String,
1842 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1844 pub margin_equity: String,
1845 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1847 pub margin_maintenance_excess: String,
1848 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1850 pub mmr: String,
1851 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1853 pub reg_t_excess: String,
1854 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1856 pub sma: String,
1857}
1858
1859#[derive(Debug, Clone, Serialize, Deserialize, Default)]
1862pub struct USCashAndBuyPower {
1863 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1865 pub buy_limit_info: USBuyLimitInfo,
1866 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1868 pub cash_list: Vec<USCashDetail>,
1869 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1871 pub margin_status: USMarginStatus,
1872}
1873
1874#[derive(Debug, Clone)]
1876pub struct GetUSTransactionHistoryOptions {
1877 pub start_at: OffsetDateTime,
1879 pub end_at: OffsetDateTime,
1881 pub page: i32,
1883 pub size: i32,
1885 pub types: Vec<String>,
1891 pub currency: String,
1893}
1894
1895impl GetUSTransactionHistoryOptions {
1896 pub fn new(start_at: OffsetDateTime, end_at: OffsetDateTime) -> Self {
1898 Self {
1899 start_at,
1900 end_at,
1901 page: 1,
1902 size: 20,
1903 types: Vec::new(),
1904 currency: String::new(),
1905 }
1906 }
1907}
1908
1909#[derive(Debug, Clone, Serialize, Deserialize, Default)]
1911pub struct USTransaction {
1912 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1914 pub title: String,
1915 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1917 pub biz_code: String,
1918 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1920 pub biz_code_name: String,
1921 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1923 pub amount: String,
1924 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1926 pub currency: String,
1927 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1929 pub symbol: String,
1930 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1932 pub market: String,
1933 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1935 pub equity_type: String,
1936 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1938 pub position_side: String,
1939 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1941 pub quantity: String,
1942 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1944 pub quantity_description: String,
1945 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1947 pub ref_no: String,
1948 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1950 pub remark: String,
1951 #[serde(
1953 default,
1954 deserialize_with = "crate::serde_utils::timestamp_opt_lenient::deserialize",
1955 serialize_with = "crate::serde_utils::rfc3339_opt::serialize"
1956 )]
1957 pub create_at: Option<OffsetDateTime>,
1958 #[serde(
1960 default,
1961 deserialize_with = "crate::serde_utils::timestamp_opt_lenient::deserialize",
1962 serialize_with = "crate::serde_utils::rfc3339_opt::serialize"
1963 )]
1964 pub done_at: Option<OffsetDateTime>,
1965}
1966
1967#[derive(Debug, Clone, Serialize, Deserialize, Default)]
1970pub struct USTransactionHistory {
1971 #[serde(default, with = "crate::serde_utils::int64_str")]
1973 pub current_page: i64,
1974 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
1977 pub items: Vec<USTransaction>,
1978}
1979
1980#[derive(Debug, Clone, Serialize, Deserialize, Default)]
1982pub struct USExchangeRate {
1983 pub universal_key: String,
1986 pub spec_key: String,
1988 pub base_currency: String,
1990 pub other_currency: String,
1992 pub bid_rate: String,
1994 pub offer_rate: String,
1996 pub average_rate: String,
1998}
1999
2000#[derive(Debug, Clone, Serialize, Deserialize, Default)]
2008#[serde(from = "RawUSExchangeRates")]
2009pub struct USExchangeRates {
2010 pub exchanges: Vec<USExchangeRate>,
2012}
2013
2014#[derive(Deserialize, Default)]
2015struct RawUSExchangeRate {
2016 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
2017 base_currency: String,
2018 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
2019 other_currency: String,
2020 #[serde(default, with = "crate::serde_utils::value_as_opt_string")]
2021 bid_rate: Option<String>,
2022 #[serde(default, with = "crate::serde_utils::value_as_opt_string")]
2023 offer_rate: Option<String>,
2024 #[serde(default, with = "crate::serde_utils::value_as_opt_string")]
2025 average_rate: Option<String>,
2026}
2027
2028#[derive(Deserialize, Default)]
2029struct RawUSSpecExchanges {
2030 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
2031 exchanges: Vec<RawUSExchangeRate>,
2032}
2033
2034#[derive(Deserialize, Default)]
2035struct RawUSUniversalExchanges {
2036 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
2037 spec_exchanges: BTreeMap<String, RawUSSpecExchanges>,
2038}
2039
2040#[derive(Deserialize, Default)]
2041struct RawUSExchangeRates {
2042 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
2043 universal_exchanges: BTreeMap<String, RawUSUniversalExchanges>,
2044 #[serde(default, deserialize_with = "crate::serde_utils::null_as_default")]
2047 exchanges: Vec<USExchangeRate>,
2048}
2049
2050impl From<RawUSExchangeRates> for USExchangeRates {
2051 fn from(raw: RawUSExchangeRates) -> Self {
2052 let mut exchanges = raw.exchanges;
2053 for (universal_key, universal) in raw.universal_exchanges {
2054 for (spec_key, spec) in universal.spec_exchanges {
2055 exchanges.extend(spec.exchanges.into_iter().map(|rate| USExchangeRate {
2056 universal_key: universal_key.clone(),
2057 spec_key: spec_key.clone(),
2058 base_currency: rate.base_currency,
2059 other_currency: rate.other_currency,
2060 bid_rate: rate.bid_rate.unwrap_or_default(),
2061 offer_rate: rate.offer_rate.unwrap_or_default(),
2062 average_rate: rate.average_rate.unwrap_or_default(),
2063 }));
2064 }
2065 }
2066 Self { exchanges }
2067 }
2068}
2069
2070#[cfg(test)]
2071mod tests {
2072 use time::macros::datetime;
2073
2074 use super::*;
2075
2076 #[test]
2077 fn execution_without_side_defaults_to_unknown() {
2078 let data = r#"{
2081 "order_id": "1000000000000000001",
2082 "trade_id": "1000000000000000001-1",
2083 "symbol": "ETHUSD.BKKT",
2084 "trade_done_at": "1790203596",
2085 "quantity": "0.168",
2086 "price": "2690.34165646"
2087 }"#;
2088 let execution: Execution = serde_json::from_str(data).expect("execution without side");
2089 assert_eq!(
2090 execution.side,
2091 OrderSide::Unknown,
2092 "missing side defaults to Unknown"
2093 );
2094 assert_eq!(execution.symbol, "ETHUSD.BKKT", "symbol is kept");
2095 }
2096
2097 #[test]
2098 fn us_exchange_rates_response() {
2099 let data = r#"{
2100 "universal_exchanges": {
2101 "2": {"spec_exchanges": {"USD": {"exchanges": [
2102 {"base_currency": "USD", "other_currency": "HKD", "bid_rate": 7.8, "offer_rate": "7.85", "average_rate": null}
2103 ]}}},
2104 "1": {"spec_exchanges": {
2105 "b": {"exchanges": [{"base_currency": "USD", "other_currency": "CNH", "bid_rate": "7.1"}]},
2106 "a": {"exchanges": null}
2107 }}
2108 }
2109 }"#;
2110 let resp: USExchangeRates = serde_json::from_str(data).unwrap();
2111 assert_eq!(resp.exchanges.len(), 2);
2112 assert_eq!(resp.exchanges[0].universal_key, "1");
2113 assert_eq!(resp.exchanges[0].spec_key, "b");
2114 assert_eq!(resp.exchanges[0].other_currency, "CNH");
2115 assert_eq!(resp.exchanges[0].offer_rate, "");
2116 assert_eq!(resp.exchanges[1].universal_key, "2");
2117 assert_eq!(resp.exchanges[1].spec_key, "USD");
2118 assert_eq!(resp.exchanges[1].bid_rate, "7.8");
2119 assert_eq!(resp.exchanges[1].offer_rate, "7.85");
2120 assert_eq!(resp.exchanges[1].average_rate, "");
2121
2122 let json = serde_json::to_string(&resp).unwrap();
2123 let back: USExchangeRates = serde_json::from_str(&json).unwrap();
2124 assert_eq!(back.exchanges.len(), 2);
2125 assert_eq!(back.exchanges[1].bid_rate, "7.8");
2126 }
2127
2128 #[test]
2129 fn us_cash_and_buy_power_response() {
2130 let data = r#"{
2131 "buy_limit_info": {"currency": "USD", "cash_buy_power": "100.5", "overnight_buy_power": "90"},
2132 "cash_list": [{"currency": "USD", "total_amount": "120", "available_cash": "100.5", "withdraw_cash": "80", "frozen_cash": null}],
2133 "margin_status": {"currency": "USD", "nlv": "1200"}
2134 }"#;
2135 let resp: USCashAndBuyPower = serde_json::from_str(data).unwrap();
2136 assert_eq!(resp.buy_limit_info.cash_buy_power, "100.5");
2137 assert_eq!(resp.cash_list[0].withdraw_cash, "80");
2138 assert_eq!(resp.cash_list[0].frozen_cash, "");
2139 assert_eq!(resp.margin_status.nlv, "1200");
2140 }
2141
2142 #[test]
2143 fn us_transaction_history_response() {
2144 let data = r#"{
2145 "current_page": "2",
2146 "items": [
2147 {"title": "Buy", "amount": "-100", "currency": "USD", "symbol": "AAPL.US", "create_at": "1792600000", "done_at": 1792600060},
2148 {"title": "Buy", "amount": "-10", "currency": "USD", "symbol": "BTCUSD.BKKT", "counter_id": "VA/BKKT/BTCUSD", "done_at": "0"}
2149 ]
2150 }"#;
2151 let resp: USTransactionHistory = serde_json::from_str(data).unwrap();
2152 assert_eq!(resp.current_page, 2);
2153 assert_eq!(resp.items[0].symbol, "AAPL.US");
2154 assert_eq!(
2155 resp.items[0].create_at.unwrap().unix_timestamp(),
2156 1792600000
2157 );
2158 assert_eq!(resp.items[0].done_at.unwrap().unix_timestamp(), 1792600060);
2159 assert_eq!(resp.items[1].symbol, "BTCUSD.BKKT");
2160 assert!(resp.items[1].done_at.is_none());
2161 }
2162
2163 #[test]
2165 fn us_asset_responses_as_returned_by_prod() {
2166 let data = r#"{
2167 "current_page": 1,
2168 "items": [{
2169 "amount": "0.5", "biz_code": "LVAVNSCR", "biz_code_name": "Cryptocurrency Deposit",
2170 "counter_id": "VA/BKKT/BTCUSD", "create_at": "1791478565", "currency": "",
2171 "done_at": "1791478565", "equity_type": "CRYPTO", "market": "BKKT",
2172 "position_side": "LONG", "quantity": "0.5", "quantity_description": "0.5",
2173 "ref_no": "R1", "remark": "", "symbol": "BTCUSD.BKKT",
2174 "title": "Cryptocurrency Deposit: BTCUSD"
2175 }]
2176 }"#;
2177 let resp: USTransactionHistory = serde_json::from_str(data).unwrap();
2178 assert_eq!(resp.current_page, 1);
2179 assert_eq!(resp.items[0].symbol, "BTCUSD.BKKT");
2180 assert_eq!(resp.items[0].equity_type, "CRYPTO");
2181 assert_eq!(resp.items[0].done_at.unwrap().unix_timestamp(), 1791478565);
2182
2183 let data = r#"{"universal_exchanges": {"us_lb_apex": {"spec_exchanges": {
2184 "reference": {"exchanges": [{"average_rate": 1, "base_currency": "USD", "bid_rate": 1, "offer_rate": 1, "other_currency": "USD"}]},
2185 "am": {"exchanges": [{"average_rate": 1, "base_currency": "USD", "bid_rate": 1, "offer_rate": 1, "other_currency": "USD"}]}
2186 }}}}"#;
2187 let resp: USExchangeRates = serde_json::from_str(data).unwrap();
2188 let keys: Vec<_> = resp
2189 .exchanges
2190 .iter()
2191 .map(|e| (e.universal_key.as_str(), e.spec_key.as_str()))
2192 .collect();
2193 assert_eq!(keys, [("us_lb_apex", "am"), ("us_lb_apex", "reference")]);
2194 assert_eq!(resp.exchanges[0].average_rate, "1");
2195
2196 let data = r#"{
2197 "account_type": "1", "asset_timestamp": "1792661597", "currency": "USD",
2198 "stock_list": [{"symbol": "MULL", "full_symbol": "MULL.US", "counter_id": "ETF/US/MULL", "quantity": "10", "market": "US"}],
2199 "crypto_list": [{
2200 "asset_type": "CRYPTO", "average_cost": "", "counter_id": "VA/BKKT/ETHUSD",
2201 "currency": "USD", "last_done": "2500", "market": "BKKT", "market_price": "2500",
2202 "name": "Ethereum", "open_position_time": "0", "position_side": "LONG",
2203 "quantity": "0.1", "symbol": "ETHUSD", "today_pl": "", "trade_status": "105"
2204 }],
2205 "option_list": [], "multi_leg": {"holdings": []}
2206 }"#;
2207 let resp: USAssetOverview = serde_json::from_str(data).unwrap();
2208 assert_eq!(resp.asset_timestamp.unwrap().unix_timestamp(), 1792661597);
2209 assert_eq!(resp.stock_list[0].full_symbol, "MULL.US");
2210 let crypto = &resp.crypto_list[0];
2211 assert_eq!(crypto.symbol, "ETHUSD");
2212 assert_eq!(crypto.market, "BKKT");
2213 assert_eq!(crypto.quantity, "0.1");
2214 assert_eq!(crypto.name, "Ethereum");
2215 assert_eq!(crypto.market_price, "2500");
2216 }
2217
2218 #[test]
2219 fn fund_position_response() {
2220 let data = r#"
2221 {
2222 "list": [{
2223 "account_channel": "lb",
2224 "fund_info": [{
2225 "counter_id": "UT/FD/HK0000447943",
2226 "symbol_name": "高腾亚洲收益基金",
2227 "currency": "USD",
2228 "holding_units": "5.000",
2229 "current_net_asset_value": "0",
2230 "cost_net_asset_value": "0.00",
2231 "net_asset_value_day": "1649865600"
2232 }]
2233 }]
2234 }
2235 "#;
2236
2237 let resp: FundPositionsResponse = serde_json::from_str(data).unwrap();
2238 assert_eq!(resp.channels.len(), 1);
2239
2240 let channel = &resp.channels[0];
2241 assert_eq!(channel.account_channel, "lb");
2242 assert_eq!(channel.positions.len(), 1);
2243
2244 let position = &channel.positions[0];
2245 assert_eq!(position.counter_id, "UT/FD/HK0000447943");
2246 assert_eq!(position.symbol_name, "高腾亚洲收益基金");
2247 assert_eq!(position.currency, "USD");
2248 assert_eq!(position.current_net_asset_value, decimal!(0i32));
2249 assert_eq!(position.cost_net_asset_value, decimal!(0i32));
2250 assert_eq!(position.holding_units, decimal!(5i32));
2251 assert_eq!(position.net_asset_value_day, datetime!(2022-4-14 0:00 +8));
2252
2253 let legacy = r#"{"list":[{"account_channel":"lb","fund_info":[{"symbol":"HK0000447943","symbol_name":"x","currency":"USD","holding_units":"0","current_net_asset_value":"0","cost_net_asset_value":"0","net_asset_value_day":"1649865600"}]}]}"#;
2256 let resp: FundPositionsResponse = serde_json::from_str(legacy).unwrap();
2257 assert_eq!(resp.channels[0].positions[0].counter_id, "HK0000447943");
2258 }
2259
2260 #[test]
2261 fn stock_position_response() {
2262 let data = r#"
2263 {
2264 "list": [
2265 {
2266 "account_channel": "lb",
2267 "stock_info": [
2268 {
2269 "symbol": "700.HK",
2270 "symbol_name": "腾讯控股",
2271 "currency": "HK",
2272 "quantity": "650",
2273 "available_quantity": "-450",
2274 "cost_price": "457.53",
2275 "market": "HK",
2276 "init_quantity": "2000"
2277 },
2278 {
2279 "symbol": "9991.HK",
2280 "symbol_name": "宝尊电商-SW",
2281 "currency": "HK",
2282 "quantity": "200",
2283 "available_quantity": "0",
2284 "cost_price": "32.25",
2285 "market": "HK",
2286 "init_quantity": ""
2287 }
2288 ]
2289 }
2290 ]
2291 }
2292 "#;
2293
2294 let resp: StockPositionsResponse = serde_json::from_str(data).unwrap();
2295 assert_eq!(resp.channels.len(), 1);
2296
2297 let channel = &resp.channels[0];
2298 assert_eq!(channel.account_channel, "lb");
2299 assert_eq!(channel.positions.len(), 2);
2300
2301 let position = &channel.positions[0];
2302 assert_eq!(position.symbol, "700.HK");
2303 assert_eq!(position.symbol_name, "腾讯控股");
2304 assert_eq!(position.currency, "HK");
2305 assert_eq!(position.quantity, decimal!(650));
2306 assert_eq!(position.available_quantity, decimal!(-450));
2307 assert_eq!(position.cost_price, decimal!(457.53f32));
2308 assert_eq!(position.market, Market::HK);
2309 assert_eq!(position.init_quantity, Some(decimal!(2000)));
2310
2311 let position = &channel.positions[0];
2312 assert_eq!(position.symbol, "700.HK");
2313 assert_eq!(position.symbol_name, "腾讯控股");
2314 assert_eq!(position.currency, "HK");
2315 assert_eq!(position.quantity, decimal!(650));
2316 assert_eq!(position.available_quantity, decimal!(-450));
2317 assert_eq!(position.cost_price, decimal!(457.53f32));
2318 assert_eq!(position.market, Market::HK);
2319
2320 let position = &channel.positions[1];
2321 assert_eq!(position.symbol, "9991.HK");
2322 assert_eq!(position.symbol_name, "宝尊电商-SW");
2323 assert_eq!(position.currency, "HK");
2324 assert_eq!(position.quantity, decimal!(200));
2325 assert_eq!(position.available_quantity, decimal!(0));
2326 assert_eq!(position.cost_price, decimal!(32.25f32));
2327 assert_eq!(position.init_quantity, None);
2328 }
2329
2330 #[test]
2331 fn cash_flow() {
2332 let data = r#"
2333 {
2334 "list": [
2335 {
2336 "transaction_flow_name": "BuyContract-Stocks",
2337 "direction": 1,
2338 "balance": "-248.60",
2339 "currency": "USD",
2340 "business_type": 1,
2341 "business_time": "1621507957",
2342 "symbol": "AAPL.US",
2343 "description": "AAPL"
2344 },
2345 {
2346 "transaction_flow_name": "BuyContract-Stocks",
2347 "direction": 1,
2348 "balance": "-125.16",
2349 "currency": "USD",
2350 "business_type": 2,
2351 "business_time": "1621504824",
2352 "symbol": "AAPL.US",
2353 "description": "AAPL"
2354 }
2355 ]
2356 }
2357 "#;
2358
2359 #[derive(Debug, Deserialize)]
2360 struct Response {
2361 list: Vec<CashFlow>,
2362 }
2363
2364 let resp: Response = serde_json::from_str(data).unwrap();
2365 assert_eq!(resp.list.len(), 2);
2366
2367 let cashflow = &resp.list[0];
2368 assert_eq!(cashflow.transaction_flow_name, "BuyContract-Stocks");
2369 assert_eq!(cashflow.direction, CashFlowDirection::Out);
2370 assert_eq!(cashflow.balance, decimal!(-248.60f32));
2371 assert_eq!(cashflow.currency, "USD");
2372 assert_eq!(cashflow.business_type, BalanceType::Cash);
2373 assert_eq!(cashflow.business_time, datetime!(2021-05-20 18:52:37 +8));
2374 assert_eq!(cashflow.symbol.as_deref(), Some("AAPL.US"));
2375 assert_eq!(cashflow.description, "AAPL");
2376
2377 let cashflow = &resp.list[1];
2378 assert_eq!(cashflow.transaction_flow_name, "BuyContract-Stocks");
2379 assert_eq!(cashflow.direction, CashFlowDirection::Out);
2380 assert_eq!(cashflow.balance, decimal!(-125.16f32));
2381 assert_eq!(cashflow.currency, "USD");
2382 assert_eq!(cashflow.business_type, BalanceType::Stock);
2383 assert_eq!(cashflow.business_time, datetime!(2021-05-20 18:00:24 +8));
2384 assert_eq!(cashflow.symbol.as_deref(), Some("AAPL.US"));
2385 assert_eq!(cashflow.description, "AAPL");
2386 }
2387
2388 #[test]
2389 fn account_balance() {
2390 let data = r#"
2391 {
2392 "list": [
2393 {
2394 "total_cash": "1759070010.72",
2395 "max_finance_amount": "977582000",
2396 "remaining_finance_amount": "0",
2397 "risk_level": "1",
2398 "margin_call": "2598051051.50",
2399 "currency": "HKD",
2400 "cash_infos": [
2401 {
2402 "withdraw_cash": "97592.30",
2403 "available_cash": "195902464.37",
2404 "frozen_cash": "11579339.13",
2405 "settling_cash": "207288537.81",
2406 "currency": "HKD"
2407 },
2408 {
2409 "withdraw_cash": "199893416.74",
2410 "available_cash": "199893416.74",
2411 "frozen_cash": "28723.76",
2412 "settling_cash": "-276806.51",
2413 "currency": "USD"
2414 }
2415 ],
2416 "net_assets": "11111.12",
2417 "init_margin": "2222.23",
2418 "maintenance_margin": "3333.45",
2419 "buy_power": "1234.67",
2420 "frozen_transaction_fees": [
2421 {
2422 "currency": "HKD",
2423 "frozen_transaction_fee": "123"
2424 }
2425 ]
2426 }
2427 ]
2428 }"#;
2429
2430 #[derive(Debug, Deserialize)]
2431 struct Response {
2432 list: Vec<AccountBalance>,
2433 }
2434
2435 let resp: Response = serde_json::from_str(data).unwrap();
2436 assert_eq!(resp.list.len(), 1);
2437
2438 let balance = &resp.list[0];
2439 assert_eq!(balance.total_cash, "1759070010.72".parse().unwrap());
2440 assert_eq!(balance.max_finance_amount, "977582000".parse().unwrap());
2441 assert_eq!(balance.remaining_finance_amount, decimal!(0i32));
2442 assert_eq!(balance.risk_level, 1);
2443 assert_eq!(balance.margin_call, "2598051051.50".parse().unwrap());
2444 assert_eq!(balance.currency, "HKD");
2445 assert_eq!(balance.net_assets, "11111.12".parse().unwrap());
2446 assert_eq!(balance.init_margin, "2222.23".parse().unwrap());
2447 assert_eq!(balance.maintenance_margin, "3333.45".parse().unwrap());
2448 assert_eq!(balance.buy_power, "1234.67".parse().unwrap());
2449
2450 assert_eq!(balance.cash_infos.len(), 2);
2451
2452 let cash_info = &balance.cash_infos[0];
2453 assert_eq!(cash_info.withdraw_cash, "97592.30".parse().unwrap());
2454 assert_eq!(cash_info.available_cash, "195902464.37".parse().unwrap());
2455 assert_eq!(cash_info.frozen_cash, "11579339.13".parse().unwrap());
2456 assert_eq!(cash_info.settling_cash, "207288537.81".parse().unwrap());
2457 assert_eq!(cash_info.currency, "HKD");
2458
2459 let cash_info = &balance.cash_infos[1];
2460 assert_eq!(cash_info.withdraw_cash, "199893416.74".parse().unwrap());
2461 assert_eq!(cash_info.available_cash, "199893416.74".parse().unwrap());
2462 assert_eq!(cash_info.frozen_cash, "28723.76".parse().unwrap());
2463 assert_eq!(cash_info.settling_cash, "-276806.51".parse().unwrap());
2464 assert_eq!(cash_info.currency, "USD");
2465
2466 assert_eq!(balance.frozen_transaction_fees.len(), 1);
2467
2468 let frozen_transaction_fee = &balance.frozen_transaction_fees[0];
2469 assert_eq!(frozen_transaction_fee.currency, "HKD");
2470 assert_eq!(
2471 frozen_transaction_fee.frozen_transaction_fee,
2472 "123".parse().unwrap()
2473 );
2474 }
2475
2476 #[test]
2477 fn history_orders() {
2478 let data = r#"
2479 {
2480 "orders": [
2481 {
2482 "currency": "HKD",
2483 "executed_price": "0.000",
2484 "executed_quantity": "0",
2485 "expire_date": "",
2486 "last_done": "",
2487 "limit_offset": "",
2488 "msg": "",
2489 "order_id": "706388312699592704",
2490 "order_type": "ELO",
2491 "outside_rth": "UnknownOutsideRth",
2492 "price": "11.900",
2493 "quantity": "200",
2494 "side": "Buy",
2495 "status": "RejectedStatus",
2496 "stock_name": "Bank of East Asia Ltd/The",
2497 "submitted_at": "1651644897",
2498 "symbol": "23.HK",
2499 "tag": "Normal",
2500 "time_in_force": "Day",
2501 "trailing_amount": "",
2502 "trailing_percent": "",
2503 "trigger_at": "0",
2504 "trigger_price": "",
2505 "trigger_status": "NOT_USED",
2506 "updated_at": "1651644898",
2507 "limit_depth_level": 0,
2508 "trigger_count": 0,
2509 "monitor_price": "",
2510 "remark": "abc",
2511 "delayed_until": "1767225600"
2512 }
2513 ]
2514 }
2515 "#;
2516
2517 #[derive(Deserialize)]
2518 struct Response {
2519 orders: Vec<Order>,
2520 }
2521
2522 let resp: Response = serde_json::from_str(data).unwrap();
2523 assert_eq!(resp.orders.len(), 1);
2524
2525 let order = &resp.orders[0];
2526 assert_eq!(order.currency, "HKD");
2527 assert!(order.executed_price.is_none());
2528 assert_eq!(order.executed_quantity, decimal!(0));
2529 assert!(order.expire_date.is_none());
2530 assert!(order.last_done.is_none());
2531 assert!(order.limit_offset.is_none());
2532 assert_eq!(order.msg, "");
2533 assert_eq!(order.order_id, "706388312699592704");
2534 assert_eq!(order.order_type, OrderType::ELO);
2535 assert!(order.outside_rth.is_none());
2536 assert_eq!(order.price, Some("11.900".parse().unwrap()));
2537 assert_eq!(order.quantity, decimal!(200));
2538 assert_eq!(order.side, OrderSide::Buy);
2539 assert_eq!(order.status, OrderStatus::Rejected);
2540 assert_eq!(order.stock_name, "Bank of East Asia Ltd/The");
2541 assert_eq!(order.submitted_at, datetime!(2022-05-04 14:14:57 +8));
2542 assert_eq!(order.symbol, "23.HK");
2543 assert_eq!(order.tag, OrderTag::Normal);
2544 assert_eq!(order.time_in_force, TimeInForceType::Day);
2545 assert!(order.trailing_amount.is_none());
2546 assert!(order.trailing_percent.is_none());
2547 assert!(order.trigger_at.is_none());
2548 assert!(order.trigger_price.is_none());
2549 assert!(order.trigger_status.is_none());
2550 assert_eq!(order.updated_at, Some(datetime!(2022-05-04 14:14:58 +8)));
2551 assert_eq!(order.remark, "abc");
2552 assert_eq!(order.delayed_until, Some(datetime!(2026-01-01 00:00 UTC)));
2553 }
2554
2555 #[test]
2556 fn today_orders() {
2557 let data = r#"
2558 {
2559 "orders": [
2560 {
2561 "currency": "HKD",
2562 "executed_price": "0.000",
2563 "executed_quantity": "0",
2564 "expire_date": "",
2565 "last_done": "",
2566 "limit_offset": "",
2567 "msg": "",
2568 "order_id": "706388312699592704",
2569 "order_type": "ELO",
2570 "outside_rth": "UnknownOutsideRth",
2571 "price": "11.900",
2572 "quantity": "200",
2573 "side": "Buy",
2574 "status": "RejectedStatus",
2575 "stock_name": "Bank of East Asia Ltd/The",
2576 "submitted_at": "1651644897",
2577 "symbol": "23.HK",
2578 "tag": "Normal",
2579 "time_in_force": "Day",
2580 "trailing_amount": "",
2581 "trailing_percent": "",
2582 "trigger_at": "0",
2583 "trigger_price": "",
2584 "trigger_status": "NOT_USED",
2585 "updated_at": "1651644898",
2586 "limit_depth_level": 0,
2587 "trigger_count": 0,
2588 "monitor_price": "",
2589 "remark": "abc"
2590 }
2591 ]
2592 }
2593 "#;
2594
2595 #[derive(Deserialize)]
2596 struct Response {
2597 orders: Vec<Order>,
2598 }
2599
2600 let resp: Response = serde_json::from_str(data).unwrap();
2601 assert_eq!(resp.orders.len(), 1);
2602
2603 let order = &resp.orders[0];
2604 assert_eq!(order.currency, "HKD");
2605 assert!(order.executed_price.is_none());
2606 assert_eq!(order.executed_quantity, decimal!(0));
2607 assert!(order.expire_date.is_none());
2608 assert!(order.last_done.is_none());
2609 assert!(order.limit_offset.is_none());
2610 assert_eq!(order.msg, "");
2611 assert_eq!(order.order_id, "706388312699592704");
2612 assert_eq!(order.order_type, OrderType::ELO);
2613 assert!(order.outside_rth.is_none());
2614 assert_eq!(order.price, Some("11.900".parse().unwrap()));
2615 assert_eq!(order.quantity, decimal!(200));
2616 assert_eq!(order.side, OrderSide::Buy);
2617 assert_eq!(order.status, OrderStatus::Rejected);
2618 assert_eq!(order.stock_name, "Bank of East Asia Ltd/The");
2619 assert_eq!(order.submitted_at, datetime!(2022-05-04 14:14:57 +8));
2620 assert_eq!(order.symbol, "23.HK");
2621 assert_eq!(order.tag, OrderTag::Normal);
2622 assert_eq!(order.time_in_force, TimeInForceType::Day);
2623 assert!(order.trailing_amount.is_none());
2624 assert!(order.trailing_percent.is_none());
2625 assert!(order.trigger_at.is_none());
2626 assert!(order.trigger_price.is_none());
2627 assert!(order.trigger_status.is_none());
2628 assert_eq!(order.updated_at, Some(datetime!(2022-05-04 14:14:58 +8)));
2629 assert_eq!(order.remark, "abc");
2630 assert!(order.delayed_until.is_none());
2631 }
2632
2633 #[test]
2634 fn history_executions() {
2635 let data = r#"
2636 {
2637 "has_more": false,
2638 "trades": [
2639 {
2640 "order_id": "693664675163312128",
2641 "price": "388",
2642 "quantity": "100",
2643 "symbol": "700.HK",
2644 "trade_done_at": "1648611351",
2645 "trade_id": "693664675163312128-1648611351433741210",
2646 "side": "Buy"
2647 }
2648 ]
2649 }
2650 "#;
2651
2652 #[derive(Deserialize)]
2653 struct Response {
2654 trades: Vec<Execution>,
2655 }
2656
2657 let resp: Response = serde_json::from_str(data).unwrap();
2658 assert_eq!(resp.trades.len(), 1);
2659
2660 let execution = &resp.trades[0];
2661 assert_eq!(execution.order_id, "693664675163312128");
2662 assert_eq!(execution.price, "388".parse().unwrap());
2663 assert_eq!(execution.quantity, decimal!(100));
2664 assert_eq!(execution.symbol, "700.HK");
2665 assert_eq!(execution.trade_done_at, datetime!(2022-03-30 11:35:51 +8));
2666 assert_eq!(execution.trade_id, "693664675163312128-1648611351433741210");
2667 assert_eq!(execution.side, OrderSide::Buy);
2668 }
2669
2670 #[test]
2671 fn order_detail() {
2672 let data = r#"
2673 {
2674 "order_id": "828940451093708800",
2675 "status": "FilledStatus",
2676 "stock_name": "Apple",
2677 "quantity": "10",
2678 "executed_quantity": "10",
2679 "price": "200.000",
2680 "executed_price": "164.660",
2681 "submitted_at": "1680863604",
2682 "side": "Buy",
2683 "symbol": "AAPL.US",
2684 "order_type": "LO",
2685 "last_done": "164.660",
2686 "trigger_price": "0.0000",
2687 "msg": "",
2688 "tag": "Normal",
2689 "time_in_force": "Day",
2690 "expire_date": "2023-04-10",
2691 "updated_at": "1681113000",
2692 "trigger_at": "0",
2693 "trailing_amount": "",
2694 "trailing_percent": "",
2695 "limit_offset": "",
2696 "trigger_status": "NOT_USED",
2697 "outside_rth": "ANY_TIME",
2698 "currency": "USD",
2699 "limit_depth_level": 0,
2700 "trigger_count": 0,
2701 "monitor_price": "",
2702 "remark": "1680863603.927165",
2703 "delayed_until": "0",
2704 "free_status": "None",
2705 "free_amount": "",
2706 "free_currency": "",
2707 "deductions_status": "NONE",
2708 "deductions_amount": "",
2709 "deductions_currency": "",
2710 "platform_deducted_status": "NONE",
2711 "platform_deducted_amount": "",
2712 "platform_deducted_currency": "",
2713 "history": [{
2714 "price": "164.6600",
2715 "quantity": "10",
2716 "status": "FilledStatus",
2717 "msg": "Execution of 10",
2718 "time": "1681113000"
2719 }, {
2720 "price": "200.0000",
2721 "quantity": "10",
2722 "status": "NewStatus",
2723 "msg": "",
2724 "time": "1681113000"
2725 }],
2726 "charge_detail": {
2727 "items": [{
2728 "code": "BROKER_FEES",
2729 "name": "Broker Fees",
2730 "fees": []
2731 }, {
2732 "code": "THIRD_FEES",
2733 "name": "Third-party Fees",
2734 "fees": []
2735 }],
2736 "total_amount": "0",
2737 "currency": "USD"
2738 }
2739 }
2740 "#;
2741
2742 let detail = serde_json::from_str::<OrderDetail>(data).unwrap();
2743 assert!(
2744 detail.delayed_until.is_none(),
2745 "\"0\" means no time condition"
2746 );
2747
2748 let mut value: serde_json::Value = serde_json::from_str(data).unwrap();
2749 value["status"] = "DelayedNotReported".into();
2750 value["delayed_until"] = "1767225600".into();
2751 let detail = serde_json::from_value::<OrderDetail>(value).unwrap();
2752 assert_eq!(detail.status, OrderStatus::DelayedNotReported);
2753 assert_eq!(detail.delayed_until, Some(datetime!(2026-01-01 00:00 UTC)));
2754
2755 let mut value: serde_json::Value = serde_json::from_str(data).unwrap();
2757 value["delayed_until"] = serde_json::Value::Null;
2758 let detail = serde_json::from_value::<OrderDetail>(value).unwrap();
2759 assert!(detail.delayed_until.is_none());
2760
2761 for (raw, expected) in [
2763 (
2764 serde_json::json!(1767225600),
2765 Some(datetime!(2026-01-01 00:00 UTC)),
2766 ),
2767 (serde_json::json!("abc"), None),
2768 (serde_json::json!("99999999999999"), None),
2769 (serde_json::json!(""), None),
2770 (serde_json::json!("-5"), None),
2771 ] {
2772 let mut value: serde_json::Value = serde_json::from_str(data).unwrap();
2773 value["delayed_until"] = raw.clone();
2774 let detail = serde_json::from_value::<OrderDetail>(value).unwrap();
2775 assert_eq!(detail.delayed_until, expected, "{raw}");
2776 }
2777 }
2778
2779 #[test]
2780 fn attached_order_limit_depth_level() {
2781 let data = r#"
2782 {
2783 "order_id": "706388312699592705",
2784 "attached_type_display": "STOP_LOSS",
2785 "trigger_price": "10.500",
2786 "quantity": "200",
2787 "executed_qty": "0",
2788 "status": "NewStatus",
2789 "updated_at": "1651644898",
2790 "withdrawn": false,
2791 "gtd": "",
2792 "time_in_force": "Day",
2793 "counter_id": "",
2794 "trigger_status": "NOT_USED",
2795 "executed_amount": "0",
2796 "tag": "Normal",
2797 "submitted_at": "1651644897",
2798 "executed_price": "0.000",
2799 "force_only_rth": "RTH_ONLY",
2800 "reviewed": false,
2801 "activate_order_type": "MIT",
2802 "activate_rth": "RTH_ONLY",
2803 "submit_price": "",
2804 "limit_depth_level": 0
2805 }
2806 "#;
2807 for (raw, expected) in [
2808 (serde_json::json!(0), None),
2809 (serde_json::json!(3), Some(3)),
2810 (serde_json::json!(-2), Some(-2)),
2811 (serde_json::json!("3"), Some(3)),
2812 (serde_json::json!("abc"), None),
2813 (serde_json::Value::Null, None),
2814 ] {
2815 let mut value: serde_json::Value = serde_json::from_str(data).unwrap();
2816 value["limit_depth_level"] = raw.clone();
2817 let detail = serde_json::from_value::<AttachedOrderDetail>(value).unwrap();
2818 assert_eq!(detail.limit_depth_level, expected, "{raw}");
2819 }
2820
2821 let mut value: serde_json::Value = serde_json::from_str(data).unwrap();
2823 value.as_object_mut().unwrap().remove("limit_depth_level");
2824 let detail = serde_json::from_value::<AttachedOrderDetail>(value).unwrap();
2825 assert!(detail.limit_depth_level.is_none());
2826 }
2827}