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