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