futu_mcp/tools/
performance.rs1use 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;