Skip to main content

futu_mcp/handlers/core/
ping_quote.rs

1//! mcp/handlers/core/ping_quote — PingOut + ping + QuoteOut + get_quote
2//! (v1.4.110 CC Batch N: 拆自 core.rs L13-96)
3
4use std::sync::Arc;
5use std::time::Duration;
6
7use anyhow::Result;
8use futu_net::client::FutuClient;
9use futu_qot::types::SubType;
10use serde::Serialize;
11
12use crate::state::{format_symbol, parse_symbol};
13
14#[derive(Serialize)]
15pub struct PingOut {
16    pub gateway: String,
17    pub ok: bool,
18    pub rtt_ms: f64,
19    pub message: String,
20}
21
22pub async fn ping(client: &Arc<FutuClient>, gateway: &str) -> PingOut {
23    let t0 = std::time::Instant::now();
24    match futu_qot::market_misc::get_sub_info(client, false).await {
25        Ok(_) => PingOut {
26            gateway: gateway.to_string(),
27            ok: true,
28            rtt_ms: t0.elapsed().as_secs_f64() * 1000.0,
29            message: "pong".into(),
30        },
31        Err(e) => PingOut {
32            gateway: gateway.to_string(),
33            ok: false,
34            rtt_ms: 0.0,
35            message: format!("request failed: {e}"),
36        },
37    }
38}
39
40#[derive(Serialize)]
41struct QuoteOut {
42    symbol: String,
43    update_time: String,
44    cur_price: f64,
45    last_close: f64,
46    open: f64,
47    high: f64,
48    low: f64,
49    volume: i64,
50    turnover: f64,
51    turnover_rate: f64,
52    amplitude: f64,
53    change_rate: f64,
54}
55
56pub async fn get_quote(client: &Arc<FutuClient>, symbol: &str) -> Result<String> {
57    let sec = parse_symbol(symbol)?;
58
59    futu_qot::sub::subscribe(
60        client,
61        std::slice::from_ref(&sec),
62        &[SubType::Basic],
63        true,
64        true,
65    )
66    .await?;
67    tokio::time::sleep(Duration::from_millis(300)).await;
68
69    let qots = futu_qot::basic_qot::get_basic_qot(client, std::slice::from_ref(&sec)).await?;
70    let q = qots
71        .first()
72        .ok_or_else(|| anyhow::anyhow!("empty quote result"))?;
73
74    let change_rate = if q.last_close_price.abs() > f64::EPSILON {
75        (q.cur_price - q.last_close_price) / q.last_close_price * 100.0
76    } else {
77        0.0
78    };
79
80    let out = QuoteOut {
81        symbol: format_symbol(&q.security),
82        update_time: q.update_time.clone(),
83        cur_price: q.cur_price,
84        last_close: q.last_close_price,
85        open: q.open_price,
86        high: q.high_price,
87        low: q.low_price,
88        volume: q.volume,
89        turnover: q.turnover,
90        turnover_rate: q.turnover_rate,
91        amplitude: q.amplitude,
92        change_rate,
93    };
94    Ok(serde_json::to_string_pretty(&out)?)
95}