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/// Frozen transaction fee
544#[derive(Debug, Clone, Serialize, Deserialize)]
545pub struct FrozenTransactionFee {
546    /// Currency
547    pub currency: String,
548    /// Frozen transaction fee amount
549    pub frozen_transaction_fee: Decimal,
550}
551
552/// Account balance
553#[derive(Debug, Clone, Serialize, Deserialize)]
554pub struct AccountBalance {
555    /// Total cash
556    pub total_cash: Decimal,
557    /// Maximum financing amount
558    pub max_finance_amount: Decimal,
559    /// Remaining financing amount
560    pub remaining_finance_amount: Decimal,
561    /// Risk control level
562    #[serde(with = "serde_utils::risk_level")]
563    pub risk_level: i32,
564    /// Margin call
565    pub margin_call: Decimal,
566    /// Currency
567    pub currency: String,
568    /// Cash details
569    #[serde(default)]
570    pub cash_infos: Vec<CashInfo>,
571    /// Net assets
572    #[serde(with = "serde_utils::decimal_empty_is_0")]
573    pub net_assets: Decimal,
574    /// Initial margin
575    #[serde(with = "serde_utils::decimal_empty_is_0")]
576    pub init_margin: Decimal,
577    /// Maintenance margin
578    #[serde(with = "serde_utils::decimal_empty_is_0")]
579    pub maintenance_margin: Decimal,
580    /// Buy power
581    #[serde(with = "serde_utils::decimal_empty_is_0")]
582    pub buy_power: Decimal,
583    /// Frozen transaction fees
584    pub frozen_transaction_fees: Vec<FrozenTransactionFee>,
585}
586
587/// Balance type
588#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, FromPrimitive, IntoPrimitive)]
589#[repr(i32)]
590pub enum BalanceType {
591    /// Unknown
592    #[num_enum(default)]
593    Unknown = 0,
594    /// Cash
595    Cash = 1,
596    /// Stock
597    Stock = 2,
598    /// Fund
599    Fund = 3,
600}
601
602impl Serialize for BalanceType {
603    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
604        let value: i32 = (*self).into();
605        value.serialize(serializer)
606    }
607}
608
609impl<'de> Deserialize<'de> for BalanceType {
610    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
611        let value = i32::deserialize(deserializer)?;
612        Ok(BalanceType::from(value))
613    }
614}
615
616/// Cash flow direction
617#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, FromPrimitive, Serialize)]
618#[repr(i32)]
619pub enum CashFlowDirection {
620    /// Unknown
621    #[num_enum(default)]
622    Unknown,
623    /// Out
624    Out = 1,
625    /// In
626    In = 2,
627}
628
629impl<'de> Deserialize<'de> for CashFlowDirection {
630    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
631        let value = i32::deserialize(deserializer)?;
632        Ok(CashFlowDirection::from(value))
633    }
634}
635
636/// Cash flow
637#[derive(Debug, Clone, Serialize, Deserialize)]
638pub struct CashFlow {
639    /// Cash flow name
640    pub transaction_flow_name: String,
641    /// Outflow direction
642    pub direction: CashFlowDirection,
643    /// Balance type
644    pub business_type: BalanceType,
645    /// Cash amount
646    pub balance: Decimal,
647    /// Cash currency
648    pub currency: String,
649    /// Business time
650    #[serde(
651        serialize_with = "time::serde::rfc3339::serialize",
652        deserialize_with = "serde_utils::timestamp::deserialize"
653    )]
654    pub business_time: OffsetDateTime,
655    /// Associated Stock code information
656    #[serde(with = "serde_utils::symbol_opt")]
657    pub symbol: Option<String>,
658    /// Cash flow description
659    pub description: String,
660}
661
662/// Fund positions response
663#[derive(Debug, Clone, Serialize, Deserialize)]
664pub struct FundPositionsResponse {
665    /// Channels
666    #[serde(rename = "list")]
667    pub channels: Vec<FundPositionChannel>,
668}
669
670/// Fund position channel
671#[derive(Debug, Clone, Serialize, Deserialize)]
672pub struct FundPositionChannel {
673    /// Account type
674    pub account_channel: String,
675
676    /// Fund positions
677    #[serde(default, rename = "fund_info")]
678    pub positions: Vec<FundPosition>,
679}
680
681/// Fund position
682#[derive(Debug, Clone, Serialize, Deserialize)]
683pub struct FundPosition {
684    /// Fund ISIN code
685    pub symbol: String,
686    /// Current equity
687    #[serde(with = "serde_utils::decimal_empty_is_0")]
688    pub current_net_asset_value: Decimal,
689    /// Current equity time
690    #[serde(
691        serialize_with = "time::serde::rfc3339::serialize",
692        deserialize_with = "serde_utils::timestamp::deserialize"
693    )]
694    pub net_asset_value_day: OffsetDateTime,
695    /// Fund name
696    pub symbol_name: String,
697    /// Currency
698    pub currency: String,
699    /// Net cost
700    #[serde(with = "serde_utils::decimal_empty_is_0")]
701    pub cost_net_asset_value: Decimal,
702    /// Holding units
703    #[serde(with = "serde_utils::decimal_empty_is_0")]
704    pub holding_units: Decimal,
705}
706
707/// Stock positions response
708#[derive(Debug, Clone, Serialize, Deserialize)]
709pub struct StockPositionsResponse {
710    /// Channels
711    #[serde(rename = "list")]
712    pub channels: Vec<StockPositionChannel>,
713}
714
715/// Stock position channel
716#[derive(Debug, Clone, Serialize, Deserialize)]
717pub struct StockPositionChannel {
718    /// Account type
719    pub account_channel: String,
720
721    /// Stock positions
722    #[serde(default, rename = "stock_info")]
723    pub positions: Vec<StockPosition>,
724}
725
726/// Stock position
727#[derive(Debug, Clone, Serialize, Deserialize)]
728pub struct StockPosition {
729    /// Stock code
730    pub symbol: String,
731    /// Stock name
732    pub symbol_name: String,
733    /// The number of holdings
734    pub quantity: Decimal,
735    /// Available quantity
736    pub available_quantity: Decimal,
737    /// Currency
738    pub currency: String,
739    /// Cost Price(According to the client's choice of average purchase or
740    /// diluted cost)
741    #[serde(with = "serde_utils::decimal_empty_is_0")]
742    pub cost_price: Decimal,
743    /// Market
744    pub market: Market,
745    /// Initial position before market opening
746    #[serde(with = "serde_utils::decimal_opt_empty_is_none")]
747    pub init_quantity: Option<Decimal>,
748}
749
750/// Margin ratio
751#[derive(Debug, Clone, Serialize, Deserialize)]
752pub struct MarginRatio {
753    /// Initial margin ratio
754    pub im_factor: Decimal,
755    /// Maintain the initial margin ratio
756    pub mm_factor: Decimal,
757    /// Forced close-out margin ratio
758    pub fm_factor: Decimal,
759}
760
761impl_serde_for_enum_string!(
762    OrderType,
763    OrderStatus,
764    OrderSide,
765    TriggerPriceType,
766    OrderTag,
767    TimeInForceType,
768    TriggerStatus,
769    OutsideRTH,
770    CommissionFreeStatus,
771    DeductionStatus,
772    ChargeCategoryCode
773);
774impl_default_for_enum_string!(
775    OrderType,
776    OrderStatus,
777    OrderSide,
778    TriggerPriceType,
779    OrderTag,
780    TimeInForceType,
781    TriggerStatus,
782    OutsideRTH,
783    CommissionFreeStatus,
784    DeductionStatus,
785    ChargeCategoryCode
786);
787
788#[cfg(test)]
789mod tests {
790    use time::macros::datetime;
791
792    use super::*;
793
794    #[test]
795    fn fund_position_response() {
796        let data = r#"
797        {
798            "list": [{
799                "account_channel": "lb",
800                "fund_info": [{
801                    "symbol": "HK0000447943",
802                    "symbol_name": "高腾亚洲收益基金",
803                    "currency": "USD",
804                    "holding_units": "5.000",
805                    "current_net_asset_value": "0",
806                    "cost_net_asset_value": "0.00",
807                    "net_asset_value_day": "1649865600"
808                }]
809            }]
810        }
811        "#;
812
813        let resp: FundPositionsResponse = serde_json::from_str(data).unwrap();
814        assert_eq!(resp.channels.len(), 1);
815
816        let channel = &resp.channels[0];
817        assert_eq!(channel.account_channel, "lb");
818        assert_eq!(channel.positions.len(), 1);
819
820        let position = &channel.positions[0];
821        assert_eq!(position.symbol, "HK0000447943");
822        assert_eq!(position.symbol_name, "高腾亚洲收益基金");
823        assert_eq!(position.currency, "USD");
824        assert_eq!(position.current_net_asset_value, decimal!(0i32));
825        assert_eq!(position.cost_net_asset_value, decimal!(0i32));
826        assert_eq!(position.holding_units, decimal!(5i32));
827        assert_eq!(position.net_asset_value_day, datetime!(2022-4-14 0:00 +8));
828    }
829
830    #[test]
831    fn stock_position_response() {
832        let data = r#"
833        {
834            "list": [
835              {
836                "account_channel": "lb",
837                "stock_info": [
838                  {
839                    "symbol": "700.HK",
840                    "symbol_name": "腾讯控股",
841                    "currency": "HK",
842                    "quantity": "650",
843                    "available_quantity": "-450",
844                    "cost_price": "457.53",
845                    "market": "HK",
846                    "init_quantity": "2000"
847                  },
848                  {
849                    "symbol": "9991.HK",
850                    "symbol_name": "宝尊电商-SW",
851                    "currency": "HK",
852                    "quantity": "200",
853                    "available_quantity": "0",
854                    "cost_price": "32.25",
855                    "market": "HK",
856                    "init_quantity": ""
857                  }
858                ]
859              }
860            ]
861          }
862        "#;
863
864        let resp: StockPositionsResponse = serde_json::from_str(data).unwrap();
865        assert_eq!(resp.channels.len(), 1);
866
867        let channel = &resp.channels[0];
868        assert_eq!(channel.account_channel, "lb");
869        assert_eq!(channel.positions.len(), 2);
870
871        let position = &channel.positions[0];
872        assert_eq!(position.symbol, "700.HK");
873        assert_eq!(position.symbol_name, "腾讯控股");
874        assert_eq!(position.currency, "HK");
875        assert_eq!(position.quantity, decimal!(650));
876        assert_eq!(position.available_quantity, decimal!(-450));
877        assert_eq!(position.cost_price, decimal!(457.53f32));
878        assert_eq!(position.market, Market::HK);
879        assert_eq!(position.init_quantity, Some(decimal!(2000)));
880
881        let position = &channel.positions[0];
882        assert_eq!(position.symbol, "700.HK");
883        assert_eq!(position.symbol_name, "腾讯控股");
884        assert_eq!(position.currency, "HK");
885        assert_eq!(position.quantity, decimal!(650));
886        assert_eq!(position.available_quantity, decimal!(-450));
887        assert_eq!(position.cost_price, decimal!(457.53f32));
888        assert_eq!(position.market, Market::HK);
889
890        let position = &channel.positions[1];
891        assert_eq!(position.symbol, "9991.HK");
892        assert_eq!(position.symbol_name, "宝尊电商-SW");
893        assert_eq!(position.currency, "HK");
894        assert_eq!(position.quantity, decimal!(200));
895        assert_eq!(position.available_quantity, decimal!(0));
896        assert_eq!(position.cost_price, decimal!(32.25f32));
897        assert_eq!(position.init_quantity, None);
898    }
899
900    #[test]
901    fn cash_flow() {
902        let data = r#"
903        {
904            "list": [
905              {
906                "transaction_flow_name": "BuyContract-Stocks",
907                "direction": 1,
908                "balance": "-248.60",
909                "currency": "USD",
910                "business_type": 1,
911                "business_time": "1621507957",
912                "symbol": "AAPL.US",
913                "description": "AAPL"
914              },
915              {
916                "transaction_flow_name": "BuyContract-Stocks",
917                "direction": 1,
918                "balance": "-125.16",
919                "currency": "USD",
920                "business_type": 2,
921                "business_time": "1621504824",
922                "symbol": "AAPL.US",
923                "description": "AAPL"
924              }
925            ]
926          }
927          "#;
928
929        #[derive(Debug, Deserialize)]
930        struct Response {
931            list: Vec<CashFlow>,
932        }
933
934        let resp: Response = serde_json::from_str(data).unwrap();
935        assert_eq!(resp.list.len(), 2);
936
937        let cashflow = &resp.list[0];
938        assert_eq!(cashflow.transaction_flow_name, "BuyContract-Stocks");
939        assert_eq!(cashflow.direction, CashFlowDirection::Out);
940        assert_eq!(cashflow.balance, decimal!(-248.60f32));
941        assert_eq!(cashflow.currency, "USD");
942        assert_eq!(cashflow.business_type, BalanceType::Cash);
943        assert_eq!(cashflow.business_time, datetime!(2021-05-20 18:52:37 +8));
944        assert_eq!(cashflow.symbol.as_deref(), Some("AAPL.US"));
945        assert_eq!(cashflow.description, "AAPL");
946
947        let cashflow = &resp.list[1];
948        assert_eq!(cashflow.transaction_flow_name, "BuyContract-Stocks");
949        assert_eq!(cashflow.direction, CashFlowDirection::Out);
950        assert_eq!(cashflow.balance, decimal!(-125.16f32));
951        assert_eq!(cashflow.currency, "USD");
952        assert_eq!(cashflow.business_type, BalanceType::Stock);
953        assert_eq!(cashflow.business_time, datetime!(2021-05-20 18:00:24 +8));
954        assert_eq!(cashflow.symbol.as_deref(), Some("AAPL.US"));
955        assert_eq!(cashflow.description, "AAPL");
956    }
957
958    #[test]
959    fn account_balance() {
960        let data = r#"
961        {
962            "list": [
963              {
964                "total_cash": "1759070010.72",
965                "max_finance_amount": "977582000",
966                "remaining_finance_amount": "0",
967                "risk_level": "1",
968                "margin_call": "2598051051.50",
969                "currency": "HKD",
970                "cash_infos": [
971                  {
972                    "withdraw_cash": "97592.30",
973                    "available_cash": "195902464.37",
974                    "frozen_cash": "11579339.13",
975                    "settling_cash": "207288537.81",
976                    "currency": "HKD"
977                  },
978                  {
979                    "withdraw_cash": "199893416.74",
980                    "available_cash": "199893416.74",
981                    "frozen_cash": "28723.76",
982                    "settling_cash": "-276806.51",
983                    "currency": "USD"
984                  }
985                ],
986                "net_assets": "11111.12",
987                "init_margin": "2222.23",
988                "maintenance_margin": "3333.45",
989                "buy_power": "1234.67",
990                "frozen_transaction_fees": [
991                    {
992                        "currency": "HKD",
993                        "frozen_transaction_fee": "123"
994                    }
995                ]
996              }
997            ]
998          }"#;
999
1000        #[derive(Debug, Deserialize)]
1001        struct Response {
1002            list: Vec<AccountBalance>,
1003        }
1004
1005        let resp: Response = serde_json::from_str(data).unwrap();
1006        assert_eq!(resp.list.len(), 1);
1007
1008        let balance = &resp.list[0];
1009        assert_eq!(balance.total_cash, "1759070010.72".parse().unwrap());
1010        assert_eq!(balance.max_finance_amount, "977582000".parse().unwrap());
1011        assert_eq!(balance.remaining_finance_amount, decimal!(0i32));
1012        assert_eq!(balance.risk_level, 1);
1013        assert_eq!(balance.margin_call, "2598051051.50".parse().unwrap());
1014        assert_eq!(balance.currency, "HKD");
1015        assert_eq!(balance.net_assets, "11111.12".parse().unwrap());
1016        assert_eq!(balance.init_margin, "2222.23".parse().unwrap());
1017        assert_eq!(balance.maintenance_margin, "3333.45".parse().unwrap());
1018        assert_eq!(balance.buy_power, "1234.67".parse().unwrap());
1019
1020        assert_eq!(balance.cash_infos.len(), 2);
1021
1022        let cash_info = &balance.cash_infos[0];
1023        assert_eq!(cash_info.withdraw_cash, "97592.30".parse().unwrap());
1024        assert_eq!(cash_info.available_cash, "195902464.37".parse().unwrap());
1025        assert_eq!(cash_info.frozen_cash, "11579339.13".parse().unwrap());
1026        assert_eq!(cash_info.settling_cash, "207288537.81".parse().unwrap());
1027        assert_eq!(cash_info.currency, "HKD");
1028
1029        let cash_info = &balance.cash_infos[1];
1030        assert_eq!(cash_info.withdraw_cash, "199893416.74".parse().unwrap());
1031        assert_eq!(cash_info.available_cash, "199893416.74".parse().unwrap());
1032        assert_eq!(cash_info.frozen_cash, "28723.76".parse().unwrap());
1033        assert_eq!(cash_info.settling_cash, "-276806.51".parse().unwrap());
1034        assert_eq!(cash_info.currency, "USD");
1035
1036        assert_eq!(balance.frozen_transaction_fees.len(), 1);
1037
1038        let frozen_transaction_fee = &balance.frozen_transaction_fees[0];
1039        assert_eq!(frozen_transaction_fee.currency, "HKD");
1040        assert_eq!(
1041            frozen_transaction_fee.frozen_transaction_fee,
1042            "123".parse().unwrap()
1043        );
1044    }
1045
1046    #[test]
1047    fn history_orders() {
1048        let data = r#"
1049        {
1050            "orders": [
1051              {
1052                "currency": "HKD",
1053                "executed_price": "0.000",
1054                "executed_quantity": "0",
1055                "expire_date": "",
1056                "last_done": "",
1057                "limit_offset": "",
1058                "msg": "",
1059                "order_id": "706388312699592704",
1060                "order_type": "ELO",
1061                "outside_rth": "UnknownOutsideRth",
1062                "price": "11.900",
1063                "quantity": "200",
1064                "side": "Buy",
1065                "status": "RejectedStatus",
1066                "stock_name": "Bank of East Asia Ltd/The",
1067                "submitted_at": "1651644897",
1068                "symbol": "23.HK",
1069                "tag": "Normal",
1070                "time_in_force": "Day",
1071                "trailing_amount": "",
1072                "trailing_percent": "",
1073                "trigger_at": "0",
1074                "trigger_price": "",
1075                "trigger_status": "NOT_USED",
1076                "updated_at": "1651644898",
1077                "remark": "abc"
1078              }
1079            ]
1080          }
1081        "#;
1082
1083        #[derive(Deserialize)]
1084        struct Response {
1085            orders: Vec<Order>,
1086        }
1087
1088        let resp: Response = serde_json::from_str(data).unwrap();
1089        assert_eq!(resp.orders.len(), 1);
1090
1091        let order = &resp.orders[0];
1092        assert_eq!(order.currency, "HKD");
1093        assert!(order.executed_price.is_none());
1094        assert_eq!(order.executed_quantity, decimal!(0));
1095        assert!(order.expire_date.is_none());
1096        assert!(order.last_done.is_none());
1097        assert!(order.limit_offset.is_none());
1098        assert_eq!(order.msg, "");
1099        assert_eq!(order.order_id, "706388312699592704");
1100        assert_eq!(order.order_type, OrderType::ELO);
1101        assert!(order.outside_rth.is_none());
1102        assert_eq!(order.price, Some("11.900".parse().unwrap()));
1103        assert_eq!(order.quantity, decimal!(200));
1104        assert_eq!(order.side, OrderSide::Buy);
1105        assert_eq!(order.status, OrderStatus::Rejected);
1106        assert_eq!(order.stock_name, "Bank of East Asia Ltd/The");
1107        assert_eq!(order.submitted_at, datetime!(2022-05-04 14:14:57 +8));
1108        assert_eq!(order.symbol, "23.HK");
1109        assert_eq!(order.tag, OrderTag::Normal);
1110        assert_eq!(order.time_in_force, TimeInForceType::Day);
1111        assert!(order.trailing_amount.is_none());
1112        assert!(order.trailing_percent.is_none());
1113        assert!(order.trigger_at.is_none());
1114        assert!(order.trigger_price.is_none());
1115        assert!(order.trigger_status.is_none());
1116        assert_eq!(order.updated_at, Some(datetime!(2022-05-04 14:14:58 +8)));
1117        assert_eq!(order.remark, "abc");
1118    }
1119
1120    #[test]
1121    fn today_orders() {
1122        let data = r#"
1123        {
1124            "orders": [
1125              {
1126                "currency": "HKD",
1127                "executed_price": "0.000",
1128                "executed_quantity": "0",
1129                "expire_date": "",
1130                "last_done": "",
1131                "limit_offset": "",
1132                "msg": "",
1133                "order_id": "706388312699592704",
1134                "order_type": "ELO",
1135                "outside_rth": "UnknownOutsideRth",
1136                "price": "11.900",
1137                "quantity": "200",
1138                "side": "Buy",
1139                "status": "RejectedStatus",
1140                "stock_name": "Bank of East Asia Ltd/The",
1141                "submitted_at": "1651644897",
1142                "symbol": "23.HK",
1143                "tag": "Normal",
1144                "time_in_force": "Day",
1145                "trailing_amount": "",
1146                "trailing_percent": "",
1147                "trigger_at": "0",
1148                "trigger_price": "",
1149                "trigger_status": "NOT_USED",
1150                "updated_at": "1651644898",
1151                "remark": "abc"
1152              }
1153            ]
1154          }
1155        "#;
1156
1157        #[derive(Deserialize)]
1158        struct Response {
1159            orders: Vec<Order>,
1160        }
1161
1162        let resp: Response = serde_json::from_str(data).unwrap();
1163        assert_eq!(resp.orders.len(), 1);
1164
1165        let order = &resp.orders[0];
1166        assert_eq!(order.currency, "HKD");
1167        assert!(order.executed_price.is_none());
1168        assert_eq!(order.executed_quantity, decimal!(0));
1169        assert!(order.expire_date.is_none());
1170        assert!(order.last_done.is_none());
1171        assert!(order.limit_offset.is_none());
1172        assert_eq!(order.msg, "");
1173        assert_eq!(order.order_id, "706388312699592704");
1174        assert_eq!(order.order_type, OrderType::ELO);
1175        assert!(order.outside_rth.is_none());
1176        assert_eq!(order.price, Some("11.900".parse().unwrap()));
1177        assert_eq!(order.quantity, decimal!(200));
1178        assert_eq!(order.side, OrderSide::Buy);
1179        assert_eq!(order.status, OrderStatus::Rejected);
1180        assert_eq!(order.stock_name, "Bank of East Asia Ltd/The");
1181        assert_eq!(order.submitted_at, datetime!(2022-05-04 14:14:57 +8));
1182        assert_eq!(order.symbol, "23.HK");
1183        assert_eq!(order.tag, OrderTag::Normal);
1184        assert_eq!(order.time_in_force, TimeInForceType::Day);
1185        assert!(order.trailing_amount.is_none());
1186        assert!(order.trailing_percent.is_none());
1187        assert!(order.trigger_at.is_none());
1188        assert!(order.trigger_price.is_none());
1189        assert!(order.trigger_status.is_none());
1190        assert_eq!(order.updated_at, Some(datetime!(2022-05-04 14:14:58 +8)));
1191        assert_eq!(order.remark, "abc");
1192    }
1193
1194    #[test]
1195    fn history_executions() {
1196        let data = r#"
1197        {
1198            "has_more": false,
1199            "trades": [
1200              {
1201                "order_id": "693664675163312128",
1202                "price": "388",
1203                "quantity": "100",
1204                "symbol": "700.HK",
1205                "trade_done_at": "1648611351",
1206                "trade_id": "693664675163312128-1648611351433741210"
1207              }
1208            ]
1209          }
1210        "#;
1211
1212        #[derive(Deserialize)]
1213        struct Response {
1214            trades: Vec<Execution>,
1215        }
1216
1217        let resp: Response = serde_json::from_str(data).unwrap();
1218        assert_eq!(resp.trades.len(), 1);
1219
1220        let execution = &resp.trades[0];
1221        assert_eq!(execution.order_id, "693664675163312128");
1222        assert_eq!(execution.price, "388".parse().unwrap());
1223        assert_eq!(execution.quantity, decimal!(100));
1224        assert_eq!(execution.symbol, "700.HK");
1225        assert_eq!(execution.trade_done_at, datetime!(2022-03-30 11:35:51 +8));
1226        assert_eq!(execution.trade_id, "693664675163312128-1648611351433741210");
1227    }
1228
1229    #[test]
1230    fn order_detail() {
1231        let data = r#"
1232        {
1233            "order_id": "828940451093708800",
1234            "status": "FilledStatus",
1235            "stock_name": "Apple",
1236            "quantity": "10",
1237            "executed_quantity": "10",
1238            "price": "200.000",
1239            "executed_price": "164.660",
1240            "submitted_at": "1680863604",
1241            "side": "Buy",
1242            "symbol": "AAPL.US",
1243            "order_type": "LO",
1244            "last_done": "164.660",
1245            "trigger_price": "0.0000",
1246            "msg": "",
1247            "tag": "Normal",
1248            "time_in_force": "Day",
1249            "expire_date": "2023-04-10",
1250            "updated_at": "1681113000",
1251            "trigger_at": "0",
1252            "trailing_amount": "",
1253            "trailing_percent": "",
1254            "limit_offset": "",
1255            "trigger_status": "NOT_USED",
1256            "outside_rth": "ANY_TIME",
1257            "currency": "USD",
1258            "remark": "1680863603.927165",
1259            "free_status": "None",
1260            "free_amount": "",
1261            "free_currency": "",
1262            "deductions_status": "NONE",
1263            "deductions_amount": "",
1264            "deductions_currency": "",
1265            "platform_deducted_status": "NONE",
1266            "platform_deducted_amount": "",
1267            "platform_deducted_currency": "",
1268            "history": [{
1269                "price": "164.6600",
1270                "quantity": "10",
1271                "status": "FilledStatus",
1272                "msg": "Execution of 10",
1273                "time": "1681113000"
1274            }, {
1275                "price": "200.0000",
1276                "quantity": "10",
1277                "status": "NewStatus",
1278                "msg": "",
1279                "time": "1681113000"
1280            }],
1281            "charge_detail": {
1282                "items": [{
1283                    "code": "BROKER_FEES",
1284                    "name": "Broker Fees",
1285                    "fees": []
1286                }, {
1287                    "code": "THIRD_FEES",
1288                    "name": "Third-party Fees",
1289                    "fees": []
1290                }],
1291                "total_amount": "0",
1292                "currency": "USD"
1293            }
1294        }
1295        "#;
1296
1297        _ = serde_json::from_str::<OrderDetail>(data).unwrap();
1298    }
1299}