longport/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/// Order side
136#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, EnumString, Display)]
137pub enum OrderSide {
138    /// Unknown
139    Unknown,
140    /// Buy
141    #[strum(serialize = "Buy")]
142    Buy,
143    /// Sell
144    #[strum(serialize = "Sell")]
145    Sell,
146}
147
148/// Order trigger price type
149#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, EnumString, Display)]
150pub enum TriggerPriceType {
151    /// Unknown
152    Unknown,
153    /// Limit If Touched
154    #[strum(serialize = "LIT")]
155    LimitIfTouched,
156    /// Market If Touched
157    #[strum(serialize = "MIT")]
158    MarketIfTouched,
159}
160
161/// Order tag
162#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, EnumString, Display)]
163pub enum OrderTag {
164    /// Unknown
165    Unknown,
166    /// Normal Order
167    #[strum(serialize = "Normal")]
168    Normal,
169    /// Long term Order
170    #[strum(serialize = "GTC")]
171    LongTerm,
172    /// Grey Order
173    #[strum(serialize = "Grey")]
174    Grey,
175    /// Force Selling
176    MarginCall,
177    /// OTC
178    Offline,
179    /// Option Exercise Long
180    Creditor,
181    /// Option Exercise Short
182    Debtor,
183    /// Wavier Of Option Exercise
184    NonExercise,
185    /// Trade Allocation
186    AllocatedSub,
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}
236
237/// Order
238#[derive(Debug, Clone, Serialize, Deserialize)]
239pub struct Order {
240    /// Order ID
241    pub order_id: String,
242    /// Order status
243    pub status: OrderStatus,
244    /// Stock name
245    pub stock_name: String,
246    /// Submitted quantity
247    pub quantity: Decimal,
248    /// Executed quantity
249    pub executed_quantity: Decimal,
250    /// Submitted price
251    #[serde(with = "serde_utils::decimal_opt_empty_is_none")]
252    pub price: Option<Decimal>,
253    /// Executed price
254    #[serde(with = "serde_utils::decimal_opt_0_is_none")]
255    pub executed_price: Option<Decimal>,
256    /// Submitted time
257    #[serde(
258        serialize_with = "time::serde::rfc3339::serialize",
259        deserialize_with = "serde_utils::timestamp::deserialize"
260    )]
261    pub submitted_at: OffsetDateTime,
262    /// Order side
263    pub side: OrderSide,
264    /// Security code
265    pub symbol: String,
266    /// Order type
267    pub order_type: OrderType,
268    /// Last done
269    #[serde(with = "serde_utils::decimal_opt_empty_is_none")]
270    pub last_done: Option<Decimal>,
271    /// `LIT` / `MIT` Order Trigger Price
272    #[serde(with = "serde_utils::decimal_opt_empty_is_none")]
273    pub trigger_price: Option<Decimal>,
274    /// Rejected Message or remark
275    pub msg: String,
276    /// Order tag
277    pub tag: OrderTag,
278    /// Time in force type
279    pub time_in_force: TimeInForceType,
280    /// Long term order expire date
281    #[serde(with = "serde_utils::date_opt")]
282    pub expire_date: Option<Date>,
283    /// Last updated time
284    #[serde(
285        deserialize_with = "serde_utils::timestamp_opt::deserialize",
286        serialize_with = "serde_utils::rfc3339_opt::serialize"
287    )]
288    pub updated_at: Option<OffsetDateTime>,
289    /// Conditional order trigger time
290    #[serde(
291        deserialize_with = "serde_utils::timestamp_opt::deserialize",
292        serialize_with = "serde_utils::rfc3339_opt::serialize"
293    )]
294    pub trigger_at: Option<OffsetDateTime>,
295    /// `TSMAMT` / `TSLPAMT` order trailing amount
296    #[serde(with = "serde_utils::decimal_opt_empty_is_none")]
297    pub trailing_amount: Option<Decimal>,
298    /// `TSMPCT` / `TSLPPCT` order trailing percent
299    #[serde(with = "serde_utils::decimal_opt_empty_is_none")]
300    pub trailing_percent: Option<Decimal>,
301    /// `TSLPAMT` / `TSLPPCT` order limit offset amount
302    #[serde(with = "serde_utils::decimal_opt_empty_is_none")]
303    pub limit_offset: Option<Decimal>,
304    /// Conditional order trigger status
305    #[serde(with = "serde_utils::trigger_status")]
306    pub trigger_status: Option<TriggerStatus>,
307    /// Currency
308    pub currency: String,
309    /// Enable or disable outside regular trading hours
310    #[serde(with = "serde_utils::outside_rth")]
311    pub outside_rth: Option<OutsideRTH>,
312    /// Remark
313    pub remark: String,
314}
315
316/// Commission-free Status
317#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, EnumString, Display)]
318pub enum CommissionFreeStatus {
319    /// Unknown
320    Unknown,
321    /// None
322    None,
323    /// Commission-free amount to be calculated
324    Calculated,
325    /// Pending commission-free
326    Pending,
327    /// Commission-free applied
328    Ready,
329}
330
331/// Deduction status
332#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, EnumString, Display)]
333pub enum DeductionStatus {
334    /// Unknown
335    Unknown,
336    /// Pending Settlement
337    #[strum(serialize = "NONE")]
338    None,
339    /// Settled with no data
340    #[strum(serialize = "NO_DATA")]
341    NoData,
342    /// Settled and pending distribution
343    #[strum(serialize = "PENDING")]
344    Pending,
345    /// Settled and distributed
346    #[strum(serialize = "DONE")]
347    Done,
348}
349
350/// Charge category code
351#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, EnumString, Display)]
352pub enum ChargeCategoryCode {
353    /// Unknown
354    Unknown,
355    /// Broker
356    #[strum(serialize = "BROKER_FEES")]
357    Broker,
358    /// Third
359    #[strum(serialize = "THIRD_FEES")]
360    Third,
361}
362
363/// Order history detail
364#[derive(Debug, Clone, Serialize, Deserialize)]
365pub struct OrderHistoryDetail {
366    /// Executed price for executed orders, submitted price for expired,
367    /// canceled, rejected orders, etc.
368    #[serde(with = "serde_utils::decimal_empty_is_0")]
369    pub price: Decimal,
370    /// Executed quantity for executed orders, remaining quantity for expired,
371    /// canceled, rejected orders, etc.
372    pub quantity: Decimal,
373    /// Order status
374    pub status: OrderStatus,
375    /// Execution or error message
376    pub msg: String,
377    /// Occurrence time
378    #[serde(
379        serialize_with = "time::serde::rfc3339::serialize",
380        deserialize_with = "serde_utils::timestamp::deserialize"
381    )]
382    pub time: OffsetDateTime,
383}
384
385/// Order charge fee
386#[derive(Debug, Clone, Serialize, Deserialize)]
387pub struct OrderChargeFee {
388    /// Charge code
389    pub code: String,
390    /// Charge name
391    pub name: String,
392    /// Charge amount
393    #[serde(with = "serde_utils::decimal_empty_is_0")]
394    pub amount: Decimal,
395    /// Charge currency
396    pub currency: String,
397}
398
399/// Order charge item
400#[derive(Debug, Clone, Serialize, Deserialize)]
401pub struct OrderChargeItem {
402    /// Charge category code
403    pub code: ChargeCategoryCode,
404    /// Charge category name
405    pub name: String,
406    /// Charge details
407    pub fees: Vec<OrderChargeFee>,
408}
409
410/// Order charge detail
411#[derive(Debug, Clone, Serialize, Deserialize)]
412pub struct OrderChargeDetail {
413    /// Total charges amount
414    pub total_amount: Decimal,
415    /// Settlement currency
416    pub currency: String,
417    /// Order charge items
418    pub items: Vec<OrderChargeItem>,
419}
420
421/// Order detail
422#[derive(Debug, Clone, Serialize, Deserialize)]
423pub struct OrderDetail {
424    /// Order ID
425    pub order_id: String,
426    /// Order status
427    pub status: OrderStatus,
428    /// Stock name
429    pub stock_name: String,
430    /// Submitted quantity
431    pub quantity: Decimal,
432    /// Executed quantity
433    pub executed_quantity: Decimal,
434    /// Submitted price
435    #[serde(with = "serde_utils::decimal_opt_empty_is_none")]
436    pub price: Option<Decimal>,
437    /// Executed price
438    #[serde(with = "serde_utils::decimal_opt_0_is_none")]
439    pub executed_price: Option<Decimal>,
440    /// Submitted time
441    #[serde(
442        serialize_with = "time::serde::rfc3339::serialize",
443        deserialize_with = "serde_utils::timestamp::deserialize"
444    )]
445    pub submitted_at: OffsetDateTime,
446    /// Order side
447    pub side: OrderSide,
448    /// Security code
449    pub symbol: String,
450    /// Order type
451    pub order_type: OrderType,
452    /// Last done
453    #[serde(with = "serde_utils::decimal_opt_empty_is_none")]
454    pub last_done: Option<Decimal>,
455    /// `LIT` / `MIT` Order Trigger Price
456    #[serde(with = "serde_utils::decimal_opt_empty_is_none")]
457    pub trigger_price: Option<Decimal>,
458    /// Rejected Message or remark
459    pub msg: String,
460    /// Order tag
461    pub tag: OrderTag,
462    /// Time in force type
463    pub time_in_force: TimeInForceType,
464    /// Long term order expire date
465    #[serde(with = "serde_utils::date_opt")]
466    pub expire_date: Option<Date>,
467    /// Last updated time
468    #[serde(
469        deserialize_with = "serde_utils::timestamp_opt::deserialize",
470        serialize_with = "serde_utils::rfc3339_opt::serialize"
471    )]
472    pub updated_at: Option<OffsetDateTime>,
473    /// Conditional order trigger time
474    #[serde(
475        deserialize_with = "serde_utils::timestamp_opt::deserialize",
476        serialize_with = "serde_utils::rfc3339_opt::serialize"
477    )]
478    pub trigger_at: Option<OffsetDateTime>,
479    /// `TSMAMT` / `TSLPAMT` order trailing amount
480    #[serde(with = "serde_utils::decimal_opt_empty_is_none")]
481    pub trailing_amount: Option<Decimal>,
482    /// `TSMPCT` / `TSLPPCT` order trailing percent
483    #[serde(with = "serde_utils::decimal_opt_empty_is_none")]
484    pub trailing_percent: Option<Decimal>,
485    /// `TSLPAMT` / `TSLPPCT` order limit offset amount
486    #[serde(with = "serde_utils::decimal_opt_empty_is_none")]
487    pub limit_offset: Option<Decimal>,
488    /// Conditional order trigger status
489    #[serde(with = "serde_utils::trigger_status")]
490    pub trigger_status: Option<TriggerStatus>,
491    /// Currency
492    pub currency: String,
493    /// Enable or disable outside regular trading hours
494    #[serde(with = "serde_utils::outside_rth")]
495    pub outside_rth: Option<OutsideRTH>,
496    /// Remark
497    pub remark: String,
498    /// Commission-free Status
499    pub free_status: CommissionFreeStatus,
500    /// Commission-free amount
501    #[serde(with = "serde_utils::decimal_opt_empty_is_none")]
502    pub free_amount: Option<Decimal>,
503    /// Commission-free currency
504    #[serde(with = "serde_utils::symbol_opt")]
505    pub free_currency: Option<String>,
506    /// Deduction status
507    pub deductions_status: DeductionStatus,
508    /// Deduction amount
509    #[serde(with = "serde_utils::decimal_opt_empty_is_none")]
510    pub deductions_amount: Option<Decimal>,
511    /// Deduction currency
512    #[serde(with = "serde_utils::symbol_opt")]
513    pub deductions_currency: Option<String>,
514    /// Platform fee deduction status
515    pub platform_deducted_status: DeductionStatus,
516    /// Platform deduction amount
517    #[serde(with = "serde_utils::decimal_opt_empty_is_none")]
518    pub platform_deducted_amount: Option<Decimal>,
519    /// Platform deduction currency
520    #[serde(with = "serde_utils::symbol_opt")]
521    pub platform_deducted_currency: Option<String>,
522    /// Order history details
523    pub history: Vec<OrderHistoryDetail>,
524    /// Order charges
525    pub charge_detail: OrderChargeDetail,
526}
527
528/// Cash info
529#[derive(Debug, Clone, Serialize, Deserialize)]
530pub struct CashInfo {
531    /// Withdraw cash
532    pub withdraw_cash: Decimal,
533    /// Available cash
534    pub available_cash: Decimal,
535    /// Frozen cash
536    pub frozen_cash: Decimal,
537    /// Cash to be settled
538    pub settling_cash: Decimal,
539    /// Currency
540    pub currency: String,
541}
542
543/// Account balance
544#[derive(Debug, Clone, Serialize, Deserialize)]
545pub struct AccountBalance {
546    /// Total cash
547    pub total_cash: Decimal,
548    /// Maximum financing amount
549    pub max_finance_amount: Decimal,
550    /// Remaining financing amount
551    pub remaining_finance_amount: Decimal,
552    /// Risk control level
553    #[serde(with = "serde_utils::risk_level")]
554    pub risk_level: i32,
555    /// Margin call
556    pub margin_call: Decimal,
557    /// Currency
558    pub currency: String,
559    /// Cash details
560    #[serde(default)]
561    pub cash_infos: Vec<CashInfo>,
562    /// Net assets
563    #[serde(with = "serde_utils::decimal_empty_is_0")]
564    pub net_assets: Decimal,
565    /// Initial margin
566    #[serde(with = "serde_utils::decimal_empty_is_0")]
567    pub init_margin: Decimal,
568    /// Maintenance margin
569    #[serde(with = "serde_utils::decimal_empty_is_0")]
570    pub maintenance_margin: Decimal,
571    /// Buy power
572    #[serde(with = "serde_utils::decimal_empty_is_0")]
573    pub buy_power: Decimal,
574}
575
576/// Balance type
577#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, FromPrimitive, IntoPrimitive)]
578#[repr(i32)]
579pub enum BalanceType {
580    /// Unknown
581    #[num_enum(default)]
582    Unknown = 0,
583    /// Cash
584    Cash = 1,
585    /// Stock
586    Stock = 2,
587    /// Fund
588    Fund = 3,
589}
590
591impl Serialize for BalanceType {
592    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
593        let value: i32 = (*self).into();
594        value.serialize(serializer)
595    }
596}
597
598impl<'de> Deserialize<'de> for BalanceType {
599    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
600        let value = i32::deserialize(deserializer)?;
601        Ok(BalanceType::from(value))
602    }
603}
604
605/// Cash flow direction
606#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, FromPrimitive, Serialize)]
607#[repr(i32)]
608pub enum CashFlowDirection {
609    /// Unknown
610    #[num_enum(default)]
611    Unknown,
612    /// Out
613    Out = 1,
614    /// In
615    In = 2,
616}
617
618impl<'de> Deserialize<'de> for CashFlowDirection {
619    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
620        let value = i32::deserialize(deserializer)?;
621        Ok(CashFlowDirection::from(value))
622    }
623}
624
625/// Cash flow
626#[derive(Debug, Clone, Serialize, Deserialize)]
627pub struct CashFlow {
628    /// Cash flow name
629    pub transaction_flow_name: String,
630    /// Outflow direction
631    pub direction: CashFlowDirection,
632    /// Balance type
633    pub business_type: BalanceType,
634    /// Cash amount
635    pub balance: Decimal,
636    /// Cash currency
637    pub currency: String,
638    /// Business time
639    #[serde(
640        serialize_with = "time::serde::rfc3339::serialize",
641        deserialize_with = "serde_utils::timestamp::deserialize"
642    )]
643    pub business_time: OffsetDateTime,
644    /// Associated Stock code information
645    #[serde(with = "serde_utils::symbol_opt")]
646    pub symbol: Option<String>,
647    /// Cash flow description
648    pub description: String,
649}
650
651/// Fund positions response
652#[derive(Debug, Clone, Serialize, Deserialize)]
653pub struct FundPositionsResponse {
654    /// Channels
655    #[serde(rename = "list")]
656    pub channels: Vec<FundPositionChannel>,
657}
658
659/// Fund position channel
660#[derive(Debug, Clone, Serialize, Deserialize)]
661pub struct FundPositionChannel {
662    /// Account type
663    pub account_channel: String,
664
665    /// Fund positions
666    #[serde(default, rename = "fund_info")]
667    pub positions: Vec<FundPosition>,
668}
669
670/// Fund position
671#[derive(Debug, Clone, Serialize, Deserialize)]
672pub struct FundPosition {
673    /// Fund ISIN code
674    pub symbol: String,
675    /// Current equity
676    #[serde(with = "serde_utils::decimal_empty_is_0")]
677    pub current_net_asset_value: Decimal,
678    /// Current equity time
679    #[serde(
680        serialize_with = "time::serde::rfc3339::serialize",
681        deserialize_with = "serde_utils::timestamp::deserialize"
682    )]
683    pub net_asset_value_day: OffsetDateTime,
684    /// Fund name
685    pub symbol_name: String,
686    /// Currency
687    pub currency: String,
688    /// Net cost
689    #[serde(with = "serde_utils::decimal_empty_is_0")]
690    pub cost_net_asset_value: Decimal,
691    /// Holding units
692    #[serde(with = "serde_utils::decimal_empty_is_0")]
693    pub holding_units: Decimal,
694}
695
696/// Stock positions response
697#[derive(Debug, Clone, Serialize, Deserialize)]
698pub struct StockPositionsResponse {
699    /// Channels
700    #[serde(rename = "list")]
701    pub channels: Vec<StockPositionChannel>,
702}
703
704/// Stock position channel
705#[derive(Debug, Clone, Serialize, Deserialize)]
706pub struct StockPositionChannel {
707    /// Account type
708    pub account_channel: String,
709
710    /// Stock positions
711    #[serde(default, rename = "stock_info")]
712    pub positions: Vec<StockPosition>,
713}
714
715/// Stock position
716#[derive(Debug, Clone, Serialize, Deserialize)]
717pub struct StockPosition {
718    /// Stock code
719    pub symbol: String,
720    /// Stock name
721    pub symbol_name: String,
722    /// The number of holdings
723    pub quantity: Decimal,
724    /// Available quantity
725    pub available_quantity: Decimal,
726    /// Currency
727    pub currency: String,
728    /// Cost Price(According to the client's choice of average purchase or
729    /// diluted cost)
730    #[serde(with = "serde_utils::decimal_empty_is_0")]
731    pub cost_price: Decimal,
732    /// Market
733    pub market: Market,
734    /// Initial position before market opening
735    #[serde(with = "serde_utils::decimal_opt_empty_is_none")]
736    pub init_quantity: Option<Decimal>,
737}
738
739/// Margin ratio
740#[derive(Debug, Clone, Serialize, Deserialize)]
741pub struct MarginRatio {
742    /// Initial margin ratio
743    pub im_factor: Decimal,
744    /// Maintain the initial margin ratio
745    pub mm_factor: Decimal,
746    /// Forced close-out margin ratio
747    pub fm_factor: Decimal,
748}
749
750impl_serde_for_enum_string!(
751    OrderType,
752    OrderStatus,
753    OrderSide,
754    TriggerPriceType,
755    OrderTag,
756    TimeInForceType,
757    TriggerStatus,
758    OutsideRTH,
759    CommissionFreeStatus,
760    DeductionStatus,
761    ChargeCategoryCode
762);
763impl_default_for_enum_string!(
764    OrderType,
765    OrderStatus,
766    OrderSide,
767    TriggerPriceType,
768    OrderTag,
769    TimeInForceType,
770    TriggerStatus,
771    OutsideRTH,
772    CommissionFreeStatus,
773    DeductionStatus,
774    ChargeCategoryCode
775);
776
777#[cfg(test)]
778mod tests {
779    use time::macros::datetime;
780
781    use super::*;
782
783    #[test]
784    fn fund_position_response() {
785        let data = r#"
786        {
787            "list": [{
788                "account_channel": "lb",
789                "fund_info": [{
790                    "symbol": "HK0000447943",
791                    "symbol_name": "高腾亚洲收益基金",
792                    "currency": "USD",
793                    "holding_units": "5.000",
794                    "current_net_asset_value": "0",
795                    "cost_net_asset_value": "0.00",
796                    "net_asset_value_day": "1649865600"
797                }]
798            }]
799        }
800        "#;
801
802        let resp: FundPositionsResponse = serde_json::from_str(data).unwrap();
803        assert_eq!(resp.channels.len(), 1);
804
805        let channel = &resp.channels[0];
806        assert_eq!(channel.account_channel, "lb");
807        assert_eq!(channel.positions.len(), 1);
808
809        let position = &channel.positions[0];
810        assert_eq!(position.symbol, "HK0000447943");
811        assert_eq!(position.symbol_name, "高腾亚洲收益基金");
812        assert_eq!(position.currency, "USD");
813        assert_eq!(position.current_net_asset_value, decimal!(0i32));
814        assert_eq!(position.cost_net_asset_value, decimal!(0i32));
815        assert_eq!(position.holding_units, decimal!(5i32));
816        assert_eq!(position.net_asset_value_day, datetime!(2022-4-14 0:00 +8));
817    }
818
819    #[test]
820    fn stock_position_response() {
821        let data = r#"
822        {
823            "list": [
824              {
825                "account_channel": "lb",
826                "stock_info": [
827                  {
828                    "symbol": "700.HK",
829                    "symbol_name": "腾讯控股",
830                    "currency": "HK",
831                    "quantity": "650",
832                    "available_quantity": "-450",
833                    "cost_price": "457.53",
834                    "market": "HK",
835                    "init_quantity": "2000"
836                  },
837                  {
838                    "symbol": "9991.HK",
839                    "symbol_name": "宝尊电商-SW",
840                    "currency": "HK",
841                    "quantity": "200",
842                    "available_quantity": "0",
843                    "cost_price": "32.25",
844                    "market": "HK",
845                    "init_quantity": ""
846                  }
847                ]
848              }
849            ]
850          }
851        "#;
852
853        let resp: StockPositionsResponse = serde_json::from_str(data).unwrap();
854        assert_eq!(resp.channels.len(), 1);
855
856        let channel = &resp.channels[0];
857        assert_eq!(channel.account_channel, "lb");
858        assert_eq!(channel.positions.len(), 2);
859
860        let position = &channel.positions[0];
861        assert_eq!(position.symbol, "700.HK");
862        assert_eq!(position.symbol_name, "腾讯控股");
863        assert_eq!(position.currency, "HK");
864        assert_eq!(position.quantity, decimal!(650));
865        assert_eq!(position.available_quantity, decimal!(-450));
866        assert_eq!(position.cost_price, decimal!(457.53f32));
867        assert_eq!(position.market, Market::HK);
868        assert_eq!(position.init_quantity, Some(decimal!(2000)));
869
870        let position = &channel.positions[0];
871        assert_eq!(position.symbol, "700.HK");
872        assert_eq!(position.symbol_name, "腾讯控股");
873        assert_eq!(position.currency, "HK");
874        assert_eq!(position.quantity, decimal!(650));
875        assert_eq!(position.available_quantity, decimal!(-450));
876        assert_eq!(position.cost_price, decimal!(457.53f32));
877        assert_eq!(position.market, Market::HK);
878
879        let position = &channel.positions[1];
880        assert_eq!(position.symbol, "9991.HK");
881        assert_eq!(position.symbol_name, "宝尊电商-SW");
882        assert_eq!(position.currency, "HK");
883        assert_eq!(position.quantity, decimal!(200));
884        assert_eq!(position.available_quantity, decimal!(0));
885        assert_eq!(position.cost_price, decimal!(32.25f32));
886        assert_eq!(position.init_quantity, None);
887    }
888
889    #[test]
890    fn cash_flow() {
891        let data = r#"
892        {
893            "list": [
894              {
895                "transaction_flow_name": "BuyContract-Stocks",
896                "direction": 1,
897                "balance": "-248.60",
898                "currency": "USD",
899                "business_type": 1,
900                "business_time": "1621507957",
901                "symbol": "AAPL.US",
902                "description": "AAPL"
903              },
904              {
905                "transaction_flow_name": "BuyContract-Stocks",
906                "direction": 1,
907                "balance": "-125.16",
908                "currency": "USD",
909                "business_type": 2,
910                "business_time": "1621504824",
911                "symbol": "AAPL.US",
912                "description": "AAPL"
913              }
914            ]
915          }
916          "#;
917
918        #[derive(Debug, Deserialize)]
919        struct Response {
920            list: Vec<CashFlow>,
921        }
922
923        let resp: Response = serde_json::from_str(data).unwrap();
924        assert_eq!(resp.list.len(), 2);
925
926        let cashflow = &resp.list[0];
927        assert_eq!(cashflow.transaction_flow_name, "BuyContract-Stocks");
928        assert_eq!(cashflow.direction, CashFlowDirection::Out);
929        assert_eq!(cashflow.balance, decimal!(-248.60f32));
930        assert_eq!(cashflow.currency, "USD");
931        assert_eq!(cashflow.business_type, BalanceType::Cash);
932        assert_eq!(cashflow.business_time, datetime!(2021-05-20 18:52:37 +8));
933        assert_eq!(cashflow.symbol.as_deref(), Some("AAPL.US"));
934        assert_eq!(cashflow.description, "AAPL");
935
936        let cashflow = &resp.list[1];
937        assert_eq!(cashflow.transaction_flow_name, "BuyContract-Stocks");
938        assert_eq!(cashflow.direction, CashFlowDirection::Out);
939        assert_eq!(cashflow.balance, decimal!(-125.16f32));
940        assert_eq!(cashflow.currency, "USD");
941        assert_eq!(cashflow.business_type, BalanceType::Stock);
942        assert_eq!(cashflow.business_time, datetime!(2021-05-20 18:00:24 +8));
943        assert_eq!(cashflow.symbol.as_deref(), Some("AAPL.US"));
944        assert_eq!(cashflow.description, "AAPL");
945    }
946
947    #[test]
948    fn account_balance() {
949        let data = r#"
950        {
951            "list": [
952              {
953                "total_cash": "1759070010.72",
954                "max_finance_amount": "977582000",
955                "remaining_finance_amount": "0",
956                "risk_level": "1",
957                "margin_call": "2598051051.50",
958                "currency": "HKD",
959                "cash_infos": [
960                  {
961                    "withdraw_cash": "97592.30",
962                    "available_cash": "195902464.37",
963                    "frozen_cash": "11579339.13",
964                    "settling_cash": "207288537.81",
965                    "currency": "HKD"
966                  },
967                  {
968                    "withdraw_cash": "199893416.74",
969                    "available_cash": "199893416.74",
970                    "frozen_cash": "28723.76",
971                    "settling_cash": "-276806.51",
972                    "currency": "USD"
973                  }
974                ],
975                "net_assets": "11111.12",
976                "init_margin": "2222.23",
977                "maintenance_margin": "3333.45",
978                "buy_power": "1234.67"
979              }
980            ]
981          }"#;
982
983        #[derive(Debug, Deserialize)]
984        struct Response {
985            list: Vec<AccountBalance>,
986        }
987
988        let resp: Response = serde_json::from_str(data).unwrap();
989        assert_eq!(resp.list.len(), 1);
990
991        let balance = &resp.list[0];
992        assert_eq!(balance.total_cash, "1759070010.72".parse().unwrap());
993        assert_eq!(balance.max_finance_amount, "977582000".parse().unwrap());
994        assert_eq!(balance.remaining_finance_amount, decimal!(0i32));
995        assert_eq!(balance.risk_level, 1);
996        assert_eq!(balance.margin_call, "2598051051.50".parse().unwrap());
997        assert_eq!(balance.currency, "HKD");
998        assert_eq!(balance.net_assets, "11111.12".parse().unwrap());
999        assert_eq!(balance.init_margin, "2222.23".parse().unwrap());
1000        assert_eq!(balance.maintenance_margin, "3333.45".parse().unwrap());
1001        assert_eq!(balance.buy_power, "1234.67".parse().unwrap());
1002
1003        assert_eq!(balance.cash_infos.len(), 2);
1004
1005        let cash_info = &balance.cash_infos[0];
1006        assert_eq!(cash_info.withdraw_cash, "97592.30".parse().unwrap());
1007        assert_eq!(cash_info.available_cash, "195902464.37".parse().unwrap());
1008        assert_eq!(cash_info.frozen_cash, "11579339.13".parse().unwrap());
1009        assert_eq!(cash_info.settling_cash, "207288537.81".parse().unwrap());
1010        assert_eq!(cash_info.currency, "HKD");
1011
1012        let cash_info = &balance.cash_infos[1];
1013        assert_eq!(cash_info.withdraw_cash, "199893416.74".parse().unwrap());
1014        assert_eq!(cash_info.available_cash, "199893416.74".parse().unwrap());
1015        assert_eq!(cash_info.frozen_cash, "28723.76".parse().unwrap());
1016        assert_eq!(cash_info.settling_cash, "-276806.51".parse().unwrap());
1017        assert_eq!(cash_info.currency, "USD");
1018    }
1019
1020    #[test]
1021    fn history_orders() {
1022        let data = r#"
1023        {
1024            "orders": [
1025              {
1026                "currency": "HKD",
1027                "executed_price": "0.000",
1028                "executed_quantity": "0",
1029                "expire_date": "",
1030                "last_done": "",
1031                "limit_offset": "",
1032                "msg": "",
1033                "order_id": "706388312699592704",
1034                "order_type": "ELO",
1035                "outside_rth": "UnknownOutsideRth",
1036                "price": "11.900",
1037                "quantity": "200",
1038                "side": "Buy",
1039                "status": "RejectedStatus",
1040                "stock_name": "Bank of East Asia Ltd/The",
1041                "submitted_at": "1651644897",
1042                "symbol": "23.HK",
1043                "tag": "Normal",
1044                "time_in_force": "Day",
1045                "trailing_amount": "",
1046                "trailing_percent": "",
1047                "trigger_at": "0",
1048                "trigger_price": "",
1049                "trigger_status": "NOT_USED",
1050                "updated_at": "1651644898",
1051                "remark": "abc"
1052              }
1053            ]
1054          }
1055        "#;
1056
1057        #[derive(Deserialize)]
1058        struct Response {
1059            orders: Vec<Order>,
1060        }
1061
1062        let resp: Response = serde_json::from_str(data).unwrap();
1063        assert_eq!(resp.orders.len(), 1);
1064
1065        let order = &resp.orders[0];
1066        assert_eq!(order.currency, "HKD");
1067        assert!(order.executed_price.is_none());
1068        assert_eq!(order.executed_quantity, decimal!(0));
1069        assert!(order.expire_date.is_none());
1070        assert!(order.last_done.is_none());
1071        assert!(order.limit_offset.is_none());
1072        assert_eq!(order.msg, "");
1073        assert_eq!(order.order_id, "706388312699592704");
1074        assert_eq!(order.order_type, OrderType::ELO);
1075        assert!(order.outside_rth.is_none());
1076        assert_eq!(order.price, Some("11.900".parse().unwrap()));
1077        assert_eq!(order.quantity, decimal!(200));
1078        assert_eq!(order.side, OrderSide::Buy);
1079        assert_eq!(order.status, OrderStatus::Rejected);
1080        assert_eq!(order.stock_name, "Bank of East Asia Ltd/The");
1081        assert_eq!(order.submitted_at, datetime!(2022-05-04 14:14:57 +8));
1082        assert_eq!(order.symbol, "23.HK");
1083        assert_eq!(order.tag, OrderTag::Normal);
1084        assert_eq!(order.time_in_force, TimeInForceType::Day);
1085        assert!(order.trailing_amount.is_none());
1086        assert!(order.trailing_percent.is_none());
1087        assert!(order.trigger_at.is_none());
1088        assert!(order.trigger_price.is_none());
1089        assert!(order.trigger_status.is_none());
1090        assert_eq!(order.updated_at, Some(datetime!(2022-05-04 14:14:58 +8)));
1091        assert_eq!(order.remark, "abc");
1092    }
1093
1094    #[test]
1095    fn today_orders() {
1096        let data = r#"
1097        {
1098            "orders": [
1099              {
1100                "currency": "HKD",
1101                "executed_price": "0.000",
1102                "executed_quantity": "0",
1103                "expire_date": "",
1104                "last_done": "",
1105                "limit_offset": "",
1106                "msg": "",
1107                "order_id": "706388312699592704",
1108                "order_type": "ELO",
1109                "outside_rth": "UnknownOutsideRth",
1110                "price": "11.900",
1111                "quantity": "200",
1112                "side": "Buy",
1113                "status": "RejectedStatus",
1114                "stock_name": "Bank of East Asia Ltd/The",
1115                "submitted_at": "1651644897",
1116                "symbol": "23.HK",
1117                "tag": "Normal",
1118                "time_in_force": "Day",
1119                "trailing_amount": "",
1120                "trailing_percent": "",
1121                "trigger_at": "0",
1122                "trigger_price": "",
1123                "trigger_status": "NOT_USED",
1124                "updated_at": "1651644898",
1125                "remark": "abc"
1126              }
1127            ]
1128          }
1129        "#;
1130
1131        #[derive(Deserialize)]
1132        struct Response {
1133            orders: Vec<Order>,
1134        }
1135
1136        let resp: Response = serde_json::from_str(data).unwrap();
1137        assert_eq!(resp.orders.len(), 1);
1138
1139        let order = &resp.orders[0];
1140        assert_eq!(order.currency, "HKD");
1141        assert!(order.executed_price.is_none());
1142        assert_eq!(order.executed_quantity, decimal!(0));
1143        assert!(order.expire_date.is_none());
1144        assert!(order.last_done.is_none());
1145        assert!(order.limit_offset.is_none());
1146        assert_eq!(order.msg, "");
1147        assert_eq!(order.order_id, "706388312699592704");
1148        assert_eq!(order.order_type, OrderType::ELO);
1149        assert!(order.outside_rth.is_none());
1150        assert_eq!(order.price, Some("11.900".parse().unwrap()));
1151        assert_eq!(order.quantity, decimal!(200));
1152        assert_eq!(order.side, OrderSide::Buy);
1153        assert_eq!(order.status, OrderStatus::Rejected);
1154        assert_eq!(order.stock_name, "Bank of East Asia Ltd/The");
1155        assert_eq!(order.submitted_at, datetime!(2022-05-04 14:14:57 +8));
1156        assert_eq!(order.symbol, "23.HK");
1157        assert_eq!(order.tag, OrderTag::Normal);
1158        assert_eq!(order.time_in_force, TimeInForceType::Day);
1159        assert!(order.trailing_amount.is_none());
1160        assert!(order.trailing_percent.is_none());
1161        assert!(order.trigger_at.is_none());
1162        assert!(order.trigger_price.is_none());
1163        assert!(order.trigger_status.is_none());
1164        assert_eq!(order.updated_at, Some(datetime!(2022-05-04 14:14:58 +8)));
1165        assert_eq!(order.remark, "abc");
1166    }
1167
1168    #[test]
1169    fn history_executions() {
1170        let data = r#"
1171        {
1172            "has_more": false,
1173            "trades": [
1174              {
1175                "order_id": "693664675163312128",
1176                "price": "388",
1177                "quantity": "100",
1178                "symbol": "700.HK",
1179                "trade_done_at": "1648611351",
1180                "trade_id": "693664675163312128-1648611351433741210"
1181              }
1182            ]
1183          }
1184        "#;
1185
1186        #[derive(Deserialize)]
1187        struct Response {
1188            trades: Vec<Execution>,
1189        }
1190
1191        let resp: Response = serde_json::from_str(data).unwrap();
1192        assert_eq!(resp.trades.len(), 1);
1193
1194        let execution = &resp.trades[0];
1195        assert_eq!(execution.order_id, "693664675163312128");
1196        assert_eq!(execution.price, "388".parse().unwrap());
1197        assert_eq!(execution.quantity, decimal!(100));
1198        assert_eq!(execution.symbol, "700.HK");
1199        assert_eq!(execution.trade_done_at, datetime!(2022-03-30 11:35:51 +8));
1200        assert_eq!(execution.trade_id, "693664675163312128-1648611351433741210");
1201    }
1202
1203    #[test]
1204    fn order_detail() {
1205        let data = r#"
1206        {
1207            "order_id": "828940451093708800",
1208            "status": "FilledStatus",
1209            "stock_name": "Apple",
1210            "quantity": "10",
1211            "executed_quantity": "10",
1212            "price": "200.000",
1213            "executed_price": "164.660",
1214            "submitted_at": "1680863604",
1215            "side": "Buy",
1216            "symbol": "AAPL.US",
1217            "order_type": "LO",
1218            "last_done": "164.660",
1219            "trigger_price": "0.0000",
1220            "msg": "",
1221            "tag": "Normal",
1222            "time_in_force": "Day",
1223            "expire_date": "2023-04-10",
1224            "updated_at": "1681113000",
1225            "trigger_at": "0",
1226            "trailing_amount": "",
1227            "trailing_percent": "",
1228            "limit_offset": "",
1229            "trigger_status": "NOT_USED",
1230            "outside_rth": "ANY_TIME",
1231            "currency": "USD",
1232            "remark": "1680863603.927165",
1233            "free_status": "None",
1234            "free_amount": "",
1235            "free_currency": "",
1236            "deductions_status": "NONE",
1237            "deductions_amount": "",
1238            "deductions_currency": "",
1239            "platform_deducted_status": "NONE",
1240            "platform_deducted_amount": "",
1241            "platform_deducted_currency": "",
1242            "history": [{
1243                "price": "164.6600",
1244                "quantity": "10",
1245                "status": "FilledStatus",
1246                "msg": "Execution of 10",
1247                "time": "1681113000"
1248            }, {
1249                "price": "200.0000",
1250                "quantity": "10",
1251                "status": "NewStatus",
1252                "msg": "",
1253                "time": "1681113000"
1254            }],
1255            "charge_detail": {
1256                "items": [{
1257                    "code": "BROKER_FEES",
1258                    "name": "Broker Fees",
1259                    "fees": []
1260                }, {
1261                    "code": "THIRD_FEES",
1262                    "name": "Third-party Fees",
1263                    "fees": []
1264                }],
1265                "total_amount": "0",
1266                "currency": "USD"
1267            }
1268        }
1269        "#;
1270
1271        _ = serde_json::from_str::<OrderDetail>(data).unwrap();
1272    }
1273}