Skip to main content

futucli/cmd/trade_ext/
parsers.rs

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