Skip to main content

futu_mcp/handlers/trade/
helpers.rs

1//! mcp/handlers/trade/helpers — parse_* + build_header_* (跨 trade sub-mod 共用)
2//! (v1.4.110 CC Batch O: 拆自 trade.rs L26-110)
3
4use anyhow::{Result, bail};
5use futu_trd::types::{TrdEnv, TrdHeader, TrdMarket};
6
7pub fn parse_trd_market(s: &str) -> Result<TrdMarket> {
8    // v1.4.93/v1.4.111: 对齐 `Trd_Common.proto::TrdMarket` 官方全集。
9    // `Trd_Common.proto::TrdMarket` + MCP schema. 也接 int 值 (per Trd_Common.proto).
10    // 5 国 (SG/AU/JP/MY/CA) + Futures=5 在 v1.4.86-90 只 4 variants 时挂.
11    //
12    // v1.4.102/v1.4.111 fund-market handoff (per pitfall #54): fund markets
13    // 是 view-only 融资融券 / 基金账户, read path 可查,active path 单独拒。
14    let trimmed = s.trim();
15    let upper = trimmed.to_ascii_uppercase();
16    let m = match upper.as_str() {
17        "HK" | "1" => TrdMarket::HK,
18        "US" | "2" => TrdMarket::US,
19        "CN" | "3" => TrdMarket::CN,
20        "HKCC" | "4" => TrdMarket::HKCC,
21        "FUTURES" | "5" => TrdMarket::Futures,
22        "SG" | "6" => TrdMarket::SG,
23        "CRYPTO" | "7" => TrdMarket::Crypto,
24        "AU" | "8" => TrdMarket::AU,
25        "FUTURES_SIMULATE_HK" | "FUTURESSIMULATEHK" | "10" => TrdMarket::FuturesSimulateHK,
26        "FUTURES_SIMULATE_US" | "FUTURESSIMULATEUS" | "11" => TrdMarket::FuturesSimulateUS,
27        "FUTURES_SIMULATE_SG" | "FUTURESSIMULATESG" | "12" => TrdMarket::FuturesSimulateSG,
28        "FUTURES_SIMULATE_JP" | "FUTURESSIMULATEJP" | "13" => TrdMarket::FuturesSimulateJP,
29        "JP" | "15" => TrdMarket::JP,
30        "MY" | "111" => TrdMarket::MY,
31        "CA" | "112" => TrdMarket::CA,
32        "HKFUND" | "HK_FUND" | "113" => TrdMarket::HKFund,
33        "USFUND" | "US_FUND" | "123" => TrdMarket::USFund,
34        "SGFUND" | "SG_FUND" | "124" => TrdMarket::SGFund,
35        "MYFUND" | "MY_FUND" | "125" => TrdMarket::MYFund,
36        "JPFUND" | "JP_FUND" | "126" => TrdMarket::JPFund,
37        other => bail!(
38            "unknown trd market {other:?} \
39             (HK|US|CN|HKCC|FUTURES|SG|CRYPTO|AU|FUTURES_SIMULATE_HK|\
40             FUTURES_SIMULATE_US|FUTURES_SIMULATE_SG|FUTURES_SIMULATE_JP|JP|MY|CA|\
41             HKFUND|USFUND|SGFUND|MYFUND|JPFUND or official TrdMarket int)"
42        ),
43    };
44    Ok(m)
45}
46
47pub fn parse_trd_env(s: &str) -> Result<TrdEnv> {
48    let e = match s.trim().to_ascii_lowercase().as_str() {
49        "simulate" | "sim" => TrdEnv::Simulate,
50        "real" => TrdEnv::Real,
51        other => bail!("unknown trd env {other:?} (real|simulate)"),
52    };
53    Ok(e)
54}
55
56/// v1.4.102 codex 38 F4 / 41 F2 / 41 F3 / 42 F3 (P2): 严格 env → i32
57/// 共享 helper, 替换 cash_flow / margin_info / account_flag / bond 4 处
58/// 旧"non-real => 0 silent"模式. typo (e.g. "reel" / "prod") 现在 reject
59/// at MCP boundary (BUG-005 反 silent wrong-env).
60pub fn parse_trd_env_int(s: &str) -> Result<i32> {
61    Ok(parse_trd_env(s)? as i32)
62}
63
64/// v1.4.102 codex 33 F5 / 34 F4 / 35 F4 (P2): 拒 fund market 在 calculation /
65/// active trade-read path. 用于 max_trd_qtys / margin_ratio / order_fee /
66/// orders / deals 等 active backend call (区别于 view-only positions/funds/
67/// cash-log/history 真机 verified).
68pub fn parse_trd_market_strict_no_fund(s: &str) -> Result<TrdMarket> {
69    let m = parse_trd_market(s)?;
70    if let Some(label) = futu_trd::market::view_only_fund_market_label(m as i32) {
71        bail!(
72            "trd market {label} 仅 view-only read endpoints \
73             (positions/funds/cash-log/history-orders/history-fills) 真机 verified; \
74             active/calculation path (orders/deals/max_trd_qtys/margin_ratio/order_fee) \
75             用主市场. v1.4.102 audit 33 F5 / 34 F4 / 35 F4 fix."
76        );
77    }
78    Ok(m)
79}
80
81/// v1.4.102 codex 35 F4: build_header for active/calculation MCP tools.
82/// view-only read tools (positions/funds/cash-log/history) 用普通 build_header
83/// 接受 fund market.
84pub fn build_header_strict_no_fund(env: &str, acc_id: u64, market: &str) -> Result<TrdHeader> {
85    build_header_strict_no_fund_with_jp_acc_type(env, acc_id, market, None)
86}
87
88pub fn build_header_strict_no_fund_with_jp_acc_type(
89    env: &str,
90    acc_id: u64,
91    market: &str,
92    jp_acc_type: Option<i32>,
93) -> Result<TrdHeader> {
94    Ok(TrdHeader {
95        trd_env: parse_trd_env(env)?,
96        acc_id,
97        trd_market: parse_trd_market_strict_no_fund(market)?,
98        jp_acc_type,
99    })
100}
101
102pub fn build_header(env: &str, acc_id: u64, market: &str) -> Result<TrdHeader> {
103    Ok(TrdHeader {
104        trd_env: parse_trd_env(env)?,
105        acc_id,
106        trd_market: parse_trd_market(market)?,
107        jp_acc_type: None,
108    })
109}