Skip to main content

futu_mcp/handlers/reference/
price_reminder.rs

1//! mcp/handlers/reference/price_reminder — set/get_price_reminder + get_option_expiration_date
2//! (v1.4.110 CC Batch L: 拆自 reference.rs L858-1055)
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// set_price_reminder / Qot_SetPriceReminder (CMD 3220)
15// ============================================================
16
17/// 到价提醒 set 操作。`op`:1=ADD / 2=DEL / 3=ENABLE / 4=DISABLE / 5=MODIFY / 6=DEL_ALL。
18/// 新增 / 修改时需要 `type_`(见 Qot_Common.PriceReminderType)+ `value`。
19///
20/// v1.4.106 codex 1116 F4 [P2]: 加 `reminder_session_list` 参数. 美股 + 空 list →
21/// daemon 默认补 [Open, USPre, USAfter] (对齐 C++ APIServer_Qot_PriceReminder.cpp:925-942).
22pub struct SetPriceReminderInput<'a> {
23    pub symbol: &'a str,
24    pub op: i32,
25    pub key: Option<i64>,
26    pub reminder_type: Option<i32>,
27    pub freq: Option<i32>,
28    pub value: Option<f64>,
29    pub note: Option<&'a str>,
30    pub reminder_session_list: &'a [i32],
31}
32
33pub async fn set_price_reminder(
34    client: &Arc<FutuClient>,
35    input: SetPriceReminderInput<'_>,
36) -> Result<String> {
37    let s = parse_symbol(input.symbol)?;
38    let req = futu_proto::qot_set_price_reminder::Request {
39        c2s: futu_proto::qot_set_price_reminder::C2s {
40            security: futu_proto::qot_common::Security {
41                market: s.market as i32,
42                code: s.code,
43            },
44            op: input.op,
45            key: input.key,
46            r#type: input.reminder_type,
47            freq: input.freq,
48            value: input.value,
49            note: input.note.map(String::from),
50            reminder_session_list: input.reminder_session_list.to_vec(),
51            header: None, // v1.4.110 codex Slice 1 schema 占位
52        },
53    };
54    let body = req.encode_to_vec();
55    let frame = client
56        .request(futu_core::proto_id::QOT_SET_PRICE_REMINDER, body)
57        .await?;
58    let resp = futu_proto::qot_set_price_reminder::Response::decode(frame.body.as_ref())
59        .map_err(|e| anyhow!("decode set_price_reminder: {e}"))?;
60    if resp.ret_type != 0 {
61        bail!(
62            "set_price_reminder ret_type={} msg={:?}",
63            resp.ret_type,
64            resp.ret_msg
65        );
66    }
67    let key_out = resp.s2c.map(|s| s.key);
68    Ok(serde_json::to_string_pretty(&serde_json::json!({
69        "ok": true,
70        "op": input.op,
71        "key": key_out,
72    }))?)
73}
74
75// ============================================================
76// get_price_reminder / Qot_GetPriceReminder (CMD 3221)
77// ============================================================
78
79#[derive(Serialize)]
80struct PriceReminderItemOut {
81    key: i64,
82    reminder_type: i32,
83    value: f64,
84    note: String,
85    freq: i32,
86}
87
88#[derive(Serialize)]
89struct PriceReminderOut {
90    code: String,
91    name: String,
92    reminder_list: Vec<PriceReminderItemOut>,
93}
94
95/// 获取到价提醒。`symbol` 和 `market` 二选一:传 `symbol` 查该股票所有
96/// 提醒;否则 `market` 查该市场全部提醒。
97pub async fn get_price_reminder(
98    client: &Arc<FutuClient>,
99    symbol: Option<&str>,
100    market: Option<i32>,
101) -> Result<String> {
102    let (security, market_code) = match symbol {
103        Some(s) => {
104            let sec = parse_symbol(s)?;
105            (
106                Some(futu_proto::qot_common::Security {
107                    market: sec.market as i32,
108                    code: sec.code,
109                }),
110                None,
111            )
112        }
113        None => (None, market),
114    };
115    if security.is_none() && market_code.is_none() {
116        bail!("price_reminder: either symbol or market required");
117    }
118    let req = futu_proto::qot_get_price_reminder::Request {
119        c2s: futu_proto::qot_get_price_reminder::C2s {
120            security,
121            market: market_code,
122            header: None,
123        },
124    };
125    let body = req.encode_to_vec();
126    let frame = client
127        .request(futu_core::proto_id::QOT_GET_PRICE_REMINDER, body)
128        .await?;
129    let resp = futu_proto::qot_get_price_reminder::Response::decode(frame.body.as_ref())
130        .map_err(|e| anyhow!("decode price_reminder: {e}"))?;
131    if resp.ret_type != 0 {
132        bail!(
133            "price_reminder ret_type={} msg={:?}",
134            resp.ret_type,
135            resp.ret_msg
136        );
137    }
138    let s2c = resp.s2c.ok_or_else(|| anyhow!("missing s2c"))?;
139    let out: Vec<PriceReminderOut> = s2c
140        .price_reminder_list
141        .iter()
142        .map(|p| PriceReminderOut {
143            code: p.security.code.clone(),
144            name: p.name.clone().unwrap_or_default(),
145            reminder_list: p
146                .item_list
147                .iter()
148                .map(|r| PriceReminderItemOut {
149                    key: r.key,
150                    reminder_type: r.r#type,
151                    value: r.value,
152                    note: r.note.clone(),
153                    freq: r.freq,
154                })
155                .collect(),
156        })
157        .collect();
158    Ok(serde_json::to_string_pretty(&out)?)
159}
160
161// ============================================================
162// get_option_expiration_date / Qot_GetOptionExpirationDate (CMD 3224)
163// ============================================================
164
165#[derive(Serialize)]
166struct OptionExpirationOut {
167    strike_time: Option<String>,
168    distance_days: i32,
169    cycle: Option<i32>,
170}
171
172/// 查期权链的到期日列表(不返具体合约,只返到期日 + 距今天数)。
173pub async fn get_option_expiration_date(
174    client: &Arc<FutuClient>,
175    owner_symbol: &str,
176    index_option_type: Option<i32>,
177) -> Result<String> {
178    let s = parse_symbol(owner_symbol)?;
179    let req = futu_proto::qot_get_option_expiration_date::Request {
180        c2s: futu_proto::qot_get_option_expiration_date::C2s {
181            owner: futu_proto::qot_common::Security {
182                market: s.market as i32,
183                code: s.code,
184            },
185            index_option_type,
186            header: None, // v1.4.110 codex Slice 1 schema 占位
187        },
188    };
189    let body = req.encode_to_vec();
190    let frame = client
191        .request(futu_core::proto_id::QOT_GET_OPTION_EXPIRATION_DATE, body)
192        .await?;
193    let resp = futu_proto::qot_get_option_expiration_date::Response::decode(frame.body.as_ref())
194        .map_err(|e| anyhow!("decode option_expiration_date: {e}"))?;
195    if resp.ret_type != 0 {
196        bail!(
197            "option_expiration_date ret_type={} msg={:?}",
198            resp.ret_type,
199            resp.ret_msg
200        );
201    }
202    let s2c = resp.s2c.ok_or_else(|| anyhow!("missing s2c"))?;
203    let out: Vec<OptionExpirationOut> = s2c
204        .date_list
205        .iter()
206        .map(|d| OptionExpirationOut {
207            strike_time: d.strike_time.clone(),
208            distance_days: d.option_expiry_date_distance,
209            cycle: d.cycle,
210        })
211        .collect();
212    Ok(serde_json::to_string_pretty(&out)?)
213}