Skip to main content

futu_mcp/handlers/reference/
futures.rs

1//! mcp/handlers/reference/futures — get_future_info
2//! (v1.4.110 CC Batch L: 拆自 reference.rs L244-323)
3
4use std::sync::Arc;
5
6use anyhow::{Result, anyhow, bail};
7use futu_net::client::FutuClient;
8use prost::Message;
9use serde::Serialize;
10
11use crate::state::parse_symbol;
12
13// ============================================================
14// get_future_info / Qot_GetFutureInfo (CMD 3218)
15// ============================================================
16
17#[derive(Serialize)]
18struct FutureInfoOut {
19    code: String,
20    name: String,
21    contract_type: String,
22    contract_size: f64,
23    last_trade_time: String,
24    /// 交易时间段(分钟为单位,begin-end 对,来自 trade_time 数组)
25    trade_time_list: Vec<String>,
26}
27
28pub async fn get_future_info(client: &Arc<FutuClient>, symbols: &[String]) -> Result<String> {
29    // v1.4.106 codex 0641 F1 (sibling): 列表型 input 契约 — 空列表 reject
30    // (与 market_state / suspend / owner_plate 一致 default ON 严格语义).
31    if symbols.is_empty() {
32        bail!("future_info: symbols empty (必须至少传入 1 个 MARKET.CODE)");
33    }
34    let sec_list: Vec<_> = symbols
35        .iter()
36        .enumerate()
37        .map(|(i, s)| {
38            parse_symbol(s).map_err(|e| {
39                anyhow!("future_info: symbols[{i}] invalid ({s:?}): {e} — 整体 reject")
40            })
41        })
42        .collect::<Result<Vec<_>>>()?;
43    let proto_secs: Vec<_> = sec_list
44        .iter()
45        .map(|s| futu_proto::qot_common::Security {
46            market: s.market as i32,
47            code: s.code.clone(),
48        })
49        .collect();
50    // 经 helper 二次 invariant assert (market != 0, code != ""):
51    let _parsed = futu_qot::symbol_list::parse_required_symbol_list(&proto_secs)
52        .map_err(|e| anyhow!("future_info: {e}"))?;
53    let req = futu_proto::qot_get_future_info::Request {
54        c2s: futu_proto::qot_get_future_info::C2s {
55            security_list: proto_secs,
56            header: None,
57        },
58    };
59    let body = req.encode_to_vec();
60    let frame = client
61        .request(futu_core::proto_id::QOT_GET_FUTURE_INFO, body)
62        .await?;
63    let resp = futu_proto::qot_get_future_info::Response::decode(frame.body.as_ref())
64        .map_err(|e| anyhow!("decode future_info: {e}"))?;
65    if resp.ret_type != 0 {
66        bail!(
67            "future_info ret_type={} msg={:?}",
68            resp.ret_type,
69            resp.ret_msg
70        );
71    }
72    let s2c = resp.s2c.ok_or_else(|| anyhow!("missing s2c"))?;
73    let out: Vec<FutureInfoOut> = s2c
74        .future_info_list
75        .iter()
76        .map(|f| FutureInfoOut {
77            code: f.security.code.clone(),
78            name: f.name.clone(),
79            contract_type: f.contract_type.clone(),
80            contract_size: f.contract_size,
81            last_trade_time: f.last_trade_time.clone(),
82            trade_time_list: f
83                .trade_time
84                .iter()
85                .map(|t| {
86                    format!(
87                        "{}-{}",
88                        t.begin.unwrap_or_default(),
89                        t.end.unwrap_or_default()
90                    )
91                })
92                .collect(),
93        })
94        .collect();
95    Ok(serde_json::to_string_pretty(&out)?)
96}