1use std::sync::Arc;
4
5use axum::extract::{Extension, Json, State};
6use serde_json::Value;
7
8use futu_auth::KeyRecord;
9use futu_core::proto_id;
10use futu_proto::trd_get_funds;
11use futu_proto::trd_get_history_order_fill_list;
12use futu_proto::trd_get_history_order_list;
13use futu_proto::trd_get_margin_ratio;
14use futu_proto::trd_get_max_trd_qtys;
15use futu_proto::trd_get_order_fee;
16use futu_proto::trd_get_order_fill_list;
17use futu_proto::trd_get_order_list;
18use futu_proto::trd_get_position_list;
19use futu_trd::read_plan;
20
21use super::ApiResult;
22use super::card_num::normalize_and_resolve_card_num_for_route;
23use super::validation::{
24 read_handler_acc_id_check, validate_header_trd_env_present, validate_header_trd_market,
25 validate_header_trd_market_write,
26};
27use crate::adapter::{self, RestState};
28
29pub async fn get_funds(
31 State(state): State<RestState>,
32 rec: Option<Extension<Arc<KeyRecord>>>,
33 Json(mut body): Json<Value>,
34) -> ApiResult {
35 normalize_and_resolve_card_num_for_route(&state, &rec, &mut body, "/api/funds")?;
37 validate_header_trd_market(&body, "/api/funds")?;
39 validate_header_trd_env_present(&body, "/api/funds")?;
41 read_handler_acc_id_check(
42 &state,
43 rec.as_deref().map(|r| r.as_ref()),
44 &body,
45 "/api/funds",
46 )?;
47
48 let requested_currency: Option<i32> = body
51 .pointer("/c2s/currency")
52 .or_else(|| body.pointer("/currency"))
53 .and_then(|v| v.as_i64())
54 .map(|v| v as i32);
55
56 let mut resp = adapter::proto_request::<trd_get_funds::Request, trd_get_funds::Response>(
57 &state,
58 proto_id::TRD_GET_FUNDS,
59 Some(body),
60 )
61 .await?;
62
63 let response_currency = resp
64 .0
65 .pointer("/s2c/funds/currency")
66 .and_then(|v| v.as_i64())
67 .map(|v| v as i32);
68 if let Some(warn_msg) =
69 read_plan::funds_currency_mismatch_warning(requested_currency, response_currency)
70 && let Some(obj) = resp.0.as_object_mut()
71 {
72 obj.insert(
73 "currency_warning".to_string(),
74 serde_json::Value::String(warn_msg.clone()),
75 );
76 let existing_msg = obj
78 .get("ret_msg")
79 .and_then(|v| v.as_str())
80 .unwrap_or("")
81 .to_string();
82 let new_msg = if existing_msg.is_empty() {
83 format!("⚠️ {warn_msg}")
84 } else {
85 format!("⚠️ {warn_msg}\n[既有] {existing_msg}")
86 };
87 obj.insert("ret_msg".to_string(), serde_json::Value::String(new_msg));
88 }
89
90 Ok(resp)
91}
92
93pub async fn get_positions(
95 State(state): State<RestState>,
96 rec: Option<Extension<Arc<KeyRecord>>>,
97 Json(mut body): Json<Value>,
98) -> ApiResult {
99 normalize_and_resolve_card_num_for_route(&state, &rec, &mut body, "/api/positions")?;
100 validate_header_trd_market(&body, "/api/positions")?;
102 validate_header_trd_env_present(&body, "/api/positions")?;
104 read_handler_acc_id_check(
105 &state,
106 rec.as_deref().map(|r| r.as_ref()),
107 &body,
108 "/api/positions",
109 )?;
110 adapter::proto_request::<trd_get_position_list::Request, trd_get_position_list::Response>(
111 &state,
112 proto_id::TRD_GET_POSITION_LIST,
113 Some(body),
114 )
115 .await
116}
117
118pub async fn get_orders(
120 State(state): State<RestState>,
121 rec: Option<Extension<Arc<KeyRecord>>>,
122 Json(mut body): Json<Value>,
123) -> ApiResult {
124 normalize_and_resolve_card_num_for_route(&state, &rec, &mut body, "/api/orders")?;
125 validate_header_trd_market_write(&body, "/api/orders")?;
128 validate_header_trd_env_present(&body, "/api/orders")?;
130 read_handler_acc_id_check(
131 &state,
132 rec.as_deref().map(|r| r.as_ref()),
133 &body,
134 "/api/orders",
135 )?;
136 adapter::proto_request::<trd_get_order_list::Request, trd_get_order_list::Response>(
137 &state,
138 proto_id::TRD_GET_ORDER_LIST,
139 Some(body),
140 )
141 .await
142}
143
144pub async fn get_order_fills(
146 State(state): State<RestState>,
147 rec: Option<Extension<Arc<KeyRecord>>>,
148 Json(mut body): Json<Value>,
149) -> ApiResult {
150 normalize_and_resolve_card_num_for_route(&state, &rec, &mut body, "/api/order-fills")?;
151 validate_header_trd_market_write(&body, "/api/order-fills")?;
154 validate_header_trd_env_present(&body, "/api/order-fills")?;
156 read_handler_acc_id_check(
157 &state,
158 rec.as_deref().map(|r| r.as_ref()),
159 &body,
160 "/api/order-fills",
161 )?;
162 adapter::proto_request::<trd_get_order_fill_list::Request, trd_get_order_fill_list::Response>(
163 &state,
164 proto_id::TRD_GET_ORDER_FILL_LIST,
165 Some(body),
166 )
167 .await
168}
169
170pub async fn get_max_trd_qtys(
172 State(state): State<RestState>,
173 rec: Option<Extension<Arc<KeyRecord>>>,
174 Json(mut body): Json<Value>,
175) -> ApiResult {
176 normalize_and_resolve_card_num_for_route(&state, &rec, &mut body, "/api/max-trd-qtys")?;
177 validate_header_trd_market_write(&body, "/api/max-trd-qtys")?;
180 validate_header_trd_env_present(&body, "/api/max-trd-qtys")?;
182 read_handler_acc_id_check(
183 &state,
184 rec.as_deref().map(|r| r.as_ref()),
185 &body,
186 "/api/max-trd-qtys",
187 )?;
188 adapter::proto_request::<trd_get_max_trd_qtys::Request, trd_get_max_trd_qtys::Response>(
189 &state,
190 proto_id::TRD_GET_MAX_TRD_QTYS,
191 Some(body),
192 )
193 .await
194}
195
196pub async fn get_history_orders(
198 State(state): State<RestState>,
199 rec: Option<Extension<Arc<KeyRecord>>>,
200 Json(mut body): Json<Value>,
201) -> ApiResult {
202 normalize_and_resolve_card_num_for_route(&state, &rec, &mut body, "/api/history-orders")?;
203 read_handler_acc_id_check(
204 &state,
205 rec.as_deref().map(|r| r.as_ref()),
206 &body,
207 "/api/history-orders",
208 )?;
209 validate_header_trd_market(&body, "/api/history-orders")?;
212 validate_header_trd_env_present(&body, "/api/history-orders")?;
214 adapter::proto_request::<
215 trd_get_history_order_list::Request,
216 trd_get_history_order_list::Response,
217 >(&state, proto_id::TRD_GET_HISTORY_ORDER_LIST, Some(body))
218 .await
219}
220
221pub async fn get_history_order_fills(
223 State(state): State<RestState>,
224 rec: Option<Extension<Arc<KeyRecord>>>,
225 Json(mut body): Json<Value>,
226) -> ApiResult {
227 normalize_and_resolve_card_num_for_route(&state, &rec, &mut body, "/api/history-order-fills")?;
228 read_handler_acc_id_check(
229 &state,
230 rec.as_deref().map(|r| r.as_ref()),
231 &body,
232 "/api/history-order-fills",
233 )?;
234 validate_header_trd_market(&body, "/api/history-order-fills")?;
236 validate_header_trd_env_present(&body, "/api/history-order-fills")?;
238 adapter::proto_request::<
239 trd_get_history_order_fill_list::Request,
240 trd_get_history_order_fill_list::Response,
241 >(
242 &state,
243 proto_id::TRD_GET_HISTORY_ORDER_FILL_LIST,
244 Some(body),
245 )
246 .await
247}
248
249pub async fn get_margin_ratio(
251 State(state): State<RestState>,
252 rec: Option<Extension<Arc<KeyRecord>>>,
253 Json(mut body): Json<Value>,
254) -> ApiResult {
255 normalize_and_resolve_card_num_for_route(&state, &rec, &mut body, "/api/margin-ratio")?;
256 validate_header_trd_market_write(&body, "/api/margin-ratio")?;
259 validate_header_trd_env_present(&body, "/api/margin-ratio")?;
261 read_handler_acc_id_check(
262 &state,
263 rec.as_deref().map(|r| r.as_ref()),
264 &body,
265 "/api/margin-ratio",
266 )?;
267 adapter::proto_request::<trd_get_margin_ratio::Request, trd_get_margin_ratio::Response>(
268 &state,
269 proto_id::TRD_GET_MARGIN_RATIO,
270 Some(body),
271 )
272 .await
273}
274
275pub async fn get_order_fee(
277 State(state): State<RestState>,
278 rec: Option<Extension<Arc<KeyRecord>>>,
279 Json(mut body): Json<Value>,
280) -> ApiResult {
281 normalize_and_resolve_card_num_for_route(&state, &rec, &mut body, "/api/order-fee")?;
282 read_handler_acc_id_check(
283 &state,
284 rec.as_deref().map(|r| r.as_ref()),
285 &body,
286 "/api/order-fee",
287 )?;
288 validate_header_trd_market_write(&body, "/api/order-fee")?;
291 validate_header_trd_env_present(&body, "/api/order-fee")?;
293 adapter::proto_request::<trd_get_order_fee::Request, trd_get_order_fee::Response>(
294 &state,
295 proto_id::TRD_GET_ORDER_FEE,
296 Some(body),
297 )
298 .await
299}