1use std::sync::Arc;
4use std::time::Duration;
5
6use anyhow::Result;
7use futu_net::client::FutuClient;
8use futu_qot::types::SubType;
9use serde::Serialize;
10
11use crate::state::{format_symbol, parse_symbol};
12
13#[derive(Serialize)]
14pub struct PingOut {
15 pub gateway: String,
16 pub ok: bool,
17 pub rtt_ms: f64,
18 pub message: String,
19}
20
21pub async fn ping(client: &Arc<FutuClient>, gateway: &str) -> PingOut {
22 let t0 = std::time::Instant::now();
23 match futu_qot::market_misc::get_sub_info(client, false).await {
24 Ok(_) => PingOut {
25 gateway: gateway.to_string(),
26 ok: true,
27 rtt_ms: t0.elapsed().as_secs_f64() * 1000.0,
28 message: "pong".into(),
29 },
30 Err(e) => PingOut {
31 gateway: gateway.to_string(),
32 ok: false,
33 rtt_ms: 0.0,
34 message: format!("request failed: {e}"),
35 },
36 }
37}
38
39#[derive(Serialize)]
40struct QuoteOut {
41 symbol: String,
42 update_time: String,
43 cur_price: f64,
44 last_close: f64,
45 open: f64,
46 high: f64,
47 low: f64,
48 volume: i64,
49 turnover: f64,
50 turnover_rate: f64,
51 amplitude: f64,
52 change_rate: f64,
53}
54
55pub async fn get_quote(client: &Arc<FutuClient>, symbol: &str) -> Result<String> {
56 let sec = parse_symbol(symbol)?;
57
58 futu_qot::sub::subscribe(
59 client,
60 std::slice::from_ref(&sec),
61 &[SubType::Basic],
62 true,
63 true,
64 )
65 .await?;
66 tokio::time::sleep(Duration::from_millis(300)).await;
67
68 let qots = futu_qot::basic_qot::get_basic_qot(client, std::slice::from_ref(&sec)).await?;
69 let q = qots
70 .first()
71 .ok_or_else(|| anyhow::anyhow!("empty quote result"))?;
72
73 let change_rate = if q.last_close_price.abs() > f64::EPSILON {
74 (q.cur_price - q.last_close_price) / q.last_close_price * 100.0
75 } else {
76 0.0
77 };
78
79 let out = QuoteOut {
80 symbol: format_symbol(&q.security),
81 update_time: q.update_time.clone(),
82 cur_price: q.cur_price,
83 last_close: q.last_close_price,
84 open: q.open_price,
85 high: q.high_price,
86 low: q.low_price,
87 volume: q.volume,
88 turnover: q.turnover,
89 turnover_rate: q.turnover_rate,
90 amplitude: q.amplitude,
91 change_rate,
92 };
93 Ok(serde_json::to_string_pretty(&out)?)
94}
95
96#[derive(Serialize)]
97struct SnapshotOut {
98 symbol: String,
99 name: Option<String>,
100 update_time: String,
101 cur_price: f64,
102 last_close: f64,
103 change_rate: f64,
104 open: f64,
105 high: f64,
106 low: f64,
107 volume: i64,
108 turnover: f64,
109 turnover_rate: f64,
110 amplitude: Option<f64>,
111 avg_price: Option<f64>,
112 volume_ratio: Option<f64>,
113 highest52: Option<f64>,
114 lowest52: Option<f64>,
115 ask_price: Option<f64>,
116 bid_price: Option<f64>,
117 is_suspend: bool,
118 lot_size: i32,
119}
120
121pub async fn get_snapshot(client: &Arc<FutuClient>, symbol: &str) -> Result<String> {
122 let sec = parse_symbol(symbol)?;
123 let s2c = futu_qot::snapshot::get_security_snapshot(client, std::slice::from_ref(&sec)).await?;
124 let snap = s2c
125 .snapshot_list
126 .first()
127 .ok_or_else(|| anyhow::anyhow!("empty snapshot result"))?;
128 let b = &snap.basic;
129
130 let change_rate = if b.last_close_price.abs() > f64::EPSILON {
131 (b.cur_price - b.last_close_price) / b.last_close_price * 100.0
132 } else {
133 0.0
134 };
135
136 let market_prefix = match b.security.market {
137 1 => "HK",
138 2 => "HK_FUTURE",
139 11 => "US",
140 21 => "SH",
141 22 => "SZ",
142 _ => "UNKNOWN",
143 };
144
145 let out = SnapshotOut {
146 symbol: format!("{market_prefix}.{}", b.security.code),
147 name: b.name.clone(),
148 update_time: b.update_time.clone(),
149 cur_price: b.cur_price,
150 last_close: b.last_close_price,
151 change_rate,
152 open: b.open_price,
153 high: b.high_price,
154 low: b.low_price,
155 volume: b.volume,
156 turnover: b.turnover,
157 turnover_rate: b.turnover_rate,
158 amplitude: b.amplitude,
159 avg_price: b.avg_price,
160 volume_ratio: b.volume_ratio,
161 highest52: b.highest52_weeks_price,
162 lowest52: b.lowest52_weeks_price,
163 ask_price: b.ask_price,
164 bid_price: b.bid_price,
165 is_suspend: b.is_suspend,
166 lot_size: b.lot_size,
167 };
168 Ok(serde_json::to_string_pretty(&out)?)
169}