Skip to main content

futu_rest/routes/trd/
algo_order.rs

1//! REST adapters for guarded v1.8 algorithm orders and execution logs.
2
3use std::sync::Arc;
4
5use axum::Json;
6use axum::extract::{Extension, State};
7use axum::http::StatusCode;
8use serde_json::Value;
9
10use futu_auth::{CheckCtx, KeyRecord};
11
12use crate::adapter::{self, RestState};
13
14use super::card_num::{
15    extract_and_resolve_card_num_into_acc_id, normalize_and_resolve_card_num_for_route,
16};
17use super::validation::{
18    authorize_trade_write, read_handler_acc_id_check, validate_header_trd_env_present,
19    validate_header_trd_market, validate_header_trd_market_write,
20};
21use super::write_pipeline::limit_reject_response;
22use super::{ApiResult, RawApiResult};
23
24fn invalid_context(endpoint: &'static str) -> (StatusCode, Json<Value>) {
25    (
26        StatusCode::BAD_REQUEST,
27        Json(serde_json::json!({
28            "ret_type": -1,
29            "ret_msg": "algorithm operation or decimal limit context is invalid",
30            "endpoint": endpoint,
31        })),
32    )
33}
34
35fn authorize_contexts(
36    state: &RestState,
37    record: &KeyRecord,
38    endpoint: &'static str,
39    operation: &'static str,
40    acc_id: u64,
41    idempotency_key: Option<&str>,
42    contexts: &[CheckCtx],
43) -> Result<(), (StatusCode, Json<Value>)> {
44    let receipt_operation = if contexts.iter().all(|ctx| ctx.mutation_no_exposure) {
45        format!("{operation}:transport-preflight")
46    } else {
47        operation.to_string()
48    };
49    let receipt_id = futu_auth::group_write_receipt_id(
50        &receipt_operation,
51        Some(&record.id),
52        acc_id,
53        idempotency_key.unwrap_or("confirmation"),
54    );
55    if let Err(outcome) = state.counters.check_batch_limits(
56        &record.id,
57        record,
58        contexts,
59        chrono::Utc::now(),
60        false,
61        &receipt_id,
62    ) {
63        let reason = outcome.reason().unwrap_or_else(|| "rejected".into());
64        return Err(limit_reject_response(
65            endpoint,
66            record,
67            &reason,
68            outcome.http_status_code(),
69        ));
70    }
71    Ok(())
72}
73
74pub async fn place_algo_order(
75    State(state): State<RestState>,
76    rec: Option<Extension<Arc<KeyRecord>>>,
77    Json(mut body): Json<Value>,
78) -> ApiResult {
79    const PATH: &str = "/api/trd/place-algo-order";
80    crate::adapter::normalize_json_keys_snake_case(&mut body);
81    authorize_trade_write(
82        &state,
83        rec.as_deref().map(|record| record.as_ref()),
84        &body,
85        PATH,
86    )?;
87    extract_and_resolve_card_num_into_acc_id(
88        &state,
89        rec.as_deref().map(|record| record.as_ref()),
90        &mut body,
91        PATH,
92    )?;
93    validate_header_trd_market_write(&body, PATH)?;
94    let request = adapter::decode_json_request::<futu_proto::trd_place_algo_order::Request>(
95        futu_core::proto_id::TRD_PLACE_ALGO_ORDER,
96        Some(body.clone()),
97        adapter::JsonRequestMode::GenericRest,
98    )?;
99    if let Some(Extension(record)) = rec.as_ref() {
100        let contexts = futu_auth_pipeline::body_aware::place_algo_check_contexts(&request.c2s)
101            .ok_or_else(|| invalid_context(PATH))?;
102        let idempotency_key = match request.c2s.operation.as_ref() {
103            Some(futu_proto::trd_place_algo_order::c2s::Operation::Initial(initial)) => {
104                Some(initial.idempotency_key.as_str())
105            }
106            _ => None,
107        };
108        authorize_contexts(
109            &state,
110            record,
111            PATH,
112            "PlaceAlgoOrder",
113            request.c2s.header.acc_id,
114            idempotency_key,
115            &contexts,
116        )?;
117    }
118    adapter::proto_request_with_idempotency_and_caller::<
119        futu_proto::trd_place_algo_order::Request,
120        futu_proto::trd_place_algo_order::Response,
121    >(
122        &state,
123        futu_core::proto_id::TRD_PLACE_ALGO_ORDER,
124        Some(body),
125        None,
126        rec.as_ref().map(|Extension(record)| record.id.clone()),
127    )
128    .await
129}
130
131pub async fn modify_algo_order(
132    State(state): State<RestState>,
133    rec: Option<Extension<Arc<KeyRecord>>>,
134    Json(mut body): Json<Value>,
135) -> ApiResult {
136    const PATH: &str = "/api/trd/modify-algo-order";
137    crate::adapter::normalize_json_keys_snake_case(&mut body);
138    authorize_trade_write(
139        &state,
140        rec.as_deref().map(|record| record.as_ref()),
141        &body,
142        PATH,
143    )?;
144    extract_and_resolve_card_num_into_acc_id(
145        &state,
146        rec.as_deref().map(|record| record.as_ref()),
147        &mut body,
148        PATH,
149    )?;
150    validate_header_trd_market_write(&body, PATH)?;
151    let request = adapter::decode_json_request::<futu_proto::trd_modify_algo_order::Request>(
152        futu_core::proto_id::TRD_MODIFY_ALGO_ORDER,
153        Some(body.clone()),
154        adapter::JsonRequestMode::GenericRest,
155    )?;
156    if let Some(Extension(record)) = rec.as_ref() {
157        let contexts = futu_auth_pipeline::body_aware::modify_algo_check_contexts(&request.c2s)
158            .ok_or_else(|| invalid_context(PATH))?;
159        let idempotency_key = match request.c2s.operation.as_ref() {
160            Some(futu_proto::trd_modify_algo_order::c2s::Operation::Initial(initial)) => {
161                Some(initial.idempotency_key.as_str())
162            }
163            _ => None,
164        };
165        authorize_contexts(
166            &state,
167            record,
168            PATH,
169            "ModifyAlgoOrder",
170            request.c2s.header.acc_id,
171            idempotency_key,
172            &contexts,
173        )?;
174    }
175    adapter::proto_request_with_idempotency_and_caller::<
176        futu_proto::trd_modify_algo_order::Request,
177        futu_proto::trd_modify_algo_order::Response,
178    >(
179        &state,
180        futu_core::proto_id::TRD_MODIFY_ALGO_ORDER,
181        Some(body),
182        None,
183        rec.as_ref().map(|Extension(record)| record.id.clone()),
184    )
185    .await
186}
187
188pub async fn algo_order_logs(
189    State(state): State<RestState>,
190    rec: Option<Extension<Arc<KeyRecord>>>,
191    Json(mut body): Json<Value>,
192) -> RawApiResult {
193    const PATH: &str = "/api/trd/algo-order-logs";
194    normalize_and_resolve_card_num_for_route(&state, &rec, &mut body, PATH)?;
195    validate_header_trd_market(&body, PATH)?;
196    validate_header_trd_env_present(&body, PATH)?;
197    read_handler_acc_id_check(
198        &state,
199        rec.as_deref().map(|record| record.as_ref()),
200        &body,
201        PATH,
202    )?;
203    adapter::proto_request_raw::<
204        futu_proto::trd_get_algo_order_logs::Request,
205        futu_proto::trd_get_algo_order_logs::Response,
206    >(
207        &state,
208        futu_core::proto_id::TRD_GET_ALGO_ORDER_LOGS,
209        Some(body),
210    )
211    .await
212}