Skip to main content

futu_mcp/tools/
performance.rs

1//! MCP proto-JSON adapters for v1.8 performance timelines.
2
3use std::sync::Arc;
4
5use futu_net::client::FutuClient;
6use rmcp::{RoleServer, handler::server::wrapper::Parameters, service::RequestContext};
7
8use crate::handlers;
9use crate::tool_args::ProtoJsonReq;
10
11use super::FutuServer;
12
13impl FutuServer {
14    async fn futu_get_asset_trend_impl(
15        &self,
16        Parameters(req): Parameters<ProtoJsonReq>,
17        req_ctx: RequestContext<RoleServer>,
18    ) -> std::result::Result<String, String> {
19        let c2s = handlers::proto_json::parse_c2s_json::<futu_proto::trd_get_asset_trend::C2s>(
20            "get-asset-trend",
21            &req.c2s_json,
22        )
23        .map_err(|error| error.to_string())?;
24        require_price_context("get-asset-trend", c2s.range.price_context.as_ref())?;
25        let client = self
26            .read_client_or_err(
27                "futu_get_asset_trend",
28                &req_ctx,
29                req.api_key.as_deref(),
30                Some(c2s.header.acc_id),
31            )
32            .await?;
33        FutuServer::wrap_result(asset_trend(client, c2s).await)
34    }
35
36    async fn futu_get_yield_trend_impl(
37        &self,
38        Parameters(req): Parameters<ProtoJsonReq>,
39        req_ctx: RequestContext<RoleServer>,
40    ) -> std::result::Result<String, String> {
41        let c2s = handlers::proto_json::parse_c2s_json::<futu_proto::trd_get_yield_trend::C2s>(
42            "get-yield-trend",
43            &req.c2s_json,
44        )
45        .map_err(|error| error.to_string())?;
46        require_price_context("get-yield-trend", c2s.range.price_context.as_ref())?;
47        let client = self
48            .read_client_or_err(
49                "futu_get_yield_trend",
50                &req_ctx,
51                req.api_key.as_deref(),
52                Some(c2s.header.acc_id),
53            )
54            .await?;
55        FutuServer::wrap_result(yield_trend(client, c2s).await)
56    }
57
58    async fn futu_get_return_calendar_impl(
59        &self,
60        Parameters(req): Parameters<ProtoJsonReq>,
61        req_ctx: RequestContext<RoleServer>,
62    ) -> std::result::Result<String, String> {
63        let c2s = handlers::proto_json::parse_c2s_json::<futu_proto::trd_get_return_calendar::C2s>(
64            "get-return-calendar",
65            &req.c2s_json,
66        )
67        .map_err(|error| error.to_string())?;
68        require_price_context("get-return-calendar", c2s.price_context.as_ref())?;
69        let client = self
70            .read_client_or_err(
71                "futu_get_return_calendar",
72                &req_ctx,
73                req.api_key.as_deref(),
74                Some(c2s.header.acc_id),
75            )
76            .await?;
77        FutuServer::wrap_result(return_calendar(client, c2s).await)
78    }
79}
80
81fn require_price_context(
82    label: &str,
83    context: Option<&futu_proto::trd_performance_common::PerformancePriceContext>,
84) -> Result<(), String> {
85    let context = context.ok_or_else(|| format!("{label}: price_context is required"))?;
86    for (field, value) in [
87        ("use_pre_market", context.use_pre_market),
88        ("use_after_hours", context.use_after_hours),
89        ("use_overnight", context.use_overnight),
90    ] {
91        if value.is_none() {
92            return Err(format!("{label}: price_context.{field} is required"));
93        }
94    }
95    Ok(())
96}
97
98async fn asset_trend(
99    client: Arc<FutuClient>,
100    c2s: futu_proto::trd_get_asset_trend::C2s,
101) -> anyhow::Result<String> {
102    handlers::proto_json::asset_trend(&client, c2s).await
103}
104
105async fn yield_trend(
106    client: Arc<FutuClient>,
107    c2s: futu_proto::trd_get_yield_trend::C2s,
108) -> anyhow::Result<String> {
109    handlers::proto_json::yield_trend(&client, c2s).await
110}
111
112async fn return_calendar(
113    client: Arc<FutuClient>,
114    c2s: futu_proto::trd_get_return_calendar::C2s,
115) -> anyhow::Result<String> {
116    handlers::proto_json::return_calendar(&client, c2s).await
117}
118
119include!(concat!(
120    env!("OUT_DIR"),
121    "/generated_mcp_routes_performance.rs"
122));
123
124#[cfg(test)]
125#[path = "performance_tests.rs"]
126mod tests;