Skip to main content

futucli/cmd/trade_ext/
parsers.rs

1use anyhow::{Context, Result, bail};
2use futu_core::trade_parsing;
3use futu_trd::types::{ModifyOrderOp, OrderType, TrdSide};
4
5use crate::trd_sdk_adapter;
6
7pub(crate) fn parse_trd_side(s: &str) -> Result<TrdSide> {
8    let side = trade_parsing::parse_trd_side_id(s).ok_or_else(|| {
9        anyhow::anyhow!(
10            "unknown trd side {:?} ({})",
11            s.trim().to_ascii_uppercase(),
12            trade_parsing::TRD_SIDE_PARSE_CHOICES
13        )
14    })?;
15    trd_sdk_adapter::trd_side_from_id(side)
16        .ok_or_else(|| anyhow::anyhow!("unknown trd side id {side} after core parser accepted it"))
17}
18
19pub(crate) fn parse_order_type(s: &str) -> Result<OrderType> {
20    let order_type = trade_parsing::parse_order_type_id(s).ok_or_else(|| {
21        anyhow::anyhow!(
22            "unknown order type {:?} ({})",
23            s.trim().to_ascii_uppercase(),
24            trade_parsing::ORDER_TYPE_PARSE_CHOICES
25        )
26    })?;
27    trd_sdk_adapter::order_type_from_id(order_type).ok_or_else(|| {
28        anyhow::anyhow!("unknown order type id {order_type} after core parser accepted it")
29    })
30}
31
32pub(crate) fn parse_modify_op(s: &str) -> Result<ModifyOrderOp> {
33    let modify_op = trade_parsing::parse_modify_op_id(s).ok_or_else(|| {
34        anyhow::anyhow!(
35            "unknown modify op {:?} ({})",
36            s.trim().to_ascii_uppercase(),
37            trade_parsing::MODIFY_OP_PARSE_CHOICES
38        )
39    })?;
40    trd_sdk_adapter::modify_order_op_from_id(modify_op).ok_or_else(|| {
41        anyhow::anyhow!("unknown modify op id {modify_op} after core parser accepted it")
42    })
43}
44
45#[derive(Debug, Clone, PartialEq, Eq)]
46pub(crate) struct ResolvedOrderIdArg {
47    pub(crate) order_id: u64,
48    pub(crate) order_id_ex: Option<String>,
49    pub(crate) idempotency_component: String,
50}
51
52pub(crate) fn resolve_order_id_arg(raw: &str) -> Result<ResolvedOrderIdArg> {
53    let trimmed = raw.trim();
54    if trimmed.is_empty() {
55        bail!("--order-id must not be empty");
56    }
57
58    // C++ APIServer accepts `orderIDEx` as an alternative to numeric `orderID`
59    // and hashes it at entry. Ref:
60    // FutuOpenD/Src/APIServer/Business/Trade/APIServer_Trd_ModifyOrder.cpp:256
61    if trimmed.bytes().all(|b| b.is_ascii_digit()) {
62        let order_id = trimmed
63            .parse::<u64>()
64            .with_context(|| format!("invalid numeric --order-id {trimmed:?}"))?;
65        return Ok(ResolvedOrderIdArg {
66            order_id,
67            order_id_ex: None,
68            idempotency_component: trimmed.to_string(),
69        });
70    }
71
72    Ok(ResolvedOrderIdArg {
73        order_id: 0,
74        order_id_ex: Some(trimmed.to_string()),
75        idempotency_component: trimmed.to_string(),
76    })
77}
78
79pub(crate) fn parse_numeric_order_id_arg(raw: &str, field: &str) -> Result<u64> {
80    let trimmed = raw.trim();
81    if trimmed.is_empty() {
82        bail!("{field} must not be empty");
83    }
84    if !trimmed.bytes().all(|b| b.is_ascii_digit()) {
85        bail!(
86            "{field} for reconfirm-order must be numeric FTAPI order_id; \
87             orderIDEx is not supported by Trd_ReconfirmOrder"
88        );
89    }
90    trimmed
91        .parse::<u64>()
92        .with_context(|| format!("invalid {field} {trimmed:?}"))
93}