1use rmcp::schemars;
4use serde::Deserialize;
5
6use crate::tool_enums;
7
8use super::*;
9
10fn sub_type_list_schema(_: &mut schemars::SchemaGenerator) -> schemars::Schema {
11 schemars::json_schema!({
12 "type": "array",
13 "items": {
14 "oneOf": [
15 { "type": "integer", "format": "int32" },
16 { "type": "string" }
17 ]
18 }
19 })
20}
21
22#[derive(Debug, Deserialize, schemars::JsonSchema)]
23#[serde(deny_unknown_fields)]
24pub struct QuerySubscriptionReq {
25 #[schemars(description = "true=query all connections; false=only this connection (default)")]
26 #[serde(default)]
27 pub is_req_all_conn: bool,
28}
29
30#[derive(Debug, Deserialize, schemars::JsonSchema)]
31#[serde(deny_unknown_fields)]
32pub struct UnsubscribeReq {
33 #[schemars(
34 description = "Security symbols to unsubscribe (ignored if unsub_all=true); alias: stocks / code_list / symbol_list / security_list"
35 )]
36 #[serde(
38 default,
39 alias = "stocks",
40 alias = "code_list",
41 alias = "symbol_list",
42 alias = "security_list"
43 )]
44 pub symbols: Vec<String>,
45 #[schemars(
46 description = "Sub-type ids to unsubscribe. Accept int (1=Basic, 2=OrderBook, 4=Ticker, \
47 5=RT, 6=KL_Day, 7=KL_5Min, 8=KL_15Min, 9=KL_30Min, 10=KL_60Min, 11=KL_1Min, \
48 12=KL_Week, 13=KL_Month, 14=Broker, 15=KL_Quarter, 16=KL_Year, 17=KL_3Min, \
49 18=KL_10Min, 19=KL_120Min, 20=KL_180Min, 21=KL_240Min, 22=OrderBook_Odd) \
50 OR string (\"Basic\" / \"OrderBook\" / \"KL_Day\" / \"day\" / ...). Alias: \
51 sub_type_list. Uses the daemon proto mapping: 3 is reserved/None, 4=Ticker, \
52 10=KL_60Min, 13=KL_Month, 18=KL_10Min."
53 )]
54 #[schemars(schema_with = "sub_type_list_schema")]
55 #[serde(
57 default,
58 alias = "sub_type_list",
59 deserialize_with = "tool_enums::deser_subtype_list_as_vec_i32"
60 )]
61 pub sub_types: Vec<i32>,
62 #[schemars(
63 description = "true=clear all subscriptions on this connection (ignores symbols/sub_types); alias: unsubscribe_all"
64 )]
65 #[serde(default, alias = "unsubscribe_all")]
66 pub unsub_all: bool,
67}
68
69#[derive(Debug, Deserialize, schemars::JsonSchema)]
72#[serde(deny_unknown_fields)]
73pub struct SubscribeReq {
74 #[schemars(
75 description = "Security symbols to subscribe, e.g. [\"HK.00700\", \"US.AAPL\"]. Alias: stocks / code_list / symbol_list / security_list"
76 )]
77 #[serde(
79 alias = "stocks",
80 alias = "code_list",
81 alias = "symbol_list",
82 alias = "security_list"
83 )]
84 pub symbols: Vec<String>,
85 #[schemars(
86 description = "Sub-type ids to subscribe. Accept int (1=Basic, 2=OrderBook, 4=Ticker, \
87 5=RT, 6=KL_Day, 7=KL_5Min, 8=KL_15Min, 9=KL_30Min, 10=KL_60Min, 11=KL_1Min, \
88 12=KL_Week, 13=KL_Month, 14=Broker, 15=KL_Quarter, 16=KL_Year, 17=KL_3Min, \
89 18=KL_10Min, 19=KL_120Min, 20=KL_180Min, 21=KL_240Min, 22=OrderBook_Odd) \
90 OR string (\"Basic\" / \"OrderBook\" / \"KL_Day\" / \"day\" / ...). Alias: \
91 sub_type_list. Uses the daemon proto mapping: 3 is reserved/None, 4=Ticker, \
92 10=KL_60Min, 13=KL_Month, 18=KL_10Min."
93 )]
94 #[schemars(schema_with = "sub_type_list_schema")]
95 #[serde(
97 alias = "sub_type_list",
98 deserialize_with = "tool_enums::deser_subtype_list_as_vec_i32"
99 )]
100 pub sub_types: Vec<i32>,
101 #[schemars(
102 description = "If true, backend pushes current snapshot immediately after subscribe (useful for agents needing warm state). Default true."
103 )]
104 #[serde(default = "default_is_first_push")]
105 pub is_first_push: bool,
106 #[schemars(
107 description = "If true, register push on this connection (agent will receive push via SSE notification in HTTP mode). Default true."
108 )]
109 #[serde(default = "default_is_reg_push")]
110 pub is_reg_push: bool,
111 #[schemars(
112 description = "Qot_Sub.extendedTime: include US pre/post-market data for supported real-time K/RT/Ticker subscriptions. Default false."
113 )]
114 #[serde(default, alias = "extendedTime")]
115 pub extended_time: Option<bool>,
116 #[schemars(
117 description = "Qot_Sub.session: 0=NONE, 1=RTH, 2=ETH, 3=ALL. 4=OVERNIGHT is rejected by C++ OpenD for QOT subscribe."
118 )]
119 #[serde(default)]
120 pub session: Option<i32>,
121 #[schemars(
122 description = "Qot_Sub.isSubOrderBookDetail: subscribe order-book detail when available. Default false."
123 )]
124 #[serde(
125 default,
126 alias = "is_sub_order_book_detail",
127 alias = "orderbook_detail"
128 )]
129 pub is_sub_order_book_detail: Option<bool>,
130}
131
132impl SubscribeReq {
133 pub fn validate(&self) -> Result<(), String> {
134 match futu_qot::subscription_plan::SubscribeOptionsPlan::from_raw(
135 self.session,
136 self.extended_time,
137 self.is_sub_order_book_detail,
138 ) {
139 Ok(_) => Ok(()),
140 Err(futu_qot::subscription_plan::SubscribePlanError::OvernightSessionUnsupported) => {
141 Err(
142 "SubscribeReq.session=4 (OVERNIGHT) is not supported by QOT subscribe; \
143 use 2=ETH or 3=ALL"
144 .to_string(),
145 )
146 }
147 }
148 }
149}
150
151#[derive(Debug, Deserialize, schemars::JsonSchema)]
152#[serde(deny_unknown_fields)]
153pub struct SubAccPushReq {
154 #[schemars(description = "Array of account IDs (u64) to receive order/deal push \
155 for. ⚠️ Call `futu_list_accounts` first to discover real \
156 `acc_id` values; do NOT hallucinate 18-digit numbers — \
157 invalid ids will silently fail to receive push. Alias: account_ids / accounts")]
158 #[serde(alias = "account_ids", alias = "accounts")]
160 pub acc_ids: Vec<u64>,
161 #[schemars(
162 description = "Optional per-call API key plaintext. Priority: tool args > HTTP Bearer > startup key. Use to scope this push subscription to a specific narrow key."
163 )]
164 #[serde(default)]
165 pub api_key: Option<String>,
166}
167
168#[derive(Debug, Deserialize, schemars::JsonSchema)]
170#[serde(deny_unknown_fields)]
171pub struct UnsubAccPushReq {
172 #[schemars(description = "Required: session_id returned by `futu_sub_acc_push` \
175 response (session_id field or unsub_hint). If omitted, \
176 handler returns an error. If session_id not found (e.g. \
177 4h auto-purged), removed_count=0 is returned.")]
178 #[serde(default)]
179 pub session_id: Option<String>,
180 #[schemars(
181 description = "Optional per-call API key plaintext. Priority: tool args > HTTP Bearer > startup key. Use the same key that created the push subscription when running in stdio mode with multiple keys."
182 )]
183 #[serde(default)]
184 pub api_key: Option<String>,
185}