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