Skip to main content

longbridge/trade/
types.rs

1use num_enum::{FromPrimitive, IntoPrimitive};
2use rust_decimal::Decimal;
3use serde::{Deserialize, Deserializer, Serialize, Serializer};
4use strum_macros::{Display, EnumString};
5use time::{Date, OffsetDateTime};
6
7use crate::{Market, serde_utils};
8
9/// Order type
10#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, EnumString, Display)]
11#[allow(clippy::upper_case_acronyms)]
12pub enum OrderType {
13    /// Unknown
14    Unknown,
15    /// Limit Order
16    #[strum(serialize = "LO")]
17    LO,
18    /// Enhanced Limit Order
19    #[strum(serialize = "ELO")]
20    ELO,
21    /// Market Order
22    #[strum(serialize = "MO")]
23    MO,
24    /// At-auction Order
25    #[strum(serialize = "AO")]
26    AO,
27    /// At-auction Limit Order
28    #[strum(serialize = "ALO")]
29    ALO,
30    /// Odd Lots
31    #[strum(serialize = "ODD")]
32    ODD,
33    /// Limit If Touched
34    #[strum(serialize = "LIT")]
35    LIT,
36    /// Market If Touched
37    #[strum(serialize = "MIT")]
38    MIT,
39    /// Trailing Limit If Touched (Trailing Amount)
40    #[strum(serialize = "TSLPAMT")]
41    TSLPAMT,
42    /// Trailing Limit If Touched (Trailing Percent)
43    #[strum(serialize = "TSLPPCT")]
44    TSLPPCT,
45    /// Trailing Market If Touched (Trailing Amount)
46    #[strum(serialize = "TSMAMT")]
47    TSMAMT,
48    /// Trailing Market If Touched (Trailing Percent)
49    #[strum(serialize = "TSMPCT")]
50    TSMPCT,
51    /// Special Limit Order
52    #[strum(serialize = "SLO")]
53    SLO,
54}
55
56/// Order status
57#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, EnumString, Display)]
58pub enum OrderStatus {
59    /// Unknown
60    Unknown,
61    /// Not reported
62    #[strum(serialize = "NotReported")]
63    NotReported,
64    /// Not reported (Replaced Order)
65    #[strum(serialize = "ReplacedNotReported")]
66    ReplacedNotReported,
67    /// Not reported (Protected Order)
68    #[strum(serialize = "ProtectedNotReported")]
69    ProtectedNotReported,
70    /// Not reported (Conditional Order)
71    #[strum(serialize = "VarietiesNotReported")]
72    VarietiesNotReported,
73    /// Filled
74    #[strum(serialize = "FilledStatus")]
75    Filled,
76    /// Wait To New
77    #[strum(serialize = "WaitToNew")]
78    WaitToNew,
79    /// New
80    #[strum(serialize = "NewStatus")]
81    New,
82    /// Wait To Replace
83    #[strum(serialize = "WaitToReplace")]
84    WaitToReplace,
85    /// Pending Replace
86    #[strum(serialize = "PendingReplaceStatus")]
87    PendingReplace,
88    /// Replaced
89    #[strum(serialize = "ReplacedStatus")]
90    Replaced,
91    /// Partial Filled
92    #[strum(serialize = "PartialFilledStatus")]
93    PartialFilled,
94    /// Wait To Cancel
95    #[strum(serialize = "WaitToCancel")]
96    WaitToCancel,
97    /// Pending Cancel
98    #[strum(serialize = "PendingCancelStatus")]
99    PendingCancel,
100    /// Rejected
101    #[strum(serialize = "RejectedStatus")]
102    Rejected,
103    /// Canceled
104    #[strum(serialize = "CanceledStatus")]
105    Canceled,
106    /// Expired
107    #[strum(serialize = "ExpiredStatus")]
108    Expired,
109    /// Partial Withdrawal
110    #[strum(serialize = "PartialWithdrawal")]
111    PartialWithdrawal,
112}
113
114/// Execution
115#[derive(Debug, Clone, Serialize, Deserialize)]
116pub struct Execution {
117    /// Order ID
118    pub order_id: String,
119    /// Execution ID
120    pub trade_id: String,
121    /// Security code
122    pub symbol: String,
123    /// Trade done time
124    #[serde(
125        serialize_with = "time::serde::rfc3339::serialize",
126        deserialize_with = "serde_utils::timestamp::deserialize"
127    )]
128    pub trade_done_at: OffsetDateTime,
129    /// Executed quantity
130    pub quantity: Decimal,
131    /// Executed price
132    pub price: Decimal,
133}
134
135/// Response for get all executions request
136#[derive(Debug, Clone, Serialize, Deserialize)]
137pub struct AllExecutionsResponse {
138    /// Has more records
139    pub has_more: bool,
140    /// Execution list
141    #[serde(default)]
142    pub trades: Vec<Execution>,
143}
144
145/// Order side
146#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, EnumString, Display)]
147pub enum OrderSide {
148    /// Unknown
149    Unknown,
150    /// Buy
151    #[strum(serialize = "Buy")]
152    Buy,
153    /// Sell
154    #[strum(serialize = "Sell")]
155    Sell,
156}
157
158/// Order trigger price type
159#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, EnumString, Display)]
160pub enum TriggerPriceType {
161    /// Unknown
162    Unknown,
163    /// Limit If Touched
164    #[strum(serialize = "LIT")]
165    LimitIfTouched,
166    /// Market If Touched
167    #[strum(serialize = "MIT")]
168    MarketIfTouched,
169}
170
171/// Order tag
172#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, EnumString, Display)]
173pub enum OrderTag {
174    /// Unknown
175    Unknown,
176    /// Normal Order
177    #[strum(serialize = "Normal")]
178    Normal,
179    /// Long term Order
180    #[strum(serialize = "Gtc")]
181    LongTerm,
182    /// Grey Order
183    #[strum(serialize = "Grey")]
184    Grey,
185}
186
187/// Time in force Type
188#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, EnumString, Display)]
189pub enum TimeInForceType {
190    /// Unknown
191    Unknown,
192    /// Day Order
193    #[strum(serialize = "Day")]
194    Day,
195    /// Good Til Canceled Order
196    #[strum(serialize = "GTC")]
197    GoodTilCanceled,
198    /// Good Til Date Order
199    #[strum(serialize = "GTD")]
200    GoodTilDate,
201}
202
203/// Trigger status
204#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, EnumString, Display)]
205pub enum TriggerStatus {
206    /// Unknown
207    Unknown,
208    /// Deactive
209    #[strum(serialize = "DEACTIVE")]
210    Deactive,
211    /// Active
212    #[strum(serialize = "ACTIVE")]
213    Active,
214    /// Released
215    #[strum(serialize = "RELEASED")]
216    Released,
217}
218
219/// Enable or disable outside regular trading hours
220#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, EnumString, Display)]
221pub enum OutsideRTH {
222    /// Unknown
223    Unknown,
224    /// Regular trading hour only
225    #[strum(serialize = "RTH_ONLY")]
226    RTHOnly,
227    /// Any time
228    #[strum(serialize = "ANY_TIME")]
229    AnyTime,
230    /// Overnight
231    #[strum(serialize = "OVERNIGHT")]
232    Overnight,
233    /// Overnight option
234    #[strum(serialize = "OPTION_PRE_MARKET")]
235    OptionPreMarket,
236}
237
238/// Attached order type
239#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, EnumString, Display)]
240pub enum AttachedOrderType {
241    /// Unknown
242    Unknown,
243    /// Take profit
244    #[strum(serialize = "PROFIT_TAKER")]
245    ProfitTaker,
246    /// Stop loss
247    #[strum(serialize = "STOP_LOSS")]
248    StopLoss,
249    /// Bracket order
250    #[strum(serialize = "BRACKET")]
251    Bracket,
252}
253
254/// Attached order detail
255#[derive(Debug, Clone, Serialize, Deserialize)]
256pub struct AttachedOrderDetail {
257    /// Attached order ID
258    pub order_id: String,
259    /// Attached order type
260    pub attached_type_display: AttachedOrderType,
261    /// Trigger price
262    #[serde(with = "serde_utils::decimal_opt_empty_is_none")]
263    pub trigger_price: Option<Decimal>,
264    /// Quantity
265    pub quantity: Decimal,
266    /// Executed quantity
267    pub executed_qty: Decimal,
268    /// Order status
269    pub status: OrderStatus,
270    /// Last updated time (unix timestamp seconds)
271    #[serde(
272        serialize_with = "time::serde::rfc3339::serialize",
273        deserialize_with = "serde_utils::timestamp::deserialize"
274    )]
275    pub updated_at: OffsetDateTime,
276    /// Whether withdrawn
277    pub withdrawn: bool,
278    /// GTD date
279    #[serde(with = "serde_utils::date_opt")]
280    pub gtd: Option<Date>,
281    /// Time in force
282    pub time_in_force: TimeInForceType,
283    /// Counter order ID
284    pub counter_id: String,
285    /// Trigger status
286    #[serde(with = "serde_utils::trigger_status")]
287    pub trigger_status: Option<TriggerStatus>,
288    /// Executed amount
289    pub executed_amount: Decimal,
290    /// Tag
291    pub tag: OrderTag,
292    /// Submitted time (unix timestamp seconds)
293    #[serde(
294        serialize_with = "time::serde::rfc3339::serialize",
295        deserialize_with = "serde_utils::timestamp::deserialize"
296    )]
297    pub submitted_at: OffsetDateTime,
298    /// Executed price
299    #[serde(with = "serde_utils::decimal_opt_empty_is_none")]
300    pub executed_price: Option<Decimal>,
301    /// Force RTH only
302    #[serde(with = "serde_utils::outside_rth")]
303    pub force_only_rth: Option<OutsideRTH>,
304    /// Whether reviewed
305    pub reviewed: bool,
306    /// Order type to submit after trigger
307    pub activate_order_type: OrderType,
308    /// RTH setting for activated order
309    #[serde(with = "serde_utils::outside_rth")]
310    pub activate_rth: Option<OutsideRTH>,
311    /// Submit price (limit price)
312    #[serde(with = "serde_utils::decimal_opt_empty_is_none")]
313    pub submit_price: Option<Decimal>,
314}
315
316/// Order
317#[derive(Debug, Clone, Serialize, Deserialize)]
318pub struct Order {
319    /// Order ID
320    pub order_id: String,
321    /// Order status
322    pub status: OrderStatus,
323    /// Stock name
324    pub stock_name: String,
325    /// Submitted quantity
326    pub quantity: Decimal,
327    /// Executed quantity
328    pub executed_quantity: Decimal,
329    /// Submitted price
330    #[serde(with = "serde_utils::decimal_opt_empty_is_none")]
331    pub price: Option<Decimal>,
332    /// Executed price
333    #[serde(with = "serde_utils::decimal_opt_0_is_none")]
334    pub executed_price: Option<Decimal>,
335    /// Submitted time
336    #[serde(
337        serialize_with = "time::serde::rfc3339::serialize",
338        deserialize_with = "serde_utils::timestamp::deserialize"
339    )]
340    pub submitted_at: OffsetDateTime,
341    /// Order side
342    pub side: OrderSide,
343    /// Security code
344    pub symbol: String,
345    /// Order type
346    pub order_type: OrderType,
347    /// Last done
348    #[serde(with = "serde_utils::decimal_opt_empty_is_none")]
349    pub last_done: Option<Decimal>,
350    /// `LIT` / `MIT` Order Trigger Price
351    #[serde(with = "serde_utils::decimal_opt_empty_is_none")]
352    pub trigger_price: Option<Decimal>,
353    /// Rejected Message or remark
354    pub msg: String,
355    /// Order tag
356    pub tag: OrderTag,
357    /// Time in force type
358    pub time_in_force: TimeInForceType,
359    /// Long term order expire date
360    #[serde(with = "serde_utils::date_opt")]
361    pub expire_date: Option<Date>,
362    /// Last updated time
363    #[serde(
364        deserialize_with = "serde_utils::timestamp_opt::deserialize",
365        serialize_with = "serde_utils::rfc3339_opt::serialize"
366    )]
367    pub updated_at: Option<OffsetDateTime>,
368    /// Conditional order trigger time
369    #[serde(
370        deserialize_with = "serde_utils::timestamp_opt::deserialize",
371        serialize_with = "serde_utils::rfc3339_opt::serialize"
372    )]
373    pub trigger_at: Option<OffsetDateTime>,
374    /// `TSMAMT` / `TSLPAMT` order trailing amount
375    #[serde(with = "serde_utils::decimal_opt_empty_is_none")]
376    pub trailing_amount: Option<Decimal>,
377    /// `TSMPCT` / `TSLPPCT` order trailing percent
378    #[serde(with = "serde_utils::decimal_opt_empty_is_none")]
379    pub trailing_percent: Option<Decimal>,
380    /// `TSLPAMT` / `TSLPPCT` order limit offset amount
381    #[serde(with = "serde_utils::decimal_opt_empty_is_none")]
382    pub limit_offset: Option<Decimal>,
383    /// Conditional order trigger status
384    #[serde(with = "serde_utils::trigger_status")]
385    pub trigger_status: Option<TriggerStatus>,
386    /// Currency
387    pub currency: String,
388    /// Enable or disable outside regular trading hours
389    #[serde(with = "serde_utils::outside_rth")]
390    pub outside_rth: Option<OutsideRTH>,
391    /// Limit depth level
392    #[serde(with = "serde_utils::int32_opt_0_is_none")]
393    pub limit_depth_level: Option<i32>,
394    /// Trigger count
395    #[serde(with = "serde_utils::int32_opt_0_is_none")]
396    pub trigger_count: Option<i32>,
397    /// Monitor price
398    #[serde(with = "serde_utils::decimal_opt_empty_is_none")]
399    pub monitor_price: Option<Decimal>,
400    /// Remark
401    pub remark: String,
402    /// Attached orders
403    #[serde(default)]
404    pub attached_orders: Vec<AttachedOrderDetail>,
405}
406
407/// Commission-free Status
408#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, EnumString, Display)]
409pub enum CommissionFreeStatus {
410    /// Unknown
411    Unknown,
412    /// None
413    None,
414    /// Commission-free amount to be calculated
415    Calculated,
416    /// Pending commission-free
417    Pending,
418    /// Commission-free applied
419    Ready,
420}
421
422/// Deduction status
423#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, EnumString, Display)]
424pub enum DeductionStatus {
425    /// Unknown
426    Unknown,
427    /// Pending Settlement
428    #[strum(serialize = "NONE")]
429    None,
430    /// Settled with no data
431    #[strum(serialize = "NO_DATA")]
432    NoData,
433    /// Settled and pending distribution
434    #[strum(serialize = "PENDING")]
435    Pending,
436    /// Settled and distributed
437    #[strum(serialize = "DONE")]
438    Done,
439}
440
441/// Charge category code
442#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, EnumString, Display)]
443pub enum ChargeCategoryCode {
444    /// Unknown
445    Unknown,
446    /// Broker
447    #[strum(serialize = "BROKER_FEES")]
448    Broker,
449    /// Third
450    #[strum(serialize = "THIRD_FEES")]
451    Third,
452}
453
454/// Order history detail
455#[derive(Debug, Clone, Serialize, Deserialize)]
456pub struct OrderHistoryDetail {
457    /// Executed price for executed orders, submitted price for expired,
458    /// canceled, rejected orders, etc.
459    #[serde(with = "serde_utils::decimal_empty_is_0")]
460    pub price: Decimal,
461    /// Executed quantity for executed orders, remaining quantity for expired,
462    /// canceled, rejected orders, etc.
463    #[serde(with = "serde_utils::decimal_empty_is_0")]
464    pub quantity: Decimal,
465    /// Order status
466    pub status: OrderStatus,
467    /// Execution or error message
468    pub msg: String,
469    /// Occurrence time
470    #[serde(
471        serialize_with = "time::serde::rfc3339::serialize",
472        deserialize_with = "serde_utils::timestamp::deserialize"
473    )]
474    pub time: OffsetDateTime,
475}
476
477/// Order charge fee
478#[derive(Debug, Clone, Serialize, Deserialize)]
479pub struct OrderChargeFee {
480    /// Charge code
481    pub code: String,
482    /// Charge name
483    pub name: String,
484    /// Charge amount
485    #[serde(with = "serde_utils::decimal_empty_is_0")]
486    pub amount: Decimal,
487    /// Charge currency
488    pub currency: String,
489}
490
491/// Order charge item
492#[derive(Debug, Clone, Serialize, Deserialize)]
493pub struct OrderChargeItem {
494    /// Charge category code
495    pub code: ChargeCategoryCode,
496    /// Charge category name
497    pub name: String,
498    /// Charge details
499    pub fees: Vec<OrderChargeFee>,
500}
501
502/// Order charge detail
503#[derive(Debug, Clone, Serialize, Deserialize)]
504pub struct OrderChargeDetail {
505    /// Total charges amount
506    pub total_amount: Decimal,
507    /// Settlement currency
508    pub currency: String,
509    /// Order charge items
510    pub items: Vec<OrderChargeItem>,
511}
512
513/// Order detail
514#[derive(Debug, Clone, Serialize, Deserialize)]
515pub struct OrderDetail {
516    /// Order ID
517    pub order_id: String,
518    /// Order status
519    pub status: OrderStatus,
520    /// Stock name
521    pub stock_name: String,
522    /// Submitted quantity
523    pub quantity: Decimal,
524    /// Executed quantity
525    pub executed_quantity: Decimal,
526    /// Submitted price
527    #[serde(with = "serde_utils::decimal_opt_empty_is_none")]
528    pub price: Option<Decimal>,
529    /// Executed price
530    #[serde(with = "serde_utils::decimal_opt_0_is_none")]
531    pub executed_price: Option<Decimal>,
532    /// Submitted time
533    #[serde(
534        serialize_with = "time::serde::rfc3339::serialize",
535        deserialize_with = "serde_utils::timestamp::deserialize"
536    )]
537    pub submitted_at: OffsetDateTime,
538    /// Order side
539    pub side: OrderSide,
540    /// Security code
541    pub symbol: String,
542    /// Order type
543    pub order_type: OrderType,
544    /// Last done
545    #[serde(with = "serde_utils::decimal_opt_empty_is_none")]
546    pub last_done: Option<Decimal>,
547    /// `LIT` / `MIT` Order Trigger Price
548    #[serde(with = "serde_utils::decimal_opt_empty_is_none")]
549    pub trigger_price: Option<Decimal>,
550    /// Rejected Message or remark
551    pub msg: String,
552    /// Order tag
553    pub tag: OrderTag,
554    /// Time in force type
555    pub time_in_force: TimeInForceType,
556    /// Long term order expire date
557    #[serde(with = "serde_utils::date_opt")]
558    pub expire_date: Option<Date>,
559    /// Last updated time
560    #[serde(
561        deserialize_with = "serde_utils::timestamp_opt::deserialize",
562        serialize_with = "serde_utils::rfc3339_opt::serialize"
563    )]
564    pub updated_at: Option<OffsetDateTime>,
565    /// Conditional order trigger time
566    #[serde(
567        deserialize_with = "serde_utils::timestamp_opt::deserialize",
568        serialize_with = "serde_utils::rfc3339_opt::serialize"
569    )]
570    pub trigger_at: Option<OffsetDateTime>,
571    /// `TSMAMT` / `TSLPAMT` order trailing amount
572    #[serde(with = "serde_utils::decimal_opt_empty_is_none")]
573    pub trailing_amount: Option<Decimal>,
574    /// `TSMPCT` / `TSLPPCT` order trailing percent
575    #[serde(with = "serde_utils::decimal_opt_empty_is_none")]
576    pub trailing_percent: Option<Decimal>,
577    /// `TSLPAMT` / `TSLPPCT` order limit offset amount
578    #[serde(with = "serde_utils::decimal_opt_empty_is_none")]
579    pub limit_offset: Option<Decimal>,
580    /// Conditional order trigger status
581    #[serde(with = "serde_utils::trigger_status")]
582    pub trigger_status: Option<TriggerStatus>,
583    /// Currency
584    pub currency: String,
585    /// Enable or disable outside regular trading hours
586    #[serde(with = "serde_utils::outside_rth")]
587    pub outside_rth: Option<OutsideRTH>,
588    /// Limit depth level
589    #[serde(with = "serde_utils::int32_opt_0_is_none")]
590    pub limit_depth_level: Option<i32>,
591    /// Trigger count
592    #[serde(with = "serde_utils::int32_opt_0_is_none")]
593    pub trigger_count: Option<i32>,
594    /// Monitor price
595    #[serde(with = "serde_utils::decimal_opt_empty_is_none")]
596    pub monitor_price: Option<Decimal>,
597    /// Remark
598    pub remark: String,
599    /// Commission-free Status
600    pub free_status: CommissionFreeStatus,
601    /// Commission-free amount
602    #[serde(with = "serde_utils::decimal_opt_empty_is_none")]
603    pub free_amount: Option<Decimal>,
604    /// Commission-free currency
605    #[serde(with = "serde_utils::symbol_opt")]
606    pub free_currency: Option<String>,
607    /// Deduction status
608    pub deductions_status: DeductionStatus,
609    /// Deduction amount
610    #[serde(with = "serde_utils::decimal_opt_empty_is_none")]
611    pub deductions_amount: Option<Decimal>,
612    /// Deduction currency
613    #[serde(with = "serde_utils::symbol_opt")]
614    pub deductions_currency: Option<String>,
615    /// Platform fee deduction status
616    pub platform_deducted_status: DeductionStatus,
617    /// Platform deduction amount
618    #[serde(with = "serde_utils::decimal_opt_empty_is_none")]
619    pub platform_deducted_amount: Option<Decimal>,
620    /// Platform deduction currency
621    #[serde(with = "serde_utils::symbol_opt")]
622    pub platform_deducted_currency: Option<String>,
623    /// Order history details
624    pub history: Vec<OrderHistoryDetail>,
625    /// Order charges
626    pub charge_detail: Option<OrderChargeDetail>,
627    /// Attached orders
628    #[serde(default)]
629    pub attached_orders: Vec<AttachedOrderDetail>,
630}
631
632/// Cash info
633#[derive(Debug, Clone, Serialize, Deserialize)]
634pub struct CashInfo {
635    /// Withdraw cash
636    pub withdraw_cash: Decimal,
637    /// Available cash
638    pub available_cash: Decimal,
639    /// Frozen cash
640    pub frozen_cash: Decimal,
641    /// Cash to be settled
642    pub settling_cash: Decimal,
643    /// Currency
644    pub currency: String,
645}
646
647/// Frozen transaction fee
648#[derive(Debug, Clone, Serialize, Deserialize)]
649pub struct FrozenTransactionFee {
650    /// Currency
651    pub currency: String,
652    /// Frozen transaction fee amount
653    pub frozen_transaction_fee: Decimal,
654}
655
656/// Account balance
657#[derive(Debug, Clone, Serialize, Deserialize)]
658pub struct AccountBalance {
659    /// Total cash
660    pub total_cash: Decimal,
661    /// Maximum financing amount
662    pub max_finance_amount: Decimal,
663    /// Remaining financing amount
664    pub remaining_finance_amount: Decimal,
665    /// Risk control level
666    #[serde(with = "serde_utils::risk_level")]
667    pub risk_level: i32,
668    /// Margin call
669    pub margin_call: Decimal,
670    /// Currency
671    pub currency: String,
672    /// Cash details
673    #[serde(default)]
674    pub cash_infos: Vec<CashInfo>,
675    /// Net assets
676    #[serde(with = "serde_utils::decimal_empty_is_0")]
677    pub net_assets: Decimal,
678    /// Initial margin
679    #[serde(with = "serde_utils::decimal_empty_is_0")]
680    pub init_margin: Decimal,
681    /// Maintenance margin
682    #[serde(with = "serde_utils::decimal_empty_is_0")]
683    pub maintenance_margin: Decimal,
684    /// Buy power
685    #[serde(with = "serde_utils::decimal_empty_is_0")]
686    pub buy_power: Decimal,
687    /// Frozen transaction fees
688    pub frozen_transaction_fees: Vec<FrozenTransactionFee>,
689}
690
691/// Balance type
692#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, FromPrimitive, IntoPrimitive)]
693#[repr(i32)]
694pub enum BalanceType {
695    /// Unknown
696    #[num_enum(default)]
697    Unknown = 0,
698    /// Cash
699    Cash = 1,
700    /// Stock
701    Stock = 2,
702    /// Fund
703    Fund = 3,
704}
705
706impl Serialize for BalanceType {
707    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
708        let value: i32 = (*self).into();
709        value.serialize(serializer)
710    }
711}
712
713impl<'de> Deserialize<'de> for BalanceType {
714    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
715        let value = i32::deserialize(deserializer)?;
716        Ok(BalanceType::from(value))
717    }
718}
719
720/// Cash flow direction
721#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, FromPrimitive, Serialize)]
722#[repr(i32)]
723pub enum CashFlowDirection {
724    /// Unknown
725    #[num_enum(default)]
726    Unknown,
727    /// Out
728    Out = 1,
729    /// In
730    In = 2,
731}
732
733impl<'de> Deserialize<'de> for CashFlowDirection {
734    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
735        let value = i32::deserialize(deserializer)?;
736        Ok(CashFlowDirection::from(value))
737    }
738}
739
740/// Cash flow
741#[derive(Debug, Clone, Serialize, Deserialize)]
742pub struct CashFlow {
743    /// Cash flow name
744    pub transaction_flow_name: String,
745    /// Outflow direction
746    pub direction: CashFlowDirection,
747    /// Balance type
748    pub business_type: BalanceType,
749    /// Cash amount
750    pub balance: Decimal,
751    /// Cash currency
752    pub currency: String,
753    /// Business time
754    #[serde(
755        serialize_with = "time::serde::rfc3339::serialize",
756        deserialize_with = "serde_utils::timestamp::deserialize"
757    )]
758    pub business_time: OffsetDateTime,
759    /// Associated Stock code information
760    #[serde(with = "serde_utils::symbol_opt")]
761    pub symbol: Option<String>,
762    /// Cash flow description
763    pub description: String,
764}
765
766/// Fund positions response
767#[derive(Debug, Clone, Serialize, Deserialize)]
768pub struct FundPositionsResponse {
769    /// Channels
770    #[serde(rename = "list")]
771    pub channels: Vec<FundPositionChannel>,
772}
773
774/// Fund position channel
775#[derive(Debug, Clone, Serialize, Deserialize)]
776pub struct FundPositionChannel {
777    /// Account type
778    pub account_channel: String,
779
780    /// Fund positions
781    #[serde(default, rename = "fund_info")]
782    pub positions: Vec<FundPosition>,
783}
784
785/// Fund position
786#[derive(Debug, Clone, Serialize, Deserialize)]
787pub struct FundPosition {
788    /// Fund ISIN code
789    pub symbol: String,
790    /// Current equity
791    #[serde(with = "serde_utils::decimal_empty_is_0")]
792    pub current_net_asset_value: Decimal,
793    /// Current equity time
794    #[serde(
795        serialize_with = "time::serde::rfc3339::serialize",
796        deserialize_with = "serde_utils::timestamp::deserialize"
797    )]
798    pub net_asset_value_day: OffsetDateTime,
799    /// Fund name
800    pub symbol_name: String,
801    /// Currency
802    pub currency: String,
803    /// Net cost
804    #[serde(with = "serde_utils::decimal_empty_is_0")]
805    pub cost_net_asset_value: Decimal,
806    /// Holding units
807    #[serde(with = "serde_utils::decimal_empty_is_0")]
808    pub holding_units: Decimal,
809}
810
811/// Stock positions response
812#[derive(Debug, Clone, Serialize, Deserialize)]
813pub struct StockPositionsResponse {
814    /// Channels
815    #[serde(rename = "list")]
816    pub channels: Vec<StockPositionChannel>,
817}
818
819/// Stock position channel
820#[derive(Debug, Clone, Serialize, Deserialize)]
821pub struct StockPositionChannel {
822    /// Account type
823    pub account_channel: String,
824
825    /// Stock positions
826    #[serde(default, rename = "stock_info")]
827    pub positions: Vec<StockPosition>,
828}
829
830/// Stock position
831#[derive(Debug, Clone, Serialize, Deserialize)]
832pub struct StockPosition {
833    /// Stock code
834    pub symbol: String,
835    /// Stock name
836    pub symbol_name: String,
837    /// The number of holdings
838    pub quantity: Decimal,
839    /// Available quantity
840    pub available_quantity: Decimal,
841    /// Currency
842    pub currency: String,
843    /// Cost Price(According to the client's choice of average purchase or
844    /// diluted cost)
845    #[serde(with = "serde_utils::decimal_empty_is_0")]
846    pub cost_price: Decimal,
847    /// Market
848    pub market: Market,
849    /// Initial position before market opening
850    #[serde(with = "serde_utils::decimal_opt_empty_is_none")]
851    pub init_quantity: Option<Decimal>,
852}
853
854/// Margin ratio
855#[derive(Debug, Clone, Serialize, Deserialize)]
856pub struct MarginRatio {
857    /// Initial margin ratio
858    pub im_factor: Decimal,
859    /// Maintain the initial margin ratio
860    pub mm_factor: Decimal,
861    /// Forced close-out margin ratio
862    pub fm_factor: Decimal,
863}
864
865impl_serde_for_enum_string!(
866    OrderType,
867    OrderStatus,
868    OrderSide,
869    TriggerPriceType,
870    OrderTag,
871    TimeInForceType,
872    TriggerStatus,
873    OutsideRTH,
874    CommissionFreeStatus,
875    DeductionStatus,
876    ChargeCategoryCode
877);
878impl_serde_for_enum_string!(AttachedOrderType);
879impl_default_for_enum_string!(AttachedOrderType);
880impl_default_for_enum_string!(
881    OrderType,
882    OrderStatus,
883    OrderSide,
884    TriggerPriceType,
885    OrderTag,
886    TimeInForceType,
887    TriggerStatus,
888    OutsideRTH,
889    CommissionFreeStatus,
890    DeductionStatus,
891    ChargeCategoryCode
892);
893
894// ── US-market types
895// ───────────────────────────────────────────────────────────
896
897/// Request for [`crate::TradeContext::us_query_orders`], modelled after
898/// [`crate::GetHistoryOrdersOptions`] for HK/CN orders.
899///
900/// `query_type`: 0 = all (includes Rejected), 1 = pending, 2 = history (filled
901/// only). Default 0 matches what the app shows as "past orders".
902///
903/// `symbol` accepts a user-facing symbol e.g. `"AAPL.US"` or `"DOGEUSD.BKKT"`.
904/// The SDK converts it to the internal `counter_id` format automatically.
905#[derive(Debug, Clone, Default)]
906pub struct GetUSHistoryOrders {
907    /// Optional symbol filter, e.g. `"AAPL.US"`. Converted to counter_id
908    /// internally.
909    pub symbol: Option<String>,
910    /// Direction filter. [`crate::OrderSide::Unknown`] = all (default).
911    pub side: OrderSide,
912    /// Start timestamp (seconds). Defaults to 90 days ago.
913    pub start_at: i64,
914    /// End timestamp (seconds). Defaults to now.
915    pub end_at: i64,
916    /// 0 = all, 1 = pending, 2 = history (filled only). Default 0.
917    pub query_type: i32,
918    /// Page number, 1-based. Default 1.
919    pub page: i32,
920    /// Page size. Default 20.
921    pub limit: i32,
922}
923
924/// Alias kept for backward compatibility.
925pub type QueryUSOrdersOptions = GetUSHistoryOrders;
926
927/// Internal JSON body sent to POST /v1/us/orders/query.
928#[derive(Debug, Clone, Serialize)]
929pub(crate) struct USQueryOrdersBody {
930    pub account_channel: String,
931    pub action: i32,
932    pub start_at: f64,
933    pub end_at: f64,
934    pub counter_ids: Vec<String>,
935    pub security_types: Vec<String>,
936    pub query_type: i32,
937    pub page: i32,
938    pub limit: i32,
939    pub query_version: f64,
940}
941
942/// Response for [`crate::TradeContext::us_query_orders`].
943#[derive(Debug, Clone, Serialize, Deserialize, Default)]
944pub struct QueryUSOrdersResponse {
945    /// Order list (raw JSON for forward compatibility).
946    /// Order ID field is `id` (not `order_id`).
947    #[serde(default)]
948    pub orders: Vec<serde_json::Value>,
949    /// Total number of orders matching the query.
950    #[serde(default)]
951    pub total_count: i32,
952}
953
954/// One order state-transition entry within [`USOrderDetail`].
955#[derive(Debug, Clone, Serialize, Deserialize, Default)]
956pub struct USOrderHistory {
957    #[serde(default)]
958    pub exec_type: i32,
959    #[serde(default)]
960    pub status: String,
961    #[serde(default)]
962    pub price: String,
963    #[serde(default)]
964    pub qty: String,
965    #[serde(default)]
966    pub time: String,
967    #[serde(default)]
968    pub msg: String,
969    #[serde(default)]
970    pub is_manually: bool,
971    #[serde(default)]
972    pub opp_party_id: String,
973    #[serde(default)]
974    pub trd_match_id: String,
975    #[serde(default)]
976    pub operator: String,
977    #[serde(default)]
978    pub op_entrust_way: String,
979    #[serde(default)]
980    pub cxl_rej_response_to: i32,
981    #[serde(default)]
982    pub withdrawal_reason: String,
983    #[serde(default)]
984    pub opp_name: String,
985    #[serde(default)]
986    pub exec_id: String,
987}
988
989/// Action-button state for an order.
990#[derive(Debug, Clone, Serialize, Deserialize, Default)]
991pub struct USButtonControl {
992    #[serde(default)]
993    pub withdraw: i32,
994    #[serde(default)]
995    pub replace: i32,
996    #[serde(default)]
997    pub exceptionable: Vec<String>,
998}
999
1000/// One fee category within [`USChargeDetail`].
1001#[derive(Debug, Clone, Serialize, Deserialize, Default)]
1002pub struct USChargeItem {
1003    #[serde(default)]
1004    pub code: i32,
1005    #[serde(default)]
1006    pub name: String,
1007    #[serde(default)]
1008    pub fees: Vec<String>,
1009}
1010
1011/// Fee breakdown for an order.
1012#[derive(Debug, Clone, Serialize, Deserialize, Default)]
1013pub struct USChargeDetail {
1014    #[serde(default)]
1015    pub currency: String,
1016    #[serde(default)]
1017    pub total_amount: String,
1018    #[serde(default)]
1019    pub items: Vec<USChargeItem>,
1020}
1021
1022/// One bracket/conditional sub-order attached to a main order.
1023#[derive(Debug, Clone, Serialize, Deserialize, Default)]
1024pub struct USAttachedOrder {
1025    #[serde(default)]
1026    pub attached_type_display: i32,
1027    #[serde(default)]
1028    pub executed_qty: String,
1029    #[serde(default)]
1030    pub quantity: String,
1031    #[serde(default)]
1032    pub status: String,
1033    #[serde(default)]
1034    pub trigger_price: String,
1035    #[serde(default)]
1036    pub order_id: String,
1037    #[serde(default)]
1038    pub gtd: String,
1039    #[serde(default)]
1040    pub time_in_force: i32,
1041    #[serde(default)]
1042    pub tag: i32,
1043    #[serde(default)]
1044    pub activate_order_type: String,
1045    #[serde(default)]
1046    pub activate_rth: i32,
1047    #[serde(default)]
1048    pub submit_price: String,
1049    /// User-facing trading symbol (e.g. `"NKE.US"`), converted from
1050    /// `counter_id`.
1051    #[serde(
1052        default,
1053        rename = "counter_id",
1054        deserialize_with = "crate::utils::counter::deserialize_counter_id_as_symbol"
1055    )]
1056    pub symbol: String,
1057    #[serde(default)]
1058    pub withdrawn: bool,
1059}
1060
1061/// Full typed order object within [`USOrderDetailResponse`].
1062/// `submitted_at` and `done_at` are raw unix-second strings.
1063/// `order_histories` is nested inside this object, not at the response top
1064/// level.
1065#[derive(Debug, Clone, Serialize, Deserialize, Default)]
1066pub struct USOrderDetail {
1067    #[serde(default)]
1068    pub id: String,
1069    #[serde(default)]
1070    pub aaid: String,
1071    #[serde(default)]
1072    pub account_channel: String,
1073    #[serde(default)]
1074    pub action: i32,
1075    /// User-facing trading symbol (e.g. `"NKE.US"`), converted from
1076    /// `counter_id`.
1077    #[serde(
1078        default,
1079        rename = "counter_id",
1080        deserialize_with = "crate::utils::counter::deserialize_counter_id_as_symbol"
1081    )]
1082    pub symbol: String,
1083    /// User-facing underlying symbol (options only), converted from
1084    /// `underlying_counter_id`.
1085    #[serde(
1086        default,
1087        rename = "underlying_counter_id",
1088        deserialize_with = "crate::utils::counter::deserialize_counter_id_as_symbol"
1089    )]
1090    pub underlying_symbol: String,
1091    #[serde(default)]
1092    pub security_type: String,
1093    #[serde(default)]
1094    pub name: String,
1095    #[serde(default)]
1096    pub currency: String,
1097    #[serde(default)]
1098    pub trade_currency: String,
1099    #[serde(default)]
1100    pub order_type: String,
1101    #[serde(default)]
1102    pub status: String,
1103    #[serde(default)]
1104    pub price: String,
1105    #[serde(default)]
1106    pub quantity: String,
1107    #[serde(default)]
1108    pub executed_qty: String,
1109    #[serde(default)]
1110    pub executed_price: String,
1111    #[serde(default)]
1112    pub executed_amount: String,
1113    #[serde(default)]
1114    pub operate_direction: String,
1115    #[serde(default)]
1116    pub time_in_force: i32,
1117    #[serde(default)]
1118    pub gtd: String,
1119    #[serde(default)]
1120    pub tag: i32,
1121    #[serde(default)]
1122    pub msg: String,
1123    #[serde(default)]
1124    pub force_only_rth: i32,
1125    #[serde(default)]
1126    pub submitted_at: String,
1127    #[serde(default)]
1128    pub done_at: String,
1129    #[serde(default)]
1130    pub trigger_price: String,
1131    #[serde(default)]
1132    pub trigger_at: String,
1133    #[serde(default)]
1134    pub trigger_status: i32,
1135    #[serde(default)]
1136    pub trigger_exchange: String,
1137    #[serde(default)]
1138    pub trigger_last_done: String,
1139    #[serde(default)]
1140    pub trigger_count: i32,
1141    #[serde(default)]
1142    pub tailing_amount: String,
1143    #[serde(default)]
1144    pub tailing_percent: String,
1145    #[serde(default)]
1146    pub limit_offset: String,
1147    #[serde(default)]
1148    pub limit_depth_level: i32,
1149    #[serde(default)]
1150    pub market_price: String,
1151    #[serde(default)]
1152    pub submitted_amount: String,
1153    #[serde(default)]
1154    pub estimated_fee: String,
1155    #[serde(default)]
1156    pub free_status: i32,
1157    #[serde(default)]
1158    pub free_amount: String,
1159    #[serde(default)]
1160    pub free_currency: String,
1161    #[serde(default)]
1162    pub deductions_status: i32,
1163    #[serde(default)]
1164    pub deductions_amount: String,
1165    #[serde(default)]
1166    pub deductions_currency: String,
1167    #[serde(default)]
1168    pub platform_deductions_status: i32,
1169    #[serde(default)]
1170    pub platform_deductions_amount: String,
1171    #[serde(default)]
1172    pub platform_deductions_currency: String,
1173    #[serde(default)]
1174    pub display_account: String,
1175    #[serde(default)]
1176    pub settlement_account: String,
1177    #[serde(default)]
1178    pub settlement_channel: String,
1179    #[serde(default)]
1180    pub customer_name: String,
1181    #[serde(default)]
1182    pub real_name: String,
1183    #[serde(default)]
1184    pub en_name: String,
1185    #[serde(default)]
1186    pub joint_real_name: String,
1187    #[serde(default)]
1188    pub joint_en_name: String,
1189    #[serde(default)]
1190    pub org_id: String,
1191    #[serde(default)]
1192    pub bcan: String,
1193    #[serde(default)]
1194    pub op_entrust_way: i32,
1195    #[serde(default)]
1196    pub op_entrust_way_name: String,
1197    #[serde(default)]
1198    pub remark: String,
1199    #[serde(default)]
1200    pub notice: String,
1201    #[serde(default)]
1202    pub short_sell_type: i32,
1203    #[serde(default)]
1204    pub ploy_type: String,
1205    #[serde(default)]
1206    pub ploy_id: String,
1207    #[serde(default)]
1208    pub ploy_status: String,
1209    #[serde(default)]
1210    pub trend: i32,
1211    #[serde(default)]
1212    pub withdrawal_reason: String,
1213    #[serde(default)]
1214    pub activate_order_type: String,
1215    #[serde(default)]
1216    pub activate_rth: i32,
1217    #[serde(default)]
1218    pub submit_price: String,
1219    #[serde(default)]
1220    pub contract_direction: String,
1221    #[serde(default)]
1222    pub strike_price: String,
1223    #[serde(default)]
1224    pub contract_size: String,
1225    #[serde(default)]
1226    pub monitor_price: String,
1227    #[serde(default)]
1228    pub button_control: USButtonControl,
1229    pub charge_detail: Option<USChargeDetail>,
1230    #[serde(default)]
1231    pub attached_orders: Vec<USAttachedOrder>,
1232    #[serde(default)]
1233    pub order_histories: Vec<USOrderHistory>,
1234}
1235
1236/// Response for [`crate::TradeContext::us_order_detail`].
1237/// Path: `GET /v1/us/orders/{order_id}`
1238#[derive(Debug, Clone, Serialize, Deserialize, Default)]
1239pub struct USOrderDetailResponse {
1240    /// Full typed order object; None only on error.
1241    pub order: Option<USOrderDetail>,
1242    /// Active bracket/conditional sub-order, or None.
1243    pub current_attached_order: Option<USOrderDetail>,
1244    /// Server response timestamp (milliseconds string).
1245    #[serde(default)]
1246    pub current_millisecond: String,
1247}
1248
1249/// One cash currency entry in [`USAssetOverview`].
1250#[derive(Debug, Clone, Serialize, Deserialize, Default)]
1251pub struct USCashEntry {
1252    #[serde(default)]
1253    pub currency: String,
1254    #[serde(default)]
1255    pub frozen_buy_cash: String,
1256    #[serde(default)]
1257    pub outstanding: String,
1258    #[serde(default)]
1259    pub settled_cash: String,
1260    #[serde(default)]
1261    pub total_amount: String,
1262    #[serde(default)]
1263    pub total_cash: String,
1264}
1265
1266/// One cryptocurrency holding in [`USAssetOverview`].
1267#[derive(Debug, Clone, Serialize, Deserialize, Default)]
1268pub struct USCryptoEntry {
1269    #[serde(default)]
1270    pub asset_type: String,
1271    #[serde(default)]
1272    pub average_cost: String,
1273    /// User-facing trading-pair symbol (e.g. `"BTCUSD.BKKT"`), converted from
1274    /// the API's `counter_id` field (e.g. `"VA/BKKT/BTCUSD"`).
1275    #[serde(
1276        default,
1277        rename = "counter_id",
1278        deserialize_with = "crate::utils::counter::deserialize_counter_id_as_symbol"
1279    )]
1280    pub symbol: String,
1281    #[serde(default)]
1282    pub currency: String,
1283    #[serde(default)]
1284    pub industry_name: String,
1285}
1286
1287/// One stock/equity position in [`USAssetOverview`].
1288#[derive(Debug, Clone, Serialize, Deserialize, Default)]
1289pub struct USStockEntry {
1290    /// Ticker code returned by the API (e.g. `"AAPL"`). See `full_symbol` for
1291    /// the qualified form.
1292    #[serde(default)]
1293    pub symbol: String,
1294    /// Qualified user-facing symbol (e.g. `"AAPL.US"`), converted from
1295    /// `counter_id`.
1296    #[serde(
1297        default,
1298        rename = "counter_id",
1299        deserialize_with = "crate::utils::counter::deserialize_counter_id_as_symbol"
1300    )]
1301    pub full_symbol: String,
1302    #[serde(default)]
1303    pub asset_type: String,
1304    #[serde(default)]
1305    pub quantity: String,
1306    #[serde(default)]
1307    pub currency: String,
1308    #[serde(default)]
1309    pub average_cost: String,
1310    #[serde(default)]
1311    pub market: String,
1312    #[serde(default)]
1313    pub trade_status: String,
1314    #[serde(default)]
1315    pub prev_close: String,
1316    #[serde(default)]
1317    pub last_done: String,
1318    #[serde(default)]
1319    pub market_price: String,
1320    #[serde(default)]
1321    pub pretrade_close: String,
1322    #[serde(default)]
1323    pub stock_invest_of_today: String,
1324    #[serde(default)]
1325    pub today_pl: String,
1326    #[serde(default)]
1327    pub pretrade_stock_invest_of_today: String,
1328    #[serde(default)]
1329    pub pretrade_today_pl: String,
1330    #[serde(default)]
1331    pub night_last_done: String,
1332    #[serde(default)]
1333    pub night_prev_close: String,
1334    #[serde(default)]
1335    pub position_side: String,
1336    #[serde(default)]
1337    pub open_position_time: String,
1338    #[serde(default)]
1339    pub name: String,
1340    #[serde(default)]
1341    pub industry_counter_id: String,
1342    #[serde(default)]
1343    pub industry_name: String,
1344}
1345
1346/// Response for [`crate::TradeContext::us_asset_overview`].
1347/// Field names match the actual API response from `GET /v1/us/assets/overview`.
1348#[derive(Debug, Clone, Serialize, Deserialize, Default)]
1349pub struct USAssetOverview {
1350    #[serde(default)]
1351    pub account_type: String,
1352    /// Account snapshot timestamp (Unix-second string → OffsetDateTime).
1353    #[serde(default, with = "crate::serde_utils::timestamp_opt")]
1354    pub asset_timestamp: Option<time::OffsetDateTime>,
1355    /// Cash buying power (top-level convenience field).
1356    #[serde(default)]
1357    pub cash_buy_power: String,
1358    #[serde(default)]
1359    pub overnight_buy_power: String,
1360    #[serde(default)]
1361    pub currency: String,
1362    #[serde(default)]
1363    pub cash_list: Vec<USCashEntry>,
1364    #[serde(default)]
1365    pub stock_list: Vec<USStockEntry>,
1366    #[serde(default)]
1367    pub option_list: Vec<serde_json::Value>,
1368    #[serde(default)]
1369    pub crypto_list: Vec<USCryptoEntry>,
1370    #[serde(default)]
1371    pub multi_leg: serde_json::Value,
1372}
1373
1374/// One time-period metric in a [`USRealizedPLEntry`].
1375#[derive(Debug, Clone, Serialize, Deserialize, Default)]
1376pub struct USRealizedPLMetric {
1377    #[serde(default)]
1378    pub amount: String,
1379    /// Period code (server-defined; 2 = current month observed in testing).
1380    #[serde(default)]
1381    pub period: i32,
1382    #[serde(default)]
1383    pub rate: String,
1384}
1385
1386/// One asset-category entry in [`USRealizedPL`].
1387/// `category`: 0 = all, 1 = stock, 2 = option, 3 = crypto (server-defined).
1388#[derive(Debug, Clone, Serialize, Deserialize, Default)]
1389pub struct USRealizedPLEntry {
1390    #[serde(default)]
1391    pub category: i32,
1392    #[serde(default)]
1393    pub currency: String,
1394    #[serde(default)]
1395    pub metrics: Vec<USRealizedPLMetric>,
1396}
1397
1398/// Request for [`crate::TradeContext::us_realized_pl`], modelled after
1399/// [`crate::GetUSHistoryOrders`].
1400#[derive(Debug, Clone, Default)]
1401pub struct GetUSRealizedPLOptions {
1402    /// Currency, e.g. `"USD"`. Defaults to `"USD"` if empty.
1403    pub currency: String,
1404    /// Asset category filter: `""` = all, `"STOCK"`, `"OPTION"`, `"CRYPTO"`.
1405    pub category: String,
1406}
1407
1408/// Response for [`crate::TradeContext::us_realized_pl`].
1409/// Field name matches the actual API response from `GET
1410/// /v1/us/assets/pl/realized`.
1411#[derive(Debug, Clone, Serialize, Deserialize, Default)]
1412pub struct USRealizedPL {
1413    #[serde(default)]
1414    pub realized_pl_list: Vec<USRealizedPLEntry>,
1415}
1416
1417#[cfg(test)]
1418mod tests {
1419    use time::macros::datetime;
1420
1421    use super::*;
1422
1423    #[test]
1424    fn fund_position_response() {
1425        let data = r#"
1426        {
1427            "list": [{
1428                "account_channel": "lb",
1429                "fund_info": [{
1430                    "symbol": "HK0000447943",
1431                    "symbol_name": "高腾亚洲收益基金",
1432                    "currency": "USD",
1433                    "holding_units": "5.000",
1434                    "current_net_asset_value": "0",
1435                    "cost_net_asset_value": "0.00",
1436                    "net_asset_value_day": "1649865600"
1437                }]
1438            }]
1439        }
1440        "#;
1441
1442        let resp: FundPositionsResponse = serde_json::from_str(data).unwrap();
1443        assert_eq!(resp.channels.len(), 1);
1444
1445        let channel = &resp.channels[0];
1446        assert_eq!(channel.account_channel, "lb");
1447        assert_eq!(channel.positions.len(), 1);
1448
1449        let position = &channel.positions[0];
1450        assert_eq!(position.symbol, "HK0000447943");
1451        assert_eq!(position.symbol_name, "高腾亚洲收益基金");
1452        assert_eq!(position.currency, "USD");
1453        assert_eq!(position.current_net_asset_value, decimal!(0i32));
1454        assert_eq!(position.cost_net_asset_value, decimal!(0i32));
1455        assert_eq!(position.holding_units, decimal!(5i32));
1456        assert_eq!(position.net_asset_value_day, datetime!(2022-4-14 0:00 +8));
1457    }
1458
1459    #[test]
1460    fn stock_position_response() {
1461        let data = r#"
1462        {
1463            "list": [
1464              {
1465                "account_channel": "lb",
1466                "stock_info": [
1467                  {
1468                    "symbol": "700.HK",
1469                    "symbol_name": "腾讯控股",
1470                    "currency": "HK",
1471                    "quantity": "650",
1472                    "available_quantity": "-450",
1473                    "cost_price": "457.53",
1474                    "market": "HK",
1475                    "init_quantity": "2000"
1476                  },
1477                  {
1478                    "symbol": "9991.HK",
1479                    "symbol_name": "宝尊电商-SW",
1480                    "currency": "HK",
1481                    "quantity": "200",
1482                    "available_quantity": "0",
1483                    "cost_price": "32.25",
1484                    "market": "HK",
1485                    "init_quantity": ""
1486                  }
1487                ]
1488              }
1489            ]
1490          }
1491        "#;
1492
1493        let resp: StockPositionsResponse = serde_json::from_str(data).unwrap();
1494        assert_eq!(resp.channels.len(), 1);
1495
1496        let channel = &resp.channels[0];
1497        assert_eq!(channel.account_channel, "lb");
1498        assert_eq!(channel.positions.len(), 2);
1499
1500        let position = &channel.positions[0];
1501        assert_eq!(position.symbol, "700.HK");
1502        assert_eq!(position.symbol_name, "腾讯控股");
1503        assert_eq!(position.currency, "HK");
1504        assert_eq!(position.quantity, decimal!(650));
1505        assert_eq!(position.available_quantity, decimal!(-450));
1506        assert_eq!(position.cost_price, decimal!(457.53f32));
1507        assert_eq!(position.market, Market::HK);
1508        assert_eq!(position.init_quantity, Some(decimal!(2000)));
1509
1510        let position = &channel.positions[0];
1511        assert_eq!(position.symbol, "700.HK");
1512        assert_eq!(position.symbol_name, "腾讯控股");
1513        assert_eq!(position.currency, "HK");
1514        assert_eq!(position.quantity, decimal!(650));
1515        assert_eq!(position.available_quantity, decimal!(-450));
1516        assert_eq!(position.cost_price, decimal!(457.53f32));
1517        assert_eq!(position.market, Market::HK);
1518
1519        let position = &channel.positions[1];
1520        assert_eq!(position.symbol, "9991.HK");
1521        assert_eq!(position.symbol_name, "宝尊电商-SW");
1522        assert_eq!(position.currency, "HK");
1523        assert_eq!(position.quantity, decimal!(200));
1524        assert_eq!(position.available_quantity, decimal!(0));
1525        assert_eq!(position.cost_price, decimal!(32.25f32));
1526        assert_eq!(position.init_quantity, None);
1527    }
1528
1529    #[test]
1530    fn cash_flow() {
1531        let data = r#"
1532        {
1533            "list": [
1534              {
1535                "transaction_flow_name": "BuyContract-Stocks",
1536                "direction": 1,
1537                "balance": "-248.60",
1538                "currency": "USD",
1539                "business_type": 1,
1540                "business_time": "1621507957",
1541                "symbol": "AAPL.US",
1542                "description": "AAPL"
1543              },
1544              {
1545                "transaction_flow_name": "BuyContract-Stocks",
1546                "direction": 1,
1547                "balance": "-125.16",
1548                "currency": "USD",
1549                "business_type": 2,
1550                "business_time": "1621504824",
1551                "symbol": "AAPL.US",
1552                "description": "AAPL"
1553              }
1554            ]
1555          }
1556          "#;
1557
1558        #[derive(Debug, Deserialize)]
1559        struct Response {
1560            list: Vec<CashFlow>,
1561        }
1562
1563        let resp: Response = serde_json::from_str(data).unwrap();
1564        assert_eq!(resp.list.len(), 2);
1565
1566        let cashflow = &resp.list[0];
1567        assert_eq!(cashflow.transaction_flow_name, "BuyContract-Stocks");
1568        assert_eq!(cashflow.direction, CashFlowDirection::Out);
1569        assert_eq!(cashflow.balance, decimal!(-248.60f32));
1570        assert_eq!(cashflow.currency, "USD");
1571        assert_eq!(cashflow.business_type, BalanceType::Cash);
1572        assert_eq!(cashflow.business_time, datetime!(2021-05-20 18:52:37 +8));
1573        assert_eq!(cashflow.symbol.as_deref(), Some("AAPL.US"));
1574        assert_eq!(cashflow.description, "AAPL");
1575
1576        let cashflow = &resp.list[1];
1577        assert_eq!(cashflow.transaction_flow_name, "BuyContract-Stocks");
1578        assert_eq!(cashflow.direction, CashFlowDirection::Out);
1579        assert_eq!(cashflow.balance, decimal!(-125.16f32));
1580        assert_eq!(cashflow.currency, "USD");
1581        assert_eq!(cashflow.business_type, BalanceType::Stock);
1582        assert_eq!(cashflow.business_time, datetime!(2021-05-20 18:00:24 +8));
1583        assert_eq!(cashflow.symbol.as_deref(), Some("AAPL.US"));
1584        assert_eq!(cashflow.description, "AAPL");
1585    }
1586
1587    #[test]
1588    fn account_balance() {
1589        let data = r#"
1590        {
1591            "list": [
1592              {
1593                "total_cash": "1759070010.72",
1594                "max_finance_amount": "977582000",
1595                "remaining_finance_amount": "0",
1596                "risk_level": "1",
1597                "margin_call": "2598051051.50",
1598                "currency": "HKD",
1599                "cash_infos": [
1600                  {
1601                    "withdraw_cash": "97592.30",
1602                    "available_cash": "195902464.37",
1603                    "frozen_cash": "11579339.13",
1604                    "settling_cash": "207288537.81",
1605                    "currency": "HKD"
1606                  },
1607                  {
1608                    "withdraw_cash": "199893416.74",
1609                    "available_cash": "199893416.74",
1610                    "frozen_cash": "28723.76",
1611                    "settling_cash": "-276806.51",
1612                    "currency": "USD"
1613                  }
1614                ],
1615                "net_assets": "11111.12",
1616                "init_margin": "2222.23",
1617                "maintenance_margin": "3333.45",
1618                "buy_power": "1234.67",
1619                "frozen_transaction_fees": [
1620                    {
1621                        "currency": "HKD",
1622                        "frozen_transaction_fee": "123"
1623                    }
1624                ]
1625              }
1626            ]
1627          }"#;
1628
1629        #[derive(Debug, Deserialize)]
1630        struct Response {
1631            list: Vec<AccountBalance>,
1632        }
1633
1634        let resp: Response = serde_json::from_str(data).unwrap();
1635        assert_eq!(resp.list.len(), 1);
1636
1637        let balance = &resp.list[0];
1638        assert_eq!(balance.total_cash, "1759070010.72".parse().unwrap());
1639        assert_eq!(balance.max_finance_amount, "977582000".parse().unwrap());
1640        assert_eq!(balance.remaining_finance_amount, decimal!(0i32));
1641        assert_eq!(balance.risk_level, 1);
1642        assert_eq!(balance.margin_call, "2598051051.50".parse().unwrap());
1643        assert_eq!(balance.currency, "HKD");
1644        assert_eq!(balance.net_assets, "11111.12".parse().unwrap());
1645        assert_eq!(balance.init_margin, "2222.23".parse().unwrap());
1646        assert_eq!(balance.maintenance_margin, "3333.45".parse().unwrap());
1647        assert_eq!(balance.buy_power, "1234.67".parse().unwrap());
1648
1649        assert_eq!(balance.cash_infos.len(), 2);
1650
1651        let cash_info = &balance.cash_infos[0];
1652        assert_eq!(cash_info.withdraw_cash, "97592.30".parse().unwrap());
1653        assert_eq!(cash_info.available_cash, "195902464.37".parse().unwrap());
1654        assert_eq!(cash_info.frozen_cash, "11579339.13".parse().unwrap());
1655        assert_eq!(cash_info.settling_cash, "207288537.81".parse().unwrap());
1656        assert_eq!(cash_info.currency, "HKD");
1657
1658        let cash_info = &balance.cash_infos[1];
1659        assert_eq!(cash_info.withdraw_cash, "199893416.74".parse().unwrap());
1660        assert_eq!(cash_info.available_cash, "199893416.74".parse().unwrap());
1661        assert_eq!(cash_info.frozen_cash, "28723.76".parse().unwrap());
1662        assert_eq!(cash_info.settling_cash, "-276806.51".parse().unwrap());
1663        assert_eq!(cash_info.currency, "USD");
1664
1665        assert_eq!(balance.frozen_transaction_fees.len(), 1);
1666
1667        let frozen_transaction_fee = &balance.frozen_transaction_fees[0];
1668        assert_eq!(frozen_transaction_fee.currency, "HKD");
1669        assert_eq!(
1670            frozen_transaction_fee.frozen_transaction_fee,
1671            "123".parse().unwrap()
1672        );
1673    }
1674
1675    #[test]
1676    fn history_orders() {
1677        let data = r#"
1678        {
1679            "orders": [
1680              {
1681                "currency": "HKD",
1682                "executed_price": "0.000",
1683                "executed_quantity": "0",
1684                "expire_date": "",
1685                "last_done": "",
1686                "limit_offset": "",
1687                "msg": "",
1688                "order_id": "706388312699592704",
1689                "order_type": "ELO",
1690                "outside_rth": "UnknownOutsideRth",
1691                "price": "11.900",
1692                "quantity": "200",
1693                "side": "Buy",
1694                "status": "RejectedStatus",
1695                "stock_name": "Bank of East Asia Ltd/The",
1696                "submitted_at": "1651644897",
1697                "symbol": "23.HK",
1698                "tag": "Normal",
1699                "time_in_force": "Day",
1700                "trailing_amount": "",
1701                "trailing_percent": "",
1702                "trigger_at": "0",
1703                "trigger_price": "",
1704                "trigger_status": "NOT_USED",
1705                "updated_at": "1651644898",
1706                "limit_depth_level": 0,
1707                "trigger_count": 0,
1708                "monitor_price": "",
1709                "remark": "abc"
1710              }
1711            ]
1712          }
1713        "#;
1714
1715        #[derive(Deserialize)]
1716        struct Response {
1717            orders: Vec<Order>,
1718        }
1719
1720        let resp: Response = serde_json::from_str(data).unwrap();
1721        assert_eq!(resp.orders.len(), 1);
1722
1723        let order = &resp.orders[0];
1724        assert_eq!(order.currency, "HKD");
1725        assert!(order.executed_price.is_none());
1726        assert_eq!(order.executed_quantity, decimal!(0));
1727        assert!(order.expire_date.is_none());
1728        assert!(order.last_done.is_none());
1729        assert!(order.limit_offset.is_none());
1730        assert_eq!(order.msg, "");
1731        assert_eq!(order.order_id, "706388312699592704");
1732        assert_eq!(order.order_type, OrderType::ELO);
1733        assert!(order.outside_rth.is_none());
1734        assert_eq!(order.price, Some("11.900".parse().unwrap()));
1735        assert_eq!(order.quantity, decimal!(200));
1736        assert_eq!(order.side, OrderSide::Buy);
1737        assert_eq!(order.status, OrderStatus::Rejected);
1738        assert_eq!(order.stock_name, "Bank of East Asia Ltd/The");
1739        assert_eq!(order.submitted_at, datetime!(2022-05-04 14:14:57 +8));
1740        assert_eq!(order.symbol, "23.HK");
1741        assert_eq!(order.tag, OrderTag::Normal);
1742        assert_eq!(order.time_in_force, TimeInForceType::Day);
1743        assert!(order.trailing_amount.is_none());
1744        assert!(order.trailing_percent.is_none());
1745        assert!(order.trigger_at.is_none());
1746        assert!(order.trigger_price.is_none());
1747        assert!(order.trigger_status.is_none());
1748        assert_eq!(order.updated_at, Some(datetime!(2022-05-04 14:14:58 +8)));
1749        assert_eq!(order.remark, "abc");
1750    }
1751
1752    #[test]
1753    fn today_orders() {
1754        let data = r#"
1755        {
1756            "orders": [
1757              {
1758                "currency": "HKD",
1759                "executed_price": "0.000",
1760                "executed_quantity": "0",
1761                "expire_date": "",
1762                "last_done": "",
1763                "limit_offset": "",
1764                "msg": "",
1765                "order_id": "706388312699592704",
1766                "order_type": "ELO",
1767                "outside_rth": "UnknownOutsideRth",
1768                "price": "11.900",
1769                "quantity": "200",
1770                "side": "Buy",
1771                "status": "RejectedStatus",
1772                "stock_name": "Bank of East Asia Ltd/The",
1773                "submitted_at": "1651644897",
1774                "symbol": "23.HK",
1775                "tag": "Normal",
1776                "time_in_force": "Day",
1777                "trailing_amount": "",
1778                "trailing_percent": "",
1779                "trigger_at": "0",
1780                "trigger_price": "",
1781                "trigger_status": "NOT_USED",
1782                "updated_at": "1651644898",
1783                "limit_depth_level": 0,
1784                "trigger_count": 0,
1785                "monitor_price": "",
1786                "remark": "abc"
1787              }
1788            ]
1789          }
1790        "#;
1791
1792        #[derive(Deserialize)]
1793        struct Response {
1794            orders: Vec<Order>,
1795        }
1796
1797        let resp: Response = serde_json::from_str(data).unwrap();
1798        assert_eq!(resp.orders.len(), 1);
1799
1800        let order = &resp.orders[0];
1801        assert_eq!(order.currency, "HKD");
1802        assert!(order.executed_price.is_none());
1803        assert_eq!(order.executed_quantity, decimal!(0));
1804        assert!(order.expire_date.is_none());
1805        assert!(order.last_done.is_none());
1806        assert!(order.limit_offset.is_none());
1807        assert_eq!(order.msg, "");
1808        assert_eq!(order.order_id, "706388312699592704");
1809        assert_eq!(order.order_type, OrderType::ELO);
1810        assert!(order.outside_rth.is_none());
1811        assert_eq!(order.price, Some("11.900".parse().unwrap()));
1812        assert_eq!(order.quantity, decimal!(200));
1813        assert_eq!(order.side, OrderSide::Buy);
1814        assert_eq!(order.status, OrderStatus::Rejected);
1815        assert_eq!(order.stock_name, "Bank of East Asia Ltd/The");
1816        assert_eq!(order.submitted_at, datetime!(2022-05-04 14:14:57 +8));
1817        assert_eq!(order.symbol, "23.HK");
1818        assert_eq!(order.tag, OrderTag::Normal);
1819        assert_eq!(order.time_in_force, TimeInForceType::Day);
1820        assert!(order.trailing_amount.is_none());
1821        assert!(order.trailing_percent.is_none());
1822        assert!(order.trigger_at.is_none());
1823        assert!(order.trigger_price.is_none());
1824        assert!(order.trigger_status.is_none());
1825        assert_eq!(order.updated_at, Some(datetime!(2022-05-04 14:14:58 +8)));
1826        assert_eq!(order.remark, "abc");
1827    }
1828
1829    #[test]
1830    fn history_executions() {
1831        let data = r#"
1832        {
1833            "has_more": false,
1834            "trades": [
1835              {
1836                "order_id": "693664675163312128",
1837                "price": "388",
1838                "quantity": "100",
1839                "symbol": "700.HK",
1840                "trade_done_at": "1648611351",
1841                "trade_id": "693664675163312128-1648611351433741210"
1842              }
1843            ]
1844          }
1845        "#;
1846
1847        #[derive(Deserialize)]
1848        struct Response {
1849            trades: Vec<Execution>,
1850        }
1851
1852        let resp: Response = serde_json::from_str(data).unwrap();
1853        assert_eq!(resp.trades.len(), 1);
1854
1855        let execution = &resp.trades[0];
1856        assert_eq!(execution.order_id, "693664675163312128");
1857        assert_eq!(execution.price, "388".parse().unwrap());
1858        assert_eq!(execution.quantity, decimal!(100));
1859        assert_eq!(execution.symbol, "700.HK");
1860        assert_eq!(execution.trade_done_at, datetime!(2022-03-30 11:35:51 +8));
1861        assert_eq!(execution.trade_id, "693664675163312128-1648611351433741210");
1862    }
1863
1864    #[test]
1865    fn order_detail() {
1866        let data = r#"
1867        {
1868            "order_id": "828940451093708800",
1869            "status": "FilledStatus",
1870            "stock_name": "Apple",
1871            "quantity": "10",
1872            "executed_quantity": "10",
1873            "price": "200.000",
1874            "executed_price": "164.660",
1875            "submitted_at": "1680863604",
1876            "side": "Buy",
1877            "symbol": "AAPL.US",
1878            "order_type": "LO",
1879            "last_done": "164.660",
1880            "trigger_price": "0.0000",
1881            "msg": "",
1882            "tag": "Normal",
1883            "time_in_force": "Day",
1884            "expire_date": "2023-04-10",
1885            "updated_at": "1681113000",
1886            "trigger_at": "0",
1887            "trailing_amount": "",
1888            "trailing_percent": "",
1889            "limit_offset": "",
1890            "trigger_status": "NOT_USED",
1891            "outside_rth": "ANY_TIME",
1892            "currency": "USD",
1893            "limit_depth_level": 0,
1894            "trigger_count": 0,
1895            "monitor_price": "",
1896            "remark": "1680863603.927165",
1897            "free_status": "None",
1898            "free_amount": "",
1899            "free_currency": "",
1900            "deductions_status": "NONE",
1901            "deductions_amount": "",
1902            "deductions_currency": "",
1903            "platform_deducted_status": "NONE",
1904            "platform_deducted_amount": "",
1905            "platform_deducted_currency": "",
1906            "history": [{
1907                "price": "164.6600",
1908                "quantity": "10",
1909                "status": "FilledStatus",
1910                "msg": "Execution of 10",
1911                "time": "1681113000"
1912            }, {
1913                "price": "200.0000",
1914                "quantity": "10",
1915                "status": "NewStatus",
1916                "msg": "",
1917                "time": "1681113000"
1918            }],
1919            "charge_detail": {
1920                "items": [{
1921                    "code": "BROKER_FEES",
1922                    "name": "Broker Fees",
1923                    "fees": []
1924                }, {
1925                    "code": "THIRD_FEES",
1926                    "name": "Third-party Fees",
1927                    "fees": []
1928                }],
1929                "total_amount": "0",
1930                "currency": "USD"
1931            }
1932        }
1933        "#;
1934
1935        _ = serde_json::from_str::<OrderDetail>(data).unwrap();
1936    }
1937}