futucli/cmd/trade_ext/
parsers.rs1use anyhow::{Context, Result, bail};
2use futu_trd::types::{ModifyOrderOp, OrderType, TrdSide};
3
4pub(crate) fn parse_trd_side(s: &str) -> Result<TrdSide> {
5 Ok(match s.trim().to_ascii_uppercase().as_str() {
6 "BUY" => TrdSide::Buy,
7 "SELL" => TrdSide::Sell,
8 "SELL_SHORT" => TrdSide::SellShort,
9 "BUY_BACK" => TrdSide::BuyBack,
10 other => bail!("unknown trd side {other:?} (BUY|SELL|SELL_SHORT|BUY_BACK)"),
11 })
12}
13
14pub(crate) fn parse_order_type(s: &str) -> Result<OrderType> {
15 Ok(match s.trim().to_ascii_uppercase().as_str() {
16 "NORMAL" | "LIMIT" => OrderType::Normal,
17 "MARKET" => OrderType::Market,
18 "ABSOLUTE_LIMIT" => OrderType::AbsoluteLimit,
19 "AUCTION" => OrderType::Auction,
20 "AUCTION_LIMIT" => OrderType::AuctionLimit,
21 "SPECIAL_LIMIT" => OrderType::SpecialLimit,
22 "SPECIAL_LIMIT_ALL" => OrderType::SpecialLimitAll,
23 "STOP" => OrderType::Stop,
25 "STOP_LIMIT" | "STOP-LIMIT" => OrderType::StopLimit,
26 "MIT" | "MARKET_IF_TOUCHED" => OrderType::MarketifTouched,
27 "LIT" | "LIMIT_IF_TOUCHED" => OrderType::LimitifTouched,
28 "TRAIL" | "TRAILING_STOP" | "TRAILING-STOP" => OrderType::TrailingStop,
29 "TRAIL_LIMIT" | "TRAILING_STOP_LIMIT" => OrderType::TrailingStopLimit,
30 "TWAP_MARKET" => OrderType::TwapMarket,
31 "TWAP_LIMIT" => OrderType::TwapLimit,
32 "VWAP_MARKET" => OrderType::VwapMarket,
33 "VWAP_LIMIT" => OrderType::VwapLimit,
34 other => bail!(
35 "unknown order type {other:?} (NORMAL|MARKET|ABSOLUTE_LIMIT|AUCTION|\
36 AUCTION_LIMIT|SPECIAL_LIMIT|SPECIAL_LIMIT_ALL|STOP|STOP_LIMIT|MIT|LIT|\
37 TRAILING_STOP|TRAILING_STOP_LIMIT|TWAP_MARKET|TWAP_LIMIT|VWAP_MARKET|VWAP_LIMIT)"
38 ),
39 })
40}
41
42pub(crate) fn parse_modify_op(s: &str) -> Result<ModifyOrderOp> {
43 Ok(match s.trim().to_ascii_uppercase().as_str() {
44 "NORMAL" => ModifyOrderOp::Normal,
45 "CANCEL" => ModifyOrderOp::Cancel,
46 "DISABLE" => ModifyOrderOp::Disable,
47 "ENABLE" => ModifyOrderOp::Enable,
48 "DELETE" => ModifyOrderOp::Delete,
49 other => bail!("unknown modify op {other:?} (NORMAL|CANCEL|DISABLE|ENABLE|DELETE)"),
50 })
51}
52
53#[derive(Debug, Clone, PartialEq, Eq)]
54pub(crate) struct ResolvedOrderIdArg {
55 pub(crate) order_id: u64,
56 pub(crate) order_id_ex: Option<String>,
57 pub(crate) idempotency_component: String,
58}
59
60pub(crate) fn resolve_order_id_arg(raw: &str) -> Result<ResolvedOrderIdArg> {
61 let trimmed = raw.trim();
62 if trimmed.is_empty() {
63 bail!("--order-id must not be empty");
64 }
65
66 if trimmed.bytes().all(|b| b.is_ascii_digit()) {
70 let order_id = trimmed
71 .parse::<u64>()
72 .with_context(|| format!("invalid numeric --order-id {trimmed:?}"))?;
73 return Ok(ResolvedOrderIdArg {
74 order_id,
75 order_id_ex: None,
76 idempotency_component: trimmed.to_string(),
77 });
78 }
79
80 Ok(ResolvedOrderIdArg {
81 order_id: 0,
82 order_id_ex: Some(trimmed.to_string()),
83 idempotency_component: trimmed.to_string(),
84 })
85}
86
87pub(crate) fn parse_numeric_order_id_arg(raw: &str, field: &str) -> Result<u64> {
88 let trimmed = raw.trim();
89 if trimmed.is_empty() {
90 bail!("{field} must not be empty");
91 }
92 if !trimmed.bytes().all(|b| b.is_ascii_digit()) {
93 bail!(
94 "{field} for reconfirm-order must be numeric FTAPI order_id; \
95 orderIDEx is not supported by Trd_ReconfirmOrder"
96 );
97 }
98 trimmed
99 .parse::<u64>()
100 .with_context(|| format!("invalid {field} {trimmed:?}"))
101}